mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
refactor: use esm only import used chart types/plugins
This commit is contained in:
@@ -1,116 +1,121 @@
|
|||||||
'use strict';
|
import {
|
||||||
|
Chart,
|
||||||
|
LineController,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler,
|
||||||
|
} from 'chart.js';
|
||||||
|
|
||||||
|
import * as bootbox from 'bootbox';
|
||||||
|
import * as alerts from '../../modules/alerts';
|
||||||
|
|
||||||
|
Chart.register(
|
||||||
|
LineController,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
define('admin/advanced/errors', [
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
'bootbox', 'alerts', 'chart.js/auto',
|
export function init() {
|
||||||
], function (bootbox, alerts, { Chart }) {
|
setupCharts();
|
||||||
const Errors = {};
|
|
||||||
|
|
||||||
Errors.init = function () {
|
$('[data-action="clear"]').on('click', clear404);
|
||||||
Errors.setupCharts();
|
}
|
||||||
|
|
||||||
$('[data-action="clear"]').on('click', Errors.clear404);
|
function clear404() {
|
||||||
};
|
bootbox.confirm('[[admin/advanced/errors:clear404-confirm]]', function (ok) {
|
||||||
|
if (ok) {
|
||||||
|
socket.emit('admin.errors.clear', {}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return alerts.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
Errors.clear404 = function () {
|
ajaxify.refresh();
|
||||||
bootbox.confirm('[[admin/advanced/errors:clear404-confirm]]', function (ok) {
|
alerts.success('[[admin/advanced/errors:clear404-success]]');
|
||||||
if (ok) {
|
});
|
||||||
socket.emit('admin.errors.clear', {}, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return alerts.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
ajaxify.refresh();
|
|
||||||
alerts.success('[[admin/advanced/errors:clear404-success]]');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Errors.setupCharts = function () {
|
|
||||||
const notFoundCanvas = document.getElementById('not-found');
|
|
||||||
const tooBusyCanvas = document.getElementById('toobusy');
|
|
||||||
let dailyLabels = utils.getDaysArray();
|
|
||||||
|
|
||||||
dailyLabels = dailyLabels.slice(-7);
|
|
||||||
|
|
||||||
if (utils.isMobile()) {
|
|
||||||
Chart.defaults.plugins.tooltip.enabled = false;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
function setupCharts() {
|
||||||
'not-found': {
|
const notFoundCanvas = document.getElementById('not-found');
|
||||||
labels: dailyLabels,
|
const tooBusyCanvas = document.getElementById('toobusy');
|
||||||
datasets: [
|
let dailyLabels = utils.getDaysArray();
|
||||||
{
|
|
||||||
label: '',
|
|
||||||
fill: 'origin',
|
|
||||||
tension: 0.25,
|
|
||||||
backgroundColor: 'rgba(186,139,175,0.2)',
|
|
||||||
borderColor: 'rgba(186,139,175,1)',
|
|
||||||
pointBackgroundColor: 'rgba(186,139,175,1)',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(186,139,175,1)',
|
|
||||||
data: ajaxify.data.analytics['not-found'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
toobusy: {
|
|
||||||
labels: dailyLabels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: '',
|
|
||||||
fill: 'origin',
|
|
||||||
tension: 0.25,
|
|
||||||
backgroundColor: 'rgba(151,187,205,0.2)',
|
|
||||||
borderColor: 'rgba(151,187,205,1)',
|
|
||||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
|
||||||
data: ajaxify.data.analytics.toobusy,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
new Chart(notFoundCanvas.getContext('2d'), {
|
dailyLabels = dailyLabels.slice(-7);
|
||||||
type: 'line',
|
|
||||||
data: data['not-found'],
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
new Chart(tooBusyCanvas.getContext('2d'), {
|
if (utils.isMobile()) {
|
||||||
type: 'line',
|
Chart.defaults.plugins.tooltip.enabled = false;
|
||||||
data: data.toobusy,
|
}
|
||||||
options: {
|
|
||||||
responsive: true,
|
const data = {
|
||||||
plugins: {
|
'not-found': {
|
||||||
legend: {
|
labels: dailyLabels,
|
||||||
display: false,
|
datasets: [
|
||||||
},
|
{
|
||||||
|
label: '',
|
||||||
|
fill: 'origin',
|
||||||
|
tension: 0.25,
|
||||||
|
backgroundColor: 'rgba(186,139,175,0.2)',
|
||||||
|
borderColor: 'rgba(186,139,175,1)',
|
||||||
|
pointBackgroundColor: 'rgba(186,139,175,1)',
|
||||||
|
pointHoverBackgroundColor: '#fff',
|
||||||
|
pointBorderColor: '#fff',
|
||||||
|
pointHoverBorderColor: 'rgba(186,139,175,1)',
|
||||||
|
data: ajaxify.data.analytics['not-found'],
|
||||||
},
|
},
|
||||||
scales: {
|
],
|
||||||
y: {
|
},
|
||||||
beginAtZero: true,
|
toobusy: {
|
||||||
},
|
labels: dailyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
fill: 'origin',
|
||||||
|
tension: 0.25,
|
||||||
|
backgroundColor: 'rgba(151,187,205,0.2)',
|
||||||
|
borderColor: 'rgba(151,187,205,1)',
|
||||||
|
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||||
|
pointHoverBackgroundColor: '#fff',
|
||||||
|
pointBorderColor: '#fff',
|
||||||
|
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
||||||
|
data: ajaxify.data.analytics.toobusy,
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
});
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return Errors;
|
new Chart(notFoundCanvas.getContext('2d'), {
|
||||||
});
|
type: 'line',
|
||||||
|
data: data['not-found'],
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(tooBusyCanvas.getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: data.toobusy,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,142 +1,151 @@
|
|||||||
'use strict';
|
import {
|
||||||
|
Chart,
|
||||||
|
LineController,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler,
|
||||||
|
} from 'chart.js';
|
||||||
|
|
||||||
|
import * as categorySelector from '../../modules/categorySelector';
|
||||||
|
|
||||||
define('admin/manage/category-analytics', [
|
Chart.register(
|
||||||
'chart.js/auto', 'categorySelector',
|
LineController,
|
||||||
], function ({ Chart }, categorySelector) {
|
CategoryScale,
|
||||||
const CategoryAnalytics = {};
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler
|
||||||
|
);
|
||||||
|
|
||||||
CategoryAnalytics.init = function () {
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
categorySelector.init($('[component="category-selector"]'), {
|
export function init() {
|
||||||
onSelect: function (selectedCategory) {
|
categorySelector.init($('[component="category-selector"]'), {
|
||||||
ajaxify.go('admin/manage/categories/' + selectedCategory.cid + '/analytics');
|
onSelect: function (selectedCategory) {
|
||||||
},
|
ajaxify.go('admin/manage/categories/' + selectedCategory.cid + '/analytics');
|
||||||
showLinks: true,
|
},
|
||||||
template: 'admin/partials/category/selector-dropdown-right',
|
showLinks: true,
|
||||||
});
|
template: 'admin/partials/category/selector-dropdown-right',
|
||||||
|
});
|
||||||
|
|
||||||
const hourlyCanvas = document.getElementById('pageviews:hourly');
|
const hourlyCanvas = document.getElementById('pageviews:hourly');
|
||||||
const dailyCanvas = document.getElementById('pageviews:daily');
|
const dailyCanvas = document.getElementById('pageviews:daily');
|
||||||
const topicsCanvas = document.getElementById('topics:daily');
|
const topicsCanvas = document.getElementById('topics:daily');
|
||||||
const postsCanvas = document.getElementById('posts:daily');
|
const postsCanvas = document.getElementById('posts:daily');
|
||||||
const hourlyLabels = utils.getHoursArray().map(function (text, idx) {
|
const hourlyLabels = utils.getHoursArray().map(function (text, idx) {
|
||||||
return idx % 3 ? '' : text;
|
return idx % 3 ? '' : text;
|
||||||
});
|
});
|
||||||
const dailyLabels = utils.getDaysArray().map(function (text, idx) {
|
const dailyLabels = utils.getDaysArray().map(function (text, idx) {
|
||||||
return idx % 3 ? '' : text;
|
return idx % 3 ? '' : text;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (utils.isMobile()) {
|
if (utils.isMobile()) {
|
||||||
Chart.defaults.plugins.tooltip.enabled = false;
|
Chart.defaults.plugins.tooltip.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const commonDataSetOpts = {
|
const commonDataSetOpts = {
|
||||||
label: '',
|
label: '',
|
||||||
fill: true,
|
fill: true,
|
||||||
tension: 0.25,
|
tension: 0.25,
|
||||||
pointHoverBackgroundColor: '#fff',
|
pointHoverBackgroundColor: '#fff',
|
||||||
pointBorderColor: '#fff',
|
pointBorderColor: '#fff',
|
||||||
};
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
'pageviews:hourly': {
|
|
||||||
labels: hourlyLabels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
...commonDataSetOpts,
|
|
||||||
backgroundColor: 'rgba(186,139,175,0.2)',
|
|
||||||
borderColor: 'rgba(186,139,175,1)',
|
|
||||||
pointBackgroundColor: 'rgba(186,139,175,1)',
|
|
||||||
pointHoverBorderColor: 'rgba(186,139,175,1)',
|
|
||||||
data: ajaxify.data.analytics['pageviews:hourly'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'pageviews:daily': {
|
|
||||||
labels: dailyLabels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
...commonDataSetOpts,
|
|
||||||
backgroundColor: 'rgba(151,187,205,0.2)',
|
|
||||||
borderColor: 'rgba(151,187,205,1)',
|
|
||||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
|
||||||
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
|
||||||
data: ajaxify.data.analytics['pageviews:daily'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'topics:daily': {
|
|
||||||
labels: dailyLabels.slice(-7),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
...commonDataSetOpts,
|
|
||||||
backgroundColor: 'rgba(171,70,66,0.2)',
|
|
||||||
borderColor: 'rgba(171,70,66,1)',
|
|
||||||
pointBackgroundColor: 'rgba(171,70,66,1)',
|
|
||||||
pointHoverBorderColor: 'rgba(171,70,66,1)',
|
|
||||||
data: ajaxify.data.analytics['topics:daily'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'posts:daily': {
|
|
||||||
labels: dailyLabels.slice(-7),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
...commonDataSetOpts,
|
|
||||||
backgroundColor: 'rgba(161,181,108,0.2)',
|
|
||||||
borderColor: 'rgba(161,181,108,1)',
|
|
||||||
pointBackgroundColor: 'rgba(161,181,108,1)',
|
|
||||||
pointHoverBorderColor: 'rgba(161,181,108,1)',
|
|
||||||
data: ajaxify.data.analytics['posts:daily'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
hourlyCanvas.width = $(hourlyCanvas).parent().width();
|
|
||||||
dailyCanvas.width = $(dailyCanvas).parent().width();
|
|
||||||
topicsCanvas.width = $(topicsCanvas).parent().width();
|
|
||||||
postsCanvas.width = $(postsCanvas).parent().width();
|
|
||||||
|
|
||||||
const chartOpts = {
|
|
||||||
responsive: true,
|
|
||||||
animation: false,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
new Chart(hourlyCanvas.getContext('2d'), {
|
|
||||||
type: 'line',
|
|
||||||
data: data['pageviews:hourly'],
|
|
||||||
options: chartOpts,
|
|
||||||
});
|
|
||||||
|
|
||||||
new Chart(dailyCanvas.getContext('2d'), {
|
|
||||||
type: 'line',
|
|
||||||
data: data['pageviews:daily'],
|
|
||||||
options: chartOpts,
|
|
||||||
});
|
|
||||||
|
|
||||||
new Chart(topicsCanvas.getContext('2d'), {
|
|
||||||
type: 'line',
|
|
||||||
data: data['topics:daily'],
|
|
||||||
options: chartOpts,
|
|
||||||
});
|
|
||||||
|
|
||||||
new Chart(postsCanvas.getContext('2d'), {
|
|
||||||
type: 'line',
|
|
||||||
data: data['posts:daily'],
|
|
||||||
options: chartOpts,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return CategoryAnalytics;
|
const data = {
|
||||||
});
|
'pageviews:hourly': {
|
||||||
|
labels: hourlyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
...commonDataSetOpts,
|
||||||
|
backgroundColor: 'rgba(186,139,175,0.2)',
|
||||||
|
borderColor: 'rgba(186,139,175,1)',
|
||||||
|
pointBackgroundColor: 'rgba(186,139,175,1)',
|
||||||
|
pointHoverBorderColor: 'rgba(186,139,175,1)',
|
||||||
|
data: ajaxify.data.analytics['pageviews:hourly'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'pageviews:daily': {
|
||||||
|
labels: dailyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
...commonDataSetOpts,
|
||||||
|
backgroundColor: 'rgba(151,187,205,0.2)',
|
||||||
|
borderColor: 'rgba(151,187,205,1)',
|
||||||
|
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||||
|
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
||||||
|
data: ajaxify.data.analytics['pageviews:daily'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'topics:daily': {
|
||||||
|
labels: dailyLabels.slice(-7),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
...commonDataSetOpts,
|
||||||
|
backgroundColor: 'rgba(171,70,66,0.2)',
|
||||||
|
borderColor: 'rgba(171,70,66,1)',
|
||||||
|
pointBackgroundColor: 'rgba(171,70,66,1)',
|
||||||
|
pointHoverBorderColor: 'rgba(171,70,66,1)',
|
||||||
|
data: ajaxify.data.analytics['topics:daily'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'posts:daily': {
|
||||||
|
labels: dailyLabels.slice(-7),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
...commonDataSetOpts,
|
||||||
|
backgroundColor: 'rgba(161,181,108,0.2)',
|
||||||
|
borderColor: 'rgba(161,181,108,1)',
|
||||||
|
pointBackgroundColor: 'rgba(161,181,108,1)',
|
||||||
|
pointHoverBorderColor: 'rgba(161,181,108,1)',
|
||||||
|
data: ajaxify.data.analytics['posts:daily'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
hourlyCanvas.width = $(hourlyCanvas).parent().width();
|
||||||
|
dailyCanvas.width = $(dailyCanvas).parent().width();
|
||||||
|
topicsCanvas.width = $(topicsCanvas).parent().width();
|
||||||
|
postsCanvas.width = $(postsCanvas).parent().width();
|
||||||
|
|
||||||
|
const chartOpts = {
|
||||||
|
responsive: true,
|
||||||
|
animation: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(hourlyCanvas.getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: data['pageviews:hourly'],
|
||||||
|
options: chartOpts,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(dailyCanvas.getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: data['pageviews:daily'],
|
||||||
|
options: chartOpts,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(topicsCanvas.getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: data['topics:daily'],
|
||||||
|
options: chartOpts,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(postsCanvas.getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: data['posts:daily'],
|
||||||
|
options: chartOpts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,194 +1,215 @@
|
|||||||
'use strict';
|
import {
|
||||||
|
Chart,
|
||||||
|
LineController,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler,
|
||||||
|
} from 'chart.js';
|
||||||
|
|
||||||
define('admin/modules/dashboard-line-graph', [
|
import * as Benchpress from 'benchpressjs';
|
||||||
'chart.js/auto', 'translator', 'benchpress', 'api', 'hooks', 'bootbox',
|
import * as bootbox from 'bootbox';
|
||||||
], function ({ Chart }, translator, Benchpress, api, hooks, bootbox) {
|
import * as translator from '../../modules/translator';
|
||||||
const Graph = {
|
import * as api from '../../modules/api';
|
||||||
_current: null,
|
import * as hooks from '../../modules/hooks';
|
||||||
};
|
|
||||||
let isMobile = false;
|
|
||||||
|
|
||||||
Graph.init = ({ set, dataset }) => {
|
|
||||||
const canvas = document.getElementById('analytics-traffic');
|
|
||||||
const canvasCtx = canvas.getContext('2d');
|
|
||||||
const trafficLabels = utils.getHoursArray();
|
|
||||||
|
|
||||||
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
Chart.register(
|
||||||
if (isMobile) {
|
LineController,
|
||||||
Chart.defaults.plugins.tooltip.enabled = false;
|
CategoryScale,
|
||||||
}
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler
|
||||||
|
);
|
||||||
|
|
||||||
Graph.handleUpdateControls({ set });
|
|
||||||
|
|
||||||
const t = translator.Translator.create();
|
let _current = null;
|
||||||
return new Promise((resolve) => {
|
let isMobile = false;
|
||||||
t.translateKey(`admin/menu:${ajaxify.data.template.name.replace('admin/', '')}`, []).then((key) => {
|
|
||||||
const data = {
|
|
||||||
labels: trafficLabels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: key,
|
|
||||||
fill: true,
|
|
||||||
tension: 0.25,
|
|
||||||
backgroundColor: 'rgba(151,187,205,0.2)',
|
|
||||||
borderColor: 'rgba(151,187,205,1)',
|
|
||||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
|
||||||
pointHoverBackgroundColor: 'rgba(151,187,205,1)',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
|
||||||
data: dataset || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
canvas.width = $(canvas).parent().width();
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export function init({ set, dataset }) {
|
||||||
|
const canvas = document.getElementById('analytics-traffic');
|
||||||
|
const canvasCtx = canvas.getContext('2d');
|
||||||
|
const trafficLabels = utils.getHoursArray();
|
||||||
|
|
||||||
data.datasets[0].yAxisID = 'left-y-axis';
|
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||||
|
if (isMobile) {
|
||||||
|
Chart.defaults.plugins.tooltip.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
Graph._current = new Chart(canvasCtx, {
|
handleUpdateControls({ set });
|
||||||
type: 'line',
|
|
||||||
data: data,
|
const t = translator.Translator.create();
|
||||||
options: {
|
return new Promise((resolve) => {
|
||||||
responsive: true,
|
t.translateKey(`admin/menu:${ajaxify.data.template.name.replace('admin/', '')}`, []).then((key) => {
|
||||||
scales: {
|
const data = {
|
||||||
'left-y-axis': {
|
labels: trafficLabels,
|
||||||
type: 'linear',
|
datasets: [
|
||||||
position: 'left',
|
{
|
||||||
beginAtZero: true,
|
label: key,
|
||||||
title: {
|
fill: true,
|
||||||
display: true,
|
tension: 0.25,
|
||||||
text: key,
|
backgroundColor: 'rgba(151,187,205,0.2)',
|
||||||
},
|
borderColor: 'rgba(151,187,205,1)',
|
||||||
|
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||||
|
pointHoverBackgroundColor: 'rgba(151,187,205,1)',
|
||||||
|
pointBorderColor: '#fff',
|
||||||
|
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
||||||
|
data: dataset || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
canvas.width = $(canvas).parent().width();
|
||||||
|
|
||||||
|
data.datasets[0].yAxisID = 'left-y-axis';
|
||||||
|
|
||||||
|
_current = new Chart(canvasCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
'left-y-axis': {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'left',
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: key,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
interaction: {
|
|
||||||
intersect: false,
|
|
||||||
mode: 'index',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
if (!dataset) {
|
mode: 'index',
|
||||||
Graph.update(set).then(resolve);
|
},
|
||||||
} else {
|
},
|
||||||
resolve(Graph._current);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Graph.handleUpdateControls = ({ set }) => {
|
if (!dataset) {
|
||||||
$('[data-action="updateGraph"]:not([data-units="custom"])').on('click', function () {
|
update(set).then(resolve);
|
||||||
let until = new Date();
|
} else {
|
||||||
const amount = $(this).attr('data-amount');
|
resolve(_current);
|
||||||
if ($(this).attr('data-units') === 'days') {
|
|
||||||
until.setHours(0, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
until = until.getTime();
|
|
||||||
Graph.update(set, $(this).attr('data-units'), until, amount);
|
|
||||||
|
|
||||||
require(['translator'], function (translator) {
|
|
||||||
translator.translate('[[admin/dashboard:page-views-custom]]', function (translated) {
|
|
||||||
$('[data-action="updateGraph"][data-units="custom"]').text(translated);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$('[data-action="updateGraph"][data-units="custom"]').on('click', function () {
|
function handleUpdateControls({ set }) {
|
||||||
const targetEl = $(this);
|
$('[data-action="updateGraph"]:not([data-units="custom"])').on('click', function () {
|
||||||
|
let until = new Date();
|
||||||
Benchpress.render('admin/partials/pageviews-range-select', {}).then(function (html) {
|
const amount = $(this).attr('data-amount');
|
||||||
const modal = bootbox.dialog({
|
if ($(this).attr('data-units') === 'days') {
|
||||||
title: '[[admin/dashboard:page-views-custom]]',
|
until.setHours(0, 0, 0, 0);
|
||||||
message: html,
|
|
||||||
buttons: {
|
|
||||||
submit: {
|
|
||||||
label: '[[global:search]]',
|
|
||||||
className: 'btn-primary',
|
|
||||||
callback: submit,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).on('shown.bs.modal', function () {
|
|
||||||
const date = new Date();
|
|
||||||
const today = date.toISOString().slice(0, 10);
|
|
||||||
date.setDate(date.getDate() - 1);
|
|
||||||
const yesterday = date.toISOString().slice(0, 10);
|
|
||||||
|
|
||||||
modal.find('#startRange').val(targetEl.attr('data-startRange') || yesterday);
|
|
||||||
modal.find('#endRange').val(targetEl.attr('data-endRange') || today);
|
|
||||||
});
|
|
||||||
|
|
||||||
function submit() {
|
|
||||||
// NEED TO ADD VALIDATION HERE FOR YYYY-MM-DD
|
|
||||||
const formData = modal.find('form').serializeObject();
|
|
||||||
const validRegexp = /\d{4}-\d{2}-\d{2}/;
|
|
||||||
|
|
||||||
// Input validation
|
|
||||||
if (!formData.startRange && !formData.endRange) {
|
|
||||||
// No range? Assume last 30 days
|
|
||||||
Graph.update(set, 'days');
|
|
||||||
return;
|
|
||||||
} else if (!validRegexp.test(formData.startRange) || !validRegexp.test(formData.endRange)) {
|
|
||||||
// Invalid Input
|
|
||||||
modal.find('.alert-danger').removeClass('hidden');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let until = new Date(formData.endRange);
|
|
||||||
until.setDate(until.getDate() + 1);
|
|
||||||
until = until.getTime();
|
|
||||||
const amount = (until - new Date(formData.startRange).getTime()) / (1000 * 60 * 60 * 24);
|
|
||||||
|
|
||||||
Graph.update(set, 'days', until, amount);
|
|
||||||
|
|
||||||
// Update "custom range" label
|
|
||||||
targetEl.attr('data-startRange', formData.startRange);
|
|
||||||
targetEl.attr('data-endRange', formData.endRange);
|
|
||||||
targetEl.html(formData.startRange + ' – ' + formData.endRange);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Graph.update = (
|
|
||||||
set,
|
|
||||||
units = ajaxify.data.query.units || 'hours',
|
|
||||||
until = ajaxify.data.query.until,
|
|
||||||
amount = ajaxify.data.query.count
|
|
||||||
) => {
|
|
||||||
if (!Graph._current) {
|
|
||||||
return Promise.reject(new Error('[[error:invalid-data]]'));
|
|
||||||
}
|
}
|
||||||
|
until = until.getTime();
|
||||||
|
update(set, $(this).attr('data-units'), until, amount);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
require(['translator'], function (translator) {
|
||||||
api.get(`/admin/analytics/${set}`, { units, until, amount }).then((dataset) => {
|
translator.translate('[[admin/dashboard:page-views-custom]]', function (translated) {
|
||||||
if (units === 'days') {
|
$('[data-action="updateGraph"][data-units="custom"]').text(translated);
|
||||||
Graph._current.data.xLabels = utils.getDaysArray(until, amount);
|
|
||||||
} else {
|
|
||||||
Graph._current.data.xLabels = utils.getHoursArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
Graph._current.data.datasets[0].data = dataset;
|
|
||||||
Graph._current.data.labels = Graph._current.data.xLabels;
|
|
||||||
Graph._current.update();
|
|
||||||
|
|
||||||
// Update address bar and "View as JSON" button url
|
|
||||||
const apiEl = $('#view-as-json');
|
|
||||||
const newHref = $.param({
|
|
||||||
units: units || 'hours',
|
|
||||||
until: until,
|
|
||||||
count: amount,
|
|
||||||
});
|
|
||||||
apiEl.attr('href', `${config.relative_path}/api/v3/admin/analytics/${ajaxify.data.set}?${newHref}`);
|
|
||||||
const url = ajaxify.removeRelativePath(ajaxify.data.url.slice(1));
|
|
||||||
ajaxify.updateHistory(`${url}?${newHref}`, true);
|
|
||||||
hooks.fire('action:admin.dashboard.updateGraph', {
|
|
||||||
graph: Graph._current,
|
|
||||||
});
|
|
||||||
resolve(Graph._current);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
|
$('[data-action="updateGraph"][data-units="custom"]').on('click', function () {
|
||||||
|
const targetEl = $(this);
|
||||||
|
|
||||||
|
Benchpress.render('admin/partials/pageviews-range-select', {}).then(function (html) {
|
||||||
|
const modal = bootbox.dialog({
|
||||||
|
title: '[[admin/dashboard:page-views-custom]]',
|
||||||
|
message: html,
|
||||||
|
buttons: {
|
||||||
|
submit: {
|
||||||
|
label: '[[global:search]]',
|
||||||
|
className: 'btn-primary',
|
||||||
|
callback: submit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).on('shown.bs.modal', function () {
|
||||||
|
const date = new Date();
|
||||||
|
const today = date.toISOString().slice(0, 10);
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
const yesterday = date.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
modal.find('#startRange').val(targetEl.attr('data-startRange') || yesterday);
|
||||||
|
modal.find('#endRange').val(targetEl.attr('data-endRange') || today);
|
||||||
|
});
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
// NEED TO ADD VALIDATION HERE FOR YYYY-MM-DD
|
||||||
|
const formData = modal.find('form').serializeObject();
|
||||||
|
const validRegexp = /\d{4}-\d{2}-\d{2}/;
|
||||||
|
|
||||||
|
// Input validation
|
||||||
|
if (!formData.startRange && !formData.endRange) {
|
||||||
|
// No range? Assume last 30 days
|
||||||
|
update(set, 'days');
|
||||||
|
return;
|
||||||
|
} else if (!validRegexp.test(formData.startRange) || !validRegexp.test(formData.endRange)) {
|
||||||
|
// Invalid Input
|
||||||
|
modal.find('.alert-danger').removeClass('hidden');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let until = new Date(formData.endRange);
|
||||||
|
until.setDate(until.getDate() + 1);
|
||||||
|
until = until.getTime();
|
||||||
|
const amount = (until - new Date(formData.startRange).getTime()) / (1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
|
update(set, 'days', until, amount);
|
||||||
|
|
||||||
|
// Update "custom range" label
|
||||||
|
targetEl.attr('data-startRange', formData.startRange);
|
||||||
|
targetEl.attr('data-endRange', formData.endRange);
|
||||||
|
targetEl.html(formData.startRange + ' – ' + formData.endRange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(
|
||||||
|
set,
|
||||||
|
units = ajaxify.data.query.units || 'hours',
|
||||||
|
until = ajaxify.data.query.until,
|
||||||
|
amount = ajaxify.data.query.count
|
||||||
|
) {
|
||||||
|
if (!_current) {
|
||||||
|
return Promise.reject(new Error('[[error:invalid-data]]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
api.get(`/admin/analytics/${set}`, { units, until, amount }).then((dataset) => {
|
||||||
|
if (units === 'days') {
|
||||||
|
_current.data.xLabels = utils.getDaysArray(until, amount);
|
||||||
|
} else {
|
||||||
|
_current.data.xLabels = utils.getHoursArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
_current.data.datasets[0].data = dataset;
|
||||||
|
_current.data.labels = _current.data.xLabels;
|
||||||
|
_current.update();
|
||||||
|
|
||||||
|
// Update address bar and "View as JSON" button url
|
||||||
|
const apiEl = $('#view-as-json');
|
||||||
|
const newHref = $.param({
|
||||||
|
units: units || 'hours',
|
||||||
|
until: until,
|
||||||
|
count: amount,
|
||||||
|
});
|
||||||
|
apiEl.attr('href', `${config.relative_path}/api/v3/admin/analytics/${ajaxify.data.set}?${newHref}`);
|
||||||
|
const url = ajaxify.removeRelativePath(ajaxify.data.url.slice(1));
|
||||||
|
ajaxify.updateHistory(`${url}?${newHref}`, true);
|
||||||
|
hooks.fire('action:admin.dashboard.updateGraph', {
|
||||||
|
graph: _current,
|
||||||
|
});
|
||||||
|
resolve(_current);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Graph;
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,300 +1,301 @@
|
|||||||
'use strict';
|
import {
|
||||||
|
Chart,
|
||||||
|
LineController,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler,
|
||||||
|
} from 'chart.js';
|
||||||
|
|
||||||
define('forum/flags/list', [
|
import * as categoryFilter from '../../modules/categoryFilter';
|
||||||
'components', 'chart.js/auto', 'categoryFilter',
|
import * as userFilter from '../../modules/userFilter';
|
||||||
'autocomplete', 'api', 'alerts',
|
import * as autocomplete from '../../modules/autocomplete';
|
||||||
'userFilter',
|
import * as api from '../../modules/api';
|
||||||
], function (
|
import * as alerts from '../../modules/alerts';
|
||||||
components, { Chart }, categoryFilter,
|
import * as components from '../../modules/components';
|
||||||
autocomplete, api, alerts,
|
|
||||||
userFilter
|
|
||||||
) {
|
|
||||||
const Flags = {};
|
|
||||||
const selected = new Map([
|
|
||||||
['cids', []],
|
|
||||||
['assignee', []],
|
|
||||||
['targetUid', []],
|
|
||||||
['reporterId', []],
|
|
||||||
]);
|
|
||||||
|
|
||||||
Flags.init = function () {
|
Chart.register(LineController, CategoryScale, LinearScale, LineElement, PointElement, Tooltip, Filler);
|
||||||
Flags.enableFilterForm();
|
|
||||||
Flags.enableCheckboxes();
|
|
||||||
Flags.handleBulkActions();
|
|
||||||
|
|
||||||
if (ajaxify.data.filters.hasOwnProperty('cid')) {
|
const selected = new Map([
|
||||||
selected.set('cids', Array.isArray(ajaxify.data.filters.cid) ?
|
['cids', []],
|
||||||
ajaxify.data.filters.cid : [ajaxify.data.filters.cid]);
|
['assignee', []],
|
||||||
}
|
['targetUid', []],
|
||||||
|
['reporterId', []],
|
||||||
|
]);
|
||||||
|
|
||||||
categoryFilter.init($('[component="category/dropdown"]'), {
|
export function init() {
|
||||||
privilege: 'moderate',
|
enableFilterForm();
|
||||||
selectedCids: selected.get('cids'),
|
enableCheckboxes();
|
||||||
updateButton: function ({ selectedCids: cids }) {
|
handleBulkActions();
|
||||||
selected.set('cids', cids);
|
|
||||||
applyFilters();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
['assignee', 'targetUid', 'reporterId'].forEach((filter) => {
|
if (ajaxify.data.filters.hasOwnProperty('cid')) {
|
||||||
if (ajaxify.data.filters.hasOwnProperty('filter')) {
|
selected.set('cids', Array.isArray(ajaxify.data.filters.cid) ?
|
||||||
selected.set(filter, ajaxify.data.selected[filter]);
|
ajaxify.data.filters.cid : [ajaxify.data.filters.cid]);
|
||||||
}
|
|
||||||
const filterEl = $(`[component="flags/filter/${filter}"]`);
|
|
||||||
userFilter.init(filterEl, {
|
|
||||||
selectedUsers: selected.get(filter),
|
|
||||||
template: 'partials/flags/filters',
|
|
||||||
selectedBlock: `selected.${filter}`,
|
|
||||||
onSelect: function (_selectedUsers) {
|
|
||||||
selected.set(filter, _selectedUsers);
|
|
||||||
},
|
|
||||||
onHidden: function () {
|
|
||||||
applyFilters();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
components.get('flags/list')
|
|
||||||
.on('click', '[data-flag-id]', function (e) {
|
|
||||||
if (['BUTTON', 'A'].includes(e.target.nodeName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const flagId = this.getAttribute('data-flag-id');
|
|
||||||
ajaxify.go('flags/' + flagId);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#flags-daily-wrapper').one('shown.bs.collapse', function () {
|
|
||||||
Flags.handleGraphs();
|
|
||||||
});
|
|
||||||
|
|
||||||
autocomplete.user($('#filter-assignee, #filter-targetUid, #filter-reporterId'), (ev, ui) => {
|
|
||||||
setTimeout(() => { ev.target.value = ui.item.user.uid; });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Flags.enableFilterForm = function () {
|
|
||||||
const $filtersEl = components.get('flags/filters');
|
|
||||||
if ($filtersEl && $filtersEl.get(0).nodeName !== 'FORM') {
|
|
||||||
// Harmony; update hidden form and submit on change
|
|
||||||
const filtersEl = $filtersEl.get(0);
|
|
||||||
const formEl = filtersEl.querySelector('form');
|
|
||||||
|
|
||||||
filtersEl.addEventListener('click', (e) => {
|
|
||||||
const subselector = e.target.closest('[data-value]');
|
|
||||||
if (!subselector) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = subselector.getAttribute('data-name');
|
|
||||||
const value = subselector.getAttribute('data-value');
|
|
||||||
|
|
||||||
formEl[name].value = value;
|
|
||||||
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Persona; parse ajaxify data to set form values to reflect current filters
|
|
||||||
for (const filter in ajaxify.data.filters) {
|
|
||||||
if (ajaxify.data.filters.hasOwnProperty(filter)) {
|
|
||||||
$filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$filtersEl.find('[name="sort"]').val(ajaxify.data.sort);
|
|
||||||
|
|
||||||
document.getElementById('apply-filters').addEventListener('click', function () {
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
|
|
||||||
$filtersEl.find('button[data-target="#more-filters"]').click((ev) => {
|
|
||||||
const textVariant = ev.target.getAttribute('data-text-variant');
|
|
||||||
if (!textVariant) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ev.target.setAttribute('data-text-variant', ev.target.textContent);
|
|
||||||
ev.target.firstChild.textContent = textVariant;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function applyFilters() {
|
|
||||||
let formEl = components.get('flags/filters').get(0);
|
|
||||||
if (!formEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (formEl.nodeName !== 'FORM') {
|
|
||||||
formEl = formEl.querySelector('form');
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = new FormData(formEl);
|
|
||||||
|
|
||||||
// cid is special comes from categoryFilter module
|
|
||||||
selected.get('cids').forEach(function (cid) {
|
|
||||||
payload.append('cid', cid);
|
|
||||||
});
|
|
||||||
|
|
||||||
// these three fields are special; comes from userFilter module
|
|
||||||
['assignee', 'targetUid', 'reporterId'].forEach((filter) => {
|
|
||||||
selected.get(filter).forEach(({ uid }) => {
|
|
||||||
payload.append(filter, uid);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const length = Array.from(payload.values()).filter(Boolean);
|
|
||||||
const qs = new URLSearchParams(payload).toString();
|
|
||||||
|
|
||||||
ajaxify.go('flags?' + (length ? qs : 'reset=1'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Flags.enableCheckboxes = function () {
|
categoryFilter.init($('[component="category/dropdown"]'), {
|
||||||
const flagsList = document.querySelector('[component="flags/list"]');
|
privilege: 'moderate',
|
||||||
const checkboxes = flagsList.querySelectorAll('[data-flag-id] input[type="checkbox"]');
|
selectedCids: selected.get('cids'),
|
||||||
const bulkEl = document.querySelector('[component="flags/bulk-actions"] button');
|
updateButton: function ({ selectedCids: cids }) {
|
||||||
let lastClicked;
|
selected.set('cids', cids);
|
||||||
|
applyFilters();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
document.querySelector('[data-action="toggle-all"]').addEventListener('click', function () {
|
['assignee', 'targetUid', 'reporterId'].forEach((filter) => {
|
||||||
const state = this.checked;
|
if (ajaxify.data.filters.hasOwnProperty('filter')) {
|
||||||
|
selected.set(filter, ajaxify.data.selected[filter]);
|
||||||
|
}
|
||||||
|
const filterEl = $(`[component="flags/filter/${filter}"]`);
|
||||||
|
userFilter.init(filterEl, {
|
||||||
|
selectedUsers: selected.get(filter),
|
||||||
|
template: 'partials/flags/filters',
|
||||||
|
selectedBlock: `selected.${filter}`,
|
||||||
|
onSelect: function (_selectedUsers) {
|
||||||
|
selected.set(filter, _selectedUsers);
|
||||||
|
},
|
||||||
|
onHidden: function () {
|
||||||
|
applyFilters();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
checkboxes.forEach(function (el) {
|
components.get('flags/list')
|
||||||
el.checked = state;
|
.on('click', '[data-flag-id]', function (e) {
|
||||||
});
|
if (['BUTTON', 'A'].includes(e.target.nodeName)) {
|
||||||
bulkEl.disabled = !state;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flagId = this.getAttribute('data-flag-id');
|
||||||
|
ajaxify.go('flags/' + flagId);
|
||||||
});
|
});
|
||||||
|
|
||||||
flagsList.addEventListener('click', function (e) {
|
$('#flags-daily-wrapper').one('shown.bs.collapse', function () {
|
||||||
const subselector = e.target.closest('input[type="checkbox"]');
|
handleGraphs();
|
||||||
if (subselector) {
|
});
|
||||||
// Stop checkbox clicks from going into the flag details
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
|
|
||||||
if (lastClicked && e.shiftKey && lastClicked !== subselector) {
|
autocomplete.user($('#filter-assignee, #filter-targetUid, #filter-reporterId'), (ev, ui) => {
|
||||||
// Select all the checkboxes in between
|
setTimeout(() => { ev.target.value = ui.item.user.uid; });
|
||||||
const state = subselector.checked;
|
});
|
||||||
let started = false;
|
}
|
||||||
|
|
||||||
checkboxes.forEach(function (el) {
|
export function enableFilterForm() {
|
||||||
if ([subselector, lastClicked].some(function (ref) {
|
const $filtersEl = components.get('flags/filters');
|
||||||
return ref === el;
|
if ($filtersEl && $filtersEl.get(0).nodeName !== 'FORM') {
|
||||||
})) {
|
// Harmony; update hidden form and submit on change
|
||||||
started = !started;
|
const filtersEl = $filtersEl.get(0);
|
||||||
}
|
const formEl = filtersEl.querySelector('form');
|
||||||
|
|
||||||
if (started) {
|
filtersEl.addEventListener('click', (e) => {
|
||||||
el.checked = state;
|
const subselector = e.target.closest('[data-value]');
|
||||||
}
|
if (!subselector) {
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = subselector.getAttribute('data-name');
|
||||||
|
const value = subselector.getAttribute('data-value');
|
||||||
|
|
||||||
|
formEl[name].value = value;
|
||||||
|
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Persona; parse ajaxify data to set form values to reflect current filters
|
||||||
|
for (const filter in ajaxify.data.filters) {
|
||||||
|
if (ajaxify.data.filters.hasOwnProperty(filter)) {
|
||||||
|
$filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$filtersEl.find('[name="sort"]').val(ajaxify.data.sort);
|
||||||
|
|
||||||
|
document.getElementById('apply-filters').addEventListener('click', function () {
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
$filtersEl.find('button[data-target="#more-filters"]').click((ev) => {
|
||||||
|
const textVariant = ev.target.getAttribute('data-text-variant');
|
||||||
|
if (!textVariant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.target.setAttribute('data-text-variant', ev.target.textContent);
|
||||||
|
ev.target.firstChild.textContent = textVariant;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
let formEl = components.get('flags/filters').get(0);
|
||||||
|
if (!formEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formEl.nodeName !== 'FORM') {
|
||||||
|
formEl = formEl.querySelector('form');
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = new FormData(formEl);
|
||||||
|
|
||||||
|
// cid is special comes from categoryFilter module
|
||||||
|
selected.get('cids').forEach(function (cid) {
|
||||||
|
payload.append('cid', cid);
|
||||||
|
});
|
||||||
|
|
||||||
|
// these three fields are special; comes from userFilter module
|
||||||
|
['assignee', 'targetUid', 'reporterId'].forEach((filter) => {
|
||||||
|
selected.get(filter).forEach(({ uid }) => {
|
||||||
|
payload.append(filter, uid);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const length = Array.from(payload.values()).filter(Boolean);
|
||||||
|
const qs = new URLSearchParams(payload).toString();
|
||||||
|
|
||||||
|
ajaxify.go('flags?' + (length ? qs : 'reset=1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enableCheckboxes() {
|
||||||
|
const flagsList = document.querySelector('[component="flags/list"]');
|
||||||
|
const checkboxes = flagsList.querySelectorAll('[data-flag-id] input[type="checkbox"]');
|
||||||
|
const bulkEl = document.querySelector('[component="flags/bulk-actions"] button');
|
||||||
|
let lastClicked;
|
||||||
|
|
||||||
|
document.querySelector('[data-action="toggle-all"]').addEventListener('click', function () {
|
||||||
|
const state = this.checked;
|
||||||
|
|
||||||
|
checkboxes.forEach(function (el) {
|
||||||
|
el.checked = state;
|
||||||
|
});
|
||||||
|
bulkEl.disabled = !state;
|
||||||
|
});
|
||||||
|
|
||||||
|
flagsList.addEventListener('click', function (e) {
|
||||||
|
const subselector = e.target.closest('input[type="checkbox"]');
|
||||||
|
if (subselector) {
|
||||||
|
// Stop checkbox clicks from going into the flag details
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
if (lastClicked && e.shiftKey && lastClicked !== subselector) {
|
||||||
|
// Select all the checkboxes in between
|
||||||
|
const state = subselector.checked;
|
||||||
|
let started = false;
|
||||||
|
|
||||||
|
checkboxes.forEach(function (el) {
|
||||||
|
if ([subselector, lastClicked].some(function (ref) {
|
||||||
|
return ref === el;
|
||||||
|
})) {
|
||||||
|
started = !started;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (started) {
|
||||||
|
el.checked = state;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// (De)activate bulk actions button based on checkboxes' state
|
||||||
|
bulkEl.disabled = !Array.prototype.some.call(checkboxes, function (el) {
|
||||||
|
return el.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
lastClicked = subselector;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you miss the checkbox, don't descend into the flag details, either
|
||||||
|
if (e.target.querySelector('input[type="checkbox"]')) {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleBulkActions() {
|
||||||
|
document.querySelector('[component="flags/bulk-actions"]').addEventListener('click', function (e) {
|
||||||
|
const subselector = e.target.closest('[data-action]');
|
||||||
|
if (subselector) {
|
||||||
|
const action = subselector.getAttribute('data-action');
|
||||||
|
const flagIds = getSelected();
|
||||||
|
const promises = flagIds.map((flagId) => {
|
||||||
|
const data = {};
|
||||||
|
if (action === 'bulk-assign') {
|
||||||
|
data.assignee = app.user.uid;
|
||||||
|
} else if (action === 'bulk-mark-resolved') {
|
||||||
|
data.state = 'resolved';
|
||||||
|
}
|
||||||
|
return api.put(`/flags/${flagId}`, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.allSettled(promises).then(function (results) {
|
||||||
|
const fulfilled = results.filter(function (res) {
|
||||||
|
return res.status === 'fulfilled';
|
||||||
|
}).length;
|
||||||
|
const errors = results.filter(function (res) {
|
||||||
|
return res.status === 'rejected';
|
||||||
|
});
|
||||||
|
if (fulfilled) {
|
||||||
|
alerts.success('[[flags:bulk-success, ' + fulfilled + ']]');
|
||||||
|
ajaxify.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// (De)activate bulk actions button based on checkboxes' state
|
errors.forEach(function (res) {
|
||||||
bulkEl.disabled = !Array.prototype.some.call(checkboxes, function (el) {
|
alerts.error(res.reason);
|
||||||
return el.checked;
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
lastClicked = subselector;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you miss the checkbox, don't descend into the flag details, either
|
|
||||||
if (e.target.querySelector('input[type="checkbox"]')) {
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Flags.handleBulkActions = function () {
|
|
||||||
document.querySelector('[component="flags/bulk-actions"]').addEventListener('click', function (e) {
|
|
||||||
const subselector = e.target.closest('[data-action]');
|
|
||||||
if (subselector) {
|
|
||||||
const action = subselector.getAttribute('data-action');
|
|
||||||
const flagIds = Flags.getSelected();
|
|
||||||
const promises = flagIds.map((flagId) => {
|
|
||||||
const data = {};
|
|
||||||
if (action === 'bulk-assign') {
|
|
||||||
data.assignee = app.user.uid;
|
|
||||||
} else if (action === 'bulk-mark-resolved') {
|
|
||||||
data.state = 'resolved';
|
|
||||||
}
|
|
||||||
return api.put(`/flags/${flagId}`, data);
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.allSettled(promises).then(function (results) {
|
|
||||||
const fulfilled = results.filter(function (res) {
|
|
||||||
return res.status === 'fulfilled';
|
|
||||||
}).length;
|
|
||||||
const errors = results.filter(function (res) {
|
|
||||||
return res.status === 'rejected';
|
|
||||||
});
|
|
||||||
if (fulfilled) {
|
|
||||||
alerts.success('[[flags:bulk-success, ' + fulfilled + ']]');
|
|
||||||
ajaxify.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
errors.forEach(function (res) {
|
|
||||||
alerts.error(res.reason);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Flags.getSelected = function () {
|
|
||||||
const checkboxes = document.querySelectorAll('[component="flags/list"] [data-flag-id] input[type="checkbox"]');
|
|
||||||
const payload = [];
|
|
||||||
checkboxes.forEach(function (el) {
|
|
||||||
if (el.checked) {
|
|
||||||
payload.push(el.closest('[data-flag-id]').getAttribute('data-flag-id'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
Flags.handleGraphs = function () {
|
|
||||||
const dailyCanvas = document.getElementById('flags:daily');
|
|
||||||
const dailyLabels = utils.getDaysArray().map(function (text, idx) {
|
|
||||||
return idx % 3 ? '' : text;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (utils.isMobile()) {
|
|
||||||
Chart.defaults.plugins.tooltip.enabled = false;
|
|
||||||
}
|
}
|
||||||
const data = {
|
});
|
||||||
'flags:daily': {
|
}
|
||||||
labels: dailyLabels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: '',
|
|
||||||
backgroundColor: 'rgba(151,187,205,0.2)',
|
|
||||||
borderColor: 'rgba(151,187,205,1)',
|
|
||||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
|
||||||
data: ajaxify.data.analytics,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
dailyCanvas.width = $(dailyCanvas).parent().width();
|
export function getSelected() {
|
||||||
new Chart(dailyCanvas.getContext('2d'), {
|
const checkboxes = document.querySelectorAll('[component="flags/list"] [data-flag-id] input[type="checkbox"]');
|
||||||
type: 'line',
|
const payload = [];
|
||||||
data: data['flags:daily'],
|
checkboxes.forEach(function (el) {
|
||||||
options: {
|
if (el.checked) {
|
||||||
responsive: true,
|
payload.push(el.closest('[data-flag-id]').getAttribute('data-flag-id'));
|
||||||
animation: false,
|
}
|
||||||
plugins: {
|
});
|
||||||
legend: {
|
|
||||||
display: false,
|
return payload;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
export function handleGraphs() {
|
||||||
|
const dailyCanvas = document.getElementById('flags:daily');
|
||||||
|
const dailyLabels = utils.getDaysArray().map(function (text, idx) {
|
||||||
|
return idx % 3 ? '' : text;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (utils.isMobile()) {
|
||||||
|
Chart.defaults.plugins.tooltip.enabled = false;
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
'flags:daily': {
|
||||||
|
labels: dailyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
backgroundColor: 'rgba(151,187,205,0.2)',
|
||||||
|
borderColor: 'rgba(151,187,205,1)',
|
||||||
|
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||||
|
pointHoverBackgroundColor: '#fff',
|
||||||
|
pointBorderColor: '#fff',
|
||||||
|
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
||||||
|
data: ajaxify.data.analytics,
|
||||||
},
|
},
|
||||||
scales: {
|
],
|
||||||
y: {
|
},
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return Flags;
|
dailyCanvas.width = $(dailyCanvas).parent().width();
|
||||||
});
|
new Chart(dailyCanvas.getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: data['flags:daily'],
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
animation: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,126 +1,129 @@
|
|||||||
'use strict';
|
import {
|
||||||
|
Chart,
|
||||||
|
LineController,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Tooltip,
|
||||||
|
Filler,
|
||||||
|
} from 'chart.js';
|
||||||
|
|
||||||
define('forum/ip-blacklist', [
|
import * as Benchpress from 'benchpressjs';
|
||||||
'chart.js/auto', 'benchpress', 'bootbox', 'alerts',
|
import * as bootbox from 'bootbox';
|
||||||
], function ({ Chart }, Benchpress, bootbox, alerts) {
|
import * as alerts from '../modules/alerts';
|
||||||
const Blacklist = {};
|
|
||||||
|
|
||||||
Blacklist.init = function () {
|
Chart.register(LineController, CategoryScale, LinearScale, LineElement, PointElement, Tooltip, Filler);
|
||||||
const blacklist = $('#blacklist-rules');
|
|
||||||
|
|
||||||
blacklist.on('keyup', function () {
|
export function init() {
|
||||||
$('#blacklist-rules-holder').val(blacklist.val());
|
const blacklist = $('#blacklist-rules');
|
||||||
});
|
|
||||||
|
|
||||||
$('[data-action="apply"]').on('click', function () {
|
blacklist.on('keyup', function () {
|
||||||
socket.emit('blacklist.save', blacklist.val(), function (err) {
|
$('#blacklist-rules-holder').val(blacklist.val());
|
||||||
if (err) {
|
});
|
||||||
return alerts.error(err);
|
|
||||||
}
|
$('[data-action="apply"]').on('click', function () {
|
||||||
alerts.alert({
|
socket.emit('blacklist.save', blacklist.val(), function (err) {
|
||||||
type: 'success',
|
if (err) {
|
||||||
alert_id: 'blacklist-saved',
|
return alerts.error(err);
|
||||||
title: '[[ip-blacklist:alerts.applied-success]]',
|
}
|
||||||
});
|
alerts.alert({
|
||||||
|
type: 'success',
|
||||||
|
alert_id: 'blacklist-saved',
|
||||||
|
title: '[[ip-blacklist:alerts.applied-success]]',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('[data-action="test"]').on('click', function () {
|
$('[data-action="test"]').on('click', function () {
|
||||||
socket.emit('blacklist.validate', {
|
socket.emit('blacklist.validate', {
|
||||||
rules: blacklist.val(),
|
rules: blacklist.val(),
|
||||||
}, function (err, data) {
|
}, function (err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return alerts.error(err);
|
return alerts.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
Benchpress.render('admin/partials/blacklist-validate', data).then(function (html) {
|
Benchpress.render('admin/partials/blacklist-validate', data).then(function (html) {
|
||||||
bootbox.alert(html);
|
bootbox.alert(html);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Blacklist.setupAnalytics();
|
setupAnalytics();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupAnalytics() {
|
||||||
|
const hourlyCanvas = document.getElementById('blacklist:hourly');
|
||||||
|
const dailyCanvas = document.getElementById('blacklist:daily');
|
||||||
|
const hourlyLabels = utils.getHoursArray().map(function (text, idx) {
|
||||||
|
return idx % 3 ? '' : text;
|
||||||
|
});
|
||||||
|
const dailyLabels = utils.getDaysArray().slice(-7).map(function (text, idx) {
|
||||||
|
return idx % 3 ? '' : text;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (utils.isMobile()) {
|
||||||
|
Chart.defaults.plugins.tooltip.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
'blacklist:hourly': {
|
||||||
|
labels: hourlyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
fill: 'origin',
|
||||||
|
tension: 0.25,
|
||||||
|
backgroundColor: 'rgba(186,139,175,0.2)',
|
||||||
|
borderColor: 'rgba(186,139,175,1)',
|
||||||
|
pointBackgroundColor: 'rgba(186,139,175,1)',
|
||||||
|
pointHoverBackgroundColor: '#fff',
|
||||||
|
pointBorderColor: '#fff',
|
||||||
|
pointHoverBorderColor: 'rgba(186,139,175,1)',
|
||||||
|
data: ajaxify.data.analytics.hourly,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'blacklist:daily': {
|
||||||
|
labels: dailyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
fill: 'origin',
|
||||||
|
tension: 0.25,
|
||||||
|
backgroundColor: 'rgba(151,187,205,0.2)',
|
||||||
|
borderColor: 'rgba(151,187,205,1)',
|
||||||
|
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||||
|
pointHoverBackgroundColor: '#fff',
|
||||||
|
pointBorderColor: '#fff',
|
||||||
|
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
||||||
|
data: ajaxify.data.analytics.daily,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Blacklist.setupAnalytics = function () {
|
const chartOpts = {
|
||||||
const hourlyCanvas = document.getElementById('blacklist:hourly');
|
responsive: true,
|
||||||
const dailyCanvas = document.getElementById('blacklist:daily');
|
scales: {
|
||||||
const hourlyLabels = utils.getHoursArray().map(function (text, idx) {
|
y: {
|
||||||
return idx % 3 ? '' : text;
|
position: 'left',
|
||||||
});
|
type: 'linear',
|
||||||
const dailyLabels = utils.getDaysArray().slice(-7).map(function (text, idx) {
|
beginAtZero: true,
|
||||||
return idx % 3 ? '' : text;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (utils.isMobile()) {
|
|
||||||
Chart.defaults.plugins.tooltip.enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
'blacklist:hourly': {
|
|
||||||
labels: hourlyLabels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: '',
|
|
||||||
fill: 'origin',
|
|
||||||
tension: 0.25,
|
|
||||||
backgroundColor: 'rgba(186,139,175,0.2)',
|
|
||||||
borderColor: 'rgba(186,139,175,1)',
|
|
||||||
pointBackgroundColor: 'rgba(186,139,175,1)',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(186,139,175,1)',
|
|
||||||
data: ajaxify.data.analytics.hourly,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
'blacklist:daily': {
|
},
|
||||||
labels: dailyLabels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: '',
|
|
||||||
backgroundColor: 'rgba(151,187,205,0.2)',
|
|
||||||
borderColor: 'rgba(151,187,205,1)',
|
|
||||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
|
||||||
data: ajaxify.data.analytics.daily,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartOpts = {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: true,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
position: 'left',
|
|
||||||
type: 'linear',
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
new Chart(hourlyCanvas.getContext('2d'), {
|
|
||||||
type: 'line',
|
|
||||||
data: data['blacklist:hourly'],
|
|
||||||
options: chartOpts,
|
|
||||||
});
|
|
||||||
|
|
||||||
new Chart(dailyCanvas.getContext('2d'), {
|
|
||||||
type: 'line',
|
|
||||||
data: data['blacklist:daily'],
|
|
||||||
options: chartOpts,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return Blacklist;
|
new Chart(hourlyCanvas.getContext('2d'), {
|
||||||
});
|
type: 'line',
|
||||||
|
data: data['blacklist:hourly'],
|
||||||
|
options: chartOpts,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(dailyCanvas.getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: data['blacklist:daily'],
|
||||||
|
options: chartOpts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="graph-container position-relative" id="analytics-traffic-container" style="aspect-ratio: 16/9;">
|
<div class="graph-container position-relative" id="analytics-traffic-container" style="aspect-ratio: 2;">
|
||||||
<canvas id="analytics-traffic" ></canvas>
|
<canvas id="analytics-traffic" ></canvas>
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|||||||
Reference in New Issue
Block a user