Merge branch 'feature/graphing' into 'master'
Added Graphing See merge request fronk/thetool!753
This commit is contained in:
9
application/Graphing/Graphing.php
Normal file
9
application/Graphing/Graphing.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @property mixed|null $name
|
||||
*/
|
||||
class Graphing extends mfBaseModel
|
||||
{
|
||||
|
||||
}
|
||||
64
application/Graphing/GraphingController.php
Normal file
64
application/Graphing/GraphingController.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
class GraphingController extends mfBaseController{
|
||||
private string $ZABBIX_API_URL = ZABBIX_API_URL;
|
||||
private string $ZABBIX_API_KEY = ZABBIX_API_KEY;
|
||||
private Zabbix $zabbix;
|
||||
|
||||
protected function init(): void {
|
||||
$me = new User();
|
||||
$me->loadMe();
|
||||
$this->layout()->set("me", $me);
|
||||
$this->me = $me;
|
||||
|
||||
if (!$this->me->isAdmin()) {
|
||||
$this->redirect("dashboard");
|
||||
}
|
||||
|
||||
$this->zabbix = new Zabbix($this->ZABBIX_API_URL, $this->ZABBIX_API_KEY);
|
||||
}
|
||||
|
||||
protected function indexAction() {
|
||||
$this->layout()->set('additionalJS', ["plugins/chart.js/chart.4.4.6.js", "plugins/chart.js/chartjs-adapter-moment.min.js"]);
|
||||
Helper::renderVue($this, "DeviceGraphing", $this->mod, []);
|
||||
}
|
||||
|
||||
|
||||
protected function dataAction() {
|
||||
header('Content-Type: application/json');
|
||||
$hostId = $this->request->hostId;
|
||||
$hostInterfaceItems = $this->zabbix->getHostInterfaceItems($hostId, '');
|
||||
// limit to 25 items
|
||||
$hostInterfaceItems = array_slice($hostInterfaceItems, 0, 25);
|
||||
|
||||
$itemIds = array_map(function($item) {
|
||||
return $item['itemid'];
|
||||
}, $hostInterfaceItems);
|
||||
|
||||
|
||||
$itemValues = $this->zabbix->getItemValues($itemIds, 1000);
|
||||
|
||||
$out = [];
|
||||
foreach ($hostInterfaceItems as $item) {
|
||||
$out[$item['itemid']] = [
|
||||
'name' => str_replace('Bits', 'Mbps', $item['name']),
|
||||
'units' => $item['units'],
|
||||
'values' => []
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($itemValues as $itemValue) {
|
||||
$out[$itemValue['itemid']]['values'][] = [
|
||||
'clock' => $itemValue['clock'],
|
||||
'value' => $itemValue['value'] / 1000000
|
||||
];
|
||||
}
|
||||
|
||||
// sort by name
|
||||
uasort($out, function($a, $b) {
|
||||
return strcmp($a['name'], $b['name']);
|
||||
});
|
||||
|
||||
|
||||
die(json_encode($out));
|
||||
}
|
||||
}
|
||||
@@ -46,13 +46,13 @@ class Zabbix {
|
||||
return $response['result'];
|
||||
}
|
||||
|
||||
public function getItemValues($itemIds) {
|
||||
public function getItemValues($itemIds, $limit = 15) {
|
||||
$response = $this->zabbixRequest('history.get', array(
|
||||
'itemids' => $itemIds,
|
||||
'output' => 'extend',
|
||||
'sortfield' => 'clock',
|
||||
'sortorder' => 'DESC',
|
||||
'limit' => 15
|
||||
'limit' => $limit
|
||||
));
|
||||
return $response['result'];
|
||||
}
|
||||
@@ -64,10 +64,13 @@ class Zabbix {
|
||||
return $response['result'];
|
||||
}
|
||||
|
||||
public function getInterfaceItems($hostId, $interfaceName) {
|
||||
public function getHostInterfaceItems($hostId) {
|
||||
$response = $this->zabbixRequest('item.get', array(
|
||||
'hostids' => $hostId,
|
||||
'search' => array('name' => array($interfaceName, "Bits"))
|
||||
'output' => ['itemid','name_resolved', 'key_', 'units'],
|
||||
'search' => ['name' => ["Bits received", "Bits sent"]],
|
||||
'searchByAny' => true,
|
||||
'sortfield' => 'name'
|
||||
));
|
||||
return $response['result'];
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ Vue.component('DeviceTable', {
|
||||
|
||||
<template v-if="row.zabbix_host_id !== '0' && row.zabbix_host_id !== null">
|
||||
<a :href="window['TT_CONFIG']['ZABBIX_URL'] + '/zabbix.php?action=latest.view&hostids%5B%5D=' + row.zabbix_host_id" target="_blank" class="text-info" title="Zabbix"><i class="fas fa-server"></i></a>
|
||||
<a :href="window['TT_CONFIG']['GRAFANA_URL'] + '/d/Ta3PtRWZk/mikrotik-dashboard?orgId=1&var-host=' + row.name" target="_blank" class="text-info" title="Grafana"><i class="fas fa-chart-line"></i></a>
|
||||
<a :href="window['TT_CONFIG']['BASE_URL'] + '/Graphing?id=' + row.zabbix_host_id + '&hostname=' + row.name" target="_blank" class="text-info" title="Graphen"><i class="fas fa-chart-line"></i></a>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
||||
133
public/js/pages/DeviceGraphing/DeviceGraphing.js
Normal file
133
public/js/pages/DeviceGraphing/DeviceGraphing.js
Normal file
@@ -0,0 +1,133 @@
|
||||
Vue.component('tt-graph', {
|
||||
template: `
|
||||
<!-- use chart js with datasets etc everything needed for chart.js here to be usable width and height aswell -->
|
||||
<tt-card>
|
||||
<template v-slot:header>
|
||||
<h3 style="text-align: center;user-select: none">{{ header }}</h3>
|
||||
</template>
|
||||
|
||||
<div ref="container">
|
||||
<canvas ref="chart" :style="{width: width + 'px', height: height + 'px'}"></canvas></div>
|
||||
</tt-card>
|
||||
`,
|
||||
props: ['data', 'labels', 'header'],
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
width: 400,
|
||||
height: 220
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const ctx = this.$refs.chart.getContext('2d');
|
||||
this.chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: this.labels,
|
||||
datasets: this.data
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
// time is epoch
|
||||
parser: 'X',
|
||||
// unit: 'hour',
|
||||
displayFormats: {
|
||||
minute: 'DD.M. HH:mm',
|
||||
}
|
||||
},
|
||||
// min: '00:00:00',
|
||||
// max: '24:00:00',
|
||||
ticks: {
|
||||
autoSkipPadding: 25,
|
||||
autoSkip: true,
|
||||
maxRotation: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
responsive: true,
|
||||
},
|
||||
});
|
||||
|
||||
// set width and height to the canvas element actual width and height
|
||||
this.width = this.$refs.container.width;
|
||||
this.height = this.$refs.container.height;
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
Vue.component('device-graphing', {
|
||||
template: `
|
||||
<div style="display: grid; grid-template-columns: 45vw 45vw; gap: 1rem;">
|
||||
<h3 style="text-align: center;user-select: none;grid-column: 1 / span 2;">{{ hostname }}</h3>
|
||||
|
||||
<tt-loader v-if="graphs.length === 0"></tt-loader>
|
||||
|
||||
<template v-for="graph in graphs">
|
||||
<tt-graph :data="graph.data" :labels="graph.labels" :header="graph.name"></tt-graph>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
graphs: [],
|
||||
hostname: ''
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
// get hostname from url params
|
||||
this.hostname = new URLSearchParams(window.location.search).get('hostname');
|
||||
console.log(this.hostname);
|
||||
// get id from url params
|
||||
const id = new URLSearchParams(window.location.search).get('id');
|
||||
const response = await axios.get('/Graphing/data?id=' + id);
|
||||
const data = response.data;
|
||||
|
||||
const graphs = {};
|
||||
|
||||
for (const item in data) {
|
||||
// Create graphs.[item.name.split(':')[0]] if it doesn't exist
|
||||
if (!graphs[data[item].name.split(':')[0]]) {
|
||||
graphs[data[item].name.split(':')[0]] = {
|
||||
name: data[item].name.split(':')[0],
|
||||
data: [],
|
||||
labels: []
|
||||
}
|
||||
}
|
||||
|
||||
// Add the data to the graph but check if it's received or sent and already exists
|
||||
|
||||
if (data[item].name.split(':')[1].includes('received') && graphs[data[item].name.split(':')[0]].data.find(data => data.label.includes('received'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[item].name.split(':')[1].includes('sent') && graphs[data[item].name.split(':')[0]].data.find(data => data.label.includes('sent'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
graphs[data[item].name.split(':')[0]].data.push({
|
||||
label: data[item].name.split(':')[1],
|
||||
data: data[item].values.map(value => value.value),
|
||||
fill: false,
|
||||
borderColor: data[item].name.includes('received') ? 'rgb(75, 192, 192)' : 'rgb(192, 75, 75)',
|
||||
tension: 0.1
|
||||
});
|
||||
}
|
||||
|
||||
// Add the labels to the this.graphs
|
||||
for (const graph in graphs) {
|
||||
graphs[graph].labels = data[Object.keys(data).find(key => data[key].name.includes(graph))].values.map(value => value.clock);
|
||||
this.graphs.push(graphs[graph]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
20
public/plugins/chart.js/chart.4.4.6.js
Normal file
20
public/plugins/chart.js/chart.4.4.6.js
Normal file
File diff suppressed because one or more lines are too long
7
public/plugins/chart.js/chartjs-adapter-moment.min.js
vendored
Normal file
7
public/plugins/chart.js/chartjs-adapter-moment.min.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* chartjs-adapter-moment v1.0.1
|
||||
* 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()}}:{})}));
|
||||
Reference in New Issue
Block a user