DTP Social Engine
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.
 
 
 
 
 

291 lines
8.3 KiB

// dtp-site-app.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const DTP_COMPONENT = { name: 'Site Admin', slug: 'site-admin-app' };
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, 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 jobQueueAction (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const queueName = target.getAttribute('data-job-queue');
const jobId = target.getAttribute('data-job-id');
const action = target.getAttribute('data-job-action');
try {
this.log.info('queueJobRemove', 'removing job from queue', { queueName, jobId });
const response = await fetch(`/admin/job-queue/${queueName}/${jobId}/action`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action }),
});
if (!response.ok) {
throw new Error('Server error');
}
const json = await response.json();
if (!json.success) {
throw new Error(json.message);
}
if (['remove','discard'].includes(action)) {
window.location = `/admin/job-queue/${queueName}`;
}
} catch (error) {
UIkit.modal.alert(`Failed to remove job: ${error.message}`);
}
}
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;