Loading data/admin.html +55 −0 Changes for data/admin.html: 55 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ <li><a href="#" onclick="loadServices(); return false;">Services</a></li> <li><a href="#" onclick="loadGpos(); return false;">Policies</a></li> <li><a href="#" onclick="loadSessions(); return false;">Sessions</a></li> <li id="nav-cluster" style="display:none"><a href="#" onclick="loadClusterStatus(); return false;">Cluster</a></li> <li><a href="#" onclick="doLogout(); return false;">Logout</a></li> </ul> </div> Loading @@ -50,6 +51,7 @@ <script> var currentDomain = "admin"; var currentView = null; var g_clusterEnabled = false; function ajaxGet(url, callback) { var xhr = new XMLHttpRequest(); Loading Loading @@ -992,6 +994,52 @@ function submitEditGpo(gpid) { }); } /* ---- Cluster Status ---- */ function loadClusterStatus() { currentView = loadClusterStatus; loadDomainSelector(function() { ajaxGet("/settings/" + currentDomain + "/api/clusterstatus", function(err, data) { if (err) { showError(err); return; } if (!data.enabled) { setContent('<p>Cluster mode is not enabled.</p>'); return; } var statusClass = 'msg-ok'; if (data.status === 'DEGRADED') statusClass = 'msg-error'; else if (data.status === 'REBUILD') statusClass = 'msg-error'; var html = '<h3>Cluster Status</h3>'; html += '<table class="admin-table">'; html += '<tr><th>Property</th><th>Value</th></tr>'; html += '<tr><td>Overall Status</td><td class="' + statusClass + '">' + data.status + '</td></tr>'; html += '<tr><td>Nodes Total</td><td>' + data.nodes_total + '</td></tr>'; html += '<tr><td>Nodes Online</td><td>' + data.nodes_online + '</td></tr>'; html += '</table>'; if (data.nodes && data.nodes.length > 0) { html += '<h4>Node Details</h4>'; html += '<table class="admin-table">'; html += '<tr><th>Node</th><th>Status</th><th>Blocks</th><th>Groups</th><th>Peers Online</th></tr>'; for (var i = 0; i < data.nodes.length; i++) { var n = data.nodes[i]; var nclass = (n.status === 'OK') ? 'msg-ok' : 'msg-error'; html += '<tr>'; html += '<td>Node ' + n.index + '</td>'; html += '<td class="' + nclass + '">' + n.status + '</td>'; html += '<td>' + n.blocks + '</td>'; html += '<td>' + n.groups + '</td>'; html += '<td>' + n.peers_online + ' / ' + n.peers_total + '</td>'; html += '</tr>'; } html += '</table>'; } html += '<p style="margin-top:1em"><a class="button" href="#" onclick="loadClusterStatus(); return false;">Refresh</a></p>'; setContent(html); }); }); } /* ---- Logout ---- */ function doLogout() { ajaxPost("/settings/api/logout", {}, function(err, data) { Loading @@ -1001,6 +1049,13 @@ function doLogout() { /* ---- Initial load ---- */ window.onload = function() { // Check if cluster mode is active, show nav item if so ajaxGet("/settings/admin/api/checkpermissions", function(err, data) { if (!err && data && data.cluster) { g_clusterEnabled = true; document.getElementById('nav-cluster').style.display = ''; } }); loadUsers(); }; </script> Loading src/admin.cpp +60 −0 Changes for src/admin.cpp: 60 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ #include "import.h" #include "plugin.h" #include "service.h" #include "cluster.h" namespace authdb { Loading Loading @@ -574,6 +575,63 @@ namespace authdb { // 0 = File, 2 = ClusterStore json_object_object_add(jobj, "backendtype", json_object_new_int(_AdminBackend.getType())); // Cluster info: indicate whether cluster mode is active bool clusterActive = (g_Cluster && g_Cluster->isRunning()); json_object_object_add(jobj, "cluster", json_object_new_boolean(clusterActive)); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Cluster Status ---- */ void apiClusterStatus(libhttppp::HttpRequest &curreq) { json_object *jobj = json_object_new_object(); if (!g_Cluster || !g_Cluster->isRunning()) { json_object_object_add(jobj, "enabled", json_object_new_boolean(false)); sendJson(curreq, jobj); json_object_put(jobj); return; } json_object_object_add(jobj, "enabled", json_object_new_boolean(true)); auto &pclient = g_Cluster->getClient(); if (pclient) { std::lock_guard<std::mutex> lock(g_Cluster->getClientMutex()); auto health = pclient->get_cluster_status(); const char *statusStr = "OK"; if (health.overall == paritypp::node_status::DEGRADED) statusStr = "DEGRADED"; else if (health.overall == paritypp::node_status::REBUILD) statusStr = "REBUILD"; json_object_object_add(jobj, "status", json_object_new_string(statusStr)); json_object_object_add(jobj, "nodes_total", json_object_new_int(health.nodes_total)); json_object_object_add(jobj, "nodes_online", json_object_new_int(health.nodes_online)); json_object *jnodes = json_object_new_array(); for (const auto &[idx, info] : health.node_statuses) { json_object *jn = json_object_new_object(); json_object_object_add(jn, "index", json_object_new_int(static_cast<int>(idx))); const char *nstat = "OK"; if (info.status == paritypp::node_status::DEGRADED) nstat = "DEGRADED"; else if (info.status == paritypp::node_status::REBUILD) nstat = "REBUILD"; json_object_object_add(jn, "status", json_object_new_string(nstat)); json_object_object_add(jn, "blocks", json_object_new_int(info.block_count)); json_object_object_add(jn, "groups", json_object_new_int(info.group_count)); json_object_object_add(jn, "peers_total", json_object_new_int(info.peers_total)); json_object_object_add(jn, "peers_online", json_object_new_int(info.peers_online)); json_object_array_add(jnodes, jn); } json_object_object_add(jobj, "nodes", jnodes); } sendJson(curreq, jobj); json_object_put(jobj); } Loading Loading @@ -1997,6 +2055,8 @@ namespace authdb { } else if (strcmp(apiurl, "vacuum") == 0) { checkDomainAccess(sdat, domain); apiVacuum(curreq, domain); } else if (strcmp(apiurl, "clusterstatus") == 0) { apiClusterStatus(curreq); } else { sendJsonError(curreq, "Unknown API endpoint", 404); } Loading Loading
data/admin.html +55 −0 Changes for data/admin.html: 55 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ <li><a href="#" onclick="loadServices(); return false;">Services</a></li> <li><a href="#" onclick="loadGpos(); return false;">Policies</a></li> <li><a href="#" onclick="loadSessions(); return false;">Sessions</a></li> <li id="nav-cluster" style="display:none"><a href="#" onclick="loadClusterStatus(); return false;">Cluster</a></li> <li><a href="#" onclick="doLogout(); return false;">Logout</a></li> </ul> </div> Loading @@ -50,6 +51,7 @@ <script> var currentDomain = "admin"; var currentView = null; var g_clusterEnabled = false; function ajaxGet(url, callback) { var xhr = new XMLHttpRequest(); Loading Loading @@ -992,6 +994,52 @@ function submitEditGpo(gpid) { }); } /* ---- Cluster Status ---- */ function loadClusterStatus() { currentView = loadClusterStatus; loadDomainSelector(function() { ajaxGet("/settings/" + currentDomain + "/api/clusterstatus", function(err, data) { if (err) { showError(err); return; } if (!data.enabled) { setContent('<p>Cluster mode is not enabled.</p>'); return; } var statusClass = 'msg-ok'; if (data.status === 'DEGRADED') statusClass = 'msg-error'; else if (data.status === 'REBUILD') statusClass = 'msg-error'; var html = '<h3>Cluster Status</h3>'; html += '<table class="admin-table">'; html += '<tr><th>Property</th><th>Value</th></tr>'; html += '<tr><td>Overall Status</td><td class="' + statusClass + '">' + data.status + '</td></tr>'; html += '<tr><td>Nodes Total</td><td>' + data.nodes_total + '</td></tr>'; html += '<tr><td>Nodes Online</td><td>' + data.nodes_online + '</td></tr>'; html += '</table>'; if (data.nodes && data.nodes.length > 0) { html += '<h4>Node Details</h4>'; html += '<table class="admin-table">'; html += '<tr><th>Node</th><th>Status</th><th>Blocks</th><th>Groups</th><th>Peers Online</th></tr>'; for (var i = 0; i < data.nodes.length; i++) { var n = data.nodes[i]; var nclass = (n.status === 'OK') ? 'msg-ok' : 'msg-error'; html += '<tr>'; html += '<td>Node ' + n.index + '</td>'; html += '<td class="' + nclass + '">' + n.status + '</td>'; html += '<td>' + n.blocks + '</td>'; html += '<td>' + n.groups + '</td>'; html += '<td>' + n.peers_online + ' / ' + n.peers_total + '</td>'; html += '</tr>'; } html += '</table>'; } html += '<p style="margin-top:1em"><a class="button" href="#" onclick="loadClusterStatus(); return false;">Refresh</a></p>'; setContent(html); }); }); } /* ---- Logout ---- */ function doLogout() { ajaxPost("/settings/api/logout", {}, function(err, data) { Loading @@ -1001,6 +1049,13 @@ function doLogout() { /* ---- Initial load ---- */ window.onload = function() { // Check if cluster mode is active, show nav item if so ajaxGet("/settings/admin/api/checkpermissions", function(err, data) { if (!err && data && data.cluster) { g_clusterEnabled = true; document.getElementById('nav-cluster').style.display = ''; } }); loadUsers(); }; </script> Loading
src/admin.cpp +60 −0 Changes for src/admin.cpp: 60 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ #include "import.h" #include "plugin.h" #include "service.h" #include "cluster.h" namespace authdb { Loading Loading @@ -574,6 +575,63 @@ namespace authdb { // 0 = File, 2 = ClusterStore json_object_object_add(jobj, "backendtype", json_object_new_int(_AdminBackend.getType())); // Cluster info: indicate whether cluster mode is active bool clusterActive = (g_Cluster && g_Cluster->isRunning()); json_object_object_add(jobj, "cluster", json_object_new_boolean(clusterActive)); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Cluster Status ---- */ void apiClusterStatus(libhttppp::HttpRequest &curreq) { json_object *jobj = json_object_new_object(); if (!g_Cluster || !g_Cluster->isRunning()) { json_object_object_add(jobj, "enabled", json_object_new_boolean(false)); sendJson(curreq, jobj); json_object_put(jobj); return; } json_object_object_add(jobj, "enabled", json_object_new_boolean(true)); auto &pclient = g_Cluster->getClient(); if (pclient) { std::lock_guard<std::mutex> lock(g_Cluster->getClientMutex()); auto health = pclient->get_cluster_status(); const char *statusStr = "OK"; if (health.overall == paritypp::node_status::DEGRADED) statusStr = "DEGRADED"; else if (health.overall == paritypp::node_status::REBUILD) statusStr = "REBUILD"; json_object_object_add(jobj, "status", json_object_new_string(statusStr)); json_object_object_add(jobj, "nodes_total", json_object_new_int(health.nodes_total)); json_object_object_add(jobj, "nodes_online", json_object_new_int(health.nodes_online)); json_object *jnodes = json_object_new_array(); for (const auto &[idx, info] : health.node_statuses) { json_object *jn = json_object_new_object(); json_object_object_add(jn, "index", json_object_new_int(static_cast<int>(idx))); const char *nstat = "OK"; if (info.status == paritypp::node_status::DEGRADED) nstat = "DEGRADED"; else if (info.status == paritypp::node_status::REBUILD) nstat = "REBUILD"; json_object_object_add(jn, "status", json_object_new_string(nstat)); json_object_object_add(jn, "blocks", json_object_new_int(info.block_count)); json_object_object_add(jn, "groups", json_object_new_int(info.group_count)); json_object_object_add(jn, "peers_total", json_object_new_int(info.peers_total)); json_object_object_add(jn, "peers_online", json_object_new_int(info.peers_online)); json_object_array_add(jnodes, jn); } json_object_object_add(jobj, "nodes", jnodes); } sendJson(curreq, jobj); json_object_put(jobj); } Loading Loading @@ -1997,6 +2055,8 @@ namespace authdb { } else if (strcmp(apiurl, "vacuum") == 0) { checkDomainAccess(sdat, domain); apiVacuum(curreq, domain); } else if (strcmp(apiurl, "clusterstatus") == 0) { apiClusterStatus(curreq); } else { sendJsonError(curreq, "Unknown API endpoint", 404); } Loading