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

SHARD-2019 - Prettier monitor-client #34

Merged
merged 4 commits into from
Mar 20, 2025
Merged
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
52 changes: 26 additions & 26 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": ["@typescript-eslint", "security", "xss"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:security/recommended",
"plugin:no-unsanitized/DOM",
"prettier"
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": ["@typescript-eslint", "security", "xss"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:security/recommended",
"plugin:no-unsanitized/DOM",
"prettier"
],
"ignorePatterns": [],
"rules": {
"no-empty": [
1,
{
"allowEmptyCatch": true
}
],
"ignorePatterns": [],
"rules": {
"no-empty": [
1,
{
"allowEmptyCatch": true
}
],
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/member-delimiter-style": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-function-return-type": "error"
}
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/member-delimiter-style": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-function-return-type": "error"
}
}
7 changes: 6 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
public/**/*.ts
build
node_modules
dist
target
bin
.vscode
7 changes: 0 additions & 7 deletions .prettierrc.js

This file was deleted.

6 changes: 2 additions & 4 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
],
};
presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
}
6 changes: 3 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,6 @@ module.exports = {
testEnvironment: 'node',
testTimeout: 30000,
transform: {
'^.+\\.jsx?$': 'babel-jest'
}
};
'^.+\\.jsx?$': 'babel-jest',
},
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@
"prepare": "npm run compile",
"pretest": "npm run compile",
"lint": "eslint \"./public/**/*.ts\"",
"format-check": "prettier --check './public/**/*.ts'",
"format-fix": "prettier --write './public/**/*.ts'",
"format-check": "prettier --check \"**/*.{js,jsx,ts,tsx,json}\"",
"format-fix": "prettier --write \"**/*.{js,jsx,ts,tsx,json}\"",
"release:prepatch": "npm run prepare && npm version prepatch --preid=prerelease && git push --follow-tags && npm publish --tag prerelease",
"release:preminor": "npm run prepare && npm version preminor --preid=prerelease && git push --follow-tags && npm publish --tag prerelease",
"release:premajor": "npm run prepare && npm version premajor --preid=prerelease && git push --follow-tags && npm publish --tag prerelease",
3 changes: 1 addition & 2 deletions prettier.config.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,5 @@ module.exports = {
singleQuote: true,
trailingComma: 'es5',
semi: false,
printWidth: 110,
printWidth: 120,
}

2 changes: 1 addition & 1 deletion public/NoOp.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
// delete me once we fix another file
// delete me once we fix another file
217 changes: 110 additions & 107 deletions public/app-versions.js
Original file line number Diff line number Diff line change
@@ -1,134 +1,137 @@
const INTERVAL = 10_000
const colors = ["#36a2eb", "#ff6384", "#4bc0c0", "#ff9f40", "#9966ff", "#ffcd56", "#c9cbcf"]
const colors = ['#36a2eb', '#ff6384', '#4bc0c0', '#ff9f40', '#9966ff', '#ffcd56', '#c9cbcf']

const fetchChanges = async (animate = false) => {
const data = []
const labels = []
const tooltips = []
const selectedNodeType = nodeTypeSelect.value; // Get the selected node type from the dropdown

const appDataResponse = await requestWithToken(`${monitorServerUrl}/app-versions`)
const appDataList = appDataResponse.data
for(const appVersion in appDataList) {
const activeNodes = appDataList[appVersion].activeNodeCount === undefined || NaN? 0 : appDataList[appVersion].activeNodeCount;
const joiningNodes = appDataList[appVersion].joiningNodeCount === undefined || NaN? 0 : appDataList[appVersion].joiningNodeCount;
const syncingNodes = appDataList[appVersion].syncingNodeCount === undefined || NaN? 0 : appDataList[appVersion].syncingNodeCount;

const count = (activeNodes + joiningNodes) === undefined || NaN? 0 : (activeNodes + joiningNodes);
const nodes = {
active: activeNodes,
joining: joiningNodes,
syncing: syncingNodes,
all: count
};

data.push(nodes);
labels.push(appVersion)
const cliVersions = Object.entries(appDataList[appVersion].cliVersions).map(([version, count]) => `${version}: ${count}`).join("\n");
const guiVersions = Object.entries(appDataList[appVersion].guiVersions).map(([version, count]) => `${version}: ${count}`).join("\n");
tooltips.push(`cliVersions:\n${cliVersions}\nguiVersions:\n${guiVersions}`)
const data = []
const labels = []
const tooltips = []
const selectedNodeType = nodeTypeSelect.value // Get the selected node type from the dropdown

const appDataResponse = await requestWithToken(`${monitorServerUrl}/app-versions`)
const appDataList = appDataResponse.data
for (const appVersion in appDataList) {
const activeNodes =
appDataList[appVersion].activeNodeCount === undefined || NaN ? 0 : appDataList[appVersion].activeNodeCount
const joiningNodes =
appDataList[appVersion].joiningNodeCount === undefined || NaN ? 0 : appDataList[appVersion].joiningNodeCount
const syncingNodes =
appDataList[appVersion].syncingNodeCount === undefined || NaN ? 0 : appDataList[appVersion].syncingNodeCount

const count = activeNodes + joiningNodes === undefined || NaN ? 0 : activeNodes + joiningNodes
const nodes = {
active: activeNodes,
joining: joiningNodes,
syncing: syncingNodes,
all: count,
}

// Sort three arrays according to 'labels' while maintaining the same order
const zipped = labels.map((e, i) => [e, data[i], tooltips[i]])
zipped.sort((a, b) => {
const aParts = a[0].split('.').map(Number);
const bParts = b[0].split('.').map(Number);
data.push(nodes)
labels.push(appVersion)
const cliVersions = Object.entries(appDataList[appVersion].cliVersions)
.map(([version, count]) => `${version}: ${count}`)
.join('\n')
const guiVersions = Object.entries(appDataList[appVersion].guiVersions)
.map(([version, count]) => `${version}: ${count}`)
.join('\n')
tooltips.push(`cliVersions:\n${cliVersions}\nguiVersions:\n${guiVersions}`)
}

for (let i = 0; i < aParts.length; i++) {
if (bParts[i] - aParts[i] !== 0) {
return bParts[i] - aParts[i];
}
}
// Sort three arrays according to 'labels' while maintaining the same order
const zipped = labels.map((e, i) => [e, data[i], tooltips[i]])
zipped.sort((a, b) => {
const aParts = a[0].split('.').map(Number)
const bParts = b[0].split('.').map(Number)

return 0; // If all parts are equal
})
for (let i = 0; i < zipped.length; i++) {
labels[i] = zipped[i][0]
data[i] = zipped[i][1]
tooltips[i] = zipped[i][2]
for (let i = 0; i < aParts.length; i++) {
if (bParts[i] - aParts[i] !== 0) {
return bParts[i] - aParts[i]
}
}

drawPieChart(data, labels, tooltips, animate, selectedNodeType)
writeInfoPanel(data, labels, selectedNodeType)
return 0 // If all parts are equal
})
for (let i = 0; i < zipped.length; i++) {
labels[i] = zipped[i][0]
data[i] = zipped[i][1]
tooltips[i] = zipped[i][2]
}

drawPieChart(data, labels, tooltips, animate, selectedNodeType)
writeInfoPanel(data, labels, selectedNodeType)
}

// From https://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-javascript
const stringToColour = (str) => {
var hash = 0
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash)
}
var color = '#'
for (var i = 0; i < 3; i++) {
var value = (hash >> (i * 8)) & 0xff
color += ('00' + value.toString(16)).substring(-2)
}
console.log('colour', color)
return color
var hash = 0
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash)
}
var color = '#'
for (var i = 0; i < 3; i++) {
var value = (hash >> (i * 8)) & 0xff
color += ('00' + value.toString(16)).substring(-2)
}
console.log('colour', color)
return color
}

const drawPieChart = (data, labels, tooltips, animate, selectedNodeType) => {
let chartStatus = Chart.getChart("app-versions-chart"); // <canvas> id
let chartStatus = Chart.getChart('app-versions-chart') // <canvas> id
if (chartStatus != undefined) {
chartStatus.destroy();
chartStatus.destroy()
}

const canvas = document.getElementById("app-versions-chart");
const ctx = canvas.getContext("2d");
const canvas = document.getElementById('app-versions-chart')
const ctx = canvas.getContext('2d')

const chartOptions = {
plugins: {
tooltip: {
callbacks: {
afterBody: (context) => {
return tooltips[context[0].dataIndex].split("\n")
}
}

}
}
tooltip: {
callbacks: {
afterBody: (context) => {
return tooltips[context[0].dataIndex].split('\n')
},
},
},
},
}

