Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDB-10162 Cluster node info panel with status change history. #1382

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/i18n/locale-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
"label": "Cluster configuration",
"nodes_list_label": "Nodes list"
},
"node_info": {
"node_state_history": "Node state history"
},
"cluster_status": {
"node": {
"rpc_address": "RPC address",
Expand Down
4 changes: 3 additions & 1 deletion src/js/angular/clustermanagement/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'angular/core/directives';
import 'angular/clustermanagement/controllers/cluster-management.controller';
import 'angular/clustermanagement/directives/cluster-graphical-view.directive';
import 'angular/clustermanagement/directives/cluster-configuration.directive';
import 'angular/clustermanagement/directives/node-info.directive';
import 'angular/core/services/repositories.service';
import 'lib/d3.patch.js';
import 'angular-pageslide-directive/dist/angular-pageslide-directive';
Expand All @@ -12,7 +13,8 @@ const modules = [
'toastr',
'graphdb.framework.clustermanagement.controllers.cluster-management',
'graphdb.framework.clustermanagement.directives.cluster-graphical-view',
'graphdb.framework.clustermanagement.directives.cluster-configuration'
'graphdb.framework.clustermanagement.directives.cluster-configuration',
'graphdb.framework.clustermanagement.directives.node-info'
];

angular.module('graphdb.framework.clustermanagement', modules);
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'angular/clustermanagement/controllers/replace-nodes.controller';
import {isString} from "lodash";
import {LinkState, NodeState} from "../../models/clustermanagement/states";
import {DELETE_CLUSTER, UPDATE_CLUSTER} from "../events";
import {nodeStatusInfoMapper} from "../../rest/mappers/cluster-management-mapper";

const modules = [
'ui.bootstrap',
Expand Down Expand Up @@ -55,19 +56,25 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod
$scope.loader = true;
$scope.isLeader = false;
$scope.currentNode = null;
$scope.nodeStatusInfo = [];
$scope.clusterModel = {};
$scope.NodeState = NodeState;
$scope.leaderChanged = false;
$scope.currentLeader = null;
// Holds child context
// TODO: remove this and replace with events and parameters to the child directive
$scope.childContext = {};
$scope.showClusterConfigurationPanel = false;
$scope.showNodeInfoPanel = false;

// =========================
// Public functions
// =========================

$scope.onopen = $scope.onclose = () => angular.noop();
$scope.onOpenClusterConfig = () => angular.noop();
$scope.onCloseClusterConfig = () => angular.noop();
$scope.onOpenNodeInfo = () => angular.noop();
$scope.onCloseNodeInfo = () => angular.noop();

$scope.isAdmin = () => {
return $jwtAuth.isAuthenticated() && $jwtAuth.isAdmin();
Expand Down Expand Up @@ -103,6 +110,7 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod
$scope.getClusterConfiguration = () => {
return ClusterRestService.getClusterConfig()
.then((response) => {
// console.log(`CLUSTER CONF`, response);
$scope.clusterConfiguration = response.data;
if (!$scope.currentNode) {
return $scope.getCurrentNodeStatus();
Expand All @@ -116,6 +124,7 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod
$scope.getClusterStatus = () => {
return ClusterRestService.getClusterStatus()
.then((response) => {
// console.log(`CLUSTER STATUS`, response);
const nodes = response.data.slice();
const leader = nodes.find((node) => node.nodeState === NodeState.LEADER);

Expand Down Expand Up @@ -177,6 +186,7 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod
$scope.getCurrentNodeStatus = () => {
return ClusterRestService.getNodeStatus()
.then((response) => {
console.log(`NODE STATUS`, response);
$scope.leaderChanged = false;
$scope.currentNode = response.data;
})
Expand Down Expand Up @@ -305,13 +315,42 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod
// Private functions
// =========================

const nodesAreEqual = (node1, node2) => {
return node1.address === node2.address;
};

const selectNode = (node) => {
if ($scope.selectedNode !== node) {
// no selected node
if (!$scope.selectedNode) {
$scope.selectedNode = node;
$scope.showNodeInfoPanel = true;
ClusterViewContextService.showNodeInfoPanel();
} else if (!nodesAreEqual(node, $scope.selectedNode)) {
// currently selected node and the newly selected node are different: select the newly selected node
$scope.selectedNode = node;
if (!$scope.showNodeInfoPanel) {
$scope.showNodeInfoPanel = true;
ClusterViewContextService.showNodeInfoPanel();
}
} else if (!$scope.showNodeInfoPanel) {
// panel was closed and selected node is the same: just reopen it
$scope.showNodeInfoPanel = true;
ClusterViewContextService.showNodeInfoPanel();
} else {
// close the panel and reset the selected node
$scope.selectedNode = null;
$scope.showNodeInfoPanel = false;
ClusterViewContextService.hideNodeInfoPanel();
}
$scope.$apply();
ClusterRestService.getNodeStatusHistory($scope.selectedNode.address).then((response) => {
const model = nodeStatusInfoMapper(response);
console.log(`model`, model);
$scope.nodeStatusInfo = model;
}).catch((error) => {
console.log(`error`, error);
});
// TODO: probably not needed
// $scope.$apply();
};

const updateCluster = (force) => {
Expand Down Expand Up @@ -431,11 +470,10 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod
};

const mousedownHandler = function (event) {
const target = event.target;
const nodeTooltipElement = document.getElementById('nodeTooltip');
if ($scope.selectedNode && nodeTooltipElement !== target && !nodeTooltipElement.contains(target)) {
$scope.childContext.selectNode(null);
}
// reimplement this to close the sidebar when clicking outside of it
// if ($scope.selectedNode) {
// $scope.childContext.selectNode(null);
// }
};

// =========================
Expand All @@ -462,6 +500,14 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod
subscriptions.push($scope.$on(DELETE_CLUSTER, (event, data) => {
deleteCluster(data.force);
}));

subscriptions.push(ClusterViewContextService.onShowNodeInfoPanel((show) => {
$scope.showNodeInfoPanel = show;
}));

subscriptions.push($scope.$on('nodeSelected', (event, node) => {
selectNode(node);
}));
};

$scope.$on('$destroy', function () {
Expand All @@ -476,8 +522,9 @@ function ClusterManagementCtrl($scope, $http, $q, toastr, $repositories, $uibMod

const init = () => {
subscribeHandlers();
$scope.childContext.selectNode = selectNode;
// $scope.childContext.selectNode = selectNode;
$scope.showClusterConfigurationPanel = ClusterViewContextService.getShowClusterConfigurationPanel();
$scope.showNodeInfoPanel = ClusterViewContextService.getShowNodeInfoPanel();

loadInitialData()
.finally(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ angular
.module('graphdb.framework.clustermanagement.directives.cluster-configuration', modules)
.directive('clusterConfiguration', ClusterConfiguration);

ClusterConfiguration.$inject = ['$jwtAuth', '$uibModal', '$translate', 'toastr', 'ClusterViewContextService', 'ClusterRestService'];
ClusterConfiguration.$inject = ['$jwtAuth', '$uibModal', '$translate', 'toastr', 'ClusterViewContextService'];

function ClusterConfiguration($jwtAuth, $uibModal, $translate, toastr, ClusterViewContextService, ClusterRestService) {
function ClusterConfiguration($jwtAuth, $uibModal, $translate, toastr, ClusterViewContextService) {
return {
restrict: 'E',
templateUrl: 'js/angular/clustermanagement/templates/cluster-configuration.html',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ clusterManagementDirectives.directive('clusterGraphicalView', ['$window', 'Local

nodesElements
.on('click', (event, d) => {
scope.childContext.selectNode(d);
scope.$emit('nodeSelected', d);

// position the tooltip according to the node!
const tooltip = d3.select('.nodetooltip');
Expand Down
63 changes: 63 additions & 0 deletions src/js/angular/clustermanagement/directives/node-info.directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {createSingleHexagon} from "../services/cluster-drawing.service";

angular
.module('graphdb.framework.clustermanagement.directives.node-info', [])
.directive('nodeInfo', nodeInfo);

function nodeInfo() {
const directive = {
restrict: 'E',
templateUrl: 'js/angular/clustermanagement/templates/node-info.html',
scope: {
currentNode: '=',
nodeStatusInfo: '='
},
// link: linkFunc,
controller: NodeInfoCtrl,
controllerAs: 'vm',
bindToController: true
};

return directive;

// function linkFunc($scope) {
// $scope.node = $scope.currentNode;
// console.log(`node`, $scope.node);
// }
}

NodeInfoCtrl.$inject = ['$scope', 'ClusterViewContextService'];

function NodeInfoCtrl($scope, ClusterViewContextService) {
const vm = this;

vm.closeNodeInfoPanel = () => {
ClusterViewContextService.hideNodeInfoPanel();
};

const createNodeHexagons = () => {
const nodeHexagons = vm.nodeStatusInfo.map((node, index) => {
return createSingleHexagon(`.item .node-${index}`, 30);
});
console.log(`nodeHexagons`, nodeHexagons, vm.nodeStatusInfo);
};

const subscriptions = [];

const removeAllListeners = () => {
subscriptions.forEach((subscription) => subscription());
};

$scope.$on('$destroy', function () {
removeAllListeners();
});

const init = () => {
console.log(`%cINIT:`, 'background: red', );
subscriptions.push(ClusterViewContextService.onShowNodeInfoPanel((show) => {
console.log(`%cOPENED:`, 'background: green', show);
}));
createNodeHexagons();
};
init();
}
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,23 @@ function createHexagon(nodeGroup, radius) {
.attr("d", d3.line());
}


export function createSingleHexagon(by, radius) {
const _s32 = (Math.sqrt(3) / 2);
const xDiff = 0;
const yDiff = 0;
const points = [[radius + xDiff, yDiff], [radius / 2 + xDiff, radius * _s32 + yDiff], [-radius / 2 + xDiff, radius * _s32 + yDiff],
[-radius + xDiff, yDiff],
[-radius / 2 + xDiff, -radius * _s32 + yDiff], [radius / 2 + xDiff, -radius * _s32 + yDiff], [radius + xDiff, yDiff],
[radius / 2 + xDiff, radius * _s32 + yDiff]];
const svg = d3.select(by);
return svg
.data([points])
.append("path")
.attr('class', 'node member')
.attr("d", d3.line());
}

export function removeEventListeners() {
d3.select(document).selectAll('.node-info-fo').on('.tooltip', null);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ClusterViewContextService.$inject = ['EventEmitterService'];

function ClusterViewContextService(EventEmitterService) {
let _showClusterConfigurationPanel = false;
let _showNodeInfoPanel = false;

function getShowClusterConfigurationPanel() {
return _showClusterConfigurationPanel;
Expand All @@ -28,11 +29,37 @@ function ClusterViewContextService(EventEmitterService) {
return EventEmitterService.subscribe('showClusterConfigurationPanel', () => callback(getShowClusterConfigurationPanel()));
}

function getShowNodeInfoPanel() {
return _showNodeInfoPanel;
}

function setShowNodeInfoPanel(showNodeInfoPanel) {
_showNodeInfoPanel = showNodeInfoPanel;
EventEmitterService.emit('showNodeInfoPanel', getShowNodeInfoPanel());
}

function showNodeInfoPanel() {
setShowNodeInfoPanel(true);
}

function hideNodeInfoPanel() {
setShowNodeInfoPanel(false);
}

function onShowNodeInfoPanel(callback) {
return EventEmitterService.subscribe('showNodeInfoPanel', () => callback(getShowNodeInfoPanel()));
}

return {
getShowClusterConfigurationPanel,
setShowClusterConfigurationPanel,
showClusterConfigurationPanel,
hideClusterConfigurationPanel,
onShowClusterConfigurationPanel
onShowClusterConfigurationPanel,
getShowNodeInfoPanel,
setShowNodeInfoPanel,
showNodeInfoPanel,
hideNodeInfoPanel,
onShowNodeInfoPanel
};
}
44 changes: 44 additions & 0 deletions src/js/angular/clustermanagement/templates/node-info.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<div class="node-info break-word-alt p-1 pt-2">
<button class="close mb-1 ml-1" ng-click="vm.closeNodeInfoPanel()"></button>
<h4 class="hovered-parent">
<a href="{{vm.currentNode.endpoint}}" class="break-word"><strong>{{vm.currentNode.endpoint}}</strong></a>
</h4>

<div class="node-info-details">
<div class="row">
<div class="col-sm-6">{{'cluster_management.cluster_status.node.rpc_address' | translate}}</div>
<div class="col-sm-6 break-word">{{vm.currentNode.address}}</div>
</div>
<div class="row">
<div class="col-sm-6">{{'cluster_management.cluster_status.node.state' | translate}}</div>
<div class="col-sm-6">{{vm.currentNode.nodeState}}</div>
</div>
<div class="row">
<div class="col-sm-6">{{'cluster_management.cluster_status.node.term' | translate}}</div>
<div class="col-sm-6">{{vm.currentNode.term}}</div>
</div>
<div class="row">
<div class="col-sm-6">{{'cluster_management.cluster_status.node.last_log_term' | translate}}</div>
<div class="col-sm-6">{{vm.currentNode.lastLogTerm}}</div>
</div>
<div class="row">
<div class="col-sm-6">{{'cluster_management.cluster_status.node.last_log_index' | translate}}</div>
<div class="col-sm-6">{{vm.currentNode.lastLogIndex}}</div>
</div>
</div>

<div class="node-state-history mt-2">
<h5>{{'cluster_management.node_info.node_state_history' | translate}}</h5>

<div class="node-state-history-list">
<div ng-repeat="nodeInfo in vm.nodeStatusInfo" class="item">
<hr />
<div class="node-{{$index}}">icon: {{nodeInfo.status}}</div>
<div class="info">
<div class="timestamp">{{nodeInfo.timestamp}}</div>
<div class="status">{{nodeInfo.message}}</div>
</div>
</div>
</div>
</div>
</div>
8 changes: 8 additions & 0 deletions src/js/angular/models/clustermanagement/node-status-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class NodeStatusInfo {
constructor(address, status, message, timestamp) {
this.address = address;
this.status = status;
this.message = message;
this.timestamp = timestamp;
}
}
Loading