The Digital Telepresence Platform core implementing user account management, authentication, search, global directory, and other platform-wide services. https://digitaltelepresence.com/
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.
 
 
 
 

399 lines
12 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 sendNewsletter (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 transmit "${newsletterTitle}"`);
} catch (error) {
this.log.info('sendNewsletter', 'aborted');
return;
}
try {
const response = await fetch(`/admin/newsletter/${newsletterId}/transmit`, {
method: 'POST',
});
if (!response.ok) {
throw new Error('Server error');
}
await this.processResponse(response);
} catch (error) {
this.log.error('sendNewsletter', 'failed to send newsletter', { newsletterId, newsletterTitle, error });
UIkit.modal.alert(`Failed to send newsletter: ${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 deleteAnnouncement (event) {
const target = event.currentTarget || event.target;
const announcementId = target.getAttribute('data-announcement-id');
try {
await UIkit.modal.confirm('Are you sure you want to delete the announcement?');
} catch (error) {
return;
}
try {
const actionUrl = `/admin/announcement/${announcementId}`;
const response = await fetch(actionUrl, { method: 'DELETE' });
if (!response.ok) {
throw new Error('Server error');
}
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to delete announcement: ${error.message}`);
}
}
async postCoreConnectResponse (event, action) {
const target = event.currentTarget || event.target;
const requestId = target.getAttribute('data-request-id');
try {
this.log.info('postCoreConnectResponse', 'posting Core Connect response', { requestId, action });
const requestUrl = `/admin/service-node/connect-queue/${requestId}`;
const response = await fetch(requestUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action }),
});
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to send Core Connect response: ${error.message}`);
}
}
async deleteServiceNode (event) {
const target = event.currentTarget || event.target;
const serviceNodeId = target.getAttribute('data-service-node-id');
try {
await UIkit.modal.confirm('Are you sure you want to delete the Service Node?');
} catch (error) {
return; // user cancel
}
try {
const actionUrl = `/admin/service-node/${serviceNodeId}`;
const response = await fetch(actionUrl, { method: 'DELETE' });
if (!response.ok) {
throw new Error('Server error');
}
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to delete Service Node: ${error.message}`);
}
}
async resolveNewsroomFeed (event) {
event.preventDefault();
event.stopPropagation();
const form = document.querySelector('form#add-feed-form');
const input = form.querySelector('input#url');
const feedUrl = input.value;
try {
const response = await fetch(`/admin/newsroom/resolve`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ feedUrl }),
});
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to resolve feed: ${error.message}`);
}
return false;
}
async removeNewsroomFeed (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const feedId = target.getAttribute('data-feed-id');
const feedTitle = target.getAttribute('data-feed-title');
try {
await UIkit.modal.confirm(`Are you sure you want to remove feed "${feedTitle}"?`);
} catch (error) {
// canceled
return false;
}
try {
const response = await fetch(`/admin/newsroom/${feedId}`, { method: 'DELETE' });
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to remove feed: ${error.message}`);
}
return false;
}
}
dtp.DtpSiteAdminHostStatsApp = DtpSiteAdminHostStatsApp;