collabora-online/browser/admin/src/AdminClusterOverview.js

586 lines
21 KiB
JavaScript

/* -*- js-indent-level: 8 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Socket to be intialized on opening the cluster overview page in Admin console
*/
/* global DlgLoading _ AdminSocketBase Admin d3 Map Util DlgYesNo */
var AdminClusterOverview = AdminSocketBase.extend({
constructor: function (host, routeToken) {
this.base(host);
this.routeToken = routeToken;
},
_statsData: new Map(),
_size: 20,
_statIntervalId: 0,
_graphDimensions: {
x: 756,
y: 115,
},
_graphMargins: {
top: 10,
right: 0,
bottom: 10,
left: 80
},
_interval: 5000,
addStat: function (serverId) {
var offset = 0;
var memData = [];
var cpuData = [];
for (var i = 0; i < this._size; i++) {
memData.unshift({ time: -(offset), value: 0 });
cpuData.unshift({ time: -(offset), value: 0 });
offset += this._interval;
}
this._statsData.set(serverId, {
mem: memData,
cpu: cpuData,
});
},
updateStat: function (serverId, mem, cpuUsage) {
var stat = this._statsData.get(serverId);
if (serverId) {
for (var i = stat.mem.length - 1; i > 0; i--) {
stat.mem[i].time = stat.mem[i - 1].time;
stat.cpu[i].time = stat.cpu[i - 1].time;
}
stat.mem.push({ time: 0, value: mem });
stat.cpu.push({ time: 0, value: cpuUsage });
if (stat.mem.length > this._size) {
stat.mem.shift();
}
if (stat.cpu.length > this._size) {
stat.cpu.shift();
}
}
},
deleteStat: function (serverId) {
this._statsData.delete(serverId);
},
createCard: function (server) {
var card = document.createElement('div');
card.className = 'tile is-child card is-rounded';
card.id = 'card-' + server.serverId;
var cardHeader = document.createElement('header');
cardHeader.className = 'card-header is-top-rounded';
var cardTitle = document.createElement('p');
cardTitle.className = 'card-header-title';
if (server.podname) {
cardTitle.textContent = server.podname;
} else {
console.warn('podname doesnot exist, using serverId instead of podname on card title');
cardTitle.textContent = server.serverId;
}
cardHeader.appendChild(cardTitle);
card.appendChild(cardHeader);
var cardContent = document.createElement('div');
cardContent.className = 'card-content';
var mainTile = document.createElement('div');
mainTile.className = 'tile is-fullwidth is-vertical';
var cpuSubTitle = document.createElement('p');
cpuSubTitle.className = 'tile is-fullwidth subtitle';
cpuSubTitle.textContent = _('CPU History');
cpuSubTitle.setAttribute('style', 'margin-bottom: 0rem !important');
mainTile.appendChild(cpuSubTitle);
var data = this._statsData.get(server.serverId);
var cpuGraph = this.createGraph('cpu', data.cpu, server.cpu);
mainTile.appendChild(cpuGraph);
var memorySubTitle = document.createElement('p');
memorySubTitle.className = 'tile is-fullwidth subtitle';
memorySubTitle.textContent = _('Memory History');
memorySubTitle.setAttribute('style', 'margin-bottom: 0rem !important; margin-top:1.5rem');
mainTile.appendChild(memorySubTitle);
var memGraph = this.createGraph('mem', data.mem, Util.humanizeMem(server.memory));
mainTile.appendChild(memGraph);
var horizontalTile = document.createElement('div');
horizontalTile.className = 'tile is-fullwidth';
var routeTokenTile = this.createTile(_('RouteToken'), server.routeToken, 'route');
horizontalTile.appendChild(routeTokenTile);
var serverIdTile = this.createTile(_('ServerId'), server.serverId, 'serverId');
horizontalTile.appendChild(serverIdTile);
cardContent.appendChild(mainTile);
cardContent.appendChild(horizontalTile);
card.appendChild(cardContent);
var parentTile = document.createElement('div');
parentTile.className = 'tile is-parent is-4';
parentTile.appendChild(card);
var tileAncestor = document.getElementById('tileAncestor');
tileAncestor.appendChild(parentTile);
},
updateCardContent: function (cardId, server) {
var card = document.getElementById(cardId);
card.querySelector('#cpu-usage').textContent = server.cpu + '%';
card.querySelector('#mem-con').textContent = Util.humanizeMem(server.memory);
card.querySelector('#route').textContent = server.routeToken;
var data = this._statsData.get(server.serverId);
var memObj = this.getScaleAndLine(data.mem);
var cpuObj = this.getScaleAndLine(data.cpu);
var memSvg = card.querySelector('#mem-graph svg');
d3.select(memSvg).select('path.line')
.datum(data.mem)
.attr('d', memObj.line);
var innerWidth = this._graphDimensions.x - this._graphMargins.left - this._graphMargins.right;
var yAxisMemGenerator = d3.axisLeft(memObj.yScale)
.tickFormat(function (d) {
return Util.humanizeMem(d);
});
yAxisMemGenerator.ticks(2);
yAxisMemGenerator.tickSize(-innerWidth);
var yAxisMem = d3.select(memSvg).select('.y-axis')
.transition()
.duration(500)
.call(yAxisMemGenerator);
yAxisMem.select('.domain').attr('stroke-width', 0);
yAxisMem.selectAll('.tick line').attr('stroke', '#EDEDED');
var cpuSvg = card.querySelector('#cpu-graph svg');
d3.select(cpuSvg).select('path.line')
.datum(data.cpu)
.attr('d', cpuObj.line);
var yAxisCpuGenerator = d3.axisLeft(cpuObj.yScale)
.tickFormat(function (d) {
return d + '%';
});
yAxisCpuGenerator.ticks(2);
yAxisCpuGenerator.tickSize(-innerWidth);
var yAxisCpu = d3.select(cpuSvg).select('.y-axis')
.transition()
.duration(500)
.call(yAxisCpuGenerator);
yAxisCpu.select('.domain').attr('stroke-width', 0);
yAxisCpu.selectAll('.tick line').attr('stroke', '#EDEDED');
},
createGraph: function (graphName, data, currData) {
var tileParent = document.createElement('div');
tileParent.className = 'tile is-parent is-vertical';
var graphChild = document.createElement('div');
graphChild.className = 'tile is-child';
var spanforBullet = document.createElement('span');
spanforBullet.textContent = '● ';
if (graphName == 'cpu') {
var cpuStat = document.createElement('p');
cpuStat.className = 'pb-1 has-text-right';
spanforBullet.style = 'color:blue';
var spanforText = document.createElement('span');
spanforText.id = 'cpu-usage';
spanforText.textContent = currData + '%';
cpuStat.appendChild(spanforBullet);
cpuStat.appendChild(spanforText);
tileParent.appendChild(cpuStat);
} else if (graphName == 'mem') {
var memStat = document.createElement('p');
memStat.className = 'pb-1 has-text-right';
spanforBullet.style = 'color:green';
var spanforText = document.createElement('span');
spanforText.id = 'mem-con';
spanforText.textContent = currData;
memStat.appendChild(spanforBullet);
memStat.appendChild(spanforText);
tileParent.appendChild(memStat);
}
var figure = document.createElement('figure');
figure.id = graphName + '-graph';
var svg = d3.select(figure).append('svg')
.attr('viewBox', '0 0 ' + this._graphDimensions.x + ' ' + this._graphDimensions.y)
.append('g')
.attr('transform', 'translate(' + this._graphMargins.left + ',' + this._graphMargins.top + ')');
var obj = this.getScaleAndLine(data);
var yAxisGenerator;
if (graphName == 'cpu') {
yAxisGenerator = d3.axisLeft(obj.yScale)
.tickFormat(function (d) {
return d + '%';
});
yAxisGenerator.ticks(3);
} else if (graphName == 'mem') {
yAxisGenerator = d3.axisLeft(obj.yScale)
.tickFormat(function (d) {
return Util.humanizeMem(d);
});
yAxisGenerator.ticks(3);
}
var innerWidth = this._graphDimensions.x - this._graphMargins.left - this._graphMargins.right;
yAxisGenerator.tickSize(-innerWidth);
var yAxis = svg.append('g')
.attr('class', 'y-axis axis')
.style('font-size', '1em')
.call(yAxisGenerator);
yAxis.select('.domain').attr('stroke-width', 0);
yAxis.selectAll('.tick line').attr('stroke', '#EDEDED');
// add empty axis
var xAxis = svg.append('g')
.attr('class', 'x-axis axis')
.attr('transform', 'translate(0,' + (this._graphDimensions.y - this._graphMargins.bottom - this._graphMargins.top) + ')')
.call(d3.axisBottom(obj.xScale).tickSize(0).tickValues([]));
xAxis.select('.domain').attr('stroke', '#DBDBDB');
var lineColor = 'blue';
if (graphName == 'mem') {
lineColor = 'green';
}
svg.append('path')
.attr('class', 'line')
.datum(data)
.attr('d', obj.line)
.attr('stroke', lineColor)
.attr('stroke-width', 2)
.attr('stroke-linejoin', 'round')
.attr('fill', 'none');
graphChild.appendChild(figure);
tileParent.appendChild(graphChild);
return tileParent;
},
getScaleAndLine: function (data) {
var xAccessor = function (d) {
return d.time;
};
var yAccessor = function (d) {
return d.value;
};
var xDomain = d3.extent(data, xAccessor);
var yDomain = [0, d3.max(data, yAccessor)];
var width = this._graphDimensions.x - this._graphMargins.left - this._graphMargins.right;
var height = this._graphDimensions.y - this._graphMargins.top - this._graphMargins.bottom;
var xScale = d3.scaleTime()
.domain(xDomain)
.range([0, width]);
var yScale = d3.scaleLinear()
.domain(yDomain)
.range([height, 0]);
var line = d3.line()
.x(function (d) {
return xScale(xAccessor(d));
}).y(function (d) {
return yScale(yAccessor(d));
}).curve(d3.curveBumpX);
return {
xScale: xScale,
yScale: yScale,
line: line,
};
},
createRow: function (filename, mem, pid) {
var row = document.createElement('tr');
row.id = 'doc-' + pid;
var documentCell = document.createElement('td');
documentCell.textContent = filename;
documentCell.className = 'has-text-left';
var memoryCell = document.createElement('td');
memoryCell.textContent = mem;
memoryCell.className = 'has-text-centered docmem';
row.appendChild(documentCell);
row.appendChild(memoryCell);
return row;
},
createDocumentTable: function (documents) {
var table = document.createElement('table');
table.className = 'table is-fullwidth is-striped is-bordered';
var tableHeader = document.createElement('thead');
var tableHeaderRow = document.createElement('tr');
var documentNameHeader = document.createElement('th');
documentNameHeader.textContent = _('Document');
documentNameHeader.className = 'has-text-centered';
var documentMemoryHeader = document.createElement('th');
documentMemoryHeader.textContent = _('Memory');
documentMemoryHeader.className = 'has-text-centered';
tableHeaderRow.appendChild(documentNameHeader);
tableHeaderRow.appendChild(documentMemoryHeader);
tableHeader.appendChild(tableHeaderRow);
table.appendChild(tableHeader);
var tableBody = document.createElement('tbody');
var that = this;
documents.forEach(function (doc) {
var row = that.createRow(doc.documentName, Util.humanizeMem(doc.memoryConsumed), doc.pid);
tableBody.appendChild(row);
});
table.appendChild(tableBody);
return table;
},
createTile: function (heading, value, id) {
var tile = document.createElement('div');
tile.className = 'tile has-text-centered is-fullwidth';
var tileParent = document.createElement('div');
tileParent.className = 'tile is-parent';
var tileChild = document.createElement('div');
tileChild.className = 'tile is-child has-text-centered';
var elementHeading = document.createElement('p');
elementHeading.className = 'heading';
elementHeading.textContent = heading;
var elementValue = document.createElement('p');
elementValue.className = 'subtitle';
elementValue.textContent = value;
elementValue.id = id;
tileChild.appendChild(elementHeading);
tileChild.appendChild(elementValue);
tileParent.appendChild(tileChild);
return tileParent;
},
createAnchor: function (server) {
var anchor = document.createElement('a');
anchor.className = 'list-item';
anchor.id = 'anchor' + server.serverId;
anchor.href = server.ingressUrl + '/browser/dist/admin/admin.html?RouteToken=' + server.routeToken;
anchor.setAttribute('target', '_blank');
if (server.podname) {
anchor.textContent = server.podname;
} else {
console.warn('podname doesnot exist, using serverId instead of podname on anchor tag');
anchor.textContent = server.serverId;
}
return anchor;
},
updateAnchor: function (server) {
var anchor = document.getElementById('anchor' + server.serverId);
anchor.id = 'anchor' + server.serverId;
anchor.href = server.ingressUrl + '/browser/dist/admin/admin.html?RouteToken=' + server.routeToken;
},
onSocketMessage: function (e) {
var textMsg;
if (typeof e.data === 'string') {
textMsg = e.data;
}
else {
textMsg = '';
}
if (textMsg.startsWith('stats')) {
var stats = JSON.parse(textMsg.substring(textMsg.indexOf('[')));
var srvList = document.querySelector('#column-admin-panel .list');
var that = this;
stats.forEach(function (srvStat) {
var anchor = document.getElementById('anchor' + srvStat.serverId);
if (anchor) {
that.updateAnchor(srvStat);
} else {
anchor = that.createAnchor(srvStat);
srvList.appendChild(anchor);
}
var cardId = 'card-' + srvStat.serverId;
var card = document.getElementById(cardId);
if (card) {
that.updateStat(srvStat.serverId, srvStat.memory, srvStat.cpu);
that.updateCardContent(cardId, srvStat);
} else {
that.addStat(srvStat.serverId);
that.createCard(srvStat);
}
});
} else if (textMsg.startsWith('documents')) {
var srvs = JSON.parse(textMsg.substring(textMsg.indexOf('[')));
var that = this;
srvs.forEach(function (srv) {
var cardId = 'card-' + srv.serverId;
var card = document.getElementById(cardId);
if (card && srv.documents.length) {
var table = that.createDocumentTable(srv.documents);
card.querySelector('.card-content').appendChild(table);
}
});
} else if (textMsg.startsWith('adddoc')) {
textMsg = textMsg.substring('adddoc'.length);
var tokens = textMsg.trim().split(' ');
if (tokens.length < 4) {
return;
}
var serverId = tokens[0];
var pid = tokens[1];
var filename = tokens[2];
var mem = tokens[3];
var cardId = 'card-' + serverId;
var card = document.getElementById(cardId);
if (card) {
var table = card.querySelector('table');
if (!table) {
table = this.createDocumentTable([{ documentName: decodeURI(filename), memoryConsumed: mem, pid: pid }]);
card.querySelector('.card-content').appendChild(table);
} else {
var row = this.createRow(decodeURI(filename), Util.humanizeMem(Number.parseInt(mem)), pid);
card.querySelector('tbody').appendChild(row);
}
}
} else if (textMsg.startsWith('rmdoc')) {
textMsg = textMsg.substring('rmdoc'.length);
var tokens = textMsg.trim().split(' ');
if (tokens.length < 3) {
return;
}
var serverId = tokens[0];
var pid = tokens[1];
var cardId = 'card-' + serverId;
var card = document.getElementById(cardId);
if (card) {
card.querySelector('#doc-' + pid).remove();
var rows = card.querySelectorAll('tbody tr');
if (rows.length == 0) {
card.querySelector('table').remove();
}
}
} else if (textMsg.startsWith('propchange')) {
textMsg = textMsg.substring('propchange'.length);
var tokens = textMsg.trim().split(' ');
if (tokens.length < 3) {
return;
}
var serverId = tokens[0];
var pid = tokens[1];
var newMem = tokens[2];
var cardId = 'card-' + serverId;
var card = document.getElementById(cardId);
if (card) {
var row = card.querySelector('#doc-' + pid);
if (row) {
row.querySelector('.docmem').textContent = Util.humanizeMem(Number.parseInt(newMem));
}
}
} else if (textMsg.startsWith('rmsrv')) {
textMsg = textMsg.substring('rmsrv'.length);
var tokens = textMsg.trim().split(' ');
if (tokens.length < 1) {
return;
}
var serverId = tokens[0];
var cardId = 'card-' + serverId;
var card = document.getElementById(cardId).parentElement;
if (card) {
card.remove();
}
var anchor = document.getElementById('anchor' + serverId);
if (anchor) {
anchor.remove();
}
} else if (textMsg.startsWith('scaling')) {
var msg = textMsg.split(' ')[1];
if (msg == 'true') {
var dialog = (new DlgLoading())
.text(_('Please wait kubernetes cluster is scaling...'));
dialog.open();
} else {
this.socket.send('stats');
DlgLoading.close();
}
} else if (textMsg == 'InvalidAuthToken' || textMsg == 'NotAuthenticated') {
var msg;
if (window.location.protocol === 'http:') {
// Browsers refuse to overwrite the jwt cookie in this case.
msg = _('Failed to set jwt authentication cookie over insecure connection');
}
else {
msg = _('Failed to authenticate this session over protocol %0');
msg = msg.replace('%0', window.location.protocol);
}
var dialog = (new DlgYesNo())
.title(_('Warning'))
.text(_(msg))
.yesButtonText(_('OK'))
.noButtonText(_('Cancel'))
.type('warning');
this.pageWillBeRefreshed = true;
dialog.open();
}
},
onSocketOpen: function () {
this.socket.send('auth jwt=' + window.jwtToken + ' routeToken=' + this.routeToken);
this.socket.send('stats');
this.socket.send('documents');
var that = this;
this._statIntervalId = setInterval(function () {
that.socket.send('stats');
}, this._interval);
},
onSocketClose: function () {
clearInterval(this._statIntervalId);
this.base.call(this);
}
});
Admin.ClusterOverview = function (host, routeToken) {
return new AdminClusterOverview(host, routeToken);
};