Reporting / Traffic: Upgrade chart.js to v3.9.1 and improve UX (#6000)

* Reporting / Traffic: Bump chart.js version and improve UX
This commit is contained in:
Stephan de Wit 2022-09-01 10:21:15 +02:00 committed by GitHub
parent 8cb79d511b
commit cc6efa4a16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 14617 additions and 17417 deletions

4
plist
View File

@ -1633,7 +1633,11 @@
/usr/local/opnsense/www/js/bootstrap3-typeahead.min.js
/usr/local/opnsense/www/js/chart.js
/usr/local/opnsense/www/js/chart.min.js
/usr/local/opnsense/www/js/chartjs-adapter-moment.js
/usr/local/opnsense/www/js/chartjs-adapter-moment.min.js
/usr/local/opnsense/www/js/chartjs-adapter-moment.min.js.map
/usr/local/opnsense/www/js/chartjs-plugin-colorschemes.js
/usr/local/opnsense/www/js/chartjs-plugin-colorschemes.min.js
/usr/local/opnsense/www/js/chartjs-plugin-streaming.js
/usr/local/opnsense/www/js/chartjs-plugin-streaming.min.js
/usr/local/opnsense/www/js/d3.min.js

View File

@ -26,10 +26,11 @@ POSSIBILITY OF SUCH DAMAGE.
#}
{% set theme_name = ui_theme|default('opnsense') %}
<script src="{{ cache_safe('/ui/js/moment-with-locales.min.js') }}"></script>
<script src="{{ cache_safe('/ui/js/chart.min.js') }}"></script>
<script src="{{ cache_safe('/ui/js/chartjs-plugin-streaming.min.js') }}"></script>
<script src="{{ cache_safe('/ui/js/chartjs-plugin-colorschemes.js') }}"></script>
<script src="{{ cache_safe('/ui/js/moment-with-locales.min.js') }}"></script>
<script src="{{ cache_safe('/ui/js/chartjs-adapter-moment.js') }}"></script>
<link rel="stylesheet" type="text/css" href="{{ cache_safe(theme_file_or_default('/css/chart.css', theme_name)) }}" rel="stylesheet" />
<style>
@ -44,6 +45,15 @@ POSSIBILITY OF SUCH DAMAGE.
'use strict';
$( document ).ready(function() {
function set_alpha(color, opacity) {
const op = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255);
return color + op.toString(16).toUpperCase();
}
function limit(number, lower_bound, upper_bound) {
return Math.min(Math.max(parseInt(number), lower_bound), upper_bound);
}
function format_field(value) {
if (!isNaN(value) && value > 0) {
let fileSizeTypes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y"];
@ -57,6 +67,7 @@ POSSIBILITY OF SUCH DAMAGE.
return "";
}
}
/**
* create new traffic chart
*/
@ -68,7 +79,7 @@ POSSIBILITY OF SUCH DAMAGE.
label: init_data.interfaces[intf].name,
hidden: true,
borderColor: init_data.interfaces[intf].color,
backgroundColor: init_data.interfaces[intf].color,
backgroundColor: set_alpha(init_data.interfaces[intf].color, 0.5),
pointHoverBackgroundColor: init_data.interfaces[intf].color,
pointHoverBorderColor: init_data.interfaces[intf].color,
pointBackgroundColor: init_data.interfaces[intf].color,
@ -88,19 +99,20 @@ POSSIBILITY OF SUCH DAMAGE.
datasets: all_datasets
},
options: {
legend: {
display: false,
},
title: {
display: true,
text: graph_title
},
maintainAspectRatio: false,
elements: {
line: {
fill: true,
cubicInterpolationMode: 'monotone',
clip: 0
}
},
scales: {
xAxes: [{
x: {
time: {
tooltipFormat:'HH:mm:ss',
unit: 'second',
stepSize: init_data.interval < 10000 ? 5 : init_data.interval / 1000,
minUnit: 'second',
displayFormats: {
second: 'HH:mm:ss',
@ -110,26 +122,16 @@ POSSIBILITY OF SUCH DAMAGE.
type: 'realtime',
realtime: {
duration: 20000,
refresh: 2000,
delay: 2000
refresh: init_data.interval,
delay: init_data.interval
},
}],
yAxes: [{
},
y: {
ticks: {
callback: function (value, index, values) {
return format_field(value);
}
}
}]
},
tooltips: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
let ds = data.datasets[tooltipItem.datasetIndex];
return ds.label + " : " + format_field(ds.data[tooltipItem.index].y).toString();
}
}
},
hover: {
@ -137,11 +139,27 @@ POSSIBILITY OF SUCH DAMAGE.
intersect: false
},
plugins: {
tooltip: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function(context) {
return context.dataset.label + ": " + format_field(context.dataset.data[context.dataIndex].y).toString();
}
}
},
title: {
display: true,
text: graph_title
},
legend: {
display: false
},
streaming: {
frameRate: 30
frameRate: 30
},
colorschemes: {
scheme: 'brewer.Paired12'
scheme: 'brewer.Paired12'
}
}
}
@ -160,7 +178,7 @@ POSSIBILITY OF SUCH DAMAGE.
label: init_data.interfaces[intf].name,
hidden: true,
borderColor: init_data.interfaces[intf].color,
backgroundColor: init_data.interfaces[intf].color,
backgroundColor: set_alpha(init_data.interfaces[intf].color, 0.5),
pointHoverBackgroundColor: init_data.interfaces[intf].color,
pointHoverBorderColor: init_data.interfaces[intf].color,
pointBackgroundColor: init_data.interfaces[intf].color,
@ -180,19 +198,13 @@ POSSIBILITY OF SUCH DAMAGE.
datasets: all_datasets
},
options: {
legend: {
display: false,
},
title: {
display: true,
text: graph_title
},
maintainAspectRatio: false,
scales: {
xAxes: [{
x: {
time: {
tooltipFormat:'HH:mm:ss',
unit: 'second',
stepSize: init_data.interval < 10000 ? 5 : init_data.interval / 1000,
minUnit: 'second',
displayFormats: {
second: 'HH:mm:ss',
@ -202,30 +214,16 @@ POSSIBILITY OF SUCH DAMAGE.
type: 'realtime',
realtime: {
duration: 40000,
refresh: 3000,
delay: 500
refresh: init_data.interval,
delay: init_data.interval,
},
}],
yAxes: [{
},
y: {
ticks: {
callback: function (value, index, values) {
return format_field(value);
}
}
}]
},
tooltips: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
let ds = data.datasets[tooltipItem.datasetIndex];
return [
tooltipItem.xLabel,
ds.label + " : " + ds.data[tooltipItem.index].address,
"@ " + format_field(ds.data[tooltipItem.index].y).toString()
];
}
}
},
hover: {
@ -233,6 +231,28 @@ POSSIBILITY OF SUCH DAMAGE.
intersect: false
},
plugins: {
tooltip: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function(context) {
let split = context.formattedValue.split(",")[0]
let time = split.replace('(', '')
return [
time,
context.dataset.label + ": " + context.dataset.data[context.dataIndex].address,
"@ " + format_field(context.dataset.data[context.dataIndex].y).toString()
];
}
}
},
title: {
display: true,
text: graph_title
},
legend: {
display: false,
},
streaming: {
frameRate: 30
},
@ -328,10 +348,34 @@ POSSIBILITY OF SUCH DAMAGE.
});
}
// Store references to the charts globally
let g_charts = {traffic: [], traffic_top: []};
$("#intervals").change(function() {
if (window.localStorage) {
window.localStorage.setItem("api.diagnostics.traffic.interval", $(this).val());
}
g_charts['interval'] = limit(Number($("#intervals").val()), 500, 10000);
Object.keys(g_charts).forEach(function(key) {
if (Array.isArray(g_charts[key])) {
g_charts[key].forEach(function(chart) {
chart.config.options.scales.x.realtime = {
duration: 20000,
refresh: g_charts['interval'],
delay: g_charts['interval']
};
chart.config.options.scales.x.time.stepSize = g_charts['interval'] < 10000 ? 5 : g_charts['interval'] / 1000;
})
}
})
});
/**
* startup, fetch initial interface stats and create graphs
*/
ajaxGet('/api/diagnostics/traffic/interface',{}, function(data, status){
ajaxGet('/api/diagnostics/traffic/interface',{}, function(data, status) {
// XXX: startup selected interfaces load/save in localStorage in a future version
let tmp = window.localStorage ? window.localStorage.getItem("api.diagnostics.traffic.interface") : null;
let selected_interfaces = ['lan', 'wan'];
@ -357,99 +401,108 @@ POSSIBILITY OF SUCH DAMAGE.
});
$('#interfaces').selectpicker('refresh');
// register traffic update event
$( document ).on( "updateTrafficCharts", {
charts: [
traffic_graph($("#rxChart"), '{{ lang._('In (bps)') }}', data),
traffic_graph($("#txChart"), '{{ lang._('Out (bps)') }}', data)
]
}, function( event, data) {
let charts = event.data.charts;
for (var i =0 ; i < charts.length; ++i) {
let this_chart = charts[i];
Object.keys(data.interfaces).forEach(function(intf) {
this_chart.config.data.datasets.forEach(function(dataset) {
if (dataset.intf == intf) {
let calc_data = data.interfaces[intf][dataset.src_field];
let elapsed_time = data.time - dataset.last_time;
dataset.hidden = !$("#interfaces").val().includes(intf);
dataset.data.push({
x: Date.now(),
y: Math.round(((calc_data - dataset.last_data) / elapsed_time) * 8, 0)
});
dataset.last_time = data.time;
dataset.last_data = calc_data;
return;
}
});
});
this_chart.update();
// XXX: limit the amount of minimum interval that can be set
$("#intervals").val(2000);
if (window.localStorage && window.localStorage.getItem("api.diagnostics.traffic.interval") !== null) {
$("#intervals").val(window.localStorage.getItem("api.diagnostics.traffic.interval"));
}
$('#intervals').selectpicker('refresh');
data.interval = limit(Number($("#intervals").val()), 500, 10000);
g_charts['interval'] = data.interval;
const chart_types = ["rxChart", "txChart", "rxTopChart", "txTopChart"];
chart_types.forEach(function(chart) {
/* Create the charts */
if (chart.includes('Top')) {
let rxtx = chart.includes('rx') ? '{{ lang._('Top hosts in (bps)') }}' : '{{ lang._('Top hosts out (bps)') }}';
let graph = traffic_top_graph($("#" + chart), rxtx, data);
g_charts['traffic_top'].push(graph);
} else {
let rxtx = chart.includes('rx') ? '{{ lang._('In (bps)') }}' : '{{ lang._('Out (bps)') }}';
let graph = traffic_graph($("#" + chart), rxtx, data);
g_charts['traffic'].push(graph);
}
});
// register traffic update event
$( document ).on( "updateTrafficTopCharts", {
charts: [
traffic_top_graph($("#rxTopChart"), '{{ lang._('Top hosts in (bps)') }}', data),
traffic_top_graph($("#txTopChart"), '{{ lang._('Top hosts out (bps)') }}', data)
]
}, function( event, data) {
let charts = event.data.charts;
for (var i =0 ; i < charts.length; ++i) {
let this_chart = charts[i];
Object.keys(data).forEach(function(intf) {
this_chart.config.data.datasets.forEach(function(dataset) {
if (dataset.intf == intf) {
let calc_data = data[intf]['records'];
dataset.hidden = !$("#interfaces").val().includes(intf);
for (var i=0; i < data[intf]['records'].length ; ++i) {
dataset.data.push({
x: Date.now(),
y: data[intf]['records'][i]['rate_bits_' + dataset.src_field],
r: 4,
address: data[intf]['records'][i]['address']
});
}
return;
}
});
});
this_chart.update();
}
});
/**
* poll for new stats and update selected charts
*/
(function traffic_poller(){
(function traffic_poller() {
ajaxGet("/api/diagnostics/traffic/interface", {}, function(data, status) {
if (data.interfaces !== undefined) {
$( document ).trigger( "updateTrafficCharts", [ data ] );
update_traffic_charts(g_charts['traffic'], data);
}
});
setTimeout(traffic_poller, 2000);
setTimeout(traffic_poller, g_charts['interval']);
})();
(function top_traffic_poller(){
(function top_traffic_poller() {
if ($("#interfaces").val().length > 0) {
ajaxGet('/api/diagnostics/traffic/top/' + $("#interfaces").val().join(","), {}, function(data, status){
if (status == 'success') {
$( document ).trigger( "updateTrafficTopCharts", [ data ] );
update_top_charts(g_charts['traffic_top'], data);
updateTopTable(data);
top_traffic_poller();
} else {
setTimeout(top_traffic_poller, 2000);
setTimeout(top_traffic_poller, g_charts['interval']);
}
});
}
})();
});
function update_traffic_charts(charts, data) {
charts.forEach(function(chart) {
Object.keys(data.interfaces).forEach(function(intf) {
chart.config.data.datasets.forEach(function(dataset) {
if (dataset.intf == intf) {
let calc_data = data.interfaces[intf][dataset.src_field];
let elapsed_time = data.time - dataset.last_time;
dataset.hidden = !$("#interfaces").val().includes(intf);
dataset.data.push({
x: Date.now(),
y: Math.round(((calc_data - dataset.last_data) / elapsed_time) * 8, 0)
});
dataset.last_time = data.time;
dataset.last_data = calc_data;
return;
}
});
});
chart.update('quiet');
});
}
function update_top_charts(charts, data) {
charts.forEach(function(chart) {
Object.keys(data).forEach(function(intf) {
chart.config.data.datasets.forEach(function(dataset) {
if (dataset.intf == intf) {
let calc_data = data[intf]['records'];
dataset.hidden = !$("#interfaces").val().includes(intf);
for (var i=0; i < data[intf]['records'].length ; ++i) {
dataset.data.push({
x: Date.now(),
y: data[intf]['records'][i]['rate_bits_' + dataset.src_field],
r: 4,
address: data[intf]['records'][i]['address']
});
}
return;
}
});
});
chart.update('quiet');
});
}
$("#interfaces").change(function(){
if (window.localStorage) {
window.localStorage.setItem("api.diagnostics.traffic.interface", $(this).val());
}
});
});
@ -458,15 +511,36 @@ POSSIBILITY OF SUCH DAMAGE.
.badge-color-1 {
background: navy !important;
}
.left {
float: left;
}
.right {
float: right;
}
</style>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" id="graph_tab" href="#graph">{{ lang._('Graph') }}</a></li>
<li><a data-toggle="tab" id="gtid_tab" href="#toptalkers">{{ lang._('Top talkers') }}</a></li>
<div class="pull-right">
<select class="selectpicker" id="interfaces" multiple=multiple>
</select>
&nbsp;
<div class="right">
<select class="selectpicker" id="interfaces" multiple=multiple>
</select>
&nbsp;
</div>
<div class="left">
<select class="selectpicker" id="intervals" data-width="auto">
<option value="500">500 Milliseconds</option>
<option value="1000">1 Second</option>
<option value="2000">2 Seconds</option>
<option value="5000">5 Seconds</option>
<option value="10000">10 Seconds</option>
</select>
&nbsp;
</div>
</div>
</ul>
<div class="tab-content content-box">

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,72 @@
/*!
* chartjs-adapter-moment v1.0.0
* https://www.chartjs.org
* (c) 2022 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('moment'), require('chart.js')) :
typeof define === 'function' && define.amd ? define(['moment', 'chart.js'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.moment, global.Chart));
}(this, (function (moment, chart_js) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
const FORMATS = {
datetime: 'MMM D, YYYY, h:mm:ss a',
millisecond: 'h:mm:ss.SSS a',
second: 'h:mm:ss a',
minute: 'h:mm a',
hour: 'hA',
day: 'MMM D',
week: 'll',
month: 'MMM YYYY',
quarter: '[Q]Q - YYYY',
year: 'YYYY'
};
chart_js._adapters._date.override(typeof moment__default['default'] === 'function' ? {
_id: 'moment', // DEBUG ONLY
formats: function() {
return FORMATS;
},
parse: function(value, format) {
if (typeof value === 'string' && typeof format === 'string') {
value = moment__default['default'](value, format);
} else if (!(value instanceof moment__default['default'])) {
value = moment__default['default'](value);
}
return value.isValid() ? value.valueOf() : null;
},
format: function(time, format) {
return moment__default['default'](time).format(format);
},
add: function(time, amount, unit) {
return moment__default['default'](time).add(amount, unit).valueOf();
},
diff: function(max, min, unit) {
return moment__default['default'](max).diff(moment__default['default'](min), unit);
},
startOf: function(time, unit, weekday) {
time = moment__default['default'](time);
if (unit === 'isoWeek') {
weekday = Math.trunc(Math.min(Math.max(0, weekday), 6));
return time.isoWeekday(weekday).startOf('day').valueOf();
}
return time.startOf(unit).valueOf();
},
endOf: function(time, unit) {
return moment__default['default'](time).endOf(unit).valueOf();
}
} : {});
})));

View File

@ -0,0 +1,8 @@
/*!
* chartjs-adapter-moment v1.0.0
* https://www.chartjs.org
* (c) 2022 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("moment"),require("chart.js")):"function"==typeof define&&define.amd?define(["moment","chart.js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).moment,e.Chart)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=n(e);const a={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};t._adapters._date.override("function"==typeof f.default?{_id:"moment",formats:function(){return a},parse:function(e,t){return"string"==typeof e&&"string"==typeof t?e=f.default(e,t):e instanceof f.default||(e=f.default(e)),e.isValid()?e.valueOf():null},format:function(e,t){return f.default(e).format(t)},add:function(e,t,n){return f.default(e).add(t,n).valueOf()},diff:function(e,t,n){return f.default(e).diff(f.default(t),n)},startOf:function(e,t,n){return e=f.default(e),"isoWeek"===t?(n=Math.trunc(Math.min(Math.max(0,n),6)),e.isoWeekday(n).startOf("day").valueOf()):e.startOf(t).valueOf()},endOf:function(e,t){return f.default(e).endOf(t).valueOf()}}:{})}));
//# sourceMappingURL=chartjs-adapter-moment.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"chartjs-adapter-moment.min.js","sources":["../src/index.js"],"sourcesContent":["'use strict';\n\nimport moment from 'moment';\nimport {_adapters} from 'chart.js';\n\nconst FORMATS = {\n datetime: 'MMM D, YYYY, h:mm:ss a',\n millisecond: 'h:mm:ss.SSS a',\n second: 'h:mm:ss a',\n minute: 'h:mm a',\n hour: 'hA',\n day: 'MMM D',\n week: 'll',\n month: 'MMM YYYY',\n quarter: '[Q]Q - YYYY',\n year: 'YYYY'\n};\n\n_adapters._date.override(typeof moment === 'function' ? {\n _id: 'moment', // DEBUG ONLY\n\n formats: function() {\n return FORMATS;\n },\n\n parse: function(value, format) {\n if (typeof value === 'string' && typeof format === 'string') {\n value = moment(value, format);\n } else if (!(value instanceof moment)) {\n value = moment(value);\n }\n return value.isValid() ? value.valueOf() : null;\n },\n\n format: function(time, format) {\n return moment(time).format(format);\n },\n\n add: function(time, amount, unit) {\n return moment(time).add(amount, unit).valueOf();\n },\n\n diff: function(max, min, unit) {\n return moment(max).diff(moment(min), unit);\n },\n\n startOf: function(time, unit, weekday) {\n time = moment(time);\n if (unit === 'isoWeek') {\n weekday = Math.trunc(Math.min(Math.max(0, weekday), 6));\n return time.isoWeekday(weekday).startOf('day').valueOf();\n }\n return time.startOf(unit).valueOf();\n },\n\n endOf: function(time, unit) {\n return moment(time).endOf(unit).valueOf();\n }\n} : {});\n"],"names":["FORMATS","datetime","millisecond","second","minute","hour","day","week","month","quarter","year","_adapters","_date","override","moment","_id","formats","parse","value","format","isValid","valueOf","time","add","amount","unit","diff","max","min","startOf","weekday","Math","trunc","isoWeekday","endOf"],"mappings":";;;;;;gXAKA,MAAMA,EAAU,CACdC,SAAU,yBACVC,YAAa,gBACbC,OAAQ,YACRC,OAAQ,SACRC,KAAM,KACNC,IAAK,QACLC,KAAM,KACNC,MAAO,WACPC,QAAS,cACTC,KAAM,QAGRC,YAAUC,MAAMC,SAA2B,mBAAXC,UAAwB,CACtDC,IAAK,SAELC,QAAS,WACP,OAAOhB,GAGTiB,MAAO,SAASC,EAAOC,GAMrB,MALqB,iBAAVD,GAAwC,iBAAXC,EACtCD,EAAQJ,UAAOI,EAAOC,GACXD,aAAiBJ,YAC5BI,EAAQJ,UAAOI,IAEVA,EAAME,UAAYF,EAAMG,UAAY,MAG7CF,OAAQ,SAASG,EAAMH,GACrB,OAAOL,UAAOQ,GAAMH,OAAOA,IAG7BI,IAAK,SAASD,EAAME,EAAQC,GAC1B,OAAOX,UAAOQ,GAAMC,IAAIC,EAAQC,GAAMJ,WAGxCK,KAAM,SAASC,EAAKC,EAAKH,GACvB,OAAOX,UAAOa,GAAKD,KAAKZ,UAAOc,GAAMH,IAGvCI,QAAS,SAASP,EAAMG,EAAMK,GAE5B,OADAR,EAAOR,UAAOQ,GACD,YAATG,GACFK,EAAUC,KAAKC,MAAMD,KAAKH,IAAIG,KAAKJ,IAAI,EAAGG,GAAU,IAC7CR,EAAKW,WAAWH,GAASD,QAAQ,OAAOR,WAE1CC,EAAKO,QAAQJ,GAAMJ,WAG5Ba,MAAO,SAASZ,EAAMG,GACpB,OAAOX,UAAOQ,GAAMY,MAAMT,GAAMJ,YAEhC"}

View File

@ -1,16 +1,16 @@
/*!
* chartjs-plugin-colorschemes v0.4.0
* https://nagix.github.io/chartjs-plugin-colorschemes
* (c) 2019 Akihiko Kusanagi
* (c) 2022 Akihiko Kusanagi
* Released under the MIT license
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js')) :
typeof define === 'function' && define.amd ? define(['chart.js'], factory) :
(global = global || self, global.ChartColorSchemes = factory(global.Chart));
}(this, function (Chart) { 'use strict';
}(this, (function (Chart) { 'use strict';
Chart = Chart && Chart.hasOwnProperty('default') ? Chart['default'] : Chart;
Chart = Chart && Object.prototype.hasOwnProperty.call(Chart, 'default') ? Chart['default'] : Chart;
// eslint-disable-next-line one-var
var
@ -318,6 +318,7 @@ var
SetThree12 = ['#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462', '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd', '#ccebc5', '#ffed6f'];
var brewer = /*#__PURE__*/Object.freeze({
__proto__: null,
YlGn3: YlGn3,
YlGn4: YlGn4,
YlGn5: YlGn5,
@ -721,6 +722,7 @@ var
YellowOrange6 = ['#f0a22e', '#a5644e', '#b58b80', '#c3986d', '#a19574', '#c17529'];
var office = /*#__PURE__*/Object.freeze({
__proto__: null,
Adjacency6: Adjacency6,
Advantage6: Advantage6,
Angles6: Angles6,
@ -942,6 +944,7 @@ var
ClassicRedGreenLight11 = ['#ffb2b6', '#fcbdc0', '#f8c7c9', '#f2d1d2', '#ecdbdc', '#e5e5e5', '#dde6d9', '#d4e6cc', '#cae6c0', '#c1e6b4', '#b7e6a7'];
var tableau = /*#__PURE__*/Object.freeze({
__proto__: null,
Tableau10: Tableau10,
Tableau20: Tableau20,
ColorBlind10: ColorBlind10,
@ -1037,11 +1040,17 @@ var hoverReset = Chart.DatasetController.prototype.removeHoverStyle.length === 2
var EXPANDO_KEY = '$colorschemes';
Chart.defaults.global.plugins.colorschemes = {
// pluginBase snippet fixes the chartjs 3 incompatibility, and is backwards-compatible
// by Github user gebrits (https://github.com/gebrits/chartjs-plugin-colorschemes)
//
// Chartjs 2 => Chart.defaults.global
// Chartjs 3 => Chart.defaults
const pluginBase = Chart.defaults.global || Chart.defaults;
pluginBase.plugins.colorschemes = {
scheme: 'brewer.Paired12',
fillAlpha: 0.5,
reverse: false,
override: false
overrideExisting: false
};
function getScheme(scheme) {
@ -1071,11 +1080,17 @@ function getScheme(scheme) {
var ColorSchemesPlugin = {
id: 'colorschemes',
beforeUpdate: function(chart, options) {
beforeUpdate: function(chart, args, options) {
// Please note that in v3, the args argument was added. It was not used before it was added,
// so we just check if it is not actually our options object
if (options === undefined) {
options = args;
}
var scheme = getScheme(options.scheme);
var fillAlpha = options.fillAlpha;
var reverse = options.reverse;
var override = options.override;
var override = options.overrideExisting;
var custom = options.custom;
var schemeClone, customResult, length, colorIndex, color;
@ -1140,6 +1155,17 @@ var ColorSchemesPlugin = {
});
}
break;
// For bar chart backgroundColor (including fillAlpha) and borderColor are set
case 'bar':
if (typeof dataset.backgroundColor === 'undefined' || override) {
dataset[EXPANDO_KEY].backgroundColor = dataset.backgroundColor;
dataset.backgroundColor = helpers.color(color).alpha(fillAlpha).rgbString();
}
if (typeof dataset.borderColor === 'undefined' || override) {
dataset[EXPANDO_KEY].borderColor = dataset.borderColor;
dataset.borderColor = color;
}
break;
// For the other chart, only backgroundColor is set
default:
if (typeof dataset.backgroundColor === 'undefined' || override) {
@ -1186,10 +1212,11 @@ var ColorSchemesPlugin = {
}
};
Chart.plugins.register(ColorSchemesPlugin);
const registerPlugin = Chart.register || Chart.plugins.register;
registerPlugin(ColorSchemesPlugin);
Chart.colorschemes = colorschemes;
return ColorSchemesPlugin;
}));
})));

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -53,10 +53,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
}
?>
<script src="<?=cache_safe('/ui/js/moment-with-locales.min.js');?>"></script>
<script src="<?=cache_safe('/ui/js/chart.min.js');?>"></script>
<script src="<?=cache_safe('/ui/js/chartjs-plugin-streaming.min.js');?>"></script>
<script src="<?=cache_safe('/ui/js/chartjs-plugin-colorschemes.js');?>"></script>
<script src="<?=cache_safe('/ui/js/moment-with-locales.min.js');?>"></script>
<script src="<?=cache_safe('/ui/js/chartjs-adapter-moment.js');?>"></script>
<link rel="stylesheet" type="text/css" href="<?=cache_safe(get_themed_filename('/css/chart.css'));?>" rel="stylesheet" />
<script>
@ -64,6 +65,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
* page setup
*/
$("#dashboard_container").on("WidgetsReady", function() {
function set_alpha(color, opacity) {
const op = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255);
return color + op.toString(16).toUpperCase();
}
function format_field(value) {
if (!isNaN(value) && value > 0) {
let fileSizeTypes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y"];
@ -77,6 +83,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
return "";
}
}
/**
* create new traffic chart
*/
@ -108,15 +115,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
datasets: all_datasets
},
options: {
legend: {
display: false,
},
title: {
display: false
},
maintainAspectRatio: false,
elements: {
line: {
fill: true,
cubicInterpolationMode: 'monotone',
clip: 0
}
},
scales: {
xAxes: [{
x: {
time: {
tooltipFormat:'HH:mm:ss',
unit: 'second',
@ -132,23 +140,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
refresh: 5000,
delay: 5000
},
}],
yAxes: [{
},
y: {
ticks: {
callback: function (value, index, values) {
return format_field(value);
}
}
}]
},
tooltips: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
let ds = data.datasets[tooltipItem.datasetIndex];
return ds.label + " : " + format_field(ds.data[tooltipItem.index].y).toString();
}
}
},
hover: {
@ -156,6 +154,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
intersect: false
},
plugins: {
tooltip: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
return context.dataset.label + ": " + format_field(context.dataset.data[context.dataIndex].y).toString();
}
}
},
legend: {
display: false,
},
title: {
display: false
},
streaming: {
frameRate: 30
},