You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
7.3 KiB
254 lines
7.3 KiB
// dtp-site-app.js
|
|
// Copyright (C) 2021 Digital Telepresence, LLC
|
|
// License: Apache-2.0
|
|
|
|
'use strict';
|
|
|
|
const DTP_COMPONENT_NAME = 'SiteAdminApp';
|
|
const dtp = window.dtp = window.dtp || { };
|
|
|
|
const GRID_COLOR = '#a0a0a0';
|
|
const GRID_TICK_COLOR = '#707070';
|
|
|
|
const AXIS_TICK_COLOR = '#c0c0c0';
|
|
|
|
const CHART_LINE_USER = 'rgb(0, 192, 0)';
|
|
const CHART_LINE_NICE = 'rgb(160, 160, 160)';
|
|
const CHART_LINE_SYSTEM = 'rgb(192, 192, 0)';
|
|
const CHART_LINE_IRQ = 'rgb(0, 0, 192)';
|
|
|
|
const CHART_LINE_TX_SEC = 'rgb(240, 185, 6)';
|
|
const CHART_LINE_RX_SEC = 'rgb(6, 154, 240)';
|
|
|
|
import DtpApp from 'dtp/dtp-app.js';
|
|
import numeral from 'numeral';
|
|
import UIkit from 'uikit';
|
|
// import UIkit from 'uikit';
|
|
|
|
export default class DtpSiteAdminHostStatsApp extends DtpApp {
|
|
|
|
constructor (user) {
|
|
super(DTP_COMPONENT_NAME, user);
|
|
this.log.debug('constructor', 'app instance created');
|
|
}
|
|
|
|
prepareGraphData ( ) {
|
|
this.charts = { cpus: [ ], interfaces: [ ] };
|
|
|
|
this.coreGraphs = document.querySelectorAll('.dtp-cpu-graph');
|
|
this.coreCount = dtp.hostStats[0].cpus.length;
|
|
this.cores = [ ];
|
|
for (let idx = 0; idx < this.coreCount; ++idx) {
|
|
this.cores.push([ ]);
|
|
}
|
|
|
|
this.ifaceGraphs = document.querySelectorAll('.dtp-iface-graph');
|
|
this.ifaceCount = this.ifaceGraphs.length;
|
|
this.ifaces = { };
|
|
dtp.hostStats[0].network.forEach((iface) => {
|
|
this.ifaces[iface.iface] = [ ];
|
|
});
|
|
|
|
dtp.hostStats.forEach((stats) => {
|
|
for (let idx = 0; idx < this.coreCount; ++idx) {
|
|
const stat = stats.cpus[idx];
|
|
stat.totalTime = stat.user + stat.nice + stat.sys + stat.idle + stat.irq;
|
|
this.cores[idx].push({
|
|
created: stats.created,
|
|
...stats.cpus[idx],
|
|
});
|
|
}
|
|
|
|
stats.network.forEach((iface) => {
|
|
iface.created = stats.created;
|
|
this.ifaces[iface.iface].push(iface);
|
|
});
|
|
});
|
|
}
|
|
|
|
renderCpuGraphs ( ) {
|
|
for (let idx = 0; idx < this.coreCount; ++idx) {
|
|
const ctx = this.coreGraphs[idx].getContext('2d');
|
|
const datasets = [
|
|
{
|
|
label: 'user',
|
|
data: this.cores[idx].map((sample) => ((sample.user / sample.totalTime) * 100.0)),
|
|
borderColor: CHART_LINE_USER,
|
|
tension: 0.5,
|
|
},
|
|
{
|
|
label: 'nice',
|
|
data: this.cores[idx].map((sample) => ((sample.nice / sample.totalTime) * 100.0)),
|
|
borderColor: CHART_LINE_NICE,
|
|
tension: 0.5,
|
|
},
|
|
{
|
|
label: 'sys',
|
|
data: this.cores[idx].map((sample) => ((sample.sys / sample.totalTime) * 100.0)),
|
|
borderColor: CHART_LINE_SYSTEM,
|
|
tension: 0.5,
|
|
},
|
|
{
|
|
label: 'irq',
|
|
data: this.cores[idx].map((sample) => ((sample.irq / sample.totalTime) * 100.0)),
|
|
borderColor: CHART_LINE_IRQ,
|
|
tension: 0.5,
|
|
},
|
|
];
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: this.cores[idx].map((sample) => sample.created),
|
|
datasets,
|
|
},
|
|
options: {
|
|
scales: {
|
|
yAxis: {
|
|
display: true,
|
|
max: 100.0,
|
|
ticks: {
|
|
color: AXIS_TICK_COLOR,
|
|
},
|
|
grid: {
|
|
color: GRID_COLOR,
|
|
tickColor: GRID_TICK_COLOR,
|
|
},
|
|
},
|
|
xAxis: {
|
|
display: false,
|
|
},
|
|
},
|
|
plugins: {
|
|
title: { display: false },
|
|
subtitle: { display: false },
|
|
legend: {
|
|
display: true,
|
|
position: 'bottom',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
this.charts.cpus.push(chart);
|
|
}
|
|
}
|
|
|
|
renderNetworkGraphs ( ) {
|
|
const ifNames = Object.keys(this.ifaces);
|
|
|
|
ifNames.forEach((ifName) => {
|
|
const iface = this.ifaces[ifName];
|
|
const canvas = document.querySelector(`.dtp-iface-graph[data-iface="${ifName}"]`);
|
|
if (!canvas) {
|
|
return;
|
|
}
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const datasets = [
|
|
{
|
|
label: 'TX/sec',
|
|
data: iface.map((sample) => sample.txPerSecond),
|
|
borderColor: CHART_LINE_TX_SEC,
|
|
tension: 0.5,
|
|
},
|
|
{
|
|
label: 'RX/sec',
|
|
data: iface.map((sample) => sample.rxPerSecond),
|
|
borderColor: CHART_LINE_RX_SEC,
|
|
tension: 0.5,
|
|
},
|
|
];
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: iface.map((sample) => sample.created),
|
|
datasets,
|
|
},
|
|
options: {
|
|
scales: {
|
|
yAxis: {
|
|
display: true,
|
|
ticks: {
|
|
color: AXIS_TICK_COLOR,
|
|
callback: (value) => {
|
|
let label = 'Mbps';
|
|
let megabits = value * (8 / 1024.0 / 1000.0);
|
|
if (megabits > 1000) {
|
|
label = 'Gbps';
|
|
megabits /= 1000.0;
|
|
}
|
|
return `${numeral(megabits).format('0,0.00')} ${label}`;
|
|
},
|
|
},
|
|
grid: {
|
|
color: GRID_COLOR,
|
|
tickColor: GRID_TICK_COLOR,
|
|
},
|
|
},
|
|
xAxis: { display: false },
|
|
},
|
|
plugins: {
|
|
title: { display: false },
|
|
subtitle: { display: false },
|
|
legend: {
|
|
display: true,
|
|
position: 'bottom',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
this.charts.interfaces.push(chart);
|
|
});
|
|
}
|
|
|
|
async deleteNewsletter (event) {
|
|
const newsletterId = event.currentTarget.getAttribute('data-newsletter-id');
|
|
const newsletterTitle = event.currentTarget.getAttribute('data-newsletter-title');
|
|
console.log(newsletterId, newsletterTitle);
|
|
try {
|
|
await UIkit.modal.confirm(`Are you sure you want to delete "${newsletterTitle}"`);
|
|
} catch (error) {
|
|
this.log.info('deleteNewsletter', 'aborted');
|
|
return;
|
|
}
|
|
try {
|
|
const response = await fetch(`/admin/newsletter/${newsletterId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete newsletter');
|
|
}
|
|
await this.processResponse(response);
|
|
} catch (error) {
|
|
this.log.error('deleteNewsletter', 'failed to delete newsletter', { newsletterId, newsletterTitle, error });
|
|
UIkit.modal.alert(`Failed to delete newsletter: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async deletePost (event) {
|
|
const postId = event.currentTarget.getAttribute('data-post-id');
|
|
const postTitle = event.currentTarget.getAttribute('data-post-title');
|
|
console.log(postId, postTitle);
|
|
try {
|
|
await UIkit.modal.confirm(`Are you sure you want to delete "${postTitle}"`);
|
|
} catch (error) {
|
|
this.log.info('deletePost', 'aborted');
|
|
return;
|
|
}
|
|
try {
|
|
const response = await fetch(`/admin/post/${postId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete post');
|
|
}
|
|
await this.processResponse(response);
|
|
} catch (error) {
|
|
this.log.error('deletePost', 'failed to delete post', { postId, postTitle, error });
|
|
UIkit.modal.alert(`Failed to delete post: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
dtp.DtpSiteAdminHostStatsApp = DtpSiteAdminHostStatsApp;
|