[389-ds-base] branch 389-ds-base-1.3.8 updated: Issue 50538 - cleanAllRUV task limit is not enforced for replicated tasks
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.8
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.8 by this push:
new 78567d7 Issue 50538 - cleanAllRUV task limit is not enforced for replicated tasks
78567d7 is described below
commit 78567d7280d9d033c183d9339b9125733118f053
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Wed Aug 7 20:36:53 2019 -0400
Issue 50538 - cleanAllRUV task limit is not enforced for replicated tasks
Bug Description:
There is a hard limit of 64 concurrent cleanAllRUV tasks, but this limit is
only enforced when creating "new" tasks. It was not enforced when a task was
received via an extended operation. There were also race conditions in the
existing logic that allowed the array of cleaned rids to get corrupted . This
allowed for a very large number of task threads to be created.
Fix Description:
Maintain a new counter to keep track of the number of clean and abort threads
to make sure it never over runs the rid array buffers.
relates: https://pagure.io/389-ds-base/issue/50538
Reviewed by: lkrispenz(Thanks!)
---
.../tests/suites/replication/cleanallruv_test.py | 47 +++-
ldap/servers/plugins/replication/repl5.h | 7 +-
.../plugins/replication/repl5_replica_config.c | 247 +++++++++++----------
ldap/servers/plugins/replication/repl_extop.c | 19 +-
4 files changed, 202 insertions(+), 118 deletions(-)
diff --git a/dirsrvtests/tests/suites/replication/cleanallruv_test.py b/dirsrvtests/tests/suites/replication/cleanallruv_test.py
index 620a53e..43801dd 100644
--- a/dirsrvtests/tests/suites/replication/cleanallruv_test.py
+++ b/dirsrvtests/tests/suites/replication/cleanallruv_test.py
@@ -1,5 +1,5 @@
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2016 Red Hat, Inc.
+# Copyright (C) 2019 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
@@ -7,7 +7,6 @@
# --- END COPYRIGHT BLOCK ---
#
import threading
-
import pytest
from lib389.tasks import *
from lib389.utils import *
@@ -859,6 +858,50 @@ def test_multiple_tasks_with_force(topology_m4):
restore_master4(topology_m4)
+def test_max_tasks(topology_m4):
+ """Test we can not create more than 64 cleaning tasks
+
+ :id: c34d0b40-3c3e-4f53-8656-5e4c2a310a1f
+ :setup: Replication setup with four masters
+ :steps:
+ 1. Stop masters 3 & 4
+ 2. Create over 64 tasks between m1 and m2
+ 3. Check logs to see if (>65) tasks were rejected
+
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ """
+
+ # Stop masters 3 & 4
+ m1 = topology_m4.ms["master1"]
+ m2 = topology_m4.ms["master2"]
+ m3 = topology_m4.ms["master3"]
+ m4 = topology_m4.ms["master4"]
+ m3.stop()
+ m4.stop()
+
+ # Add over 64 tasks between master1 & 2 to try to exceed the 64 task limit
+ for i in range(1, 64):
+ cruv_task = CleanAllRUVTask(m1)
+ cruv_task.create(properties={
+ 'replica-id': str(i),
+ 'replica-base-dn': DEFAULT_SUFFIX,
+ 'replica-force-cleaning': 'no', # This forces these tasks to stick around
+ })
+ cruv_task = CleanAllRUVTask(m2)
+ cruv_task.create(properties={
+ 'replica-id': "10" + str(i),
+ 'replica-base-dn': DEFAULT_SUFFIX,
+ 'replica-force-cleaning': 'yes', # This allows the tasks to propagate
+ })
+
+ # Check the errors log for our error message in master 1
+ assert m1.searchErrorsLog('Exceeded maximum number of active CLEANALLRUV tasks')
+>>>>>>> ab24aa4cb... Issue 50538 - cleanAllRUV task limit is not enforced for replicated tasks
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
index e08fec7..d414926 100644
--- a/ldap/servers/plugins/replication/repl5.h
+++ b/ldap/servers/plugins/replication/repl5.h
@@ -80,6 +80,8 @@
#define CLEANRUV_FINISHED "finished"
#define CLEANRUV_CLEANING "cleaning"
#define CLEANRUV_NO_MAXCSN "no maxcsn"
+#define CLEANALLRUV_ID "CleanAllRUV Task"
+#define ABORT_CLEANALLRUV_ID "Abort CleanAllRUV Task"
/* DS 5.0 replication protocol error codes */
#define NSDS50_REPL_REPLICA_READY 0x00 /* Replica ready, go ahead */
@@ -784,6 +786,7 @@ void multimaster_mtnode_construct_replicas(void);
void multimaster_be_state_change(void *handle, char *be_name, int old_be_state, int new_be_state);
#define CLEANRIDSIZ 64 /* maximum number for concurrent CLEANALLRUV tasks */
+#define CLEANRID_BUFSIZ 128
typedef struct _cleanruv_data
{
@@ -815,6 +818,8 @@ int get_replica_type(Replica *r);
int replica_execute_cleanruv_task_ext(Object *r, ReplicaId rid);
void add_cleaned_rid(cleanruv_data *data, char *maxcsn);
int is_cleaned_rid(ReplicaId rid);
+int32_t check_and_set_cleanruv_task_count(ReplicaId rid);
+int32_t check_and_set_abort_cleanruv_task_count(void);
int replica_cleanall_ruv_abort(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg);
void replica_cleanallruv_thread_ext(void *arg);
void stop_ruv_cleaning(void);
@@ -833,8 +838,6 @@ void set_cleaned_rid(ReplicaId rid);
void cleanruv_log(Slapi_Task *task, int rid, char *task_type, int sev_level, char *fmt, ...);
char *replica_cleanallruv_get_local_maxcsn(ReplicaId rid, char *base_dn);
-
-
/* replutil.c */
LDAPControl *create_managedsait_control(void);
LDAPControl *create_backend_control(Slapi_DN *sdn);
diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c
index b4aff9e..0ba2cd9 100644
--- a/ldap/servers/plugins/replication/repl5_replica_config.c
+++ b/ldap/servers/plugins/replication/repl5_replica_config.c
@@ -30,17 +30,18 @@
#define CLEANALLRUV "CLEANALLRUV"
#define CLEANALLRUVLEN 11
#define REPLICA_RDN "cn=replica"
-#define CLEANALLRUV_ID "CleanAllRUV Task"
-#define ABORT_CLEANALLRUV_ID "Abort CleanAllRUV Task"
int slapi_log_urp = SLAPI_LOG_REPL;
-static ReplicaId cleaned_rids[CLEANRIDSIZ + 1] = {0};
-static ReplicaId pre_cleaned_rids[CLEANRIDSIZ + 1] = {0};
-static ReplicaId aborted_rids[CLEANRIDSIZ + 1] = {0};
-static Slapi_RWLock *rid_lock = NULL;
-static Slapi_RWLock *abort_rid_lock = NULL;
+static ReplicaId cleaned_rids[CLEANRID_BUFSIZ] = {0};
+static ReplicaId pre_cleaned_rids[CLEANRID_BUFSIZ] = {0};
+static ReplicaId aborted_rids[CLEANRID_BUFSIZ] = {0};
+static PRLock *rid_lock = NULL;
+static PRLock *abort_rid_lock = NULL;
static PRLock *notify_lock = NULL;
static PRCondVar *notify_cvar = NULL;
+static PRLock *task_count_lock = NULL;
+static int32_t clean_task_count = 0;
+static int32_t abort_task_count = 0;
/* Forward Declartions */
static int replica_config_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter, int *returncode, char *returntext, void *arg);
@@ -67,8 +68,6 @@ static int replica_cleanallruv_send_abort_extop(Repl_Agmt *ra, Slapi_Task *task,
static int replica_cleanallruv_check_maxcsn(Repl_Agmt *agmt, char *basedn, char *rid_text, char *maxcsn, Slapi_Task *task);
static int replica_cleanallruv_replica_alive(Repl_Agmt *agmt);
static int replica_cleanallruv_check_ruv(char *repl_root, Repl_Agmt *ra, char *rid_text, Slapi_Task *task, char *force);
-static int get_cleanruv_task_count(void);
-static int get_abort_cleanruv_task_count(void);
static int replica_cleanup_task(Object *r, const char *task_name, char *returntext, int apply_mods);
static int replica_task_done(Replica *replica);
static void delete_cleaned_rid_config(cleanruv_data *data);
@@ -114,20 +113,27 @@ replica_config_init()
PR_GetError());
return -1;
}
- rid_lock = slapi_new_rwlock();
+ rid_lock = PR_NewLock();
if (rid_lock == NULL) {
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_config_init - "
"Failed to create rid_lock; NSPR error - %d\n",
PR_GetError());
return -1;
}
- abort_rid_lock = slapi_new_rwlock();
+ abort_rid_lock = PR_NewLock();
if (abort_rid_lock == NULL) {
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_config_init - "
"Failed to create abort_rid_lock; NSPR error - %d\n",
PR_GetError());
return -1;
}
+ task_count_lock = PR_NewLock();
+ if (task_count_lock == NULL) {
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_config_init - "
+ "Failed to create task_count_lock; NSPR error - %d\n",
+ PR_GetError());
+ return -1;
+ }
if ((notify_lock = PR_NewLock()) == NULL) {
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_config_init - "
"Failed to create notify lock; NSPR error - %d\n",
@@ -1484,12 +1490,6 @@ replica_execute_cleanall_ruv_task(Object *r, ReplicaId rid, Slapi_Task *task, co
cleanruv_log(pre_task, rid, CLEANALLRUV_ID, SLAPI_LOG_INFO, "Initiating CleanAllRUV Task...");
- if (get_cleanruv_task_count() >= CLEANRIDSIZ) {
- /* we are already running the maximum number of tasks */
- cleanruv_log(pre_task, rid, CLEANALLRUV_ID, SLAPI_LOG_ERR,
- "Exceeded maximum number of active CLEANALLRUV tasks(%d)", CLEANRIDSIZ);
- return LDAP_UNWILLING_TO_PERFORM;
- }
/*
* Grab the replica
*/
@@ -1541,6 +1541,13 @@ replica_execute_cleanall_ruv_task(Object *r, ReplicaId rid, Slapi_Task *task, co
goto fail;
}
+ if (check_and_set_cleanruv_task_count(rid) != LDAP_SUCCESS) {
+ cleanruv_log(NULL, rid, CLEANALLRUV_ID, SLAPI_LOG_ERR,
+ "Exceeded maximum number of active CLEANALLRUV tasks(%d)", CLEANRIDSIZ);
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto fail;
+ }
+
/*
* Launch the cleanallruv thread. Once all the replicas are cleaned it will release the rid
*/
@@ -1548,6 +1555,9 @@ replica_execute_cleanall_ruv_task(Object *r, ReplicaId rid, Slapi_Task *task, co
if (data == NULL) {
cleanruv_log(pre_task, rid, CLEANALLRUV_ID, SLAPI_LOG_ERR, "Failed to allocate cleanruv_data. Aborting task.");
rc = -1;
+ PR_Lock(task_count_lock);
+ clean_task_count--;
+ PR_Unlock(task_count_lock);
goto fail;
}
data->repl_obj = r;
@@ -1630,13 +1640,13 @@ replica_cleanallruv_thread(void *arg)
int aborted = 0;
int rc = 0;
- if (!data || slapi_is_shutting_down()) {
- return; /* no data */
- }
-
/* Increase active thread count to prevent a race condition at server shutdown */
g_incr_active_threadcnt();
+ if (!data || slapi_is_shutting_down()) {
+ goto done;
+ }
+
if (data->task) {
slapi_task_inc_refcount(data->task);
slapi_log_err(SLAPI_LOG_PLUGIN, repl_plugin_name,
@@ -1683,16 +1693,13 @@ replica_cleanallruv_thread(void *arg)
slapi_task_begin(data->task, 1);
}
/*
- * Presetting the rid prevents duplicate thread creation, but allows the db and changelog to still
- * process updates from the rid.
- * set_cleaned_rid() blocks updates, so we don't want to do that... yet unless we are in force mode.
- * If we are forcing a clean independent of state of other servers for this RID we can set_cleaned_rid()
+ * We have already preset this rid, but if we are forcing a clean independent of state
+ * of other servers for this RID we can set_cleaned_rid()
*/
if (data->force) {
set_cleaned_rid(data->rid);
- } else {
- preset_cleaned_rid(data->rid);
}
+
rid_text = slapi_ch_smprintf("%d", data->rid);
csn_as_string(data->maxcsn, PR_FALSE, csnstr);
/*
@@ -1862,6 +1869,9 @@ done:
/*
* If the replicas are cleaned, release the rid
*/
+ if (slapi_is_shutting_down()) {
+ stop_ruv_cleaning();
+ }
if (!aborted && !slapi_is_shutting_down()) {
/*
* Success - the rid has been cleaned!
@@ -1880,10 +1890,9 @@ done:
} else {
cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO, "Propagated task does not delete Keep alive entry (%d).", data->rid);
}
-
clean_agmts(data);
remove_cleaned_rid(data->rid);
- cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO, "Successfully cleaned rid(%d).", data->rid);
+ cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO, "Successfully cleaned rid(%d)", data->rid);
} else {
/*
* Shutdown or abort
@@ -1916,6 +1925,10 @@ done:
slapi_ch_free_string(&data->force);
slapi_ch_free_string(&rid_text);
slapi_ch_free((void **)&data);
+ /* decrement task count */
+ PR_Lock(task_count_lock);
+ clean_task_count--;
+ PR_Unlock(task_count_lock);
g_decr_active_threadcnt();
}
@@ -2415,16 +2428,14 @@ replica_send_cleanruv_task(Repl_Agmt *agmt, cleanruv_data *clean_data)
int
is_cleaned_rid(ReplicaId rid)
{
- int i;
-
- slapi_rwlock_rdlock(rid_lock);
- for (i = 0; i < CLEANRIDSIZ && cleaned_rids[i] != 0; i++) {
+ PR_Lock(rid_lock);
+ for (size_t i = 0; i < CLEANRID_BUFSIZ; i++) {
if (rid == cleaned_rids[i]) {
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(rid_lock);
return 1;
}
}
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(rid_lock);
return 0;
}
@@ -2432,16 +2443,14 @@ is_cleaned_rid(ReplicaId rid)
int
is_pre_cleaned_rid(ReplicaId rid)
{
- int i;
-
- slapi_rwlock_rdlock(rid_lock);
- for (i = 0; i < CLEANRIDSIZ && pre_cleaned_rids[i] != 0; i++) {
+ PR_Lock(rid_lock);
+ for (size_t i = 0; i < CLEANRID_BUFSIZ; i++) {
if (rid == pre_cleaned_rids[i]) {
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(rid_lock);
return 1;
}
}
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(rid_lock);
return 0;
}
@@ -2454,14 +2463,14 @@ is_task_aborted(ReplicaId rid)
if (rid == 0) {
return 0;
}
- slapi_rwlock_rdlock(abort_rid_lock);
- for (i = 0; i < CLEANRIDSIZ && aborted_rids[i] != 0; i++) {
+ PR_Lock(abort_rid_lock);
+ for (i = 0; i < CLEANRID_BUFSIZ && aborted_rids[i] != 0; i++) {
if (rid == aborted_rids[i]) {
- slapi_rwlock_unlock(abort_rid_lock);
+ PR_Unlock(abort_rid_lock);
return 1;
}
}
- slapi_rwlock_unlock(abort_rid_lock);
+ PR_Unlock(abort_rid_lock);
return 0;
}
@@ -2470,15 +2479,14 @@ preset_cleaned_rid(ReplicaId rid)
{
int i;
- slapi_rwlock_wrlock(rid_lock);
- for (i = 0; i < CLEANRIDSIZ; i++) {
+ PR_Lock(rid_lock);
+ for (i = 0; i < CLEANRID_BUFSIZ && pre_cleaned_rids[i] != rid; i++) {
if (pre_cleaned_rids[i] == 0) {
pre_cleaned_rids[i] = rid;
- pre_cleaned_rids[i + 1] = 0;
break;
}
}
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(rid_lock);
}
/*
@@ -2491,14 +2499,13 @@ set_cleaned_rid(ReplicaId rid)
{
int i;
- slapi_rwlock_wrlock(rid_lock);
- for (i = 0; i < CLEANRIDSIZ; i++) {
+ PR_Lock(rid_lock);
+ for (i = 0; i < CLEANRID_BUFSIZ && cleaned_rids[i] != rid; i++) {
if (cleaned_rids[i] == 0) {
cleaned_rids[i] = rid;
- cleaned_rids[i + 1] = 0;
}
}
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(rid_lock);
}
/*
@@ -2570,15 +2577,14 @@ add_aborted_rid(ReplicaId rid, Replica *r, char *repl_root)
int rc;
int i;
- slapi_rwlock_wrlock(abort_rid_lock);
- for (i = 0; i < CLEANRIDSIZ; i++) {
+ PR_Lock(abort_rid_lock);
+ for (i = 0; i < CLEANRID_BUFSIZ; i++) {
if (aborted_rids[i] == 0) {
aborted_rids[i] = rid;
- aborted_rids[i + 1] = 0;
break;
}
}
- slapi_rwlock_unlock(abort_rid_lock);
+ PR_Unlock(abort_rid_lock);
/*
* Write the rid to the config entry
*/
@@ -2621,21 +2627,24 @@ delete_aborted_rid(Replica *r, ReplicaId rid, char *repl_root, int skip)
char *data;
char *dn;
int rc;
- int i;
if (r == NULL)
return;
if (skip) {
/* skip the deleting of the config, and just remove the in memory rid */
- slapi_rwlock_wrlock(abort_rid_lock);
- for (i = 0; i < CLEANRIDSIZ && aborted_rids[i] != rid; i++)
- ; /* found rid, stop */
- for (; i < CLEANRIDSIZ; i++) {
- /* rewrite entire array */
- aborted_rids[i] = aborted_rids[i + 1];
- }
- slapi_rwlock_unlock(abort_rid_lock);
+ ReplicaId new_abort_rids[CLEANRID_BUFSIZ] = {0};
+ int32_t idx = 0;
+
+ PR_Lock(abort_rid_lock);
+ for (size_t i = 0; i < CLEANRID_BUFSIZ; i++) {
+ if (aborted_rids[i] != rid) {
+ new_abort_rids[idx] = aborted_rids[i];
+ idx++;
+ }
+ }
+ memcpy(aborted_rids, new_abort_rids, sizeof(new_abort_rids));
+ PR_Unlock(abort_rid_lock);
} else {
/* only remove the config, leave the in-memory rid */
dn = replica_get_dn(r);
@@ -2793,27 +2802,31 @@ bail:
void
remove_cleaned_rid(ReplicaId rid)
{
- int i;
- /*
- * Remove this rid, and optimize the array
- */
- slapi_rwlock_wrlock(rid_lock);
+ ReplicaId new_cleaned_rids[CLEANRID_BUFSIZ] = {0};
+ ReplicaId new_pre_cleaned_rids[CLEANRID_BUFSIZ] = {0};
+ size_t idx = 0;
+
+ PR_Lock(rid_lock);
- for (i = 0; i < CLEANRIDSIZ && cleaned_rids[i] != rid; i++)
- ; /* found rid, stop */
- for (; i < CLEANRIDSIZ; i++) {
- /* rewrite entire array */
- cleaned_rids[i] = cleaned_rids[i + 1];
+ for (size_t i = 0; i < CLEANRID_BUFSIZ; i++) {
+ if (cleaned_rids[i] != rid) {
+ new_cleaned_rids[idx] = cleaned_rids[i];
+ idx++;
+ }
}
+ memcpy(cleaned_rids, new_cleaned_rids, sizeof(new_cleaned_rids));
+
/* now do the preset cleaned rids */
- for (i = 0; i < CLEANRIDSIZ && pre_cleaned_rids[i] != rid; i++)
- ; /* found rid, stop */
- for (; i < CLEANRIDSIZ; i++) {
- /* rewrite entire array */
- pre_cleaned_rids[i] = pre_cleaned_rids[i + 1];
+ idx = 0;
+ for (size_t i = 0; i < CLEANRID_BUFSIZ; i++) {
+ if (pre_cleaned_rids[i] != rid) {
+ new_pre_cleaned_rids[idx] = pre_cleaned_rids[i];
+ idx++;
+ }
}
+ memcpy(pre_cleaned_rids, new_pre_cleaned_rids, sizeof(new_pre_cleaned_rids));
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(rid_lock);
}
/*
@@ -2841,16 +2854,6 @@ replica_cleanall_ruv_abort(Slapi_PBlock *pb __attribute__((unused)),
char *ridstr = NULL;
int rc = SLAPI_DSE_CALLBACK_OK;
- if (get_abort_cleanruv_task_count() >= CLEANRIDSIZ) {
- /* we are already running the maximum number of tasks */
- PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
- "Exceeded maximum number of active ABORT CLEANALLRUV tasks(%d)",
- CLEANRIDSIZ);
- cleanruv_log(task, -1, ABORT_CLEANALLRUV_ID, SLAPI_LOG_ERR, "%s", returntext);
- *returncode = LDAP_OPERATIONS_ERROR;
- return SLAPI_DSE_CALLBACK_ERROR;
- }
-
/* allocate new task now */
task = slapi_new_task(slapi_entry_get_ndn(e));
@@ -2935,6 +2938,16 @@ replica_cleanall_ruv_abort(Slapi_PBlock *pb __attribute__((unused)),
*/
certify_all = "no";
}
+
+ if (check_and_set_abort_cleanruv_task_count() != LDAP_SUCCESS) {
+ /* we are already running the maximum number of tasks */
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Exceeded maximum number of active ABORT CLEANALLRUV tasks(%d)",
+ CLEANRIDSIZ);
+ cleanruv_log(task, -1, ABORT_CLEANALLRUV_ID, SLAPI_LOG_ERR, "%s", returntext);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ goto out;
+ }
/*
* Create payload
*/
@@ -3143,6 +3156,9 @@ done:
slapi_ch_free_string(&data->certify);
slapi_sdn_free(&data->sdn);
slapi_ch_free((void **)&data);
+ PR_Lock(task_count_lock);
+ abort_task_count--;
+ PR_Unlock(task_count_lock);
g_decr_active_threadcnt();
}
@@ -3494,36 +3510,43 @@ replica_cleanallruv_check_ruv(char *repl_root, Repl_Agmt *agmt, char *rid_text,
return rc;
}
-static int
-get_cleanruv_task_count(void)
+/*
+ * Before starting a cleanAllRUV task make sure there are not
+ * too many task threads already running. If everything is okay
+ * also pre-set the RID now so rebounding extended ops do not
+ * try to clean it over and over.
+ */
+int32_t
+check_and_set_cleanruv_task_count(ReplicaId rid)
{
- int i, count = 0;
+ int32_t rc = 0;
- slapi_rwlock_wrlock(rid_lock);
- for (i = 0; i < CLEANRIDSIZ; i++) {
- if (pre_cleaned_rids[i] != 0) {
- count++;
- }
+ PR_Lock(task_count_lock);
+ if (clean_task_count >= CLEANRIDSIZ) {
+ rc = -1;
+ } else {
+ clean_task_count++;
+ preset_cleaned_rid(rid);
}
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(task_count_lock);
- return count;
+ return rc;
}
-static int
-get_abort_cleanruv_task_count(void)
+int32_t
+check_and_set_abort_cleanruv_task_count(void)
{
- int i, count = 0;
+ int32_t rc = 0;
- slapi_rwlock_wrlock(rid_lock);
- for (i = 0; i < CLEANRIDSIZ; i++) {
- if (aborted_rids[i] != 0) {
- count++;
+ PR_Lock(task_count_lock);
+ if (abort_task_count > CLEANRIDSIZ) {
+ rc = -1;
+ } else {
+ abort_task_count++;
}
- }
- slapi_rwlock_unlock(rid_lock);
+ PR_Unlock(task_count_lock);
- return count;
+ return rc;
}
/*
diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c
index 68e2544..0c2abb6 100644
--- a/ldap/servers/plugins/replication/repl_extop.c
+++ b/ldap/servers/plugins/replication/repl_extop.c
@@ -1393,6 +1393,12 @@ multimaster_extop_abort_cleanruv(Slapi_PBlock *pb)
rc = LDAP_OPERATIONS_ERROR;
goto out;
}
+ if (check_and_set_abort_cleanruv_task_count() != LDAP_SUCCESS) {
+ cleanruv_log(NULL, rid, CLEANALLRUV_ID, SLAPI_LOG_ERR,
+ "Exceeded maximum number of active abort CLEANALLRUV tasks(%d)", CLEANRIDSIZ);
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto out;
+ }
/*
* Prepare the abort data
*/
@@ -1499,6 +1505,7 @@ multimaster_extop_cleanruv(Slapi_PBlock *pb)
if (force == NULL) {
force = "no";
}
+
maxcsn = csn_new();
csn_init_by_string(maxcsn, csnstr);
/*
@@ -1535,13 +1542,21 @@ multimaster_extop_cleanruv(Slapi_PBlock *pb)
goto free_and_return;
}
+ if (check_and_set_cleanruv_task_count((ReplicaId)rid) != LDAP_SUCCESS) {
+ cleanruv_log(NULL, rid, CLEANALLRUV_ID, SLAPI_LOG_ERR,
+ "Exceeded maximum number of active CLEANALLRUV tasks(%d)", CLEANRIDSIZ);
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto free_and_return;
+ }
+
if (replica_get_type(r) != REPLICA_TYPE_READONLY) {
/*
* Launch the cleanruv monitoring thread. Once all the replicas are cleaned it will release the rid
*
* This will also release mtnode_ext->replica
*/
- slapi_log_err(SLAPI_LOG_INFO, repl_plugin_name, "multimaster_extop_cleanruv - CleanAllRUV Task - Launching cleanAllRUV thread...\n");
+
+ cleanruv_log(NULL, rid, CLEANALLRUV_ID, SLAPI_LOG_ERR, "Launching cleanAllRUV thread...\n");
data = (cleanruv_data *)slapi_ch_calloc(1, sizeof(cleanruv_data));
if (data == NULL) {
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "multimaster_extop_cleanruv - CleanAllRUV Task - Failed to allocate "
@@ -1635,7 +1650,7 @@ free_and_return:
ber_printf(resp_bere, "{s}", CLEANRUV_ACCEPTED);
ber_flatten(resp_bere, &resp_bval);
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval);
- slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
+ slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
/* resp_bere */
if (NULL != resp_bere) {
ber_free(resp_bere, 1);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] branch 389-ds-base-1.4.1 updated: Issue 50592 - Port Replication Tab to ReactJS
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 a380325 Issue 50592 - Port Replication Tab to ReactJS
a380325 is described below
commit a3803254628e804775a8a4df5cc7193b22f46d50
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Sep 23 11:49:03 2019 -0400
Issue 50592 - Port Replication Tab to ReactJS
Description: Ported the replication tab to React. Made many
other improvements throughout the UI:
- Protected "Treeviews" by disable/enable as components are relaoded
- Add a new Double Confirmation Modal/Popup
- Added a script (buildAndWatch.sh) for faster/more convenient developing
- Added a new RUV fucntion for the CLI, and made other lib389 improvements:
- Added support for not only "dc" suffixes, but also "o", "ou", and "cn"
relates: https://pagure.io/389-ds-base/issue/50592
Reviewed by: spichugi(Thanks!)
---
rpm/389-ds-base.spec.in | 2 +
src/cockpit/389-console/buildAndRun.sh | 45 +
src/cockpit/389-console/src/css/ds.css | 96 +-
src/cockpit/389-console/src/database.jsx | 71 +-
src/cockpit/389-console/src/ds.js | 44 +-
src/cockpit/389-console/src/index.es6 | 7 +
src/cockpit/389-console/src/index.html | 28 +-
.../src/lib/database/attrEncryption.jsx | 3 +-
.../389-console/src/lib/database/backups.jsx | 101 +-
.../389-console/src/lib/database/chaining.jsx | 592 ++++--
.../src/lib/database/databaseConfig.jsx | 52 +-
.../389-console/src/lib/database/databaseModal.jsx | 25 +-
.../389-console/src/lib/database/indexes.jsx | 2 -
.../389-console/src/lib/database/referrals.jsx | 20 +-
.../389-console/src/lib/database/suffix.jsx | 96 +-
.../389-console/src/lib/database/suffixConfig.jsx | 79 +-
.../389-console/src/lib/database/vlvIndexes.jsx | 15 +-
.../389-console/src/lib/monitor/accesslog.jsx | 11 +-
.../389-console/src/lib/monitor/auditfaillog.jsx | 15 +-
.../389-console/src/lib/monitor/auditlog.jsx | 15 +-
.../src/lib/monitor/chainingMonitor.jsx | 15 +-
.../389-console/src/lib/monitor/dbMonitor.jsx | 124 +-
.../389-console/src/lib/monitor/errorlog.jsx | 14 +-
.../389-console/src/lib/monitor/monitorModals.jsx | 15 +-
.../389-console/src/lib/monitor/replMonitor.jsx | 10 +-
.../389-console/src/lib/monitor/serverMonitor.jsx | 21 +-
.../389-console/src/lib/monitor/snmpMonitor.jsx | 119 +-
.../389-console/src/lib/monitor/suffixMonitor.jsx | 109 +-
src/cockpit/389-console/src/lib/notifications.jsx | 123 ++
.../src/lib/plugins/pluginBasicConfig.jsx | 5 +-
.../389-console/src/lib/replication/replAgmts.jsx | 1150 ++++++++++
.../src/lib/replication/replChangelog.jsx | 395 ++++
.../389-console/src/lib/replication/replConfig.jsx | 640 ++++++
.../389-console/src/lib/replication/replModals.jsx | 1684 +++++++++++++++
.../389-console/src/lib/replication/replSuffix.jsx | 456 ++++
.../389-console/src/lib/replication/replTables.jsx | 608 ++++++
.../389-console/src/lib/replication/replTasks.jsx | 314 +++
.../src/lib/replication/winsyncAgmts.jsx | 1210 +++++++++++
.../src/lib/security/certificateManagement.jsx | 53 +-
src/cockpit/389-console/src/lib/tools.jsx | 9 +-
src/cockpit/389-console/src/monitor.jsx | 69 +-
src/cockpit/389-console/src/replication.html | 724 -------
src/cockpit/389-console/src/replication.js | 2207 --------------------
src/cockpit/389-console/src/replication.jsx | 1148 ++++++++++
src/cockpit/389-console/src/security.jsx | 2 +-
src/cockpit/389-console/src/servers.html | 4 +-
src/cockpit/389-console/webpack.config.js | 2 -
src/lib389/lib389/agreement.py | 10 +-
src/lib389/lib389/backend.py | 2 +-
src/lib389/lib389/chaining.py | 2 +-
src/lib389/lib389/cli_conf/backend.py | 36 +-
src/lib389/lib389/cli_conf/monitor.py | 4 +-
src/lib389/lib389/cli_conf/replication.py | 28 +-
.../lib389/configurations/config_001004000.py | 35 +-
src/lib389/lib389/replica.py | 47 +-
55 files changed, 8996 insertions(+), 3717 deletions(-)
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index c73c554..ffba3ef 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -246,6 +246,8 @@ Requires: perl-NetAddr-IP
Requires: perl-Mozilla-LDAP
# for setup-ds.pl
Requires: bind-utils
+%global __provides_exclude_from %{_libdir}/%{pkgname}/perl
+%global __requires_exclude perl\\((DSCreate|DSMigration|DSUpdate|DSUtil|Dialog|DialogManager|FileConn|Inf|Migration|Resource|Setup|SetupLog)
%{?perl_default_filter}
%endif
# End use perl
diff --git a/src/cockpit/389-console/buildAndRun.sh b/src/cockpit/389-console/buildAndRun.sh
new file mode 100755
index 0000000..8021003
--- /dev/null
+++ b/src/cockpit/389-console/buildAndRun.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Do a fresh build of the UI, and run it. While in this state all updates made
+# are built immediately. Just refresh the browser to test them.
+
+AUDIT=0
+while (( "$#" )); do
+ case "$1" in
+ -a|--audit)
+ AUDIT=1
+ break
+ ;;
+ -h|--help)
+ echo Usage:
+ echo This is a development script to quickly refresh the UI and watch it live
+ echo Options:
+ echo -a|--audit Audit the build
+ exit 0
+ ;;
+ -*|--*=)
+ echo "Error: Unsupported argument $1" >&2
+ echo "Available Options:" >&2
+ echo " -a|--audit Audit the build" >&2
+ exit 1
+ ;;
+ esac
+done
+
+printf "\nCleaning and installing npm packages ...\n\n"
+make -f node_modules.mk clean > /dev/null
+make -f node_modules.mk install > /dev/null
+if [ $? != 0 ]; then
+ exit 1
+fi
+
+if [ $AUDIT == 1 ]; then
+ printf "\nAuditing npm packages ...\n\n"
+ make -f node_modules.mk build-cockpit-plugin
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+fi
+
+printf "\nBuilding and watching ...\n"
+node_modules/webpack/bin/webpack.js --watch
diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css
index f5b1e4f..fdc79e7 100644
--- a/src/cockpit/389-console/src/css/ds.css
+++ b/src/cockpit/389-console/src/css/ds.css
@@ -19,6 +19,10 @@
vertical-align: top !important;
}
+.ds-left-align {
+ right: 0 !important;
+}
+
/* Main nav page index.html */
.ds-content {
padding: 0;
@@ -116,6 +120,10 @@ td {
width: 525px;
}
+.ds-no-padding {
+ padding: 0px !important;
+}
+
.ds-tree {
font-size: 0.5;
background-color: #f8f8f8;
@@ -131,6 +139,11 @@ td {
padding-top: 5px;
}
+.ds-disabled {
+ pointer-events: none;
+ opacity: 0.7;
+}
+
.ds-split {
width: 400px;
}
@@ -142,12 +155,12 @@ td {
}
.ds-input-bad {
- border-color: red;
+ border-color: red !important;
}
.ds-input-auto-bad {
width: 100%;
- border-color: red;
+ border-color: red !important;
padding-left: 5px;
}
@@ -238,20 +251,6 @@ td {
overflow: auto;
}
-.ds-agmt-wiz-dropdown {
- width: 175px !important;
- text-align: left;
-}
-
-.ds-agmt-wiz-dropdown a {
- padding: 0px !important;
- padding-left: 10px !important;
- line-height: 0 !important;
- height: 40px;
- width: 175px !important;
- text-align: left;
-}
-
.ds-dblink-dropdown {
width: 140px !important;
text-align: left;
@@ -594,6 +593,17 @@ option {
float: left;
}
+.ds-inline-btn {
+ margin-top: -3px;
+ margin-left: 10px;
+ display: inline-block;
+}
+
+.spinner-sm {
+ height: 18px !important;
+ Width: 18px !important;
+}
+
.ds-footer {
background-color: #f5f5f5 !important;
margin-left: -25px;
@@ -635,7 +645,7 @@ option {
border: 0;
position: relative;
overflow: hidden;
- width: 700px;
+ width: 100%;
text-align: left;
font: inherit;
}
@@ -646,7 +656,7 @@ option {
height: 1px;
background: #228bc0;
position: absolute;
- width: 700px;
+ width: 100%;
top: 50% !important;
margin-left: 10px;
text-align: left;
@@ -671,7 +681,7 @@ option {
}
.ds-margin-top {
- margin-top: 10px;
+ margin-top: 10px !important;
}
.ds-margin-top-med {
@@ -686,6 +696,10 @@ option {
margin-top: 35px !important;
}
+.ds-margin-top-xxlg {
+ margin-top: 45px !important;
+}
+
.ds-modal-spinner {
margin-top: 10px;
text-align: center;
@@ -736,6 +750,10 @@ option {
margin-left: 15px !important;
}
+.ds-left-indent-md {
+ margin-left: 30px !important;
+}
+
.ds-right-indent {
margin-right: 15px !important;
}
@@ -798,6 +816,10 @@ option {
color: red;
}
+.ds-clear-text {
+ opacity: 0;
+}
+
.ds-margin-left-sm {
margin-left: 30px !important;
}
@@ -807,7 +829,7 @@ option {
}
.ds-margin-left-piechart {
- margin-left: 130px !important;
+ margin-left: 140px !important;
}
.ds-nested-modal {
@@ -839,6 +861,13 @@ option {
transform: translate(-25%, -50%);
}
+.ds-loading-spinner-tree {
+ position: fixed;
+ top: 40%;
+ left: 40%;
+ transform: translate(-40%, -40%);
+}
+
.ds-loading {
position: fixed;
top: 25%;
@@ -870,6 +899,10 @@ option {
margin-left: 80px;
}
+.ds-h4 {
+ font-size: 18px !important;
+}
+
@media screen and (max-width: 1300px) {
.ds-plugin-spinner {
margin-left: 0px;
@@ -955,12 +988,12 @@ option {
.ds-word-wrap {
word-wrap: break-word;
- padding-top: 3px;
}
.ds-suffix-header {
- font-size: 16px;
+ font-size: 18px;
margin-bottom: 15px;
+ padding-top: 5px;
}
.ds-header {
@@ -1018,3 +1051,22 @@ input {
th {
text-align: center;
}
+
+td {
+ text-align: center;
+}
+
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+h3 {
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+h4 {
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
index 36b38be..b1c3953 100644
--- a/src/cockpit/389-console/src/database.jsx
+++ b/src/cockpit/389-console/src/database.jsx
@@ -48,6 +48,7 @@ export class Database extends React.Component {
createSuffixEntry: false,
createSampleEntries: false,
noSuffixInit: true,
+ disableTree: false,
// DB config
globalDBConfig: {},
@@ -83,7 +84,6 @@ export class Database extends React.Component {
// Suffix
this.showSuffixModal = this.showSuffixModal.bind(this);
this.closeSuffixModal = this.closeSuffixModal.bind(this);
- this.handleChange = this.handleChange.bind(this);
this.createSuffix = this.createSuffix.bind(this);
this.loadSuffix = this.loadSuffix.bind(this);
this.loadSuffixConfig = this.loadSuffixConfig.bind(this);
@@ -102,6 +102,7 @@ export class Database extends React.Component {
// Other
this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ this.enableTree = this.enableTree.bind(this);
}
componentWillMount () {
@@ -449,6 +450,13 @@ export class Database extends React.Component {
}
selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
+ this.setState({
+ disableTree: true // Disable the tree to allow node to be fully loaded
+ });
+
if (selectedNode.id == "dbconfig" ||
selectedNode.id == "chaining-config" ||
selectedNode.id == "backups") {
@@ -976,8 +984,38 @@ export class Database extends React.Component {
suffixLoading: false
});
});
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error attribute encryption for ${suffix} - ${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false
+ });
});
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading VLV indexes for ${suffix} - ${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false
+ });
});
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading config for ${suffix} - ${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false
+ });
});
}
@@ -1047,9 +1085,20 @@ export class Database extends React.Component {
});
}
+ enableTree () {
+ this.setState({
+ disableTree: false
+ });
+ }
+
render() {
const { nodes } = this.state;
let db_element = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
+
if (this.state.loaded) {
if (this.state.node_name == DB_CONFIG || this.state.node_name == "") {
db_element =
@@ -1058,6 +1107,7 @@ export class Database extends React.Component {
addNotification={this.addNotification}
reload={this.loadGlobalConfig}
data={this.state.globalDBConfig}
+ enableTree={this.enableTree}
key={this.state.configUpdated}
/>;
} else if (this.state.node_name == CHAINING_CONFIG) {
@@ -1067,6 +1117,7 @@ export class Database extends React.Component {
addNotification={this.addNotification}
reload={this.loadChainingConfig}
data={this.state.chainingConfig}
+ enableTree={this.enableTree}
key={this.state.chainingUpdated}
/>;
} else if (this.state.node_name == BACKUP_CONFIG) {
@@ -1077,6 +1128,7 @@ export class Database extends React.Component {
backups={this.state.BackupRows}
suffixes={this.state.suffixList}
ldifs={this.state.LDIFRows}
+ enableTree={this.enableTree}
reload={this.loadBackups}
/>;
} else if (this.state.node_name != "") {
@@ -1084,10 +1136,9 @@ export class Database extends React.Component {
if (this.state.dbtype == "suffix" || this.state.dbtype == "subsuffix") {
if (this.state.suffixLoading) {
db_element =
- <div className="ds-loading-spinner ds-center">
- <p />
+ <div className="ds-margin-top ds-loading-spinner ds-center">
<h4>Loading suffix configuration for <b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else {
db_element =
@@ -1107,6 +1158,7 @@ export class Database extends React.Component {
dbtype={this.state.dbtype}
data={this.state[this.state.node_text]}
attrs={this.state.attributes}
+ enableTree={this.enableTree}
key={this.state.node_text}
/>;
}
@@ -1114,10 +1166,9 @@ export class Database extends React.Component {
// Chaining
if (this.state.chainingLoading) {
db_element =
- <div className="ds-loading-spinner ds-center">
- <p />
+ <div className="ds-margin-top ds-loading-spinner ds-center">
<h4>Loading chaining configuration for <b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else {
db_element =
@@ -1128,6 +1179,7 @@ export class Database extends React.Component {
loadSuffixTree={this.loadSuffixTree}
addNotification={this.addNotification}
data={this.state[this.state.node_text]}
+ enableTree={this.enableTree}
reload={this.loadChainingLink}
/>;
}
@@ -1144,7 +1196,7 @@ export class Database extends React.Component {
<div className="ds-container">
<div>
<div className="ds-tree">
- <div className="tree-view-container" id="db-tree"
+ <div className={disabled} id="db-tree"
style={treeViewContainerStyles}>
<TreeView
nodes={nodes}
@@ -1219,8 +1271,7 @@ class CreateSuffixModal extends React.Component {
<input onChange={handleChange} className={error.createSuffix ? "ds-input-bad" : "ds-input"} type="text" id="createSuffix" size="40" />
</Col>
</Row>
- <p />
- <Row title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends.">
+ <Row className="ds-margin-top" title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends.">
<Col sm={3}>
<ControlLabel>Database Name</ControlLabel>
</Col>
diff --git a/src/cockpit/389-console/src/ds.js b/src/cockpit/389-console/src/ds.js
index 1274b3f..7b4db77 100644
--- a/src/cockpit/389-console/src/ds.js
+++ b/src/cockpit/389-console/src/ds.js
@@ -10,7 +10,7 @@ var dn_regex = new RegExp( "^([A-Za-z]+=.*)" );
var server_page_loaded = 0;
var security_page_loaded = 1;
var db_page_loaded = 1;
-var repl_page_loaded = 0;
+var repl_page_loaded = 1;
var plugin_page_loaded = 1;
var schema_page_loaded = 0;
var monitor_page_loaded = 1;
@@ -355,39 +355,6 @@ function save_all () {
save_config(); // Server Config Page
}
-function load_repl_suffix_dropdowns() {
- // Update replication drop downs (agmts mainly)
- var repl_dropdowns = ['select-repl-agmt-suffix', 'select-repl-winsync-suffix',
- 'cleanallruv-suffix', 'monitor-repl-backend-list'];
- var repl_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'list'];
- log_cmd('load_repl_suffix_dropdowns', 'get replicated suffix list', repl_cmd);
- cockpit.spawn(repl_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- // Update dropdowns
- for (var idx in repl_dropdowns) {
- $("#" + repl_dropdowns[idx]).find('option').remove();
- }
- var obj = JSON.parse(data);
- for (var idx in obj['items']) {
- for (var list in repl_dropdowns){
- $("#" + repl_dropdowns[list]).append('<option value="' + obj['items'][idx] + '" selected="selected">' + obj['items'][idx] +'</option>');
- }
- }
- get_and_set_repl_agmts();
- get_and_set_repl_winsync_agmts();
- if (obj['items'].length == 0){
- // Disable create agmt buttons
- $("#create-agmt").prop("disabled", true);
- $("#winsync-create-agmt").prop("disabled", true);
- $("#create-cleanallruv-btn").prop("disabled", true);
- } else {
- // Enable repl agmt buttons
- $("#create-agmt").prop("disabled", false);
- $("#winsync-create-agmt").prop("disabled", false);
- $("#create-cleanallruv-btn").prop("disabled", false);
- }
- });
-}
-
var progress = 10;
function update_progress () {
@@ -449,11 +416,6 @@ function load_config (refresh){
get_and_set_schema_tables();
update_progress();
- // Replication page
- get_and_set_repl_config();
- get_and_set_cleanallruv();
- update_progress();
-
// Initialize the tabs
$(".ds-tab-list").css( 'color', '#777');
$("#server-tab").css( 'color', '#228bc0');
@@ -510,4 +472,8 @@ $(window.document).ready(function() {
$(".all-pages").hide();
$("#security-content").show();
});
+ $("#replication-tab").on("click", function() {
+ $(".all-pages").hide();
+ $("#replication-content").show();
+ });
});
diff --git a/src/cockpit/389-console/src/index.es6 b/src/cockpit/389-console/src/index.es6
index 71e9c5e..6797dd5 100644
--- a/src/cockpit/389-console/src/index.es6
+++ b/src/cockpit/389-console/src/index.es6
@@ -4,6 +4,7 @@ import { Plugins } from "./plugins.jsx";
import { Database } from "./database.jsx";
import { Monitor } from "./monitor.jsx";
import { Security } from "./security.jsx";
+import { Replication } from "./replication.jsx";
var serverIdElem;
@@ -31,6 +32,12 @@ function renderReactDOM(clear) {
document.getElementById("database")
);
+ // Replication tab
+ ReactDOM.render(
+ <Replication serverId={serverIdElem} key={tabKey} />,
+ document.getElementById("replication")
+ );
+
// Monitor tab
ReactDOM.render(
<Monitor serverId={serverIdElem} key={tabKey} />,
diff --git a/src/cockpit/389-console/src/index.html b/src/cockpit/389-console/src/index.html
index 3eef2a7..6a0479c 100644
--- a/src/cockpit/389-console/src/index.html
+++ b/src/cockpit/389-console/src/index.html
@@ -20,7 +20,6 @@
<script src="ds.js"></script>
<script src="schema.js"></script>
<script src="servers.js"></script>
- <script src="replication.js"></script>
<link href="static/bootstrap.min.css" rel="stylesheet">
<link href="static/jquery.dataTables.min.css" type="text/css" rel="stylesheet">
<link href="static/jquery.timepicker.min.css" type="text/css" rel="stylesheet">
@@ -35,9 +34,9 @@
<div id="reload-page" hidden></div>
<div id="loading-page" class="ds-center ds-loading" hidden>
<h4 id="loading-msg">Loading Directory Server Configuration...</h4>
- <p><span class="spinner spinner-lg spinner-inline"></span></p>
+ <p class="ds-margin-top-lg"><span class="spinner spinner-lg spinner-inline"></span></p>
- <div class="progress">
+ <div class="progress ds-margin-top-lg">
<div class="progress-bar" role="progressbar" id="ds-progress-bar"
aria-valuenow="20" aria-valuemin="0"
aria-valuemax="100" style="width: 20%;"
@@ -46,7 +45,6 @@
</div>
</div>
-
</div>
<div id="everything" hidden>
<div class="ds-nav-bar">
@@ -103,17 +101,10 @@
</li>
<!-- Replication navtab -->
- <li class="dropdown ds-nav-tab">
- <a href="#0" class="ds-tab-list" data-toggle="dropdown" id="replication-tab">
- Replication
- <b class="caret"></b>
- </a>
- <ul class="dropdown-menu ds-nav-item">
- <li><a href="#0" class="ds-nav-choice" id="repl-config-btn" parent-id="replication-tab">Configuration</a></li>
- <li><a href="#0" class="ds-nav-choice" id="repl-agmts-btn" parent-id="replication-tab">Agreements</a></li>
- <li><a href="#0" class="ds-nav-choice" id="repl-winsync-btn" parent-id="replication-tab">Winsync Agreements</a></li>
- <li><a href="#0" class="ds-nav-choice" id="repl-tasks-btn" parent-id="replication-tab">Replication Tasks</a></li>
- </ul>
+ <li class="ds-nav-tab">
+ <a href="#0" class="ds-tab-list ds-tab-standalone" id="replication-tab">
+ Replication
+ </a>
</li>
<!-- Schema navtab -->
@@ -502,6 +493,12 @@
</div>
<div id="server-content" class="all-pages" hidden>
+ <div id="server-settings"></div>
+ <div id="server-tuning"></div>
+ <div id="server-sasl"></div>
+ <div id="server-pwp"></div>
+ <div id="server-ldapi"></div>
+ <div id="server-logs"></div>
</div>
<div id="security-content" class="all-pages" hidden>
@@ -513,6 +510,7 @@
</div>
<div id="replication-content" class="all-pages" hidden>
+ <div id="replication"></div>
</div>
<div id="schema-content" class="all-pages" hidden>
diff --git a/src/cockpit/389-console/src/lib/database/attrEncryption.jsx b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
index 9bc86bc..f34f6f8 100644
--- a/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
+++ b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
@@ -129,8 +129,7 @@ export class AttrEncryption extends React.Component {
rows={this.props.rows}
loadModalHandler={this.showConfirmAttrDelete}
/>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={6}>
<Typeahead
id="attrEncrypt"
diff --git a/src/cockpit/389-console/src/lib/database/backups.jsx b/src/cockpit/389-console/src/lib/database/backups.jsx
index f04c348..6204673 100644
--- a/src/cockpit/389-console/src/lib/database/backups.jsx
+++ b/src/cockpit/389-console/src/lib/database/backups.jsx
@@ -8,6 +8,7 @@ import {
TabContent,
TabPane,
TabContainer,
+ Checkbox,
Col,
Button,
Spinner,
@@ -46,6 +47,7 @@ export class Backups extends React.Component {
showLDIFDeleteSpinningModal: false,
showExportModal: false,
exportSpinner: false,
+ includeReplData: false,
ldifName: "",
ldifSuffix: "",
errObj: {}
@@ -88,12 +90,17 @@ export class Backups extends React.Component {
this.closeConfirmLDIFReplace = this.closeConfirmLDIFReplace.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
showExportModal () {
this.setState({
showExportModal: true,
exportSpinner: false,
ldifName: "",
- ldifSuffix: this.props.suffixes[0]
+ ldifSuffix: this.props.suffixes[0],
+ includeReplData: false,
});
}
@@ -254,7 +261,7 @@ export class Backups extends React.Component {
importLDIF() {
this.showLDIFSpinningModal();
- const cmd = [
+ let cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
"backend", "import", this.state.ldifSuffix, this.state.ldifName, "--encrypted"
];
@@ -281,7 +288,7 @@ export class Backups extends React.Component {
deleteLDIF (e) {
this.showLDIFDeleteSpinningModal();
- const cmd = [
+ let cmd = [
"dsctl", this.props.serverId, "ldifs", "--delete", this.state.ldifName
];
log_cmd("deleteLDIF", "Deleting LDIF", cmd);
@@ -491,6 +498,10 @@ export class Backups extends React.Component {
"backend", "export", this.state.ldifSuffix, "--encrypted", "--ldif=" + this.state.ldifName
];
+ if (this.state.includeReplData) {
+ export_cmd.push("--replication");
+ }
+
this.setState({
exportSpinner: true,
});
@@ -545,13 +556,22 @@ export class Backups extends React.Component {
confirmDelete={this.showConfirmBackupDelete}
/>
</div>
- <p />
- <Button
- bsStyle="primary"
- onClick={this.showBackupModal}
- >
- Create Backup
- </Button>
+ <div className="ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showBackupModal}
+ className="ds-margin-top"
+ >
+ Create Backup
+ </Button>
+ <Button
+ bsStyle="default"
+ onClick={this.props.reload}
+ className="ds-left-margin ds-margin-top"
+ >
+ Refresh Backups
+ </Button>
+ </div>
</TabPane>
<TabPane eventKey={2}>
@@ -562,13 +582,22 @@ export class Backups extends React.Component {
confirmDelete={this.showConfirmLDIFDelete}
/>
</div>
- <p />
- <Button
- bsStyle="primary"
- onClick={this.showExportModal}
- >
- Create LDIF Export
- </Button>
+ <div className="ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showExportModal}
+ className="ds-margin-top"
+ >
+ Create LDIF Export
+ </Button>
+ <Button
+ bsStyle="default"
+ onClick={this.props.reload}
+ className="ds-left-margin ds-margin-top"
+ >
+ Refresh LDIFs
+ </Button>
+ </div>
</TabPane>
</TabContent>
</div>
@@ -677,7 +706,7 @@ class ExportModal extends React.Component {
if (spinning) {
spinner =
<Row>
- <div className="ds-modal-spinner">
+ <div className="ds-margin-top ds-modal-spinner">
<Spinner loading inline size="md" />Exporting database... <font size="2">(You can safely close this window)</font>
</div>
</Row>;
@@ -713,8 +742,7 @@ class ExportModal extends React.Component {
</select>
</Col>
</Row>
- <p />
- <Row title="Name of exported LDIF file, if left blank the data and time will be used as the file name">
+ <Row className="ds-margin-top" title="Name of exported LDIF file, if left blank the data and time will be used as the file name">
<Col sm={3}>
<ControlLabel>LDIF File Name</ControlLabel>
</Col>
@@ -727,7 +755,17 @@ class ExportModal extends React.Component {
/>
</Col>
</Row>
- <p />
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-margin-left">
+ <Checkbox
+ id="includeReplData"
+ onChange={handleChange}
+ title="Include the replication metadata needed to restore or initialize another replica."
+ >
+ Include Replication Data
+ </Checkbox>
+ </Col>
+ </Row>
{spinner}
</Form>
</Modal.Body>
@@ -766,7 +804,7 @@ export class BackupModal extends React.Component {
if (spinning) {
spinner =
<Row>
- <div className="ds-modal-spinner">
+ <div className="ds-margin-top ds-modal-spinner">
<Spinner loading inline size="md" />Backing up databases... <font size="2">(You can safely close this window)</font>
</div>
</Row>;
@@ -803,7 +841,6 @@ export class BackupModal extends React.Component {
/>
</Col>
</Row>
- <p />
{spinner}
</Form>
</Modal.Body>
@@ -856,8 +893,7 @@ class RestoreModal extends React.Component {
<Form horizontal autoComplete="off">
<div className="ds-modal-spinner">
<Spinner loading inline size="md" /> Restoring backup <b>{msg}</b> ...
- <p />
- <p><font size="2"> (You can safely close this window)</font></p>
+ <p className="ds-margin-top"><font size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -904,8 +940,7 @@ class DeleteBackupModal extends React.Component {
<Form horizontal autoComplete="off">
<div className="ds-modal-spinner">
<Spinner loading inline size="md" /> Deleting backup <b>{msg}</b> ...
- <p />
- <p><font size="2"> (You can safely close this window)</font></p>
+ <p className="ds-margin-top"><font size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -952,8 +987,7 @@ class ImportingModal extends React.Component {
<Form horizontal autoComplete="off">
<div className="ds-modal-spinner">
<Spinner loading inline size="md" /> Importing LDIF <b>{msg}</b> ...
- <p />
- <p><font size="2"> (You can safely close this window)</font></p>
+ <p className="ds-margin-top"><font size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -1000,8 +1034,7 @@ class DeletingLDIFModal extends React.Component {
<Form horizontal autoComplete="off">
<div className="ds-modal-spinner">
<Spinner loading inline size="md" /> Deleting LDIF file <b>{msg}</b> ...
- <p />
- <p><font size="2"> (You can safely close this window)</font></p>
+ <p className="ds-margin-top"><font size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -1068,11 +1101,13 @@ ImportingModal.propTypes = {
Backups.propTypes = {
backups: PropTypes.array,
ldifs: PropTypes.array,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
Backups.defaultProps = {
backups: [],
ldifs: [],
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/chaining.jsx b/src/cockpit/389-console/src/lib/database/chaining.jsx
index 2a1d423..749d414 100644
--- a/src/cockpit/389-console/src/lib/database/chaining.jsx
+++ b/src/cockpit/389-console/src/lib/database/chaining.jsx
@@ -1,6 +1,6 @@
import cockpit from "cockpit";
import React from "react";
-import { ConfirmPopup } from "../notifications.jsx";
+import { ConfirmPopup, DoubleConfirmModal } from "../notifications.jsx";
import CustomCollapse from "../customCollapse.jsx";
import { log_cmd } from "../tools.jsx";
import {
@@ -11,7 +11,6 @@ import {
Row,
Col,
ControlLabel,
- Checkbox,
FormControl,
noop
} from "patternfly-react";
@@ -98,6 +97,10 @@ export class ChainingDatabaseConfig extends React.Component {
this.closeConfirmCompDelete = this.closeConfirmCompDelete.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleChange(e) {
// Generic
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
@@ -454,7 +457,7 @@ export class ChainingDatabaseConfig extends React.Component {
);
return (
- <div id="db-global-page">
+ <div id="chaining-page">
<h3 className="ds-config-header">Database Chaining Settings</h3>
<hr />
<div className="ds-container">
@@ -492,66 +495,143 @@ export class ChainingDatabaseConfig extends React.Component {
</div>
</div>
</div>
-
- <h4 className="ds-sub-header"><br />Default Database Link Creation Settings</h4>
+ <h4 className="ds-margin-top ds-sub-header ds-center">Default Database Link Creation Settings</h4>
<hr />
- <div className="ds-container">
- <div className="ds-split">
- <label htmlFor="defSizeLimit" className="ds-config-label" title="The size limit of entries returned over a database link (nsslapd-sizelimit).">
- Size Limit</label><input className="ds-input" type="text" id="defSizeLimit" onChange={this.handleChange} value={this.state.defSizeLimit} size="15" />
- <label htmlFor="defTimeLimit" className="ds-config-label" title="The time limit of an operation over a database link (nsslapd-timelimit).">
- Time Limit</label><input className="ds-input" type="text" id="defTimeLimit" onChange={this.handleChange} value={this.state.defTimeLimit} size="15" />
- <label htmlFor="defBindConnLimit" className="ds-config-label" title="The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
- Max TCP Connections</label><input className="ds-input" id="defBindConnLimit" type="text" onChange={this.handleChange} value={this.state.defBindConnLimit} size="15" />
- <label htmlFor="defOpConnLimit" className="ds-config-label" title="The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
- Max LDAP Connections</label><input className="ds-input" id="defOpConnLimit" type="text" onChange={this.handleChange} value={this.state.defOpConnLimit} size="15" />
- <label htmlFor="defConcurLimit" className="ds-config-label" title="The maximum number of concurrent bind operations per TCP connection. (nsconcurrentbindlimit).">
- Max Binds Per Connection</label><input className="ds-input" id="defConcurLimit" type="text" onChange={this.handleChange} value={this.state.defConcurLimit} size="15" />
- <label htmlFor="defBindTimeout" className="ds-config-label" title="The amount of time before the bind attempt times out. (nsbindtimeout).">
- Bind Timeout</label><input className="ds-input" id="defBindTimeout" type="text" onChange={this.handleChange} value={this.state.defBindTimeout} size="15" />
- <label htmlFor="defBindRetryLimit" className="ds-config-label" title="The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
- Bind Retry Limit</label><input className="ds-input" id="defBindRetryLimit" type="text" onChange={this.handleChange} value={this.state.defBindRetryLimit} size="15" />
- </div>
- <div className="ds-divider" />
- <div className="ds-split ds-inline">
- <div>
- <label htmlFor="defConcurOpLimit" className="ds-config-label" title="The maximum number of operations per connections. (nsconcurrentoperationslimit).">
- Max Operations Per Connection</label><input className="ds-input" id="defConcurOpLimit" type="text" onChange={this.handleChange} value={this.state.defConcurOpLimit} size="15" />
- </div>
- <div>
- <label htmlFor="defConnLife" className="ds-config-label" title="The life of a database link connection to the remote server. 0 is unlimited (nsconnectionlife).">
- Connection Lifetime (in seconds)</label><input className="ds-input" id="defConnLife" type="text" onChange={this.handleChange} value={this.state.defConnLife} size="15" />
- </div>
- <div>
- <label htmlFor="defSearchCheck" className="ds-config-label" title="The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
- Abandoned Op Check Interval</label><input className="ds-input" id="defSearchCheck" type="text" onChange={this.handleChange} value={this.state.defSearchCheck} size="15" />
- </div>
- <div>
- <label htmlFor="defHopLimit" className="ds-config-label" title="The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
- Database Link Hop Limit</label><input className="ds-input" type="text" onChange={this.handleChange} value={this.state.defHopLimit} id="defHopLimit" size="15" />
- </div>
- <div>
- <p />
- <input type="checkbox" onChange={this.handleChange} checked={this.state.defCheckAci} className="ds-config-checkbox" id="defCheckAci" /><label
- htmlFor="defCheckAci" className="ds-label" title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci)."> Check Local ACIs</label>
- </div>
- <div>
- <input type="checkbox" onChange={this.handleChange} checked={this.state.defRefOnScoped} className="ds-config-checkbox" id="defRefOnScoped" /><label
- htmlFor="defRefOnScoped" className="ds-label" title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch)."> Send Referral On Scoped Search</label>
- </div>
- <div>
- <input type="checkbox" onChange={this.handleChange} checked={this.state.defProxy} className="ds-config-checkbox" id="defProxy" /><label
- htmlFor="defProxy" className="ds-label" title="Sets whether proxied authentication is allowed (nsproxiedauthorization)."> Allow Proxied Authentication</label>
- </div>
- <div>
- <input type="checkbox" onChange={this.handleChange} checked={this.state.defUseStartTLS} className="ds-config-checkbox" id="defUseStartTLS" /><label
- htmlFor="defUseStartTLS" className="ds-label" title="Sets whether to use Start TLS to initiate a secure, encrypted connection over an insecure port. (nsusestarttls)."> Use StartTLS</label>
- </div>
- </div>
- </div>
- <div className="ds-margin-top-lg">
- <button className="btn btn-primary save-button" onClick={this.save_chaining_config}>Save Default Settings</button>
- </div>
+ <Form horizontal>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The size limit of entries returned over a database link (nsslapd-sizelimit).">
+ Size Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defSizeLimit" onChange={this.handleChange} defaultValue={this.state.defSizeLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ Max Operations Per Conn
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defConcurOpLimit" onChange={this.handleChange} defaultValue={this.state.defConcurOpLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The time limit of an operation over a database link (nsslapd-timelimit).">
+ Time Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defTimeLimit" onChange={this.handleChange} defaultValue={this.state.defTimeLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ Connection Lifetime
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defConnLife" onChange={this.handleChange} defaultValue={this.state.defConnLife} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
+ Max TCP Connections
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defBindConnLimit" onChange={this.handleChange} defaultValue={this.state.defBindConnLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
+ Abandoned Op Check Interval
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defSearchCheck" onChange={this.handleChange} defaultValue={this.state.defSearchCheck} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
+ Max LDAP Connections
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defOpConnLimit" onChange={this.handleChange} defaultValue={this.state.defOpConnLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
+ Abandoned Op Check Interval
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defSearchCheck" onChange={this.handleChange} defaultValue={this.state.defSearchCheck} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
+ Max Binds Per Connection
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defOpConnLimit" onChange={this.handleChange} defaultValue={this.state.defOpConnLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
+ Database Link Hop Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defHopLimit" onChange={this.handleChange} defaultValue={this.state.defHopLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The amount of time before the bind attempt times out. (nsbindtimeout).">
+ Bind Timeout
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defBindTimeout" onChange={this.handleChange} defaultValue={this.state.defBindTimeout} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
+ Bind Retry Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto" type="text" id="defBindRetryLimit" onChange={this.handleChange} defaultValue={this.state.defBindRetryLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci).">
+ Check Local ACIs
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.handleChange} defaultChecked={this.state.defCheckAci} className="ds-config-checkbox" id="nsusdefCheckAciestarttls" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch).">
+ Send Referral On Scoped Search
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.handleChange} defaultChecked={this.state.defRefOnScoped} className="ds-config-checkbox" id="defRefOnScoped" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci).">
+ Allow Proxied Authentication
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.handleChange} defaultChecked={this.state.defProxy} className="ds-config-checkbox" id="defProxy" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch).">
+ Use StartTLS
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.handleChange} defaultChecked={this.state.defUseStartTLS} className="ds-config-checkbox" id="defUseStartTLS" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg">
+ <Col sm={5}>
+ <button className="btn btn-primary save-button" onClick={this.save_chaining_config}>Save Default Settings</button>
+ </Col>
+ </Row>
+ </Form>
<ChainControlsModal
showModal={this.state.showOidModal}
@@ -598,6 +678,8 @@ export class ChainingConfig extends React.Component {
errObj: {},
showDeleteConfirm: false,
linkPwdMatch: true,
+ modalSpinning: false,
+ modalChecked: false,
// Settings
nsfarmserverurl: this.props.data.nsfarmserverurl,
nsmultiplexorbinddn: this.props.data.nsmultiplexorbinddn,
@@ -655,15 +737,23 @@ export class ChainingConfig extends React.Component {
this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
showDeleteConfirm () {
this.setState({
- showDeleteConfirm: true
+ showDeleteConfirm: true,
+ modalSpinning: false,
+ modalChecked: false,
});
}
closeDeleteConfirm () {
this.setState({
- showDeleteConfirm: false
+ showDeleteConfirm: false,
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -874,89 +964,26 @@ export class ChainingConfig extends React.Component {
render () {
const error = this.state.errObj;
- let useStartTLSCheckBox;
- let checkLocalAci;
- let referralOnScope;
- let proxiedAuth;
-
- // Check local aci's checkbox
- if (this.state.nschecklocalaci) {
- checkLocalAci =
- <Checkbox id="nschecklocalaci" onChange={this.handleChange} key={this.state.nschecklocalaci} defaultChecked
- title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci).">
- Check Local ACIs
- </Checkbox>;
- } else {
- checkLocalAci =
- <Checkbox id="nschecklocalaci" onChange={this.handleChange} key={this.state.nschecklocalaci}
- title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci).">
- Check Local ACIs
- </Checkbox>;
- }
- // Referral on scoped search checkbox
- if (this.state.nsreferralonscopedsearch) {
- referralOnScope =
- <Checkbox id="nsreferralonscopedsearch" onChange={this.handleChange} defaultChecked
- title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch).">
- Send Referral On Scoped Search
- </Checkbox>;
- } else {
- referralOnScope =
- <Checkbox id="nsreferralonscopedsearch" onChange={this.handleChange}
- title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch).">
- Send Referral On Scoped Search
- </Checkbox>;
- }
- // Allow proxied auth checkbox
- if (this.state.nsproxiedauthorization) {
- proxiedAuth =
- <Checkbox id="nsproxiedauthorization" onChange={this.handleChange} defaultChecked
- title="Allow proxied authentication to the remote server. (nsproxiedauthorization).">
- Allow Proxied Authentication
- </Checkbox>;
- } else {
- proxiedAuth =
- <Checkbox id="nsproxiedauthorization" onChange={this.handleChange}
- title="Allow proxied authentication to the remote server. (nsproxiedauthorization).">
- Allow Proxied Authentication
- </Checkbox>;
- }
- // use startTLS checkbox
- if (this.state.nsusestarttls) {
- useStartTLSCheckBox =
- <Checkbox id="nsusestarttls" onChange={this.handleChange} title="Use StartTLS for connection to remote server. (nsusestarttls)"
- defaultChecked
- >
- Use StartTLS for remote connection
- </Checkbox>;
- } else {
- useStartTLSCheckBox =
- <Checkbox id="nsusestarttls" onChange={this.handleChange} title="Use StartTLS for connection to remote server. (nsusestarttls)">
- Use StartTLS for remote connection
- </Checkbox>;
- }
return (
- <div className="container-fluid">
+ <div>
<Row>
- <Col sm={8} className="ds-word-wrap">
+ <Col sm={10} className="ds-word-wrap">
<ControlLabel className="ds-suffix-header"><Icon type="fa" name="link" /> <b>{this.props.suffix}</b> (<i>{this.props.bename}</i>)</ControlLabel>
</Col>
<Col sm={2}>
<Button
- bsStyle="primary"
+ bsStyle="danger"
onClick={this.showDeleteConfirm}
>
Delete Link
</Button>
</Col>
</Row>
- <h4>Database Link Configuration</h4>
- <hr />
- <Form horizontal autoComplete="off">
+ <Form horizontal autoComplete="off" className="ds-margin-top-xlg">
<Row title="The LDAP URL for the remote server. Add additional failure server URLs by separating them with a space. (nsfarmserverurl)">
- <Col sm={4}>
- <ControlLabel>Remote Server LDAP URL</ControlLabel>
+ <Col componentClass={ControlLabel} sm={4}>
+ Remote Server LDAP URL
</Col>
<Col sm={8}>
<FormControl
@@ -968,10 +995,9 @@ export class ChainingConfig extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row title="The distinguished name (DN) of the entry to authenticate to the remote server. (nsmultiplexorbinddn)">
- <Col sm={4}>
- <ControlLabel>Remote Server Bind DN</ControlLabel>
+ <Row className="ds-margin-top" title="The distinguished name (DN) of the entry to authenticate to the remote server. (nsmultiplexorbinddn)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Remote Server Bind DN
</Col>
<Col sm={8}>
<FormControl
@@ -983,10 +1009,9 @@ export class ChainingConfig extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row title="The password for the authenticating entry. (nsmultiplexorcredentials)">
- <Col sm={4}>
- <ControlLabel>Bind DN Password</ControlLabel>
+ <Row className="ds-margin-top" title="The password for the authenticating entry. (nsmultiplexorcredentials)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN Password
</Col>
<Col sm={8}>
<FormControl
@@ -998,10 +1023,9 @@ export class ChainingConfig extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row title="Confirm the password for the authenticating entry. (nsmultiplexorcredentials)">
- <Col sm={4}>
- <ControlLabel>Confirm Password</ControlLabel>
+ <Row className="ds-margin-top" title="Confirm the password for the authenticating entry. (nsmultiplexorcredentials)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
</Col>
<Col sm={8}>
<FormControl
@@ -1013,10 +1037,9 @@ export class ChainingConfig extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row title="The authentication mechanism. Simple (user name and password), SASL/DIGEST-MD5, or SASL>GSSAPI. (nsbindmechanism)">
- <Col sm={4}>
- <ControlLabel>Bind Mechanism</ControlLabel>
+ <Row className="ds-margin-top" title="The authentication mechanism. Simple (user name and password), SASL/DIGEST-MD5, or SASL>GSSAPI. (nsbindmechanism)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Mechanism
</Col>
<Col sm={8}>
<select value={this.state.nsbindmechanism}
@@ -1030,87 +1053,214 @@ export class ChainingConfig extends React.Component {
</select>
</Col>
</Row>
- <p />
- <Row>
- <Col sm={9}>
- {useStartTLSCheckBox}
+ <Row className="ds-margin-top" title="Use StartTLS for connections to the remote server. (nsusestarttls)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Use StartTLS
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.props.handleChange} defaultChecked={this.state.nsusestarttls} className="ds-config-checkbox" id="nsusestarttls" />
</Col>
</Row>
</Form>
- <p />
- <CustomCollapse>
- <div className="ds-margin-top">
- <div className="ds-margin-left">
- <div>
- <label htmlFor="sizelimit" className="ds-config-label" title="The size limit of entries returned over a database link (nsslapd-sizelimit).">
- Size Limit</label><input onChange={this.handleChange} defaultValue={this.state.sizelimit} className="ds-input" type="text" id="sizelimit" size="15" />
- </div>
- <div>
- <label htmlFor="timelimit" className="ds-config-label" title="The time limit of an operation over a database link (nsslapd-timelimit).">
- Time Limit</label><input onChange={this.handleChange} defaultValue={this.state.timelimit} className="ds-input" type="text" id="timelimit" size="15" />
- </div>
- <div>
- <label htmlFor="bindconnlimit" className="ds-config-label" title="The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
- Max TCP Connections</label><input onChange={this.handleChange} defaultValue={this.state.bindconnlimit} className="ds-input" type="text" id="bindconnlimit" size="15" />
- </div>
- <div>
- <label htmlFor="opconnlimit" className="ds-config-label" title="The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
- Max LDAP Connections</label><input onChange={this.handleChange} defaultValue={this.state.opconnlimit} className="ds-input" type="text" id="opconnlimit" size="15" />
- </div>
- <div>
- <label htmlFor="concurrbindlimit" className="ds-config-label" title="The maximum number of concurrent bind operations per TCP connection. (nsconcurrentbindlimit).">
- Max Binds Per Connection</label><input onChange={this.handleChange} defaultValue={this.state.concurrbindlimit} className="ds-input" type="text" id="concurrbindlimit" size="15" />
- </div>
- <div>
- <label htmlFor="bindtimeout" className="ds-config-label" title="The amount of time before the bind attempt times out. (nsbindtimeout).">
- Bind Timeout</label><input onChange={this.handleChange} defaultValue={this.state.bindtimeout} className="ds-input" type="text" id="bindtimeout" size="15" />
- </div>
-
- <div>
- <label htmlFor="bindretrylimit" className="ds-config-label" title="The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
- Bind Retry Limit</label><input onChange={this.handleChange} defaultValue={this.state.bindretrylimit} className="ds-input" type="text" id="bindretrylimit" size="15" />
- </div>
- <div>
- <label htmlFor="concurroplimit" className="ds-config-label" title="The maximum number of operations per connections. (nsconcurrentoperationslimit).">
- Max Operations Per Connection</label><input onChange={this.handleChange} defaultValue={this.state.concurroplimit} className="ds-input" type="text" id="concurroplimit" size="15" />
- </div>
- <div>
- <label htmlFor="connlifetime" className="ds-config-label" title="The life of a database link connection to the remote server. 0 is unlimited (nsconnectionlife).">
- Connection Lifetime (in seconds)</label><input onChange={this.handleChange} defaultValue={this.state.connlifetime} className="ds-input" type="text" id="connlifetime" size="15" />
- </div>
- <div>
- <label htmlFor="searchcheckinterval" className="ds-config-label" title="The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
- Abandoned Op Check Interval</label><input onChange={this.handleChange} defaultValue={this.state.searchcheckinterval} className="ds-input" type="text" id="searchcheckinterval" size="15" />
- </div>
- <div>
- <label htmlFor="hoplimit" className="ds-config-label" title="The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
- Database Link Hop Limit</label><input onChange={this.handleChange} defaultValue={this.state.hoplimit} className="ds-input" type="text" id="hoplimit" size="15" />
- </div>
- <p />
- <div>
- {proxiedAuth}
- </div>
- <div>
- {checkLocalAci}
- </div>
- <div>
- {referralOnScope}
- </div>
- </div>
- </div>
+ <CustomCollapse className="ds-margin-top">
+ <Form horizontal className="ds-margin-top ds-margin-left">
+ <Row className="ds-margin-top" title="The size limit of entries returned over a database link (nsslapd-sizelimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Size Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="sizelimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.sizelimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The time limit of an operation over a database link (nsslapd-timelimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Time Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="timelimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.timelimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max TCP Connections
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="bindconnlimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.bindconnlimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max LDAP Connections
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="opconnlimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.opconnlimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of concurrent bind operations per TCP connection. (nsconcurrentbindlimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max Binds Per Connection
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="concurrbindlimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.concurrbindlimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The amount of time before the bind attempt times out. (nsbindtimeout).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Timeout
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="bindtimeout"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.bindtimeout}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Retry Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="bindretrylimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.bindretrylimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max Operations Per Connection
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="concurroplimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.concurroplimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The life of a database link connection to the remote server in seconds. 0 is unlimited (nsconnectionlife).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Connection Lifetime
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="connlifetime"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.connlifetime}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Abandoned Op Check Interval
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="searchcheckinterval"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.searchcheckinterval}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Hop Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="hoplimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.hoplimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Allow proxied authentication to the remote server. (nsproxiedauthorization).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Allow Proxied Authentication
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.props.handleChange} defaultChecked={this.state.nsproxiedauthorization} className="ds-config-checkbox" id="nsproxiedauthorization" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Check Local ACIs
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.props.handleChange} defaultChecked={this.state.nschecklocalaci} className="ds-config-checkbox" id="nschecklocalaci" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Send Referral On Scoped Search
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.props.handleChange} defaultChecked={this.state.nsreferralonscopedsearch} className="ds-config-checkbox" id="nsreferralonscopedsearch" />
+ </Col>
+ </Row>
+ </Form>
<hr />
</CustomCollapse>
<div className="ds-margin-top-lg">
<button onClick={this.saveLink} className="btn btn-primary">Save Configuration</button>
</div>
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showDeleteConfirm}
closeHandler={this.closeDeleteConfirm}
- actionFunc={this.deleteLink}
- actionParam={this.props.suffix}
- msg="Are you really sure you want to delete this database link?"
- msgContent={this.props.suffix}
+ handleChange={this.handleChange}
+ actionHandler={this.deleteLink}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Delete Database Link"
+ mMsg="Are you really sure you want to delete this database link?"
+ mSpinningMsg="Deleting Database Linkt ..."
+ mBtnName="Delete Database Link"
/>
</div>
);
@@ -1282,6 +1432,7 @@ ChainingDatabaseConfig.propTypes = {
addNotification: PropTypes.func,
reload: PropTypes.func,
data: PropTypes.object,
+ enableTree: PropTypes.func,
};
ChainingDatabaseConfig.defaultProps = {
@@ -1289,6 +1440,7 @@ ChainingDatabaseConfig.defaultProps = {
addNotification: noop,
reload: noop,
data: {},
+ enableTree: PropTypes.noop,
};
ChainingConfig.propTypes = {
@@ -1299,6 +1451,7 @@ ChainingConfig.propTypes = {
addNotification: PropTypes.func,
data: PropTypes.object,
reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
ChainingConfig.defaultProps = {
@@ -1309,4 +1462,5 @@ ChainingConfig.defaultProps = {
addNotification: noop,
data: {},
reload: noop,
+ enableTree: noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
index b94f28f..5adac79 100644
--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
@@ -6,6 +6,7 @@ import {
Row,
Col,
ControlLabel,
+ Form,
Spinner,
noop
} from "patternfly-react";
@@ -58,6 +59,10 @@ export class GlobalDatabaseConfig extends React.Component {
this.save_db_config = this.save_db_config.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
select_auto_cache (e) {
this.setState({
db_cache_auto: !this.state.db_cache_auto
@@ -213,8 +218,8 @@ export class GlobalDatabaseConfig extends React.Component {
db_cache_form = <div id="auto-cache-form" className="ds-margin-left">
<div>
<label htmlFor="autosize" className="ds-config-label-xlrg"
- title="Enable database and entry cache auto-tuning using a percentage of the systems current resources (nsslapd-cache-autosize).">
- System Memory Percentage</label><input className="ds-input" type="text"
+ title="Enable database and entry cache auto-tuning using a percentage of the system's current resources (nsslapd-cache-autosize).">
+ Memory Percentage</label><input className="ds-input" type="text"
id="autosizee" size="10" onChange={this.handleChange}
value={this.state.autosize} />
</div>
@@ -231,7 +236,7 @@ export class GlobalDatabaseConfig extends React.Component {
db_cache_form = <div id="manual-cache-form" className="ds-margin-left">
<label htmlFor="dbcachesize" className="ds-config-label-xlrg"
title="Specifies the database index cache size in bytes (nsslapd-dbcachesize).">
- Database Cache Size (bytes)</label><input className="ds-input" type="text"
+ Database Cache Size</label><input className="ds-input" type="text"
id="dbcachesize" size="10" onChange={this.handleChange} value={this.state.dbcachesize} />
</div>;
db_auto_checked = false;
@@ -249,8 +254,8 @@ export class GlobalDatabaseConfig extends React.Component {
} else {
import_cache_form = <div id="manual-import-cache-form" className="ds-margin-left">
<label htmlFor="importcachesize" className="ds-config-label-xlrg"
- title="The size of the database cache used in the bulk import process. (nsslapd-import-cachesize).">
- Import Cache Size (bytes)</label><input className="ds-input" type="text" id="importcachesize"
+ title="The size of the database cache in bytes used in the bulk import process. (nsslapd-import-cachesize).">
+ Import Cache Size</label><input className="ds-input" type="text" id="importcachesize"
size="10" onChange={this.handleChange} value={this.state.importcachesize} />
</div>;
import_auto_checked = false;
@@ -259,10 +264,9 @@ export class GlobalDatabaseConfig extends React.Component {
let spinner = "";
if (this.state.loading) {
spinner =
- <div className="ds-loading-spinner ds-center">
- <p />
+ <div className="ds-loading-spinner ds-margin-top ds-center">
<h4>Loading global database configuration ...</h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top" loading size="md" />
</div>;
}
@@ -272,15 +276,15 @@ export class GlobalDatabaseConfig extends React.Component {
<div className={this.state.loading ? 'ds-fadeout' : 'ds-fadein'}>
<h3 className="ds-config-header">Global Database Configuration</h3>
<hr />
- <div>
+ <Form horizontal>
<Row
title="The maximum number of entries that the Directory Server will check when examining candidate entries in response to a search request (nsslapd-lookthrough-limit)."
className="ds-margin-top"
>
- <Col componentClass={ControlLabel} sm={4}>
+ <Col componentClass={ControlLabel} sm={5}>
Database Look Though Limit
</Col>
- <Col sm={8}>
+ <Col sm={4}>
<input
id="looklimit"
value={this.state.looklimit}
@@ -292,10 +296,10 @@ export class GlobalDatabaseConfig extends React.Component {
title="The number of entry IDs that are searched during a search operation (nsslapd-idlistscanlimit)."
className="ds-margin-top"
>
- <Col componentClass={ControlLabel} sm={4}>
+ <Col componentClass={ControlLabel} sm={5}>
ID List Scan Limit
</Col>
- <Col sm={8}>
+ <Col sm={4}>
<input
id="idscanlimit"
value={this.state.idscanlimit}
@@ -307,10 +311,10 @@ export class GlobalDatabaseConfig extends React.Component {
title="The maximum number of entries that the Directory Server will check when examining candidate entries for a search which uses the simple paged results control (nsslapd-pagedlookthroughlimit)."
className="ds-margin-top"
>
- <Col componentClass={ControlLabel} sm={4}>
+ <Col componentClass={ControlLabel} sm={5}>
Paged Search Look Through Limit
</Col>
- <Col sm={8}>
+ <Col sm={4}>
<input
id="pagelooklimit"
value={this.state.pagelooklimit}
@@ -322,10 +326,10 @@ export class GlobalDatabaseConfig extends React.Component {
title="The number of entry IDs that are searched, specifically, for a search operation using the simple paged results control (nsslapd-pagedidlistscanlimit)."
className="ds-margin-top"
>
- <Col componentClass={ControlLabel} sm={4}>
+ <Col componentClass={ControlLabel} sm={5}>
Paged Search ID List Scan Limit
</Col>
- <Col sm={8}>
+ <Col sm={4}>
<input
id="pagescanlimit"
value={this.state.pagescanlimit}
@@ -337,10 +341,10 @@ export class GlobalDatabaseConfig extends React.Component {
title="The maximum number of entries that the Directory Server will check when examining candidate entries in response to a range search request (nsslapd-rangelookthroughlimit)."
className="ds-margin-top"
>
- <Col componentClass={ControlLabel} sm={4}>
+ <Col componentClass={ControlLabel} sm={5}>
Range Search Look Through Limit
</Col>
- <Col sm={8}>
+ <Col sm={4}>
<input
id="rangelooklimit"
value={this.state.rangelooklimit}
@@ -348,7 +352,7 @@ export class GlobalDatabaseConfig extends React.Component {
/>
</Col>
</Row>
- </div>
+ </Form>
<div className="ds-container">
<div>
@@ -377,7 +381,7 @@ export class GlobalDatabaseConfig extends React.Component {
<CustomCollapse>
<div className="ds-margin-top">
<div className="ds-margin-left">
- <div>
+ <Form horizontal>
<Row className="ds-margin-top" title="Database Transaction Log Location (nsslapd-db-logdirectory).">
<Col componentClass={ControlLabel} sm={4}>
Transaction Logs Directory
@@ -418,12 +422,10 @@ export class GlobalDatabaseConfig extends React.Component {
<input id="compactinterval" value={this.state.compactinterval} onChange={this.handleChange} className="ds-input-auto" type="text" />
</Col>
</Row>
- </div>
+ </Form>
</div>
- <p />
</div>
</CustomCollapse>
- <p />
<div className="ds-margin-top-lg">
<button className="btn btn-primary save-button"
onClick={this.save_db_config}>Save Configuration</button>
@@ -441,6 +443,7 @@ GlobalDatabaseConfig.propTypes = {
addNotification: PropTypes.func,
data: PropTypes.object,
reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
GlobalDatabaseConfig.defaultProps = {
@@ -448,4 +451,5 @@ GlobalDatabaseConfig.defaultProps = {
addNotification: noop,
data: {},
reload: noop,
+ enableTree: noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/databaseModal.jsx b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
index 092f22d..881b0b4 100644
--- a/src/cockpit/389-console/src/lib/database/databaseModal.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
@@ -2,6 +2,7 @@ import React from "react";
import {
Modal,
Row,
+ Checkbox,
Col,
ControlLabel,
Radio,
@@ -45,7 +46,7 @@ class CreateLinkModal extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <Form horizontal autoComplete="off">
+ <Form horizontal>
<div>
<label htmlFor="createLinkSuffix" className="ds-config-label" title="The RDN of the link suffix">
Link Sub-Suffix</label><input className={error.createLinkSuffix ? "ds-input-bad ds-input-right" : "ds-input ds-input-right"} onChange={handleChange} type="text" id="createLinkSuffix" size="12" /><b><font color="blue"> ,{suffix}</font></b>
@@ -154,8 +155,7 @@ class CreateSubSuffixModal extends React.Component {
<ControlLabel><b><font color="blue">,{suffix}</font></b></ControlLabel>
</Col>
</Row>
- <p />
- <Row title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends.">
+ <Row className="ds-margin-top" title="The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends.">
<Col sm={3}>
<ControlLabel>Database Name</ControlLabel>
</Col>
@@ -223,7 +223,7 @@ class ExportModal extends React.Component {
if (spinning) {
spinner =
<Row>
- <div className="ds-modal-spinner">
+ <div className="ds-margin-top-lg ds-modal-spinner">
<Spinner loading inline size="lg" />Exporting database... <font size="1">(You can safely close this window)</font>
</div>
</Row>;
@@ -260,7 +260,17 @@ class ExportModal extends React.Component {
/>
</Col>
</Row>
- <p />
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-margin-left">
+ <Checkbox
+ id="includeReplData"
+ onChange={handleChange}
+ title="Include the replication metadata needed to restore or initialize another replica."
+ >
+ Include Replication Data
+ </Checkbox>
+ </Col>
+ </Row>
{spinner}
</Form>
</Modal.Body>
@@ -302,8 +312,7 @@ class ImportModal extends React.Component {
if (spinning) {
spinner =
<Row>
- <div className="ds-modal-spinner">
- <p />
+ <div className="ds-margin-top-lg ds-modal-spinner">
<Spinner loading inline size="lg" />Importing LDIF file... <font size="1">(You can safely close this window)</font>
</div>
</Row>;
@@ -331,7 +340,6 @@ class ImportModal extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <p />
<LDIFTable
rows={suffixRows}
confirmImport={this.props.showConfirmImport}
@@ -358,7 +366,6 @@ class ImportModal extends React.Component {
</Button>
</Col>
</Row>
- <p />
{spinner}
</Form>
</Modal.Body>
diff --git a/src/cockpit/389-console/src/lib/database/indexes.jsx b/src/cockpit/389-console/src/lib/database/indexes.jsx
index d85bf8a..d33db25 100644
--- a/src/cockpit/389-console/src/lib/database/indexes.jsx
+++ b/src/cockpit/389-console/src/lib/database/indexes.jsx
@@ -665,7 +665,6 @@ class AddIndexModal extends React.Component {
</Col>
</Row>
</div>
- <p />
<Row className="ds-margin-top-lg">
<Col sm={12} title="List of matching rules separated by a 'space'">
<p><b>Matching Rules</b></p>
@@ -836,7 +835,6 @@ class EditIndexModal extends React.Component {
</Col>
</Row>
</div>
- <p />
<Row className="ds-margin-top-lg">
<Col sm={12}>
<p><b>Matching Rules</b></p>
diff --git a/src/cockpit/389-console/src/lib/database/referrals.jsx b/src/cockpit/389-console/src/lib/database/referrals.jsx
index a44569b..13f4c15 100644
--- a/src/cockpit/389-console/src/lib/database/referrals.jsx
+++ b/src/cockpit/389-console/src/lib/database/referrals.jsx
@@ -295,8 +295,7 @@ class AddReferralModal extends React.Component {
</select>
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Host Name</ControlLabel>
</Col>
@@ -304,8 +303,7 @@ class AddReferralModal extends React.Component {
<input className={error.refHost ? "ds-input-auto-bad" : "ds-input-auto"} type="text" onChange={handleChange} id="refHost" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Port Number</ControlLabel>
</Col>
@@ -313,8 +311,7 @@ class AddReferralModal extends React.Component {
<input className={error.refPort ? "ds-input-auto-bad" : "ds-input-auto"} type="text" onChange={handleChange} id="refPort" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Suffix</ControlLabel>
</Col>
@@ -322,8 +319,7 @@ class AddReferralModal extends React.Component {
<input className="ds-input-auto" onChange={handleChange} type="text" id="refSuffix" />
</Col>
</Row>
- <p />
- <Row title="Comma separated list of attributes to return">
+ <Row className="ds-margin-top" title="Comma separated list of attributes to return">
<Col sm={3}>
<ControlLabel>Attributes</ControlLabel>
</Col>
@@ -331,8 +327,7 @@ class AddReferralModal extends React.Component {
<input className="ds-input-auto" onChange={handleChange} type="text" id="refAttrs" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Filter</ControlLabel>
</Col>
@@ -340,13 +335,12 @@ class AddReferralModal extends React.Component {
<input onChange={handleChange} className="ds-input-auto" type="text" id="refFilter" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Scope</ControlLabel>
</Col>
<Col sm={9}>
- <select className="btn btn-default dropdown" onChange={handleChange} defaultValue="" name="refScope">
+ <select className="btn btn-default dropdown" onChange={handleChange} defaultValue="" id="refScope">
<option />
<option>sub</option>
<option>one</option>
diff --git a/src/cockpit/389-console/src/lib/database/suffix.jsx b/src/cockpit/389-console/src/lib/database/suffix.jsx
index 8413799..9be54a8 100644
--- a/src/cockpit/389-console/src/lib/database/suffix.jsx
+++ b/src/cockpit/389-console/src/lib/database/suffix.jsx
@@ -1,6 +1,6 @@
import cockpit from "cockpit";
import React from "react";
-import { ConfirmPopup } from "../notifications.jsx";
+import { DoubleConfirmModal } from "../notifications.jsx";
import { AttrEncryption } from "./attrEncryption.jsx";
import { SuffixConfig } from "./suffixConfig.jsx";
import { SuffixReferrals } from "./referrals.jsx";
@@ -73,6 +73,9 @@ export class Suffix extends React.Component {
importSpinner: false,
showConfirmLDIFImport: false,
deleleLDIFName: "",
+ modalChecked: false,
+ modalSpinning: false,
+ includeReplData: false,
// Reindex all
showReindexConfirm: false,
// Create Sub Suffix
@@ -128,12 +131,15 @@ export class Suffix extends React.Component {
this.handleLinkChange = this.handleLinkChange.bind(this);
// Suffix config
this.saveSuffixConfig = this.saveSuffixConfig.bind(this);
- // Attr Encrypt Modal
this.showDeleteConfirm = this.showDeleteConfirm.bind(this);
this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
this.doDelete = this.doDelete.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -176,6 +182,8 @@ export class Suffix extends React.Component {
this.setState({
showConfirmLDIFImport: true,
importLDIFName: item.name,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -183,6 +191,8 @@ export class Suffix extends React.Component {
// call importLDIF
this.setState({
showConfirmLDIFImport: false,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -240,6 +250,7 @@ export class Suffix extends React.Component {
attrEncryption: false,
showExportModal: true,
exportSpinner: false,
+ includeReplData: false,
errObj: {},
});
}
@@ -288,6 +299,10 @@ export class Suffix extends React.Component {
export_cmd.push("--encrypted");
}
+ if (this.state.includeReplData) {
+ export_cmd.push("--replication");
+ }
+
this.setState({
exportSpinner: true,
});
@@ -323,13 +338,17 @@ export class Suffix extends React.Component {
//
showReindexConfirm() {
this.setState({
- showReindexConfirm: true
+ showReindexConfirm: true,
+ modalChecked: false,
+ modalSpinning: false,
});
}
closeReindexConfirm() {
this.setState({
- showReindexConfirm: false
+ showReindexConfirm: false,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -624,13 +643,17 @@ export class Suffix extends React.Component {
//
showDeleteConfirm(item) {
this.setState({
- showDeleteConfirm: true
+ showDeleteConfirm: true,
+ modalSpinning: false,
+ modalChecked: false
});
}
closeDeleteConfirm() {
this.setState({
- showDeleteConfirm: false
+ showDeleteConfirm: false,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -733,11 +756,6 @@ export class Suffix extends React.Component {
suffixIcon = "leaf";
}
- const confirm_msg =
- <span>
- Are you sure you want to import: <b>{this.state.importLDIFName}</b> ?
- </span>;
-
return (
<div id="suffix-page">
<Row>
@@ -746,7 +764,7 @@ export class Suffix extends React.Component {
</Col>
<Col sm={2}>
<div>
- <DropdownButton className="ds-action-button" bsStyle="primary" title="Suffix Tasks" id="mydropdown">
+ <DropdownButton className="ds-action-button" bsStyle="primary" title="Suffix Tasks" id="mydropdown" pullRight>
<MenuItem eventKey="1" onClick={this.showImportModal} title="Import an LDIF file to initialize the database">
Initialize Suffix
</MenuItem>
@@ -770,10 +788,9 @@ export class Suffix extends React.Component {
</div>
</Col>
</Row>
- <p />
<TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
- <div>
+ <div className="ds-margin-top-xlg">
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
<div dangerouslySetInnerHTML={{__html: 'Settings'}} />
@@ -856,14 +873,18 @@ export class Suffix extends React.Component {
</TabContent>
</div>
</TabContainer>
-
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showDeleteConfirm}
closeHandler={this.closeDeleteConfirm}
- actionFunc={this.doDelete}
- actionParam={this.props.suffix}
- msg="Are you really sure you want to delete the database?"
- msgContent={this.props.suffix}
+ handleChange={this.handleChange}
+ actionHandler={this.doDelete}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Delete Replication Agreement"
+ mMsg="Are you really sure you want to delete the database?"
+ mSpinningMsg="Deleting database ..."
+ mBtnName="Delete Database"
/>
<CreateLinkModal
showModal={this.state.showLinkModal}
@@ -896,15 +917,19 @@ export class Suffix extends React.Component {
rows={this.props.LDIFRows}
suffix={this.props.suffix}
/>
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showConfirmLDIFImport}
closeHandler={this.closeConfirmLDIFImport}
- actionFunc={this.importLDIF}
- actionParam={this.state.importLDIFName}
- msg={confirm_msg}
- msgContent="WARNING: This action will permanently overwrite the current database!"
+ handleChange={this.handleChange}
+ actionHandler={this.importLDIF}
+ spinning={this.state.modalSpinning}
+ item={this.state.importLDIFName}
+ checked={this.state.modalChecked}
+ mTitle="Initialize Database From LDIF"
+ mMsg="Are you sure you want to initialize the database (it will permanently overwrite the current database)?"
+ mSpinningMsg="Initializing Database ..."
+ mBtnName="Initialize Database"
/>
-
<ExportModal
showModal={this.state.showExportModal}
closeHandler={this.closeExportModal}
@@ -913,13 +938,18 @@ export class Suffix extends React.Component {
spinning={this.state.exportSpinner}
error={this.state.errObj}
/>
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showReindexConfirm}
closeHandler={this.closeReindexConfirm}
- actionFunc={this.doReindex}
- actionParam={this.props.suffix}
- msg="Are you sure you want to reindex all the attribute indexes?"
- msgContent=""
+ handleChange={this.handlChange}
+ actionHandler={this.doReindex}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Reindex All Attributes"
+ mMsg="Are you sure you want to reindex all the attribute indexes?"
+ mSpinningMsg="Reindexing Database ..."
+ mBtnName="Reindex"
/>
<ReindexModal
showModal={this.state.showReindexModal}
@@ -949,6 +979,7 @@ Suffix.propTypes = {
data: PropTypes.object,
attrs: PropTypes.array,
LDIFRows: PropTypes.array,
+ enableTree: PropTypes.func,
};
Suffix.defaultProps = {
@@ -966,5 +997,6 @@ Suffix.defaultProps = {
dbtype: "",
data: {},
attrs: [],
- LDIFRows: []
+ LDIFRows: [],
+ enableTree: PropTypes.noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/suffixConfig.jsx b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
index 99c1804..e9b71b2 100644
--- a/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
+++ b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
@@ -6,6 +6,7 @@ import {
Row,
Col,
ControlLabel,
+ Form,
} from "patternfly-react";
export class SuffixConfig extends React.Component {
@@ -15,76 +16,82 @@ export class SuffixConfig extends React.Component {
const cacheValue = this.props.cachesize + " (auto-sized)";
const cachememValue = this.props.cachememsize + " (auto-sized)";
cacheInputs =
- <div>
- <Row className="ds-margin-top" title="The entry cache size setting is being auto-sized and is read-only - see Global Database Configuration">
- <Col componentClass={ControlLabel} sm={3}>
- Entry Cache Size (bytes)
+ <Form horizontal>
+ <Row className="ds-margin-top" title="The entry cache size in bytes setting is being auto-sized and is read-only - see Global Database Configuration">
+ <Col componentClass={ControlLabel} sm={4}>
+ Entry Cache Size
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<input disabled value={cachememValue} className="ds-input-auto" type="text" id="cachememsize" />
</Col>
</Row>
<Row className="ds-margin-top" title="The entry cache max entries setting is being auto-sized and is read-only - see Global Database Configuration">
- <Col componentClass={ControlLabel} sm={3}>
+ <Col componentClass={ControlLabel} sm={4}>
Entry Cache Max Entries
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<input disabled value={cacheValue} className="ds-input-auto" type="text" id="cachesize" />
</Col>
</Row>
- <Row className="ds-margin-top" title="The available memory space for the DN cache. The DN cache is similar to the entry cache for a database, only its table stores only the entry ID and the entry DN (nsslapd-dncachememsize).">
- <Col componentClass={ControlLabel} sm={3}>
- DN Cache Size (bytes)
+ <Row className="ds-margin-top" title="The available memory space in bytes for the DN cache. The DN cache is similar to the entry cache for a database, only its table stores only the entry ID and the entry DN (nsslapd-dncachememsize).">
+ <Col componentClass={ControlLabel} sm={4}>
+ DN Cache Size
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<input onChange={this.props.handleChange} value={this.props.dncachememsize} className="ds-input-auto" type="text" id="dncachememsize" />
</Col>
</Row>
- </div>;
+ </Form>;
} else {
cacheInputs =
- <div>
- <Row className="ds-margin-top" title="The size for the available memory space for the entry cache (nsslapd-cachememsize).">
- <Col componentClass={ControlLabel} sm={3}>
- Entry Cache Size (bytes)
+ <Form horizontal>
+ <Row className="ds-margin-top" title="The size for the available memory space in bytes for the entry cache (nsslapd-cachememsize).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Entry Cache Size
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<input onChange={this.props.handleChange} value={this.props.cachememsize} className="ds-input-auto" type="text" id="cachememsize" />
</Col>
</Row>
<Row className="ds-margin-top" title="The number of entries to keep in the entry cache, use'-1' for unlimited (nsslapd-cachesize).">
- <Col componentClass={ControlLabel} sm={3}>
+ <Col componentClass={ControlLabel} sm={4}>
Entry Cache Max Entries
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<input onChange={this.props.handleChange} value={this.props.cachesize} className="ds-input-auto" type="text" id="cachesize" />
</Col>
</Row>
- <Row className="ds-margin-top" title="the available memory space for the DN cache. The DN cache is similar to the entry cache for a database, only its table stores only the entry ID and the entry DN (nsslapd-dncachememsize).">
- <Col componentClass={ControlLabel} sm={3}>
- DN Cache Size (bytes)
+ <Row className="ds-margin-top" title="the available memory space in bytes for the DN cache. The DN cache is similar to the entry cache for a database, only its table stores only the entry ID and the entry DN (nsslapd-dncachememsize).">
+ <Col componentClass={ControlLabel} sm={4}>
+ DN Cache Size
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<input onChange={this.props.handleChange} value={this.props.dncachememsize} className="ds-input-auto" type="text" id="dncachememsize" />
</Col>
</Row>
- </div>;
+ </Form>;
}
return (
<div className="ds-margin-top-lg">
- <p />
{cacheInputs}
- <p />
- <div>
- <div>
- <input type="checkbox" onChange={this.props.handleChange} checked={this.props.readOnly} className="ds-config-checkbox" id="readOnly" /><label
- htmlFor="readOnly" className="ds-label" title="Put database in Read-Only mode (nsslapd-readonly)."> Database Read-Only Mode</label>
- </div>
- <div>
- <input type="checkbox" onChange={this.props.handleChange} checked={this.props.requireIndex} className="ds-config-checkbox" id="requireIndex" /><label
- htmlFor="requireIndex" className="ds-label" title="Block unindexed searches on this suffix (nsslapd-require-index)."> Block Unindexed Searches</label>
- </div>
- </div>
+ <Form horizontal>
+ <Row className="ds-margin-top-lg" title="Put database in Read-Only mode (nsslapd-readonly).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Database Read-Only Mode
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.props.handleChange} checked={this.props.readOnly} className="ds-config-checkbox" id="readOnly" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Block unindexed searches on this suffix (nsslapd-require-index).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Block Unindexed Searches
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox" onChange={this.props.handleChange} checked={this.props.requireIndex} className="ds-config-checkbox" id="requireIndex" />
+ </Col>
+ </Row>
+ </Form>
<div className="ds-margin-top-lg">
<button className="btn btn-primary save-button" onClick={this.props.saveHandler}>Save Configuration</button>
</div>
diff --git a/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
index 9c27224..582bfd2 100644
--- a/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
+++ b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
@@ -831,8 +831,7 @@ class AddVLVModal extends React.Component {
{nameInput}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Search Base</ControlLabel>
</Col>
@@ -841,8 +840,7 @@ class AddVLVModal extends React.Component {
onChange={handleChange} type="text" id="vlvBase" defaultValue={base} />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Search Filter</ControlLabel>
</Col>
@@ -851,16 +849,14 @@ class AddVLVModal extends React.Component {
onChange={handleChange} type="text" id="vlvFilter" defaultValue={filter} />
</Col>
</Row>
- <p />
{vlvscope}
<hr />
<div>
- <p />
- <div>
+ <div className="ds-margin-top">
{sortTable}
- <p />
<Typeahead
multiple
+ className="ds-margin-top"
id="vlvsortindex"
onChange={values => {
this.handleTypeaheadChange(values);
@@ -870,8 +866,7 @@ class AddVLVModal extends React.Component {
placeholder="Start typing attribute names to create a sort index"
ref={(typeahead) => { this.typeahead = typeahead }}
/>
- <p />
- <button type="button" onClick={this.updateSorts}>Add Sort Index</button>
+ <button className="ds-margin-top" type="button" onClick={this.updateSorts}>Add Sort Index</button>
</div>
</div>
<hr />
diff --git a/src/cockpit/389-console/src/lib/monitor/accesslog.jsx b/src/cockpit/389-console/src/lib/monitor/accesslog.jsx
index b791c5d..42053f4 100644
--- a/src/cockpit/389-console/src/lib/monitor/accesslog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/accesslog.jsx
@@ -17,6 +17,10 @@ export class AccessLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -73,8 +77,7 @@ export class AccessLogMonitor extends React.Component {
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={6}>
{selectLines}
</Col>
@@ -102,6 +105,7 @@ AccessLogMonitor.propTypes = {
reloading: PropTypes.bool,
refreshing: PropTypes.bool,
lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
AccessLogMonitor.defaultProps = {
@@ -111,7 +115,8 @@ AccessLogMonitor.defaultProps = {
reload: noop,
reloading: false,
refreshing: false,
- line: "50"
+ line: "50",
+ enableTree: noop,
};
export default AccessLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx b/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx
index 9c84775..b2ddca9 100644
--- a/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx
@@ -17,6 +17,10 @@ export class AuditFailLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -60,7 +64,7 @@ export class AuditFailLogMonitor extends React.Component {
return (
<div id="monitor-log-auditfail-page">
<Row>
- <Col sm={3}>
+ <Col sm={4}>
<ControlLabel className="ds-suffix-header">
Audit Failure Log
<Icon className="ds-left-margin ds-refresh"
@@ -69,12 +73,11 @@ export class AuditFailLogMonitor extends React.Component {
/>
</ControlLabel>
</Col>
- <Col sm={9} className="ds-float-left">
+ <Col sm={8} className="ds-float-left">
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={6}>
{selectLines}
</Col>
@@ -102,6 +105,7 @@ AuditFailLogMonitor.propTypes = {
reloading: PropTypes.bool,
refreshing: PropTypes.bool,
lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
AuditFailLogMonitor.defaultProps = {
@@ -111,7 +115,8 @@ AuditFailLogMonitor.defaultProps = {
reload: noop,
reloading: false,
refreshing: false,
- line: "50"
+ line: "50",
+ enableTree: noop,
};
export default AuditFailLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/auditlog.jsx b/src/cockpit/389-console/src/lib/monitor/auditlog.jsx
index 29bf0e4..8d1c9f3 100644
--- a/src/cockpit/389-console/src/lib/monitor/auditlog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/auditlog.jsx
@@ -17,6 +17,10 @@ export class AuditLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -60,7 +64,7 @@ export class AuditLogMonitor extends React.Component {
return (
<div id="monitor-log-audit-page">
<Row>
- <Col sm={3}>
+ <Col sm={4}>
<ControlLabel className="ds-suffix-header">
Audit Log
<Icon className="ds-left-margin ds-refresh"
@@ -69,12 +73,11 @@ export class AuditLogMonitor extends React.Component {
/>
</ControlLabel>
</Col>
- <Col sm={9} className="ds-float-left">
+ <Col sm={8} className="ds-float-left">
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={6}>
{selectLines}
</Col>
@@ -102,6 +105,7 @@ AuditLogMonitor.propTypes = {
reloading: PropTypes.bool,
refreshing: PropTypes.bool,
lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
AuditLogMonitor.defaultProps = {
@@ -111,7 +115,8 @@ AuditLogMonitor.defaultProps = {
reload: noop,
reloading: false,
refreshing: false,
- line: "50"
+ line: "50",
+ enableTree: noop,
};
export default AuditLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx
index 9c108ff..717e03d 100644
--- a/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx
@@ -6,9 +6,14 @@ import {
Col,
ControlLabel,
Icon,
+ noop
} from "patternfly-react";
export class ChainingMonitor extends React.Component {
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
return (
<div id="monitor-server-page" className="container-fluid">
@@ -23,9 +28,7 @@ export class ChainingMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <p />
- <hr />
- <div>
+ <div className="ds-margin-top-lg">
<Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>
@@ -165,13 +168,15 @@ export class ChainingMonitor extends React.Component {
ChainingMonitor.propTypes = {
suffix: PropTypes.string,
bename: PropTypes.string,
- data: PropTypes.object
+ data: PropTypes.object,
+ enableTree: PropTypes.func,
};
ChainingMonitor.defaultProps = {
suffix: "",
bename: "",
- data: {}
+ data: {},
+ enableTree: noop,
};
export default ChainingMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
index 6f12c8d..ea5dcd8 100644
--- a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
@@ -12,6 +12,7 @@ import {
Row,
Col,
ControlLabel,
+ Form,
Icon,
noop,
} from "patternfly-react";
@@ -22,10 +23,15 @@ export class DatabaseMonitor extends React.Component {
super(props);
this.state = {
activeKey: 1,
+ disableTabs: false,
};
this.handleNavSelect = this.handleNavSelect.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -82,7 +88,7 @@ export class DatabaseMonitor extends React.Component {
}
return (
- <div id="db-content" className="container-fluid">
+ <div id="db-content">
<Row>
<Col sm={12} className="ds-word-wrap">
<ControlLabel className="ds-suffix-header">
@@ -94,7 +100,7 @@ export class DatabaseMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <TabContainer className="ds-margin-top-lg" id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -125,83 +131,70 @@ export class DatabaseMonitor extends React.Component {
<b className="ds-left-margin">DB Cache Hit Ratio</b>
</div>
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- Database Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Database Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dbcachehitratio} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- Database Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Database Cache Tries
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dbcachetries} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- Database Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Database Cache Hits
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dbcachehits} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- Cache Pages Read
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Cache Pages Read
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dbcachepagein} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- Cache Pages Written
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Cache Pages Written
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dbcachepageout} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- Read-Only Page Evictions
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Read-Only Page Evictions
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dbcacheroevict} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- Read-Write Page Evictions
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Read-Write Page Evictions
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dbcacherwevict} size="28" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</TabPane>
<TabPane eventKey={2}>
<div className="ds-margin-top-lg">
<div className="ds-container">
+ <div className="ds-divider" />
<div className="ds-left-margin">
<DonutChart
id="monitor-db-cache-ndn-hitratio-chart"
@@ -219,6 +212,8 @@ export class DatabaseMonitor extends React.Component {
/>
<b>NDN Cache Hit Ratio</b>
</div>
+ <div className="ds-divider" />
+ <div className="ds-divider" />
<div className="ds-chart-right">
<PieChart
id="monitor-db-cache-ndn-util-chart"
@@ -243,6 +238,7 @@ export class DatabaseMonitor extends React.Component {
}}
title={{type: 'pie'}}
legend={{show: true, position: 'right'}}
+ unloadBeforeLoad
/>
<b>NDN Cache Utilization</b>
<div>
@@ -251,98 +247,80 @@ export class DatabaseMonitor extends React.Component {
</div>
</div>
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.normalizeddncachehitratio} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache Tries
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.normalizeddncachetries} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache Hits
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.normalizeddncachehits} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache Evictions
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache Evictions
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.normalizeddncacheevictions} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache Max Size
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache Max Size
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.maxnormalizeddncachesize} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Current Cache Size
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Current Cache Size
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.currentnormalizeddncachesize} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache DN Count
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache DN Count
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.currentnormalizeddncachecount} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache Thread Size
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache Thread Size
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.normalizeddncachethreadsize} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>
- NDN Cache Thread Slots
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ NDN Cache Thread Slots
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.normalizeddncachethreadslots} size="28" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
</TabPane>
</TabContent>
@@ -357,12 +335,14 @@ export class DatabaseMonitor extends React.Component {
DatabaseMonitor.propTypes = {
data: PropTypes.object,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
DatabaseMonitor.defaultProps = {
data: {},
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
export default DatabaseMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/errorlog.jsx b/src/cockpit/389-console/src/lib/monitor/errorlog.jsx
index 3a1af45..b8c28fc 100644
--- a/src/cockpit/389-console/src/lib/monitor/errorlog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/errorlog.jsx
@@ -17,6 +17,10 @@ export class ErrorLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -73,8 +77,7 @@ export class ErrorLogMonitor extends React.Component {
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={5}>
{selectLines}
</Col>
@@ -122,8 +125,8 @@ ErrorLogMonitor.propTypes = {
handleSevLevel: PropTypes.func,
refreshing: PropTypes.bool,
handleRefresh: PropTypes.func,
- lines: PropTypes.string
-
+ lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
ErrorLogMonitor.defaultProps = {
@@ -134,7 +137,8 @@ ErrorLogMonitor.defaultProps = {
handleSevLevel: noop,
refreshing: false,
handleRefresh: noop,
- lines: "50"
+ lines: "50",
+ enableTree: noop,
};
export default ErrorLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
index 5593cb0..519acb7 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -76,8 +76,7 @@ class ReplLoginModal extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>
Password
@@ -559,8 +558,7 @@ class ConflictCompareModal extends React.Component {
<Row>
<textarea className="ds-conflict" value={conflict} readOnly />
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<p>Child Entries: <b>{conflictChildren}</b></p>
</Row>
</Col>
@@ -572,8 +570,7 @@ class ConflictCompareModal extends React.Component {
<Row>
<textarea className="ds-conflict" value={valid} readOnly />
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<p>Child Entries: <b>{validChildren}</b></p>
</Row>
</Col>
@@ -598,8 +595,7 @@ class ConflictCompareModal extends React.Component {
<input onChange={handleConvertChange} type="text" placeholder="Enter new RDN here" size="30" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<h4>Or, you can replace, or swap, the <b>Valid Entry</b> (and its child entries) with the <b>Conflict Entry</b></h4>
</Row>
<Row>
@@ -615,8 +611,7 @@ class ConflictCompareModal extends React.Component {
</Button>
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<h4>Or, you can delete the <b>Conflict Entry</b></h4>
</Row>
<Row>
diff --git a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
index 5a6eb92..ffbb5cc 100644
--- a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
@@ -105,6 +105,10 @@ export class ReplMonitor extends React.Component {
this.closeConfirmSwapConflict = this.closeConfirmSwapConflict.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
convertConflict (dn) {
let cmd = ["dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
"repl-conflict", "convert", dn, "--new-rdn=" + this.state.convertRDN];
@@ -753,7 +757,7 @@ export class ReplMonitor extends React.Component {
return (
<div id="monitor-suffix-page" className="ds-tab-table">
- <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <TabContainer className="ds-margin-top-lg" id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -777,8 +781,8 @@ export class ReplMonitor extends React.Component {
pokeAgmt={this.pokeAgmt}
viewAgmt={this.showAgmtModal}
/>
- <p />
<Button
+ className="ds-margin-top"
bsStyle="primary"
onClick={this.getLagReportCreds}
title="Display report that shows the lag time and replication status of each agreement in relationship to its replica"
@@ -907,6 +911,7 @@ ReplMonitor.propTypes = {
reloadAgmts: PropTypes.func,
reloadWinsyncAgmts: PropTypes.func,
reloadConflicts: PropTypes.func,
+ enableTree: PropTypes.func,
};
ReplMonitor.defaultProps = {
@@ -917,6 +922,7 @@ ReplMonitor.defaultProps = {
reloadAgmts: noop,
reloadWinsyncAgmts: noop,
reloadConflicts: noop,
+ enableTree: noop,
};
export default ReplMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
index 93c2aef..f87fb00 100644
--- a/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
@@ -14,6 +14,7 @@ import {
Row,
Col,
ControlLabel,
+ Form,
Icon,
noop
} from "patternfly-react";
@@ -27,6 +28,10 @@ export class ServerMonitor extends React.Component {
this.handleNavSelect = this.handleNavSelect.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -52,7 +57,7 @@ export class ServerMonitor extends React.Component {
</Col>
</Row>
- <TabContainer id="server-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <TabContainer className="ds-margin-top-lg" id="server-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -65,7 +70,7 @@ export class ServerMonitor extends React.Component {
<TabContent>
<TabPane eventKey={1}>
- <div className="ds-margin-top-lg">
+ <Form horizontal className="ds-margin-top-lg">
<Row>
<Col componentClass={ControlLabel} sm={4}>
Server Instance
@@ -98,9 +103,9 @@ export class ServerMonitor extends React.Component {
<input type="text" className="ds-input-auto" id="monitor-server-uptime" value={uptime} readOnly />
</Col>
</Row>
- </div>
+ </Form>
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
<Col componentClass={ControlLabel} sm={4}>
Worker Threads
@@ -181,7 +186,7 @@ export class ServerMonitor extends React.Component {
<input type="text" className="ds-input-auto" id="monitor-server-bytessent" value={this.props.data.bytessent} readOnly />
</Col>
</Row>
- </div>
+ </Form>
</TabPane>
<TabPane eventKey={2}>
<ConnectionTable conns={this.props.data.connection} />
@@ -197,13 +202,15 @@ export class ServerMonitor extends React.Component {
ServerMonitor.propTypes = {
serverId: PropTypes.string,
data: PropTypes.object,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
ServerMonitor.defaultProps = {
serverId: "",
data: {},
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
export default ServerMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx
index 179d271..1d3eb5d 100644
--- a/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx
@@ -4,15 +4,20 @@ import "../../css/ds.css";
import {
Row,
Col,
+ Form,
Icon,
ControlLabel,
noop
} from "patternfly-react";
export class SNMPMonitor extends React.Component {
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
return (
- <div id="db-global-page">
+ <div id="snmp-page">
<Row>
<Col sm={12} className="ds-word-wrap">
<ControlLabel className="ds-suffix-header">
@@ -24,185 +29,185 @@ export class SNMPMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <div className="ds-margin-top-med">
+ <Form horizontal className="ds-margin-top-lg">
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Anonymous Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Anonymous Binds
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.anonymousbinds} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Referrals</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Referrals
</Col>
- <Col sm={2}>
+ <Col sm={3}>
<input type="text" value={this.props.data.referrals} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Unauthenticated Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Unauthenticated Binds
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.anonymousbinds} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Returned Referrals</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Returned Referrals
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.referralsreturned} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Simple Auth Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Simple Auth Binds
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.simpleauthbinds} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Bind Security Errors</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Bind Security Errors
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.bindsecurityerrors} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Strong Auth Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Strong Auth Binds
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.strongauthbinds} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Security Errors</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Security Errors
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.securityerrors} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Initiated Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Initiated Operations
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.inops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Errors</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Errors
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.errors} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Compare Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Compare Operations
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.compareops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Current Connections</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Current Connections
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.connections} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Add Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Add Operations
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.addentryops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Total Connections</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Total Connections
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.connectionseq} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Delete Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Delete Operations
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.removeentryops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Conns in Max Threads</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Conns in Max Threads
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.connectionsinmaxthreads} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Modify Operation</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Modify Operation
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.modifyentryops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Conns Exceeded Max Threads</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Conns Exceeded Max Threads
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.connectionsmaxthreadscount} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>ModRDN Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ ModRDN Operations
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.modifyrdnops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Bytes Received</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Bytes Received
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.bytesrecv} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Search Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Search Operations
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.searchops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Bytes Sent</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Bytes Sent
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.bytessent} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>One Level Searches</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ One Level Searches
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.onelevelsearchops} size="10" readOnly />
</Col>
- <Col sm={4}>
- <ControlLabel>Entries Sent</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entries Sent
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.entriesreturned} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={4}>
- <ControlLabel>Whole Tree Searches</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Whole Tree Searches
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.wholesubtreesearchops} size="10" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
);
}
@@ -212,12 +217,14 @@ export class SNMPMonitor extends React.Component {
SNMPMonitor.propTypes = {
data: PropTypes.object,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
SNMPMonitor.defaultProps = {
data: {},
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
export default SNMPMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
index 8166e1c..2174290 100644
--- a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
@@ -7,6 +7,7 @@ import {
ControlLabel,
Row,
Col,
+ Form,
Icon,
Nav,
NavItem,
@@ -26,6 +27,10 @@ export class SuffixMonitor extends React.Component {
this.handleNavSelect = this.handleNavSelect.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -124,8 +129,7 @@ export class SuffixMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <p />
- <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <TabContainer className="ds-margin-top-lg" id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -159,6 +163,8 @@ export class SuffixMonitor extends React.Component {
/>
<b>Entry Cache Hit Ratio</b>
</div>
+ <div className="ds-divider" />
+ <div className="ds-divider" />
<div className="ds-chart-right" title="How much of the allocated cache space is used (max size vs current size). If the chart is RED then you should to increase the max cache size because the cache hit ratio is below 90%">
<PieChart
id="monitor-entry-util-pie"
@@ -184,6 +190,7 @@ export class SuffixMonitor extends React.Component {
}}
title={{type: 'pie'}}
legend={{show: true, position: 'right'}}
+ unloadBeforeLoad
/>
<b>Entry Cache Utilization</b>
<div>
@@ -191,80 +198,65 @@ export class SuffixMonitor extends React.Component {
</div>
</div>
</div>
- <p />
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entry Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.entrycachehitratio} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entry Cache Tries
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.entrycachetries} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entry Cache Hits
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.entrycachehits} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Max Size
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entry Cache Max Size
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.maxentrycachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Current Size
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entry Cache Current Size
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.currententrycachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Max Entries
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entry Cache Max Entries
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.maxentrycachecount} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Count
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entry Cache Count
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.currententrycachecount} size="35" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
</TabPane>
@@ -289,6 +281,8 @@ export class SuffixMonitor extends React.Component {
/>
<b className="ds-left-margin">DN Cache Hit Ratio</b>
</div>
+ <div className="ds-divider" />
+ <div className="ds-divider" />
<div className="ds-chart-right" title="How much of the allocated cache space is used (max size vs current size). If the chart is RED then you should to increase the max cache size because the cache hit ratio is below 90%">
<PieChart
id="monitor-entry-util-pie"
@@ -314,6 +308,7 @@ export class SuffixMonitor extends React.Component {
}}
title={{type: 'pie'}}
legend={{show: true, position: 'right'}}
+ unloadBeforeLoad
/>
<div className="ds-left-margin">
<b>DN Cache Utilization</b>
@@ -324,78 +319,64 @@ export class SuffixMonitor extends React.Component {
</div>
</div>
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ DN Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dncachehitratio} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ DN Cache Tries
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dncachetries} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ DN Cache Hits
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.dncachehits} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Max Size
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ DN Cache Max Size
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.maxdncachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Current Size
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ DN Cache Current Size
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.currentdncachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Max Count
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ DN Cache Max Count
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.maxdncachecount} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Current Count
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ DN Cache Current Count
</Col>
<Col sm={3}>
<input type="text" value={this.props.data.currentdncachecount} size="35" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
</TabPane>
</TabContent>
@@ -411,6 +392,7 @@ SuffixMonitor.propTypes = {
data: PropTypes.object,
bename: PropTypes.string,
reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
SuffixMonitor.defaultProps = {
@@ -418,6 +400,7 @@ SuffixMonitor.defaultProps = {
data: {},
bename: "",
reload: noop,
+ enableTree: noop,
};
export default SuffixMonitor;
diff --git a/src/cockpit/389-console/src/lib/notifications.jsx b/src/cockpit/389-console/src/lib/notifications.jsx
index ad0b87f..990cdc7 100644
--- a/src/cockpit/389-console/src/lib/notifications.jsx
+++ b/src/cockpit/389-console/src/lib/notifications.jsx
@@ -1,8 +1,16 @@
import React from "react";
import PropTypes from "prop-types";
import {
+ Button,
+ Checkbox,
+ Col,
+ Form,
Icon,
MessageDialog,
+ Modal,
+ noop,
+ Row,
+ Spinner,
TimedToastNotification,
ToastNotificationList
} from "patternfly-react";
@@ -103,4 +111,119 @@ class ConfirmPopup extends React.Component {
}
}
+export class DoubleConfirmModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ actionHandler,
+ checked,
+ spinning,
+ item,
+ mTitle,
+ mMsg,
+ mSpinningMsg,
+ mBtnName,
+ } = this.props;
+ let spinner = "";
+ let saveDisabled = true;
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />{mSpinningMsg}
+ </div>
+ </Row>;
+ saveDisabled = true;
+ }
+ if (checked) {
+ saveDisabled = false;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {mTitle}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <h4>{mMsg}</h4>
+ <h5 className="ds-center ds-margin-top-xlg"><b>{item}</b></h5>
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-center">
+ <Checkbox
+ id="modalChecked"
+ defaultChecked={checked}
+ onChange={handleChange}
+ >
+ <b>Yes</b>, I am sure.
+ </Checkbox>
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={actionHandler}
+ disabled={saveDisabled}
+ >
+ {mBtnName}
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+DoubleConfirmModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ actionHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ item: PropTypes.string,
+ checked: PropTypes.bool,
+ mTitle: PropTypes.string,
+ mMsg: PropTypes.string,
+ mSpinningMsg: PropTypes.string,
+ mBtnName: PropTypes.string,
+};
+
+DoubleConfirmModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ actionHandler: noop,
+ spinning: false,
+ item: "",
+ checked: false,
+ mTitle: "",
+ mMsg: "",
+ mSpinningMsg: "",
+ mBtnName: "",
+};
+
export { NotificationController, ConfirmPopup };
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
index b0e4a21..966c60f 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
@@ -216,8 +216,9 @@ class PluginBasicConfig extends React.Component {
)}
</Row>
</Form>
- <p />
- {this.props.children}
+ <div className="ds-margin-top">
+ {this.props.children}
+ </div>
<CustomCollapse>
<Row>
<Col sm={12}>
diff --git a/src/cockpit/389-console/src/lib/replication/replAgmts.jsx b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx
new file mode 100644
index 0000000..e3c4781
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx
@@ -0,0 +1,1150 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup, DoubleConfirmModal } from "../notifications.jsx";
+import { ReplAgmtTable } from "./replTables.jsx";
+import { ReplAgmtModal } from "./replModals.jsx";
+import { log_cmd, valid_dn, valid_port } from "../tools.jsx";
+import PropTypes from "prop-types";
+import {
+ Button,
+ noop,
+} from "patternfly-react";
+
+export class ReplAgmts extends React.Component {
+ _mounted = false;
+ constructor(props) {
+ super(props);
+ this.state = {
+ showCreateAgmtModal: false,
+ showEditAgmtModal: false,
+ showConfirmDeleteAgmt: false,
+ showConfirmInitAgmt: false,
+ showConfirmEnableAgmt: false,
+ showConfirmDisableAgmt: false,
+ errObj: {},
+ modalMsg: "",
+ modalScheduleMsg: "",
+ savingAgmt: false,
+ mounted: false,
+ // Create agreement
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindMethod: "SIMPLE",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtStripAttrs: [],
+ agmtFracAttrs: [],
+ agmtFracInitAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "0",
+ agmtEndTime: "0",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ modalChecked: false,
+ modalSpinning: false,
+ // Init agmt
+ agmtInitCounter: 0,
+ agmtInitIntervals: [],
+ };
+ this.showCreateAgmtModal = this.showCreateAgmtModal.bind(this);
+ this.closeCreateAgmtModal = this.closeCreateAgmtModal.bind(this);
+ this.closeEditAgmtModal = this.closeEditAgmtModal.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleModalChange = this.handleModalChange.bind(this);
+ this.handleTAFracInitAttrChange = this.handleTAFracInitAttrChange.bind(this);
+ this.handleTAFracAttrChange = this.handleTAFracAttrChange.bind(this);
+ this.handleTAStripAttrChange = this.handleTAStripAttrChange.bind(this);
+ this.handleTAFracInitAttrChangeEdit = this.handleTAFracInitAttrChangeEdit.bind(this);
+ this.handleTAFracAttrChangeEdit = this.handleTAFracAttrChangeEdit.bind(this);
+ this.handleTAStripAttrChangeEdit = this.handleTAStripAttrChangeEdit.bind(this);
+ this.createAgmt = this.createAgmt.bind(this);
+ this.showEditAgmt = this.showEditAgmt.bind(this);
+ this.saveAgmt = this.saveAgmt.bind(this);
+ this.pokeAgmt = this.pokeAgmt.bind(this);
+ this.initAgmt = this.initAgmt.bind(this);
+ this.enableAgmt = this.enableAgmt.bind(this);
+ this.disableAgmt = this.disableAgmt.bind(this);
+ this.deleteAgmt = this.deleteAgmt.bind(this);
+ this.showConfirmDeleteAgmt = this.showConfirmDeleteAgmt.bind(this);
+ this.closeConfirmDeleteAgmt = this.closeConfirmDeleteAgmt.bind(this);
+ this.showConfirmInitAgmt = this.showConfirmInitAgmt.bind(this);
+ this.closeConfirmInitAgmt = this.closeConfirmInitAgmt.bind(this);
+ this.confirmToggle = this.confirmToggle.bind(this);
+ this.closeConfirmEnableAgmt = this.closeConfirmEnableAgmt.bind(this);
+ this.closeConfirmDisableAgmt = this.closeConfirmDisableAgmt.bind(this);
+ this.watchAgmtInit = this.watchAgmtInit.bind(this);
+ }
+
+ componentDidMount () {
+ this._mounted = true;
+ }
+
+ componentWillUnmount () {
+ this._mounted = false;
+ }
+
+ listEqual(old_values, new_values) {
+ if (old_values.length != new_values.length) {
+ return false;
+ }
+ for (let i = old_values.length; i--;) {
+ if (old_values[i] != new_values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ handleModalChange (e) {
+ this.setState({
+ [e.target.id]: e.target.checked,
+ });
+ }
+
+ handleChange (e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let time_val = "";
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ let all_good = true;
+ let modal_msg = "";
+ let modal_schedule_msg = "";
+ let edit = false;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ if (e.target.name == "agmt-modal-edit") {
+ let orig_attr = "_" + e.target.id;
+ let attr = e.target.id;
+ all_good = false;
+ if ((attr != 'agmtHost' && this.state.agmtHost != this.state._agmtHost) ||
+ (attr != 'agmtPort' && this.state.agmtPort != this.state._agmtPort) ||
+ (attr != 'agmtBindDN' && this.state.agmtBindDN != this.state._agmtBindDN) ||
+ (attr != 'agmtBindMethod' && this.state.agmtBindMethod != this.state._agmtBindMethod) ||
+ (attr != 'agmtProtocol' && this.state.agmtProtocol != this.state._agmtProtocol) ||
+ (attr != 'agmtSync' && this.state.agmtSync != this.state._agmtSync) ||
+ (attr != 'agmtStripAttrs' && !this.listEqual(this.state.agmtStripAttrs, this.state._agmtStripAttrs)) ||
+ (attr != 'agmtFracAttrs' && !this.listEqual(this.state.agmtFracAttrs, this.state._agmtFracAttrs)) ||
+ (attr != 'agmtFracInitAttrs' && !this.listEqual(this.state.agmtFracInitAttrs, this.state._agmtFracInitAttrs))) {
+ all_good = true;
+ }
+ if (!this.state._agmtSync) {
+ if ((attr != 'agmtSyncMon' && this.state.agmtSyncMon != this.state._agmtSyncMon) ||
+ (attr != 'agmtSyncTue' && this.state.agmtSyncTue != this.state._agmtSyncTue) ||
+ (attr != 'agmtSyncWed' && this.state.agmtSyncWed != this.state._agmtSyncWed) ||
+ (attr != 'agmtSyncThu' && this.state.agmtSyncThu != this.state._agmtSyncThu) ||
+ (attr != 'agmtSyncFri' && this.state.agmtSyncFri != this.state._agmtSyncFri) ||
+ (attr != 'agmtSyncSat' && this.state.agmtSyncSat != this.state._agmtSyncSat) ||
+ (attr != 'agmtSyncSun' && this.state.agmtSyncSun != this.state._agmtSyncSun)) {
+ all_good = true;
+ }
+ }
+ if (attr != 'agmtStripAttrs' &&
+ attr != 'agmtFracAttrs' &&
+ attr != 'agmtFracInitAttrs' &&
+ value != this.state[orig_attr]) {
+ all_good = true;
+ } else if ((attr == 'agmtStripAttrs' && !this.listEqual(value, this.state._agmtStripAttrs)) ||
+ (attr == 'agmtFracAttrs' && !this.listEqual(value, this.state._agmtFracAttrs)) ||
+ (attr == 'agmtFracInitAttrs' && !this.listEqual(value, this.state._agmtFracInitAttrs))) {
+ all_good = true;
+ }
+ }
+
+ if (e.target.type == "time") {
+ // Strip out the colon from the time
+ time_val = value.replace(':', '');
+ }
+
+ if (e.target.name.startsWith("agmt-modal")) {
+ // Validate modal settings "live"
+ if (e.target.id == 'agmtName') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtName == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtHost') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtHost == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtHost) {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtPort') {
+ if (value == "") {
+ all_good = false;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['agmtPort'] = true;
+ modal_msg = "Invalid Consumer Port number";
+ }
+ } else if (this.state.agmtPort == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindDN') {
+ if (value == "") {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Bind DN";
+ }
+ } else if (this.state.agmtBindDN == "") {
+ all_good = false;
+ } else if (!valid_dn(this.state.agmtBindDN)) {
+ modal_msg = "Invalid DN for Bind DN";
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPW') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPWConfirm) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPW == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPWConfirm') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPW) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPWConfirm == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtSync') {
+ if (!value) {
+ if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ } else if (!this.state.agmtSync) {
+ // Check the days first
+ let have_days = false;
+ let days = ["agmtSyncSun", "agmtSyncMon", "agmtSyncTue", "agmtSyncWed",
+ "agmtSyncThu", "agmtSyncFri", "agmtSyncSat"];
+ for (let day of days) {
+ if ((e.target.id != day && this.state[day]) || (e.target.id == day && value)) {
+ have_days = true;
+ break;
+ }
+ }
+ if (!have_days) {
+ modal_schedule_msg = "You must select at least one day for replication";
+ all_good = false;
+ } else if (e.target.id == 'agmtStartTime') {
+ if (time_val == "") {
+ all_good = false;
+ errObj['agmtStartTime'] = true;
+ modal_schedule_msg = "Schedule start time must be set";
+ } else if (time_val >= this.state.agmtEndTime.replace(":", "")) {
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (e.target.id == 'agmtEndTime') {
+ if (time_val == "") {
+ errObj['agmtEndTime'] = true;
+ all_good = false;
+ } else if (this.state.agmtStartTime.replace(":", "") >= time_val) {
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ // End of agmt modal live validation
+ }
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj,
+ agmtSaveOK: all_good,
+ modalMsg: modal_msg,
+ modalScheduleMsg: modal_schedule_msg,
+ });
+ }
+
+ handleTAStripAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtStripAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracInitAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtFracInitAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAStripAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtStripAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracInitAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtFracInitAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ showConfirmDeleteAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDeleteAgmt: true,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ closeConfirmDeleteAgmt () {
+ this.setState({
+ showConfirmDeleteAgmt: false,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ showConfirmInitAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmInitAgmt: true,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ closeConfirmInitAgmt () {
+ this.setState({
+ showConfirmInitAgmt: false,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ showCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: true,
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindMethod: "SIMPLE",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtStripAttrs: [],
+ agmtFracAttrs: [],
+ agmtFracInitAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ modalScheduleMsg: "",
+ errObj: {
+ // Marks all the fields as required
+ agmtName: true,
+ agmtHost: true,
+ agmtPort: true,
+ agmtBindDN: true,
+ agmtBindPW: true,
+ agmtBindPWConfirm: true,
+ agmtStartTime: false,
+ agmtEndTime: false,
+ }
+ });
+ }
+
+ closeCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: false
+ });
+ }
+
+ closeEditAgmtModal () {
+ this.setState({
+ showEditAgmtModal: false
+ });
+ }
+
+ showEditAgmt (agmtName) {
+ // Search for the agmt to get all the details
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'get', agmtName, '--suffix=' + this.props.suffix,
+ ];
+
+ log_cmd('showEditAgmt', 'Edit replication agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let agmtName = "";
+ let agmtHost = "";
+ let agmtPort = "";
+ let agmtProtocol = "";
+ let agmtBindMethod = "";
+ let agmtBindDN = "";
+ let agmtBindPW = "";
+ let agmtBindPWConfirm = "";
+ let agmtStripAttrs = [];
+ let agmtFracAttrs = [];
+ let agmtFracInitAttrs = [];
+ let agmtSync = true;
+ let agmtSyncMon = false;
+ let agmtSyncTue = false;
+ let agmtSyncWed = false;
+ let agmtSyncThu = false;
+ let agmtSyncFri = false;
+ let agmtSyncSat = false;
+ let agmtSyncSun = false;
+ let agmtStartTime = "";
+ let agmtEndTime = "";
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "cn") {
+ agmtName = val;
+ }
+ if (attr == "nsds5replicahost") {
+ agmtHost = val;
+ }
+ if (attr == "nsds5replicaport") {
+ agmtPort = val;
+ }
+ if (attr == "nsds5replicatransportinfo") {
+ agmtProtocol = val;
+ }
+ if (attr == "nsds5replicabindmethod") {
+ agmtBindMethod = val.toUpperCase();
+ }
+ if (attr == "nsds5replicabinddn") {
+ agmtBindDN = val;
+ }
+ if (attr == "nsds5replicacredentials") {
+ agmtBindPW = val;
+ agmtBindPWConfirm = val;
+ }
+ if (attr == "nsds5replicatedattributelist") {
+ let attrs = val.replace("(objectclass=*) $ EXCLUDE", "").trim();
+ agmtFracAttrs = attrs.split(' ');
+ }
+ if (attr == "nsds5replicatedattributelisttotal") {
+ let attrs = val.replace("(objectclass=*) $ EXCLUDE", "").trim();
+ agmtFracInitAttrs = attrs.split(' ');
+ }
+ if (attr == "nsds5replicastripattrs") {
+ agmtStripAttrs = val.split(' ');
+ }
+ if (attr == "nsds5replicaupdateschedule") {
+ agmtSync = false;
+ // Parse schedule
+ let parts = val.split(' ');
+ let times = parts[0].split('-');
+ let days = parts[1];
+
+ // Do the times
+ agmtStartTime = times[0].substring(0, 2) + ":" + times[0].substring(2, 4);
+ agmtEndTime = times[1].substring(0, 2) + ":" + times[1].substring(2, 4);
+
+ // Do the days
+ if (days.includes("0")) {
+ agmtSyncSun = true;
+ }
+ if (days.includes("1")) {
+ agmtSyncMon = true;
+ }
+ if (days.includes("2")) {
+ agmtSyncTue = true;
+ }
+ if (days.includes("3")) {
+ agmtSyncWed = true;
+ }
+ if (days.includes("4")) {
+ agmtSyncThu = true;
+ }
+ if (days.includes("5")) {
+ agmtSyncFri = true;
+ }
+ if (days.includes("6")) {
+ agmtSyncSat = true;
+ }
+ }
+ }
+ if (this._mounted) {
+ this.setState({
+ showEditAgmtModal: true,
+ agmtName: agmtName,
+ agmtHost: agmtHost,
+ agmtPort: agmtPort,
+ agmtProtocol: agmtProtocol,
+ agmtBindMethod: agmtBindMethod,
+ agmtBindDN: agmtBindDN,
+ agmtBindPW: agmtBindPW,
+ agmtBindPWConfirm: agmtBindPWConfirm,
+ agmtStripAttrs: agmtStripAttrs,
+ agmtFracAttrs: agmtFracAttrs,
+ agmtFracInitAttrs: agmtFracInitAttrs,
+ agmtSync: agmtSync,
+ agmtSyncMon: agmtSyncMon,
+ agmtSyncTue: agmtSyncTue,
+ agmtSyncWed: agmtSyncWed,
+ agmtSyncThu: agmtSyncThu,
+ agmtSyncFri: agmtSyncFri,
+ agmtSyncSat: agmtSyncSat,
+ agmtSyncSun: agmtSyncSun,
+ agmtStartTime: agmtStartTime,
+ agmtEndTime: agmtEndTime,
+ agmtSaveOK: false,
+ // Record original values before editing
+ _agmtName: agmtName,
+ _agmtHost: agmtHost,
+ _agmtPort: agmtPort,
+ _agmtProtocol: agmtProtocol,
+ _agmtBindMethod: agmtBindMethod,
+ _agmtBindDN: agmtBindDN,
+ _agmtBindPW: agmtBindPW,
+ _agmtBindPWConfirm: agmtBindPWConfirm,
+ _agmtStripAttrs: agmtStripAttrs,
+ _agmtFracAttrs: agmtFracAttrs,
+ _agmtFracInitAttrs: agmtFracInitAttrs,
+ _agmtSync: agmtSync,
+ _agmtSyncMon: agmtSyncMon,
+ _agmtSyncTue: agmtSyncTue,
+ _agmtSyncWed: agmtSyncWed,
+ _agmtSyncThu: agmtSyncThu,
+ _agmtSyncFri: agmtSyncFri,
+ _agmtSyncSat: agmtSyncSat,
+ _agmtSyncSun: agmtSyncSun,
+ _agmtStartTime: agmtStartTime,
+ _agmtEndTime: agmtEndTime,
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to get agreement information for: "${agmtName}" - ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'set', this.state.agmtName, '--suffix=' + this.props.suffix,
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' + this.state.agmtStartTime.replace(':', '') + "-" + this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ } else if (this.state.agmtSync != this.state._agmtSync && this.state.agmtSync) {
+ // We disabled custom scheduleRow
+ cmd.push('--schedule=');
+ }
+ if (this.state.agmtBindMethod != this.state._agmtBindMethod) {
+ cmd.push('--bind-method=' + this.state.agmtBindMethod);
+ }
+ if (this.state.agmtProtocol != this.state._agmtProtocol) {
+ cmd.push('--conn-protocol=' + this.state.agmtProtocol);
+ }
+ if (this.state.agmtBindPW != this.state._agmtBindPW) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindPW);
+ }
+ if (this.state.agmtBindDN != this.state._agmtBindDN) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindDN);
+ }
+ if (this.state.agmtFracAttrs != this.state._agmtFracAttrs) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join(' '));
+ }
+ if (this.state.agmtFracInitAttrs != this.state._agmtFracInitAttrs) {
+ cmd.push('--frac-list-total=' + this.state.agmtFracInitAttrs.join(' '));
+ }
+ if (this.state.agmtStripAttrs != this.state._agmtStripAttrs) {
+ cmd.push('--strip-list=' + this.state.agmtStripAttrs.join(' '));
+ }
+ if (this.state.agmtHost != this.state._agmtHost) {
+ cmd.push('--host=' + this.state.agmtHost);
+ }
+ if (this.state.agmtPort != this.state._agmtPort) {
+ cmd.push('--port=' + this.state.agmtPort);
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('saveAgmt', 'update replication agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showEditAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully updated replication agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to update replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ pokeAgmt (agmtName) {
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'poke', agmtName, '--suffix=' + this.props.suffix];
+ log_cmd('pokeAgmt', 'send updates now', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, "err": "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully poked replication agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to poke replication agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ initAgmt (agmtName) {
+ let init_cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'init', '--suffix=' + this.props.suffix, this.state.agmtName];
+ log_cmd('initAgmt', 'Initialize agreement', init_cmd);
+ cockpit
+ .spawn(init_cmd, { superuser: true, "err": "message" })
+ .done(content => {
+ var agmtIntervalCount = this.state.agmtInitCounter + 1;
+ var intervals = this.state.agmtInitIntervals;
+ this.props.reload(this.props.suffix);
+ intervals[agmtIntervalCount] = setInterval(this.watchAgmtInit, 2000, this.state.agmtName, agmtIntervalCount);
+ if (this._mounted) {
+ this.setState({
+ agmtInitCounter: agmtIntervalCount,
+ agmtInitIntervals: intervals,
+ showConfirmInitAgmt: false
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to initialize replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showConfirmInitAgmt: false
+ });
+ });
+ }
+
+ confirmToggle (agmtName, state) {
+ if (state == 'Enabled') {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDisableAgmt: true
+ });
+ } else {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmEnableAgmt: true
+ });
+ }
+ }
+
+ closeConfirmEnableAgmt () {
+ this.setState({
+ showConfirmEnableAgmt: false
+ });
+ }
+
+ closeConfirmDisableAgmt () {
+ this.setState({
+ showConfirmDisableAgmt: false
+ });
+ }
+
+ enableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'enable', agmtName, '--suffix=' + this.props.suffix];
+ log_cmd('enableAgmt', 'enable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully enabled replication agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to enabled replication agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ disableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'disable', agmtName, '--suffix=' + this.props.suffix];
+ log_cmd('disableAgmt', 'Disable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully disabled replication agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to disable replication agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ deleteAgmt () {
+ this.setState({
+ deleteSpinning: true
+ });
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'delete', '--suffix=' + this.props.suffix, this.state.agmtName];
+ log_cmd('deleteAgmt', 'Delete agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully deleted replication agreement');
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to delete replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ });
+ }
+
+ createAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'create', this.state.agmtName, '--suffix=' + this.props.suffix,
+ '--host=' + this.state.agmtHost, '--port=' + this.state.agmtPort,
+ '--bind-method=' + this.state.agmtBindMethod, '--conn-protocol=' + this.state.agmtProtocol,
+ '--bind-dn=' + this.state.agmtBindDN, '--bind-passwd=' + this.state.agmtBindPW
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' + this.state.agmtStartTime.replace(':', '') + "-" + this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ }
+
+ // Handle fractional and stripped attributes
+ if (this.state.agmtFracAttrs.length > 0) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join(' '));
+ }
+ if (this.state.agmtFracInitAttrs.length > 0) {
+ cmd.push('--frac-list-total=' + this.state.agmtFracAttrs.join(' '));
+ }
+ if (this.state.agmtStripAttrs.length > 0) {
+ cmd.push('--strip-list=' + this.state.agmtStripAttrs.join(' '));
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('createAgmt', 'Create replication agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showCreateAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully created replication agreement'
+ );
+ if (this.state.agmtInit == 'online-init') {
+ this.initAgmt(this.state.agmtName);
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to create replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ watchAgmtInit(agmtName, idx) {
+ // Watch the init, then clear the interval index
+ let status_cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'init-status', '--suffix=' + this.props.suffix, agmtName];
+ log_cmd('watchAgmtInit', 'Get initialization status for agmt', status_cmd);
+ cockpit
+ .spawn(status_cmd, {superuser: true, "err": "message"})
+ .done(data => {
+ let init_status = JSON.parse(data);
+ if (init_status.startsWith('Agreement successfully initialized') ||
+ init_status.startsWith('Agreement initialization failed')) {
+ // Either way we're done, stop watching the status
+ clearInterval(this.state.agmtInitIntervals[idx]);
+ }
+ this.props.reload(this.props.suffix);
+ });
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-right">
+ <ReplAgmtTable
+ rows={this.props.rows}
+ edit={this.showEditAgmt}
+ poke={this.pokeAgmt}
+ init={this.showConfirmInitAgmt}
+ enable={this.confirmToggle}
+ delete={this.showConfirmDeleteAgmt}
+ />
+ <div className="ds-margin-top ds-container ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showCreateAgmtModal}
+ >
+ Create Agreement
+ </Button>
+ <Button
+ className="ds-left-margin"
+ bsStyle="default"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+ >
+ Refresh Agreements
+ </Button>
+ </div>
+ <ReplAgmtModal
+ showModal={this.state.showCreateAgmtModal}
+ closeHandler={this.closeCreateAgmtModal}
+ handleChange={this.handleChange}
+ handleStripChange={this.handleTAStripAttrChange}
+ handleFracChange={this.handleTAFracAttrChange}
+ handleFracInitChange={this.handleTAFracInitAttrChange}
+ saveHandler={this.createAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtBindMethod={this.state.agmtBindMethod}
+ agmtStripAttrs={this.state.agmtStripAttrs}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtFracInitAttrs={this.state.agmtFracInitAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ />
+ <ReplAgmtModal
+ showModal={this.state.showEditAgmtModal}
+ closeHandler={this.closeEditAgmtModal}
+ handleChange={this.handleChange}
+ handleStripChange={this.handleTAStripAttrChangeEdit}
+ handleFracChange={this.handleTAFracAttrChangeEdit}
+ handleFracInitChange={this.handleTAFracInitAttrChangeEdit}
+ saveHandler={this.saveAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtBindMethod={this.state.agmtBindMethod}
+ agmtStripAttrs={this.state.agmtStripAttrs}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtFracInitAttrs={this.state.agmtFracInitAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ edit
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmDeleteAgmt}
+ closeHandler={this.closeConfirmDeleteAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.deleteAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Delete Replication Agreement"
+ mMsg="Are you sure you want to delete this replication agreement?"
+ mSpinningMsg="Deleting Replication Agreement ..."
+ mBtnName="Delete Agreement"
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmInitAgmt}
+ closeHandler={this.closeConfirmInitAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.initAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Initialize Replication Agreement"
+ mMsg="Are you sure you want to initialize this replication agreement?"
+ mSpinningMsg="Initializing Replication Agreement ..."
+ mBtnName="Initialize Agreement"
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmEnableAgmt}
+ closeHandler={this.closeConfirmEnableAgmt}
+ actionFunc={this.enableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to enable this replication agreement?"
+ msgContent={this.state.agmtName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmDisableAgmt}
+ closeHandler={this.closeConfirmDisableAgmt}
+ actionFunc={this.disableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to disable this replication agreement?"
+ msgContent={this.state.agmtName}
+ />
+ </div>
+ );
+ }
+}
+
+ReplAgmts.propTypes = {
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ rows: PropTypes.array,
+ addNotification: PropTypes.func,
+ attrs: PropTypes.array,
+};
+
+ReplAgmts.defaultProps = {
+ serverId: "",
+ suffix: "",
+ rows: [],
+ addNotification: noop,
+ attrs: [],
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replChangelog.jsx b/src/cockpit/389-console/src/lib/replication/replChangelog.jsx
new file mode 100644
index 0000000..1386393
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replChangelog.jsx
@@ -0,0 +1,395 @@
+import cockpit from "cockpit";
+import React from "react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+import { ConfirmPopup } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ noop,
+ Row,
+ Button,
+ Col,
+ ControlLabel,
+ Checkbox,
+ Form,
+ Icon,
+ Spinner,
+} from "patternfly-react";
+
+export class Changelog extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ loading: false,
+ errObj: {},
+ showConfirmDelete: false,
+ // Changelog settings
+ clDir: this.props.clDir,
+ clMaxEntries: this.props.clMaxEntries,
+ clMaxAge: this.props.clMaxAge,
+ clCompactInt: this.props.clCompactInt,
+ clTrimInt: this.props.clTrimInt,
+ clEncrypt: this.props.clEncrypt,
+ // Preserve original settings
+ _clDir: this.props.clDir,
+ _clMaxEntries: this.props.clMaxEntries,
+ _clMaxAge: this.props.clMaxAge,
+ _clCompactInt: this.props.clCompactInt,
+ _clTrimInt: this.props.clTrimInt,
+ _clEncrypt: this.props.clEncrypt,
+ };
+
+ this.handleChange = this.handleChange.bind(this);
+ this.saveSettings = this.saveSettings.bind(this);
+ this.createChangelog = this.createChangelog.bind(this);
+ this.confirmChangelogDelete = this.confirmChangelogDelete.bind(this);
+ this.closeConfirmDelete = this.closeConfirmDelete.bind(this);
+ this.deleteChangelog = this.deleteChangelog.bind(this);
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ createChangelog () {
+ this.setState({
+ saving: true
+ });
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'create-changelog'
+ ];
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ "Successfully created replication changelog"
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error creating changelog - ${msg}`
+ );
+ });
+ }
+
+ confirmChangelogDelete () {
+ this.setState({
+ showConfirmDelete: true
+ });
+ }
+
+ closeConfirmDelete () {
+ this.setState({
+ showConfirmDelete: false
+ });
+ }
+
+ deleteChangelog () {
+ this.setState({
+ saving: true
+ });
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'delete-changelog'
+ ];
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ "Successfully deleted replication changelog"
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error deleting changelog - ${msg}`
+ );
+ });
+ }
+
+ saveSettings () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'set-changelog'
+ ];
+ if (this.state.clDir != this.state._clDir) {
+ if (this.state.clDir == "") {
+ // Changelog directory can not be empty
+ let errObj = this.state.errObj;
+ errObj["clDir"] = true;
+ this.setState({
+ errObj: errObj
+ });
+ return;
+ }
+ cmd.push("--cl-dir =" + this.state.clDir);
+ }
+ if (this.state.clMaxEntries != this.state._clMaxEntries) {
+ cmd.push("--max-entries=" + this.state.clMaxEntries);
+ }
+ if (this.state.clMaxAge != this.state._clMaxAge) {
+ cmd.push("--max-age=" + this.state.clMaxAge);
+ }
+ if (this.state.clCompactInt != this.state._clCompactInt) {
+ cmd.push("--compact-interval=" + this.state.clCompactInt);
+ }
+ if (this.state.clTrimInt != this.state._clTrimInt) {
+ cmd.push("--trim-interval=" + this.state.clTrimInt);
+ }
+ if (this.state.clEncrypt != this.state._clEncrypt) {
+ // TODO - Not implemented in dsconf yet
+ // cmd.push("--encrypt=" + this.state.clEncrypt);
+ }
+ if (cmd.length > 5) {
+ this.setState({
+ // Start the spinner
+ saving: true
+ });
+ log_cmd("saveSettings", "Applying replication changelog changes", cmd);
+ let msg = "Successfully updated changelog configuration.";
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ msg
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error updating changelog configuration - ${msg}`
+ );
+ });
+ }
+ }
+
+ handleChange(e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (e.target.id == "clDir" && value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ render() {
+ let clPage;
+ if (this.state._clDir == "") {
+ // No changelog, only show clDir and Create button
+ clPage =
+ <div>
+ <Row>
+ <Col sm={12} className="ds-word-wrap">
+ <ControlLabel className="ds-suffix-header">
+ Replication Changelog
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh changelog settings"
+ onClick={this.props.reload}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ <hr />
+ <div className="ds-margin-top-med ds-center">
+ <p>There is no Replication Changelog</p>
+ <Row className="ds-margin-top-lg" title="Create the replication changelog">
+ <Button
+ bsStyle="primary"
+ onClick={this.createChangelog}
+ >
+ Create Changelog
+ </Button>
+ </Row>
+ </div>
+ </div>;
+ } else if (this.state.saving) {
+ clPage =
+ <div className="ds-margin-top ds-loading-spinner ds-center">
+ <h4>Saving changelog configuration ...</h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
+ </div>;
+ } else if (this.props.loading) {
+ clPage =
+ <div className="ds-loading-spinner ds-center">
+ <h4>Loading changelog configuration ...</h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
+ </div>;
+ } else {
+ clPage =
+ <div>
+ <Row>
+ <Col sm={5} className="ds-word-wrap">
+ <h4>
+ Replication Changelog
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh changelog settings"
+ onClick={this.props.reload}
+ />
+ </h4>
+ </Col>
+ <Col sm={7}>
+ <Button
+ className="ds-float-right"
+ bsStyle="danger"
+ onClick={this.confirmChangelogDelete}
+ >
+ Delete Changelog
+ </Button>
+ </Col>
+ </Row>
+ <Form horizontal>
+ <hr />
+ <Row className="ds-margin-top" title="The filesystem location of the replication changelog database">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Directory
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clDir} id="clDir" onChange={this.handleChange} className={this.state.errObj.clDir ? "ds-input-auto-bad" : "ds-input-auto"} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Changelog trimming parameter. Set the maximum number of changelog entries allowed in the database (nsslapd-changelogmaxentries).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Maximum Entries
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clMaxEntries} id="clMaxEntries" onChange={this.handleChange} className="ds-input-auto" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Changelog trimming parameter. This set the maximum age of a changelog entry. It is recommended to use the same value as the Replication Purge Delay. (nsslapd-changelogmaxage).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Maximum Age
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clMaxAge} id="clMaxAge" onChange={this.handleChange} className="ds-input-auto" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The changelog trimming interval. Set how often the changelog checks if there are entries that can be purged from the changelog based on the trimming parameters (nsslapd-changelogtrim-interval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Trimming Interval
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clTrimInt} id="clTrimInt" onChange={this.handleChange} className="ds-input-auto" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The changelog compaction interval. Set how often the changelog will compact itself, meaning remove empty/trimmed database slots. The default is 30 days. (nsslapd-changelogcompactdb-interval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Compaction Interval
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clCompactInt} id="clCompactInt" onChange={this.handleChange} className="ds-input-auto" />
+ </Col>
+ </Row>
+ <Row hidden className="ds-margin-top" title="TLS must first be enabled in the server for change encryption to work. Please consult Administration Guide for more details.">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Encryption
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="clEncrypt"
+ checked={this.state.clEncrypt}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg">
+ <Col sm={2}>
+ <Button
+ bsStyle="primary"
+ onClick={this.saveSettings}
+ >
+ Save
+ </Button>
+ </Col>
+ </Row>
+ </Form>
+ </div>;
+ }
+
+ return (
+ <div>
+ {clPage}
+ <ConfirmPopup
+ showModal={this.state.showConfirmDelete}
+ closeHandler={this.closeConfirmDelete}
+ actionFunc={this.deleteChangelog}
+ msg="Are you sure you want to delete the changelog?"
+ msgContent="This will invalidate all replication agreements, and they will need to be reinitialized."
+ />
+ </div>
+ );
+ }
+}
+
+Changelog.propTypes = {
+ serverId: PropTypes.string,
+ clDir: PropTypes.string,
+ clMaxEntries: PropTypes.string,
+ clMaxAge: PropTypes.string,
+ clCompactInt: PropTypes.string,
+ clTrimInt: PropTypes.string,
+ clEncrypt: PropTypes.bool,
+ addNotification: PropTypes.func,
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
+};
+
+Changelog.defaultProps = {
+ serverId: "",
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+ addNotification: noop,
+ reload: noop,
+ enableTree: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replConfig.jsx b/src/cockpit/389-console/src/lib/replication/replConfig.jsx
new file mode 100644
index 0000000..2bec513
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replConfig.jsx
@@ -0,0 +1,640 @@
+import cockpit from "cockpit";
+import React from "react";
+import { log_cmd, valid_dn } from "../tools.jsx";
+import { ConfirmPopup } from "../notifications.jsx";
+import CustomCollapse from "../customCollapse.jsx";
+import { ManagerTable } from "./replTables.jsx";
+import { AddManagerModal, ChangeReplRoleModal } from "./replModals.jsx";
+import {
+ Button,
+ Row,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Spinner,
+ // noop,
+} from "patternfly-react";
+// import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class ReplConfig extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ saving: false,
+ showConfirmManagerDelete: false,
+ showAddManagerModal: false,
+ showPromoteDemoteModal: false,
+ addManagerSpinning: false,
+ roleChangeSpinning: false,
+ manager: "cn=replication manager,cn=config",
+ manager_passwd: "",
+ manager_passwd_confirm: "",
+ newRole: this.props.role == "Master" ? "Hub" : "Master",
+ newRID: "1",
+ modalChecked: false,
+ errObj: {},
+ // Config Settings
+ nsds5replicabinddn: this.props.data['nsds5replicabinddn'],
+ nsds5replicabinddngroup: this.props.data['nsds5replicabinddngroup'],
+ nsds5replicabinddngroupcheckinterval: this.props.data['nsds5replicabinddngroupcheckinterval'],
+ nsds5replicareleasetimeout: this.props.data['nsds5replicareleasetimeout'],
+ nsds5replicapurgedelay: this.props.data['nsds5replicapurgedelay'],
+ nsds5replicatombstonepurgeinterval: this.props.data['nsds5replicatombstonepurgeinterval'],
+ nsds5replicaprecisetombstonepurging: this.props.data['nsds5replicaprecisetombstonepurging'],
+ nsds5replicaprotocoltimeout: this.props.data['nsds5replicaprotocoltimeout'],
+ nsds5replicabackoffmin: this.props.data['nsds5replicabackoffmin'],
+ nsds5replicabackoffmax: this.props.data['nsds5replicabackoffmax'],
+ // Original settings
+ _nsds5replicabinddn: this.props.data['nsds5replicabinddn'],
+ _nsds5replicabinddngroup: this.props.data['nsds5replicabinddngroup'],
+ _nsds5replicabinddngroupcheckinterval: this.props.data['nsds5replicabinddngroupcheckinterval'],
+ _nsds5replicareleasetimeout: this.props.data['nsds5replicareleasetimeout'],
+ _nsds5replicapurgedelay: this.props.data['nsds5replicapurgedelay'],
+ _nsds5replicatombstonepurgeinterval: this.props.data['nsds5replicatombstonepurgeinterval'],
+ _nsds5replicaprecisetombstonepurging: this.props.data['nsds5replicaprecisetombstonepurging'],
+ _nsds5replicaprotocoltimeout: this.props.data['nsds5replicaprotocoltimeout'],
+ _nsds5replicabackoffmin: this.props.data['nsds5replicabackoffmin'],
+ _nsds5replicabackoffmax: this.props.data['nsds5replicabackoffmax'],
+
+ };
+
+ this.confirmManagerDelete = this.confirmManagerDelete.bind(this);
+ this.closeConfirmManagerDelete = this.closeConfirmManagerDelete.bind(this);
+ this.deleteManager = this.deleteManager.bind(this);
+ this.showAddManager = this.showAddManager.bind(this);
+ this.closeAddManagerModal = this.closeAddManagerModal.bind(this);
+ this.addManager = this.addManager.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleManagerChange = this.handleManagerChange.bind(this);
+ this.showPromoteDemoteModal = this.showPromoteDemoteModal.bind(this);
+ this.closePromoteDemoteModal = this.closePromoteDemoteModal.bind(this);
+ this.doRoleChange = this.doRoleChange.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ doRoleChange (changeType) {
+ let action = "demote";
+ if (changeType == "Promoting") {
+ action = "promote";
+ }
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket', 'replication', action,
+ '--suffix=' + this.props.suffix, "--newrole=" + this.state.newRole];
+ if (this.state.newRole == "Master") {
+ let ridNum = parseInt(this.state.newRID, 10);
+ if (ridNum < 1 || ridNum >= 65535) {
+ this.props.addNotification(
+ "error",
+ "A Master replica requires a unique numerical identifier. Please enter an ID between 1 and 65534"
+ );
+ return;
+ }
+ cmd.push("--replica-id=" + this.state.newRID);
+ }
+ this.setState({
+ roleChangeSpinning: true
+ });
+ log_cmd('doRoleChange', 'change replica role', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ `Successfully ${action}d replica to a ${this.state.newRole}`
+ );
+ this.setState({
+ roleChangeSpinning: false,
+ showPromoteDemoteModal: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Failed to ${action} replica - ${errMsg.desc}`
+ );
+ this.setState({
+ roleChangeSpinning: false,
+ showPromoteDemoteModal: false
+ });
+ });
+ }
+
+ closePromoteDemoteModal () {
+ this.setState({
+ showPromoteDemoteModal: false
+ });
+ }
+
+ showPromoteDemoteModal () {
+ this.setState({
+ showPromoteDemoteModal: true,
+ modalChecked: false,
+ });
+ }
+
+ closeAddManagerModal () {
+ this.setState({
+ showAddManagerModal: false
+ });
+ }
+
+ showAddManager () {
+ this.setState({
+ showAddManagerModal: true,
+ manager: "cn=replication manager,cn=config",
+ manager_passwd: "",
+ manager_passwd_confirm: "",
+ errObj: {
+ manager_passwd: true,
+ manager_passwd_confirm: true,
+ }
+ });
+ }
+
+ addManager () {
+ // Validate DN
+ if (!valid_dn(this.state.manager)) {
+ this.props.addNotification(
+ "error",
+ `Invalid DN for the Replication Manager: ${this.state.manager}`
+ );
+ return;
+ }
+
+ if (this.state.manager_passwd == "" || this.state.manager_passwd_confirm == "") {
+ this.props.addNotification(
+ "error", "You must provide a password for the Replication Manager"
+ );
+ return;
+ }
+ if (this.state.manager_passwd != this.state.manager_passwd_confirm) {
+ this.props.addNotification(
+ "error", "Passwords do not match"
+ );
+ return;
+ }
+
+ this.setState({
+ addManagerSpinning: true
+ });
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "replication", "create-manager", "--suffix=" + this.props.suffix, "--name=" + this.state.manager,
+ "--passwd=" + this.state.manager_passwd
+ ];
+
+ log_cmd("addManager", "Adding Replication Manager", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully added Replication Manager`
+ );
+ this.setState({
+ addManagerSpinning: false,
+ showAddManagerModal: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failure adding Replication Manager - ${errMsg.desc}`
+ );
+ this.setState({
+ addManagerSpinning: false,
+ showAddManagerModal: false
+ });
+ });
+ }
+
+ handleChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ handleManagerChange(e) {
+ let value = e.target.value;
+ let attr = e.target.id;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ // Handle password chnages
+ if (attr == "manager_passwd") {
+ if (value != this.state.manager_passwd_confirm) {
+ // No match
+ valueErr = true;
+ } else {
+ errObj[attr] = false;
+ errObj['manager_passwd_confirm'] = false;
+ }
+ } else if (attr == "manager_passwd_confirm") {
+ if (value != this.state.manager_passwd) {
+ // No match
+ valueErr = true;
+ } else {
+ errObj[attr] = false;
+ errObj['manager_passwd'] = false;
+ }
+ }
+
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ errObj: errObj
+ });
+ }
+
+ confirmManagerDelete (item) {
+ this.setState({
+ showConfirmManagerDelete: true,
+ manager: item.name,
+ });
+ }
+
+ closeConfirmManagerDelete () {
+ this.setState({
+ showConfirmManagerDelete: false,
+ manager: "",
+ });
+ }
+
+ deleteManager (dn) {
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "replication", "delete-manager", "--suffix=" + this.props.suffix, "--name=" + dn
+ ];
+ log_cmd("deleteManager", "Deleting Replication Manager", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully removed Replication Manager`
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failure removing Replication Manager - ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveConfig () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'set', '--suffix=' + this.props.suffix
+ ];
+
+ if (this.state.nsds5replicabackoffmax != this.state._nsds5replicabackoffmax) {
+ cmd.push("--repl-backoff-max=" + this.state.nsds5replicabackoffmax);
+ }
+ if (this.state.nsds5replicabackoffmin != this.state._nsds5replicabackoffmin) {
+ cmd.push("--repl-backoff-min=" + this.state.nsds5replicabackoffmin);
+ }
+
+ if (this.state.nsds5replicaprotocoltimeout != this.state._nsds5replicaprotocoltimeout) {
+ cmd.push("--repl-protocol-timeout=" + this.state.nsds5replicaprotocoltimeout);
+ }
+ if (this.state.nsds5replicaprecisetombstonepurging != this.state._nsds5replicaprecisetombstonepurging) {
+ if (this.state.nsds5replicaprecisetombstonepurging) {
+ cmd.push("--repl-fast-tombstone-purging=on");
+ } else {
+ cmd.push("--repl-fast-tombstone-purging=off");
+ }
+ }
+ if (this.state.nsds5replicatombstonepurgeinterval != this.state._nsds5replicatombstonepurgeinterval) {
+ cmd.push("--repl-tombstone-purge-interval=" + this.state.nsds5replicatombstonepurgeinterval);
+ }
+ if (this.state.nsds5replicabinddngroup != this.state._nsds5replicabinddngroup) {
+ cmd.push("--repl-bind-group=" + this.state.nsds5replicabinddngroup);
+ }
+ if (this.state.nsds5replicabinddngroupcheckinterval != this.state._nsds5replicabinddngroupcheckinterval) {
+ cmd.push("--repl-bind-group-interval=" + this.state.nsds5replicabinddngroupcheckinterval);
+ }
+ if (this.state.nsds5replicareleasetimeout != this.state._nsds5replicareleasetimeout) {
+ cmd.push("--repl-release-timeout=" + this.state.nsds5replicareleasetimeout);
+ }
+ if (this.state.nsds5replicapurgedelay != this.state._nsds5replicapurgedelay) {
+ cmd.push("--repl-purge-delay=" + this.state.nsds5replicapurgedelay);
+ }
+ if (cmd.length > 6) {
+ this.setState({
+ // Start the spinner
+ saving: true
+ });
+ log_cmd("saveConfig", "Applying replication changes", cmd);
+ let msg = "Successfully updated replication configuration.";
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ msg
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reloadConfig(this.props.suffix);
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error updating replication configuration - ${msg}`
+ );
+ });
+ }
+ }
+
+ render() {
+ let content = "";
+ let roleButton = "";
+ let manager_rows = [];
+ for (let row of this.props.data.nsds5replicabinddn) {
+ manager_rows.push({'name': row});
+ }
+
+ if (this.props.role == "Master") {
+ roleButton =
+ <Button
+ bsStyle="primary"
+ onClick={this.showPromoteDemoteModal}
+ title="Demote this Master replica to a Hub or Consumer"
+ className="ds-inline-btn"
+ >
+ Demote
+ </Button>;
+ } else if (this.props.role == "Hub") {
+ roleButton =
+ <Button
+ bsStyle="primary"
+ onClick={this.showPromoteDemoteModal}
+ title="Promote or Demote this Hub replica to a Master or Consumer"
+ className="ds-inline-btn"
+ >
+ Promote/Demote
+ </Button>;
+ } else {
+ // Consumer
+ roleButton =
+ <Button
+ bsStyle="primary"
+ onClick={this.showPromoteDemoteModal}
+ title="Promte this Consumer replica to a Master or Hub"
+ className="ds-inline-btn"
+ >
+ Promote
+ </Button>;
+ }
+
+ if (this.state.saving) {
+ content =
+ <div className="ds-margin-top-xxlg ds-loading-spinner-tree ds-center">
+ <h4>Saving replication configuration ...</h4>
+ <Spinner loading size="md" />
+ </div>;
+ } else {
+ content =
+ <div className="ds-margin-top-xxlg ds-left-margin">
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg">
+ <Col sm={2}>
+ <ControlLabel>
+ Replica Role
+ </ControlLabel>
+ </Col>
+ <Col sm={6}>
+ <input type="text" defaultValue={this.props.role} size="10" disabled />{roleButton}
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={2}>
+ <ControlLabel>
+ Replica ID
+ </ControlLabel>
+ </Col>
+ <Col sm={4}>
+ <input type="text" defaultValue={this.props.data.nsds5replicaid} size="10" disabled />
+ </Col>
+ </Row>
+ <hr />
+ <Row className="ds-margin-top">
+ <Col sm={9}>
+ <ManagerTable
+ rows={manager_rows}
+ confirmDelete={this.confirmManagerDelete}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={4}>
+ <Button
+ bsStyle="primary"
+ onClick={this.showAddManager}
+ >
+ Add Replication Manager
+ </Button>
+ </Col>
+ </Row>
+ <CustomCollapse>
+ <div className="ds-margin-top">
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top-lg" title="The DN of the replication manager">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN Group
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicabinddngroup"
+ type="text"
+ defaultValue={this.state.nsds5replicabinddngroup}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The interval to check for any changes in the group memebrship specified in the Bind DN Group and automatically rebuilds the list for the replication managers accordingly. (nsds5replicabinddngroupcheckinterval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN Group Check Interval
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicabinddngroupcheckinterval"
+ type="text"
+ defaultValue={this.state.nsds5replicabinddngroupcheckinterval}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="This controls the maximum age of deleted entries (tombstone entries), and entry state information. (nsds5replicapurgedelay).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Purge Delay
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicapurgedelay"
+ type="text"
+ defaultValue={this.state.nsds5replicapurgedelay}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="This attribute specifies the time interval in seconds between purge operation cycles. (nsds5replicatombstonepurgeinterval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Tombstone Purge Interval
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicatombstonepurgeinterval"
+ type="text"
+ defaultValue={this.state.nsds5replicatombstonepurgeinterval}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="A time limit (in seconds) that tells a replication session to yield if other replicas are trying to acquire this one (nsds5replicareleasetimeout).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replica Release Timeout
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicareleasetimeout"
+ type="text"
+ defaultValue={this.state.nsds5replicareleasetimeout}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="A timeout on how long to wait before stopping a replication session when the server is being stopped, replication is being disabled, or when removing a replication agreement. (nsds5replicaprotocoltimeout).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Timeout
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicaprotocoltimeout"
+ type="text"
+ defaultValue={this.state.nsds5replicaprotocoltimeout}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="This is the minimum amount of time in seconds that a replication will go into a backoff state (nsds5replicabackoffmin).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Back Off Minimum
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicabackoffmin"
+ type="text"
+ defaultValue={this.state.nsds5replicabackoffmin}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="This is the maximum amount of time in seconds that a replication will go into a backoff state (nsds5replicabackoffmax).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Back Off Maximum
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicabackoffmax"
+ type="text"
+ defaultValue={this.state.nsds5replicabackoffmax}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Enables faster tombstone purging (nsds5replicaprecisetombstonepurging)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Fast Tombstone Purging
+ </Col>
+ <Col sm={6}>
+ <Checkbox
+ id="nsds5replicaprecisetombstonepurging"
+ defaultChecked={this.props.data.nsds5replicaprecisetombstonepurging}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg">
+ <Col componentClass={ControlLabel} sm={4}>
+ <Button
+ bsStyle="primary"
+ onClick={this.saveConfig}
+ >
+ Save Configuration
+ </Button>
+ </Col>
+ </Row>
+ </div>
+ </div>
+ </CustomCollapse>
+ </Form>
+ <ConfirmPopup
+ showModal={this.state.showConfirmManagerDelete}
+ closeHandler={this.closeConfirmManagerDelete}
+ actionFunc={this.deleteManager}
+ actionParam={this.state.manager}
+ msg="Are you sure you want to remove this Replication Manager?"
+ msgContent={this.state.manager}
+ />
+ <AddManagerModal
+ showModal={this.state.showAddManagerModal}
+ closeHandler={this.closeAddManagerModal}
+ handleChange={this.handleManagerChange}
+ saveHandler={this.addManager}
+ spinning={this.state.addManagerSpinning}
+ error={this.state.errObj}
+ />
+ <ChangeReplRoleModal
+ showModal={this.state.showPromoteDemoteModal}
+ closeHandler={this.closePromoteDemoteModal}
+ handleChange={this.handleChange}
+ saveHandler={this.doRoleChange}
+ spinning={this.state.roleChangeSpinning}
+ role={this.props.role}
+ newRole={this.state.newRole}
+ checked={this.state.modalChecked}
+ />
+ </div>;
+ }
+
+ return (
+ <div>
+ {content}
+ </div>
+ );
+ }
+}
diff --git a/src/cockpit/389-console/src/lib/replication/replModals.jsx b/src/cockpit/389-console/src/lib/replication/replModals.jsx
new file mode 100644
index 0000000..0e7a0d9
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replModals.jsx
@@ -0,0 +1,1684 @@
+import React from "react";
+import {
+ Button,
+ Row,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Modal,
+ noop,
+ Spinner,
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import CustomCollapse from "../customCollapse.jsx";
+import { Typeahead } from "react-bootstrap-typeahead";
+import "../../css/ds.css";
+
+export class WinsyncAgmtModal extends React.Component {
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ saveHandler,
+ handleChange,
+ handleFracChange,
+ spinning,
+ agmtName,
+ agmtHost,
+ agmtPort,
+ agmtProtocol,
+ agmtBindDN,
+ agmtBindPW,
+ agmtBindPWConfirm,
+ agmtFracAttrs,
+ agmtSync,
+ agmtSyncMon,
+ agmtSyncTue,
+ agmtSyncWed,
+ agmtSyncThu,
+ agmtSyncFri,
+ agmtSyncSat,
+ agmtSyncSun,
+ agmtStartTime,
+ agmtEndTime,
+ agmtSyncGroups,
+ agmtSyncUsers,
+ agmtWinDomain,
+ agmtWinSubtree,
+ agmtDSSubtree,
+ agmtOneWaySync, // "both", "toWindows", "fromWindows"
+ agmtSyncInterval,
+ availAttrs,
+ error,
+ errorMsg,
+ errorScheduleMsg,
+ } = this.props;
+ let spinner = "";
+ let saveDisabled = !this.props.saveOK;
+ let title = "Create";
+ let initRow = "";
+ let errMsgClass = "ds-center ds-modal-error";
+ let errMsg = errorMsg;
+ let name = "agmt-modal";
+
+ if (this.props.edit) {
+ title = "Edit";
+ name = "agmt-modal-edit";
+ } else {
+ initRow =
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Initialization
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown" id="agmtInit" name={name} onChange={handleChange}>
+ <option value="noinit">Do Not Initialize</option>
+ <option value="online-init">Do Online Initialization</option>
+ </select>
+ </Col>
+ </Row>;
+ }
+
+ if (errMsg == "") {
+ // To keep the modal nice and stable during input validation
+ // We need text that is invisible to keep the modal input from
+ // jumping around
+ errMsgClass = "ds-center ds-clear-text";
+ errMsg = "No errors";
+ }
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Creating winsync agreement ...
+ </div>
+ </Row>;
+ }
+
+ let scheduleRow =
+ <div className="ds-left-indent-md">
+ <Row className="ds-margin-top-lg">
+ <Col sm={12}>
+ <i>Custom Synchronization Schedule</i>
+ </Col>
+ </Row>
+ <hr />
+ <div className="ds-indent">
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncMon"
+ onChange={handleChange}
+ name={name}
+ title="Monday"
+ defaultChecked={agmtSyncMon}
+ >
+ Mon
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncWed"
+ onChange={handleChange}
+ title="Wednesday"
+ name={name}
+ defaultChecked={agmtSyncWed}
+ >
+ Wed
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncFri"
+ onChange={handleChange}
+ title="Friday"
+ name={name}
+ defaultChecked={agmtSyncFri}
+ >
+ Fri
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSun"
+ onChange={handleChange}
+ title="Sunday"
+ name={name}
+ defaultChecked={agmtSyncSun}
+ >
+ Sun
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncTue"
+ onChange={handleChange}
+ title="Tuesday"
+ name={name}
+ defaultChecked={agmtSyncTue}
+ >
+ Tue
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncThu"
+ onChange={handleChange}
+ title="Thursday"
+ name={name}
+ defaultChecked={agmtSyncThu}
+ >
+ Thu
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSat"
+ onChange={handleChange}
+ title="Saturday"
+ name={name}
+ defaultChecked={agmtSyncSat}
+ >
+ Sat
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p className="ds-modal-error">{errorScheduleMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to start initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Start Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtStartTime"
+ type="time"
+ name={name}
+ className={error.agmtStartTime ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtStartTime}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication End Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtEndTime"
+ type="time"
+ name={name}
+ className={error.agmtEndTime ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtEndTime}
+ />
+ </Col>
+ </Row>
+ </div>;
+
+ if (agmtSync) {
+ scheduleRow = "";
+ }
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {title} Winsync Agreement
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p className={errMsgClass}>{errMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Agreement Name
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtName"
+ type="text"
+ name={name}
+ className={error.agmtName ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtName}
+ disabled={this.props.edit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows AD Host
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtHost"
+ type="text"
+ name={name}
+ className={error.agmtHost ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtHost}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows AD Port
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtPort"
+ type="text"
+ name={name}
+ className={error.agmtPort ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtPort}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindDN"
+ type="text"
+ name={name}
+ className={error.agmtBindDN ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="false"
+ defaultValue={agmtBindDN}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPW"
+ type="password"
+ name={name}
+ className={error.agmtBindPW ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPW}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPWConfirm"
+ type="password"
+ name={name}
+ className={error.agmtBindPWConfirm ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPWConfirm}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows Domain Name
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtWinDomain"
+ name={name}
+ className={error.agmtWinDomain ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtWinDomain}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The Active Directory subtree to synchronize">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows Subtree
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtWinSubtree"
+ name={name}
+ className={error.agmtWinSubtree ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtWinSubtree}
+ placeholder="e.g. cn=Users,dc=domain,dc=com"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Directory Server subtree to synchronize">
+ <Col componentClass={ControlLabel} sm={4}>
+ DS Subtree
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtDSSubtree"
+ name={name}
+ className={error.agmtDSSubtree ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtDSSubtree}
+ placeholder="e.g. ou=People,dc=domain,dc=com"
+ />
+ </Col>
+ </Row>
+ {initRow}
+ <CustomCollapse
+ className="ds-margin-top"
+ textOpened="Hide Advanced Settings"
+ textClosed="Show Advanced Setting"
+ >
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Connection Protocol
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown" id="agmtProtocol" defaultValue={agmtProtocol} name={name} onChange={handleChange}>
+ <option>LDAPS</option>
+ <option title="Currently not recommended">StartTLS</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Synchronization Direction
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown" defaultValue={agmtOneWaySync} id="agmtOneWaySync" name={name} onChange={handleChange}>
+ <option title="Synchronization in both directions (default behavior).">both</option>
+ <option title="Only synchronize Directory Server updates to Windows.">toWindows</option>
+ <option title="Only synchronize Windows updates to Directory Server.">fromWindows</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The interval to check for updates on Windows. Default is 300 seconds">
+ <Col componentClass={ControlLabel} sm={4}>
+ Synchronization Interval
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtSyncInterval"
+ name={name}
+ className={error.agmtSyncInterval ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtSyncInterval}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Attribute to exclude from replication">
+ <Col componentClass={ControlLabel} sm={4}>
+ Exclude Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleFracChange}
+ selected={agmtFracAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute: "
+ placeholder="Start typing an attribute..."
+ id="agmtFracAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-med">
+ <Col>
+ <Checkbox
+ id="agmtSyncGroups"
+ onChange={handleChange}
+ name={name}
+ defaultChecked={agmtSyncGroups}
+ >
+ Synchronize New Windows Groups
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col>
+ <Checkbox
+ id="agmtSyncUsers"
+ onChange={handleChange}
+ name={name}
+ defaultChecked={agmtSyncUsers}
+ >
+ Synchronize New Windows Users
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col>
+ <Checkbox
+ id="agmtSync"
+ defaultChecked={agmtSync}
+ onChange={handleChange}
+ name={name}
+ title="Always keep replication in synchronization, or use a specific schedule by unchecking the box."
+ >
+ Keep Replication In Constant Synchronization
+ </Checkbox>
+ </Col>
+ </Row>
+ {scheduleRow}
+ </div>
+ </CustomCollapse>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ disabled={saveDisabled}
+ >
+ Save Agreement
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class ReplAgmtModal extends React.Component {
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ saveHandler,
+ handleChange,
+ handleStripChange,
+ handleFracChange,
+ handleFracInitChange,
+ spinning,
+ agmtName,
+ agmtHost,
+ agmtPort,
+ agmtProtocol,
+ agmtBindMethod,
+ agmtBindDN,
+ agmtBindPW,
+ agmtBindPWConfirm,
+ agmtStripAttrs,
+ agmtFracAttrs,
+ agmtFracInitAttrs,
+ agmtSync,
+ agmtSyncMon,
+ agmtSyncTue,
+ agmtSyncWed,
+ agmtSyncThu,
+ agmtSyncFri,
+ agmtSyncSat,
+ agmtSyncSun,
+ agmtStartTime,
+ agmtEndTime,
+ availAttrs,
+ error,
+ errorMsg,
+ errorScheduleMsg,
+ } = this.props;
+ let spinner = "";
+ let saveDisabled = !this.props.saveOK;
+ let title = "Create";
+ let initRow = "";
+ let errMsgClass = "ds-center ds-modal-error";
+ let errMsg = errorMsg;
+ let name = "agmt-modal";
+
+ if (this.props.edit) {
+ title = "Edit";
+ name = "agmt-modal-edit";
+ } else {
+ initRow =
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Initialization
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown" id="agmtInit" name={name} onChange={handleChange}>
+ <option value="noinit">Do Not Initialize</option>
+ <option value="online-init">Do Online Initialization</option>
+ </select>
+ </Col>
+ </Row>;
+ }
+
+ if (errMsg == "") {
+ // To keep the modal nice and stable during input validation
+ // We need text that is invisible to keep the modal input from
+ // jumping around
+ errMsgClass = "ds-center ds-clear-text";
+ errMsg = "No errors";
+ }
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Creating replication agreement ...
+ </div>
+ </Row>;
+ }
+
+ let scheduleRow =
+ <div className="ds-left-indent-md">
+ <Row className="ds-margin-top-lg">
+ <Col sm={12}>
+ <i>Custom Synchronization Schedule</i>
+ </Col>
+ </Row>
+ <hr />
+ <div className="ds-indent">
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncMon"
+ onChange={handleChange}
+ name={name}
+ title="Monday"
+ defaultChecked={agmtSyncMon}
+ >
+ Mon
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncWed"
+ onChange={handleChange}
+ title="Wednesday"
+ name={name}
+ defaultChecked={agmtSyncWed}
+ >
+ Wed
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncFri"
+ onChange={handleChange}
+ title="Friday"
+ name={name}
+ defaultChecked={agmtSyncFri}
+ >
+ Fri
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSun"
+ onChange={handleChange}
+ title="Sunday"
+ name={name}
+ defaultChecked={agmtSyncSun}
+ >
+ Sun
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncTue"
+ onChange={handleChange}
+ title="Tuesday"
+ name={name}
+ defaultChecked={agmtSyncTue}
+ >
+ Tue
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncThu"
+ onChange={handleChange}
+ title="Thursday"
+ name={name}
+ defaultChecked={agmtSyncThu}
+ >
+ Thu
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSat"
+ onChange={handleChange}
+ title="Saturday"
+ name={name}
+ defaultChecked={agmtSyncSat}
+ >
+ Sat
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p className="ds-modal-error">{errorScheduleMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to start initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Start Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtStartTime"
+ type="time"
+ name={name}
+ className={error.agmtStartTime ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtStartTime}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication End Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtEndTime"
+ type="time"
+ name={name}
+ className={error.agmtEndTime ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtEndTime}
+ /> </Col>
+ </Row>
+ </div>;
+
+ if (agmtSync) {
+ scheduleRow = "";
+ }
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {title} Replication Agreement
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p className={errMsgClass}>{errMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Agreement Name
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtName"
+ type="text"
+ name={name}
+ className={error.agmtName ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtName}
+ disabled={this.props.edit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Host
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtHost"
+ type="text"
+ name={name}
+ className={error.agmtHost ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtHost}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Port
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtPort"
+ type="text"
+ name={name}
+ className={error.agmtPort ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtPort}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindDN"
+ type="text"
+ name={name}
+ className={error.agmtBindDN ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="false"
+ defaultValue={agmtBindDN}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPW"
+ type="password"
+ name={name}
+ className={error.agmtBindPW ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPW}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPWConfirm"
+ type="password"
+ name={name}
+ className={error.agmtBindPWConfirm ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPWConfirm}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Connection Protocol
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown" id="agmtProtocol" defaultValue={agmtProtocol} name={name} onChange={handleChange}>
+ <option>LDAP</option>
+ <option>LDAPS</option>
+ <option title="Currently not recommended">StartTLS</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Authentication Method
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown" defaultValue={agmtBindMethod} id="agmtBindMethod" name={name} onChange={handleChange}>
+ <option title="Use bind DN and password">SIMPLE</option>
+ <option title="Use SSL Client Certificate">SSLCLIENTAUTH</option>
+ <option title="Use SASL Digest-MD5">SASL/DIGEST-MD5</option>
+ <option title="Use SASL GSSAPI">SASL/GSSAPI</option>
+ </select>
+ </Col>
+ </Row>
+ {initRow}
+ <CustomCollapse
+ className="ds-margin-top"
+ textOpened="Hide Advanced Settings"
+ textClosed="Show Advanced Settings"
+ >
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top" title="Attribute to exclude from replication">
+ <Col componentClass={ControlLabel} sm={4}>
+ Exclude Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleFracChange}
+ selected={agmtFracAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute: "
+ placeholder="Start typing an attribute..."
+ id="agmtFracAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Attribute to exclude from replica Initializations">
+ <Col componentClass={ControlLabel} sm={4}>
+ Exclude Init Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleFracInitChange}
+ selected={agmtFracInitAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute: "
+ placeholder="Start typing an attribute..."
+ id="agmtFracInitAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Attributes to strip from a replication update">
+ <Col componentClass={ControlLabel} sm={4}>
+ Strip Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleStripChange}
+ selected={agmtStripAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute: "
+ placeholder="Start typing an attribute..."
+ id="agmtStripAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-med">
+ <Col sm={8}>
+ <Checkbox
+ id="agmtSync"
+ defaultChecked={agmtSync}
+ onChange={handleChange}
+ name={name}
+ title="Always keep replication in synchronization, or use a specific schedule by unchecking the box."
+ >
+ Keep Replication In Constant Synchronization
+ </Checkbox>
+ </Col>
+ </Row>
+ {scheduleRow}
+ </div>
+ </CustomCollapse>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ disabled={saveDisabled}
+ >
+ Save Agreement
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class ChangeReplRoleModal extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ showConfirmPromote: false,
+ showConfirmDemote: false,
+ };
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ role,
+ spinning,
+ checked,
+ } = this.props;
+ let spinner = "";
+ let changeType = "";
+ let roleOptions = [];
+ let ridRow = "";
+ let newRole = this.props.newRole;
+ let saveDisabled = !checked;
+
+ // Set the change type
+ if (role == "Master") {
+ changeType = "Demoting";
+ roleOptions = ["Hub", "Consumer"];
+ } else if (role == "Consumer") {
+ changeType = "Promoting";
+ roleOptions = ["Master", "Hub"];
+ } else {
+ // Hub
+ if (newRole == "Master") {
+ changeType = "Promoting";
+ } else {
+ changeType = "Demoting";
+ }
+ roleOptions = ["Master", "Consumer"];
+ }
+ if (newRole == "Master") {
+ ridRow =
+ <Row className="ds-margin-top-lg" title="Master Replica Identifier. This must be unique across all the Master replicas in your environment">
+ <Col componentClass={ControlLabel} sm={2}>
+ Replica ID
+ </Col>
+ <Col sm={4}>
+ <input id="newRID" type="number" min="1" max="65534"
+ onChange={handleChange} defaultValue="1" size="10"
+ />
+ </Col>
+ </Row>;
+ }
+
+ let selectOptions = roleOptions.map((role) =>
+ <option key={role} value={role}>{role}</option>
+ );
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />{changeType} replica ...
+ </div>
+ </Row>;
+ saveDisabled = true;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Change Replica Role
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <h4>Please choose the new replication role you would like for this suffix</h4>
+ <Row className="ds-margin-top-lg">
+ <Col componentClass={ControlLabel} sm={2}>
+ New Role
+ </Col>
+ <Col sm={4}>
+ <select id="newRole" onChange={handleChange}>
+ {selectOptions}
+ </select>
+ </Col>
+ </Row>
+ {ridRow}
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-center">
+ <Checkbox
+ id="modalChecked"
+ defaultChecked={checked}
+ onChange={handleChange}
+ >
+ <b>Yes</b>, I am sure.
+ </Checkbox>
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={() => {
+ saveHandler(changeType);
+ }}
+ disabled={saveDisabled}
+ >
+ Change Role
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class AddManagerModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ error
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Adding Replication Manager...
+ </div>
+ </Row>;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Add Replication Manager
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <p>Create a Replication Manager entry, and add it to the replication configuration for this suffix. If the entry already exists it will be overwritten with the new credentials.</p>
+ <Row className="ds-margin-top" title="The DN of the replication manager">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Manager DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="manager"
+ defaultValue="cn=replication manager,cn=config"
+ className={error.manager ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="manager_passwd"
+ className={error.manager_passwd ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="manager_passwd_confirm"
+ className={error.manager_passwd_confirm ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Add Replication Manager
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class EnableReplModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ role,
+ error
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Enabling Replication ...
+ </div>
+ </Row>;
+ }
+
+ let replicaIDRow = "";
+ if (role == "Master") {
+ replicaIDRow =
+ <Row className="ds-margin-top">
+ <Col sm={3} componentClass={ControlLabel}>
+ Replica ID
+ </Col>
+ <Col sm={9}>
+ <input id="enableRID" type="number" min="1" max="65534"
+ onChange={handleChange} defaultValue="1" size="10"
+ />
+ </Col>
+ </Row>;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Enable Replication
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <p>
+ Choose the replication role for this suffix. If it
+ is a Master replica then you must pick a unique ID
+ to identify it among the other Master replicas in your
+ environment. You can optionally define the authentication
+ information for this replicated suffix, either a Bind DN and
+ Password, or a Group DN. The replication changelog will
+ also automatically be created if it does not exist.
+ </p>
+ <hr />
+ <Row className="ds-margin-top-lg">
+ <Col sm={3} componentClass={ControlLabel}>
+ Replication Role
+ </Col>
+ <Col sm={9}>
+ <select className="btn btn-default dropdown" id="enableRole" defaultValue="Master" onChange={handleChange}>
+ <option>Master</option>
+ <option>Hub</option>
+ <option>Consumer</option>
+ </select>
+ </Col>
+ </Row>
+ {replicaIDRow}
+ <hr />
+ <Row className="ds-margin-top" title="The DN of the replication manager. If you supply a password the entry will be created in the server (it will also overwrite the entry is it already exists).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Manager DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="enableBindDN"
+ defaultValue="cn=replication manager,cn=config"
+ className={error.enableBindDN ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="enableBindPW"
+ className={error.enableBindPW ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Confirm the Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="enableBindPWConfirm"
+ className={error.enableBindPWConfirm ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The DN of a group that contains users that can perform replication updates">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Group DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="enableBindGroupDN"
+ className={error.enableBindGroupDN ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Enable Replication
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class ExportModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ saveOK
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="lg" />Exporting database... <font size="1">(You can safely close this window)</font>
+ </div>
+ </Row>;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Create Replication Initialization LDIF File
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row>
+ <Col sm={11} className="ds-left-indent-md">
+ <p>Enter the name of the LDIF file, do not use a path as the file will only be written to the server's LDIF directory</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg" title="Name of the exported LDIF file">
+ <Col sm={3}>
+ <b>LDIF Name</b>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ id="ldifLocation"
+ className={saveOK ? "" : "ds-input-bad"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ disabled={!saveOK}
+ >
+ Export Replica
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+EnableReplModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+EnableReplModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ error: {},
+};
+
+AddManagerModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+AddManagerModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ error: {},
+};
+
+ChangeReplRoleModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ role: PropTypes.string,
+ newRole: PropTypes.string,
+};
+
+ChangeReplRoleModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ role: "",
+ newRole: "",
+};
+
+ReplAgmtModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleStripChange: PropTypes.func,
+ handleFracChange: PropTypes.func,
+ handleFracInitChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ availAttrs: PropTypes.array,
+ agmtName: PropTypes.string,
+ agmtHost: PropTypes.string,
+ agmtPort: PropTypes.string,
+ agmtProtocol: PropTypes.string,
+ agmtBindMethod: PropTypes.string,
+ agmtBindDN: PropTypes.string,
+ agmtBindPW: PropTypes.string,
+ agmtBindPWConfirm: PropTypes.string,
+ agmtStripAttrs: PropTypes.array,
+ agmtFracAttrs: PropTypes.array,
+ agmtFracInitAttrs: PropTypes.array,
+ agmtSync: PropTypes.bool,
+ agmtSyncMon: PropTypes.bool,
+ agmtSyncTue: PropTypes.bool,
+ agmtSyncWed: PropTypes.bool,
+ agmtSyncThu: PropTypes.bool,
+ agmtSyncFri: PropTypes.bool,
+ agmtSyncSat: PropTypes.bool,
+ agmtSyncSun: PropTypes.bool,
+ agmtStartTime: PropTypes.string,
+ agmtEndTime: PropTypes.string,
+ saveOK: PropTypes.bool,
+ error: PropTypes.object,
+ errorMsg: PropTypes.string,
+ edit: PropTypes.bool,
+};
+
+ReplAgmtModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ handleStripChange: noop,
+ handleFracChange: noop,
+ handleFracInitChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ availAttrs: [],
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindMethod: "SIMPLE",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtStripAttrs: [],
+ agmtFracAttrs: [],
+ agmtFracInitAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: false,
+ agmtSyncTue: false,
+ agmtSyncWed: false,
+ agmtSyncThu: false,
+ agmtSyncFri: false,
+ agmtSyncSat: false,
+ agmtSyncSun: false,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ saveOK: false,
+ error: {},
+ errorMsg: "",
+ edit: false,
+};
+
+WinsyncAgmtModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleFracChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ availAttrs: PropTypes.array,
+ agmtName: PropTypes.string,
+ agmtHost: PropTypes.string,
+ agmtPort: PropTypes.string,
+ agmtProtocol: PropTypes.string,
+ agmtBindDN: PropTypes.string,
+ agmtBindPW: PropTypes.string,
+ agmtBindPWConfirm: PropTypes.string,
+ agmtFracAttrs: PropTypes.array,
+ agmtSync: PropTypes.bool,
+ agmtSyncMon: PropTypes.bool,
+ agmtSyncTue: PropTypes.bool,
+ agmtSyncWed: PropTypes.bool,
+ agmtSyncThu: PropTypes.bool,
+ agmtSyncFri: PropTypes.bool,
+ agmtSyncSat: PropTypes.bool,
+ agmtSyncSun: PropTypes.bool,
+ agmtStartTime: PropTypes.string,
+ agmtEndTime: PropTypes.string,
+ agmtSyncGroups: PropTypes.bool,
+ agmtSyncUsers: PropTypes.bool,
+ agmtWinDomain: PropTypes.string,
+ agmtWinSubtree: PropTypes.string,
+ agmtDSSubtree: PropTypes.string,
+ agmtOneWaySync: PropTypes.string,
+ agmtSyncInterval: PropTypes.string,
+ saveOK: PropTypes.bool,
+ error: PropTypes.object,
+ errorMsg: PropTypes.string,
+ edit: PropTypes.bool,
+};
+
+WinsyncAgmtModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ handleFracChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ availAttrs: [],
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAPS",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtFracAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: false,
+ agmtSyncTue: false,
+ agmtSyncWed: false,
+ agmtSyncThu: false,
+ agmtSyncFri: false,
+ agmtSyncSat: false,
+ agmtSyncSun: false,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ agmtSyncGroups: false,
+ agmtSyncUsers: false,
+ agmtWinDomain: "",
+ agmtWinSubtree: "",
+ agmtDSSubtree: "",
+ agmtOneWaySync: "both", // "both", "toWindows", "fromWindows"
+ agmtSyncInterval: "",
+ saveOK: false,
+ error: {},
+ errorMsg: "",
+ edit: false,
+};
+
+ExportModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ saveOK: PropTypes.bool,
+ spinning: PropTypes.bool
+};
+
+ExportModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ saveOK: false,
+ spinning: false
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replSuffix.jsx b/src/cockpit/389-console/src/lib/replication/replSuffix.jsx
new file mode 100644
index 0000000..0978c0c
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replSuffix.jsx
@@ -0,0 +1,456 @@
+import React from "react";
+import cockpit from "cockpit";
+import { ReplConfig } from "./replConfig.jsx";
+import { WinsyncAgmts } from "./winsyncAgmts.jsx";
+import { ReplAgmts } from "./replAgmts.jsx";
+import { ReplRUV } from "./replTasks.jsx";
+import { DoubleConfirmModal } from "../notifications.jsx";
+import { EnableReplModal } from "./replModals.jsx";
+import {
+ Button,
+ Col,
+ ControlLabel,
+ Icon,
+ Nav,
+ NavItem,
+ noop,
+ Row,
+ Spinner,
+ TabContainer,
+ TabContent,
+ TabPane,
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import { log_cmd, valid_dn } from "../tools.jsx";
+import "../../css/ds.css";
+
+export class ReplSuffix extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ loading: false,
+ activeKey: 1,
+ showDisableConfirm: false,
+ replicationEnabled: false,
+ errObj: {},
+ replEnabled: this.props.replicated,
+ // Enable replication settings
+ showEnableReplModal: false,
+ enableRole: "Master",
+ enableRID: "1",
+ enableBindDN: "cn=replication manager,cn=config",
+ enableBindPW: "",
+ enableBindPWConfirm: "",
+ enableBindGroupDN: "",
+ // Disable replication
+ showDisableReplModal: false,
+ disableChecked: false,
+ disableSpinning: false,
+ modalChecked: false,
+ modalSpinning: false,
+ };
+
+ // General bindings
+ this.handleReplChange = this.handleReplChange.bind(this);
+ this.handleEnableChange = this.handleEnableChange.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.disableReplication = this.disableReplication.bind(this);
+ this.enableReplication = this.enableReplication.bind(this);
+ this.closeEnableReplModal = this.closeEnableReplModal.bind(this);
+ this.closeDisableReplModal = this.closeDisableReplModal.bind(this);
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ handleReplChange() {
+ if (this.props.replicated) {
+ // Disable Replication
+ this.setState({
+ showDisableReplModal: true,
+ modalChecked: false,
+ modalSpinning: false
+ });
+ } else {
+ // Enable replication
+ this.setState({
+ showEnableReplModal: true
+ });
+ }
+ }
+
+ handleChange (e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ handleEnableChange (e) {
+ let value = e.target.value;
+ let attr = e.target.id;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+
+ if (attr == "enableBindDN" && value != "" && !valid_dn(value)) {
+ valueErr = true;
+ }
+ if (attr == "enableBindGroupDN" && value != "" && !valid_dn(value)) {
+ valueErr = true;
+ }
+ if (attr == "enableBindPW") {
+ if (value != this.state.enableBindPWConfirm) {
+ valueErr = true;
+ } else {
+ errObj.enableBindPW = false;
+ errObj.enableBindPWConfirm = false;
+ }
+ }
+ if (attr == "enableBindPWConfirm") {
+ if (value != this.state.enableBindPW) {
+ valueErr = true;
+ } else {
+ errObj.enableBindPW = false;
+ errObj.enableBindPWConfirm = false;
+ }
+ }
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ errObj: errObj
+ });
+ }
+
+ closeEnableReplModal () {
+ this.setState({
+ showEnableReplModal: false,
+ });
+ }
+
+ enableReplication () {
+ // First, Validate
+ if (this.state.enableBindDN != "" && !valid_dn(this.state.enableBindDN)) {
+ this.props.addNotification(
+ "error",
+ `The Bind DN is not a valid DN (Distinguished Name) ${this.state.enableBindDN}`
+ );
+ return;
+ }
+ if (this.state.enableBindGroupDN != "" && !valid_dn(this.state.enableBindGroupDN)) {
+ this.props.addNotification(
+ "error",
+ `The Group DN is not a valid DN (Distinguished Name)`
+ );
+ return;
+ }
+ if (this.state.enableBindPW != this.state.enableBindPWConfirm) {
+ this.props.addNotification(
+ "error",
+ `The Bind DN passwords do not match`
+ );
+ return;
+ }
+ if (this.state.enableRID != "" && (this.state.enableRID < 1 || this.state.enableRID > 65534)) {
+ this.props.addNotification(
+ "error",
+ `The Replica ID is not in the valid range of 1 - 65534`
+ );
+ return;
+ }
+
+ // Now enable replication
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'enable', '--suffix=' + this.props.suffix,
+ '--role=' + this.state.enableRole
+ ];
+ if (this.state.enableBindDN != "") {
+ cmd.push('--bind-dn=' + this.state.enableBindDN);
+ }
+ if (this.state.enableBindPW != "") {
+ cmd.push('--bind-passwd=' + this.state.enableBindPW);
+ }
+ if (this.state.enableBindGroupDN != "") {
+ cmd.push('--bind-group-dn=' + this.state.enableBindGroupDN);
+ }
+ if (this.state.enableRole == "Master") {
+ cmd.push('--replica-id=' + this.state.enableRID);
+ }
+
+ this.props.disableTree();
+ log_cmd('enableReplication', 'Enable replication', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(1);
+ this.props.addNotification(
+ "success",
+ `Successfully enabled replication for "${this.props.suffix}"`
+ );
+ })
+ .fail(err => {
+ this.props.reload(1);
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to enable replication for "${this.props.suffix}" - ${errMsg.desc}`
+ );
+ });
+ }
+
+ closeDisableReplModal () {
+ this.setState({
+ showDisableReplModal: false
+ });
+ }
+
+ disableReplication () {
+ this.props.disableTree();
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket', 'replication', 'disable', '--suffix=' + this.props.suffix];
+ log_cmd('disableReplication', 'Disable replication', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(1);
+ this.props.addNotification(
+ "success",
+ `Successfully disabled replication for "${this.props.suffix}"`
+ );
+ })
+ .fail(err => {
+ this.props.reload(1);
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to disable replication for "${this.props.suffix}" - ${errMsg.desc}`
+ );
+ });
+ }
+
+ //
+ // Render the component
+ //
+ render () {
+ let spinning = "";
+ let spintext = "";
+ let suffixIcon = "tree";
+ if (this.props.replicated) {
+ suffixIcon = "clone";
+ } else {
+ if (this.props.repl == "subsuffix") {
+ suffixIcon = "leaf";
+ }
+ }
+ if (this.props.spinning) {
+ spinning =
+ <Spinner className="ds-margin-top ds-margin-left ds-inline-spinner" loading inline size="sm" />;
+ spintext =
+ <font size="2"><i>Refreshing</i></font>;
+ }
+ let suffixClass = "ds-margin-top-xlg";
+ if (this.props.disabled) {
+ suffixClass = "ds-margin-top-xlg ds-disabled";
+ }
+ let replAgmtNavTitle = 'Replication Agreements <font size="1">(' + this.props.agmtRows.length + ')</font>';
+ let winsyncNavTitle = 'Winsync Agreements <font size="1">(' + this.props.winsyncRows.length + ')</font>';
+
+ let enabledContent =
+ <div className={suffixClass}>
+ <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div>
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Configuration'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: replAgmtNavTitle}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: winsyncNavTitle}} />
+ </NavItem>
+ <NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: "RUV's & Tasks"}} />
+ </NavItem>
+ </Nav>
+ <TabContent>
+ <TabPane eventKey={1}>
+ <ReplConfig
+ suffix={this.props.suffix}
+ role={this.props.role}
+ data={this.props.data}
+ serverId={this.props.serverId}
+ addNotification={this.props.addNotification}
+ reload={this.props.reload}
+ reloadConfig={this.props.reloadConfig}
+ />
+ </TabPane>
+ <TabPane eventKey={2}>
+ <ReplAgmts
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ rows={this.props.agmtRows}
+ addNotification={this.props.addNotification}
+ reload={this.props.reloadAgmts}
+ attrs={this.props.attrs}
+ disableTable={this.props.disableAgmtTable}
+ key={this.props.agmtRows}
+ />
+ </TabPane>
+ <TabPane eventKey={3}>
+ <WinsyncAgmts
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ rows={this.props.winsyncRows}
+ addNotification={this.props.addNotification}
+ reload={this.props.reloadWinsyncAgmts}
+ attrs={this.props.attrs}
+ disableTable={this.props.disableWSAgmtTable}
+ key={this.props.winsyncRows}
+ />
+ </TabPane>
+ <TabPane eventKey={4}>
+ <ReplRUV
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ rows={this.props.ruvRows}
+ addNotification={this.props.addNotification}
+ reload={this.props.reloadRUV}
+ localRID={this.props.data.nsds5replicaid}
+ key={this.props.ruvRows}
+ />
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+
+ let replActionButton = "";
+ if (this.props.replicated) {
+ replActionButton =
+ <Button
+ bsStyle="danger"
+ onClick={this.handleReplChange}
+ title="Disable replication, and remove all replication agreements."
+ >
+ Disable
+ </Button>;
+ } else {
+ enabledContent =
+ <div className="ds-center ds-margin-top-xlg">
+ <h4>
+ Replication is not enabled for this suffix
+ </h4>
+ <Button
+ bsStyle="primary"
+ onClick={this.handleReplChange}
+ className="ds-margin-top-lg"
+ >
+ Enable Replication
+ </Button>
+ </div>;
+ }
+
+ return (
+ <div id="suffix-page">
+ <Row>
+ <Col sm={8} className="ds-word-wrap">
+ <ControlLabel className="ds-suffix-header"><Icon type="fa" name={suffixIcon} />
+ {" " + this.props.suffix}
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh replication settings for this suffix"
+ onClick={() => {
+ this.props.reload(false);
+ }}
+ />
+ {spinning} {spintext}
+ </ControlLabel>
+ </Col>
+ <Col sm={4}>
+ <Row>
+ <Col className="ds-no-padding ds-container" componentClass={ControlLabel} sm={12}>
+ {replActionButton}
+ </Col>
+ </Row>
+ </Col>
+ </Row>
+ <p />
+ {enabledContent}
+ <EnableReplModal
+ showModal={this.state.showEnableReplModal}
+ closeHandler={this.closeEnableReplModal}
+ handleChange={this.handleEnableChange}
+ saveHandler={this.enableReplication}
+ spinning={this.state.addManagerSpinning}
+ role={this.state.enableRole}
+ error={this.state.errObj}
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showDisableReplModal}
+ closeHandler={this.closeDisableReplModal}
+ handleChange={this.handleChange}
+ actionHandler={this.disableReplication}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Disable Replication"
+ mMsg="Are you sure you want to disable replication for this suffix?"
+ mSpinningMsg="Disabling Replication ..."
+ mBtnName="Disable Replication"
+ />
+ </div>
+ );
+ }
+}
+
+ReplSuffix.propTypes = {
+ serverId: PropTypes.string,
+ suffix: PropTypes.string,
+ role: PropTypes.string,
+ addNotification: PropTypes.func,
+ agmtRows: PropTypes.array,
+ winsyncRows: PropTypes.array,
+ ruvRows: PropTypes.array,
+ reloadAgmts: PropTypes.func,
+ reloadRUV: PropTypes.func,
+ reloadConfig: PropTypes.func,
+ reload: PropTypes.func,
+ replicated: PropTypes.bool,
+ attrs: PropTypes.array,
+ enableTree: PropTypes.func,
+ disableTree: PropTypes.func,
+ spinning: PropTypes.bool,
+ disabled: PropTypes.bool,
+};
+
+ReplSuffix.defaultProps = {
+ serverId: "",
+ suffix: "",
+ role: "",
+ addNotification: noop,
+ agmtRows: [],
+ winsyncRows: [],
+ ruvRows: [],
+ reloadAgmts: noop,
+ reloadRUV: noop,
+ reloadConfig: noop,
+ reload: noop,
+ replicated: false,
+ attrs: [],
+ enableTree: noop,
+ disableTree: noop,
+ spinning: false,
+ disabled: false,
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replTables.jsx b/src/cockpit/389-console/src/lib/replication/replTables.jsx
new file mode 100644
index 0000000..332d458
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replTables.jsx
@@ -0,0 +1,608 @@
+import React from "react";
+import {
+ Button,
+ DropdownButton,
+ MenuItem,
+ actionHeaderCellFormatter,
+ sortableHeaderCellFormatter,
+ tableCellFormatter,
+ noop
+} from "patternfly-react";
+import { DSTable, DSShortTable } from "../dsTable.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+class ReplAgmtTable extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ searchField: "Agreements",
+ fieldsToSearch: ["name"],
+ rowKey: "name",
+ columns: [
+ {
+ property: "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: "host",
+ header: {
+ label: "Replica Hostname",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "port",
+ header: {
+ label: "Replica Port",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "state",
+ header: {
+ label: "State",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "status",
+ header: {
+ label: "Last Update Status",
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "initstatus",
+ header: {
+ label: "Last Init Status",
+ props: {
+ index: 5,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 5
+ },
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 6,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 6
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.name[0]}>
+ <DropdownButton
+ pullRight
+ id={rowData.name[0]}
+ className="ds-action-button"
+ bsStyle="primary" title="Actions"
+ >
+ <MenuItem eventKey="1" onClick={() => {
+ this.props.edit(rowData.name[0]);
+ }}
+ >
+ Edit Agreement
+ </MenuItem>
+ <MenuItem eventKey="2" onClick={() => {
+ this.props.init(rowData.name[0]);
+ }}
+ >
+ Initialize Agreement
+ </MenuItem>
+ <MenuItem eventKey="3" onClick={() => {
+ this.props.poke(rowData.name[0]);
+ }}
+ title="Awaken agreement if it is sleeping"
+ >
+ Poke Agreement
+ </MenuItem>
+ <MenuItem eventKey="4" onClick={() => {
+ this.props.enable(rowData.name[0], rowData.state[0]);
+ }}
+ >
+ Disable/Enable Agreement
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem eventKey="3" onClick={() => {
+ this.props.delete(rowData.name[0], rowData.state[0]);
+ }}
+ >
+ Delete Agreement
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ },
+ ],
+ };
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Agreements",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ let agmtTable;
+ if (this.props.rows.length == 0) {
+ agmtTable =
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No Agreements"}]}
+ />;
+ } else {
+ agmtTable =
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />;
+ }
+
+ return (
+ <div className="ds-margin-top-xlg">
+ {agmtTable}
+ </div>
+
+ );
+ }
+}
+
+class ManagerTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "name",
+ columns: [
+ {
+ property: "name",
+ header: {
+ label: "Replication Manager",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ label: "",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.name[0]}>
+ <Button
+ bsStyle="default"
+ onClick={() => {
+ this.props.confirmDelete(rowData);
+ }}
+ title="Delete replication manager"
+ >
+ Delete
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Replication Managers",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ let managerTable;
+ if (this.props.rows.length == 0) {
+ managerTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No Replication Managers"}]}
+ />;
+ } else {
+ managerTable = <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />;
+ }
+ return (
+ <div>
+ {managerTable}
+ </div>
+ );
+ }
+}
+
+class RUVTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "rid",
+ columns: [
+ {
+ property: "rid",
+ header: {
+ label: "Replica ID",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "url",
+ header: {
+ label: "Replica LDAP URL",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "maxcsn",
+ header: {
+ label: "Max CSN",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ label: "",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.rid[0]}>
+ <Button
+ bsStyle="default"
+ onClick={() => {
+ this.props.confirmDelete(rowData.rid[0]);
+ }}
+ title="Attempt to clean and remove this Replica ID from this suffix"
+ >
+ Clean
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Remote RUV's",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ let ruvTable;
+ if (this.props.rows.length == 0) {
+ ruvTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No RUV's"}]}
+ />;
+ } else {
+ ruvTable = <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />;
+ }
+ return (
+ <div>
+ {ruvTable}
+ </div>
+ );
+ }
+}
+
+ReplAgmtTable.propTypes = {
+ rows: PropTypes.array,
+ edit: PropTypes.func,
+ poke: PropTypes.func,
+ init: PropTypes.func,
+ enable: PropTypes.func,
+ delete: PropTypes.func,
+};
+
+ReplAgmtTable.defaultProps = {
+ rows: [],
+ edit: noop,
+ poke: noop,
+ init: noop,
+ enable: noop,
+ delete: noop,
+};
+
+ManagerTable.propTypes = {
+ rows: PropTypes.array,
+ confirmDelete: PropTypes.func
+};
+
+ManagerTable.defaultProps = {
+ rows: [],
+ confirmDelete: noop
+};
+
+RUVTable.propTypes = {
+ rows: PropTypes.array,
+ confirmDelete: PropTypes.func
+};
+
+RUVTable.defaultProps = {
+ rows: [],
+ confirmDelete: noop
+};
+
+export {
+ ReplAgmtTable,
+ ManagerTable,
+ RUVTable,
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replTasks.jsx b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
new file mode 100644
index 0000000..acdc6c8
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
@@ -0,0 +1,314 @@
+import cockpit from "cockpit";
+import React from "react";
+import { log_cmd, bad_file_name } from "../tools.jsx";
+import { RUVTable } from "./replTables.jsx";
+import { ExportModal } from "./replModals.jsx";
+import { DoubleConfirmModal } from "../notifications.jsx";
+import PropTypes from "prop-types";
+import {
+ Button,
+ Col,
+ ControlLabel,
+ Icon,
+ noop,
+ Row,
+} from "patternfly-react";
+
+export class ReplRUV extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ errObj: {},
+ rid: "",
+ localRID: "",
+ ldifLocatio: "",
+ saveOK: false,
+ showConfirmCleanRUV: false,
+ modalChecked: false,
+ modalSpinning: false,
+ };
+ this.showConfirmCleanRUV = this.showConfirmCleanRUV.bind(this);
+ this.closeConfirmCleanRUV = this.closeConfirmCleanRUV.bind(this);
+ this.showConfirmExport = this.showConfirmExport.bind(this);
+ this.closeConfirmExport = this.closeConfirmExport.bind(this);
+ this.handleLDIFChange = this.handleLDIFChange.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.cleanRUV = this.cleanRUV.bind(this);
+ this.doExport = this.doExport.bind(this);
+ }
+
+ showConfirmCleanRUV (rid) {
+ this.setState({
+ rid: rid,
+ showConfirmCleanRUV: true,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ closeConfirmCleanRUV () {
+ this.setState({
+ showConfirmCleanRUV: false,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ showConfirmExport () {
+ this.setState({
+ saveOK: false,
+ showConfirmExport: true,
+ });
+ }
+
+ closeConfirmExport () {
+ this.setState({
+ showConfirmExport: false,
+ });
+ }
+
+ cleanRUV () {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-tasks', 'cleanallruv', '--replica-id=' + this.state.rid,
+ '--force-cleaning', '--suffix=' + this.props.suffix];
+
+ log_cmd('cleanRUV', 'Clean the rid', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully started CleanAllRUV task');
+ this.closeConfirmCleanRUV();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to start CleanAllRUV task - ${errMsg.desc}`
+ );
+ this.closeConfirmCleanRUV();
+ });
+ }
+
+ handleLDIFChange (e) {
+ let value = e.target.value;
+ let saveOK = true;
+ if (value == "" || bad_file_name(value)) {
+ saveOK = false;
+ }
+ this.setState({
+ [e.target.id]: value,
+ saveOK: saveOK
+ });
+ }
+
+ handleChange (e) {
+ this.setState({
+ [e.target.id]: e.target.checked,
+ });
+ }
+
+ doExport () {
+ // Do Export
+ let export_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "export", this.props.suffix, "--ldif=" + this.state.ldifLocation,
+ '--replication', "--encrypted"
+ ];
+
+ this.setState({
+ exportSpinner: true,
+ });
+
+ log_cmd("doExport", "replication do online export", export_cmd);
+ cockpit
+ .spawn(export_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ `Database export complete`
+ );
+ this.setState({
+ showConfirmExport: false,
+ exportSpinner: false,
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error exporting database - ${errMsg.desc}`
+ );
+ this.setState({
+ showConfirmExport: false,
+ exportSpinner: false,
+ });
+ });
+ }
+
+ render() {
+ // Strip outthe loca RUV and display it diffent then onmly allow cleaning of remote rids
+ let remote_rows = [];
+ let localRID = "";
+ let localURL = "";
+ let localCSN = "";
+ let localRawCSN = "";
+ let localMinCSN = "";
+ let localRawMinCSN = "";
+ for (let row of this.props.rows) {
+ if (row.rid == this.props.localRID) {
+ localRID = row.rid;
+ localURL = row.url;
+ localCSN = row.maxcsn;
+ localRawCSN = row.raw_maxcsn;
+ localMinCSN = row.csn;
+ localRawMinCSN = row.raw_csn;
+ } else {
+ remote_rows.push(row);
+ }
+ }
+ let localRUV =
+ <div className="ds-left-indent-md">
+ <Row className="ds-margin-top-med">
+ <Col sm={2}>
+ <ControlLabel>
+ Replica ID
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localRID}</b>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={2}>
+ <ControlLabel>
+ LDAP URL
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localURL}</b>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={2}>
+ <ControlLabel>
+ Min CSN
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localMinCSN}</b> ({localRawMinCSN})
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={2}>
+ <ControlLabel>
+ Max CSN
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localCSN}</b> ({localRawCSN})
+ </Col>
+ </Row>
+ </div>;
+
+ if (localRID == "") {
+ localRUV =
+ <div className="ds-indent">
+ <i>
+ There is no local RUV, the database might not have been initialized yet.
+ </i>
+ </div>;
+ }
+
+ return (
+ <div className="ds-margin-top-xlg ds-indent">
+ <ControlLabel className="ds-h4">
+ Local RUV
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the RUV for this suffix"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+ />
+ </ControlLabel>
+ {localRUV}
+ <hr />
+ <Row className="ds-margin-top">
+ <Col sm={12} className="ds-word-wrap">
+ <ControlLabel className="ds-h4">
+ Remote RUV's
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh the RUV for this suffix"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ <div className="ds-left-indent-md ds-margin-top-lg">
+ <RUVTable
+ rows={remote_rows}
+ confirmDelete={this.showConfirmCleanRUV}
+ />
+ </div>
+ <hr />
+ <h4>Create Initialization LDIF File</h4>
+ <div className="ds-left-indent-md ds-margin-top-lg">
+ <p><i>Export this suffix with the replication metadata needed to initialize another replica</i></p>
+ <Button
+ bsStyle="primary"
+ onClick={this.showConfirmExport}
+ className="ds-margin-top"
+ title="See Database Tab -> Backups & LDIFs to manage the new LDIF"
+ >
+ Export To LDIF
+ </Button>
+ </div>
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmCleanRUV}
+ closeHandler={this.closeConfirmCleanRUV}
+ handleChange={this.handleChange}
+ actionHandler={this.cleanRUV}
+ spinning={this.state.modalSpinning}
+ item={"Replica ID " + this.state.rid}
+ checked={this.state.modalChecked}
+ mTitle="Remove RUV Element (CleanAllRUV)"
+ mMsg="Are you sure you want to attempt to clean this Replica ID from the suffix?"
+ mSpinningMsg="Starting cleaning task (CleanAllRUV) ..."
+ mBtnName="Remove RUV Element"
+ />
+ <ExportModal
+ showModal={this.state.showConfirmExport}
+ closeHandler={this.closeConfirmExport}
+ handleChange={this.handleLDIFChange}
+ saveHandler={this.doExport}
+ spinning={this.state.exportSpinner}
+ saveOK={this.state.saveOK}
+ />
+ </div>
+ );
+ }
+}
+
+ReplRUV.propTypes = {
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ rows: PropTypes.array,
+ addNotification: PropTypes.func,
+ localRID: PropTypes.string,
+ reload: PropTypes.func,
+};
+
+ReplRUV.defaultProps = {
+ serverId: "",
+ suffix: "",
+ rows: [],
+ addNotification: noop,
+ localRID: "",
+ reload: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx b/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx
new file mode 100644
index 0000000..9303720
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx
@@ -0,0 +1,1210 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup, DoubleConfirmModal } from "../notifications.jsx";
+import { ReplAgmtTable } from "./replTables.jsx";
+import { WinsyncAgmtModal } from "./replModals.jsx";
+import { log_cmd, valid_dn, valid_port } from "../tools.jsx";
+import PropTypes from "prop-types";
+import {
+ Button,
+ // Icon,
+ noop,
+} from "patternfly-react";
+
+export class WinsyncAgmts extends React.Component {
+ _mounted = false;
+ constructor(props) {
+ super(props);
+ this.state = {
+ showCreateAgmtModal: false,
+ showEditAgmtModal: false,
+ showConfirmDeleteAgmt: false,
+ showConfirmInitAgmt: false,
+ showConfirmEnableAgmt: false,
+ showConfirmDisableAgmt: false,
+ errObj: {},
+ modalMsg: "",
+ modalScheduleMsg: "",
+ savingAgmt: false,
+ mounted: false,
+ // Create agreement
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAPS",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtFracAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "0",
+ agmtEndTime: "0",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ modalChecked: false,
+ modalSpinning: false,
+ // Winsync specific settings
+ agmtSyncGroups: false,
+ agmtSyncUsers: true,
+ agmtWinDomain: "",
+ agmtWinSubtree: "",
+ agmtDSSubtree: "",
+ agmtOneWaySync: "both", // "both", "toWindows", "fromWindows"
+ agmtSyncInterval: "",
+ // Init agmt
+ agmtInitCounter: 0,
+ agmtInitIntervals: [],
+ };
+ this.showCreateAgmtModal = this.showCreateAgmtModal.bind(this);
+ this.closeCreateAgmtModal = this.closeCreateAgmtModal.bind(this);
+ this.closeEditAgmtModal = this.closeEditAgmtModal.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleModalChange = this.handleModalChange.bind(this);
+ this.handleTAFracAttrChange = this.handleTAFracAttrChange.bind(this);
+ this.handleTAFracAttrChangeEdit = this.handleTAFracAttrChangeEdit.bind(this);
+ this.createAgmt = this.createAgmt.bind(this);
+ this.showEditAgmt = this.showEditAgmt.bind(this);
+ this.saveAgmt = this.saveAgmt.bind(this);
+ this.pokeAgmt = this.pokeAgmt.bind(this);
+ this.initAgmt = this.initAgmt.bind(this);
+ this.enableAgmt = this.enableAgmt.bind(this);
+ this.disableAgmt = this.disableAgmt.bind(this);
+ this.deleteAgmt = this.deleteAgmt.bind(this);
+ this.showConfirmDeleteAgmt = this.showConfirmDeleteAgmt.bind(this);
+ this.closeConfirmDeleteAgmt = this.closeConfirmDeleteAgmt.bind(this);
+ this.showConfirmInitAgmt = this.showConfirmInitAgmt.bind(this);
+ this.closeConfirmInitAgmt = this.closeConfirmInitAgmt.bind(this);
+ this.confirmToggle = this.confirmToggle.bind(this);
+ this.closeConfirmEnableAgmt = this.closeConfirmEnableAgmt.bind(this);
+ this.closeConfirmDisableAgmt = this.closeConfirmDisableAgmt.bind(this);
+ this.watchAgmtInit = this.watchAgmtInit.bind(this);
+ }
+
+ componentDidMount () {
+ this._mounted = true;
+ }
+
+ componentWillUnmount () {
+ this._mounted = false;
+ }
+
+ listEqual(old_values, new_values) {
+ if (old_values.length != new_values.length) {
+ return false;
+ }
+ for (let i = old_values.length; i--;) {
+ if (old_values[i] != new_values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ handleModalChange (e) {
+ this.setState({
+ [e.target.id]: e.target.checked,
+ });
+ }
+
+ handleChange (e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let time_val = "";
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ let all_good = true;
+ let modal_msg = "";
+ let modal_schedule_msg = "";
+ let edit = false;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ if (e.target.name == "agmt-modal-edit") {
+ let orig_attr = "_" + e.target.id;
+ let attr = e.target.id;
+ all_good = false;
+ if ((attr != 'agmtHost' && this.state.agmtHost != this.state._agmtHost) ||
+ (attr != 'agmtPort' && this.state.agmtPort != this.state._agmtPort) ||
+ (attr != 'agmtBindDN' && this.state.agmtBindDN != this.state._agmtBindDN) ||
+ (attr != 'agmtProtocol' && this.state.agmtProtocol != this.state._agmtProtocol) ||
+ (attr != 'agmtSync' && this.state.agmtSync != this.state._agmtSync) ||
+ (attr != 'agmtSyncGroups' && this.state.agmtSyncGroups != this.state._agmtSyncGroups) ||
+ (attr != 'agmtSyncUsers' && this.state.agmtSyncUsers != this.state._agmtSyncUsers) ||
+ (attr != 'agmtWinDomain' && this.state.agmtWinDomain != this.state._agmtWinDomain) ||
+ (attr != 'agmtWinSubtree' && this.state.agmtWinSubtree != this.state._agmtWinSubtree) ||
+ (attr != 'agmtDSSubtree' && this.state.agmtDSSubtree != this.state._agmtDSSubtree) ||
+ (attr != 'agmtOneWaySync' && this.state.agmtOneWaySync != this.state._agmtOneWaySync) ||
+ (attr != 'agmtSyncInterval' && this.state.agmtSyncInterval != this.state._agmtSyncInterval) ||
+ (attr != 'agmtFracAttrs' && !this.listEqual(this.state.agmtFracAttrs, this.state._agmtFracAttrs))) {
+ all_good = true;
+ }
+ if (!this.state._agmtSync) {
+ if ((attr != 'agmtSyncMon' && this.state.agmtSyncMon != this.state._agmtSyncMon) ||
+ (attr != 'agmtSyncTue' && this.state.agmtSyncTue != this.state._agmtSyncTue) ||
+ (attr != 'agmtSyncWed' && this.state.agmtSyncWed != this.state._agmtSyncWed) ||
+ (attr != 'agmtSyncThu' && this.state.agmtSyncThu != this.state._agmtSyncThu) ||
+ (attr != 'agmtSyncFri' && this.state.agmtSyncFri != this.state._agmtSyncFri) ||
+ (attr != 'agmtSyncSat' && this.state.agmtSyncSat != this.state._agmtSyncSat) ||
+ (attr != 'agmtSyncSun' && this.state.agmtSyncSun != this.state._agmtSyncSun)) {
+ all_good = true;
+ }
+ }
+ if (attr != 'agmtFracAttrs' &&
+ value != this.state[orig_attr]) {
+ all_good = true;
+ } else if (attr == 'agmtFracAttrs' && !this.listEqual(value, this.state._agmtFracAttrs)) {
+ all_good = true;
+ }
+ }
+
+ if (e.target.type == "time") {
+ // Strip out the colon from the time
+ time_val = value.replace(':', '');
+ }
+
+ if (e.target.name.startsWith("agmt-modal")) {
+ // Validate modal settings "live"
+ if (e.target.id == 'agmtName') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtName == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtHost') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtHost == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtHost) {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtPort') {
+ if (value == "") {
+ all_good = false;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['agmtPort'] = true;
+ modal_msg = "Invalid Consumer Port number";
+ }
+ } else if (this.state.agmtPort == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindDN') {
+ if (value == "") {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Bind DN";
+ }
+ } else if (this.state.agmtBindDN == "") {
+ all_good = false;
+ } else if (!valid_dn(this.state.agmtBindDN)) {
+ modal_msg = "Invalid DN for Bind DN";
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPW') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPWConfirm) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPW == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPWConfirm') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPW) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPWConfirm == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtSync') {
+ if (!value) {
+ if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ } else if (!this.state.agmtSync) {
+ // Check the days first
+ let have_days = false;
+ let days = ["agmtSyncSun", "agmtSyncMon", "agmtSyncTue", "agmtSyncWed",
+ "agmtSyncThu", "agmtSyncFri", "agmtSyncSat"];
+ for (let day of days) {
+ if ((e.target.id != day && this.state[day]) || (e.target.id == day && value)) {
+ have_days = true;
+ break;
+ }
+ }
+ if (!have_days) {
+ modal_schedule_msg = "You must select at least one day for replication";
+ all_good = false;
+ } else if (e.target.id == 'agmtStartTime') {
+ if (time_val == "") {
+ all_good = false;
+ errObj['agmtStartTime'] = true;
+ } else if (time_val >= this.state.agmtEndTime.replace(":", "")) {
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (e.target.id == 'agmtEndTime') {
+ if (time_val == "") {
+ errObj['agmtEndTime'] = true;
+ all_good = false;
+ } else if (this.state.agmtStartTime.replace(":", "") >= time_val) {
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtSyncGroups') {
+ if (edit && value == this.state._agmtSyncGroups) {
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtSyncUsers') {
+ if (edit && value == this.state._agmtSyncUsers) {
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtWinSubtree') {
+ if (value == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtWinSubtree) {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtWinSubtree'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Windows Subtree";
+ }
+ }
+ if (e.target.id == 'agmtDSSubtree') {
+ if (value == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtDSSubtree) {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtDSSubtree'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Directory Server Subtree";
+ }
+ }
+ if (e.target.id == 'agmtSyncInterval') {
+ if (value != "" && isNaN(value)) {
+ errObj['agmtSyncInterval'] = true;
+ all_good = false;
+ modal_msg = "Invalid value, value must be a number";
+ } else if (edit && value == this.state._agmtSyncInterval) {
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtOneWaySync') {
+ if (edit && value == this.state._agmtOneWaySync) {
+ all_good = false;
+ }
+ }
+ // End of agmt modal live validation
+ }
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj,
+ agmtSaveOK: all_good,
+ modalMsg: modal_msg,
+ modalScheduleMsg: modal_schedule_msg,
+ });
+ }
+
+ handleTAFracAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ showConfirmDeleteAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDeleteAgmt: true,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ closeConfirmDeleteAgmt () {
+ this.setState({
+ showConfirmDeleteAgmt: false,
+ modalChecked: false,
+ });
+ }
+
+ showConfirmInitAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmInitAgmt: true,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ closeConfirmInitAgmt () {
+ this.setState({
+ showConfirmInitAgmt: false,
+ modalChecked: false,
+ });
+ }
+
+ showCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: true,
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtFracAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ agmtSyncGroups: false,
+ agmtSyncUsers: true,
+ agmtWinDomain: "",
+ agmtWinSubtree: "",
+ agmtDSSubtree: "",
+ agmtOneWaySync: "both", // "both", "toWindows", "fromWindows"
+ agmtSyncInterval: "",
+ modalScheduleMsg: "",
+ errObj: {
+ // Marks all the fields as required
+ agmtName: true,
+ agmtHost: true,
+ agmtPort: true,
+ agmtBindDN: true,
+ agmtBindPW: true,
+ agmtBindPWConfirm: true,
+ agmtStartTime: false,
+ agmtEndTime: false,
+ agmtWinDomain: true,
+ agmtWinSubtree: true,
+ agmtDSSubtree: true,
+ }
+ });
+ }
+
+ closeCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: false
+ });
+ }
+
+ closeEditAgmtModal () {
+ this.setState({
+ showEditAgmtModal: false
+ });
+ }
+
+ showEditAgmt (agmtName) {
+ // Search for the agmt to get all the details
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'get', agmtName, '--suffix=' + this.props.suffix,
+ ];
+
+ log_cmd('showEditAgmt', 'Edit winsync agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let agmtName = "";
+ let agmtHost = "";
+ let agmtPort = "";
+ let agmtProtocol = "";
+ let agmtBindDN = "";
+ let agmtBindPW = "";
+ let agmtBindPWConfirm = "";
+ let agmtFracAttrs = [];
+ let agmtSync = true;
+ let agmtSyncMon = false;
+ let agmtSyncTue = false;
+ let agmtSyncWed = false;
+ let agmtSyncThu = false;
+ let agmtSyncFri = false;
+ let agmtSyncSat = false;
+ let agmtSyncSun = false;
+ let agmtStartTime = "";
+ let agmtEndTime = "";
+ let agmtSyncGroups = false;
+ let agmtSyncUsers = false;
+ let agmtWinDomain = "";
+ let agmtWinSubtree = "";
+ let agmtDSSubtree = "";
+ let agmtOneWaySync = "both";
+ let agmtSyncInterval = "";
+
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "winsyncinterval") {
+ agmtSyncInterval = val;
+ }
+ if (attr == "onewaysync") {
+ agmtOneWaySync = val;
+ }
+ if (attr == "nsds7directoryreplicasubtree") {
+ agmtDSSubtree = val;
+ }
+ if (attr == "nsds7windowsreplicasubtree") {
+ agmtWinSubtree = val;
+ }
+ if (attr == "nsds7windowsdomain") {
+ agmtWinDomain = val;
+ }
+ if (attr == "nsds7newwinusersyncenabled") {
+ if (val.toLowerCase() == "on") {
+ agmtSyncUsers = true;
+ }
+ }
+ if (attr == "nsds7newwingroupsyncenabled") {
+ if (val.toLowerCase() == "on") {
+ agmtSyncGroups = true;
+ }
+ }
+ if (attr == "cn") {
+ agmtName = val;
+ }
+ if (attr == "nsds5replicahost") {
+ agmtHost = val;
+ }
+ if (attr == "nsds5replicaport") {
+ agmtPort = val;
+ }
+ if (attr == "nsds5replicatransportinfo") {
+ agmtProtocol = val;
+ }
+ if (attr == "nsds5replicabinddn") {
+ agmtBindDN = val;
+ }
+ if (attr == "nsds5replicacredentials") {
+ agmtBindPW = val;
+ agmtBindPWConfirm = val;
+ }
+ if (attr == "nsds5replicatedattributelist") {
+ let attrs = val.replace("(objectclass=*) $ EXCLUDE", "").trim();
+ agmtFracAttrs = attrs.split(' ');
+ }
+ if (attr == "nsds5replicaupdateschedule") {
+ agmtSync = false;
+ // Parse schedule
+ let parts = val.split(' ');
+ let times = parts[0].split('-');
+ let days = parts[1];
+
+ // Do the times
+ agmtStartTime = times[0].substring(0, 2) + ":" + times[0].substring(2, 4);
+ agmtEndTime = times[1].substring(0, 2) + ":" + times[1].substring(2, 4);
+
+ // Do the days
+ if (days.includes("0")) {
+ agmtSyncSun = true;
+ }
+ if (days.includes("1")) {
+ agmtSyncMon = true;
+ }
+ if (days.includes("2")) {
+ agmtSyncTue = true;
+ }
+ if (days.includes("3")) {
+ agmtSyncWed = true;
+ }
+ if (days.includes("4")) {
+ agmtSyncThu = true;
+ }
+ if (days.includes("5")) {
+ agmtSyncFri = true;
+ }
+ if (days.includes("6")) {
+ agmtSyncSat = true;
+ }
+ }
+ }
+ if (this._mounted) {
+ this.setState({
+ showEditAgmtModal: true,
+ errObj: {},
+ agmtName: agmtName,
+ agmtHost: agmtHost,
+ agmtPort: agmtPort,
+ agmtProtocol: agmtProtocol,
+ agmtBindDN: agmtBindDN,
+ agmtBindPW: agmtBindPW,
+ agmtBindPWConfirm: agmtBindPWConfirm,
+ agmtFracAttrs: agmtFracAttrs,
+ agmtSync: agmtSync,
+ agmtSyncMon: agmtSyncMon,
+ agmtSyncTue: agmtSyncTue,
+ agmtSyncWed: agmtSyncWed,
+ agmtSyncThu: agmtSyncThu,
+ agmtSyncFri: agmtSyncFri,
+ agmtSyncSat: agmtSyncSat,
+ agmtSyncSun: agmtSyncSun,
+ agmtStartTime: agmtStartTime,
+ agmtEndTime: agmtEndTime,
+ agmtSyncGroups: agmtSyncGroups,
+ agmtSyncUsers: agmtSyncUsers,
+ agmtWinDomain: agmtWinDomain,
+ agmtWinSubtree: agmtWinSubtree,
+ agmtDSSubtree: agmtDSSubtree,
+ agmtOneWaySync: agmtOneWaySync,
+ agmtSyncInterval: agmtSyncInterval,
+ agmtSaveOK: false,
+ // Record original values before editing
+ _agmtName: agmtName,
+ _agmtHost: agmtHost,
+ _agmtPort: agmtPort,
+ _agmtProtocol: agmtProtocol,
+ _agmtBindDN: agmtBindDN,
+ _agmtBindPW: agmtBindPW,
+ _agmtBindPWConfirm: agmtBindPWConfirm,
+ _agmtFracAttrs: agmtFracAttrs,
+ _agmtSync: agmtSync,
+ _agmtSyncMon: agmtSyncMon,
+ _agmtSyncTue: agmtSyncTue,
+ _agmtSyncWed: agmtSyncWed,
+ _agmtSyncThu: agmtSyncThu,
+ _agmtSyncFri: agmtSyncFri,
+ _agmtSyncSat: agmtSyncSat,
+ _agmtSyncSun: agmtSyncSun,
+ _agmtStartTime: agmtStartTime,
+ _agmtEndTime: agmtEndTime,
+ _agmtSyncGroups: agmtSyncGroups,
+ _agmtSyncUsers: agmtSyncUsers,
+ _agmtWinDomain: agmtWinDomain,
+ _agmtWinSubtree: agmtWinSubtree,
+ _agmtDSSubtree: agmtDSSubtree,
+ _agmtOneWaySync: agmtOneWaySync,
+ _agmtSyncInterval: agmtSyncInterval,
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to get agreement information for: "${agmtName}" - ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'set', this.state.agmtName, '--suffix=' + this.props.suffix,
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' + this.state.agmtStartTime.replace(':', '') + "-" + this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ } else if (this.state.agmtSync != this.state._agmtSync && this.state.agmtSync) {
+ // We disabled custom scheduleRow
+ cmd.push('--schedule=');
+ }
+ if (this.state.agmtSyncGroups != this.state._agmtSyncGroups) {
+ let val = "off";
+ if (this.state.agmtSyncGroups) {
+ val = "on";
+ }
+ cmd.push('--sync-groups=' + val);
+ }
+ if (this.state.agmtSyncUsers != this.state._agmtSyncUsers) {
+ let val = "off";
+ if (this.state.agmtSyncUsers) {
+ val = "on";
+ }
+ cmd.push('--sync-users=' + val);
+ }
+ if (this.state.agmtWinDomain != this.state._agmtWinDomain) {
+ cmd.push('--win-domain=' + this.state.agmtWinDomain);
+ }
+ if (this.state.agmtWinSubtree != this.state._agmtWinSubtree) {
+ cmd.push('--win-subtree=' + this.state.agmtWinSubtree);
+ }
+ if (this.state.agmtDSSubtree != this.state._agmtDSSubtree) {
+ cmd.push('--ds-subtree=' + this.state.agmtDSSubtree);
+ }
+ if (this.state.agmtOneWaySync != this.state._agmtOneWaySync) {
+ cmd.push('--one-way-sync=' + this.state.agmtOneWaySync);
+ }
+ if (this.state.agmtSyncInterval != this.state._agmtSyncInterval) {
+ cmd.push('--sync-interval=' + this.state.agmtSyncInterval);
+ }
+ if (this.state.agmtProtocol != this.state._agmtProtocol) {
+ cmd.push('--conn-protocol=' + this.state.agmtProtocol);
+ }
+ if (this.state.agmtBindPW != this.state._agmtBindPW) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindPW);
+ }
+ if (this.state.agmtBindDN != this.state._agmtBindDN) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindDN);
+ }
+ if (this.state.agmtFracAttrs != this.state._agmtFracAttrs) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join(' '));
+ }
+ if (this.state.agmtHost != this.state._agmtHost) {
+ cmd.push('--host=' + this.state.agmtHost);
+ }
+ if (this.state.agmtPort != this.state._agmtPort) {
+ cmd.push('--port=' + this.state.agmtPort);
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('saveAgmt', 'update winsync agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showEditAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully updated winsync agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to update winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ pokeAgmt (agmtName) {
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'poke', agmtName, '--suffix=' + this.props.suffix];
+ log_cmd('pokeAgmt', 'send updates now', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, "err": "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully poked winsync agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to poke winsync agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ initAgmt () {
+ let init_cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'init', '--suffix=' + this.props.suffix, this.state.agmtName];
+ log_cmd('initAgmt', 'Initialize agreement', init_cmd);
+ cockpit
+ .spawn(init_cmd, { superuser: true, "err": "message" })
+ .done(content => {
+ var agmtIntervalCount = this.state.agmtInitCounter + 1;
+ var intervals = this.state.agmtInitIntervals;
+ this.props.reload(this.props.suffix);
+ intervals[agmtIntervalCount] = setInterval(this.watchAgmtInit, 2000, this.state.agmtName, agmtIntervalCount);
+ // This triggers error and does not actually work
+ if (this._mounted) {
+ this.setState({
+ agmtInitCounter: agmtIntervalCount,
+ agmtInitIntervals: intervals,
+ showConfirmInitAgmt: false
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to initialize winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showConfirmInitAgmt: false
+ });
+ });
+ }
+
+ confirmToggle (agmtName, state) {
+ if (state == 'Enabled') {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDisableAgmt: true
+ });
+ } else {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmEnableAgmt: true
+ });
+ }
+ }
+
+ closeConfirmEnableAgmt () {
+ this.setState({
+ showConfirmEnableAgmt: false
+ });
+ }
+
+ closeConfirmDisableAgmt () {
+ this.setState({
+ showConfirmDisableAgmt: false
+ });
+ }
+
+ enableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'enable', agmtName, '--suffix=' + this.props.suffix];
+ log_cmd('enableAgmt', 'enable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully enabled winsync agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to enabled winsync agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ disableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'disable', agmtName, '--suffix=' + this.props.suffix];
+ log_cmd('disableAgmt', 'Disable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully disabled winsync agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to disable winsync agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ deleteAgmt () {
+ this.setState({
+ deleteSpinning: true
+ });
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'delete', '--suffix=' + this.props.suffix, this.state.agmtName];
+ log_cmd('deleteAgmt', 'Delete agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully deleted winsync agreement');
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to delete winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ });
+ }
+
+ createAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'create', this.state.agmtName, '--suffix=' + this.props.suffix,
+ '--host=' + this.state.agmtHost, '--port=' + this.state.agmtPort,
+ '--conn-protocol=' + this.state.agmtProtocol,
+ '--bind-dn=' + this.state.agmtBindDN, '--bind-passwd=' + this.state.agmtBindPW,
+ '--ds-subtree=' + this.state.agmtDSSubtree, '--win-subtree=' + this.state.agmtWinSubtree,
+ '--win-domain=' + this.state.agmtWinDomain, '--one-way-sync=' + this.state.agmtOneWaySync
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' + this.state.agmtStartTime.replace(':', '') + "-" + this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ }
+
+ if (this.state.agmtFracAttrs.length > 0) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join(' '));
+ }
+ if (this.state.agmtSyncGroups) {
+ cmd.push('--sync-groups=on');
+ }
+ if (this.state.agmtSyncUsers) {
+ cmd.push('--sync-users=on');
+ }
+ if (this.state.agmtSyncInterval != "") {
+ cmd.push('--sync-interval=' + this.state.agmtSyncInterval);
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('createAgmt', 'Create winsync agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showCreateAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully created winsync agreement'
+ );
+ if (this.state.agmtInit == 'online-init') {
+ this.initAgmt(this.state.agmtName);
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to create winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ watchAgmtInit(agmtName, idx) {
+ // Watch the init, then clear the interval index
+ let status_cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'init-status', '--suffix=' + this.props.suffix, agmtName];
+ log_cmd('watchAgmtInit', 'Get initialization status for agmt', status_cmd);
+ cockpit
+ .spawn(status_cmd, {superuser: true, "err": "message"})
+ .done(data => {
+ let init_status = JSON.parse(data);
+ if (init_status.startsWith('Agreement successfully initialized') ||
+ init_status.startsWith('Agreement initialization failed')) {
+ // Either way we're done, stop watching the status
+ clearInterval(this.state.agmtInitIntervals[idx]);
+ }
+ this.props.reload(this.props.suffix);
+ });
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-right">
+ <ReplAgmtTable
+ rows={this.props.rows}
+ edit={this.showEditAgmt}
+ poke={this.pokeAgmt}
+ init={this.showConfirmInitAgmt}
+ enable={this.confirmToggle}
+ delete={this.showConfirmDeleteAgmt}
+ />
+ <div className="ds-margin-top ds-container ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showCreateAgmtModal}
+ >
+ Create Agreement
+ </Button>
+ <Button
+ className="ds-left-margin"
+ bsStyle="default"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+ >
+ Refresh Agreements
+ </Button>
+ </div>
+ <WinsyncAgmtModal
+ showModal={this.state.showCreateAgmtModal}
+ closeHandler={this.closeCreateAgmtModal}
+ handleChange={this.handleChange}
+ handleFracChange={this.handleTAFracAttrChange}
+ saveHandler={this.createAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ agmtSyncGroups={this.state.agmtSyncGroups}
+ agmtSyncUsers={this.state.agmtSyncUsers}
+ agmtWinDomain={this.state.agmtWinDomain}
+ agmtWinSubtree={this.state.agmtWinSubtree}
+ agmtDSSubtree={this.state.agmtDSSubtree}
+ agmtOneWaySync={this.state.agmtOneWaySync}
+ agmtSyncInterval={this.state.agmtSyncInterval}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ />
+ <WinsyncAgmtModal
+ showModal={this.state.showEditAgmtModal}
+ closeHandler={this.closeEditAgmtModal}
+ handleChange={this.handleChange}
+ handleFracChange={this.handleTAFracAttrChangeEdit}
+ saveHandler={this.saveAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtStripAttrs={this.state.agmtStripAttrs}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtFracInitAttrs={this.state.agmtFracInitAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ agmtSyncGroups={this.state.agmtSyncGroups}
+ agmtSyncUsers={this.state.agmtSyncUsers}
+ agmtWinDomain={this.state.agmtWinDomain}
+ agmtWinSubtree={this.state.agmtWinSubtree}
+ agmtDSSubtree={this.state.agmtDSSubtree}
+ agmtOneWaySync={this.state.agmtOneWaySync}
+ agmtSyncInterval={this.state.agmtSyncInterval}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ edit
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmDeleteAgmt}
+ closeHandler={this.closeConfirmDeleteAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.deleteAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Delete Winsync Agreement"
+ mMsg="Are you sure you want to delete this winsync agreement"
+ mSpinningMsg="Deleting Winsync Agreement ..."
+ mBtnName="Delete Agreement"
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmInitAgmt}
+ closeHandler={this.closeConfirmInitAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.initAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Initialize Winsync Agreement"
+ mMsg="Are you sure you want to initialize this winsync agreement"
+ mSpinningMsg="Initializing Winsync Agreement ..."
+ mBtnName="Initialize Agreement"
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmEnableAgmt}
+ closeHandler={this.closeConfirmEnableAgmt}
+ actionFunc={this.enableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to enable this winsync agreement?"
+ msgContent={this.state.agmtName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmDisableAgmt}
+ closeHandler={this.closeConfirmDisableAgmt}
+ actionFunc={this.disableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to disable this winsync agreement?"
+ msgContent={this.state.agmtName}
+ />
+ </div>
+ );
+ }
+}
+
+WinsyncAgmts.propTypes = {
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ rows: PropTypes.array,
+ addNotification: PropTypes.func,
+};
+
+WinsyncAgmts.defaultProps = {
+ serverId: "",
+ suffix: "",
+ rows: [],
+ addNotification: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/security/certificateManagement.jsx b/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
index d5cd927..5bf091c 100644
--- a/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
+++ b/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
@@ -10,7 +10,7 @@ import {
Spinner,
noop
} from "patternfly-react";
-import { ConfirmPopup } from "../../lib/notifications.jsx";
+import { DoubleConfirmModal } from "../../lib/notifications.jsx";
import {
CertTable
} from "./securityTables.jsx";
@@ -41,11 +41,13 @@ export class CertificateManagement extends React.Component {
isCACert: false,
showConfirmCAChange: false,
loading: false,
+ modalSpinning: false,
+ modalChecked: false,
};
this.handleNavSelect = this.handleNavSelect.bind(this);
this.addCACert = this.addCACert.bind(this);
- this.handleAddChange = this.handleAddChange.bind(this);
+ this.handleChange = this.handleChange.bind(this);
this.addCert = this.addCert.bind(this);
this.showAddModal = this.showAddModal.bind(this);
this.closeAddModal = this.closeAddModal.bind(this);
@@ -217,6 +219,8 @@ export class CertificateManagement extends React.Component {
this.setState({
showConfirmDelete: true,
certName: dataRow.nickname[0],
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -295,7 +299,9 @@ export class CertificateManagement extends React.Component {
if (!SSLFlags[0].includes('C') || !SSLFlags[0].includes('T')) {
// This could remove the CA cert properties, better warn user
this.setState({
- showConfirmCAChange: true
+ showConfirmCAChange: true,
+ modalSpinning: false,
+ modalChecked: false
});
return;
}
@@ -305,7 +311,9 @@ export class CertificateManagement extends React.Component {
closeConfirmCAChange () {
this.setState({
- showConfirmCAChange: false
+ showConfirmCAChange: false,
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -354,8 +362,8 @@ export class CertificateManagement extends React.Component {
});
}
- handleAddChange (e) {
- const value = e.target.value;
+ handleChange (e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
let valueErr = false;
let errObj = this.state.errObj;
@@ -418,6 +426,8 @@ export class CertificateManagement extends React.Component {
closeConfirmDelete () {
this.setState({
showConfirmDelete: false,
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -567,7 +577,7 @@ export class CertificateManagement extends React.Component {
<SecurityAddCertModal
showModal={this.state.showAddModal}
closeHandler={this.closeAddModal}
- handleChange={this.handleAddChange}
+ handleChange={this.handleChange}
saveHandler={this.addCert}
spinning={this.state.modalSpinner}
error={this.state.errObj}
@@ -575,23 +585,36 @@ export class CertificateManagement extends React.Component {
<SecurityAddCACertModal
showModal={this.state.showAddCAModal}
closeHandler={this.closeAddCAModal}
- handleChange={this.handleAddChange}
+ handleChange={this.handleChange}
saveHandler={this.addCACert}
spinning={this.state.modalSpinner}
error={this.state.errObj}
/>
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showConfirmDelete}
closeHandler={this.closeConfirmDelete}
- actionFunc={this.delCert}
- msg="Are you sure you want to delete this certificate?"
- msgContent={this.state.certName}
+ handleChange={this.handleChange}
+ actionHandler={this.delCert}
+ spinning={this.state.modalSpinning}
+ item={this.state.certName}
+ checked={this.state.modalChecked}
+ mTitle="Delete Certificate"
+ mMsg="Are you sure you want to delete this certificate?"
+ mSpinningMsg="Deleting Certificate ..."
+ mBtnName="Delete Certificate"
/>
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showConfirmCAChange}
closeHandler={this.closeConfirmCAChange}
- actionFunc={this.doEditCert}
- msg="Removing the 'C' or 'T' flags from the SSL trust catagory could break all TLS connectivity to and from the server, are you sure you want to proceed?"
+ handleChange={this.handleChange}
+ actionHandler={this.doEditCert}
+ spinning={this.state.modalSpinning}
+ item={this.state.certName}
+ checked={this.state.modalChecked}
+ mTitle="Warning - "
+ mMsg="Removing the 'C' or 'T' flags from the SSL trust catagory could break all TLS connectivity to and from the server, are you sure you want to proceed?"
+ mSpinningMsg="Editing CA Certificate ..."
+ mBtnName="Change Trust Flags"
/>
</div>
);
diff --git a/src/cockpit/389-console/src/lib/tools.jsx b/src/cockpit/389-console/src/lib/tools.jsx
index dc8a701..ca402f5 100644
--- a/src/cockpit/389-console/src/lib/tools.jsx
+++ b/src/cockpit/389-console/src/lib/tools.jsx
@@ -65,7 +65,7 @@ export function get_date_string (timestamp) {
let hour = timestamp.substr(8, 2);
let minute = timestamp.substr(10, 2);
let sec = timestamp.substr(12, 2);
- let date = new Date(parseInt(year), parseInt(month), parseInt(day),
+ let date = new Date(parseInt(year), (parseInt(month) - 1), parseInt(day),
parseInt(hour), parseInt(minute), parseInt(sec));
return date.toLocaleString();
}
@@ -122,3 +122,10 @@ export function valid_port (val) {
}
return result;
}
+
+export function valid_dn (dn) {
+ // Validate value is a valid DN (sanity validation)
+ let dn_regex = new RegExp("^([A-Za-z]+=.*)");
+ let result = dn_regex.test(dn);
+ return result;
+}
diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx
index 89d47b0..d9d7807 100644
--- a/src/cockpit/389-console/src/monitor.jsx
+++ b/src/cockpit/389-console/src/monitor.jsx
@@ -39,9 +39,9 @@ export class Monitor extends React.Component {
snmpData: {},
ldbmData: {},
serverData: {},
- showLoading: false,
loadingMsg: "",
notifications: [],
+ disableTree: false,
// Suffix
suffixLoading: false,
serverLoading: false,
@@ -112,9 +112,11 @@ export class Monitor extends React.Component {
this.addNotification = this.addNotification.bind(this);
this.removeNotification = this.removeNotification.bind(this);
this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ this.enableTree = this.enableTree.bind(this);
this.update_tree_nodes = this.update_tree_nodes.bind(this);
this.selectNode = this.selectNode.bind(this);
this.loadMonitorSuffix = this.loadMonitorSuffix.bind(this);
+ this.disableSuffixLoading = this.disableSuffixLoading.bind(this);
this.loadMonitorLDBM = this.loadMonitorLDBM.bind(this);
this.reloadLDBM = this.reloadLDBM.bind(this);
this.loadMonitorSNMP = this.loadMonitorSNMP.bind(this);
@@ -209,6 +211,7 @@ export class Monitor extends React.Component {
icon: "pficon-catalog",
selectable: false,
id: "log-monitor",
+ state: {"expanded": true},
nodes: [
{
text: "Access Log",
@@ -279,8 +282,11 @@ export class Monitor extends React.Component {
}
selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
this.setState({
- showLoading: true
+ disableTree: true, // Disable the tree to allow node to be fully loaded
});
if (selectedNode.id == "database-monitor" ||
@@ -344,12 +350,7 @@ export class Monitor extends React.Component {
});
} else {
if (selectedNode.id in this.state) {
- // This suffix is already cached, but it might be incomplete...
- if (selectedNode.type == "dblink" && this.state.nsaddcount === undefined) {
- this.loadMonitorChaining(selectedNode.id);
- } else if (selectedNode.type != "dblink" && this.state.entrycachehitratio === undefined) {
- this.loadMonitorSuffix(selectedNode.id);
- }
+ // This suffix is already cached
this.setState(prevState => {
return {
nodes: this.nodeSelector(prevState.nodes, selectedNode),
@@ -597,6 +598,12 @@ export class Monitor extends React.Component {
});
}
+ disableSuffixLoading () {
+ this.setState({
+ suffixLoading: false
+ });
+ }
+
loadMonitorSuffix(suffix) {
this.setState({
suffixLoading: true
@@ -616,8 +623,7 @@ export class Monitor extends React.Component {
...this.state[suffix],
suffixData: config.attrs,
},
- suffixLoading: false,
- });
+ }, this.disableSuffixLoading);
})
.fail(() => {
// Notification of failure (could only be server down)
@@ -1001,10 +1007,20 @@ export class Monitor extends React.Component {
), this.loadMonitorReplication);
}
+ enableTree () {
+ this.setState({
+ disableTree: false
+ });
+ }
+
render() {
const { nodes } = this.state;
let monitorPage = "";
let monitor_element = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
if (this.state.loaded) {
if (this.state.node_name == "database-monitor" || this.state.node_name == "") {
@@ -1012,14 +1028,15 @@ export class Monitor extends React.Component {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
- <h4><b>Loading database monitor information ...</b></h4>
- <Spinner loading size="md" />
+ <h4>Loading database monitor information ...</h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else {
monitor_element =
<DatabaseMonitor
data={this.state.ldbmData}
reload={this.reloadLDBM}
+ enableTree={this.enableTree}
/>;
}
} else if (this.state.node_name == "server-monitor") {
@@ -1027,8 +1044,8 @@ export class Monitor extends React.Component {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
- <h4><b>Loading server monitor information ...</b></h4>
- <Spinner loading size="md" />
+ <h4>Loading server monitor information ...</h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else {
monitor_element =
@@ -1036,6 +1053,7 @@ export class Monitor extends React.Component {
data={this.state.serverData}
reload={this.reloadServer}
serverId={this.props.serverId}
+ enableTree={this.enableTree}
/>;
}
} else if (this.state.node_name == "snmp-monitor") {
@@ -1043,14 +1061,15 @@ export class Monitor extends React.Component {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
- <h4><b>Loading SNMP monitor information ...</b></h4>
- <Spinner loading size="md" />
+ <h4>Loading SNMP monitor information ...</h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else {
monitor_element =
<SNMPMonitor
data={this.state.snmpData}
reload={this.reloadSNMP}
+ enableTree={this.enableTree}
/>;
}
} else if (this.state.node_name == "access-log-monitor") {
@@ -1063,6 +1082,7 @@ export class Monitor extends React.Component {
refreshing={this.state.accessRefreshing}
handleRefresh={this.accessRefreshCont}
lines={this.state.accessLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "audit-log-monitor") {
monitor_element =
@@ -1074,6 +1094,7 @@ export class Monitor extends React.Component {
refreshing={this.state.auditRefreshing}
handleRefresh={this.auditRefreshCont}
lines={this.state.auditLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "auditfail-log-monitor") {
monitor_element =
@@ -1085,6 +1106,7 @@ export class Monitor extends React.Component {
refreshing={this.state.auditfailRefreshing}
handleRefresh={this.auditFailRefreshCont}
lines={this.state.auditfailLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "error-log-monitor") {
monitor_element =
@@ -1097,6 +1119,7 @@ export class Monitor extends React.Component {
handleRefresh={this.errorRefreshCont}
handleSevLevel={this.handleSevChange}
lines={this.state.errorLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "replication-monitor") {
if (this.state.replLoading) {
@@ -1104,7 +1127,7 @@ export class Monitor extends React.Component {
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading replication monitor information ...</h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else {
if (this.state.replicatedSuffixes.length < 1) {
@@ -1139,6 +1162,7 @@ export class Monitor extends React.Component {
reloadAgmts={this.reloadReplAgmts}
reloadWinsyncAgmts={this.reloadReplWinsyncAgmts}
reloadConflicts={this.loadConflicts}
+ enableTree={this.enableTree}
key={this.state.replSuffix}
/>
</div>
@@ -1152,14 +1176,14 @@ export class Monitor extends React.Component {
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading suffix monitor information for <b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else if (this.state.chainingLoading) {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading chaining monitor information for <b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
} else {
if (this.state.node_type == "dblink") {
@@ -1169,6 +1193,7 @@ export class Monitor extends React.Component {
bename={this.state.bename}
reload={this.loadMonitorChaining}
data={this.state[this.state.node_text].chainingData}
+ enableTree={this.enableTree}
key={this.state.node_text}
/>;
} else {
@@ -1179,6 +1204,7 @@ export class Monitor extends React.Component {
bename={this.state.bename}
reload={this.loadMonitorSuffix}
data={this.state[this.state.node_text].suffixData}
+ enableTree={this.enableTree}
key={this.state.node_text}
/>;
}
@@ -1193,13 +1219,14 @@ export class Monitor extends React.Component {
<div className="ds-container">
<div>
<div className="ds-tree">
- <div className="tree-view-container" id="db-tree"
+ <div className={disabled} id="monitor-tree"
style={treeViewContainerStyles}>
<TreeView
nodes={nodes}
highlightOnHover
highlightOnSelect
selectNode={this.selectNode}
+ key={this.state.node_text}
/>
</div>
</div>
@@ -1214,7 +1241,7 @@ export class Monitor extends React.Component {
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading monitor information ...</h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading size="md" />
</div>;
}
diff --git a/src/cockpit/389-console/src/replication.html b/src/cockpit/389-console/src/replication.html
deleted file mode 100644
index af9fca4..0000000
--- a/src/cockpit/389-console/src/replication.html
+++ /dev/null
@@ -1,724 +0,0 @@
-
-<!-- Replication Configuration Panel -->
-<div id="repl-config" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Replication Configuration Settings</h3>
-
- <label class="ds-config-label-med" for="select-repl-role">Database Suffix</Label><select
- class="btn btn-default dropdown" id="select-repl-cfg-suffix">
- </select>
-
- <div id="ds-repl-enabled" class="ds-margin-top-lg" hidden>
- <button class="btn btn-primary" data-toggle="modal" data-target="#enable-repl-form" id="enable-repl-btn">Enable Replication</button>
- </div>
-
- <div id="repl-config-content">
- <div>
- <label class="ds-config-label-med" for="select-repl-role">Replication Role</Label><select class="btn btn-default dropdown" id="select-repl-role">
- <option>Master</option>
- <option>Hub</option>
- <option>Consumer</option>
- </select>
- </div>
- <div>
- <label id="replicaid-label" for="nsds5replicaid" class="ds-config-label-med" title=
- "The replica identifier for Master/Supplier (nsds5ReplicaId).">
- Replica ID</label><input class="ds-input" type="text" id="nsds5replicaid" size="5"/>
- <p></p>
- <button class="btn btn-danger" id="disable-repl-btn">Disable Replication</button>
- </div>
- <hr>
- <div class="ds-repl-mgr">
- <div class="ds-inline ds-repl-mgr">
- <label for="nsds5replicabinddngroup" title=
- "The DN of a group that contains entries that are allowed to make replication updates (nsDS5ReplicaBindDnGroup).">Bind DN Group</label><input
- type="text" id="nsds5replicabinddngroup" size="50" />
- </div>
- <table class="table table-bordered ds-mgr-table" id="repl-mgr-table"
- title="Specifies entries that can perform replication updates on this server. Also known as supplier DN, update DN, or Replication Manager">
- <thead>
- <tr>
- <th>Replication Managers</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- </tbody>
- </table>
- <button class="btn btn-default ds-repl-managers-button" data-toggle="modal" data-target="#add-repl-mgr-form" id="add-repl-manager">Add Replication Manager</button>
- </div>
-
- <button class="accordion ds-accordion" id="repl-config-accordion" type="button">► Show Advanced Settings</button>
- <div class="ds-accordion-panel">
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="nsds5replicareleasetimeout" class="ds-config-label" title=
- "Sets how long an agreement should keep its replication session established while other agreements are trying to update the same replica (nsds5ReplicaReleaseTimeout).">
- Replication Release Timeout</label><input class="ds-input ds-cfg" type="text" id="nsds5replicareleasetimeout" size="15"/>
- </div>
- <div>
- <label for="nsds5replicapurgedelay" class="ds-config-label" title=
- "NEEDS WORK (nsds5ReplicaPurgeDelay).">Replication Purge Delay</label><input class="ds-input ds-cfg" type="text" id="nsds5replicapurgedelay" size="15"/>
- </div>
- <div>
- <label for="nsds5replicatombstonepurgeinterval" class="ds-config-label" title=
- "NEEDS WORK (nsds5ReplicaTombstonePurgeInterval).">Tombstone Purge Interval</label><input class="ds-input ds-cfg" type="text" id="nsds5replicatombstonepurgeinterval" size="15"/>
- </div>
- <div>
- <label for="nsds5replicaprecisetombstonepurging" class="ds-config-label" title=
- "NEEDS WORK (nsds5ReplicaPreciseTombstonePurging).">Precise Tombstone Purging</label><input type="checkbox"
- id="nsds5replicaprecisetombstonepurging">
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <label for="nsds5replicabinddngroupcheckinterval" class="ds-config-label" title=
- "NEEDS WORK (nsDS5ReplicaBindDnGroupCheckInterval).">Bind DN Group Check Interval</label><input class="ds-input ds-cfg" type="text" id="nsds5replicabinddngroupcheckinterval" size="15"/>
- </div>
- <div>
- <label for="nsds5replicaprotocoltimeout" class="ds-config-label" title=
- "NEEDS WORK (nsds5ReplicaProtocolTimeout).">Replication Protocol Timeout</label><input class="ds-input ds-cfg" type="text" id="nsds5replicaprotocoltimeout" size="15"/>
- </div>
- <div>
- <label for="nsds5replicabackoffmin" class="ds-config-label" title=
- "NEEDS WORK (nsds5ReplicaBackoffMin).">Replication Backoff Minimum</label><input class="ds-input ds-cfg" type="text" id="nsds5replicabackoffmin" size="15"/>
- </div>
- <div>
- <label for="nsds5replicabackoffmax" class="ds-config-label" title=
- "NEEDS WORK (nsds5ReplicaBackoffMax).">Replication Backoff Maximum</label><input class="ds-input ds-cfg" type="text" id="nsds5replicabackoffmax" size="15"/>
- </div>
- </div>
- </div>
- <hr>
- <div>
- <label for="nsslapd-changelogdir" class="ds-config-label" title="The location on the filesystem for the replication changelog database (nsslapd-changelogdir).">Changelog Location</label><input
- class="ds-cl" type="text" id="nsslapd-changelogdir" size="60" />
- </div>
- <div class="ds-container">
- <div name="changelog" class="ds-line">
- <div>
- <label for="nsslapd-changelogmaxentries" class="ds-config-label" title=
- "Changelog trimming parameter. Set the maximum number of changelog entries (nsslapd-changelogmaxentries).">
- Changelog Maximum Entries</label><input class="ds-input ds-cl" type="text" id="nsslapd-changelogmaxentries" size="15"/>
- </div>
- <div>
- <label for="nsslapd-changelogmaxage" class="ds-config-label" title=
- "Changelog trimming parameter. This set the maximum age of a changelog entry. It is recommended to use the same value as the ReplicationPurgeDelay. (nsslapd-changelogmaxage).">
- Changelog Maximum Age</label><input class="ds-input ds-cl" type="text" id="nsslapd-changelogmaxage" size="15"/>
- </div>
- <div>
- <label for="nsslapd-changelogcompactdb-interval" class="ds-config-label" title=
- "The changelog compaction internal. Set how often the changelog will compact itself, meaning remove empty/trimmed database slots (nsslapd-changelogcompactdb-interval).">
- Changelog Compaction Interval</label><input class="ds-input ds-cl" type="text" id="nsslapd-changelogcompactdb-interval" size="15"/>
- </div>
- <div>
- <label for="nsslapd-changelogtrim-interval" class="ds-config-label" title=
- "The changelog trimming internal. Set how often the changelog checks if there are entries that can be purged from the changelog based on the trimming parameters (nsslapd-changelogtrim-interval).">
- Changelog Trim Interval</label><input class="ds-input ds-cl" type="text" id="nsslapd-changelogtrim-interval" size="15"/>
- </div>
- </div>
- </div>
-
- <!-- Buttons to create and delete changelog -->
- <div id="cl-create-div" class="ds-margin-top">
- <button class="btn btn-default" id="create-cl-btn">Create Changelog</button>
- </div>
- <div id="cl-del-div" class="ds-margin-top" hidden>
- <button class="btn btn-danger" id="delete-cl-btn">Delete Changelog</button>
- </div>
- <hr>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary" id="repl-config-save">Save</button>
- </div>
-</div>
-
-
-<!-- -----------------------------
-
-Replication Agreements
-
--------------------------------- -->
-<div id="repl-agmts" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Replication Agreements for <select
- class="btn btn-default dropdown" id="select-repl-agmt-suffix">
- </select></h3>
-
- <div class="ds-page-content">
- <table id="repl-agmt-table" class="display ds-repl-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Agreement Name</th>
- <th>Host</th>
- <th>Port</th>
- <th>State</th>
- <th>Last Update Status</th>
- <th>Last Init Status</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody id="agmt-body">
- </tbody>
- </table>
- <button class="btn btn-primary" type="button" data-toggle="modal" data-target="#agmt-form" id="create-agmt">Create Replication Agreement</button><div
- class="ds-float-right"><button class="btn btn-default ds-float-right" type="button" id="refresh-agmts-btn">Refresh Agreements</button></div>
- <p></p>
- </div>
-</div>
-
-<!-- -----------------------------
-
-Winsync Agreements
-
--------------------------------- -->
-<div id="repl-winsync" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">Windows Synchronization Agreements for <select
- class="btn btn-default dropdown" id="select-repl-winsync-suffix">
- </select></h3>
- <div class="ds-page-content">
- <table id="repl-winsync-agmt-table" class="display ds-repl-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Agreement Name</th>
- <th>Windows Host</th>
- <th>Port</th>
- <th>State</th>
- <th>Last Update Status</th>
- <th>Last Init Status</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody id="winsync-agmt-body">
- </tbody>
- </table>
- <button class="btn btn-primary" data-toggle="modal" data-target="#winsync-agmt-form" type="button" id="winsync-create-agmt">Create Winsync Agreement</button><div
- class="ds-float-right"><button class="btn btn-default ds-float-right" type="button" id="refresh-winsync-agmts-btn">Refresh Agreements</button></div>
- <p></p>
- </div>
-</div>
-
-<!-- -----------------------------
-
-CleanAllRUV Tasks
-
--------------------------------- -->
-<div id="repl-cleanallruv" class="all-pages ds-margin-left" hidden>
- <h3 class="ds-config-header">CleanAllRUV Tasks</h3>
- <div class="ds-page-content">
-
- <div>
- <button class="btn btn-primary" type="button" data-toggle="modal" data-target="#cleanallruv-form" id="create-cleanallruv-btn">Create CleanAllRUV Task</button>
- </div>
- <p></p>
- <div class="ds-margin-top-lg">
- <p></p>
- <table id="repl-clean-table" class="display ds-repl-table" cellspacing="0" width="100%">
- <caption class="ds-center"><h4>Running CleanAllRUV Tasks</h4></caption>
- <thead>
- <tr class="ds-table-header">
- <th>Task Name</th>
- <th>Creation Time</th>
- <th>Suffix</th>
- <th>Replica ID</th>
- <th>Last Update Status</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- </tbody>
- </table>
- <button class="btn btn-default" type="button" id="refresh-cleanlist-btn">Refresh Task List</button>
- </div>
- </div>
-</div>
-
-
-
- <!-- --------------------------------------------------------
-
- Replication Agreement Form (wizard)
-
- ----------------------------------------------------------- -->
-
- <div class="modal fade" id="agmt-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="agmt-wizard-title" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h3 class="modal-title" id="agmt-wizard-title">Create Replication Agreement</h3>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="agmt-cn" class="ds-config-label" title="Agreement name (cn).">Agreement Name</label><input
- class="ds-input agmt-form-input" type="text" placeholder="Agreement name" id="agmt-cn" name="name" size="40" required>
- </div>
- <div>
- <label for="nsds5replicahost" class="ds-config-label" title="Agreement name (nsDS5ReplicaHost).">Consumer Host</label><input
- class="ds-input agmt-form-input" type="text" placeholder="Consumer hostname" id="nsds5replicahost" name="port" size="40" required>
- </div>
- <div>
- <label for="nsds5replicaport" class="ds-config-label" title="Agreement name (nsDS5ReplicaPort).">Consumer Port</label><input
- class="ds-input agmt-form-input" type="text" placeholder="Consumer port number" id="nsds5replicaport" name="name" size="40" required>
- </div>
- <div>
- <label for="nsds5replicabinddn" class="ds-config-label" title="Replication Bind DN (nsDS5ReplicaBindDN).">Replication Bind DN</label><input
- class="ds-input agmt-form-input" type="text" autocomplete="username" placeholder="Bind DN" id="nsds5replicabinddn" name="name" size="40" required>
- </div>
- <div>
- <label for="nsds5replicacredentials" class="ds-config-label" title="Replication Bind DN (nsDS5ReplicaCredentials).">Bind DN Password</label><input
- class="ds-input agmt-form-input" type="password" autocomplete="new-password" placeholder="Enter password" id="nsds5replicacredentials" name="name" size="40" required>
- </div>
- <div>
- <label for="nsds5replicacredentials-confirm" class="ds-config-label" title="Confirm password">Confirm Password</label><input
- class="ds-input agmt-form-input" type="password" autocomplete="new-password" placeholder="Confirm password" id="nsds5replicacredentials-confirm" name="name" size="40" required>
- </div>
- <div>
- <label for="nsds5replicatransportinfo" class="ds-config-label" title="The protocol used to connect to the replica (nsDS5ReplicaTransportInfo).">Connection Protocol</label><select
- class="btn btn-default dropdown ds-agmt-wiz-dropdown" id="nsds5replicatransportinfo">
- <option>LDAP</option>
- <option>LDAPS</option>
- <option>StartTLS</option>
- </select>
- </div>
- <div>
- <label for="nsds5replicabindmethod" class="ds-config-label" title="The authentication method (nsDS5ReplicaBindMethod).">Bind Method</label><select
- class="btn btn-default dropdown ds-agmt-wiz-dropdown" id="nsds5replicabindmethod">
- <option>SIMPLE</option>
- <option>SSLCLIENTAUTH</option>
- <option>SASL/DIGEST-MD5</option>
- <option>SASL/GSSAPI</option>
- </select>
- </div>
- <div id="init-agmt-dropdown">
- <label for="init-options" class="ds-config-label">Initialize Consumer</label><select
- class="btn btn-default dropdown ds-agmt-wiz-dropdown ds-agmt-init-dropdown" id="init-options">
- <option id="init-nothing" value="noinit">Do Not Initialize</option>
- <option id="init-online" value="online-init">Do Online Initialization</option>
- <option id="init-offline" value="offline-init">Create Initialization LDIF File</option>
- </select>
- </div>
- </div>
- </div>
-
- <!-- Fractional settings -->
- <p></p>
- <button class="accordion ds-wiz-accordion" id="frac-accordion" type="button">► Show Fractional Settings</button>
- <div class="ds-accordion-panel">
- <p><b>Excluded Attributes</b></p>
- <div class="ds-container">
- <div>
- <form>
- <select id="frac-exclude-list" class="ds-fractional-list" name="exclude-attrs" multiple>
- </select>
- </form>
- </div>
- <div>
- <div>
- <p></p>
- <input type="button" id="frac-list-add-btn" data-toggle="modal" data-target="#select-attr-form" class="ds-fractional-btn" value="Add Attributes"/>
- </div>
- <div>
- <input type="button" id="frac-list-remove-btn" class="ds-fractional-btn" value="Remove Attributes"/>
- </div>
- </div>
- </div>
-
- <hr>
- <p><b>Excluded Attributes From Total Initializations</b></p>
- <div class="ds-container">
- <div>
- <form>
- <select id="frac-exclude-tot-list" class="ds-fractional-list" name="exclude-attrs" multiple>
- </select>
- </form>
- </div>
- <div>
- <div>
- <p></p>
- <input type="button" id="frac-total-list-add-btn" data-toggle="modal" data-target="#select-attr-form" class="ds-fractional-btn" value="Add Attributes"/>
- </div>
- <div>
- <input type="button" id="frac-total-list-remove-btn" class="ds-fractional-btn" value="Remove Attributes"/>
- </div>
- </div>
- </div>
-
- <hr>
- <p><b>Strip Attributes</b></p>
- <div class="ds-container">
- <div>
- <form>
- <select id="frac-strip-list" class="ds-fractional-list" name="exclude-attrs" multiple>
- </select>
- </form>
- </div>
- <div>
- <div>
- <p></p>
- <input type="button" id="frac-strip-list-add-btn" data-toggle="modal" data-target="#select-attr-form" class="ds-fractional-btn" value="Add Attributes"/>
- </div>
- <div>
- <input type="button" id="frac-strip-list-remove-btn" class="ds-fractional-btn" value="Remove Attributes"/>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Schedule settings -->
-
- <button class="accordion ds-wiz-accordion" id="schedule-accordion" type="button">► Show Scheduling Settings</button>
- <div class="ds-accordion-panel">
- <input type="checkbox" class="ds-repl-manager-checkbox" id="agmt-schedule-checkbox" checked><label
- for="agmt-schedule-checkbox" class="ds-label"> Always keep directories in sync </label>
- <div id="agmt-schedule-panel">
- <div class="ds-container" id="schedule-settings" hidden>
- <div class="ds-fractional-panel">
- <hr>
- <b>Days to keep replication in sync</b>
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <input type="checkbox" class="ds-agmt-schedule-checkbox" id="schedule-mon" checked><label
- for="schedule-mon" class="ds-label"> Mon</label>
- </div>
- <div>
- <input type="checkbox" class="ds-agmt-schedule-checkbox" id="schedule-tue" checked><label
- for="schedule-tue" class="ds-label"> Tue </label>
- </div>
- <div>
- <input type="checkbox" class="ds-agmt-schedule-checkbox" id="schedule-wed" checked><label
- for="schedule-wed" class="ds-label"> Wed </label>
- </div>
- <div>
- <input type="checkbox" class="ds-agmt-schedule-checkbox" id="schedule-thu" checked><label
- for="schedule-thu" class="ds-label"> Thu </label>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <input type="checkbox" class="ds-agmt-schedule-checkbox" id="schedule-fri" checked><label
- for="schedule-fri" class="ds-label"> Fri </label>
- </div>
- <div>
- <input type="checkbox" class="ds-agmt-schedule-checkbox" id="schedule-sat" checked><label
- for="schedule-sat" class="ds-label"> Sat </label>
- </div>
- <div>
- <input type="checkbox" class="ds-agmt-schedule-checkbox" id="schedule-sun" checked><label
- for="schedule-sun" class="ds-label"> Sun</label>
- </div>
- </div>
- </div>
- </div>
- <div class="ds-fractional-panel">
- <hr>
- <b>Replication Time Range</b>
- <div class="ds-container">
- <div class="ds-inline ds-left-margin">
- <div>
- <label for="agmt-start-time" class="ds-config-label-med">Start Time</label><input class="timepicker" name="start" id="agmt-start-time" size="4"/>
- </div>
- <div>
- <label for="agmt-start-time" class="ds-config-label-med">End Time</label><input class="timepicker" name="start" id="agmt-end-time" size="4"/>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="agmt-save">Save Agreement</button>
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Winsync Agreement Wizard -->
-
- <div class="modal fade" id="winsync-agmt-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="winsync-agmt-wizard-title" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h4 class="modal-title" id="winsync-agmt-wizard-title">Create Winsync Agreement</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="winsync-agmt-cn" class="ds-config-label" title="Agreement name (cn).">Agreement Name</label><input
- class="ds-input" type="text" placeholder="Agreement name" id="winsync-agmt-cn" name="name" size="40" required>
- </div>
- <div>
- <label for="winsync-nsds7windowsdomain" class="ds-config-label" title="Agreement name (nsds7WindowsDomain).">Windows Domain Name</label><input
- class="ds-input" type="text" placeholder="Windows Domain Name, example: mydomain.com" id="winsync-nsds7windowsdomain" size="40" name="name" required>
- </div>
- <div>
- <label for="winsync-nsds5replicahost" class="ds-config-label" title="Agreement name (nsDS5ReplicaHost).">Windows Host</label><input
- class="ds-input" type="text" placeholder="Windows hostname" id="winsync-nsds5replicahost" name="port" size="40" required>
- </div>
- <div>
- <label for="winsync-nsds5replicaport" class="ds-config-label" title="Agreement name (nsDS5ReplicaPort).">Windows Port</label><input
- class="ds-input" type="text" placeholder="Windows server port number" id="winsync-nsds5replicaport" name="name" size="40" required>
- </div>
- <div>
- <label for="winsync-nsds7windowsreplicasubtree" class="ds-config-label" title="Agreement name (nsds7WindowsReplicaSubtree).">Windows Subtree</label><input
- class="ds-input" type="text" placeholder="Active Directory subtree" id="winsync-nsds7windowsreplicasubtree" name="name" size="40" required>
- </div>
- <div>
- <label for="winsync-nsds7directoryreplicasubtree" class="ds-config-label" title="Agreement name (nsds7DirectoryReplicaSubtree).">Directory Server Subtree</label><input
- class="ds-input" type="text" placeholder="The local Directory Server subtree" id="winsync-nsds7directoryreplicasubtree" name="name" size="40" required>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="winsync-nsds7newwinusersyncenabled-checkbox" checked><label
- for="winsync-nsds7newwinusersyncenabled-checkbox" class="ds-label" title=
- "Add new user to Directory Server when it is added to Active Directory (nsds7NewWinUserSyncEnabled)."> Sync New Windows Users</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="winsync-nsds7newwingroupsyncenabled-checkbox" checked><label
- for="winsync-nsds7newwingroupsyncenabled-checkbox" class="ds-label" title=
- "Add new group to Directory Server when it is added to Active Directory (nsds7NewWinGroupSyncEnabled)."> Sync New Windows Groups</label>
- </div>
- <div id="winsync-init-chbx">
- <input type="checkbox" class="ds-config-checkbox" id="winsync-init-checkbox" checked><label
- for="winsync-init-checkbox" class="ds-label" title=
- "Do a full synchronization after creating winsync agreement."> Initiate Full Synchronization</label>
- </div>
- <div>
- <hr class="ds-hr">
- <label for="winsync-nsds5replicabinddn" class="ds-config-label" title="Replication Bind DN (nsDS5ReplicaBindDN).">Replication Bind DN</label><input
- class="ds-input" type="text" autocomplete="username" placeholder="Bind DN" id="winsync-nsds5replicabinddn" name="name" size="40" required>
- </div>
- <div>
- <label for="winsync-nsds5replicacredentials" class="ds-config-label" title="Replication Bind DN (nsDS5ReplicaCredentials).">Bind DN Password</label><input
- class="ds-input" type="password" autocomplete="new-password" placeholder="Enter password" id="winsync-nsds5replicacredentials" name="name" size="40" required>
- </div>
- <div>
- <label for="winsync-nsds5replicacredentials-confirm" class="ds-config-label" title="Confirm password">Confirm Password</label><input
- class="ds-input" type="password"autocomplete="new-password" placeholder="Confirm password" id="winsync-nsds5replicacredentials-confirm" name="name" size="40" required>
- </div>
- <div>
- <label for="winsync-nsds5replicatransportinfo" class="ds-config-label" title="The protocol used to connect to the replica (nsDS5ReplicaTransportInfo).">Connection Protocol</label><select
- class="btn btn-default dropdown ds-agmt-wiz-dropdown" id="winsync-nsds5replicatransportinfo">
- <option>LDAPS</option>
- <option>StartTLS</option>
- </select>
- </div>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="winsync-agmt-save">Save Agreement</button>
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- CleanAllRUV Form -->
- <div class="modal fade" id="cleanallruv-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="cleanall-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h4 class="modal-title ds-center" id="cleanall-label">Create CleanAllRUV Task</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <p>The CleanAllRUV task will clean up traces of a deleted replica from
- the database and changelog. The task follows the replication agreements
- so the cleaning task propagates itself to all the replication servers.</p>
- <hr>
- <div>
- <label for="cleanallruv-suffix" class="ds-cleanallruv-label"><b>Replication Suffix</b></label><select
- class="btn btn-default dropdown" id="cleanallruv-suffix">
- </select>
- </div>
- <p></p>
- <div>
- <label for="cleanallruv-rid" class="ds-cleanallruv-label" title=
- "The Replica ID of a deleted replica (replica-id)"><b
- >Replica ID</b></label><input type="text" id="cleanallruv-rid" size="5"/>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="force-clean" checked><label
- for="force-clean" class="ds-label" title=
- "Clean the replica id regardless if replicas are online or if updates from the deleted replica have been fully replicated. (replica-force-cleaning)"> Force Cleaning</label>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="cleanallruv-save">Create Task</button>
- </div>
- </div>
- </div>
- </div>
-
-
-
-
- <!-- Add replication manager Form -->
- <div class="modal fade" id="add-repl-mgr-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="repl-mgr-label" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h4 class="modal-title" id="repl-mgr-label">Add Replication Manager</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <div>
- <label for="add-repl-mgr-dn" class="" title=
- "The DN of the replication manager. The entry should use 'cn' for the RDN, and the entry should be under 'cn=config'. (nsds5replicabinddn)">
- Replication Manager DN</label>
- </div>
- <div>
- <input type="text" title="The DN of the replication manager entry. It must use the RDN attribute 'cn', and it must be located under 'cn=config'. For example: cn=replication manager,cn=config"
- id="add-repl-mgr-dn" size="60"/>
- <p></p>
- </div>
- <div>
- <label for="add-repl-pw" class="ds-label-sm" title="Replication manager password (userpassword).">Manager Password</label><input
- class="ds-input" type="password" autocomplete="new-password" placeholder="Enter credentials" id="add-repl-pw" name="name" required>
- </div>
- <div>
- <label for="add-repl-pw-confirm" class="ds-label-sm" title="Confirm password">Confirm Password</label><input
- class="ds-input" type="password" autocomplete="new-password" placeholder="Confirm credentials" id="add-repl-pw-confirm" name="name" required>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="add-repl-mgr-save">Add Manager</button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Select an attribute for repl agmt forms -->
- <div class="modal fade" id="select-attr-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="attr-header" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <div class="modal-content ds-nested-modal">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h4 class="modal-title" id="attr-header">Choose Attributes...</h4>
- </div>
- <input class="ds-input" type="text" id="attr-form-id" value="" hidden>
- <div class="modal-body">
- <form class="form-horizontal">
- <select id="select-attr-list" class="ds-attr-select" multiple>
- </select>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
- <button type="button" class="btn btn-primary" id="select-attr-save">Add Attributes</button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Enable replication form -->
- <div class="modal fade" id="enable-repl-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="enable-repl-header" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
- <span class="pficon pficon-close"></span>
- </button>
- <h4 class="modal-title ds-center" id="enable-repl-header">Enable Replication</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div>
- <label class="ds-config-label-med" for="select-enable-repl-role">Replication Role</Label><select class="btn btn-default dropdown" id="select-enable-repl-role">
- <option>Master</option>
- <option>Hub</option>
- <option>Consumer</option>
- </select>
- </div>
- <div id="repl-rid-form">
- <label for="nsds5replicaid-form" class="ds-config-label-med" title=
- "The unique replica identifier for Master/Supplier. Please choose a number between 1 and 65534 (nsds5ReplicaId).">
- Replica ID</label><input class="ds-input" type="text" id="nsds5replicaid-form" size="4"/>
- </div>
- <hr>
- <div class="ds-inline">
- <div>
- <label id="enable-repl-mgr-title"><b>Replication Authentication</b></label>
- </div>
- <div class="ds-left-indent">
- <div>
- <input id="auth-mgr" type="radio" name="auth-option" value="manager" checked><label class="ds-sm-left-margin" for="auth-mgr"> Replication Manager</label>
- </div>
- <div>
- <input id="auth-group" type="radio" name="auth-option" value="group"><label class="ds-sm-left-margin" for="auth-group"> Replication Bind Group</label>
- </div>
- <hr>
- <div class="ds-left-indent">
- <div id="auth-manager-div">
- <div>
- <label for="enable-repl-mgr-dn" class="ds-label-sm" title=
- "The DN of the replication manager. The DN should use 'cn' for the RDN, 'cn=replication manager,cn=config' (nsds5replicabinddn)">Replication Manager DN</label><input
- type="text" title="The DN of the replication manager entry. It must use the RDN attribute 'cn', and it must be located under 'cn=config'. For example: cn=replication manager,cn=config"
- id="enable-repl-mgr-dn" value="cn=replication manager,cn=config" size="40" class="ds-left-margin" />
- <p></p>
- </div>
- <div>
- <label for="enable-repl-pw" class="ds-label-sm" title="Replication manager password (userpassword).">Manager Password</label><input
- class="ds-left-margin" size="40" type="password" autocomplete="new-password" placeholder="Enter credentials" id="enable-repl-pw" name="name" required>
- </div>
- <div>
- <label for="enable-repl-pw-confirm" class="ds-label-sm" title="Confirm password">Confirm Password</label><input
- class="ds-left-margin" size="40" type="password" autocomplete="new-password" placeholder="Confirm credentials" id="enable-repl-pw-confirm" name="name" required>
- </div>
- </div>
-
- <div id="auth-group-div" hidden>
- <div>
- <label for="enable-bindgroupdn" class="ds-label-sm" title=
- "The DN of a group that contains members that are allowed to make replication updates (nsDS5ReplicaBindDnGroup).">Bind Group DN</label><input
- type="text" id="enable-bindgroupdn" size="40"/>
- </div>
- </div>
-
- </div>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="enable-repl-save">Save</button>
- </div>
- </div>
- </div>
- </div>
diff --git a/src/cockpit/389-console/src/replication.js b/src/cockpit/389-console/src/replication.js
deleted file mode 100644
index 92566ad..0000000
--- a/src/cockpit/389-console/src/replication.js
+++ /dev/null
@@ -1,2207 +0,0 @@
-var repl_suffix = "";
-var prev_repl_role = "no-repl";
-var prev_role_button;
-var binddn_list_color = "";
-var repl_mgr_table;
-var current_role = "";
-var current_rid = "";
-var repl_agmt_table;
-var repl_winsync_agmt_table;
-var repl_clean_table;
-var repl_agmt_values = {};
-var repl_winsync_agmt_values = {};
-var frac_prefix = "(objectclass=*) $ EXCLUDE";
-var agmt_init_intervals = [];
-var agmt_init_counter = 0;
-var winsync_init_intervals = [];
-var winsync_init_counter = 0;
-
-// HTML items
-var progress_html = '<p><span class="spinner spinner-xs spinner-inline"></span> Initializing Agreement...</p>';
-
-var agmt_action_html =
- '<div class="dropdown">' +
- '<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">' +
- ' Choose Action...' +
- '<span class="caret"></span>' +
- '</button>' +
- '<ul class="dropdown-menu ds-agmt-dropdown" role="menu">' +
- '<li role=""><a class="repl-agmt-btn agmt-edit-btn" href="#">Edit Agreement</a></li>' +
- '<li role=""><a class="repl-agmt-btn agmt-init-btn" href="#">Initialize Agreement</a></li>' +
- '<li role=""><a class="repl-agmt-btn agmt-send-updates-btn" href="#">Send Updates Now</a></li>' +
- '<li role=""><a class="repl-agmt-btn agmt-enable-btn" href="#">Enable/Disable Agreement</a></li>' +
- '<li role=""><a class="repl-agmt-btn agmt-del-btn" href="#">Delete Agreement</a></li>' +
- '</ul>' +
- '</div>';
-
-var winsync_agmt_action_html =
- '<div class="dropdown">' +
- '<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">' +
- ' Choose Action...' +
- '<span class="caret"></span>' +
- '</button>' +
- '<ul class="dropdown-menu ds-agmt-dropdown" role="menu" aria-labelledby="dropdownMenu2">' +
- '<li role=""><a class="repl-agmt-btn winsync-agmt-edit-btn" href="#">Edit Agreement</a></li>' +
- '<li role=""><a class="repl-agmt-btn winsync-agmt-send-updates-btn" href="#">Send/Receives Updates Now</a></li>' +
- '<li role=""><a class="repl-agmt-btn winsync-agmt-init-btn" href="#">Full Re-synchronization</a></li>' +
- '<li role=""><a class="repl-agmt-btn winsync-agmt-enable-btn" href="#">Enable/Disable Agreement</a></li>' +
- '<li role=""><a class="repl-agmt-btn winsync-agmt-del-btn" href="#">Delete Agreement</a></li>' +
- '</ul>' +
- '</div>';
-
-var cleanallruv_action_html =
- '<button class="btn btn-default abort_cleanallruv_btn" type="button" class="abort-cleanallruv">Abort Task</button>';
-
-// Attribute to CLI argument mappings
-var repl_attr_map = {
- 'nsds5replicaid': '--replica-id',
- 'nsds5replicapurgedelay': '--repl-purge-delay',
- 'nsds5replicatombstonepurgeinterval': '--repl-tombstone-purge-interval',
- 'nsds5replicaprecisetombstonepurging': '--repl-fast-tombstone-purging',
- 'nsds5replicabinddngroup': '--repl-bind-group',
- 'nsds5replicabinddngroupcheckinterval': '--repl-bind-group-interval',
- 'nsds5replicaprotocoltimeout': '--repl-protocol-timeout',
- 'nsds5replicabackoffmin': '--repl-backoff-min',
- 'nsds5replicabackoffmax': '--repl-backoff-max',
- 'nsds5replicareleasetimeout': '--repl-release-timeout',
- 'nsds5flags': '',
- 'nsds5replicatype': '',
- 'nsds5replicabinddn': '',
- 'nsslapd-changelogdir': '--cl-dir',
- 'nsslapd-changelogmaxentries': '--max-entries',
- 'nsslapd-changelogmaxage': '--max-age',
- 'nsslapd-changelogcompactdb-interval': '--compact-interval',
- 'nsslapd-changelogtrim-interval': '--trim-interval'
-};
-
-var repl_cl_attrs = ['nsslapd-changelogdir', 'nsslapd-changelogmaxentries', 'nsslapd-changelogmaxage',
- 'nsslapd-changelogcompactdb-interval', 'nsslapd-changelogtrim-interval'];
-
-var repl_attrs = ['nsds5replicaid', 'nsds5replicapurgedelay', 'nsds5replicatombstonepurgeinterval',
- 'nsds5replicaprecisetombstonepurging', 'nsds5replicabinddngroup',
- 'nsds5replicabinddngroupcheckinterval', 'nsds5replicaprotocoltimeout', 'nsds5replicabackoffmin',
- 'nsds5replicabackoffmax', 'nsds5replicareleasetimeout'];
-
-
-// Helper functions
-function clear_agmt_wizard () {
- // Clear input fields and reset dropboxes
- $('.ds-agmt-schedule-checkbox').prop('checked', false);
- $('#agmt-schedule-checkbox').prop('checked', true);
- $("#agmt-start-time").val("00:00");
- $("#agmt-end-time").val("00:15");
- $(".ds-agmt-wiz-dropdown").prop('selectedIndex',0);
- $(".ds-agmt-panel").css('display','none');
- $(".agmt-form-input").css("border-color", "initial");
- $(".agmt-form-input").val("");
- $('#frac-exclude-list').find('option').remove();
- $('#frac-exclude-tot-list').find('option').remove();
- $('#frac-strip-list').find('option').remove();
- $("#select-attr-list").prop('selectedIndex',-1);
- $("#init-options").prop("selectedIndex", 0);
- $("#init-agmt-dropdown").show();
- $("#agmt-wizard-title").html("<b>Create Replication Agreement</b>");
-};
-
-function clear_enable_repl_form () {
- $("#nsds5replicaid-form").css("border-color", "initial");
- $("#nsds5replicaid-form").val("");
- $("#select-enable-repl-role").prop("selectedIndex", 0);
- $("#enable-repl-pw").val("");
- $("#enable-repl-pw-confirm").val("");
- $("#enable-repl-mgr-dn").val("cn=replication manager,cn=config");
- $("#enable-repl-mgr-checkbox").prop('checked', false);
- $("#enable-repl-mgr-passwd").hide();
-}
-
-function clear_winsync_agmt_wizard() {
- // Clear out winsync agreement form
- $("#winsync-agmt-cn").val("");
- $("#winsync-nsds7windowsdomain").val("");
- $("#winsync-nsds5replicahost").val("");
- $("#winsync-nsds5replicaport").val("");
- $("#winsync-nsds7windowsreplicasubtree").val("");
- $("#winsync-nsds7directoryreplicasubtree").val("");
- $("#winsync-nsds7newwinusersyncenabled-checkbox").prop('checked', false);
- $("#winsync-nsds7newwingroupsyncenabled-checkbox").prop('checked', false);
- $("#winsync-init-checkbox").prop('checked', false);
- $("#winsync-init-chbx").show();
- $("#winsync-nsds5replicabinddn").val("");
- $("#winsync-nsds5replicacredentials").val("");
- $("#winsync-nsds5replicacredentials-confirm").val("");
- $("#winsync-nsds5replicatransportinfo").prop('selectedIndex', 0);
- $("#winsync-agmt-wizard-title").html("<b>Create Winsync Agreement</b>");
-}
-
-function clear_cleanallruv_form () {
- // Clear input fields and reset dropboxes
- $('#force-clean').prop('checked', true);
- $("#cleanallruv-rid").val("");
-};
-
-function clear_repl_mgr_form () {
- $("#add-repl-pw").val("");
- $("#add-repl-pw-confirm").val("");
- $("#add-repl-mgr-dn").val("cn=replication manager,cn=config");
- $("#add-repl-mgr-checkbox").prop('checked', false);
- $("#add-repl-mgr-passwd").hide();
-}
-
-
-function add_repl_mgr(dn){
- // First check if manager is set to none
- $("#repl-mgr-table tbody").append(
- "<tr>"+
- "<td class='ds-td'>" + dn +"</td>"+
- "<td class='ds-center'>"+
- "<button type='button' class='btn btn-default remove-repl-mgr' title='Remove the manager from the replication configuration'>" +
- "<span class='glyphicon glyphicon-trash'></span> Remove </button></td>" +
- "</tr>");
-};
-
-
-function do_agmt_init(suffix, agmt_cn, idx) {
- var status_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'init-status', '--suffix=' + suffix, '"' + agmt_cn + '"' ];
- log_cmd('do_agmt_init', 'Get initialization status for agmt', status_cmd);
- cockpit.spawn(status_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var init_stat = JSON.parse(data);
- if (init_stat == 'Agreement successfully initialized.' ||
- init_stat == 'Agreement initialization failed.')
- {
- // Init is done (good or bad)
- get_and_set_repl_agmts();
- clearInterval(agmt_init_intervals[idx]);
- }
- });
-}
-
-function do_winsync_agmt_init(suffix, agmt_cn, idx) {
- var status_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'init-status', '--suffix=' + suffix, '"' + agmt_cn + '"' ];
- log_cmd('do_winsync_agmt_init', 'Get initialization status for agmt', status_cmd);
- cockpit.spawn(status_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var init_stat = JSON.parse(data);
- if (init_stat == 'Agreement successfully initialized.' ||
- init_stat == 'Agreement initialization failed.')
- {
- // Init is done (good or bad)
- get_and_set_repl_winsync_agmts();
- clearInterval(agmt_init_intervals[idx]);
- }
- });
-}
-
-function get_and_set_repl_winsync_agmts() {
- /*
- * Get the replication agreements for the selected suffix
- */
- var suffix = $("#select-repl-winsync-suffix").val();
- repl_winsync_agmt_table.clear();
-
- if (suffix) {
- console.log("Loading winsync replication agreements...");
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'list', '--suffix=' + suffix ];
- log_cmd('get_and_set_repl_winsync_agmts', 'Get the winsync agmts', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var obj = JSON.parse(data);
- update_progress();
- for (var idx in obj['items']) {
- var state = "Enabled";
- var con_host = "";
- var con_port = "";
- var ds_subtree = "";
- var win_subtree = "";
- agmt_attrs = obj['items'][idx]['attrs'];
- var agmt_name = agmt_attrs['cn'][0];
-
- // Compute state (enabled by default)
- if ('nsds5replicaenabled' in agmt_attrs) {
- if (agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off'){
- state = "Disabled";
- }
- }
- var ws_agmt_init_status = "Initialized";
- if ('nsds5replicalastinitstatus' in agmt_attrs &&
- agmt_attrs['nsds5replicalastinitstatus'][0] != "")
- {
- ws_agmt_init_status = agmt_attrs['nsds5replicalastinitstatus'][0];
- if (ws_agmt_init_status == "Error (0) Total update in progress" ||
- ws_agmt_init_status == "Error (0)")
- {
- ws_agmt_init_status = progress_html;
- var ws_interval_agmt_name = agmt_name;
- var ws_init_status_interval = setInterval(function() {
- var status_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'init-status', '--suffix=' + suffix, '"' + ws_interval_agmt_name + '"' ];
- log_cmd('get_and_set_repl_winsync_agmts', 'Get initialization status for winsync agmt', status_cmd);
- cockpit.spawn(status_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var init_stat = JSON.parse(data);
- if (init_stat == 'Agreement successfully initialized.' ||
- init_stat == 'Agreement initialization failed.')
- {
- // Init is done (good or bad)
- get_and_set_repl_winsync_agmts();
- clearInterval(ws_init_status_interval);
- }
- });
- }, 2000);
- } else if (ws_agmt_init_status == "Error (0) Total update succeeded") {
- ws_agmt_init_status = "Initialized";
- }
- } else if (agmt_attrs['nsds5replicalastinitstart'][0] == "19700101000000Z"){
- ws_agmt_init_status = "Not initialized";
- }
-
- repl_winsync_agmt_table.row.add( [
- agmt_attrs['cn'][0],
- agmt_attrs['nsds5replicahost'][0],
- agmt_attrs['nsds5replicaport'][0],
- state,
- agmt_attrs['nsds5replicalastupdatestatus'][0],
- ws_agmt_init_status,
- winsync_agmt_action_html
- ] ).draw( false );
- console.log("Finished loading winsync replication agreements.");
- }
- });
- } // suffix
-}
-
-
-function get_and_set_repl_agmts () {
- /*
- * Get the replication agreements for the selected suffix
- */
- var suffix = $("#select-repl-agmt-suffix").val();
- if (suffix) {
- console.log("Loading replication agreements...");
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'list', '--suffix=' + suffix ];
- log_cmd('get_and_set_repl_agmts', 'Get replication agreements', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- repl_agmt_table.clear().draw();
- var obj = JSON.parse(data);
- update_progress();
- for (var idx in obj['items']) {
- agmt_attrs = obj['items'][idx]['attrs'];
- var agmt_name = agmt_attrs['cn'][0];
- var state = "Enabled";
- var update_status = "";
- var agmt_init_status = "Initialized";
-
- // Compute state (enabled by default)
- if ('nsds5replicaenabled' in agmt_attrs) {
- if (agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off'){
- state = "Disabled";
- }
- }
-
- // Check for status msgs
- if ('nsds5replicalastupdatestatus' in agmt_attrs) {
- update_status = agmt_attrs['nsds5replicalastupdatestatus'][0];
- }
- if ('nsds5replicalastinitstatus' in agmt_attrs &&
- agmt_attrs['nsds5replicalastinitstatus'][0] != "")
- {
- agmt_init_status = agmt_attrs['nsds5replicalastinitstatus'][0];
- if (agmt_init_status == "Error (0) Total update in progress" ||
- agmt_init_status == "Error (0)")
- {
- agmt_init_status = progress_html;
- var interval_agmt_name = agmt_name;
- var init_idx = agmt_init_counter;
- agmt_init_counter += 1;
- agmt_init_intervals[init_idx] = setInterval( do_agmt_init, 2000, suffix, interval_agmt_name, init_idx);
- } else if (agmt_init_status == "Error (0) Total update succeeded") {
- agmt_init_status = "Initialized";
- }
- } else if (agmt_attrs['nsds5replicalastinitstart'][0] == "19700101000000Z"){
- agmt_init_status = "Not initialized";
- }
-
- // Update table
- repl_agmt_table.row.add( [
- agmt_attrs['cn'][0],
- agmt_attrs['nsds5replicahost'][0],
- agmt_attrs['nsds5replicaport'][0],
- state,
- update_status,
- agmt_init_status,
- agmt_action_html
- ] ).draw( false );
- }
- console.log("Finished loading replication agreements.");
- }).fail(function () {
- repl_agmt_table.clear().draw();
- });
- } // suffix
-
- // Load fractional replication agreement attr list here
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'schema', 'list'];
- log_cmd('get_and_set_repl_agmts', 'Get all schema objects', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(schema_data) {
- var schema_json = JSON.parse(schema_data);
- load_schema_objects_to_select('attributetypes', 'select-attr-list', schema_json);
- }).fail(function(oc_data) {
- console.log("Get all schema objects failed: " + oc_data.message);
- check_inst_alive(1);
- });
-}
-
-
-function get_and_set_cleanallruv() {
- console.log("Loading replication tasks...");
- let cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-tasks', 'list-cleanruv-tasks'];
- log_cmd('get_and_set_cleanallruv', 'Get the cleanAllRUV tasks', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var tasks = JSON.parse(data);
- update_progress();
- repl_clean_table.clear().draw();
- for (var idx in tasks['items']) {
- task_attrs = tasks['items'][idx]['attrs'];
- // Update table
- var abort_btn = cleanallruv_action_html;
- if (task_attrs['nstaskstatus'][0].includes('Successfully cleaned rid') ){
- abort_btn = "<i>Task Complete</i>";
- } else if (task_attrs['nstaskstatus'][0].includes('Task aborted for rid') ){
- abort_btn = "<i>Task Aborted</i>";
- }
- repl_clean_table.row.add( [
- task_attrs['cn'][0],
- get_date_string(task_attrs['createtimestamp'][0]),
- task_attrs['replica-base-dn'][0],
- task_attrs['replica-id'][0],
- task_attrs['nstaskstatus'][0],
- abort_btn
- ] );
- }
- repl_clean_table.draw(false);
- console.log("Finished loading replication tasks.");
- });
-}
-
-
-function get_and_set_repl_config () {
- var suffix = $("#select-repl-cfg-suffix").val();
-
- if (suffix) {
- $("#nsds5replicaid").css("border-color", "initial");
- repl_config_values = {};
- console.log("Loading replication configuration...");
-
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'get', '--suffix=' + suffix ];
- log_cmd('get_and_set_repl_config', 'Get replication configuration', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var repl = JSON.parse(data);
- var repl_type;
- var repl_flags;
- var manager = false;
- $('#repl-mgr-table').find("tr:gt(0)").remove();
- $(".ds-cfg").val("");
- $("#nsds5replicaprecisetombstonepurging").prop('checked', false);
- update_progress();
-
- // Set configuration and the repl manager table
- for (var attr in repl['attrs']) {
- var vals = repl['attrs'][attr];
- attr = attr.toLowerCase();
-
- if (attr in repl_attr_map) {
- if (attr == "nsds5replicabinddn") {
- // update manager table
- for (var val_idx in vals){
- add_repl_mgr(vals[val_idx]);
- manager = true;
- }
- } else if (attr == "nsds5replicatype") {
- repl_type = vals[0];
- } else if (attr == "nsds5flags") {
- repl_flags = vals[0];
- } else {
- // Regular attribute
- if (vals[0] == "on") {
- $("#" + attr).prop('checked', true);
- $("#" + attr).trigger('change');
- } else if (vals[0] == "off") {
- $("#" + attr).prop('checked', false);
- $("#" + attr).trigger('change');
- }
- $("#" + attr ).val(vals[0]);
- repl_config_values[attr] = vals[0];
- }
- }
- }
- if (!manager) {
- // Add an empty row to define the table
- $("#repl-mgr-table tbody").append(
- "<tr>"+
- "<td class='ds-td'>None</td>"+
- "<td></td>" +
- "</tr>");
- }
-
- // Set the replica role
- if (repl_type == "3"){
- $("#select-repl-role").val("Master");
- current_role = "Master";
- $("#nsds5replicaid").show();
- $("#replicaid-label").show();
- } else {
- $("#nsds5replicaid").hide();
- $("#replicaid-label").hide();
- if (repl_flags == "1"){
- $("#select-repl-role").val("Hub");
- current_role = "Hub";
- } else {
- $("#select-repl-role").val("Consumer");
- current_role = "Consumer";
- }
- }
- current_rid = $("#nsds5replicaid").val();
-
- // Show the page (in case it was hidden)
- $("#ds-repl-enabled").hide();
- $("#repl-config-content").show();
- load_repl_suffix_dropdowns();
-
- console.log("Finished loading replication configuration.");
- }).fail(function(data) {
- // No replication
- current_role = "Disabled";
- $("#repl-config-content").hide();
- $("#ds-repl-enabled").show();
- load_repl_suffix_dropdowns();
- });
- } else {
- // No Suffix
- $("#repl-config-content").hide();
- $("#ds-repl-enabled").hide();
- }
-
- // Do the changelog settings
- $("#cl-create-div").show();
- $("#cl-del-div").hide();
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'get-changelog'];
- log_cmd('get_and_set_repl_config', 'Get replication changelog', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- $(".ds-cl").val(""); // Clear form
- var cl = JSON.parse(data);
- repl_cl_values = {};
- for (var attr in cl['attrs']) {
- var val = cl['attrs'][attr][0];
- attr = attr.toLowerCase();
- $("#" + attr ).val(val);
- $("#cl-create-div").hide();
- $("#cl-del-div").show();
- repl_cl_values[attr] = val;
- }
- }).fail(function() {
- // No changelog, clear the form
- $(".ds-cl").val("");
- });
-}
-
-
-function save_repl_config(suffix, ignore_rid) {
- /*
- * Check for changes in the replication settings
- */
- var set_repl_values = {};
- var set_cl_values = {};
- var arg_list = [];
- for (var attr in repl_attrs) {
- attr = repl_attrs[attr];
- var val = "";
- if ( $("#" + attr).is(':checkbox')) {
- if ( $("#" + attr).is(":checked")) {
- val = "on";
- } else {
- val = "off";
- }
- } else {
- val = $("#" + attr).val();
- }
- var prev_val = "";
-
- if (attr in repl_config_values) {
- prev_val = repl_config_values[attr];
- }
-
- if (val != prev_val) {
- if (attr == "nsds5replicaid"){
- // skip it since we are doing a promotion
- continue;
- }
- // Handle checkbox input
- if ( $("#" + attr).is(':checkbox')) {
- if ( $("#" + attr).is(":checked")) {
- arg_list.push(repl_attr_map[attr] + "=" + "on" );
- } else {
- // Not checked
- arg_list.push(repl_attr_map[attr] + "=" + "off" );
- }
- } else {
- // Regular input
- if (val != "") {
- // Regular setting - add to the list
- arg_list.push(repl_attr_map[attr] + "=" + val );
- } else {
- // removed
- arg_list.push(repl_attr_map[attr] + "=");
- }
- }
- set_repl_values[attr] = val;
- }
- }
-
- /*
- * Check for changes in the changelog settings
- */
- var arg_cl_list = [];
- for (var attr in repl_cl_attrs) {
- attr = repl_cl_attrs[attr];
- var val = $("#" + attr).val();
- var prev_val = "";
- if (attr in repl_cl_values) {
- prev_val = repl_cl_values[attr];
- }
- if (val != prev_val) {
- // we have a difference
- if (val != "") {
- // Regular setting -add to the list
- arg_cl_list.push(repl_attr_map[attr] + "=" + val);
- } else {
- // removed
- arg_cl_list.push(repl_attr_map[attr] + "=");
- }
- set_cl_values[attr] = val;
- }
- }
-
- /*
- * Save repl config settings
- */
- if (arg_list.length > 0){
-
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'set', '--suffix=' + suffix ];
- cmd = cmd.concat(arg_list);
- log_cmd('save_repl_config', 'Set replication configuration', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success('Saved replication configuration');
- for (var key in set_repl_values) {
- // Update current in memory values
- repl_config_values[key] = set_repl_values[key];
- }
- /*
- * Save changelog settings
- */
- if (arg_cl_list.length > 0){
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication' ,'set-changelog'];
- cmd = cmd.concat(arg_cl_list);
- log_cmd('save_repl_config', 'Set changelog configuration', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success('Saved changelog configuration');
- for (var key in set_cl_values) {
- // Update current in memory values
- repl_cl_values[key] = set_cl_values[key];
- }
- get_and_set_repl_config();
- }).fail(function(data) {
- get_and_set_repl_config();
- popup_err("Failed to save changelog configuration", data.message);
- });
- } else {
- // No changelog changes, so we're done, refresh the settings
- get_and_set_repl_config();
- }
- }).fail(function(data) {
- // Restore prev values
- get_and_set_repl_config();
- popup_err("Failed to set replication configuration", data.message);
- });
- } else if (arg_cl_list.length > 0) {
- /*
- * Only changelog settings need to be applied
- */
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication' ,'set-changelog'];
- cmd = cmd.concat(arg_cl_list);
- log_cmd('save_repl_config', 'Set changelog configuration', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success('Saved changelog configuration');
- for (var key in set_cl_values) {
- // Update current in memory values
- repl_cl_values[key] = set_cl_values[key];
- }
- get_and_set_repl_config();
- }).fail(function(data) {
- get_and_set_repl_config();
- popup_err("Failed to save changelog configuration", data.message);
- });
- }
-}
-
-
-/*
- * Load the replication page, and set the handlers
- */
-$(document).ready( function() {
- $("#replication-content").load("replication.html", function () {
- // Set up agreement table
- repl_agmt_table = $('#repl-agmt-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "emptyTable": "No agreements configured",
- "search": "Search Agreements"
- },
- "columnDefs": [ {
- "targets": 6,
- "orderable": false
- } ],
- "columns": [
- { "width": "20%" },
- { "width": "20%" },
- { "width": "50px" },
- { "width": "50px" },
- { "width": "20%" },
- { "width": "20%" },
- { "width": "130px" }
- ],
- });
-
- // Set up windows sync agreement table
- repl_winsync_agmt_table = $('#repl-winsync-agmt-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "emptyTable": "No winsync agreements configured",
- "search": "Search Agreements"
- },
- "columnDefs": [ {
- "targets": 6,
- "orderable": false
- } ]
- });
-
- // Set up CleanAllRUV Table
- repl_clean_table = $('#repl-clean-table').DataTable( {
- "paging": true,
- "searching": false,
- "bAutoWidth": false,
- "dom": 'B<"pull-left"f><"pull-right"l>tip',
- buttons: [
- {
- text: 'Refresh Task List',
- action: function ( e, dt, node, config ) {
- console.log("hmmm");
- get_and_set_cleanallruv();
- }
- }
- ],
- "lengthChange": false,
- "language": {
- "emptyTable": "No CleanAllRUV tasks",
- },
- "columnDefs": [ {
- "targets": 5,
- "orderable": false
- } ]
- });
-
- binddn_list_color = $("#repl-managers-list").css("border-color");
-
- // Load existing replication config (if any), set role, etc
-
- $("#schedule-settings").hide();
-
- $("#repl-config-btn").on("click", function() {
- $(".all-pages").hide();
- $("#replication-content").show();
- $("#repl-config").show();
- });
-
- $("#repl-agmts-btn").on("click", function() {
- $(".all-pages").hide();
- $("#replication-content").show();
- $("#repl-agmts").show();
- });
-
- $("#repl-winsync-btn").on("click", function() {
- $(".all-pages").hide();
- $("#replication-content").show();
- $("#repl-winsync").show();
- });
- $("#repl-tasks-btn").on("click", function() {
- $(".all-pages").hide();
- $("#replication-content").show();
- $("#repl-cleanallruv").show();
- });
-
- $("#select-repl-cfg-suffix").on("change", function() {
- get_and_set_repl_config();
- });
-
- $("#select-repl-agmt-suffix").on("change", function() {
- get_and_set_repl_agmts();
- });
-
- $("#select-repl-winsync-suffix").on("change", function() {
- get_and_set_repl_winsync_agmts();
- });
-
- $("#select-repl-role").on("change", function() {
- var new_role = $(this).val();
- if (new_role == "Master"){
- if (current_role != new_role) {
- // Reset replica ID for a new master
- $("#nsds5replicaid").val("0");
- }
- $("#nsds5replicaid").show();
- $("#replicaid-label").show();
- $("#repl-config-content").show();
- } else {
- $("#nsds5replicaid").hide();
- $("#replicaid-label").hide();
- $("#repl-config-content").show();
- $("#nsds5replicaid").css("border-color", "initial");
- }
- });
-
- $("#enable-repl-btn").on('click', function () {
- clear_enable_repl_form();
- });
-
- /*
- * Enable replication - select role dynamics
- */
- $("#select-enable-repl-role").on("change", function() {
- var new_role = $(this).val();
- if (new_role == "Master"){
- $("#repl-rid-form").show();
- } else {
- $("#repl-rid-form").hide();
- }
- });
- /*
- * Enable replication - save it
- */
- $("#enable-repl-save").on('click', function () {
- var suffix = $("#select-repl-cfg-suffix").val();
- var role = $("#select-enable-repl-role").val();
- var repl_dn = $("#enable-repl-mgr-dn").val();
- var repl_pw = $("#enable-repl-pw").val();
- var repl_pw_confirm = $("#enable-repl-pw-confirm").val();
- var repl_group = $("#enable-bindgroupdn").val();
- var cmd = [];
-
- // Validate all the inputs
- if (role == "Master") {
- // Master role, get and valid replica id
- var rid = $("#nsds5replicaid-form").val();
- if (rid == ""){
- $("#nsds5replicaid-form").css("border-color", "red");
- popup_msg("Missing Replica ID",
- "A Master replica requires a unique identifier. " +
- "Please enter a value for <b>Replica ID</b> between 1 and 65534");
- return;
- }
- if (valid_num(rid)){
- if (rid < 1 || rid >= 65535){
- $("#nsds5replicaid-form").css("border-color", "red");
- popup_msg("Invalid Replica ID",
- "A Master replica requires a unique identifier. " +
- "Please enter a value for <b>Replica ID</b> between 1 and 65534");
- return;
- }
- } else {
- $("#nsds5replicaid-form").css("border-color", "red");
- popup_msg("Replica ID is not a number",
- "A Master replica requires a unique identifier. " +
- "Please enter a value for <b>Replica ID</b> between 1 and 65534");
- return;
- }
- $("#nsds5replicaid-form").css("border-color", "initial");
- cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'enable',
- '--suffix=' + suffix, '--role=' + role, '--replica-id=' + rid];
- } else {
- // Hub or Consumer - must have a bind dn/group
- if (repl_dn == "" && repl_group == ""){
- popup_msg("Attention!", "Replication Manager or Replication Bind Group is required");
- return;
- }
- cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'enable',
- '--suffix=' + suffix, '--role=' + role];
- }
-
- if (repl_dn != ''){
- if (repl_pw != repl_pw_confirm) {
- popup_msg("Attention!", "Passwords do not match");
- $("#enable-repl-pw").val("");
- $("#enable-repl-pw-confirm").val("");
- return;
- }
- if (!valid_dn(repl_dn)) {
- popup_msg("Attention!", "Invalid DN for Replication Manager");
- return;
- }
- cmd.push.apply(cmd, ['--bind-dn=' + repl_dn]);
- cmd.push.apply(cmd, ['--bind-passwd=' + repl_pw]);
- }
- if (repl_group != ""){
- if (!valid_dn(repl_group)){
- popup_msg("Attention!", "Invalid DN for Replication Bind Group");
- return;
- }
- cmd.push.apply(cmd, ['--bind-group-dn=' + repl_group]);
- }
-
- // Enable replication finally
- log_cmd('#enable-repl-save (click)', 'Enable replication', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- // Replication has been enabled
- popup_success("Successfully enabled replication");
- $("#enable-repl-form").modal('toggle');
- get_and_set_repl_config();
- }).fail(function(data) {
- // Undo what we have done
- popup_err("Failed to enable replication", data.message);
- var disable_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'disable', '--suffix=' + suffix ];
- log_cmd('#enable-repl-save (click)', 'Disable replication after error', disable_cmd);
- cockpit.spawn(disable_cmd, { superuser: true, "err": "message", "environ": [ENV]}).always(function(data) {
- get_and_set_repl_config();
- $("#enable-repl-form").modal('toggle');
- });
- });
- });
-
- /*
- * Disable replication
- */
- $("#disable-repl-btn").on('click', function () {
- var suffix = $("#select-repl-cfg-suffix").val();
- popup_confirm("Are you sure you want to disable replication? This will remove all your replication agreements and can not be undone!", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'disable', '--suffix=' + suffix ];
- log_cmd('#disable-repl-btn (click)', 'Disable replication', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- current_role = "Disabled";
- $("#repl-config-content").hide();
- popup_success('Successfully disabled replication');
- get_and_set_repl_config();
- }).fail(function(data) {
- popup_err("Failed to disable replication", data.message);
- get_and_set_repl_config();
- });
- }
- });
- });
-
- /*
- * Save replication configuration
- */
- $("#repl-config-save").on('click', function () {
- var suffix = $("#select-repl-cfg-suffix").val();
- var rid = $("#nsds5replicaid").val();
-
- if (suffix) {
- /*
- * Did we config change, promote, or demote this replica?
- */
- var new_role = $("#select-repl-role").val();
- if (new_role != current_role) {
- /*
- * Promote/demote the replica
- */
- popup_confirm("Are you sure you want to change the <i>replication role</i> to \"<b>" + new_role + "</b>\"?", "Confirmation", function (yes) {
- if (yes) {
- if (new_role == "Master"){
- /*
- * Promote to Master
- */
- if ( !valid_num(rid) ) {
- popup_msg("Invalid Replica ID",
- "A Master replica requires a unique numerical identifier. Please enter a value for <b>Replica ID</b> between 1 and 65534");
- get_and_set_repl_config();
- return;
- }
- var rid_num = parseInt(rid, 10);
- if (rid_num < 1 || rid_num >= 65535){
- popup_msg("Missing Required Replica ID",
- "A Master replica requires a unique numerical identifier. Please enter a value for <b>Replica ID</b> between 1 and 65534");
- get_and_set_repl_config();
- return;
- }
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'promote',
- '--suffix=' + suffix, "--newrole=" + new_role, "--replica-id=" + rid];
- log_cmd('#repl-config-save (click)', 'Promote replica to Master', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- current_role = "Master";;
- popup_success('Successfully promoted replica to a <b>Master</b>');
- get_and_set_repl_config();
- save_repl_config(suffix, true);
- }).fail(function(data) {
- popup_err("Failed to promote replica to a Master", data.message);
- get_and_set_repl_config();
- });
- } else if (new_role == "Hub" && current_role == "Master"){
- /*
- * Demote to Hub, but first check that we have a replication manager
- */
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'get', '--suffix=' + suffix ];
- log_cmd('get_and_set_repl_config', 'Get replication configuration', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var repl = JSON.parse(data);
- var manager = false;
- for (var attr in repl['attrs']) {
- if (attr.toLowerCase() == "nsds5replicabinddn") {
- manager = true;
- break;
- }
- }
- if (manager == false) {
- popup_msg("Missing Required Setting",
- "You must create a replication manager before you can demote this replica to a Hub");
- return;
- }
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'demote',
- '--suffix=' + suffix, "--newrole=" + new_role];
- log_cmd('#repl-config-save (click)', 'Demote replica to Hub', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- current_role = "Hub";
- popup_success('Successfully demoted replica to a <b>Hub</b>');
- save_repl_config(suffix, true);
- }).fail(function(data) {
- popup_err("Failed to demote replica to a Hub", data.message);
- get_and_set_repl_config();
- });
- });
- } else if (new_role == "Hub" && current_role == "Consumer"){
- /*
- * Promote to Hub
- */
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'promote',
- '--suffix=' + suffix, "--newrole=" + new_role];
- log_cmd('#repl-config-save (click)', 'Promote replica to Hub ', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- current_role = "Hub";;
- popup_success('Successfully promoted replica to a <b>Hub</b>');
- save_repl_config(suffix, true);
- }).fail(function(data) {
- popup_err("Failed to promote replica to a Hub", data.message);
- get_and_set_repl_config();
- });
- } else {
- /*
- * Demote to Consumer, but first confirm we have a replication manager
- */
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'get', '--suffix=' + suffix ];
- log_cmd('get_and_set_repl_config', 'Get replication configuration', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var repl = JSON.parse(data);
- var manager = false;
- for (var attr in repl['attrs']) {
- if (attr.toLowerCase() == "nsds5replicabinddn") {
- manager = true;
- break;
- }
- }
- if (manager == false) {
- popup_msg("Missing Required Setting",
- "You must create a replication manager before you can demote this replica to a Consumer.");
- return;
- }
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'demote',
- '--suffix=' + suffix, "--newrole=" + new_role];
- log_cmd('#repl-config-save (click)', 'Demote replication to Consumer', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- current_role = "Consumer";
- popup_success('Successfully demoted replica to a <b>Consumer</b>');
- save_repl_config(suffix, true);
- }).fail(function(data) {
- popup_err("Failed to demote replica to a Consumer", data.message);
- get_and_set_repl_config();
- });
- });
- }
- } else {
- // Not changing the role - reset the dropdown
- $("#select-repl-role").val(current_role);
- get_and_set_repl_config();
- }
- }); // popup_confirm
- } else {
- /*
- * We did NOT promote/demote, etc. This was just a configuration change...
- */
- save_repl_config(suffix, false);
- }
- } // Suffix
- });
-
- // Create changelog
- $("#create-cl-btn").on('click', function () {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication' ,'create-changelog'];
- log_cmd('#create-cl-btn (click)', 'Create replication changelog', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- get_and_set_repl_config();
- popup_success('Successfully created replication changelog');
- }).fail(function(data) {
- get_and_set_repl_config();
- popup_err("Failed to create replication changelog", data.message);
- });
- });
-
- // Remove changelog
- $("#delete-cl-btn").on('click', function () {
- popup_confirm("Are you sure you want to delete the replication changelog as it will break all the existing agreements?", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication' ,'delete-changelog'];
- log_cmd('#delete-cl-btn (click)', 'Delete replication changelog', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- get_and_set_repl_config();
- popup_success('Successfully removed replication changelog');
- }).fail(function(data) {
- get_and_set_repl_config();
- popup_err("Failed to remove replication changelog", data.message);
- });
- }
- });
- });
-
- // Save Repl Agreement Wizard
- $("#agmt-save").on("click", function() {
- // Get all the settings
- var suffix = $("#select-repl-agmt-suffix").val();
- var cmd = [];
- var cmd_args = [];
- var param_err = false;
- var agmt_name = $("#agmt-cn").val();
- var agmt_host = $("#nsds5replicahost").val();
- var agmt_port = $("#nsds5replicaport").val();
- var agmt_bind = $("#nsds5replicabinddn").val();
- var agmt_bindpw = $("#nsds5replicacredentials").val();
- var agmt_bindpw_confirm = $("#nsds5replicacredentials-confirm").val();
- var agmt_conn = $("#nsds5replicatransportinfo").val();
- var agmt_method = $("#nsds5replicabindmethod").val();
- var agmt_schedule = "";
- var agmt_init = $("#init-options").val();
- var agmt_exclude = "";
- var agmt_tot_exclude = "";
- var agmt_strip = "";
- var editing = false;
- var init_replica = false;
-
- if ($("#agmt-wizard-title").text().includes('Edit') ) {
- editing = true;
- }
-
- // Check required settings
- if ( agmt_port == "") {
- $("#nsds5replicaport").css("border-color", "red");
- param_err = true;
- } else {
- $("#nsds5replicaport").css("border-color", "initial");
- cmd_args.push("--port=" + agmt_port);
- }
- if ( agmt_host == "") {
- $("#nsds5replicahost").css("border-color", "red");
- param_err = true;
- } else {
- $("#nsds5replicahost").css("border-color", "initial");
- cmd_args.push('--host=' + agmt_host);
- }
- if ( agmt_conn == "") {
- $("#nsds5replicatransportinfo").css("border-color", "red");
- param_err = true;
- } else {
- $("#nsds5replicatransportinfo").css("border-color", "initial");
- cmd_args.push('--conn-protocol=' + agmt_conn);
- }
- if ( agmt_method == "") {
- $("#nsds5replicabindmethod").css("border-color", "red");
- param_err = true;
- } else {
- $("#nsds5replicabindmethod").css("border-color", "initial");
- cmd_args.push('--bind-method=' + agmt_method);
- }
- if ( agmt_bind == "") {
- $("#nsds5replicabinddn").css("border-color", "red");
- param_err = true;
- } else {
- $("#nsds5replicabinddn").css("border-color", "initial");
- cmd_args.push('--bind-dn=' + agmt_bind);
- }
- if (agmt_name == "") {
- $("#agmt-cn").css("border-color", "red");
- param_err = true;
- } else {
- $("#agmt-cn").css("border-color", "initial");
- cmd_args.push('"' + agmt_name + '"');
- }
- if (param_err ){
- popup_msg("Error", "Missing required parameters");
- return;
- }
-
- /*
- * Handle the optional settings
- */
- $("#frac-exclude-list option").each(function() {
- agmt_exclude += $(this).val() + " ";
- });
- $("#frac-exclude-tot-list option").each(function() {
- agmt_tot_exclude += $(this).val() + " ";
- });
- $("#frac-strip-list option").each(function() {
- agmt_strip += $(this).val() + " ";
- });
-
- if (agmt_bindpw != agmt_bindpw_confirm) {
- popup_msg("Attention!", "Passwords do not match");
- return;
- }
-
- // Bind Password
- if (!editing ){
- if (agmt_bindpw != "") {
- cmd_args.push('--bind-passwd=' + agmt_bindpw);
- }
- } else {
- if ( !('nsds5replicacredentials' in repl_agmt_values) ||
- agmt_bindpw != repl_agmt_values['nsds5replicacredentials'])
- {
- cmd_args.push('--bind-passwd=' + agmt_bindpw);
- }
- }
- // Frac attrs
- agmt_exclude = agmt_exclude.trim();
- if (!editing) {
- if (agmt_exclude != "") {
- cmd_args.push('--frac-list='+ agmt_exclude);
- }
- } else {
- if ( !('nsds5replicatedattributelist' in repl_agmt_values) ||
- agmt_exclude != repl_agmt_values['nsds5replicatedattributelist'].replace(frac_prefix, ""))
- {
- cmd_args.push('--frac-list=' + frac_prefix + ' ' + agmt_exclude);
- }
- }
- // Frac total attr
- agmt_tot_exclude = agmt_tot_exclude.trim();
- if (!editing) {
- if (agmt_tot_exclude != "") {
- cmd_args.push('--frac-list-total='+ agmt_tot_exclude);
- }
- } else {
- if ( !('nsds5replicatedattributelisttotal' in repl_agmt_values) ||
- agmt_tot_exclude != repl_agmt_values['nsds5replicatedattributelisttotal'].replace(frac_prefix, ""))
- {
- cmd_args.push('--frac-list-total=' + frac_prefix + ' ' + agmt_tot_exclude);
- }
- }
- // Strip attrs
- agmt_strip = agmt_strip.trim();
- if (!editing) {
- if (agmt_strip != "") {
- cmd_args.push('--strip-list='+ agmt_strip);
- }
- } else {
- if ( !('nsds5replicastripattrs' in repl_agmt_values) ||
- agmt_strip != repl_agmt_values['nsds5replicastripattrs'] )
- {
- cmd_args.push('--strip-list='+ agmt_strip);
- }
- }
-
- if ( !($("#agmt-schedule-checkbox").is(":checked")) ){
- agmt_start = $("#agmt-start-time").val().replace(':','');
- agmt_end = $("#agmt-end-time").val().replace(':','');
-
- if (agmt_start == agmt_end) {
- popup_msg("Error", "The replication start and end times can not behte same");
- return;
- }
-
- // build the days
- var agmt_days = "";
- if ( $("#schedule-sun").is(":checked") ){
- agmt_days = "0";
- }
- if ( $("#schedule-mon").is(":checked") ){
- agmt_days += "1";
- }
- if ( $("#schedule-tue").is(":checked") ){
- agmt_days += "2";
- }
- if ( $("#schedule-wed").is(":checked") ){
- agmt_days += "3";
- }
- if ( $("#schedule-thu").is(":checked") ){
- agmt_days += "4";
- }
- if ( $("#schedule-fri").is(":checked") ){
- agmt_days += "5";
- }
- if ( $("#schedule-sat").is(":checked") ){
- agmt_days += "6";
- }
- if (agmt_days == "" ){
- popup_msg("Error", "You must set at least one day in the schedule to perform replication");
- return;
- }
- // Set final value
- agmt_schedule = agmt_start + "-" + agmt_end + " " + agmt_days;
-
- if (!editing ){
- cmd_args.push('--schedule=' + agmt_schedule);
- } else {
- if ( !('nsds5replicaupdateschedule' in repl_agmt_values) ||
- agmt_schedule != repl_agmt_values['nsds5replicaupdateschedule'] )
- {
- cmd_args.push('--schedule=' + agmt_schedule);
- }
- }
- } else {
- // if "sync all the time" is checked, might need to remove the schedule attribute
- if ('nsds5replicaupdateschedule' in repl_agmt_values) {
- cmd_args.push('--schedule=');
- }
- }
- if (agmt_init == "online-init") {
- init_replica = true;
- }
-
- // Create agreement in DS
- if ( editing ) {
- cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'set', '--suffix=' + suffix ];
- } else {
- cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'create', '--suffix=' + suffix];
- }
- cmd = cmd.concat(cmd_args);
- log_cmd('#agmt-save (click)', 'Create/Set replication agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- if (editing){
- popup_success('Successfully edited replication agreement');
- } else {
- popup_success('Successfully created replication agreement');
- }
- if (init_replica) {
- // Launch popup stating initialization has begun
- var init_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'init', '--suffix=' + suffix, agmt_name ];
- log_cmd('#agmt-save (click)', 'Initialize agreement', init_cmd);
- cockpit.spawn(init_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- get_and_set_repl_agmts();
- }).fail(function(data) {
- popup_err("Failed to initialize replication agreement", data.message);
- });
- } else {
- get_and_set_repl_agmts();
- }
- }).fail(function(data) {
- if (editing) {
- popup_err("Failed to edit replication agreement", data.message);
- } else {
- popup_err("Failed to create replication agreement", data.message);
- }
- });
-
- // Done, close the form
- $("#agmt-form").modal('toggle');
- clear_agmt_wizard();
- });
-
- /*
- * Initialize agreement
- */
- $(document).on('click', '.agmt-init-btn', function(e) {
- e.preventDefault();
- var suffix = $("#select-repl-agmt-suffix").val();
- var data = repl_agmt_table.row( $(this).parents('tr') ).data();
- var agmt_name = data[0];
- var row_idx = $(this).closest('tr').index();
- repl_agmt_table.cell({row: row_idx, column: 5}).data(progress_html).draw();
-
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'init', '--suffix=' + suffix, '"' + agmt_name + '"' ];
- log_cmd('.agmt-init-btn (click)', 'Initialize replication agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var init_idx = agmt_init_counter;
- agmt_init_counter += 1;
- agmt_init_intervals[init_idx] = setInterval( do_agmt_init, 2000, suffix, agmt_name, init_idx);
- }).fail(function(data) {
- get_and_set_repl_agmts();
- popup_err("Failed to initialize agreement", data.message);
- });
- });
-
- $(document).on('click', '.winsync-agmt-init-btn', function(e) {
- e.preventDefault();
- var suffix = $("#select-repl-winsync-suffix").val();
- var data = repl_winsync_agmt_table.row( $(this).parents('tr') ).data();
- var agmt_name = data[0];
- var row_idx = $(this).closest('tr').index();
- repl_winsync_agmt_table.cell({row: row_idx, column: 5}).data(progress_html).draw();
-
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'init', '--suffix=' + suffix, '"' + agmt_name + '"' ];
- log_cmd('.winsync-agmt-init-btn (click)', 'Initialize replication winsync agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var init_idx = agmt_init_counter;
- agmt_init_counter += 1;
- agmt_init_intervals[init_idx] = setInterval( do_winsync_agmt_init, 2000, suffix, agmt_name, init_idx);
- }).fail(function(data) {
- get_and_set_repl_winsync_agmts();
- popup_err("Failed to initialize winsync agreement", data.message);
- });
- });
-
-
- /* Store the repl dn from the table when opening the mgr delete confirmation modal */
- $(document).on('click', '.remove-repl-mgr', function(e) {
- e.preventDefault();
- var mgr_row = $(this).parent().parent();
- var suffix = $("#select-repl-cfg-suffix").val();
- var mgr_dn = mgr_row.children("td:nth-child(1)");
- popup_confirm("Are you sure you want to delete Replication Manager: <b>" + mgr_dn.text() + "<b>", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication' ,'set',
- '--repl-del-bind-dn=' + mgr_dn.text(), '--suffix=' + suffix];
- log_cmd('.remove-repl-mgr (click)', 'Remove replication manager ', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- /* Remove the manager entry */
- var del_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication',
- 'delete-manager', "--name=" + mgr_dn.text()];
- log_cmd('.remove-repl-mgr(click)', 'Delete replication manager entry', del_cmd);
- cockpit.spawn(del_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success('Successfully removed replication manager');
- get_and_set_repl_config();
- });
- }).fail(function(data) {
- get_and_set_repl_config();
- popup_err("Failed to remove replication manager", data.message);
- });
- }
- });
- });
-
- /*
- * Delete repl agreement
- */
- $(document).on('click', '.agmt-del-btn', function(e) {
- e.preventDefault();
- var data = repl_agmt_table.row( $(this).parents('tr') ).data();
- var del_agmt_name = data[0];
- var agmt_row = $(this);
- popup_confirm("Are you sure you want to delete replication agreement: <b>" + del_agmt_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var suffix = $("#select-repl-agmt-suffix").val();
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'delete', '--suffix=' + suffix, '"' + del_agmt_name + '"'];
- log_cmd('.agmt-del-btn (click)', 'Delete replication agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success('Successfully removed replication agreement');
- // Update table
- repl_agmt_table.row( agmt_row.parents('tr') ).remove().draw( false );
- }).fail(function(data) {
- get_and_set_repl_config();
- popup_err("Failed to remove replication agreement", data.message);
- });
- }
- });
- });
-
- $(document).on('click', '.winsync-agmt-del-btn', function(e) {
- e.preventDefault();
- var data = repl_winsync_agmt_table.row( $(this).parents('tr') ).data();
- var del_agmt_name = data[0];
- var agmt_row = $(this);
- popup_confirm("Are you sure you want to delete replication agreement: <b>" + del_agmt_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var suffix = $("#select-repl-agmt-suffix").val();
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'delete', '--suffix=' + suffix, '"' + del_agmt_name + '"'];
- log_cmd('.winsync-agmt-del-btn (click)', 'Delete replication winsync agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success('Successfully removed replication winsync agreement');
- // Update table
- repl_winsync_agmt_table.row( agmt_row.parents('tr') ).remove().draw( false );
- }).fail(function(data) {
- get_and_set_repl_config();
- popup_err("Failed to remove replication winsync agreement", data.message);
- });
- }
- });
- });
-
- /*
- * Edit Agreement
- */
- $(document).on('click', '.agmt-edit-btn', function(e) {
- e.preventDefault();
- clear_agmt_wizard();
- var suffix = $("#select-repl-agmt-suffix").val();
- var data = repl_agmt_table.row( $(this).parents('tr') ).data();
- var edit_agmt_name = data[0];
- // Set agreement form values
- $("#agmt-wizard-title").html("<b>Edit Replication Agreement</b>");
-
- // Hide init dropdown
- $("#init-agmt-dropdown").hide();
-
- // Get agreement from DS and populate form
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'get', '--suffix=' + suffix, '"' + edit_agmt_name + '"'];
- log_cmd('.agmt-edit-btn (click)', 'Edit replication agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var agmt_obj = JSON.parse(data);
- var frac_attrs = "";
- var frac_tot_attrs = "";
- var strip_attrs = "";
-
- $("#agmt-cn").val(edit_agmt_name);
- for (var attr in agmt_obj['attrs']) {
- var val = agmt_obj['attrs'][attr][0];
- attr = attr.toLowerCase();
- $("#" + attr).val(val);
- repl_agmt_values[attr] = val;
- }
-
- // Fill Password Confirm Input
- if ( 'nsds5replicacredentials' in agmt_obj['attrs'] ){
- $("#nsds5replicacredentials-confirm").val(agmt_obj['attrs']["nsds5replicacredentials"][0]);
- }
-
- // Transport info
- val = agmt_obj['attrs']["nsds5replicatransportinfo"][0].toLowerCase();
- if (val == "ldap"){
- $("#nsds5replicatransportinfo").val("LDAP");
- } else if (val == "ldaps"){
- $("#nsds5replicatransportinfo").val("LDAPS");
- } else if (val == "starttls" || val == "tls"){
- $("#nsds5replicatransportinfo").val("StartTLS");
- }
-
- // Bind Method
- val = agmt_obj['attrs']["nsds5replicabindmethod"][0].toLowerCase();
- if (val == "simple"){
- $("#nsds5replicabindmethod").val("SIMPLE");
- } else if (val == "sasl/digest-md5"){
- $("#nsds5replicabindmethod").val("SASL/DIGEST-MD5");
- } else if (val == "sasl/gssapi"){
- $("#nsds5replicabindmethod").val("SASL/GSSAPI");
- } else if (val == "sslclientauth"){
- $("#nsds5replicatransportinfo").val("SSLCLIENTAUTH");
- }
-
- // Load fractional lists
- if ( 'nsds5replicatedattributelist' in agmt_obj['attrs'] ){
- frac_attrs = agmt_obj['attrs']['nsds5replicatedattributelist'][0];
- frac_attrs = frac_attrs.replace(frac_prefix, "").split(" ");
- for(var i = 0; i < frac_attrs.length; i++) {
- var opt = frac_attrs[i];
- if (opt != "") {
- var option = $('<option></option>').attr("value", opt).text(opt);
- $("#frac-exclude-list").append(option);
- }
- }
- }
- if ( 'nsds5replicatedattributelisttotal' in agmt_obj['attrs'] ){
- frac_tot_attrs = agmt_obj['attrs']['nsds5replicatedattributelisttotal'][0];
- frac_tot_attrs = frac_tot_attrs.replace(frac_prefix, "").split(" ");
- for(var i = 0; i < frac_tot_attrs.length; i++) {
- var opt = frac_tot_attrs[i];
- if (opt != "") {
- var option = $('<option></option>').attr("value", opt).text(opt);
- $("#frac-exclude-tot-list").append(option);
- }
- }
- }
- if ( 'nsds5replicastripattrs' in agmt_obj['attrs'] ){
- strip_attrs = agmt_obj['attrs']['nsds5replicastripattrs'][0];
- strip_attrs = strip_attrs.split(" ");
- for(var i = 0; i < strip_attrs.length; i++) {
- var opt = strip_attrs[i];
- if (opt != "") {
- var option = $('<option></option>').attr("value", opt).text(opt);
- $("#frac-strip-list").append(option);
- }
- }
- }
-
- // Set schedule
- if ( 'nsds5replicaupdateschedule' in agmt_obj['attrs'] ){
- var val = agmt_obj['attrs']['nsds5replicaupdateschedule'][0];
- var parts = val.split(" ");
- var days = parts[1];
- var times = parts[0].split("-");
- var start_time = times[0].substring(0,2) + ":" + times[0].substring(2,4);
- var end_time = times[1].substring(0,2) + ":" + times[1].substring(2,4);
-
- $("#agmt-schedule-checkbox").prop('checked', false);
- $('#agmt-schedule-panel *').attr('disabled', false);
- $("#schedule-settings").show();
-
- $("#agmt-start-time").val(start_time);
- $("#agmt-end-time").val(end_time);
- if ( days.indexOf('0') != -1){ // Sunday
- $("#schedule-sun").prop('checked', true);
- }
- if ( days.indexOf('1') != -1){ // Monday
- $("#schedule-mon").prop('checked', true);
- }
- if ( days.indexOf('2') != -1){ // Tuesday
- $("#schedule-tue").prop('checked', true);
- }
- if ( days.indexOf('3') != -1){ // Wednesday
- $("#schedule-wed").prop('checked', true);
- }
- if ( days.indexOf('4') != -1){ // Thursday
- $("#schedule-thu").prop('checked', true);
- }
- if ( days.indexOf('5') != -1){ // Friday
- $("#schedule-fri").prop('checked', true);
- }
- if ( days.indexOf('6') != -1){ // Saturday
- $("#schedule-sat").prop('checked', true);
- }
- }
- // Finally Open form
- $("#agmt-form").modal('toggle');
- }).fail(function(data) {
- popup_err("Failed to get replication agreement entry", data.message);
- });
- });
-
- /*
- * Edit Winsync Agreement
- */
- $(document).on('click', '.winsync-agmt-edit-btn', function(e) {
- e.preventDefault();
- clear_winsync_agmt_wizard();
- var suffix = $("#select-repl-winsync-suffix").val();
- var data = repl_winsync_agmt_table.row( $(this).parents('tr') ).data();
- var edit_agmt_name = data[0];
-
- // Get agreement from DS and populate form
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'get', '--suffix=' + suffix, '"' + edit_agmt_name + '"'];
- log_cmd('.winsync-agmt-edit-btn (click)', 'Get replication winsync agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- var agmt_obj = JSON.parse(data);
-
- // Set agreement form values
- $("#winsync-agmt-wizard-title").html("<b>Edit Winsync Agreement</b>");
- // Hide init dropdown
- $("#winsync-init-chbx").hide();
- $("#winsync-agmt-cn").val(edit_agmt_name);
- for (var attr in agmt_obj['attrs']) {
- var val = agmt_obj['attrs'][attr][0];
- attr = attr.toLowerCase();
- $("#winsync-" + attr).val(val);
- if (val == "on") {
- $("#winsync-" + attr + "-checkbox").prop('checked', true);
- } else if (val == "off") {
- $("#winsync-" + attr + "-checkbox").prop('checked', false);
- }
- repl_winsync_agmt_values[attr] = val;
- }
-
- // Fill Password Confirm Input
- if ( 'nsds5replicacredentials' in agmt_obj['attrs'] ){
- $("#winsync-nsds5replicacredentials-confirm").val(agmt_obj['attrs']["nsds5replicacredentials"][0]);
- }
-
- // Transport info
- val = agmt_obj['attrs']["nsds5replicatransportinfo"][0].toLowerCase();
- if (val == "ldap"){
- $("#winsync-nsds5replicatransportinfo").val("LDAP");
- } else if (val == "ldaps"){
- $("#winsync-nsds5replicatransportinfo").val("LDAPS");
- } else if (val == "starttls" || val == "tls"){
- $("#winsync-nsds5replicatransportinfo").val("StartTLS");
- }
-
- // Finally open the form
- $("#winsync-agmt-form").modal('toggle');
- }).fail(function(data) {
- popup_err("Failed to load replication winsync agreement entry", data.message);
- });
- });
-
-
- // Handle disabling/enabling of agmt schedule panel
- $('#agmt-schedule-panel *').attr('disabled', true); /// Disabled by default
- $("#agmt-schedule-checkbox").change(function() {
- if(this.checked) {
- $('#agmt-schedule-panel *').attr('disabled', true);
- $("#schedule-settings").hide();
- } else {
- $('#agmt-schedule-panel *').attr('disabled', false);
- $("#schedule-settings").show();
- }
- });
-
- // Based on the connection type change the agmt-auth options
- $("#nsds5replicatransportinfo").change(function() {
- var ldap_opts = {"Simple": "Simple",
- "SASL/DIGEST-MD5": "SASL/DIGEST-MD5",
- "SASL/GSSAPI": "SASL/GSSAPI"};
- var ldaps_opts = {"Simple": "Simple",
- "SSL Client Authentication": "SSL Client Authentication",
- "SASL/DIGEST-MD5": "SASL/DIGEST-MD5"};
- var $auth = $("#nsds5replicabindmethod");
- $auth.empty();
- var conn = $('#nsds5replicatransportinfo').val();
- if (conn == "LDAP"){
- $.each(ldap_opts, function(key, value) {
- $auth.append($("<option></option>").attr("value", value).text(key));
- });
- } else {
- // TLS options
- $.each(ldaps_opts, function(key, value) {
- $auth.append($("<option></option>").attr("value", value).text(key));
- });
- }
- $("#nsds5replicabinddn").prop('disabled', false);
- $("#nsds5replicacredentials").prop('disabled', false);
- $("#nsds5replicacredentials-confirm").prop('disabled', false);
- });
-
- // Check for auth changes and disable/enable bind DN & password
- $("#nsds5replicabindmethod").change(function() {
- var authtype = $('#nsds5replicabindmethod').val();
- if (authtype == "SSL Client Authentication") {
- $("#nsds5replicabinddn").prop('disabled', true);
- $("#nsds5replicacredentials").prop('disabled', true);
- $("#nsds5replicacredentials-confirm").prop('disabled', true);
- } else {
- $("#nsds5replicabinddn").prop('disabled', false);
- $("#nsds5replicacredentials").prop('disabled', false);
- $("#nsds5replicacredentials-confirm").prop('disabled', false);
- }
- });
-
- // Create time picker for agmt schedule (start end times))
- $('input.timepicker').timepicker({
- 'timeFormat': 'H:i',
- 'step': 15
- });
-
- // Accordion opening/closings
-
- $(".ds-accordion-panel").css('display','none');
-
- $("#repl-config-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Advanced Settings ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Advanced Settings ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- $("#frac-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Fractional Settings ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Fractional Settings ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- $("#schedule-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Schedule Settings ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Schedule Settings ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- /*
- * Handle the repl agmt wizard select lists
- */
-
- /*
- * Set the "select"'s list-id in a hidden field on the select attribute form
- * so we know what list to update after the selection
- */
-
- $(".ds-fractional-btn").on('click', function() {
- // reset the list
- $("#select-attr-list").prop('selectedIndex',-1);
- });
- $("#frac-list-add-btn").on('click', function () {
- $("#attr-form-id").val("frac-exclude-list");
- });
- $("#frac-total-list-add-btn").on('click', function () {
- $("#attr-form-id").val("frac-exclude-tot-list");
- });
- $("#frac-strip-list-add-btn").on('click', function () {
- $("#attr-form-id").val("frac-strip-list");
- });
-
- // Handle the attribute removal from the lists
- $("#frac-list-remove-btn").on("click", function () {
- $("#frac-exclude-list").find('option:selected').remove();
- });
- $("#frac-total-list-remove-btn").on("click", function () {
- $("#frac-exclude-tot-list").find('option:selected').remove();
- });
- $("#frac-strip-list-remove-btn").on("click", function () {
- $("#frac-strip-list").find('option:selected').remove();
- });
-
- // Update agmt form attribute selection lists
- $("#select-attr-save").on("click", function () {
- // Get the id from the hidden input filed and append the attribute to it
- var list_id = $("#attr-form-id").val();
- var add_attrs = $("#select-attr-list").find('option:selected');
- if (add_attrs && add_attrs != '' && add_attrs.length > 0) {
- for (var i = 0; i < add_attrs.length; i++) {
- if ( $('#' + list_id + ' option[value="' + add_attrs[i].text + '"]').val() === undefined) {
- $('#' + list_id).append($("<option/>").val(add_attrs[i].text).text(add_attrs[i].text));
- }
- }
- sort_list( $("#" + list_id) );
- }
- $("#select-attr-form").modal('toggle');
- });
-
-
- /*
- * Modals
- */
-
- // Winsync-agmt Agreement Wizard
-
- $("#winsync-create-agmt").on("click", function() {
- clear_winsync_agmt_wizard(); // TODO
- });
-
- $("#create-agmt").on("click", function() {
- clear_agmt_wizard();
- });
-
- $("#winsync-agmt-save").on("click", function() {
- var suffix = $("#select-repl-winsync-suffix").val();
- var cmd = [];
- var cmd_args = [];
- var param_err = false;
- var editing = false;
- var init_replica = false;
-
- // Check passwords match:
- var agmt_passwd = $("#winsync-nsds5replicacredentials").val();
- var passwd_confirm = $("#winsync-nsds5replicacredentials-confirm").val();
- if (agmt_passwd != passwd_confirm) {
- popup_msg("Attention!", "Passwords do not match!");
- return;
- }
- // Get form values
- var repl_root = $("#select-repl-winsync-suffix").val();
- var agmt_name = $("#winsync-agmt-cn").val();
- var win_domain = $("#winsync-nsds7windowsdomain").val();
- var agmt_host = $("#winsync-nsds5replicahost").val();
- var agmt_port = $("#winsync-nsds5replicaport").val();
- var win_subtree = $("#winsync-nsds7windowsreplicasubtree").val();
- var ds_subtree = $("#winsync-nsds7directoryreplicasubtree").val();
- var bind_dn = $("#winsync-nsds5replicabinddn").val();
- var bind_pw = $("#winsync-nsds5replicacredentials").val();
- var agmt_conn = $("#winsync-nsds5replicatransportinfo").val();
- var sync_new_users = "off";
- var sync_new_groups = "off";
- if ( $("#winsync-nsds7newwinusersyncenabled-checkbox").is(":checked") ){
- sync_new_users = "on";
- }
- if ( $("#winsync-nsds7newwingroupsyncenabled-checkbox").is(":checked") ){
- sync_new_groups = "on"
- }
- if ($("#winsync-agmt-wizard-title").text().includes('Edit') ) {
- editing = true;
- }
-
- // Check required settings
- if (bind_pw == "") {
- $("#winsync-nsds5replicacredentials").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds5replicacredentials").css("border-color", "initial");
- cmd_args.push("--bind-passwd=" + bind_pw);
- }
- if ( ds_subtree == "") {
- $("#winsync-nsds7directoryreplicasubtree").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds7directoryreplicasubtree").css("border-color", "initial");
- cmd_args.push("--ds-subtree=" + ds_subtree);
- }
- if ( win_subtree == "") {
- $("#winsync-nsds7windowsreplicasubtree").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds7windowsreplicasubtree").css("border-color", "initial");
- cmd_args.push("--win-subtree=" + win_subtree);
- }
- if ( win_domain == "") {
- $("#winsync-nsds7windowsdomain").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds7windowsdomain").css("border-color", "initial");
- cmd_args.push("--win-domain=" + win_domain);
- }
- if ( agmt_port == "") {
- $("#winsync-nsds5replicaport").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds5replicaport").css("border-color", "initial");
- cmd_args.push("--port=" + agmt_port);
- }
- if ( agmt_host == "") {
- $("#winsync-nsds5replicahost").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds5replicahost").css("border-color", "initial");
- cmd_args.push('--host=' + agmt_host);
- }
- if ( agmt_conn == "") {
- $("#winsync-nsds5replicatransportinfo").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds5replicatransportinfo").css("border-color", "initial");
- cmd_args.push('--conn-protocol=' + agmt_conn);
- }
- if ( bind_dn == "") {
- $("#winsync-nsds5replicabinddn").css("border-color", "red");
- param_err = true;
- } else {
- $("#winsync-nsds5replicabinddn").css("border-color", "initial");
- cmd_args.push('--bind-dn=' + bind_dn);
- }
- if (param_err ){
- popup_msg("Error", "Missing required parameters");
- return;
- }
-
- // Checkboxes
- if ( ($("#winsync-nsds7newwinusersyncenabled-checkbox").is(":checked")) ){
- sync_new_users = "on";
- }
- if ( ($("#winsync-nsds7newwingroupsyncenabled-checkbox").is(":checked")) ){
- sync_groups_users = "on";
- }
- if ( ($("#winsync-init-chbx").is(":checked")) ){
- init_replica = true;
- }
- cmd_args.push('--sync-users=' + sync_new_users);
- cmd_args.push('--sync-groups=' + sync_new_groups);
-
- // Create winsync agreement in DS
- if ( editing ) {
- cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'set', '"' + agmt_name + '"', '--suffix=' + suffix ];
- } else {
- cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'create', '"' + agmt_name + '"', '--suffix=' + suffix];
- }
- cmd = cmd.concat(cmd_args);
- log_cmd('#winsync-agmt-save (click)', 'Create/Set replication winsync agreement', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- if (editing){
- popup_success('Successfully edited replication winsync agreement');
- } else {
- popup_success('Successfully created replication winsync agreement');
- }
- if (init_replica) {
- // Launch popup stating initialization has begun
- var init_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'init', '--suffix=' + suffix, '"' + agmt_name + '"' ];
- log_cmd('#winsync-agmt-save (click)', 'Initialize winsync agreement', init_cmd);
- cockpit.spawn(init_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- popup_msg("Agreement Initialization", "The agreement initialization has begun...");
- }).fail(function(data) {
- popup_err("Failed to initialize replication agreement", data.message);
- });
- }
- // Reload table
- get_and_set_repl_winsync_agmts();
- }).fail(function(data) {
- if (editing) {
- popup_err("Failed to edit replication winsync agreement", data.message);
- } else {
- popup_err("Failed to create replication winsync agreement", data.message);
- }
- });
-
- // Reload winsync agmt table
- $("#winsync-agmt-form").modal('toggle');
- });
-
- // Create CleanAllRUV Task - TODO
- $("#create-cleanallruv-btn").on("click", function() {
- clear_cleanallruv_form();
- });
-
- $("#cleanallruv-save").on("click", function() {
- // Do the actual save in DS
- var suffix = $("#cleanallruv-suffix").val();
- var rid = $("#cleanallruv-rid").val();
- var force = false;
- if ( $("#force-clean").is(":checked") ) {
- force = true;
- }
- if (suffix == ""){
- popup_msg("Error", "There is no suffix to run the task on");
- return;
- }
- if (rid == ""){
- popup_msg("Error", "You must enter a Replica ID to clean");
- return;
- }
- $("#cleanallruv-form").modal('toggle');
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-tasks', 'cleanallruv', '--suffix=' + suffix, '--replica-id=' + rid ];
- if (force) {
- cmd.push('--force-cleaning');
- }
- log_cmd('#cleanallruv-save (click)', 'Create CleanAllRUV Task', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- let list_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-tasks', 'list-cleanruv-tasks'];
- log_cmd('#cleanallruv-save (click)', 'List all the CleanAllRUV tasks', list_cmd);
- cockpit.spawn(list_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- repl_clean_table.clear().draw();
- var obj = JSON.parse(data);
- for (var idx in obj['items']) {
- task_attrs = obj['items'][idx]['attrs'];
- var task_create_date = task_attrs['createtimestamp'][0];
- var abort_btn = cleanallruv_action_html;
- if (task_attrs['nstaskstatus'][0].includes('Successfully cleaned rid') ){
- abort_btn = "";
- }
- repl_clean_table.row.add( [
- task_attrs['cn'][0],
- get_date_string(task_create_date),
- suffix,
- task_attrs['replica-id'][0],
- task_attrs['nstaskstatus'][0],
- abort_btn
- ] ).draw( false );
- }
- }).fail( function (data) {
- popup_err("Failed to get CleanAllRUV Tasks", data.message);
- });
- }).fail( function (data) {
- popup_err("Failed to create CleanAllRUV Task", data.message);
- });
- });
-
- $(document).on('click', '.abort_cleanallruv_btn', function(e) {
- e.preventDefault();
- var data = repl_clean_table.row( $(this).parents('tr') ).data();
- var suffix = $("#cleanallruv-suffix").val();
- var task_rid = data[3];
- popup_confirm("Are you sure you want to abort the cleaning task on: <b>" + suffix + "</b> for Replica ID <b>" + task_rid + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-tasks', 'abort-cleanallruv', '--replica-id=' + task_rid, '--suffix=' + suffix];
- log_cmd('.abort_cleanallruv_btn (click)', 'Abort CleanAllRUV task', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
- popup_success("Creating task to abort the CleanAllRUV Task");
- }).fail(function(data) {
- popup_err("Failed to add Abort CleanAllRUV Task", data.message);
- });
- }
- });
- });
-
- $("#refresh-cleanlist-btn").on('click', function () {
- // Refresh the list
- get_and_set_cleanallruv();
- });
- $("#refresh-agmts-btn").on('click', function () {
- // Refresh the list
- get_and_set_repl_agmts();
- });
- $("#refresh-winsync-agmts-btn").on('click', function () {
- // Refresh the list
- get_and_set_repl_winsync_agmts();
- });
-
- /*
- * Add repl manager modal
- */
- $("#add-repl-manager").on("click", function() {
- clear_repl_mgr_form();
- });
-
- $("#add-repl-mgr-save").on("click", function() {
- var suffix = $("#select-repl-cfg-suffix").val();
- var repl_dn = $("#add-repl-mgr-dn").val();
- var repl_pw = $("#add-repl-pw").val();
- var repl_pw_confirm = $("#add-repl-pw-confirm").val();
-
- // Validate
- if (repl_dn == ""){
- popup_msg("Attention!", "Replication Manager DN is required");
- return;
- }
- if (!valid_dn(repl_dn)){
- popup_msg("Attention!", "Invalid DN for Replication Manager");
- return;
- }
- if (repl_pw == ""){
- popup_msg("Attention!", "Replication Manager DN password is required");
- return;
- }
- if (repl_pw != repl_pw_confirm) {
- popup_msg("Attention!", "Passwords do not match");
- $("#add-repl-pw").val("");
- $("#add-repl-pw-confirm").val("");
- return;
- }
-
- // Add manager
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','replication', 'create-manager', '--name=' + repl_dn, '--passwd=' + repl_pw, '--suffix=' + suffix ];
- log_cmd('#add-repl-mgr-save (click)', 'Create replication manager entry and add it to config', cmd);
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- get_and_set_repl_config();
- popup_success("Success created replication manager and added it to the replication configuration");
- $("#add-repl-mgr-form").modal('toggle');
- }).fail( function(err) {
- popup_err("Failed to create replication manager entry", err.message);
- $("#add-repl-mgr-form").modal('toggle');
- });
- });
-
- /* Send update now */
- $(document).on('click', '.agmt-send-updates-btn', function(e) {
- var suffix = $("#select-repl-agmt-suffix").val();
- var data = repl_agmt_table.row( $(this).parents('tr') ).data();
- var update_agmt_name = data[0];
-
- var update_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'poke', update_agmt_name, '--suffix=' + suffix];
- log_cmd('.agmt-send-updates-btn (click)', 'Trigger send updates now (replication)', update_cmd);
- cockpit.spawn(update_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- popup_success("Triggered replication updates");
- }).fail( function(err) {
- popup_err("Failed to send updates", err.message);
- });
- });
-
- /* Send update now (winsync) */
- $(document).on('click', '.winsync-agmt-send-updates-btn', function(e) {
- var suffix = $("#select-repl-winsync-suffix").val();
- var data = repl_winsync_agmt_table.row( $(this).parents('tr') ).data();
- var update_agmt_name = data[0];
-
- var update_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'poke', update_agmt_name, '--suffix=' + suffix];
- log_cmd('.winsync-agmt-send-updates-btn (click)', 'Trigger send updates now (winsync)', update_cmd);
- cockpit.spawn(update_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- popup_success("Kick started replication");
- }).fail( function(err) {
- popup_err("Failed to send updates", err.message);
- });
- });
-
- /*
- * Enable/Disable repl agmt
- */
- $(document).on('click', '.agmt-enable-btn', function(e) {
- var suffix = $("#select-repl-agmt-suffix").val();
- var data = repl_agmt_table.row( $(this).parents('tr') ).data();
- var enable_agmt_name = data[0];
- var agmt_state = data[3]; // 4th column in table
- if (agmt_state.toLowerCase() == "enabled") {
- // We must be trying to disable this agreement - confirm it
- popup_confirm("Are you sure you want to disable replication agreement: <b>" + enable_agmt_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var disable_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'disable', enable_agmt_name, '--suffix=' + suffix];
- log_cmd('.agmt-enable-btn (click)', 'Disable replication agreement', disable_cmd);
- cockpit.spawn(disable_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- get_and_set_repl_agmts();
- popup_success("The replication agreement has been disabled.");
- }).fail( function(err) {
- popup_err("Failed to disable agreement", err.message);
- });
- }
- });
- } else {
- // Enabling agreement - confirm it
- popup_confirm("Are you sure you want to enable replication agreement: <b>" + enable_agmt_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var enable_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-agmt', 'enable', enable_agmt_name, '--suffix=' + suffix];
- log_cmd('.agmt-enable-btn (click)', 'Enable replication agreement', enable_cmd);
- cockpit.spawn(enable_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- get_and_set_repl_agmts();
- popup_success("The replication agreement has been enabled.");
- }).fail( function(err) {
- popup_err("Failed to enable agreement", err.message);
- });
- }
- });
- }
- });
-
- /*
- * Enable/Disable winsync repl agmt
- */
- $(document).on('click', '.winsync-agmt-enable-btn', function(e) {
- var suffix = $("#select-repl-winsync-suffix").val();
- var data = repl_winsync_agmt_table.row( $(this).parents('tr') ).data();
- var enable_agmt_name = data[0];
- var agmt_state = data[3]; // 4th column in table
- if (agmt_state.toLowerCase() == "enabled") {
- // We must be trying to disable this agreement - confirm it
- popup_confirm("Are you sure you want to disable replication agreement: <b>" + enable_agmt_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var disable_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'disable', enable_agmt_name, '--suffix=' + suffix];
- log_cmd('.winsync-agmt-enable-btn (click)', 'Disable winsync agreement', disable_cmd);
- cockpit.spawn(disable_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- get_and_set_repl_winsync_agmts();
- popup_success("The replication agreement has been disabled.");
- }).fail( function(err) {
- popup_err("Failed to disable agreement", err.message);
- });
- }
- });
- } else {
- // Enabling agreement - confirm it
- popup_confirm("Are you sure you want to enable replication agreement: <b>" + enable_agmt_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- var enable_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','repl-winsync-agmt', 'enable', enable_agmt_name, '--suffix=' + suffix];
- log_cmd('.winsync-agmt-enable-btn (click)', 'Enable winsync agreement', disable_cmd);
- cockpit.spawn(enable_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function() {
- get_and_set_repl_winsync_agmts();
- popup_success("The replication agreement has been enabled.");
- }).fail( function(err) {
- popup_err("Failed to enable agreement", err.message);
- });
- }
- });
- }
- });
-
- $("#auth-mgr").click(function() {
- $("#auth-group-div").hide();
- $("#auth-manager-div").show();
- });
- $("#auth-group").click(function() {
- $("#auth-manager-div").hide();
- $("#auth-group-div").show();
- });
-
- // Page is loaded, mark it as so...
- repl_page_loaded = 1;
- });
-});
diff --git a/src/cockpit/389-console/src/replication.jsx b/src/cockpit/389-console/src/replication.jsx
new file mode 100644
index 0000000..7663129
--- /dev/null
+++ b/src/cockpit/389-console/src/replication.jsx
@@ -0,0 +1,1148 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "./lib/notifications.jsx";
+import { log_cmd } from "./lib/tools.jsx";
+import { ReplSuffix } from "./lib/replication/replSuffix.jsx";
+import { Changelog } from "./lib/replication/replChangelog.jsx";
+import { TreeView, Spinner } from "patternfly-react";
+import PropTypes from "prop-types";
+import "./css/ds.css";
+
+const treeViewContainerStyles = {
+ width: '295px',
+};
+
+export class Replication extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ notifications: [],
+ errObj: {},
+ nodes: [],
+ node_name: "",
+ node_text: "",
+ node_type: "",
+ node_replicated: false,
+ disableTree: true,
+
+ // Changelog
+ clLoading: false,
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+
+ // Suffix
+ suffixLoading: false,
+ attributes: [],
+ role: "",
+ rid: "0",
+ bindDNs: [],
+ bindDNGroup: "",
+ agmtRows: [],
+ winsyncRows: [],
+ ruvRows: [],
+ suffixSpinning: false,
+ disabled: false,
+ suffixKey: 0,
+
+ showDisableConfirm: false,
+ loaded: false,
+ };
+
+ // General
+ this.selectNode = this.selectNode.bind(this);
+ this.removeNotification = this.removeNotification.bind(this);
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.disableTree = this.disableTree.bind(this);
+ this.enableTree = this.enableTree.bind(this);
+
+ this.reloadConfig = this.reloadConfig.bind(this);
+ this.reloadAgmts = this.reloadAgmts.bind(this);
+ this.reloadWinsyncAgmts = this.reloadWinsyncAgmts.bind(this);
+ this.reloadRUV = this.reloadRUV.bind(this);
+ this.loadAttrs = this.loadAttrs.bind(this);
+ this.loadReplication = this.loadReplication.bind(this);
+ this.loadReplSuffix = this.loadReplSuffix.bind(this);
+ this.reloadChangelog = this.reloadChangelog.bind(this);
+ this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ }
+
+ componentWillMount () {
+ this.loadReplication();
+ if (!this.state.loaded) {
+ this.loadAttrs();
+ }
+ }
+
+ componentDidMount() {
+ // this.loadSuffixTree(false);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.serverId !== prevProps.serverId) {
+ this.loadSuffixTree(true);
+ }
+ }
+
+ loadReplication () {
+ // Load the changelog, and build the suffix tree
+ this.setState({
+ loaded: false
+ });
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket', 'replication', 'get-changelog'];
+ log_cmd("loadReplication", "Load the replication info", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let clDir = "";
+ let clMaxEntries = "";
+ let clMaxAge = "";
+ let clCompactInt = "";
+ let clTrimInt = "";
+ let clEncrypt = false;
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "nsslapd-changelogdir") {
+ clDir = val;
+ }
+ if (attr == "nsslapd-changelogmaxentries") {
+ clMaxEntries = val;
+ }
+ if (attr == "nsslapd-changelogmaxage") {
+ clMaxAge = val;
+ }
+ if (attr == "nsslapd-changelogcompactdb-interval") {
+ clCompactInt = val;
+ }
+ if (attr == "nsslapd-changelogtrim-interval") {
+ clTrimInt = val;
+ }
+ if (attr == "nsslapd-encryptionalgorithm") {
+ clEncrypt = true;
+ }
+ }
+ this.setState({
+ clDir: clDir,
+ clMaxEntries: clMaxEntries,
+ clMaxAge: clMaxAge,
+ clCompactInt: clCompactInt,
+ clTrimInt: clTrimInt,
+ clEncrypt: clEncrypt,
+ }, this.loadSuffixTree(true));
+ })
+ .fail(() => {
+ this.setState({
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+ }, this.loadSuffixTree(true));
+ });
+ }
+
+ reloadChangelog () {
+ // Refresh the changelog
+ this.setState({
+ clLoading: true
+ });
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket', 'replication', 'get-changelog'];
+ log_cmd("reloadChangelog", "Reload the changelog", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let clDir = "";
+ let clMaxEntries = "";
+ let clMaxAge = "";
+ let clCompactInt = "";
+ let clTrimInt = "";
+ let clEncrypt = false;
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "nsslapd-changelogdir") {
+ clDir = val;
+ }
+ if (attr == "nsslapd-changelogmaxentries") {
+ clMaxEntries = val;
+ }
+ if (attr == "nsslapd-changelogmaxage") {
+ clMaxAge = val;
+ }
+ if (attr == "nsslapd-changelogcompactdb-interval") {
+ clCompactInt = val;
+ }
+ if (attr == "nsslapd-changelogtrim-interval") {
+ clTrimInt = val;
+ }
+ if (attr == "nsslapd-encryptionalgorithm") {
+ clEncrypt = true;
+ }
+ }
+ this.setState({
+ clDir: clDir,
+ clMaxEntries: clMaxEntries,
+ clMaxAge: clMaxAge,
+ clCompactInt: clCompactInt,
+ clTrimInt: clTrimInt,
+ clEncrypt: clEncrypt,
+ clLoading: false
+ });
+ })
+ .fail(() => {
+ this.setState({
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+ clLoading: false
+ });
+ });
+ }
+
+ processBranch(treeBranch) {
+ if (treeBranch.length == 0) {
+ return;
+ }
+ for (let sub in treeBranch) {
+ if (!treeBranch[sub].type.endsWith("suffix")) {
+ // Not a suffix, skip it
+ treeBranch.splice(sub, 1);
+ continue;
+ } else if (treeBranch[sub].replicated) {
+ treeBranch[sub].icon = "fa fa-clone";
+ treeBranch[sub].replicated = true;
+ }
+ this.processBranch(treeBranch[sub].nodes);
+ }
+ }
+
+ loadSuffixTree(fullReset) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "get-tree",
+ ];
+ log_cmd("loadSuffixTree", "Start building the suffix tree", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let treeData = [];
+ if (content != "") {
+ treeData = JSON.parse(content);
+ }
+ let basicData = [
+ {
+ text: "Replication Changelog",
+ selectable: true,
+ selected: false,
+ icon: "pficon-catalog",
+ id: "changelog",
+
+ },
+ {
+ text: "Suffixes",
+ icon: "pficon-topology",
+ state: {"expanded": true},
+ selectable: false,
+ id: "repl-suffixes",
+ nodes: []
+ }
+ ];
+ let current_node = this.state.node_name;
+ let current_type = this.state.node_type;
+ let replicated = this.state.node_replicated;
+ if (fullReset && treeData.length > 0) {
+ let found = false;
+ for (let i = 0; i < treeData.length; i++) {
+ if (treeData[i].replicated) {
+ treeData[i].icon = "fa fa-clone";
+ replicated = true;
+ if (!found) {
+ // Load the first replicated suffix we find
+ treeData[i].selected = true;
+ current_node = treeData[i].id;
+ current_type = treeData[i].type;
+ this.loadReplSuffix(treeData[i].id);
+ found = true;
+ }
+ }
+ this.processBranch(treeData[i].nodes);
+ }
+ if (!found) {
+ // No replicated suffixes, load the first one
+ treeData[0].selected = true;
+ current_node = treeData[0].id;
+ current_type = treeData[0].type;
+ this.loadReplSuffix(treeData[0].id);
+ }
+ } else if (treeData.length > 0) {
+ // Reset current suffix
+ for (let suffix of treeData) {
+ this.processBranch(suffix.nodes);
+ if (suffix.id == current_node) {
+ suffix.selected = true;
+ replicated = suffix.replicated;
+ }
+ if (suffix.replicated) {
+ suffix.icon = "fa fa-clone";
+ }
+ }
+ this.loadReplSuffix(current_node);
+ }
+ basicData[1].nodes = treeData;
+ this.setState(() => ({
+ nodes: basicData,
+ node_name: current_node,
+ node_type: current_type,
+ node_replicated: replicated,
+ }), this.update_tree_nodes);
+ });
+ }
+
+ selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
+
+ this.setState({
+ disableTree: true // Disable the tree to allow node to be fully loaded
+ });
+
+ if (selectedNode.id == "changelog") {
+ // Nothing special to do, these configurations have already been loaded
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ node_type: selectedNode.id,
+ };
+ });
+ } else {
+ if (selectedNode.id in this.state) {
+ // This suffix is already cached, just use what we have...
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ node_type: selectedNode.type,
+ node_replicated: selectedNode.replicated,
+ disableTree: false,
+ suffixKey: new Date(),
+ };
+ });
+ } else {
+ // Suffix/subsuffix
+ this.loadReplSuffix(selectedNode.id);
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ node_type: selectedNode.type,
+ node_replicated: selectedNode.replicated,
+ suffixKey: new Date(),
+ };
+ });
+ }
+ }
+ }
+
+ nodeSelector(nodes, targetNode) {
+ return nodes.map(node => {
+ if (node.nodes) {
+ return {
+ ...node,
+ nodes: this.nodeSelector(node.nodes, targetNode),
+ selected: node.id === targetNode.id ? !node.selected : false
+ };
+ } else if (node.id === targetNode.id) {
+ return { ...node, selected: !node.selected };
+ } else if (node.id !== targetNode.id && node.selected) {
+ return { ...node, selected: false };
+ } else {
+ return node;
+ }
+ });
+ }
+
+ addNotification(type, message, timerdelay, persistent) {
+ this.setState(prevState => ({
+ notifications: [
+ ...prevState.notifications,
+ {
+ key: prevState.notifications.length + 1,
+ type: type,
+ persistent: persistent,
+ timerdelay: timerdelay,
+ message: message,
+ }
+ ]
+ }));
+ }
+
+ removeNotification(notificationToRemove) {
+ this.setState({
+ notifications: this.state.notifications.filter(
+ notification => notificationToRemove.key !== notification.key
+ )
+ });
+ }
+
+ update_tree_nodes() {
+ // Set title to the text value of each suffix node. We need to do this
+ // so we can read long suffixes in the UI tree div. This is the last
+ // step of loading the page, so mark it loaded!
+ let elements = document.getElementsByClassName('treeitem-row');
+ for (let el of elements) {
+ el.setAttribute('title', el.innerText);
+ }
+ this.setState({
+ loaded: true
+ });
+ }
+
+ handleChange(e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ closeSuffixModal() {
+ this.setState({
+ showSuffixModal: false
+ });
+ }
+
+ reloadAgmts (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "repl-agmt", "list", "--suffix", suffix
+ ];
+ log_cmd("reloadAgmts", "get repl agreements", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let rows = [];
+ for (let idx in obj['items']) {
+ let agmt_attrs = obj['items'][idx]['attrs'];
+ let state = "Enabled";
+ let update_status = "";
+ let agmt_init_status = "";
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in agmt_attrs) {
+ if (agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ // Check for status msgs
+ if ('nsds5replicalastupdatestatus' in agmt_attrs) {
+ update_status = agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+ if ('nsds5replicalastinitstatus' in agmt_attrs &&
+ agmt_attrs['nsds5replicalastinitstatus'][0] != "") {
+ agmt_init_status = agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (agmt_init_status == "Error (0) Total update in progress" ||
+ agmt_init_status == "Error (0)") {
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ } else if (agmt_init_status == "Error (0) Total update succeeded") {
+ agmt_init_status = "Initialized";
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ agmt_init_status = <td key={agmt_attrs['cn']}>{agmt_init_status}</td>;
+ }
+ } else if (agmt_attrs['nsds5replicalastinitstart'][0] == "19700101000000Z") {
+ agmt_init_status = "Not initialized";
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Not Initialized</i></td>;
+ } else if ('nsds5beginreplicarefresh' in agmt_attrs) {
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ }
+
+ // Update table
+ rows.push({
+ 'name': agmt_attrs['cn'],
+ 'host': agmt_attrs['nsds5replicahost'],
+ 'port': agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus': [agmt_init_status]
+ });
+ }
+
+ // Set agmt
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ agmtRows: rows,
+ },
+ suffixSpinning: false,
+ disabled: false,
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixSpinning: false,
+ disabled: false,
+ });
+ });
+ }
+
+ reloadWinsyncAgmts (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "repl-winsync-agmt", "list", "--suffix", suffix
+ ];
+ log_cmd("reloadWinsyncAgmts", "Get Winsync Agreements", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let ws_rows = [];
+ for (var idx in obj['items']) {
+ let state = "Enabled";
+ let update_status = "";
+ let ws_agmt_init_status = "Initialized";
+ let agmt_attrs = obj['items'][idx]['attrs'];
+ // let agmt_name = agmt_attrs['cn'][0];
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in agmt_attrs) {
+ if (agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ if ('nsds5replicalastupdatestatus' in agmt_attrs) {
+ update_status = agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+
+ if ('nsds5replicalastinitstatus' in agmt_attrs &&
+ agmt_attrs['nsds5replicalastinitstatus'][0] != "") {
+ ws_agmt_init_status = agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (ws_agmt_init_status == "Error (0) Total update in progress" ||
+ ws_agmt_init_status == "Error (0)") {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ } else if (ws_agmt_init_status == "Error (0) Total update succeeded") {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}>{ws_agmt_init_status}</td>;
+ }
+ } else if ('nsds5replicalastinitstart' in agmt_attrs && agmt_attrs['nsds5replicalastinitstart'][0] == "19700101000000Z") {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Not initialized</i></td>;
+ } else if ('nsds5beginreplicarefresh' in agmt_attrs) {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ }
+
+ // Update table
+ ws_rows.push({
+ 'name': agmt_attrs['cn'],
+ 'host': agmt_attrs['nsds5replicahost'],
+ 'port': agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus': [ws_agmt_init_status]
+ });
+ }
+ // Set winsync agmts
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ winsyncRows: ws_rows,
+ },
+ suffixSpinning: false,
+ disabled: false,
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixSpinning: false,
+ disabled: false,
+ });
+ });
+ }
+
+ reloadConfig (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "replication", "get", "--suffix", suffix
+ ];
+ log_cmd("reloadConfig", "Reload suffix repl config", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let current_role = "";
+ let nsds5replicaprecisetombstonepurging = false;
+ if ('nsds5replicaprecisetombstonepurging' in config['attrs']) {
+ if (config['attrs']['nsds5replicaprecisetombstonepurging'][0].toLowerCase() == "on") {
+ nsds5replicaprecisetombstonepurging = true;
+ }
+ }
+ // Set the replica role
+ if (config['attrs']['nsds5replicatype'][0] == "3") {
+ current_role = "Master";
+ } else {
+ if (config['attrs']['nsds5flags'][0] == "1") {
+ current_role = "Hub";
+ } else {
+ current_role = "Consumer";
+ }
+ }
+
+ this.setState({
+ [suffix]: {
+ role: current_role,
+ nsds5flags: config['attrs']['nsds5flags'][0],
+ nsds5replicatype: config['attrs']['nsds5replicatype'][0],
+ nsds5replicaid: 'nsds5replicaid' in config['attrs'] ? config['attrs']['nsds5replicaid'][0] : "",
+ nsds5replicabinddn: 'nsds5replicabinddn' in config['attrs'] ? config['attrs']['nsds5replicabinddn'] : "",
+ nsds5replicabinddngroup: 'nsds5replicabinddngroup' in config['attrs'] ? config['attrs']['nsds5replicabinddngroup'][0] : "",
+ nsds5replicabinddngroupcheckinterval: 'nsds5replicabinddngroupcheckinterval' in config['attrs'] ? config['attrs']['nsds5replicabinddngroupcheckinterval'][0] : "",
+ nsds5replicareleasetimeout: 'nsds5replicareleasetimeout' in config['attrs'] ? config['attrs']['nsds5replicareleasetimeout'][0] : "",
+ nsds5replicapurgedelay: 'nsds5replicapurgedelay' in config['attrs'] ? config['attrs']['nsds5replicapurgedelay'][0] : "",
+ nsds5replicatombstonepurgeinterval: 'nsds5replicatombstonepurgeinterval' in config['attrs'] ? config['attrs']['nsds5replicatombstonepurgeinterval'][0] : "",
+ nsds5replicaprecisetombstonepurging: nsds5replicaprecisetombstonepurging,
+ nsds5replicaprotocoltimeout: 'nsds5replicaprotocoltimeout' in config['attrs'] ? config['attrs']['nsds5replicaprotocoltimeout'][0] : "",
+ nsds5replicabackoffmin: 'nsds5replicabackoffmin' in config['attrs'] ? config['attrs']['nsds5replicabackoffmin'][0] : "",
+ nsds5replicabackoffmax: 'nsds5replicabackoffmax' in config['attrs'] ? config['attrs']['nsds5replicabackoffmax'][0] : "",
+ },
+ suffixSpinning: false,
+ disabled: false,
+ suffixKey: new Date(),
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixSpinning: false,
+ disabled: false,
+ });
+ });
+ }
+
+ reloadRUV (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ // Load suffix RUV
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'get-ruv', '--suffix=' + suffix];
+ log_cmd('reloadRUV', 'Get the suffix RUV', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let ruvs = JSON.parse(content);
+ let ruv_rows = [];
+ for (let idx in ruvs['items']) {
+ let ruv = ruvs['items'][idx];
+ // Update table
+ ruv_rows.push({
+ 'rid': ruv['rid'],
+ 'url': ruv['url'],
+ 'csn': ruv['csn'],
+ 'raw_csn': ruv['raw_csn'],
+ 'maxcsn': ruv['maxcsn'],
+ 'raw_maxcsn': ruv['raw_maxcsn'],
+ });
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ ruvRows: ruv_rows,
+ },
+ suffixSpinning: false,
+ disabled: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (errMsg.desc != "No such object") {
+ this.addNotification(
+ "error",
+ `Error loading suffix RUV - ${errMsg.desc}`
+ );
+ }
+ this.setState({
+ suffixSpinning: false,
+ disabled: false
+ });
+ });
+ }
+
+ loadReplSuffix(suffix) {
+ // Load everything, we must nest cockpit promise so we can proper set
+ // the loading is finished.
+ // - Get Suffix config
+ // - Get Repl agmts
+ // - Get Winsync Agmts
+ // - Get RUV's
+ this.setState({
+ activeKey: 1,
+ suffixLoading: true,
+ [suffix]: {},
+ });
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "replication", "get", "--suffix", suffix
+ ];
+ log_cmd("loadReplSuffix", "Load suffix repl config", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let current_role = "";
+ let nsds5replicaprecisetombstonepurging = false;
+ if ('nsds5replicaprecisetombstonepurging' in config['attrs']) {
+ if (config['attrs']['nsds5replicaprecisetombstonepurging'][0].toLowerCase() == "on") {
+ nsds5replicaprecisetombstonepurging = true;
+ }
+ }
+ // Set the replica role
+ if (config['attrs']['nsds5replicatype'][0] == "3") {
+ current_role = "Master";
+ } else {
+ if (config['attrs']['nsds5flags'][0] == "1") {
+ current_role = "Hub";
+ } else {
+ current_role = "Consumer";
+ }
+ }
+
+ this.setState({
+ [suffix]: {
+ role: current_role,
+ nsds5flags: config['attrs']['nsds5flags'][0],
+ nsds5replicatype: config['attrs']['nsds5replicatype'][0],
+ nsds5replicaid: 'nsds5replicaid' in config['attrs'] ? config['attrs']['nsds5replicaid'][0] : "",
+ nsds5replicabinddn: 'nsds5replicabinddn' in config['attrs'] ? config['attrs']['nsds5replicabinddn'] : "",
+ nsds5replicabinddngroup: 'nsds5replicabinddngroup' in config['attrs'] ? config['attrs']['nsds5replicabinddngroup'][0] : "",
+ nsds5replicabinddngroupcheckinterval: 'nsds5replicabinddngroupcheckinterval' in config['attrs'] ? config['attrs']['nsds5replicabinddngroupcheckinterval'][0] : "",
+ nsds5replicareleasetimeout: 'nsds5replicareleasetimeout' in config['attrs'] ? config['attrs']['nsds5replicareleasetimeout'][0] : "",
+ nsds5replicapurgedelay: 'nsds5replicapurgedelay' in config['attrs'] ? config['attrs']['nsds5replicapurgedelay'][0] : "",
+ nsds5replicatombstonepurgeinterval: 'nsds5replicatombstonepurgeinterval' in config['attrs'] ? config['attrs']['nsds5replicatombstonepurgeinterval'][0] : "",
+ nsds5replicaprecisetombstonepurging: nsds5replicaprecisetombstonepurging,
+ nsds5replicaprotocoltimeout: 'nsds5replicaprotocoltimeout' in config['attrs'] ? config['attrs']['nsds5replicaprotocoltimeout'][0] : "",
+ nsds5replicabackoffmin: 'nsds5replicabackoffmin' in config['attrs'] ? config['attrs']['nsds5replicabackoffmin'][0] : "",
+ nsds5replicabackoffmax: 'nsds5replicabackoffmax' in config['attrs'] ? config['attrs']['nsds5replicabackoffmax'][0] : "",
+ }
+ });
+
+ // Now load agmts, then the winsync agreement, and finally the RUV
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "repl-agmt", "list", "--suffix", suffix
+ ];
+ log_cmd("loadReplSuffix", "get repl agreements", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let rows = [];
+ for (let idx in obj['items']) {
+ let agmt_attrs = obj['items'][idx]['attrs'];
+ let state = "Enabled";
+ let update_status = "";
+ let agmt_init_status = "";
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in agmt_attrs) {
+ if (agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ // Check for status msgs
+ if ('nsds5replicalastupdatestatus' in agmt_attrs) {
+ update_status = agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+ if ('nsds5replicalastinitstatus' in agmt_attrs &&
+ agmt_attrs['nsds5replicalastinitstatus'][0] != "") {
+ agmt_init_status = agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (agmt_init_status == "Error (0) Total update in progress" ||
+ agmt_init_status == "Error (0)") {
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ } else if (agmt_init_status == "Error (0) Total update succeeded") {
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ agmt_init_status = <td key={agmt_attrs['cn']}>{agmt_init_status}</td>;
+ }
+ } else if ('nsds5replicalastinitstart' in agmt_attrs && agmt_attrs['nsds5replicalastinitstart'][0] == "19700101000000Z") {
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Not initialized</i></td>;
+ } else if ('nsds5beginreplicarefresh' in agmt_attrs) {
+ agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ }
+
+ // Update table
+ rows.push({
+ 'name': agmt_attrs['cn'],
+ 'host': agmt_attrs['nsds5replicahost'],
+ 'port': agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus': [agmt_init_status]
+ });
+ }
+
+ // Set agmt
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ agmtRows: rows,
+ }
+ });
+
+ // Load winsync agreements
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "repl-winsync-agmt", "list", "--suffix", suffix
+ ];
+ log_cmd("loadReplSuffix", "Get Winsync Agreements", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let ws_rows = [];
+ for (var idx in obj['items']) {
+ let state = "Enabled";
+ let update_status = "";
+ let ws_agmt_init_status = "Initialized";
+ let agmt_attrs = obj['items'][idx]['attrs'];
+ // let agmt_name = agmt_attrs['cn'][0];
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in agmt_attrs) {
+ if (agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ if ('nsds5replicalastupdatestatus' in agmt_attrs) {
+ update_status = agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+
+ if ('nsds5replicalastinitstatus' in agmt_attrs &&
+ agmt_attrs['nsds5replicalastinitstatus'][0] != "") {
+ ws_agmt_init_status = agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (ws_agmt_init_status == "Error (0) Total update in progress" ||
+ ws_agmt_init_status == "Error (0)") {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ } else if (ws_agmt_init_status == "Error (0) Total update succeeded") {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}>{ws_agmt_init_status}</td>;
+ }
+ } else if ('nsds5replicalastinitstart' in agmt_attrs && agmt_attrs['nsds5replicalastinitstart'][0] == "19700101000000Z") {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Not initialized</i></td>;
+ } else if ('nsds5beginreplicarefresh' in agmt_attrs) {
+ ws_agmt_init_status = <td key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading size="sm" /></td>;
+ }
+
+ // Update table
+ ws_rows.push({
+ 'name': agmt_attrs['cn'],
+ 'host': agmt_attrs['nsds5replicahost'],
+ 'port': agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus': [ws_agmt_init_status]
+ });
+ }
+ // Set winsync agmts
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ winsyncRows: ws_rows,
+ }
+ });
+
+ // Load suffix RUV
+ let cmd = ['dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'get-ruv', '--suffix=' + suffix];
+ log_cmd('loadReplSuffix', 'Get the suffix RUV', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let ruvs = JSON.parse(content);
+ let ruv_rows = [];
+ for (let idx in ruvs['items']) {
+ let ruv = ruvs['items'][idx];
+ // Update table
+ ruv_rows.push({
+ 'rid': ruv['rid'],
+ 'url': ruv['url'],
+ 'csn': ruv['csn'],
+ 'raw_csn': ruv['raw_csn'],
+ 'maxcsn': ruv['maxcsn'],
+ 'raw_maxcsn': ruv['raw_maxcsn'],
+ });
+ }
+
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ ruvRows: ruv_rows,
+ },
+ suffixLoading: false,
+ disableTree: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (errMsg.desc != "No such object") {
+ this.addNotification(
+ "error",
+ `Error loading suffix RUV - ${errMsg.desc}`
+ );
+ }
+ this.setState({
+ suffixLoading: false,
+ disableTree: false
+ });
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading winsync agreements - ${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false,
+ disableTree: false
+ });
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading replication agreements configuration - ${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false,
+ disableTree: false
+ });
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixLoading: false,
+ disableTree: false,
+ node_replicated: false
+ });
+ });
+ }
+
+ loadAttrs() {
+ // Now get the schema that various tabs use
+ const attr_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema", "attributetypes", "list"
+ ];
+ log_cmd("Suffixes", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent['items']) {
+ attrs.push(content.name);
+ }
+ this.setState({
+ attributes: attrs,
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Failed to get attributes - ${errMsg.desc}`
+ );
+ });
+ }
+
+ enableTree () {
+ this.setState({
+ disableTree: false
+ });
+ }
+
+ disableTree () {
+ this.setState({
+ disableTree: true
+ });
+ }
+
+ render() {
+ const { nodes } = this.state;
+ let repl_page = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
+ let repl_element =
+ <h4>There are currently no databases to configure for replication</h4>;
+ if (this.state.loaded) {
+ // We have a suffix, or database link
+ if (this.state.node_type == "suffix" || this.state.node_type == "subsuffix") {
+ if (this.state.suffixLoading) {
+ repl_element =
+ <div className="ds-margin-top ds-loading-spinner ds-center">
+ <h4>Loading replication configuration for <b>{this.state.node_name} ...</b></h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
+ </div>;
+ } else {
+ if (this.state.node_name in this.state) {
+ repl_element =
+ <div>
+ <ReplSuffix
+ serverId={this.props.serverId}
+ suffix={this.state.node_name}
+ role={this.state[this.state.node_name].role}
+ data={this.state[this.state.node_name]}
+ addNotification={this.addNotification}
+ agmtRows={this.state[this.state.node_name].agmtRows}
+ winsyncRows={this.state[this.state.node_name].winsyncRows}
+ ruvRows={this.state[this.state.node_name].ruvRows}
+ reloadAgmts={this.reloadAgmts}
+ reloadWinsyncAgmts={this.reloadWinsyncAgmts}
+ reloadRUV={this.reloadRUV}
+ reloadConfig={this.reloadConfig}
+ reload={this.loadSuffixTree}
+ attrs={this.state.attributes}
+ replicated={this.state.node_replicated}
+ enableTree={this.enableTree}
+ disableTree={this.disableTree}
+ key={this.state.suffixKey}
+ disabled={this.state.disabled}
+ spinning={this.state.suffixSpinning}
+ />
+ </div>;
+ } else {
+ // Suffix is not replicated
+ repl_element =
+ <ReplSuffix
+ serverId={this.props.serverId}
+ suffix={this.state.node_name}
+ role=""
+ data=""
+ addNotification={this.addNotification}
+ disableWSAgmtTable={this.state.disableWSAgmtTable}
+ disableAgmtTable={this.state.disableAgmtTable}
+ reloadAgmts={this.reloadAgmts}
+ reloadWinsyncAgmts={this.reloadWinsyncAgmts}
+ reloadRUV={this.reloadRUV}
+ reloadConfig={this.reloadConfig}
+ reload={this.loadSuffixTree}
+ attrs={this.state.attributes}
+ replicated={this.state.node_replicated}
+ enableTree={this.enableTree}
+ disableTree={this.disableTree}
+ spinning={this.state.suffixSpinning}
+ disabled={this.state.disabled}
+ key={this.state.node_name}
+ />;
+ }
+ }
+ } else if (this.state.node_type == "changelog") {
+ repl_element =
+ <Changelog
+ serverId={this.props.serverId}
+ clDir={this.state.clDir}
+ clMaxEntries={this.state.clMaxEntries}
+ clMaxAge={this.state.clMaxAge}
+ clCompactInt={this.state.clCompactInt}
+ clTrimInt={this.state.clTrimInt}
+ clEncrypt={this.state.clEncrypt}
+ addNotification={this.addNotification}
+ enableTree={this.enableTree}
+ reload={this.reloadChangelog}
+ loading={this.state.clLoading}
+ key={this.state.clDir}
+ />;
+ }
+ repl_page =
+ <div className="container-fluid">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <div className="ds-container">
+ <div>
+ <div className="ds-tree">
+ <div className={disabled} id="repl-tree"
+ style={treeViewContainerStyles}>
+ <TreeView
+ nodes={nodes}
+ highlightOnHover
+ highlightOnSelect
+ selectNode={this.selectNode}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="ds-tree-content">
+ {repl_element}
+ </div>
+ </div>
+ </div>;
+ } else {
+ repl_page =
+ <div className="ds-margin-top ds-loading-spinner ds-center">
+ <h4>Loading Replication Information ...</h4>
+ <Spinner className="ds-margin-top-lg" loading size="md" />
+ </div>;
+ }
+
+ return (
+ <div>
+ {repl_page}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+Replication.propTypes = {
+ serverId: PropTypes.string
+};
+
+Replication.defaultProps = {
+ serverId: ""
+};
+
+export default Replication;
diff --git a/src/cockpit/389-console/src/security.jsx b/src/cockpit/389-console/src/security.jsx
index fd681e2..2e61f52 100644
--- a/src/cockpit/389-console/src/security.jsx
+++ b/src/cockpit/389-console/src/security.jsx
@@ -644,7 +644,7 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="The name, or nickname, of the server certificate inthe NSS datgabase the server should use (nsSSLPersonalitySSL).">
- <Col className="ds-no-padding" sm={3}>
+ <Col sm={3}>
<ControlLabel>Server Certificate Name</ControlLabel>
</Col>
<Col sm={4}>
diff --git a/src/cockpit/389-console/src/servers.html b/src/cockpit/389-console/src/servers.html
index 1d17872..f60bd5d 100644
--- a/src/cockpit/389-console/src/servers.html
+++ b/src/cockpit/389-console/src/servers.html
@@ -64,8 +64,8 @@
</div>
<button class="accordion ds-accordion" id="config-accordion" type="button">► Show Advanced Settings</button>
- <div class="ds-accordion-panel">
- <div class="ds-container">
+ <div class="ds-accordion-panel ds-indent">
+ <div class="ds-container ds-indent">
<div class="ds-inline">
<div>
<input type="checkbox" class="ds-config-checkbox" id="nsslapd-schemacheck" checked><label
diff --git a/src/cockpit/389-console/webpack.config.js b/src/cockpit/389-console/webpack.config.js
index 226fa20..026eb59 100644
--- a/src/cockpit/389-console/webpack.config.js
+++ b/src/cockpit/389-console/webpack.config.js
@@ -30,8 +30,6 @@ var info = {
"fonts",
"images",
"index.html",
- "replication.html",
- "replication.js",
"schema.html",
"schema.js",
"servers.html",
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
index a9ee68c..a0d4597 100644
--- a/src/lib389/lib389/agreement.py
+++ b/src/lib389/lib389/agreement.py
@@ -61,8 +61,8 @@ class Agreement(DSLdapObject):
self.set('nsds5BeginReplicaRefresh', 'start')
def check_reinit(self):
- """Check the status of a reinit. Returns done and error. A correct
- reinit will return (True, False).
+ """Check the status of a reinit. Returns done, inprogress, and error text. A correct
+ reinit will return (True, False, False).
:returns: tuple(done, error), where done, error are bool.
"""
@@ -74,16 +74,16 @@ class Agreement(DSLdapObject):
if not status:
pass
elif 'replica busy' in status:
- error = True
+ error = status
elif 'Total update succeeded' in status:
done = True
inprogress = False
elif 'Replication error' in status:
- error = True
+ error = status
elif 'Total update in progress' in status:
inprogress = True
elif 'LDAP error' in status:
- error = True
+ error = status
return (done, inprogress, error)
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index d2249a4..91b3711 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -23,7 +23,7 @@ from lib389.replica import Replicas
# We need to be a factor to the backend monitor
from lib389.monitor import MonitorBackend
from lib389.index import Index, Indexes, VLVSearches, VLVSearch
-from lib389.tasks import ImportTask, ExportTask, Tasks
+from lib389.tasks import ImportTask, ExportTask, CleanAllRUVTask, Tasks
from lib389.encrypted_attributes import EncryptedAttr, EncryptedAttrs
diff --git a/src/lib389/lib389/chaining.py b/src/lib389/lib389/chaining.py
index 7a0401e..5f6ac68 100644
--- a/src/lib389/lib389/chaining.py
+++ b/src/lib389/lib389/chaining.py
@@ -115,7 +115,7 @@ class ChainingLink(DSLdapObject):
Delete chaining entry
"""
- rdn = self.get_attr_val_utf8_l('cn')
+ rdn = self.get_attr_val_utf8_l('nsslapd-suffix')
try:
mt = self._mts.get(selector=rdn)
mt.delete()
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
index 0b55ba4..93bf044 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -17,6 +17,7 @@ from lib389.configurations.sample import (
from lib389.chaining import (ChainingLinks)
from lib389.index import Index, VLVIndex, VLVSearches
from lib389.monitor import MonitorLDBM
+from lib389.replica import Replicas
from lib389.utils import ensure_str, is_a_dn, is_dn_parent
from lib389._constants import *
from lib389.cli_base import (
@@ -187,7 +188,7 @@ def backend_create(inst, basedn, log, args):
suffix_rdn_attr = args.suffix.split('=')[0].lower()
if suffix_rdn_attr == 'dc':
domain = create_base_domain(inst, args.suffix)
- domain.add('aci', dc-aci)
+ domain.add('aci', dc_aci)
elif suffix_rdn_attr == 'o':
org = create_base_org(inst, args.suffix)
org.add('aci', o_aci)
@@ -281,10 +282,19 @@ def backend_export(inst, basedn, log, args):
def is_db_link(inst, rdn):
links = ChainingLinks(inst).list()
for link in links:
- cn = ensure_str(link.get_attr_val('cn')).lower()
+ cn = link.get_attr_val_utf8('cn').lower()
if cn == rdn.lower():
return True
return False
+
+
+def is_db_replicated(inst, suffix):
+ replicas = Replicas(inst)
+ try:
+ replica = replicas.get(suffix)
+ return True
+ except:
+ return False
def backend_get_subsuffixes(inst, basedn, log, args):
@@ -343,8 +353,6 @@ def build_node(suffix, be_name, subsuf=False, link=False, replicated=False):
if link:
icon = "glyphicon glyphicon-link"
suffix_type = "dblink"
- if replicated:
- suffix_type = "replicated"
return {
"text": suffix,
@@ -352,6 +360,7 @@ def build_node(suffix, be_name, subsuf=False, link=False, replicated=False):
"selectable": True,
"icon": icon,
"type": suffix_type,
+ "replicated": replicated,
"be": be_name,
"nodes": []
}
@@ -372,18 +381,20 @@ def backend_build_tree(inst, be_insts, nodes):
if be_suffix == node_suffix.lower():
# We have our parent, now find the children
mts = be._mts.list()
+
for mt in mts:
- sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
+ sub_parent = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
- if sub == be_suffix:
+ sub_suffix = mt.get_attr_val_utf8_l('cn')
+ if sub_parent == be_suffix:
# We have a subsuffix (maybe a db link?)
- link = False
- if is_db_link(inst, sub_be):
- link = True
- node['nodes'].append(build_node(mt.get_attr_val_utf8_l('cn'),
+ link = is_db_link(inst, sub_be)
+ replicated = is_db_replicated(inst, sub_suffix)
+ node['nodes'].append(build_node(sub_suffix,
sub_be,
subsuf=True,
- link=link))
+ link=link,
+ replicated=replicated))
# Recurse over the new subsuffixes
backend_build_tree(inst, be_insts, node['nodes'])
@@ -415,7 +426,8 @@ def backend_get_tree(inst, basedn, log, args):
sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
if sub is not None:
continue
- nodes.append(build_node(suffix, be_name))
+ replicated = is_db_replicated(inst, suffix)
+ nodes.append(build_node(suffix, be_name, replicated=replicated))
# No suffixes, return empty list
if len(nodes) == 0:
diff --git a/src/lib389/lib389/cli_conf/monitor.py b/src/lib389/lib389/cli_conf/monitor.py
index 53637e1..87eb75f 100644
--- a/src/lib389/lib389/cli_conf/monitor.py
+++ b/src/lib389/lib389/cli_conf/monitor.py
@@ -71,7 +71,7 @@ def create_parser(subparsers):
monitor_parser = subparsers.add_parser('monitor', help="Monitor the state of the instance")
subcommands = monitor_parser.add_subparsers(help='action')
- server_parser = subcommands.add_parser('server', help="Monitor the server statistics, connectinos and operations")
+ server_parser = subcommands.add_parser('server', help="Monitor the server statistics, connections and operations")
server_parser.set_defaults(func=monitor)
ldbm_parser = subcommands.add_parser('ldbm', help="Monitor the ldbm statistics, such as dbcache")
@@ -86,4 +86,4 @@ def create_parser(subparsers):
chaining_parser = subcommands.add_parser('chaining', help="Monitor database chaining statistics")
chaining_parser.add_argument('backend', nargs='?', help="Optional name of the chaining backend to monitor")
- chaining_parser.set_defaults(func=chaining_monitor)
\ No newline at end of file
+ chaining_parser.set_defaults(func=chaining_monitor)
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
index de2099f..58bbd13 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -104,6 +104,27 @@ def _args_to_attrs(args):
#
# Replica config
#
+def get_ruv(inst, basedn, log, args):
+ replicas = Replicas(inst)
+ replica = replicas.get(args.suffix)
+ ruv = replica.get_ruv()
+ ruv_dict = ruv.format_ruv()
+ ruvs = ruv_dict['ruvs']
+ if args and args.json:
+ log.info(json.dumps({"type": "list", "items": ruvs}))
+ else:
+ add_gap = False
+ for ruv in ruvs:
+ if add_gap:
+ log.info("")
+ log.info("RUV: " + ruv['raw_ruv'])
+ log.info("Replica ID: " + ruv['rid'])
+ log.info("LDAP URL: " + ruv['url'])
+ log.info("Min CSN: " + ruv['csn'] + " (" + ruv['raw_csn'] + ")")
+ log.info("Max CSN: " + ruv['maxcsn'] + " (" + ruv['raw_maxcsn'] + ")")
+ add_gap = True
+
+
def enable_replication(inst, basedn, log, args):
repl_root = args.suffix
role = args.role.lower()
@@ -670,7 +691,7 @@ def check_init_agmt(inst, basedn, log, args):
elif inprogress:
status = "Agreement initialization in progress."
elif error:
- status = "Agreement initialization failed."
+ status = "Agreement initialization failed: " + error
if args.json:
log.info(json.dumps(status))
else:
@@ -893,7 +914,6 @@ def get_winsync_agmt_status(inst, basedn, log, args):
status = agmt.status(winsync=True, use_json=args.json)
log.info(status)
-
#
# Tasks
#
@@ -1015,6 +1035,10 @@ def create_parser(subparsers):
repl_disable_parser.set_defaults(func=disable_replication)
repl_disable_parser.add_argument('--suffix', required=True, help='The DN of the suffix to have replication disabled')
+ repl_ruv_parser = repl_subcommands.add_parser('get-ruv', help='Get the database RUV entry for his suffix')
+ repl_ruv_parser.set_defaults(func=get_ruv)
+ repl_ruv_parser.add_argument('--suffix', required=True, help='The DN of the replicated suffix')
+
repl_list_parser = repl_subcommands.add_parser('list', help='List all the replicated suffixes')
repl_list_parser.set_defaults(func=list_suffixes)
diff --git a/src/lib389/lib389/configurations/config_001004000.py b/src/lib389/lib389/configurations/config_001004000.py
index 697a911..dbeab1f 100644
--- a/src/lib389/lib389/configurations/config_001004000.py
+++ b/src/lib389/lib389/configurations/config_001004000.py
@@ -7,16 +7,21 @@
# --- END COPYRIGHT BLOCK ---
from .config import baseconfig, configoperation
-from .sample import sampleentries, create_base_domain
-
+from .sample import (
+ sampleentries,
+ create_base_domain,
+ create_base_org,
+ create_base_orgunit,
+ create_base_cn,
+)
from lib389.idm.organizationalunit import OrganizationalUnits
from lib389.idm.group import Groups
from lib389.idm.posixgroup import PosixGroups
from lib389.idm.user import nsUserAccounts
from lib389.idm.services import ServiceAccounts
-
from lib389.idm.nscontainer import nsHiddenContainers
+
class c001004000_sample_entries(sampleentries):
def __init__(self, instance, basedn):
super(c001004000_sample_entries, self).__init__(instance, basedn)
@@ -24,11 +29,27 @@ class c001004000_sample_entries(sampleentries):
# All checks done, apply!
def _apply(self):
- # Create the base domain object
- domain = create_base_domain(self._instance, self._basedn)
- domain.add('aci', [
+ suffix_rdn_attr = self._basedn.split('=')[0].lower()
+ if suffix_rdn_attr == 'dc':
+ suffix_obj = create_base_domain(self._instance, self._basedn)
+ aci_vals = ['dc', 'domain']
+ elif suffix_rdn_attr == 'o':
+ suffix_obj = create_base_org(self._instance, self._basedn)
+ aci_vals = ['o', 'organization']
+ elif suffix_rdn_attr == 'ou':
+ suffix_obj = create_base_orgunit(self._instance, self._basedn)
+ aci_vals = ['ou', 'organizationalunit']
+ elif suffix_rdn_attr == 'cn':
+ suffix_obj = create_base_cn(self._instance, self._basedn)
+ aci_vals = ['cn', 'nscontainer']
+ else:
+ # Unsupported rdn
+ raise ValueError("Suffix RDN is not supported for creating sample entries. Only 'dc', 'o', 'ou', and 'cn' are supported.")
+
+ # Create the base object
+ suffix_obj.add('aci', [
# Allow reading the base domain object
- '(targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)',
+ '(targetattr="' + aci_vals[0] + ' || description || objectClass")(targetfilter="(objectClass=' + aci_vals[1] + ')")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)',
# Allow reading the ou
'(targetattr="ou || objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compare)(userdn="ldap:///anyone");)'
])
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index 7f2fd5e..7145e86 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -26,7 +26,7 @@ from lib389.passwd import password_generate
from lib389.mappingTree import MappingTrees
from lib389.agreement import Agreements
from lib389.tombstone import Tombstones
-
+from lib389.tasks import CleanAllRUVTask
from lib389.idm.domain import Domain
from lib389.idm.group import Groups
from lib389.idm.services import ServiceAccounts
@@ -897,9 +897,11 @@ class RUV(object):
ruvs.append({"raw_ruv": self._rid_rawruv.get(rid),
"rid": rid,
"url": self._rid_url.get(rid),
- "csn": parse_csn(self._rid_csn.get(rid, '00000000000000000000')),
- "maxcsn": parse_csn(self._rid_maxcsn.get(rid, '00000000000000000000')),
- "modts": parse_csn(self._rid_modts.get(rid, '00000000'))})
+ "csn": RUV().parse_csn(self._rid_csn.get(rid, '00000000000000000000')),
+ "raw_csn": self._rid_csn.get(rid, '00000000000000000000'),
+ "maxcsn": RUV().parse_csn(self._rid_maxcsn.get(rid, '00000000000000000000')),
+ "raw_maxcsn": self._rid_maxcsn.get(rid, '00000000000000000000'),
+ "modts": RUV().parse_csn(self._rid_modts.get(rid, '00000000'))})
result["ruvs"] = ruvs
return result
@@ -1153,13 +1155,27 @@ class Replica(DSLdapObject):
return False
return True
+ def cleanRUV(self, rid):
+ """Run a cleanallruv task, only on a master, after deleting or demoting
+ it. It is okay if it fails.
+ """
+ if rid != '65535':
+ properties = {'replica-base-dn': self.get_attr_val_utf8('nsDS5ReplicaRoot'),
+ 'replica-id': rid,
+ 'replica-force-cleaning': 'yes'}
+ try:
+ clean_task = CleanAllRUVTask(self._instance)
+ clean_task.create(properties=properties)
+ except ldap.LDAPError as e:
+ self._log.debug("Failed to run cleanAllRUV task: " + str(e))
+
def delete(self):
"""Delete a replica related to the provided suffix.
If this replica role was ReplicaRole.HUB or ReplicaRole.MASTER, it
also deletes the changelog associated to that replica. If it
exists some replication agreement below that replica, they are
- deleted.
+ deleted. If this is a master we also clean the database ruv.
:returns: None
:raises: - InvalidArgumentError - if suffix is missing
@@ -1167,6 +1183,7 @@ class Replica(DSLdapObject):
"""
# Delete the agreements
self._delete_agreements()
+
# Delete the replica
return super(Replica, self).delete()
@@ -1265,14 +1282,14 @@ class Replica(DSLdapObject):
elif replicarole == ReplicaRole.CONSUMER and newrole == ReplicaRole.MASTER:
try:
self.replace_many((REPL_TYPE, str(REPLICA_RDWR_TYPE)),
- (REPL_FLAGS, str(REPLICA_FLAGS_WRITE)),
- (REPL_ID, str(rid)))
+ (REPL_FLAGS, str(REPLICA_FLAGS_WRITE)),
+ (REPL_ID, str(rid)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
elif replicarole == ReplicaRole.HUB and newrole == ReplicaRole.MASTER:
try:
self.replace_many((REPL_TYPE, str(REPLICA_RDWR_TYPE)),
- (REPL_ID, str(rid)))
+ (REPL_ID, str(rid)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
@@ -1288,21 +1305,22 @@ class Replica(DSLdapObject):
# Check the role type
replicarole = self.get_role()
+ rid = self.get_attr_val_utf8(REPL_ID)
if newrole.value >= replicarole.value:
raise ValueError('Can not demote replica to higher or the same role: {} -> {}'.format(replicarole.name, newrole.name))
# Demote it - set the replica type, flags and rid
if replicarole == ReplicaRole.MASTER and newrole == ReplicaRole.HUB:
try:
- self.replace_many([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
- (REPL_ID, str(CONSUMER_REPLICAID))])
+ self.replace_many((REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
+ (REPL_ID, str(CONSUMER_REPLICAID)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
elif replicarole == ReplicaRole.MASTER and newrole == ReplicaRole.CONSUMER:
try:
- self.replace_many([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
- (REPL_FLAGS, str(REPLICA_FLAGS_RDONLY)),
- (REPL_ID, str(CONSUMER_REPLICAID))])
+ self.replace_many((REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
+ (REPL_FLAGS, str(REPLICA_FLAGS_RDONLY)),
+ (REPL_ID, str(CONSUMER_REPLICAID)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
elif replicarole == ReplicaRole.HUB and newrole == ReplicaRole.CONSUMER:
@@ -1310,6 +1328,9 @@ class Replica(DSLdapObject):
self.set(REPL_FLAGS, str(REPLICA_FLAGS_RDONLY))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
+ if replicarole == ReplicaRole.MASTER:
+ # We are no longer a master, clean up the old RID
+ self.cleanRUV(rid)
def get_role(self):
"""Return the replica role
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] 03/03: Issue #50067 - Fix krb5 dependency in a specfile
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
spichugi pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
commit 6f086aabe5973863ba2d4686e44738c60a584a71
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Thu Apr 25 17:33:37 2019 +0200
Issue #50067 - Fix krb5 dependency in a specfile
Bug Description: The build fails because the krb5 dependencies
are not installed while using specfile.
Fix Description: Add pkgconfig(krb5) to the BuildRequires section.
https://pagure.io/389-ds-base/issue/50067
Reviewed by: mhonek, mreynolds (Thanks!)
---
rpm/389-ds-base.spec.in | 1 +
1 file changed, 1 insertion(+)
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index 63cc389..8b014b8 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -107,6 +107,7 @@ BuildRequires: rust
%endif
BuildRequires: pkgconfig
BuildRequires: pkgconfig(systemd)
+BuildRequires: pkgconfig(krb5)
# Needed to support regeneration of the autotool artifacts.
BuildRequires: autoconf
BuildRequires: automake
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] 02/03: Issue 50545 - Port repl-monitor.pl to lib389 CLI
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
spichugi pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
commit dbb89923719224d3e808ef1c0e6ec0c4967c1148
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Wed Sep 11 20:21:17 2019 +0200
Issue 50545 - Port repl-monitor.pl to lib389 CLI
Description: Add a new command to 'dsconf replication' CLI.
'dsconf replication monitor' generates a report which
shows the replication topology to which the instance does belong.
Additional arguments:
-c or --connections [CONNECTION [CONNECTION ...]]
The connection values for monitoring other not
connected topologies. The format:
'host:port:binddn:bindpwd'. You can use regex for host
and port. You can set bindpwd to * and it will be
requested at the runtime or you can include the path
to the password file in square brackets - [~/pwd.txt]
-a or --aliases [ALIAS [ALIAS ...]]
If a host:port is assigned an alias, then the alias
instead of host:port will be displayed in the output.
The format: alias=host:port
Also, ~/.dsrc can be used for specifying the connections and aliases.
[repl-monitor-connections]
connection1 = server1.example.com:38901:cn=Directory manager:*
connection2 = server2.example.com:38902:cn=Directory manager:[~/pwd.txt]
connection3 = hub1.example.com:.*:cn=Directory manager:password
[repl-monitor-aliases]
M1 = server1.example.com:38901
M2 = server2.example.com:38902
https://pagure.io/389-ds-base/issue/50545
Reviewed by: mreynolds (Thanks!)
---
src/lib389/cli/dsconf | 2 +-
src/lib389/cli/dsidm | 4 +-
src/lib389/lib389/_constants.py | 3 +
src/lib389/lib389/agreement.py | 2 +-
src/lib389/lib389/cli_base/dsrc.py | 75 ++++++++++--
src/lib389/lib389/cli_conf/replication.py | 105 ++++++++++++++++-
src/lib389/lib389/replica.py | 186 +++++++++++++++++++++++++++++-
7 files changed, 355 insertions(+), 22 deletions(-)
diff --git a/src/lib389/cli/dsconf b/src/lib389/cli/dsconf
index 7486dbe..2059a06 100755
--- a/src/lib389/cli/dsconf
+++ b/src/lib389/cli/dsconf
@@ -110,7 +110,7 @@ if __name__ == '__main__':
log.debug("Inspired by works of: ITS, The University of Adelaide")
# Now that we have our args, see how they relate with our instance.
- dsrc_inst = dsrc_to_ldap("~/.dsrc", args.instance, log.getChild('dsrc'))
+ dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
# Now combine this with our arguments
dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
index 51ffb56..05fcb64 100755
--- a/src/lib389/cli/dsidm
+++ b/src/lib389/cli/dsidm
@@ -15,7 +15,7 @@ import argparse, argcomplete
import logging
import sys
import signal
-from lib389._constants import DN_DM
+from lib389._constants import DSRC_HOME
from lib389.cli_idm import account as cli_account
from lib389.cli_idm import initialise as cli_init
from lib389.cli_idm import organizationalunit as cli_ou
@@ -96,7 +96,7 @@ if __name__ == '__main__':
log.debug("Inspired by works of: ITS, The University of Adelaide")
# Now that we have our args, see how they relate with our instance.
- dsrc_inst = dsrc_to_ldap("~/.dsrc", args.instance, log.getChild('dsrc'))
+ dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
# Now combine this with our arguments
diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py
index e656131..1ad72b5 100644
--- a/src/lib389/lib389/_constants.py
+++ b/src/lib389/lib389/_constants.py
@@ -345,3 +345,6 @@ args_instance = {SER_DEPLOYED_DIR: os.environ.get('PREFIX', None),
# Helper for linking dse.ldif values to the parse_config function
args_dse_keys = SER_PROPNAME_TO_ATTRNAME
+
+DSRC_HOME = '~/.dsrc'
+DSRC_CONTAINER = '/data/config/container.inf'
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
index 84e2f8c..a9ee68c 100644
--- a/src/lib389/lib389/agreement.py
+++ b/src/lib389/lib389/agreement.py
@@ -366,7 +366,7 @@ class Agreement(DSLdapObject):
return (json.dumps(result))
else:
retstr = (
- "Status for agreement: \"%(cn)s\" (%(nsDS5ReplicaHost)s:"
+ "Status For Agreement: \"%(cn)s\" (%(nsDS5ReplicaHost)s:"
"%(nsDS5ReplicaPort)s)" "\n"
"Replica Enabled: %(nsds5ReplicaEnabled)s" "\n"
"Update In Progress: %(nsds5replicaUpdateInProgress)s" "\n"
diff --git a/src/lib389/lib389/cli_base/dsrc.py b/src/lib389/lib389/cli_base/dsrc.py
index 8fd8364..bbd160e 100644
--- a/src/lib389/lib389/cli_base/dsrc.py
+++ b/src/lib389/lib389/cli_base/dsrc.py
@@ -8,14 +8,18 @@
import sys
import os
+import json
import ldap
-from lib389.properties import *
+from lib389.properties import (SER_LDAP_URL, SER_ROOT_DN, SER_LDAPI_ENABLED,
+ SER_LDAPI_SOCKET, SER_LDAPI_AUTOBIND)
+from lib389._constants import DSRC_CONTAINER
MAJOR, MINOR, _, _, _ = sys.version_info
if MAJOR >= 3:
import configparser
+
def dsrc_arg_concat(args, dsrc_inst):
"""
Given a set of argparse args containing:
@@ -64,6 +68,23 @@ def dsrc_arg_concat(args, dsrc_inst):
dsrc_inst['starttls'] = True
return dsrc_inst
+
+def _read_dsrc(path, log, case_sensetive=False):
+ path = os.path.expanduser(path)
+ log.debug("dsrc path: %s" % path)
+ log.debug("dsrc container path: %s" % DSRC_CONTAINER)
+ config = configparser.ConfigParser()
+ if case_sensetive:
+ config.optionxform = str
+ # First read our container config if it exists
+ # Then overlap the user config.
+ config.read([DSRC_CONTAINER, path])
+
+ log.debug("dsrc instances: %s" % config.sections())
+
+ return config
+
+
def dsrc_to_ldap(path, instance_name, log):
"""
Given a path to a file, return the required details for an instance.
@@ -83,14 +104,7 @@ def dsrc_to_ldap(path, instance_name, log):
tls_reqcert = [never, hard, allow]
starttls = [true, false]
"""
- path = os.path.expanduser(path)
- log.debug("dsrc path: %s" % path)
- # First read our config
- # No such file?
- config = configparser.ConfigParser()
- config.read([path])
-
- log.debug("dsrc instances: %s" % config.sections())
+ config = _read_dsrc(path, log)
# Does our section exist?
if not config.has_section(instance_name):
@@ -107,14 +121,15 @@ def dsrc_to_ldap(path, instance_name, log):
dsrc_inst['binddn'] = config.get(instance_name, 'binddn', fallback=None)
dsrc_inst['saslmech'] = config.get(instance_name, 'saslmech', fallback=None)
if dsrc_inst['saslmech'] is not None and dsrc_inst['saslmech'] not in ['EXTERNAL', 'PLAIN']:
- raise Exception("~/.dsrc [%s] saslmech must be one of EXTERNAL or PLAIN" % instance_name)
+ raise Exception("%s [%s] saslmech must be one of EXTERNAL or PLAIN" % (path, instance_name))
dsrc_inst['tls_cacertdir'] = config.get(instance_name, 'tls_cacertdir', fallback=None)
dsrc_inst['tls_cert'] = config.get(instance_name, 'tls_cert', fallback=None)
dsrc_inst['tls_key'] = config.get(instance_name, 'tls_key', fallback=None)
dsrc_inst['tls_reqcert'] = config.get(instance_name, 'tls_reqcert', fallback='hard')
if dsrc_inst['tls_reqcert'] not in ['never', 'allow', 'hard']:
- raise Exception("dsrc tls_reqcert value invalid. ~/.dsrc [%s] tls_reqcert should be one of never, allow or hard" % instance_name)
+ raise Exception("dsrc tls_reqcert value invalid. %s [%s] tls_reqcert should be one of never, allow or hard" % (instance_name,
+ path))
if dsrc_inst['tls_reqcert'] == 'never':
dsrc_inst['tls_reqcert'] = ldap.OPT_X_TLS_NEVER
elif dsrc_inst['tls_reqcert'] == 'allow':
@@ -131,9 +146,45 @@ def dsrc_to_ldap(path, instance_name, log):
dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][9:]
dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on"
-
# Return the dict.
log.debug("dsrc completed with %s" % dsrc_inst)
return dsrc_inst
+def dsrc_to_repl_monitor(path, log):
+ """
+ Given a path to a file, return the required details for an instance.
+
+ The connection values for monitoring other not connected topologies. The format:
+ 'host:port:binddn:bindpwd'. You can use regex for host and port. You can set bindpwd
+ to * and it will be requested at the runtime.
+
+ If a host:port is assigned an alias, then the alias instead of host:port will be
+ displayed in the output. The format: "alias=host:port".
+
+ The file should be an ini file, and instance should identify a section.
+
+ The ini fileshould have the content:
+
+ [repl-monitor-connections]
+ connection1 = server1.example.com:38901:cn=Directory manager:*
+ connection2 = server2.example.com:38902:cn=Directory manager:[~/pwd.txt]
+ connection3 = hub1.example.com:.*:cn=Directory manager:password
+
+ [repl-monitor-aliases]
+ M1 = server1.example.com:38901
+ M2 = server2.example.com:38902
+ """
+
+ config = _read_dsrc(path, log, case_sensetive=True)
+ dsrc_repl_monitor = {"connections": None,
+ "aliases": None}
+
+ if config.has_section("repl-monitor-connections"):
+ dsrc_repl_monitor["connections"] = [conn for _, conn in config.items("repl-monitor-connections")]
+
+ if config.has_section("repl-monitor-aliases"):
+ dsrc_repl_monitor["aliases"] = {alias: inst for alias, inst in config.items("repl-monitor-aliases")}
+
+ log.debug(f"dsrc completed with {dsrc_repl_monitor}")
+ return dsrc_repl_monitor
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
index 3e147e9..282fe91 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -6,16 +6,16 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import re
import logging
-import time
-import base64
import os
import json
import ldap
from getpass import getpass
-from lib389._constants import *
-from lib389.utils import is_a_dn, ensure_str
-from lib389.replica import Replicas, BootstrapReplicationManager, RUV, Changelog5, ChangelogLDIF
+from lib389._constants import ReplicaRole, DSRC_HOME
+from lib389.cli_base.dsrc import dsrc_to_repl_monitor
+from lib389.utils import is_a_dn
+from lib389.replica import Replicas, ReplicationMonitor, BootstrapReplicationManager, Changelog5, ChangelogLDIF
from lib389.tasks import CleanAllRUVTask, AbortCleanAllRUVTask
from lib389._mapped_object import DSLdapObjects
@@ -338,6 +338,90 @@ def set_repl_config(inst, basedn, log, args):
log.info("Successfully updated replication configuration")
+def get_repl_monitor_info(inst, basedn, log, args):
+ connection_data = dsrc_to_repl_monitor(DSRC_HOME, log)
+
+ # Additional details for the connections to the topology
+ def get_credentials(host, port):
+ found = False
+ if args.connections:
+ connections = args.connections
+ elif connection_data["connections"]:
+ connections = connection_data["connections"]
+ else:
+ connections = []
+
+ 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]
+ if re.match(host_regex, host) and re.match(port_regex, port):
+ found = True
+ binddn = connection_str.split(":")[2]
+ bindpw = connection_str.split(":")[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])
+ try:
+ with open(pwd_file_path) as f:
+ bindpw = f.readline().strip()
+ except FileNotFoundError:
+ bindpw = getpass(f"File '{pwd_file_path}' was not found. Please, enter "
+ f"a password for {binddn} on {host}:{port}: ").rstrip()
+ if bindpw == "*":
+ bindpw = getpass(f"Enter a password for {binddn} on {host}:{port}: ").rstrip()
+ if not found:
+ binddn = input(f'\nEnter a bind DN for {host}:{port}: ').rstrip()
+ bindpw = getpass(f"Enter a password for {binddn} on {host}:{port}: ").rstrip()
+
+ return {"binddn": binddn,
+ "bindpw": bindpw}
+
+ repl_monitor = ReplicationMonitor(inst)
+ report_dict = repl_monitor.generate_report(get_credentials)
+
+ 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}"
+ log.info(supplier_header)
+ # Draw a line with the same length as the header
+ 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"]
+ log.info(f"Replica Root: {replica_root}")
+ log.info(f"Replica ID: {replica_id}")
+ log.info(f"Max CSN: {maxcsn}\n")
+ for agreement_status in replica["agmts_status"]:
+ log.info(agreement_status)
+
+
def create_cl(inst, basedn, log, args):
cl = Changelog5(inst)
try:
@@ -1029,6 +1113,17 @@ def create_parser(subparsers):
repl_set_parser.add_argument('--repl-release-timeout', help="A timeout in seconds a replication master should send "
"updates before it yields its replication session")
+ repl_monitor_parser = repl_subcommands.add_parser('monitor', help='Get the full replication topology report')
+ repl_monitor_parser.set_defaults(func=get_repl_monitor_info)
+ repl_monitor_parser.add_argument('-c', '--connections', nargs="*",
+ help="The connection values for monitoring other not connected topologies. "
+ "The format: 'host:port:binddn:bindpwd'. You can use regex for host and port. "
+ "You can set bindpwd to * and it will be requested at the runtime or "
+ "you can include the path to the password file in square brackets - [~/pwd.txt]")
+ repl_monitor_parser.add_argument('-a', '--aliases', nargs="*",
+ help="If a host:port is assigned an alias, then the alias instead of "
+ "host:port will be displayed in the output. The format: alias=host:port")
+#
############################################
# Replication Agmts
############################################
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index a67a829..e0b41a9 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -1381,10 +1381,62 @@ class Replica(DSLdapObject):
"""Return the set of agreements related to this suffix replica
:param: winsync: If True then return winsync replication agreements,
otherwise return teh standard replication agreements.
- :returns: Agreements object
+ :returns: A list Replicas objects
"""
return Agreements(self._instance, self.dn, winsync=winsync)
+ def get_consumer_replicas(self, get_credentials):
+ """Return the set of consumer replicas related to this suffix replica through its agreements
+
+ :param get_credentials: A user-defined callback function which returns the binding credentials
+ using given host and port data - {"binddn": "cn=Directory Manager",
+ "bindpw": "password"}
+ :returns: Replicas object
+ """
+
+ agmts = self.get_agreements()
+ result_replicas = []
+ connections = []
+
+ try:
+ for agmt in agmts:
+ host = agmt.get_attr_val_utf8("nsDS5ReplicaHost")
+ port = agmt.get_attr_val_utf8("nsDS5ReplicaPort")
+ protocol = agmt.get_attr_val_utf8("nsDS5ReplicaTransportInfo").lower()
+
+ # The function should be defined outside and
+ # it should have all the logic for figuring out the credentials
+ credentials = get_credentials(host, port)
+ if not credentials["binddn"]:
+ report_data[supplier] = {"status": "Unavailable",
+ "reason": "Bind DN was not specified"}
+ continue
+
+ # Open a connection to the consumer
+ consumer = DirSrv(verbose=self._instance.verbose)
+ args_instance[SER_HOST] = host
+ if protocol == "ssl" or protocol == "ldaps":
+ args_instance[SER_SECURE_PORT] = int(port)
+ else:
+ args_instance[SER_PORT] = int(port)
+ args_instance[SER_ROOT_DN] = credentials["binddn"]
+ args_instance[SER_ROOT_PW] = credentials["bindpw"]
+ args_standalone = args_instance.copy()
+ consumer.allocate(args_standalone)
+ try:
+ consumer.open()
+ except ldap.LDAPError as e:
+ self._log.debug(f"Connection to consumer ({host}:{port}) failed, error: {e}")
+ raise
+ connections.append(consumer)
+ result_replicas.append(Replicas(consumer))
+ except:
+ for conn in connections:
+ conn.close()
+ raise
+
+ return result_replicas
+
def get_rid(self):
"""Return the current replicas RID for this suffix
@@ -1412,6 +1464,15 @@ class Replica(DSLdapObject):
return RUV(data)
+ def get_maxcsn(self):
+ """Return the current replica's maxcsn for this suffix
+
+ :returns: str
+ """
+ replica_id = self.get_rid()
+ replica_ruvs = self.get_ruv()
+ return replica_ruvs._rid_maxcsn.get(replica_id, '00000000000000000000')
+
def get_ruv_agmt_maxcsns(self):
"""Return the in memory ruv of this replica suffix.
@@ -2227,3 +2288,126 @@ class ReplicationManager(object):
replicas = Replicas(instance)
replica = replicas.get(self._suffix)
return replica.get_rid()
+
+
+class ReplicationMonitor(object):
+ """The lib389 replication monitor. This is used to check the status
+ of many instances at once.
+ It also allows to monitor independent topologies and get them into
+ the one combined report.
+
+ :param instance: A supplier or hub for replication topology monitoring
+ :type instance: list of DirSrv objects
+ :param logger: A logging interface
+ :type logger: python logging
+ """
+
+ def __init__(self, instance, logger=None):
+ self._instance = instance
+ if logger is not None:
+ self._log = logger
+ else:
+ self._log = logging.getLogger(__name__)
+
+ def _get_replica_status(self, instance, report_data, use_json):
+ """Load all of the status data to report
+ and add new hostname:port pairs for future processing
+ """
+
+ replicas_status = []
+ replicas = Replicas(instance)
+ for replica in replicas.list():
+ replica_id = replica.get_rid()
+ replica_root = replica.get_suffix()
+ replica_maxcsn = replica.get_maxcsn()
+ agmts_status = []
+ agmts = replica.get_agreements()
+ for agmt in agmts.list():
+ host = agmt.get_attr_val_utf8_l("nsds5replicahost")
+ port = agmt.get_attr_val_utf8_l("nsds5replicaport")
+ 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}"
+ if consumer not in report_data:
+ report_data[consumer] = None
+ agmts_status.append(agmt.status(use_json))
+ replicas_status.append({"replica_id": replica_id,
+ "replica_root": replica_root,
+ "maxcsn": replica_maxcsn,
+ "agmts_status": agmts_status})
+ return replicas_status
+
+ def generate_report(self, get_credentials, use_json=False):
+ """Generate a replication report for each supplier or hub and the instances
+ that are connected with it by agreements.
+
+ :param get_credentials: A user-defined callback function with parameters (host, port) which returns
+ a dictionary with binddn and bindpw keys -
+ example values "cn=Directory Manager" and "password"
+ :type get_credentials: function
+ :returns: dict
+ """
+ report_data = {}
+
+ initial_inst_key = f"{self._instance.host.lower()}:{str(self._instance.port).lower()}"
+ # 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)
+
+ # Check if at least some replica report on other instances was generated
+ repl_exists = False
+
+ # While we have unprocessed instances - continue
+ while True:
+ try:
+ supplier = [host_port for host_port, processed_data in report_data.items() if processed_data is None][0]
+ except IndexError:
+ break
+
+ s_splitted = supplier.split(":")
+ supplier_hostname = s_splitted[0]
+ supplier_port = s_splitted[1]
+ supplier_protocol = 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"}
+ continue
+
+ # Open a connection to the consumer
+ supplier_inst = DirSrv(verbose=self._instance.verbose)
+ args_instance[SER_HOST] = supplier_hostname
+ if supplier_protocol == "ssl" or supplier_protocol == "ldaps":
+ args_instance[SER_SECURE_PORT] = int(supplier_port)
+ else:
+ args_instance[SER_PORT] = int(supplier_port)
+ args_instance[SER_ROOT_DN] = credentials["binddn"]
+ args_instance[SER_ROOT_PW] = credentials["bindpw"]
+ args_standalone = args_instance.copy()
+ supplier_inst.allocate(args_standalone)
+ try:
+ 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']}
+ continue
+
+ report_data[supplier] = self._get_replica_status(supplier_inst, report_data, use_json)
+ repl_exists = True
+
+ # 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 not value:
+ value = {"status": "Unavailable",
+ "reason": "No replicas were found"}
+ report_data_final[":".join(key.split(":")[:2])] = value
+
+ return report_data_final
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] 01/03: Issue 50497 - Port cl-dump.pl tool to Python using lib389
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
spichugi pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
commit e7e6687754c3f7f510503562c090f8dd2676d8cb
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Tue Jul 16 21:32:27 2019 +0200
Issue 50497 - Port cl-dump.pl tool to Python using lib389
Bug Description: We're going to deprecate all Perl scripts in 389-ds
so cl-dump.pl should be ported as soon as possible.
Fix Description: Put the tool to dsconf replication dump-changelog.
Preserve all the functionality and output format.
Depricate ChangelogLegacy object.
Move Changelog5 object to replica.py so we can avoid import loops.
Also it makes more sense to have it there because it is part of Replication.
Add ChangelogLDIF object.
Add process_and_dump_changelog() method to Replicas object.
https://pagure.io/389-ds-base/issue/50497
Reviewed by: mreynolds, mhonek, wibrown (Thanks!)
---
.../suites/replication/changelog_trimming_test.py | 3 +-
.../tests/suites/replication/regression_test.py | 10 +-
src/lib389/lib389/changelog.py | 204 -----------------
src/lib389/lib389/cli_conf/replication.py | 41 +++-
src/lib389/lib389/replica.py | 248 ++++++++++++++++++++-
5 files changed, 288 insertions(+), 218 deletions(-)
diff --git a/dirsrvtests/tests/suites/replication/changelog_trimming_test.py b/dirsrvtests/tests/suites/replication/changelog_trimming_test.py
index e8cc3da..39abc92 100644
--- a/dirsrvtests/tests/suites/replication/changelog_trimming_test.py
+++ b/dirsrvtests/tests/suites/replication/changelog_trimming_test.py
@@ -6,7 +6,7 @@ import time
from lib389._constants import *
from lib389.properties import *
from lib389.topologies import topology_m1 as topo
-from lib389.changelog import Changelog5
+from lib389.replica import Changelog5
from lib389.idm.domain import Domain
DEBUGGING = os.getenv("DEBUGGING", default=False)
@@ -130,4 +130,3 @@ if __name__ == '__main__':
# -s for DEBUG mode
CURRENT_FILE = os.path.realpath(__file__)
pytest.main("-s %s" % CURRENT_FILE)
-
diff --git a/dirsrvtests/tests/suites/replication/regression_test.py b/dirsrvtests/tests/suites/replication/regression_test.py
index 99280dd..f2bcd3b 100644
--- a/dirsrvtests/tests/suites/replication/regression_test.py
+++ b/dirsrvtests/tests/suites/replication/regression_test.py
@@ -17,8 +17,14 @@ from lib389.agreement import Agreements
from lib389.idm.user import UserAccount
from lib389 import Entry
from lib389.idm.group import Groups, Group
-from lib389.replica import Replicas, ReplicationManager
-from lib389.changelog import Changelog5
+from lib389.idm.domain import Domain
+from lib389.idm.directorymanager import DirectoryManager
+from lib389.replica import Replicas, ReplicationManager, Changelog5
+from lib389.agreement import Agreements
+from lib389 import pid_from_file
+
+
+pytestmark = pytest.mark.tier1
NEW_SUFFIX_NAME = 'test_repl'
NEW_SUFFIX = 'o={}'.format(NEW_SUFFIX_NAME)
diff --git a/src/lib389/lib389/changelog.py b/src/lib389/lib389/changelog.py
deleted file mode 100644
index cb218d4..0000000
--- a/src/lib389/lib389/changelog.py
+++ /dev/null
@@ -1,204 +0,0 @@
-# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2015 Red Hat, Inc.
-# All rights reserved.
-#
-# License: GPL (version 3 or any later version).
-# See LICENSE for details.
-# --- END COPYRIGHT BLOCK ---
-
-import os
-import ldap
-
-from lib389._constants import *
-from lib389.properties import *
-from lib389 import DirSrv, Entry, InvalidArgumentError
-
-from lib389._mapped_object import DSLdapObject
-from lib389.utils import ds_is_older
-
-
-class Changelog5(DSLdapObject):
- """Represents the Directory Server changelog. This is used for
- replication. Only one changelog is needed for every server.
-
- :param instance: An instance
- :type instance: lib389.DirSrv
- """
-
- def __init__(self, instance, dn='cn=changelog5,cn=config'):
- super(Changelog5, self).__init__(instance, dn)
- self._rdn_attribute = 'cn'
- self._must_attributes = ['cn', 'nsslapd-changelogdir']
- self._create_objectclasses = [
- 'top',
- 'nsChangelogConfig',
- ]
- if ds_is_older('1.4.0'):
- self._create_objectclasses = [
- 'top',
- 'extensibleobject',
- ]
- self._protected = False
-
- def set_max_entries(self, value):
- """Configure the max entries the changelog can hold.
-
- :param value: the number of entries.
- :type value: str
- """
- self.replace('nsslapd-changelogmaxentries', value)
-
- def set_trim_interval(self, value):
- """The time between changelog trims in seconds.
-
- :param value: The time in seconds
- :type value: str
- """
- self.replace('nsslapd-changelogtrim-interval', value)
-
- def set_max_age(self, value):
- """The maximum age of entries in the changelog.
-
- :param value: The age with a time modifier of s, m, h, d, w.
- :type value: str
- """
- self.replace('nsslapd-changelogmaxage', value)
-
-
-class ChangelogLegacy(object):
- """An object that helps to work with changelog entry
-
- :param conn: An instance
- :type conn: lib389.DirSrv
- """
-
- proxied_methods = 'search_s getEntry'.split()
-
- def __init__(self, conn):
- self.conn = conn
- self.log = conn.log
-
- def __getattr__(self, name):
- if name in Changelog.proxied_methods:
- return DirSrv.__getattr__(self.conn, name)
-
- def list(self, suffix=None, changelogdn=DN_CHANGELOG):
- """Get a changelog entry using changelogdn parameter
-
- :param suffix: Not used
- :type suffix: str
- :param changelogdn: DN of the changelog entry, DN_CHANGELOG by default
- :type changelogdn: str
-
- :returns: Search result of the replica agreements.
- Enpty list if nothing was found
- """
-
- base = changelogdn
- filtr = "(objectclass=extensibleobject)"
-
- # now do the effective search
- try:
- ents = self.conn.search_s(base, ldap.SCOPE_BASE, filtr)
- except ldap.NO_SUCH_OBJECT:
- # There are no objects to select from, se we return an empty array
- # as we do in DSLdapObjects
- ents = []
- return ents
-
- def create(self, dbname=DEFAULT_CHANGELOG_DB):
- """Add and return the replication changelog entry.
-
- :param dbname: Database name, it will be used for creating
- a changelog dir path
- :type dbname: str
- """
-
- dn = DN_CHANGELOG
- attribute, changelog_name = dn.split(",")[0].split("=", 1)
- dirpath = os.path.join(os.path.dirname(self.conn.dbdir), dbname)
- entry = Entry(dn)
- entry.update({
- 'objectclass': ("top", "extensibleobject"),
- CHANGELOG_PROPNAME_TO_ATTRNAME[CHANGELOG_NAME]: changelog_name,
- CHANGELOG_PROPNAME_TO_ATTRNAME[CHANGELOG_DIR]: dirpath
- })
- self.log.debug("adding changelog entry: %r", entry)
- self.conn.changelogdir = dirpath
- try:
- self.conn.add_s(entry)
- except ldap.ALREADY_EXISTS:
- self.log.warning("entry %s already exists", dn)
- return dn
-
- def delete(self):
- """Delete the changelog entry
-
- :raises: LDAPError - failed to delete changelog entry
- """
-
- try:
- self.conn.delete_s(DN_CHANGELOG)
- except ldap.LDAPError as e:
- self.log.error('Failed to delete the changelog: %s', e)
- raise
-
- def setProperties(self, changelogdn=None, properties=None):
- """Set the properties of the changelog entry.
-
- :param changelogdn: DN of the changelog
- :type changelogdn: str
- :param properties: Dictionary of properties
- :type properties: dict
-
- :returns: None
- :raises: - ValueError - if invalid properties
- - ValueError - if changelog entry is not found
- - InvalidArgumentError - changelog DN is missing
-
- :supported properties are:
- CHANGELOG_NAME, CHANGELOG_DIR, CHANGELOG_MAXAGE,
- CHANGELOG_MAXENTRIES, CHANGELOG_TRIM_INTERVAL,
- CHANGELOG_COMPACT_INTV, CHANGELOG_CONCURRENT_WRITES,
- CHANGELOG_ENCRYPT_ALG, CHANGELOG_SYM_KEY
- """
-
- if not changelogdn:
- raise InvalidArgumentError("changelog DN is missing")
-
- ents = self.conn.changelog.list(changelogdn=changelogdn)
- if len(ents) != 1:
- raise ValueError("Changelog entry not found: %s" % changelogdn)
-
- # check that the given properties are valid
- for prop in properties:
- # skip the prefix to add/del value
- if not inProperties(prop, CHANGELOG_PROPNAME_TO_ATTRNAME):
- raise ValueError("unknown property: %s" % prop)
-
- # build the MODS
- mods = []
- for prop in properties:
- # take the operation type from the property name
- val = rawProperty(prop)
- if str(prop).startswith('+'):
- op = ldap.MOD_ADD
- elif str(prop).startswith('-'):
- op = ldap.MOD_DELETE
- else:
- op = ldap.MOD_REPLACE
-
- mods.append((op, CHANGELOG_PROPNAME_TO_ATTRNAME[val],
- properties[prop]))
-
- # that is fine now to apply the MOD
- self.conn.modify_s(ents[0].dn, mods)
-
- def getProperties(self, changelogdn=None, properties=None):
- """Get a dictionary of the requested properties.
- If properties parameter is missing, it returns all the properties.
-
- NotImplemented
- """
-
- raise NotImplemented
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
index b25eba6..3e147e9 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -6,13 +6,16 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import logging
+import time
+import base64
+import os
import json
import ldap
from getpass import getpass
from lib389._constants import *
-from lib389.changelog import Changelog5
-from lib389.utils import is_a_dn
-from lib389.replica import Replicas, BootstrapReplicationManager
+from lib389.utils import is_a_dn, ensure_str
+from lib389.replica import Replicas, BootstrapReplicationManager, RUV, Changelog5, ChangelogLDIF
from lib389.tasks import CleanAllRUVTask, AbortCleanAllRUVTask
from lib389._mapped_object import DSLdapObjects
@@ -885,6 +888,25 @@ def list_abort_cleanallruv(inst, basedn, log, args):
log.info("No CleanAllRUV abort tasks found")
+def dump_cl(inst, basedn, log, args):
+ if args.output_file:
+ fh = logging.FileHandler(args.output_file, mode='w')
+ log.addHandler(fh)
+ replicas = Replicas(inst)
+ if not args.changelog_ldif:
+ replicas.process_and_dump_changelog(replica_roots=args.replica_roots, csn_only=args.csn_only)
+ else:
+ try:
+ assert os.path.exists(args.changelog_ldif)
+ except AssertionError:
+ raise FileNotFoundError(f"File {args.changelog_ldif} was not found")
+ cl_ldif = ChangelogLDIF(args.changelog_ldif, log)
+ if args.csn_only:
+ cl_ldif.grep_csn()
+ else:
+ cl_ldif.decode()
+
+
def create_parser(subparsers):
############################################
@@ -971,6 +993,18 @@ def create_parser(subparsers):
repl_get_cl = repl_subcommands.add_parser('get-changelog', help='Display replication changelog attributes.')
repl_get_cl.set_defaults(func=get_cl)
+ repl_set_cl = repl_subcommands.add_parser('dump-changelog', help='Decode Directory Server replication change log and dump it to an LDIF')
+ repl_set_cl.set_defaults(func=dump_cl)
+ repl_set_cl.add_argument('-c', '--csn-only', action='store_true',
+ help="Dump and interpret CSN only. This option can be used with or without -i option.")
+ repl_set_cl.add_argument('-i', '--changelog-ldif',
+ help="If you already have a ldif-like changelog, but the changes in that file are encoded,"
+ " you may use this option to decode that ldif-like changelog. It should be base64 encoded.")
+ repl_set_cl.add_argument('-o', '--output-file', help="Path name for the final result. Default to STDOUT if omitted.")
+ repl_set_cl.add_argument('-r', '--replica-roots', nargs="+",
+ help="Specify replica roots whose changelog you want to dump. The replica "
+ "roots may be seperated by comma. All the replica roots would be dumped if the option is omitted.")
+
repl_set_parser = repl_subcommands.add_parser('set', help='Set an attribute in the replication configuration')
repl_set_parser.set_defaults(func=set_repl_config)
repl_set_parser.add_argument('--suffix', required=True, help='The DN of the replication suffix')
@@ -1264,4 +1298,3 @@ def create_parser(subparsers):
task_abort_cleanallruv_list = task_subcommands.add_parser('list-abortruv-tasks', help='List all the running CleanAllRUV abort Tasks')
task_abort_cleanallruv_list.set_defaults(func=list_abort_cleanallruv)
task_abort_cleanallruv_list.add_argument('--suffix', help="List only tasks from for suffix")
-
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index 456e493..a67a829 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -6,9 +6,12 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import os
+import base64
import ldap
import decimal
import time
+import datetime
import logging
import uuid
import json
@@ -22,7 +25,6 @@ from lib389._mapped_object import DSLdapObjects, DSLdapObject
from lib389.passwd import password_generate
from lib389.mappingTree import MappingTrees
from lib389.agreement import Agreements
-from lib389.changelog import Changelog5
from lib389.tombstone import Tombstones
from lib389.idm.domain import Domain
@@ -815,15 +817,19 @@ class RUV(object):
:type logger: logging object
"""
- def __init__(self, ruvs, logger=None):
+ def __init__(self, ruvs=[], logger=None):
if logger is not None:
self._log = logger
else:
self._log = logging.getLogger(__name__)
self._rids = []
- self._rid_csn = {}
self._rid_url = {}
+ self._rid_rawruv = {}
+ self._rid_csn = {}
+ self._rid_maxcsn = {}
+ self._rid_modts = {}
self._data_generation = None
+ self._data_generation_csn = None
# Process the array of data
for r in ruvs:
pr = r.replace('{', '').replace('}', '').split(' ')
@@ -836,10 +842,66 @@ class RUV(object):
rid = pr[1]
self._rids.append(rid)
self._rid_url[rid] = pr[2]
+ self._rid_rawruv[rid] = r
try:
- self._rid_csn[rid] = pr[4]
+ self._rid_csn[rid] = pr[3]
except IndexError:
self._rid_csn[rid] = '00000000000000000000'
+ try:
+ self._rid_maxcsn[rid] = pr[4]
+ except IndexError:
+ self._rid_maxcsn[rid] = '00000000000000000000'
+ try:
+ self._rid_modts[rid] = pr[5]
+ except IndexError:
+ self._rid_modts[rid] = '00000000'
+
+ @staticmethod
+ def parse_csn(csn):
+ """Parse CSN into human readable format '1970-01-31 00:00:00'
+
+ :param csn: the CSN to format
+ :type csn: str
+ :returns: str
+ """
+ if len(csn) != 20 or len(csn) != 8 and not isinstance(csn, str):
+ ValueError("Wrong CSN value was supplied")
+
+ timestamp = int(csn[:8], 16)
+ time_str = datetime.datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
+ # We are parsing shorter CSN which contains only timestamp
+ if len(csn) == 8:
+ return time_str
+ else:
+ seq = int(csn[8:12], 16)
+ subseq = int(csn[16:20], 16)
+ if seq != 0 or subseq != 0:
+ return f"{time_str} {str(seq)} {str(subseq)}"
+ else:
+ return f"{time_str}"
+
+ def format_ruv(self):
+ """Parse RUV into human readable format
+
+ :returns: dict
+ """
+ result = {}
+ if self._data_generation:
+ result["data_generation"] = {"name": self._data_generation,
+ "value": self._data_generation_csn}
+ else:
+ result["data_generation"] = None
+
+ ruvs = []
+ for rid in self._rids:
+ ruvs.append({"raw_ruv": self._rid_rawruv.get(rid),
+ "rid": rid,
+ "url": self._rid_url.get(rid),
+ "csn": parse_csn(self._rid_csn.get(rid, '00000000000000000000')),
+ "maxcsn": parse_csn(self._rid_maxcsn.get(rid, '00000000000000000000')),
+ "modts": parse_csn(self._rid_modts.get(rid, '00000000'))})
+ result["ruvs"] = ruvs
+ return result
def alloc_rid(self):
"""Based on the RUV, determine an available RID for the replication
@@ -880,6 +942,133 @@ class RUV(object):
return True
+class ChangelogLDIF(object):
+ def __init__(self, file_path, logger=None):
+ """A class for working with Changelog LDIF file
+
+ :param file_path: LDIF file path
+ :type file_path: str
+ :param logger: A logging object
+ :type logger: logging.Logger
+ """
+
+ if logger is not None:
+ self._log = logger
+ else:
+ self._log = logging.getLogger(__name__)
+ self.file_path = file_path
+
+ def grep_csn(self):
+ """Grep and interpret CSNs
+
+ :param file: LDIF file path
+ :type file: str
+ """
+
+ self._log.info(f"# LDIF File: {self.file_path}")
+ with open(self.file_path) as f:
+ for line in f.readlines():
+ if "ruv:" in line or "csn:" in line:
+ csn = ""
+ maxcsn = ""
+ modts = ""
+ line = line.split("\n")[0]
+ if "ruv:" in line:
+ ruv = RUV([line.split("ruv: ")[1]])
+ ruv_dict = ruv.parse_ruv()
+ csn = ruv_dict["csn"]
+ maxcsn = ruv_dict["maxcsn"]
+ modts = ruv_dict["modts"]
+ elif "csn:" in line:
+ csn = RUV().parse_csn(line.split("csn: ")[1])
+ if maxcsn or modts:
+ self._log.info(f'{line} ({csn}')
+ if maxcsn:
+ self._log.info(f"; {maxcsn}")
+ if modts:
+ self._log.info(f"; {modts}")
+ self._log.info(")")
+ else:
+ self._log.info(f"{line} ({csn})")
+
+ def decode(self):
+ """Decode the changelog
+
+ :param file: LDIF file path
+ :type file: str
+ """
+
+ self._log.info(f"# LDIF File: {self.file_path}")
+ with open(self.file_path) as f:
+ encoded_str = ""
+ for line in f.readlines():
+ if line.startswith("change::") or line.startswith("changes::"):
+ self._log.info("change::")
+ try:
+ encoded_str = line.split("change:: ")[1]
+ except IndexError:
+ encoded_str = line.split("changes:: ")[1]
+ continue
+ if not encoded_str:
+ self._log.info(line.split('\n')[0])
+ continue
+ if line == "\n":
+ decoded_str = ensure_str(base64.b64decode(encoded_str))
+ self._log.info(decoded_str)
+ encoded_str = ""
+ continue
+ encoded_str += line
+
+
+class Changelog5(DSLdapObject):
+ """Represents the Directory Server changelog. This is used for
+ replication. Only one changelog is needed for every server.
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ """
+
+ def __init__(self, instance, dn='cn=changelog5,cn=config'):
+ super(Changelog5, self).__init__(instance, dn)
+ self._rdn_attribute = 'cn'
+ self._must_attributes = ['cn', 'nsslapd-changelogdir']
+ self._create_objectclasses = [
+ 'top',
+ 'nsChangelogConfig',
+ ]
+ if ds_is_older('1.4.0'):
+ self._create_objectclasses = [
+ 'top',
+ 'extensibleobject',
+ ]
+ self._protected = False
+
+ def set_max_entries(self, value):
+ """Configure the max entries the changelog can hold.
+
+ :param value: the number of entries.
+ :type value: str
+ """
+ self.replace('nsslapd-changelogmaxentries', value)
+
+ def set_trim_interval(self, value):
+ """The time between changelog trims in seconds.
+
+ :param value: The time in seconds
+ :type value: str
+ """
+ self.replace('nsslapd-changelogtrim-interval', value)
+
+ def set_max_age(self, value):
+ """The maximum age of entries in the changelog.
+
+ :param value: The age with a time modifier of s, m, h, d, w.
+ :type value: str
+ """
+
+ self.replace('nsslapd-changelogmaxage', value)
+
+
class Replica(DSLdapObject):
"""Replica DSLdapObject with:
- must attributes = ['cn', 'nsDS5ReplicaType', 'nsDS5ReplicaRoot',
@@ -1307,6 +1496,55 @@ class Replicas(DSLdapObjects):
replica._populate_suffix()
return replica
+ def process_and_dump_changelog(self, replica_roots=[], csn_only=False):
+ """Dump and decode Directory Server replication change log
+
+ :param replica_roots: Replica suffixes that need to be processed
+ :type replica_roots: list of str
+ :param csn_only: Grep only the CSNs from the file
+ :type csn_only: bool
+ """
+
+ repl_roots = []
+ try:
+ cl = Changelog5(self._instance)
+ cl_dir = cl.get_attr_val_utf8_l("nsslapd-changelogdir")
+ except ldap.NO_SUCH_OBJECT:
+ raise ValueError("Changelog entry was not found. Probably, the replication is not enabled on this instance")
+
+ # Get all the replicas on the server if --replica-roots option is not specified
+ if not replica_roots:
+ for replica in self.list():
+ repl_roots.append(replica.get_attr_val_utf8("nsDS5ReplicaRoot"))
+ else:
+ for repl_root in replica_roots:
+ repl_roots.append(repl_root)
+
+ # Dump the changelog for the replica
+ for repl_root in repl_roots:
+ got_ldif = False
+ current_time = time.time()
+ replica = self.get(repl_root)
+ self._log.info(f"# Replica Root: {repl_root}")
+ replica.replace("nsDS5Task", 'CL2LDIF')
+
+ # Decode the dumped changelog
+ for file in [i for i in os.listdir(cl_dir) if i.endswith('.ldif')]:
+ file_path = os.path.join(cl_dir, file)
+ # Skip older ldif files
+ if os.path.getmtime(file_path) < current_time:
+ continue
+ got_ldif = True
+ cl_ldif = ChangelogLDIF(file_path, self._log)
+
+ if csn_only:
+ cl_ldif.grep_csn()
+ else:
+ cl_ldif.decode()
+ os.rename(file_path, f'{file_path}.done')
+ if not got_ldif:
+ self._log.info("LDIF file: Not found")
+
class BootstrapReplicationManager(DSLdapObject):
"""A Replication Manager credential for bootstrapping the repl process.
@@ -1989,5 +2227,3 @@ class ReplicationManager(object):
replicas = Replicas(instance)
replica = replicas.get(self._suffix)
return replica.get_rid()
-
-
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] branch 389-ds-base-1.3.10 updated: Ticket 49850 cont -fix crash in ldbm_non_leaf
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
lkrispen pushed a commit to branch 389-ds-base-1.3.10
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.10 by this push:
new 428a8ff Ticket 49850 cont -fix crash in ldbm_non_leaf
428a8ff is described below
commit 428a8ff5b9ef45f8acc74836fe0c83332a0cea27
Author: Ludwig Krispenz <lkrispen(a)redhat.com>
AuthorDate: Thu Oct 24 14:26:20 2019 +0200
Ticket 49850 cont -fix crash in ldbm_non_leaf
Bug: if the ldif to be imported contains only one entry there are no leaf nodes
and the call to qsort crashes
Fix: check that nodes is not NULL
---
ldap/servers/slapd/back-ldbm/ancestorid.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/back-ldbm/ancestorid.c b/ldap/servers/slapd/back-ldbm/ancestorid.c
index 254a3aa..f26ac13 100644
--- a/ldap/servers/slapd/back-ldbm/ancestorid.c
+++ b/ldap/servers/slapd/back-ldbm/ancestorid.c
@@ -114,7 +114,7 @@ ldbm_get_nonleaf_ids(backend *be, DB_TXN *txn, IDList **idl, ImportJob *job)
if (ret != 0)
ldbm_nasty("ldbm_get_nonleaf_ids", sourcefile, 13030, ret);
- if (ret == 0) {
+ if (ret == 0 && nodes) {
/* now sort it */
import_log_notice(job, SLAPI_LOG_INFO, "ldbm_get_nonleaf_ids",
"Starting sort of ancestorid non-leaf IDs...");
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 49850 cont -fix crash in ldbm_non_leaf
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
lkrispen pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new 6f61502 Ticket 49850 cont -fix crash in ldbm_non_leaf
6f61502 is described below
commit 6f61502eaa96eeab6d37a7822354850555f06974
Author: Ludwig Krispenz <lkrispen(a)redhat.com>
AuthorDate: Thu Oct 24 14:26:20 2019 +0200
Ticket 49850 cont -fix crash in ldbm_non_leaf
Bug: if the ldif to be imported contains only one entry there are no leaf nodes
and the call to qsort crashes
Fix: check that nodes is not NULL
---
ldap/servers/slapd/back-ldbm/ancestorid.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/back-ldbm/ancestorid.c b/ldap/servers/slapd/back-ldbm/ancestorid.c
index 254a3aa..f26ac13 100644
--- a/ldap/servers/slapd/back-ldbm/ancestorid.c
+++ b/ldap/servers/slapd/back-ldbm/ancestorid.c
@@ -114,7 +114,7 @@ ldbm_get_nonleaf_ids(backend *be, DB_TXN *txn, IDList **idl, ImportJob *job)
if (ret != 0)
ldbm_nasty("ldbm_get_nonleaf_ids", sourcefile, 13030, ret);
- if (ret == 0) {
+ if (ret == 0 && nodes) {
/* now sort it */
import_log_notice(job, SLAPI_LOG_INFO, "ldbm_get_nonleaf_ids",
"Starting sort of ancestorid non-leaf IDs...");
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] branch 389-ds-base-1.4.1 updated: Ticket 49850 cont -fix crash in ldbm_non_leaf
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
lkrispen 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 e1bc14f Ticket 49850 cont -fix crash in ldbm_non_leaf
e1bc14f is described below
commit e1bc14f91a40410007e0704fb49a41c7052a44aa
Author: Ludwig Krispenz <lkrispen(a)redhat.com>
AuthorDate: Thu Oct 24 14:26:20 2019 +0200
Ticket 49850 cont -fix crash in ldbm_non_leaf
Bug: if the ldif to be imported contains only one entry there are no leaf nodes
and the call to qsort crashes
Fix: check that nodes is not NULL
---
ldap/servers/slapd/back-ldbm/ancestorid.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/back-ldbm/ancestorid.c b/ldap/servers/slapd/back-ldbm/ancestorid.c
index 254a3aa..f26ac13 100644
--- a/ldap/servers/slapd/back-ldbm/ancestorid.c
+++ b/ldap/servers/slapd/back-ldbm/ancestorid.c
@@ -114,7 +114,7 @@ ldbm_get_nonleaf_ids(backend *be, DB_TXN *txn, IDList **idl, ImportJob *job)
if (ret != 0)
ldbm_nasty("ldbm_get_nonleaf_ids", sourcefile, 13030, ret);
- if (ret == 0) {
+ if (ret == 0 && nodes) {
/* now sort it */
import_log_notice(job, SLAPI_LOG_INFO, "ldbm_get_nonleaf_ids",
"Starting sort of ancestorid non-leaf IDs...");
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] 02/02: Issue 50634 - Clean up CLI errors output - Fix wrong exception
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
spichugi pushed a commit to branch 389-ds-base-1.4.1
in repository 389-ds-base.
commit 4e32c54971f2d4fcbb5926c81c6eb46c8bb525f4
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Mon Oct 21 18:25:20 2019 +0200
Issue 50634 - Clean up CLI errors output - Fix wrong exception
Description: The previous commit takes care only about ValueError
evaluation. But it is possible that other exceptions will be raised
which will result in a wrong error output.
Make the exception object more general.
https://pagure.io/389-ds-base/issue/50634
Reviewed by: ?
---
src/lib389/lib389/cli_base/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
index 23fc312..12b1c51 100644
--- a/src/lib389/lib389/cli_base/__init__.py
+++ b/src/lib389/lib389/cli_base/__init__.py
@@ -423,6 +423,6 @@ def format_error_to_dict(exception):
errmsg = str(exception)
try:
msg = ast.literal_eval(errmsg)
- except ValueError:
+ except Exception:
msg = {'desc': errmsg}
return msg
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month
[389-ds-base] 01/02: Issue 50634 - Clean up CLI errors output
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
spichugi pushed a commit to branch 389-ds-base-1.4.1
in repository 389-ds-base.
commit 49eef87e53a91ac9f0a7ed32d3bd1d198c90a6a2
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Thu Oct 17 13:46:56 2019 +0200
Issue 50634 - Clean up CLI errors output
Description: CLI tools should print human easy readable messages
if something went wrong.
As discussed here: https://pagure.io/389-ds-base/pull-request/50624
Change the CLI error processing so the dict type is always transformed.
https://pagure.io/389-ds-base/issue/50634
Reviewed by: mreynolds, mhonek (Thanks!)
---
src/lib389/cli/dsconf | 11 ++++-------
src/lib389/cli/dscreate | 4 +++-
src/lib389/cli/dsctl | 11 +++++++----
src/lib389/cli/dsidm | 4 +++-
src/lib389/lib389/cli_base/__init__.py | 20 ++++++++++++++++++++
5 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/src/lib389/cli/dsconf b/src/lib389/cli/dsconf
index 1940b8e..2059a06 100755
--- a/src/lib389/cli/dsconf
+++ b/src/lib389/cli/dsconf
@@ -15,7 +15,6 @@ import ldap
import sys
import signal
import json
-import ast
from lib389._constants import DSRC_HOME
from lib389.cli_conf import config as cli_config
from lib389.cli_conf import backend as cli_backend
@@ -34,6 +33,7 @@ from lib389.cli_conf import security as cli_security
from lib389.cli_base import disconnect_instance, connect_instance
from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat
from lib389.cli_base import setup_script_logger
+from lib389.cli_base import format_error_to_dict
parser = argparse.ArgumentParser(allow_abbrev=True)
@@ -138,15 +138,12 @@ if __name__ == '__main__':
log.info("Command successful.")
except Exception as e:
log.debug(e, exc_info=True)
- errmsg = str(e)
+ msg = format_error_to_dict(e)
+
if args and args.json:
- try:
- msg = ast.literal_eval(errmsg)
- except:
- msg = {'desc': errmsg}
sys.stderr.write(json.dumps(msg))
else:
- log.error("Error: %s" % errmsg)
+ log.error("Error: %s" % " - ".join(msg.values()))
result = False
disconnect_instance(inst)
diff --git a/src/lib389/cli/dscreate b/src/lib389/cli/dscreate
index 4af852b..b516eb9 100755
--- a/src/lib389/cli/dscreate
+++ b/src/lib389/cli/dscreate
@@ -17,6 +17,7 @@ import signal
from lib389 import DirSrv
from lib389.cli_ctl import instance as cli_instance
from lib389.cli_base import setup_script_logger
+from lib389.cli_base import format_error_to_dict
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose',
@@ -74,7 +75,8 @@ if __name__ == '__main__':
result = args.func(inst, log, args)
except Exception as e:
log.debug(e, exc_info=True)
- log.error("Error: %s" % str(e))
+ msg = format_error_to_dict(e)
+ log.error("Error: %s" % " - ".join(msg.values()))
result = False
# Done!
diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl
index e76f3e5..3be9540 100755
--- a/src/lib389/cli/dsctl
+++ b/src/lib389/cli/dsctl
@@ -21,6 +21,7 @@ from lib389 import DirSrv
from lib389.cli_ctl import instance as cli_instance
from lib389.cli_ctl import dbtasks as cli_dbtasks
from lib389.cli_base import disconnect_instance, setup_script_logger
+from lib389.cli_base import format_error_to_dict
from lib389.cli_ctl.instance import instance_remove_all
from lib389._constants import DSRC_CONTAINER
@@ -106,10 +107,12 @@ if __name__ == '__main__':
insts = inst.list(serverid=args.instance)
except (PermissionError, IOError) as e:
log.error("Unable to access instance information. Are you running as the correct user? (usually dirsrv or root)")
- log.error("Error: %s" % str(e))
+ msg = format_error_to_dict(e)
+ log.error("Error: %s" % " - ".join(msg.values()))
sys.exit(1)
except Exception as e:
- log.error("Error: %s" % str(e))
+ msg = format_error_to_dict(e)
+ log.error("Error: %s" % " - ".join(msg.values()))
sys.exit(1)
if len(insts) != 1:
log.error("No such instance '%s'" % args.instance)
@@ -123,11 +126,11 @@ if __name__ == '__main__':
result = args.func(inst, log, args)
except Exception as e:
log.debug(e, exc_info=True)
- log.error("Error: %s" % str(e))
+ msg = format_error_to_dict(e)
+ log.error("Error: %s" % " - ".join(msg.values()))
result = False
disconnect_instance(inst)
# Done!
if result is False:
sys.exit(1)
-
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
index 7fd77b6..da246b5 100755
--- a/src/lib389/cli/dsidm
+++ b/src/lib389/cli/dsidm
@@ -26,6 +26,7 @@ from lib389.cli_idm import client_config as cli_client_config
from lib389.cli_idm import role as cli_role
from lib389.cli_base import connect_instance, disconnect_instance, setup_script_logger
from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat
+from lib389.cli_base import format_error_to_dict
parser = argparse.ArgumentParser(allow_abbrev=True)
@@ -131,7 +132,8 @@ if __name__ == '__main__':
log.info("Command successful.")
except Exception as e:
log.debug(e, exc_info=True)
- log.error("Error: %s" % str(e))
+ msg = format_error_to_dict(e)
+ log.error("Error: %s" % " - ".join(msg.values()))
result = False
disconnect_instance(inst)
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
index 932938b..23fc312 100644
--- a/src/lib389/lib389/cli_base/__init__.py
+++ b/src/lib389/lib389/cli_base/__init__.py
@@ -7,6 +7,7 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import ast
import logging
import sys
import json
@@ -406,3 +407,22 @@ def setup_script_logger(name, verbose=False):
root.addHandler(log_handler)
return log
+
+
+def format_error_to_dict(exception):
+ """python-ldap str(exception) processing is not consistent.
+ This function makes sure that the result is dict
+
+ :param exception: Exception you need to print
+ :type exception: ldap.LDAPError
+ :returns: dict
+ """
+
+ # The issue was raise on python-ldap repo: https://github.com/python-ldap/python-ldap/issues/304
+ # We should fix the code here after the issue is fixed
+ errmsg = str(exception)
+ try:
+ msg = ast.literal_eval(errmsg)
+ except ValueError:
+ msg = {'desc': errmsg}
+ return msg
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 1 month