if(!animate) {
if (!animate) {
chartOptions.animation = false
}

let graphType = 'pie'
let datasets

let graphType = "pie";
let datasets;

if (selectedNodeType === "all") {
if (selectedNodeType === 'all') {
datasets = [
{
data: data.map((nodes) => nodes.all),
backgroundColor: colors,
},
];
]
} else {
datasets = [
{
data: data.map((nodes) => nodes[selectedNodeType]),
backgroundColor: colors,
label: selectedNodeType,
},
];
]
}

// Check if all data values are 0
const allZero = datasets.every((dataset) =>
dataset.data.every((value) => value === 0)
);
const allZero = datasets.every((dataset) => dataset.data.every((value) => value === 0))

if (allZero) {
datasets = [
{
data: [1], // Add a dummy value to display an empty pie chart
backgroundColor: ["#808080"], // Set the color to grey
backgroundColor: ['#808080'], // Set the color to grey
borderWidth: 0, // Remove the border
label: "No data available",
label: 'No data available',
},
];
]
}

new Chart(ctx, {
@@ -137,45 +140,45 @@ const drawPieChart = (data, labels, tooltips, animate, selectedNodeType) => {
datasets: datasets,
labels: labels,
},
options: chartOptions
});
options: chartOptions,
})
}

const writeInfoPanel = (data, labels, selectedNodeType) => {
const infoPanel = document.getElementById("app-versions-info");
infoPanel.innerHTML = "";
if (selectedNodeType === "all") {
const totalAll = data.reduce((total, nodes) => total + nodes.all, 0); // Calculate the total count for all nodes
for (let i = 0; i < data.length; i++) {
const total = data[i].active + data[i].joining + data[i].syncing;
const percentage = totalAll > 0 ? Math.round((total / totalAll) * 100) : 0; // Calculate the percentage for each node version
// eslint-disable-next-line no-unsanitized/property
infoPanel.innerHTML += `
const infoPanel = document.getElementById('app-versions-info')
infoPanel.innerHTML = ''
if (selectedNodeType === 'all') {
const totalAll = data.reduce((total, nodes) => total + nodes.all, 0) // Calculate the total count for all nodes
for (let i = 0; i < data.length; i++) {
const total = data[i].active + data[i].joining + data[i].syncing
const percentage = totalAll > 0 ? Math.round((total / totalAll) * 100) : 0 // Calculate the percentage for each node version
// eslint-disable-next-line no-unsanitized/property
infoPanel.innerHTML += `
<div>
<span style="display: inline-block; width: 12px; height: 12px; background-color: ${colors[i]};"></span>
<span style="font-weight: bold;">${labels[i]}:</span> ${total} (${percentage}%)
</div>
`;
}
} else {
const total = data.reduce((total, nodes) => total + nodes[selectedNodeType], 0);
for (let i = 0; i < data.length; i++) {
const percentage = total > 0 ? Math.round((data[i][selectedNodeType] / total) * 100) : 0;
// eslint-disable-next-line no-unsanitized/property
infoPanel.innerHTML += `
`
}
} else {
const total = data.reduce((total, nodes) => total + nodes[selectedNodeType], 0)
for (let i = 0; i < data.length; i++) {
const percentage = total > 0 ? Math.round((data[i][selectedNodeType] / total) * 100) : 0
// eslint-disable-next-line no-unsanitized/property
infoPanel.innerHTML += `
<div>
<span style="display: inline-block; width: 12px; height: 12px; background-color: ${colors[i]};"></span>
<span style="font-weight: bold;">${labels[i]}:</span> ${data[i][selectedNodeType]} (${percentage}%)
</div>
`;
}
`
}
};
}
}

const nodeTypeSelect = document.getElementById("node-type");
nodeTypeSelect.addEventListener("change", () => {
fetchChanges(true);
});
const nodeTypeSelect = document.getElementById('node-type')
nodeTypeSelect.addEventListener('change', () => {
fetchChanges(true)
})

fetchChanges(true)
setInterval(fetchChanges, INTERVAL)
4,043 changes: 1,988 additions & 2,055 deletions public/app.js

Large diffs are not rendered by default.

78 changes: 39 additions & 39 deletions public/auth.js
Original file line number Diff line number Diff line change
@@ -11,58 +11,58 @@ var monitorServerUrl = server.slice(-1) === '/' ? server + 'api' : server + '/ap
console.log('monitor server url', monitorServerUrl)

function redirectToSignIn() {
location.href = 'signin'
location.href = 'signin'
}

async function requestWithToken(url) {
console.log('requesting with token', url)
let token = loadToken()
const options = {
headers: {
'Authorization': `${token}`
}
}
const res = await request.get(url, options)
return res
console.log('requesting with token', url)
let token = loadToken()
const options = {
headers: {
Authorization: `${token}`,
},
}
const res = await request.get(url, options)
return res
}

function loadToken(G) {
let token = localStorage.getItem('token')
if (G) G.token = token
console.log('Token is set to', token)
return token
let token = localStorage.getItem('token')
if (G) G.token = token
console.log('Token is set to', token)
return token
}

async function checkAuthRequirement() {
console.log('Checking auth requirement...')
let token = localStorage.getItem('token')
console.log('Checking auth requirement...')
let token = localStorage.getItem('token')

let res = await request.get(`${monitorServerUrl}/status`)
let env = res.data.env
let res = await request.get(`${monitorServerUrl}/status`)
let env = res.data.env

if (env === 'release' && !token) {
console.log('You are not sign in yet...')
console.log(window)
if (window.location.pathname !== '/signin') setTimeout(redirectToSignIn, 500)
return
}
if (env === 'release' && !token) {
console.log('You are not sign in yet...')
console.log(window)
if (window.location.pathname !== '/signin') setTimeout(redirectToSignIn, 500)
return
}

if (env === 'release' && token != null) {
try {
let response = await request.get(`${monitorServerUrl}/report`, {
headers: {
'Authorization': `${token}`
}
})
if (response.data) {
console.log('Your token is valid')
}
} catch (e) {
console.log('You have invalid token')
if (window.location.pathname !== '/signin') setTimeout(redirectToSignIn, 500)
return
}
if (env === 'release' && token != null) {
try {
let response = await request.get(`${monitorServerUrl}/report`, {
headers: {
Authorization: `${token}`,
},
})
if (response.data) {
console.log('Your token is valid')
}
} catch (e) {
console.log('You have invalid token')
if (window.location.pathname !== '/signin') setTimeout(redirectToSignIn, 500)
return
}
}
}

checkAuthRequirement()
1,454 changes: 1,453 additions & 1 deletion public/axios.min.js

Large diffs are not rendered by default.

9,355 changes: 9,346 additions & 9 deletions public/chart.min.js

Large diffs are not rendered by default.

12,397 changes: 12,396 additions & 1 deletion public/fabric.js

Large diffs are not rendered by default.

334 changes: 167 additions & 167 deletions public/history-log.js
Original file line number Diff line number Diff line change
@@ -1,180 +1,180 @@
const socket = io()
socket.on('connection', async (err, data) => {
console.log('connection is made...', err, data)
console.log('connection is made...', err, data)
})

new Vue({
el: '#app',
data: {
fileContent: '',
history: [],
yValue: [],
xIncrement: [],
xBase: [],
layout: {},
nodeCount: 0,
eventColors: []
},
computed: {
trace() {
return {
x: this.xIncrement,
y: this.yValue,
base: this.xBase,
type: 'bar',
// width: 0.5,
name: 'Sync Duration',
text: this.history.map(event => event.name),
textposition: 'auto',
hoverinfo: 'none',
marker: {
color: this.eventColors,
opacity: 0.6,
line: {
color: 'rgb(8,48,107)',
width: 1.5
}
},
orientation: 'h'
}
}
el: '#app',
data: {
fileContent: '',
history: [],
yValue: [],
xIncrement: [],
xBase: [],
layout: {},
nodeCount: 0,
eventColors: [],
},
computed: {
trace() {
return {
x: this.xIncrement,
y: this.yValue,
base: this.xBase,
type: 'bar',
// width: 0.5,
name: 'Sync Duration',
text: this.history.map((event) => event.name),
textposition: 'auto',
hoverinfo: 'none',
marker: {
color: this.eventColors,
opacity: 0.6,
line: {
color: 'rgb(8,48,107)',
width: 1.5,
},
},
orientation: 'h',
}
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search)
let maxHistory = urlParams.get('max_history')
if (maxHistory) maxHistory = parseInt(maxHistory)
else maxHistory = 1440 // last 24 hours
socket.on('connect', async (e, data) => {
console.log(`Connected as ${socket.id}`, e, data)
})
socket.on('old-data', async (data) => {
this.fileContent += `${data}\n`
socket.emit('message', 'let get started')
let oldEventLines = data.split('\n')
// for (let i = 0; i < oldEventLines.length; i++) {
// let line = oldEventLines[i]
// let splitted = line.split(" ")
// if (splitted[4] === 'started') {
// startLineNumber = i
// }
// }
let startLineNumber = oldEventLines.length - maxHistory
oldEventLines = oldEventLines.slice(startLineNumber + 1)
oldEventLines = oldEventLines.filter(line => line.split(" ").length === 9)
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search)
let maxHistory = urlParams.get('max_history')
if (maxHistory) maxHistory = parseInt(maxHistory)
else maxHistory = 1440 // last 24 hours
socket.on('connect', async (e, data) => {
console.log(`Connected as ${socket.id}`, e, data)
})
socket.on('old-data', async (data) => {
this.fileContent += `${data}\n`
socket.emit('message', 'let get started')
let oldEventLines = data.split('\n')
// for (let i = 0; i < oldEventLines.length; i++) {
// let line = oldEventLines[i]
// let splitted = line.split(" ")
// if (splitted[4] === 'started') {
// startLineNumber = i
// }
// }
let startLineNumber = oldEventLines.length - maxHistory
oldEventLines = oldEventLines.slice(startLineNumber + 1)
oldEventLines = oldEventLines.filter((line) => line.split(' ').length === 9)

let latestCycle = oldEventLines[oldEventLines.length - 1].split(" ")[8]
let latestCycle = oldEventLines[oldEventLines.length - 1].split(' ')[8]

let allHistory = []
let allHistory = []

for (let eachEvent of oldEventLines) {
let splittedData = eachEvent.split(' ')
if (splittedData.length < 2) continue
let name = splittedData[4]
let nodeId = splittedData[5]
let ip = splittedData[6]
let port = splittedData[7]
let cycle = parseInt(splittedData[8])
if (this.validateIPV4(ip) === false) continue
let event = {
name,
nodeId,
ip,
port,
cycle
}
allHistory.push(event)
}
if (latestCycle > maxHistory) {
allHistory = allHistory.filter(event => event.cycle > latestCycle - maxHistory)
}
this.history = [...allHistory]
this.history = this.history.sort((a, b) => a.cycle - b.cycle)
this.updateChart()
})
socket.on('new-history-log', async (data) => {
// this.fileContent += `${data}\n`
let splittedData = data.split(' ')
console.log('splitted data', splittedData)
let name = splittedData[4]
let nodeId = splittedData[5]
let ip = splittedData[6]
let port = splittedData[7]
let cycle = parseInt(splittedData[8])
let event = {
name,
nodeId,
ip,
port,
cycle
}
if (this.validateIPV4(ip) === false) return
this.history.push(event)
this.updateChart()
})
for (let eachEvent of oldEventLines) {
let splittedData = eachEvent.split(' ')
if (splittedData.length < 2) continue
let name = splittedData[4]
let nodeId = splittedData[5]
let ip = splittedData[6]
let port = splittedData[7]
let cycle = parseInt(splittedData[8])
if (this.validateIPV4(ip) === false) continue
let event = {
name,
nodeId,
ip,
port,
cycle,
}
allHistory.push(event)
}
if (latestCycle > maxHistory) {
allHistory = allHistory.filter((event) => event.cycle > latestCycle - maxHistory)
}
this.history = [...allHistory]
this.history = this.history.sort((a, b) => a.cycle - b.cycle)
this.updateChart()
})
socket.on('new-history-log', async (data) => {
// this.fileContent += `${data}\n`
let splittedData = data.split(' ')
console.log('splitted data', splittedData)
let name = splittedData[4]
let nodeId = splittedData[5]
let ip = splittedData[6]
let port = splittedData[7]
let cycle = parseInt(splittedData[8])
let event = {
name,
nodeId,
ip,
port,
cycle,
}
if (this.validateIPV4(ip) === false) return
this.history.push(event)
this.updateChart()
})

console.log('Node history timeline')
let data = [this.trace]
this.layout = {
title: 'Nodes History',
barmode: 'stack',
xaxis: {
autotick: false,
ticks: 'outside',
tick0: 0,
dtick: 1,
ticklen: 8,
tickwidth: 4,
tickcolor: '#000',
},
yaxis: {
automargin: true
}
};
console.log('Node history timeline')
let data = [this.trace]
this.layout = {
title: 'Nodes History',
barmode: 'stack',
xaxis: {
autotick: false,
ticks: 'outside',
tick0: 0,
dtick: 1,
ticklen: 8,
tickwidth: 4,
tickcolor: '#000',
},
yaxis: {
automargin: true,
},
}

Plotly.newPlot('myDiv', data, this.layout, { scrollZoom: true });
this.updateChart()
// setInterval(this.updateChart, 5000)
Plotly.newPlot('myDiv', data, this.layout, { scrollZoom: true })
this.updateChart()
// setInterval(this.updateChart, 5000)
},
methods: {
getChart() {
return this.chart
},
methods: {
getChart() {
return this.chart
},
// a function to check if the input string is a valid IPV4 ip address
validateIPV4(ip) {
let ipArr = ip.split('.')
if (ipArr.length !== 4) return false
for (let i = 0; i < ipArr.length; i++) {
let num = parseInt(ipArr[i])
if (Number.isNaN(num) || num < 0 || num > 255) return false
}
return true
},
async updateChart() {
console.log('updating chart')
const shouldUpdateChart = await this.getReport()
if (shouldUpdateChart) {
let data = [{ ...this.trace }]
Plotly.newPlot('myDiv', data, this.layout)
}
},
async getReport() {
console.log(this.history.map(event => event.ip + "__" + event.port))
this.yValue = this.history.map(event => {
return `${event.ip}:${event.port}`
})
this.xBase = this.history.map(event => {
return event.cycle
})
this.xIncrement = this.history.map(event => {
return event.cycle + 1 - event.cycle
})
this.eventColors = this.history.map(event => {
if (event.name === 'active') return '#00ff00'
else if (event.name === 'joined') return '#ffd480'
else if (event.name === 'removed') return '#00ffff'
else if (event.name === 'dead') return '#ff0000'
})
return true
}
}
// a function to check if the input string is a valid IPV4 ip address
validateIPV4(ip) {
let ipArr = ip.split('.')
if (ipArr.length !== 4) return false
for (let i = 0; i < ipArr.length; i++) {
let num = parseInt(ipArr[i])
if (Number.isNaN(num) || num < 0 || num > 255) return false
}
return true
},
async updateChart() {
console.log('updating chart')
const shouldUpdateChart = await this.getReport()
if (shouldUpdateChart) {
let data = [{ ...this.trace }]
Plotly.newPlot('myDiv', data, this.layout)
}
},
async getReport() {
console.log(this.history.map((event) => event.ip + '__' + event.port))
this.yValue = this.history.map((event) => {
return `${event.ip}:${event.port}`
})
this.xBase = this.history.map((event) => {
return event.cycle
})
this.xIncrement = this.history.map((event) => {
return event.cycle + 1 - event.cycle
})
this.eventColors = this.history.map((event) => {
if (event.name === 'active') return '#00ff00'
else if (event.name === 'joined') return '#ffd480'
else if (event.name === 'removed') return '#00ffff'
else if (event.name === 'dead') return '#ff0000'
})
return true
},
},
})
427 changes: 212 additions & 215 deletions public/history.js

Large diffs are not rendered by default.

2,047 changes: 1,018 additions & 1,029 deletions public/large-network.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions public/log.js
Original file line number Diff line number Diff line change
@@ -7,14 +7,14 @@ new Vue({
el: '#app',
data: {
ip: null,
port: null
port: null,
},
mounted: function () {
// console.log('mounted')
const urlParams = new URLSearchParams(window.location.search)
let ip = urlParams.get('ip')
if (ip === 'localhost' || ip === '127.0.0.1') {
ip = window.location.href.split('//')[1].split(":")[0]
ip = window.location.href.split('//')[1].split(':')[0]
}
console.log('ip', ip)
this.ip = ip
@@ -32,6 +32,6 @@ new Vue({
scrollToLastLine(slotId) {
let textArea = document.getElementById(`output-${slotId}`)
textArea.scrollTop = textArea.scrollHeight
}
}
},
},
})
238 changes: 115 additions & 123 deletions public/monitor-events.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
const INTERVAL = 10_000

const fetchChanges = async () => {
const countedEventsResponse = await requestWithToken('/api/counted-events')
const countedEvents = countedEventsResponse.data

countedEvents.forEach((countedEvent) => {
const eventCategoryEl = createOrGetEventCategoryElement(countedEvent.eventCategory)
const eventNameEl = createOrGetEventNameElement(eventCategoryEl, countedEvent.eventName)
createOrGetEventCountElement(eventNameEl, countedEvent.eventCount)
const nodesListEl = createOrGetNodesList(eventNameEl)
const eventMessagesListEl = createOrGetEventMessagesList(eventNameEl)

for (const nodeId in countedEvent.instanceData) {
const instanceData = countedEvent.instanceData[nodeId]
createOrGetInstanceDataElement(nodesListEl, nodeId, instanceData)
}

for (const eventMessage in countedEvent.eventMessages) {
const eventMessageCount = countedEvent.eventMessages[eventMessage]
createOrGetEventMessageElement(eventMessagesListEl, eventMessage, eventMessageCount)
}
})
const countedEventsResponse = await requestWithToken('/api/counted-events')
const countedEvents = countedEventsResponse.data

countedEvents.forEach((countedEvent) => {
const eventCategoryEl = createOrGetEventCategoryElement(countedEvent.eventCategory)
const eventNameEl = createOrGetEventNameElement(eventCategoryEl, countedEvent.eventName)
createOrGetEventCountElement(eventNameEl, countedEvent.eventCount)
const nodesListEl = createOrGetNodesList(eventNameEl)
const eventMessagesListEl = createOrGetEventMessagesList(eventNameEl)

for (const nodeId in countedEvent.instanceData) {
const instanceData = countedEvent.instanceData[nodeId]
createOrGetInstanceDataElement(nodesListEl, nodeId, instanceData)
}

for (const eventMessage in countedEvent.eventMessages) {
const eventMessageCount = countedEvent.eventMessages[eventMessage]
createOrGetEventMessageElement(eventMessagesListEl, eventMessage, eventMessageCount)
}
})
}

/**
@@ -37,8 +37,7 @@ const eventCategoryToHTMLId = (eventCategory) => `c-${eventCategory}`
* @param {string} eventName
* @returns
*/
const eventNameToHTMLId = (eventCategory, eventName) =>
`${eventCategoryToHTMLId(eventCategory)}-n-${eventName}`
const eventNameToHTMLId = (eventCategory, eventName) => `${eventCategoryToHTMLId(eventCategory)}-n-${eventName}`

/**
* Gets a shorter node ID for display purposes
@@ -53,13 +52,13 @@ const getTruncatedNodeId = (nodeId) => nodeId.substring(0, 10)
const nodeIdToHTMLId = (nodeId) => `node-${nodeId}`

const generateHash = function (num) {
const table = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
let hash = ''
for (let i = 0; i < num; i++) {
const randomIndex = Math.floor(Math.random() * table.length)
hash += table[randomIndex]
}
return hash
const table = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
let hash = ''
for (let i = 0; i < num; i++) {
const randomIndex = Math.floor(Math.random() * table.length)
hash += table[randomIndex]
}
return hash
}

/**
@@ -68,23 +67,22 @@ const generateHash = function (num) {
* @returns
*/
const createOrGetEventCategoryElement = (eventCategory) => {
const eventList = document.getElementById('event-list')
const eventList = document.getElementById('event-list')

const eventCategoryHTMLId = eventCategoryToHTMLId(eventCategory)
const eventCategoryExists = document.getElementById(eventCategoryHTMLId) !== null
const eventCategoryEl =
document.getElementById(eventCategoryHTMLId) ?? document.createElement('details')
const eventCategoryHTMLId = eventCategoryToHTMLId(eventCategory)
const eventCategoryExists = document.getElementById(eventCategoryHTMLId) !== null
const eventCategoryEl = document.getElementById(eventCategoryHTMLId) ?? document.createElement('details')

if (!eventCategoryExists) {
eventCategoryEl.id = eventCategoryHTMLId
eventCategoryEl.innerHTML = `<summary>${eventCategory}</summary>`
eventList.appendChild(eventCategoryEl)
if (!eventCategoryExists) {
eventCategoryEl.id = eventCategoryHTMLId
eventCategoryEl.innerHTML = `<summary>${eventCategory}</summary>`
eventList.appendChild(eventCategoryEl)

const eventNameList = document.createElement('ul')
eventCategoryEl.appendChild(eventNameList)
}
const eventNameList = document.createElement('ul')
eventCategoryEl.appendChild(eventNameList)
}

return eventCategoryEl
return eventCategoryEl
}

/**
@@ -94,23 +92,22 @@ const createOrGetEventCategoryElement = (eventCategory) => {
* @param {string} eventName
*/
const createOrGetEventNameElement = (eventCategoryEl, eventName) => {
const eventNameHTMLId = eventNameToHTMLId(eventCategoryEl.id, eventName)
const eventNameExists = eventCategoryEl.querySelector(`#${eventNameHTMLId}`) !== null
const eventNameEl =
eventCategoryEl.querySelector(`#${eventNameHTMLId}`) ?? document.createElement('details')
const eventNameHTMLId = eventNameToHTMLId(eventCategoryEl.id, eventName)
const eventNameExists = eventCategoryEl.querySelector(`#${eventNameHTMLId}`) !== null
const eventNameEl = eventCategoryEl.querySelector(`#${eventNameHTMLId}`) ?? document.createElement('details')

const eventNamesList = eventCategoryEl.querySelector('ul')
const eventNamesList = eventCategoryEl.querySelector('ul')

if (!eventNameExists) {
eventNameEl.id = eventNameHTMLId
eventNameEl.innerHTML = `<summary>${eventName}</summary>`
eventNamesList.appendChild(eventNameEl)
if (!eventNameExists) {
eventNameEl.id = eventNameHTMLId
eventNameEl.innerHTML = `<summary>${eventName}</summary>`
eventNamesList.appendChild(eventNameEl)

const eventsList = document.createElement('ul')
eventNameEl.appendChild(eventsList)
}
const eventsList = document.createElement('ul')
eventNameEl.appendChild(eventsList)
}

return eventNameEl.querySelector('ul')
return eventNameEl.querySelector('ul')
}

/**
@@ -119,17 +116,16 @@ const createOrGetEventNameElement = (eventCategoryEl, eventName) => {
* @param {number} eventCount
*/
const createOrGetEventCountElement = (eventNameEl, eventCount) => {
const eventCountHTMLId = 'event-count'
const eventCountExists = eventNameEl.querySelector(`#${eventCountHTMLId}`) !== null
const eventCountEl =
eventNameEl.querySelector(`#${eventCountHTMLId}`) ?? document.createElement('div')
const eventCountHTMLId = 'event-count'
const eventCountExists = eventNameEl.querySelector(`#${eventCountHTMLId}`) !== null
const eventCountEl = eventNameEl.querySelector(`#${eventCountHTMLId}`) ?? document.createElement('div')

eventCountEl.id = eventCountHTMLId
eventCountEl.textContent = `Event count: ${eventCount}`
eventCountEl.id = eventCountHTMLId
eventCountEl.textContent = `Event count: ${eventCount}`

if (!eventCountExists) {
eventNameEl.appendChild(eventCountEl)
}
if (!eventCountExists) {
eventNameEl.appendChild(eventCountEl)
}
}

/**
@@ -138,22 +134,21 @@ const createOrGetEventCountElement = (eventNameEl, eventCount) => {
* @returns
*/
const createOrGetNodesList = (eventNameEl) => {
const eventNodesListHTMLId = 'nodes-list'
const eventNodesListExists = eventNameEl.querySelector(`#${eventNodesListHTMLId}`) !== null
const eventNodesListEl =
eventNameEl.querySelector(`#${eventNodesListHTMLId}`) ?? document.createElement('details')
const eventNodesListHTMLId = 'nodes-list'
const eventNodesListExists = eventNameEl.querySelector(`#${eventNodesListHTMLId}`) !== null
const eventNodesListEl = eventNameEl.querySelector(`#${eventNodesListHTMLId}`) ?? document.createElement('details')

if (!eventNodesListExists) {
eventNodesListEl.innerHTML = `<summary>Nodes</summary>`
eventNodesListEl.id = eventNodesListHTMLId
if (!eventNodesListExists) {
eventNodesListEl.innerHTML = `<summary>Nodes</summary>`
eventNodesListEl.id = eventNodesListHTMLId

const nodesList = document.createElement('ul')
eventNodesListEl.appendChild(nodesList)
const nodesList = document.createElement('ul')
eventNodesListEl.appendChild(nodesList)

eventNameEl.appendChild(eventNodesListEl)
}
eventNameEl.appendChild(eventNodesListEl)
}

return eventNodesListEl.querySelector('ul')
return eventNodesListEl.querySelector('ul')
}

/**
@@ -163,28 +158,28 @@ const createOrGetNodesList = (eventNameEl) => {
* @param {{eventCount: number, externalIp: string, externalPort: number}} instanceData
*/
const createOrGetInstanceDataElement = (nodesListEl, nodeId, instanceData) => {
const { eventCount, externalIp, externalPort } = instanceData
const { eventCount, externalIp, externalPort } = instanceData

const truncatedNodeId = getTruncatedNodeId(nodeId)
const nodeHTMLId = nodeIdToHTMLId(truncatedNodeId)
const nodeExists = nodesListEl.querySelector(`#${nodeHTMLId}`) !== null
const nodeEl = nodesListEl.querySelector(`#${nodeHTMLId}`) ?? document.createElement('div')
const truncatedNodeId = getTruncatedNodeId(nodeId)
const nodeHTMLId = nodeIdToHTMLId(truncatedNodeId)
const nodeExists = nodesListEl.querySelector(`#${nodeHTMLId}`) !== null
const nodeEl = nodesListEl.querySelector(`#${nodeHTMLId}`) ?? document.createElement('div')

nodeEl.id = nodeHTMLId
const href = `/log?ip=${externalIp}&port=${externalPort}`
nodeEl.innerHTML = `
nodeEl.id = nodeHTMLId
const href = `/log?ip=${externalIp}&port=${externalPort}`
nodeEl.innerHTML = `
Count for
<a href="${href}" target="_blank" rel="noopener noreferrer">
${truncatedNodeId}
</a>:
${eventCount}
`

if (!nodeExists) {
nodesListEl.appendChild(nodeEl)
if (!nodeExists) {
nodesListEl.appendChild(nodeEl)

nodeEl.classList.add('node')
}
nodeEl.classList.add('node')
}
}

/**
@@ -193,24 +188,22 @@ const createOrGetInstanceDataElement = (nodesListEl, nodeId, instanceData) => {
* @returns
*/
const createOrGetEventMessagesList = (eventNameEl) => {
const eventMessagesListHTMLId = 'event-messages-list'
const eventMessagesListExists =
eventNameEl.querySelector(`#${eventMessagesListHTMLId}`) !== null
const eventMessagesListEl =
eventNameEl.querySelector(`#${eventMessagesListHTMLId}`) ??
document.createElement('details')
const eventMessagesListHTMLId = 'event-messages-list'
const eventMessagesListExists = eventNameEl.querySelector(`#${eventMessagesListHTMLId}`) !== null
const eventMessagesListEl =
eventNameEl.querySelector(`#${eventMessagesListHTMLId}`) ?? document.createElement('details')

if (!eventMessagesListExists) {
eventMessagesListEl.innerHTML = `<summary>Event Messages</summary>`
eventMessagesListEl.id = eventMessagesListHTMLId
if (!eventMessagesListExists) {
eventMessagesListEl.innerHTML = `<summary>Event Messages</summary>`
eventMessagesListEl.id = eventMessagesListHTMLId

const messagesList = document.createElement('ul')
eventMessagesListEl.appendChild(messagesList)
const messagesList = document.createElement('ul')
eventMessagesListEl.appendChild(messagesList)

eventNameEl.appendChild(eventMessagesListEl)
}
eventNameEl.appendChild(eventMessagesListEl)
}

return eventMessagesListEl.querySelector('ul')
return eventMessagesListEl.querySelector('ul')
}

/**
@@ -221,39 +214,38 @@ const createOrGetEventMessagesList = (eventNameEl) => {
* @param {number} eventMessageCount
*/
const createOrGetEventMessageElement = (eventMessagesList, eventMessage, eventMessageCount) => {
const eventMessageHTMLId = 'message' + generateHash(10)
const eventMessageExists = eventMessagesList.querySelector(`#${eventMessageHTMLId}`) !== null
const eventMessageEl =
eventMessagesList.querySelector(`#${eventMessageHTMLId}`) ?? document.createElement('div')
const eventMessageHTMLId = 'message' + generateHash(10)
const eventMessageExists = eventMessagesList.querySelector(`#${eventMessageHTMLId}`) !== null
const eventMessageEl = eventMessagesList.querySelector(`#${eventMessageHTMLId}`) ?? document.createElement('div')

eventMessageEl.id = eventMessageHTMLId
eventMessageEl.textContent = `Message: "${eventMessage}". Count: ${eventMessageCount}`
eventMessageEl.id = eventMessageHTMLId
eventMessageEl.textContent = `Message: "${eventMessage}". Count: ${eventMessageCount}`

if (!eventMessageExists) {
eventMessagesList.appendChild(eventMessageEl)
}
if (!eventMessageExists) {
eventMessagesList.appendChild(eventMessageEl)
}
}

/**
* Filter the rendered nodes by a searched nodeId
* @param {Event} input
*/
const filterByNodesId = (input) => {
const searchString = input.target.value
const truncatedSearchString = getTruncatedNodeId(searchString)
const searchString = input.target.value
const truncatedSearchString = getTruncatedNodeId(searchString)

const allNodes = document.querySelectorAll('.node')
allNodes.forEach((node) => {
// Remove "node-" prefix
const nodeId = node.id.substring(5)
node.classList.remove('hidden')
const allNodes = document.querySelectorAll('.node')
allNodes.forEach((node) => {
// Remove "node-" prefix
const nodeId = node.id.substring(5)
node.classList.remove('hidden')

const nodeContainsSearchString = nodeId.startsWith(truncatedSearchString)
const nodeContainsSearchString = nodeId.startsWith(truncatedSearchString)

if (!nodeContainsSearchString) {
node.classList.add('hidden')
}
})
if (!nodeContainsSearchString) {
node.classList.add('hidden')
}
})
}

fetchChanges()
579 changes: 290 additions & 289 deletions public/myChart.js

Large diffs are not rendered by default.

223 changes: 113 additions & 110 deletions public/node-loads.js
Original file line number Diff line number Diff line change
@@ -1,125 +1,128 @@
(function main() {
;(function main() {
const G = {}
loadToken(G)
G.monitorServerUrl = monitorServerUrl || `https://127.0.0.1:3000/api`
G.REFRESH_TIME = 10000

new Vue({
el: '#app',
data() {
return {
nodeLoads: [],
sortKey: 'ip',
sortAsc: true,
}
},
computed: {
sortedNodes() {
return this.nodeLoads.sort((a, b) => {
let modifier = this.sortAsc ? 1 : -1
const valueA = a[this.sortKey]
const valueB = b[this.sortKey]
el: '#app',
data() {
return {
nodeLoads: [],
sortKey: 'ip',
sortAsc: true,
}
},
computed: {
sortedNodes() {
return this.nodeLoads.sort((a, b) => {
let modifier = this.sortAsc ? 1 : -1
const valueA = a[this.sortKey]
const valueB = b[this.sortKey]

if (typeof valueA === 'number' && typeof valueB === 'number') {
return (valueA - valueB) * modifier
}
if (typeof valueA === 'number' && typeof valueB === 'number') {
return (valueA - valueB) * modifier
}

if (valueA < valueB) return -1 * modifier
if (valueA > valueB) return 1 * modifier
return 0
})
},
if (valueA < valueB) return -1 * modifier
if (valueA > valueB) return 1 * modifier
return 0
})
},
},
methods: {
sortTable(key) {
if (this.sortKey === key) {
this.sortAsc = !this.sortAsc
} else {
this.sortKey = key
this.sortAsc = true
}
},
methods: {
sortTable(key) {
if (this.sortKey === key) {
this.sortAsc = !this.sortAsc
} else {
this.sortKey = key
this.sortAsc = true
}
},
async fetchChanges() {
const results = await Promise.all([
requestWithToken(
`${G.monitorServerUrl}/report?timestamp=${G.lastUpdatedTimestamp}`
),
requestWithToken(
`${G.monitorServerUrl}/list-foundation-nodes`
),
])
const listOfFoundationNodes = results[1].data
const data = results[0].data
const activeNodesIds = Object.keys(data.nodes.active)
for (let i = 0; i < activeNodesIds.length; i++) {
const nodeId = activeNodesIds[i]
data.nodes.active[nodeId].nodeIsFoundationNode = listOfFoundationNodes.includes(data.nodes.active[nodeId].nodeIpInfo.externalIp)
}
return data
},
updateNetworkStatus(report) {
this.nodeLoads = []
for (let nodeId in report.nodes.active) {
const node = report.nodes.active[nodeId]
this.nodeLoads.push({
id: nodeId,
ip: node.nodeIpInfo.externalIp,
port: node.nodeIpInfo.externalPort,
loadInternal: node.currentLoad.nodeLoad.internal.toFixed(3),
loadExternal: node.currentLoad.nodeLoad.external.toFixed(3),
queueLengthAll: node.queueLengthAll || 0,
queueLength: node.queueLength || 0,
bucket15: node.queueLengthBuckets?.c15 || 0,
bucket60: node.queueLengthBuckets?.c60 || 0,
bucket120: node.queueLengthBuckets?.c120 || 0,
bucket600: node.queueLengthBuckets?.c600 || 0,
avgQueueTime: node.txTimeInQueue.toFixed(3),
maxQueueTime: node.maxTxTimeInQueue.toFixed(3),
memoryRss: node.memory?.rss || 0,
memoryHeapTotal: node.memory?.heapTotal || 0,
memoryHeapUsed: node.memory?.heapUsed || 0,
memoryExternal: node.memory?.external || 0,
memoryArrayBuffers: node.memory?.arrayBuffers || 0,
nodeIsFoundationNode: node.nodeIsFoundationNode,
appStartupTimestamp: node.appData.appStartupTimestamp,
activeTimestamp: node.activeTimestamp
})
}
},
formatMilliseconds(ms) {
if (!ms) return "N/A"
let seconds = Math.floor(ms / 1000);
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
async fetchChanges() {
const results = await Promise.all([
requestWithToken(`${G.monitorServerUrl}/report?timestamp=${G.lastUpdatedTimestamp}`),
requestWithToken(`${G.monitorServerUrl}/list-foundation-nodes`),
])
const listOfFoundationNodes = results[1].data
const data = results[0].data
const activeNodesIds = Object.keys(data.nodes.active)
for (let i = 0; i < activeNodesIds.length; i++) {
const nodeId = activeNodesIds[i]
data.nodes.active[nodeId].nodeIsFoundationNode = listOfFoundationNodes.includes(
data.nodes.active[nodeId].nodeIpInfo.externalIp
)
}
return data
},
updateNetworkStatus(report) {
this.nodeLoads = []
for (let nodeId in report.nodes.active) {
const node = report.nodes.active[nodeId]
this.nodeLoads.push({
id: nodeId,
ip: node.nodeIpInfo.externalIp,
port: node.nodeIpInfo.externalPort,
loadInternal: node.currentLoad.nodeLoad.internal.toFixed(3),
loadExternal: node.currentLoad.nodeLoad.external.toFixed(3),
queueLengthAll: node.queueLengthAll || 0,
queueLength: node.queueLength || 0,
bucket15: node.queueLengthBuckets?.c15 || 0,
bucket60: node.queueLengthBuckets?.c60 || 0,
bucket120: node.queueLengthBuckets?.c120 || 0,
bucket600: node.queueLengthBuckets?.c600 || 0,
avgQueueTime: node.txTimeInQueue.toFixed(3),
maxQueueTime: node.maxTxTimeInQueue.toFixed(3),
memoryRss: node.memory?.rss || 0,
memoryHeapTotal: node.memory?.heapTotal || 0,
memoryHeapUsed: node.memory?.heapUsed || 0,
memoryExternal: node.memory?.external || 0,
memoryArrayBuffers: node.memory?.arrayBuffers || 0,
nodeIsFoundationNode: node.nodeIsFoundationNode,
appStartupTimestamp: node.appData.appStartupTimestamp,
activeTimestamp: node.activeTimestamp,
})
}
},
formatMilliseconds(ms) {
if (!ms) return 'N/A'
let seconds = Math.floor(ms / 1000)
let minutes = Math.floor(seconds / 60)
let hours = Math.floor(minutes / 60)

seconds = seconds % 60;
minutes = minutes % 60;
seconds = seconds % 60
minutes = minutes % 60

// Pad with leading zeros if necessary
let formattedTime =
(hours < 10 ? '0' : '') + hours + ':' +
(minutes < 10 ? '0' : '') + minutes + ':' +
(seconds < 10 ? '0' : '') + seconds;
// Pad with leading zeros if necessary
let formattedTime =
(hours < 10 ? '0' : '') +
hours +
':' +
(minutes < 10 ? '0' : '') +
minutes +
':' +
(seconds < 10 ? '0' : '') +
seconds

return formattedTime;
},
async updateNodes() {
try {
let changes = await this.fetchChanges()
console.log({method: 'updateNodes', changes})
this.updateNetworkStatus(changes)
} catch (e) {
console.log('Error while trying to update nodes.', e)
}
},
start() {
this.updateNodes()
setInterval(this.updateNodes, G.REFRESH_TIME)
},
return formattedTime
},
async updateNodes() {
try {
let changes = await this.fetchChanges()
console.log({ method: 'updateNodes', changes })
this.updateNetworkStatus(changes)
} catch (e) {
console.log('Error while trying to update nodes.', e)
}
},
mounted() {
console.log('Mounted')
this.start()
start() {
this.updateNodes()
setInterval(this.updateNodes, G.REFRESH_TIME)
},
},
mounted() {
console.log('Mounted')
this.start()
},
})
})()
})()
860 changes: 859 additions & 1 deletion public/popmotion.min.js

Large diffs are not rendered by default.

56 changes: 28 additions & 28 deletions public/signin.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
var request = axios.default
new Vue({
el: '#app',
data: {
username: 'admin',
password: 'password'
el: '#app',
data: {
username: 'admin',
password: 'password',
},
mounted: function () {
console.log('mounted')
},
methods: {
async onSubmit(e, slotId) {
console.log('slotID', slotId)
e.preventDefault()
await this.signIn({
username: this.username,
password: this.password,
})
},
mounted: function () {
console.log('mounted')
async signIn(payload) {
const res = await request.post(`${monitorServerUrl}/signin`, payload)
if (res.data && res.data.token) {
console.log('SingIn Successful', res.data.token)
localStorage.setItem('token', res.data.token)
location.href = '/'
} else {
alert('Incorrect username or password')
this.username = ''
this.password = ''
}
},
methods: {
async onSubmit(e, slotId) {
console.log('slotID', slotId)
e.preventDefault()
await this.signIn({
username: this.username,
password: this.password
})
},
async signIn(payload) {
const res = await request.post(`${monitorServerUrl}/signin`, payload)
if (res.data && res.data.token) {
console.log('SingIn Successful', res.data.token)
localStorage.setItem('token', res.data.token)
location.href = "/"
} else {
alert('Incorrect username or password')
this.username = ''
this.password = ''
}
}
}
},
})
695 changes: 339 additions & 356 deletions public/sync-detail.js

Large diffs are not rendered by default.

173 changes: 86 additions & 87 deletions public/sync.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,92 @@

function initSyncChart() {
new Vue({
el: '#app',
data: {
yValue: [],
xIncrement: [],
xBase: [],
layout: {},
nodeCount: 0
},
computed: {
trace() {
return {
x: this.xIncrement,
y: this.yValue,
base: this.xBase,
type: 'bar',
// width: 0.5,
name: 'Sync Duration',
text: this.xIncrement.map(String),
textposition: 'auto',
hoverinfo: 'none',
marker: {
color: 'rgb(158,202,225)',
opacity: 0.6,
line: {
color: 'rgb(8,48,107)',
width: 1.5
}
},
orientation: 'h'
}
}
},
mounted: function () {
console.log('Sync page loaded!')
let data = [this.trace]
this.layout = {
title: 'Node Sync Timeline',
barmode: 'stack',
xaxis: {
autotick: false,
ticks: 'outside',
tick0: 0,
dtick: 1,
ticklen: 8,
tickwidth: 4,
tickcolor: '#000',
},
};

Plotly.newPlot('myDiv', data, this.layout, { scrollZoom: true });
this.updateChart()
setInterval(this.updateChart, 5000)
},
methods: {
getChart() {
return this.chart
new Vue({
el: '#app',
data: {
yValue: [],
xIncrement: [],
xBase: [],
layout: {},
nodeCount: 0,
},
computed: {
trace() {
return {
x: this.xIncrement,
y: this.yValue,
base: this.xBase,
type: 'bar',
// width: 0.5,
name: 'Sync Duration',
text: this.xIncrement.map(String),
textposition: 'auto',
hoverinfo: 'none',
marker: {
color: 'rgb(158,202,225)',
opacity: 0.6,
line: {
color: 'rgb(8,48,107)',
width: 1.5,
},
async updateChart() {
const shouldUpdateChart = await this.getReport()
if (shouldUpdateChart) {
let data = [{ ...this.trace }]
Plotly.newPlot('myDiv', data, this.layout)
}
},
async getReport() {
const response = await requestWithToken(`${monitorServerUrl}/sync-report`)
const heartbeatResponse = await requestWithToken(`${monitorServerUrl}/report`)
const report = response.data
if (Object.keys(report).length === 0) return
if (Object.keys(report).length <= this.nodeCount) return false
else this.nodeCount = Object.keys(report).length
},
orientation: 'h',
}
},
},
mounted: function () {
console.log('Sync page loaded!')
let data = [this.trace]
this.layout = {
title: 'Node Sync Timeline',
barmode: 'stack',
xaxis: {
autotick: false,
ticks: 'outside',
tick0: 0,
dtick: 1,
ticklen: 8,
tickwidth: 4,
tickcolor: '#000',
},
}

let newLables = Object.keys(report)
this.yValue = newLables.map(nodeId => {
let node = heartbeatResponse.data.nodes.syncing[nodeId] || heartbeatResponse.data.nodes.active[nodeId]
if (node) return `${node.nodeIpInfo.externalIp}:${node.nodeIpInfo.externalPort}`
else return nodeId
})
this.xBase = Object.values(report).map(r => {
return r.cycleStarted
})
this.xIncrement = Object.values(report).map(r => {
return r.cycleEnded - r.cycleStarted
})
return true
}
Plotly.newPlot('myDiv', data, this.layout, { scrollZoom: true })
this.updateChart()
setInterval(this.updateChart, 5000)
},
methods: {
getChart() {
return this.chart
},
async updateChart() {
const shouldUpdateChart = await this.getReport()
if (shouldUpdateChart) {
let data = [{ ...this.trace }]
Plotly.newPlot('myDiv', data, this.layout)
}
})
},
async getReport() {
const response = await requestWithToken(`${monitorServerUrl}/sync-report`)
const heartbeatResponse = await requestWithToken(`${monitorServerUrl}/report`)
const report = response.data
if (Object.keys(report).length === 0) return
if (Object.keys(report).length <= this.nodeCount) return false
else this.nodeCount = Object.keys(report).length

let newLables = Object.keys(report)
this.yValue = newLables.map((nodeId) => {
let node = heartbeatResponse.data.nodes.syncing[nodeId] || heartbeatResponse.data.nodes.active[nodeId]
if (node) return `${node.nodeIpInfo.externalIp}:${node.nodeIpInfo.externalPort}`
else return nodeId
})
this.xBase = Object.values(report).map((r) => {
return r.cycleStarted
})
this.xIncrement = Object.values(report).map((r) => {
return r.cycleEnded - r.cycleStarted
})
return true
},
},
})
}
initSyncChart()
25 changes: 12 additions & 13 deletions public/version.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@

async function getVersionNumbers() {
const versionResponse = await requestWithToken(`${monitorServerUrl}/version`)
const versionResponse = await requestWithToken(`${monitorServerUrl}/version`)

console.log("test string:", versionResponse.data)
clientVersion = versionResponse.data.clientPackageVersion
serverVersion = versionResponse.data.serverPackageVersion
console.log('test string:', versionResponse.data)
clientVersion = versionResponse.data.clientPackageVersion
serverVersion = versionResponse.data.serverPackageVersion

const clientVersionEle = document.getElementById("client-version")
if (clientVersionEle){
clientVersionEle.textContent = clientVersion
}
const clientVersionEle = document.getElementById('client-version')
if (clientVersionEle) {
clientVersionEle.textContent = clientVersion
}

const serverVersionEle = document.getElementById("server-version")
if (serverVersionEle){
serverVersionEle.textContent = serverVersion
}
const serverVersionEle = document.getElementById('server-version')
if (serverVersionEle) {
serverVersionEle.textContent = serverVersion
}
}

getVersionNumbers()
209 changes: 103 additions & 106 deletions public/vis-network-animated.js
Original file line number Diff line number Diff line change
@@ -8,19 +8,19 @@
* @param {*} edgesTrafficList
*/
vis.Network.prototype.animateTraffic = function ({ edgesTrafficList, animationDuration = 1500 }) {
const network = this // The current Network instance
const trafficCanvas = getNetworkTrafficCanvas(network)
const network = this // The current Network instance
const trafficCanvas = getNetworkTrafficCanvas(network)

const ctx = trafficCanvas.getContext('2d')
const ctx = trafficCanvas.getContext('2d')

var s = network.getScale() // edgeTraffic.edge.body.view.scale;
var t = network.body.view.translation //edgeTraffic.edge.body.view.translation;
var s = network.getScale() // edgeTraffic.edge.body.view.scale;
var t = network.body.view.translation //edgeTraffic.edge.body.view.translation;

ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.translate(t.x, t.y)
ctx.scale(s, s)
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.translate(t.x, t.y)
ctx.scale(s, s)

animate(ctx, network, edgesTrafficList, animationDuration)
animate(ctx, network, edgesTrafficList, animationDuration)
}

/**
@@ -30,40 +30,39 @@ vis.Network.prototype.animateTraffic = function ({ edgesTrafficList, animationDu
*/
let currentCanvas = 0
const getNetworkTrafficCanvas = (network) => {
let trafficCanvas =
network.body.container.getElementsByClassName('networkTrafficCanvas')[currentCanvas]
currentCanvas = (currentCanvas + 1) % 2

if (trafficCanvas === undefined) {
var frame = network.canvas.frame
trafficCanvas = document.createElement('canvas')
trafficCanvas.className = 'networkTrafficCanvas'
trafficCanvas.style.position = 'absolute'
trafficCanvas.style.top = trafficCanvas.style.left = 0
trafficCanvas.style.pointerEvents = 'none'
trafficCanvas.style.width = frame.style.width
trafficCanvas.style.height = frame.style.height
trafficCanvas.width = frame.canvas.clientWidth
trafficCanvas.height = frame.canvas.clientHeight

frame.appendChild(trafficCanvas)
}

return trafficCanvas
let trafficCanvas = network.body.container.getElementsByClassName('networkTrafficCanvas')[currentCanvas]
currentCanvas = (currentCanvas + 1) % 2

if (trafficCanvas === undefined) {
var frame = network.canvas.frame
trafficCanvas = document.createElement('canvas')
trafficCanvas.className = 'networkTrafficCanvas'
trafficCanvas.style.position = 'absolute'
trafficCanvas.style.top = trafficCanvas.style.left = 0
trafficCanvas.style.pointerEvents = 'none'
trafficCanvas.style.width = frame.style.width
trafficCanvas.style.height = frame.style.height
trafficCanvas.width = frame.canvas.clientWidth
trafficCanvas.height = frame.canvas.clientHeight

frame.appendChild(trafficCanvas)
}

return trafficCanvas
}

/**
* Clears the current networkTrafficCanvas
* @param {*} ctx Canvas context
*/
const clearAnimationCanvas = (ctx) => {
const canvasWidth = ctx.canvas.width
const canvasHeight = ctx.canvas.height
const canvasWidth = ctx.canvas.width
const canvasHeight = ctx.canvas.height

ctx.save()
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
ctx.restore()
ctx.save()
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
ctx.restore()
}

/**
@@ -73,88 +72,86 @@ const clearAnimationCanvas = (ctx) => {
* @returns
*/
const parseEdgeTraffic = (edgeTraffic, network) => {
const edge = edgeTraffic.edge.edgeType
? edgeTraffic.edge
: network.body.edges[edgeTraffic.edge.id] || network.body.edges[edgeTraffic.edge]

return {
delayArray: edgeTraffic.delayArray,
edge: edge,
trafficSize: edgeTraffic.trafficSize || 2,
numTraffic: edgeTraffic.numTraffic || 5,
trafficStyle: edgeTraffic.trafficStyle || {},
delay: edgeTraffic.delay || 0,
}
const edge = edgeTraffic.edge.edgeType
? edgeTraffic.edge
: network.body.edges[edgeTraffic.edge.id] || network.body.edges[edgeTraffic.edge]

return {
delayArray: edgeTraffic.delayArray,
edge: edge,
trafficSize: edgeTraffic.trafficSize || 2,
numTraffic: edgeTraffic.numTraffic || 5,
trafficStyle: edgeTraffic.trafficStyle || {},
delay: edgeTraffic.delay || 0,
}
}

const animate = (ctx, network, edgesTrafficList, duration) => {
let start
const stopAt = 1 // Stop when the animation has been running for this much of the duration
const reportedErrors = {} // Helps to avoid reporting the same error in multiple setTimeout events

const drawTrafficOnEdge = (p, { trafficSize, trafficStyle }) => {
ctx.beginPath()
ctx.arc(p.x, p.y, parseInt(trafficSize) || 1, 0, Math.PI * 2, false)
ctx.lineWidth = 1
ctx.strokeWidth = 4
ctx.strokeStyle = trafficStyle.strokeStyle ?? 'rgba(57,138,255,0.1)'
ctx.fillStyle = trafficStyle.fillStyle ?? '#1262e3'
ctx.fill()
ctx.stroke()
ctx.closePath()
let start
const stopAt = 1 // Stop when the animation has been running for this much of the duration
const reportedErrors = {} // Helps to avoid reporting the same error in multiple setTimeout events

const drawTrafficOnEdge = (p, { trafficSize, trafficStyle }) => {
ctx.beginPath()
ctx.arc(p.x, p.y, parseInt(trafficSize) || 1, 0, Math.PI * 2, false)
ctx.lineWidth = 1
ctx.strokeWidth = 4
ctx.strokeStyle = trafficStyle.strokeStyle ?? 'rgba(57,138,255,0.1)'
ctx.fillStyle = trafficStyle.fillStyle ?? '#1262e3'
ctx.fill()
ctx.stroke()
ctx.closePath()
}

const animateFrame = (timestamp) => {
if (start === undefined) {
start = timestamp
}

const animateFrame = (timestamp) => {
if (start === undefined) {
start = timestamp
}
clearAnimationCanvas(ctx)

clearAnimationCanvas(ctx)
const offset = (timestamp - start) / duration
if (offset > stopAt) {
return
}
const offsetInMS = timestamp - start

const parsedEdgeTrafficList = edgesTrafficList.map((edgeTraffic) => parseEdgeTraffic(edgeTraffic, network))

const offset = (timestamp - start) / duration
if (offset > stopAt) {
return
for (const edgeTraffic of parsedEdgeTrafficList) {
if (!edgeTraffic.edge) {
if (!reportedErrors[edgeTraffic]) {
console.error('No edge path defined: ', edgeTraffic)
reportedErrors[edgeTraffic] = true
}
const offsetInMS = timestamp - start

const parsedEdgeTrafficList = edgesTrafficList.map((edgeTraffic) =>
parseEdgeTraffic(edgeTraffic, network)
)

for (const edgeTraffic of parsedEdgeTrafficList) {
if (!edgeTraffic.edge) {
if (!reportedErrors[edgeTraffic]) {
console.error('No edge path defined: ', edgeTraffic)
reportedErrors[edgeTraffic] = true
}
continue
}

const numTraffic = edgeTraffic.numTraffic

// Draw multiple dots so it looks like a stream
for (let trafficIndex = 0; trafficIndex < numTraffic; trafficIndex++) {
//let delay = edgeTraffic.delay * ((trafficIndex+1) / numTraffic)
let delay = edgeTraffic.delayArray[trafficIndex]

let location
if (offsetInMS < delay) {
location = 0
} else if (offsetInMS > delay + 500) {
location = stopAt
} else {
location = (offsetInMS - delay) / 500
}

if (location === 0 || location === stopAt) continue

var p = edgeTraffic.edge.edgeType.getPoint(location)
drawTrafficOnEdge(p, edgeTraffic)
}
continue
}

const numTraffic = edgeTraffic.numTraffic

// Draw multiple dots so it looks like a stream
for (let trafficIndex = 0; trafficIndex < numTraffic; trafficIndex++) {
//let delay = edgeTraffic.delay * ((trafficIndex+1) / numTraffic)
let delay = edgeTraffic.delayArray[trafficIndex]

let location
if (offsetInMS < delay) {
location = 0
} else if (offsetInMS > delay + 500) {
location = stopAt
} else {
location = (offsetInMS - delay) / 500
}

requestAnimationFrame(animateFrame)
if (location === 0 || location === stopAt) continue

var p = edgeTraffic.edge.edgeType.getPoint(location)
drawTrafficOnEdge(p, edgeTraffic)
}
}

requestAnimationFrame(animateFrame)
}

requestAnimationFrame(animateFrame)
}
91 changes: 44 additions & 47 deletions tests/navigation.test.js
Original file line number Diff line number Diff line change
@@ -3,56 +3,53 @@ import fs from 'fs'
import path from 'path'

describe('Monitor Navigation Tests', () => {
let navigationHtml;

beforeAll(() => {
// Read the actual navigation HTML file
navigationHtml = fs.readFileSync(
path.join(process.cwd(), 'views/shared/navigation.html'),
'utf8'
);
let navigationHtml

beforeAll(() => {
// Read the actual navigation HTML file
navigationHtml = fs.readFileSync(path.join(process.cwd(), 'views/shared/navigation.html'), 'utf8')
})

beforeEach(() => {
// setup
})

describe('Basic Navigation Tests', () => {
it('should contain all required navigation sections', () => {
// Check for main sections
expect(navigationHtml).toContain('<div class="menu-title">Main</div>')
expect(navigationHtml).toContain('<div class="menu-title">Logs</div>')
expect(navigationHtml).toContain('<div class="menu-title">Network</div>')
expect(navigationHtml).toContain('<div class="menu-title">Analytics</div>')
})

it('should have correct navigation links in Main section', () => {
// Check for specific links in the Main section
expect(navigationHtml).toContain('<a href="/" class="nav-link">Home</a>')
expect(navigationHtml).toContain('<a href="/large-network" class="nav-link highlight">Large Network View</a>')
expect(navigationHtml).toContain('<a href="/signin" class="nav-link">Sign In</a>')
})

it('should have correct navigation links in Logs section', () => {
// Check for specific links in the Logs section
expect(navigationHtml).toContain('<a href="/log" class="nav-link">Log</a>')
expect(navigationHtml).toContain('<a href="/history-log" class="nav-link">Historical Logs</a>')
expect(navigationHtml).toContain('<a href="/history" class="nav-link">Node History</a>')
})

beforeEach(() => {
// setup
it('should have correct navigation links in Network section', () => {
// Check for specific links in the Network section
expect(navigationHtml).toContain('<a href="/node-loads" class="nav-link">Node Loads</a>')
expect(navigationHtml).toContain('<a href="/sync-details" class="nav-link">Sync Details</a>')
expect(navigationHtml).toContain('<a href="/sync" class="nav-link">Sync Status</a>')
})

describe('Basic Navigation Tests', () => {
it('should contain all required navigation sections', () => {
// Check for main sections
expect(navigationHtml).toContain('<div class="menu-title">Main</div>');
expect(navigationHtml).toContain('<div class="menu-title">Logs</div>');
expect(navigationHtml).toContain('<div class="menu-title">Network</div>');
expect(navigationHtml).toContain('<div class="menu-title">Analytics</div>');
});

it('should have correct navigation links in Main section', () => {
// Check for specific links in the Main section
expect(navigationHtml).toContain('<a href="/" class="nav-link">Home</a>');
expect(navigationHtml).toContain('<a href="/large-network" class="nav-link highlight">Large Network View</a>');
expect(navigationHtml).toContain('<a href="/signin" class="nav-link">Sign In</a>');
});

it('should have correct navigation links in Logs section', () => {
// Check for specific links in the Logs section
expect(navigationHtml).toContain('<a href="/log" class="nav-link">Log</a>');
expect(navigationHtml).toContain('<a href="/history-log" class="nav-link">Historical Logs</a>');
expect(navigationHtml).toContain('<a href="/history" class="nav-link">Node History</a>');
});

it('should have correct navigation links in Network section', () => {
// Check for specific links in the Network section
expect(navigationHtml).toContain('<a href="/node-loads" class="nav-link">Node Loads</a>');
expect(navigationHtml).toContain('<a href="/sync-details" class="nav-link">Sync Details</a>');
expect(navigationHtml).toContain('<a href="/sync" class="nav-link">Sync Status</a>');
});

it('should have correct navigation links in Analytics section', () => {
// Check for specific links in the Analytics section
expect(navigationHtml).toContain('<a href="/chart" class="nav-link">Charts</a>');
expect(navigationHtml).toContain('<a href="/monitor-events" class="nav-link">Monitor Events</a>');
expect(navigationHtml).toContain('<a href="/app-versions" class="nav-link">Application Versions</a>');
expect(navigationHtml).toContain('<a href="/summary" class="nav-link">Summary</a>');
});
it('should have correct navigation links in Analytics section', () => {
// Check for specific links in the Analytics section
expect(navigationHtml).toContain('<a href="/chart" class="nav-link">Charts</a>')
expect(navigationHtml).toContain('<a href="/monitor-events" class="nav-link">Monitor Events</a>')
expect(navigationHtml).toContain('<a href="/app-versions" class="nav-link">Application Versions</a>')
expect(navigationHtml).toContain('<a href="/summary" class="nav-link">Summary</a>')
})
})
})
28 changes: 14 additions & 14 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"extends": "./node_modules/gts/tsconfig-google.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
"allowJs": true,
"strict": false,
"noImplicitAny": false,
"strictNullChecks": false,
"noImplicitReturns": false,
"noImplicitThis": false,
"esModuleInterop": true
},
"include": ["public/**/*.ts", "public/**/*.js"],
"allowSyntheticDefaultImports": true
"extends": "./node_modules/gts/tsconfig-google.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
"allowJs": true,
"strict": false,
"noImplicitAny": false,
"strictNullChecks": false,
"noImplicitReturns": false,
"noImplicitThis": false,
"esModuleInterop": true
},
"include": ["public/**/*.ts", "public/**/*.js"],
"allowSyntheticDefaultImports": true
}