|
@@ -10,73 +10,144 @@
|
|
|
<script language="javascript" type="text/javascript" src="../flot/jquery.flot.min.js"></script>
|
|
|
<script language="javascript" type="text/javascript" src="../flot/jquery.flot.stack.min.js"></script>
|
|
|
<script language="javascript" type="text/javascript" src="../flot/jquery.flot.time.min.js"></script>
|
|
|
- <script language="javascript" type="text/javascript" src="../flot/jquery.flot.selection.min.js"></script>
|
|
|
+ <script language="javascript" type="text/javascript" src="../flot/jquery.flot.navigate.min.js"></script>
|
|
|
<script language="javascript" type="text/javascript" src="../flot/jquery.flot.resize.min.js"></script>
|
|
|
|
|
|
</head>
|
|
|
<body>
|
|
|
- <header class="main-head">TS3 channel usage</header>
|
|
|
- <div id="tree"></div>
|
|
|
- <div id="graphs"></div>
|
|
|
+ <div class="wrapper">
|
|
|
+ <header class="main-head">TS3 channel usage</header>
|
|
|
+ <div id="graphs"></div>
|
|
|
+ <div id="settings">
|
|
|
+ <input type=button onclick="updateDate(addDays(startDate, -1))" value="-1 Day">
|
|
|
+ <input type="date" onchange="updateDate(this.valueAsDate)" id="date">
|
|
|
+ <input type=button onclick="updateDate(addDays(startDate, 1))" value="+1 Day">
|
|
|
+ </div>
|
|
|
+ <div id="tree"></div>
|
|
|
+ </div>
|
|
|
<script>
|
|
|
- function update() {
|
|
|
+ var startDate, endDate;
|
|
|
+ var eventTimeRange = [0, 0];
|
|
|
+
|
|
|
+ function updateDate(newDate) {
|
|
|
+ newDate.setHours(6, 0, 0, 0);
|
|
|
+ if(newDate.getTime() > eventTimeRange[1])
|
|
|
+ newDate = new Date(eventTimeRange[1]);
|
|
|
+ if(newDate.getTime() < eventTimeRange[0])
|
|
|
+ newDate = new Date(eventTimeRange[0]);
|
|
|
+
|
|
|
+ newDate.setHours(6, 0, 0, 0);
|
|
|
+ startDate = newDate;
|
|
|
+ endDate = addDays(startDate, 1);
|
|
|
+ document.getElementById('date').valueAsDate = startDate;
|
|
|
+ updatePlots();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ function update(init = false) {
|
|
|
fetch("ajax.php").then(res=>res.json()).then(function(json) {
|
|
|
-
|
|
|
- let channelTree = {channelId: 0, name: 'Server'};
|
|
|
+ console.log(json);
|
|
|
+ let channelTree = {channelId: 0, name: 'Server', id: 0};
|
|
|
+ eventTimeRange[0] = json.events.reduce((min, e) => min && (min < e[1]) ? min : e[1]) * 1000 - 12*3600000;
|
|
|
+ eventTimeRange[1] = json.events.reduce((max, e) => max && (max > e[1]) ? max : e[1]) * 1000 + 12*3600000;
|
|
|
+ var dateInp = document.getElementById('date');
|
|
|
+ dateInp.setAttribute('min', (new Date(eventTimeRange[0])).toISOString().split("T")[0]);
|
|
|
+ dateInp.setAttribute('max', (new Date(eventTimeRange[1])).toISOString().split("T")[0]);
|
|
|
+
|
|
|
+ if(init)
|
|
|
+ updateDate(new Date());
|
|
|
makeTree(channelTree, json.channels, json.events);
|
|
|
showTree(channelTree, document.getElementById('tree'));
|
|
|
|
|
|
- let div = $("#graphs")[0];
|
|
|
- div.innerHTML = "";
|
|
|
- for(const channel of channelTree.children) {
|
|
|
- if(channel.clientCount == 0)
|
|
|
- continue;
|
|
|
-
|
|
|
- let container = document.createElement("div");
|
|
|
- container.classList.add("demo-container");
|
|
|
- div.appendChild(container);
|
|
|
+ if(init) {
|
|
|
+ for(const channel of channelTree.children) {
|
|
|
+ if(channel.activeCount == 0)
|
|
|
+ continue;
|
|
|
+ let inp = document.getElementById(`cb_${channel.channelId}`);
|
|
|
+ inp.checked = true;
|
|
|
|
|
|
- let placeholder = document.createElement("div");
|
|
|
- placeholder.classList.add("demo-placeholder");
|
|
|
- container.appendChild(placeholder);
|
|
|
-
|
|
|
- plotEvents(channel, placeholder);
|
|
|
+ plotEvents(channel, document.getElementById("graphs"), true);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
console.log(channelTree);
|
|
|
});
|
|
|
}
|
|
|
- update();
|
|
|
+ update(true);
|
|
|
setInterval(update, 30 * 1000);
|
|
|
|
|
|
function makeTree(tree, channels, events) {
|
|
|
tree.events = events.filter(e => e[0] == tree.id);
|
|
|
+ if(tree.events.length > 0) {
|
|
|
+ tree.events.splice(0,0,[tree.events[0][0], (new Date()).getTime()/1000, tree.events[0][2]]);
|
|
|
+ }
|
|
|
tree.children = channels.filter((ch) => ch.parentId == tree.channelId).sort((a, b) => a.position - b.position);
|
|
|
tree.clientCount = tree.events.reduce((sum, e) => sum+e[2], 0) / (tree.events.length > 0 ? tree.events.length : 1);
|
|
|
-
|
|
|
+ tree.activeCount = tree.events.length > 0 ? tree.events[0][2] : 0;
|
|
|
for(const child of tree.children) {
|
|
|
- tree.clientCount += makeTree(child, channels.filter( ch => ch.parentId != tree.channelId), events.filter( e => e[0] != tree.id));
|
|
|
+ makeTree(child, channels.filter( ch => ch.parentId != tree.channelId), events.filter( e => e[0] != tree.id));
|
|
|
+ tree.clientCount += child.clientCount;
|
|
|
+ tree.activeCount += child.activeCount;
|
|
|
}
|
|
|
- return tree.clientCount;
|
|
|
}
|
|
|
-
|
|
|
function showTree(tree, el) {
|
|
|
- el.innerHTML = `${formatHeadings(tree.name)}`;
|
|
|
- if(tree.events.length > 0 && (tree.events.length != 1 || tree.events[0][2] != 0))
|
|
|
- for(const event of tree.events) {
|
|
|
- el.innerHTML += `<br>${(new Date(event[1]*1000)).toLocaleTimeString('de-DE', {timeZone: 'Europe/Berlin'})}: ${event[2]}`
|
|
|
- }
|
|
|
- let ul = document.createElement('ul');
|
|
|
- el.appendChild(ul);
|
|
|
+ let ul = document.getElementById(`ul_${tree.channelId}`);
|
|
|
+ if(!ul) {
|
|
|
+ let inp = document.createElement('input');
|
|
|
+ inp.type = 'checkbox';
|
|
|
+ inp.id = `cb_${tree.channelId}`;
|
|
|
+ inp.classList.add('checkbox');
|
|
|
+ el.appendChild(inp);
|
|
|
+
|
|
|
+ let cnt = document.createElement('label');
|
|
|
+ cnt.classList.add("count");
|
|
|
+ cnt.htmlFor = `cb_${tree.channelId}`;
|
|
|
+ el.appendChild(cnt);
|
|
|
+
|
|
|
+ let label = document.createElement('label');
|
|
|
+ label.htmlFor = `cb_${tree.channelId}`;
|
|
|
+ el.appendChild(label);
|
|
|
+
|
|
|
+ ul = document.createElement('ul');
|
|
|
+ ul.id = `ul_${tree.channelId}`;
|
|
|
+ el.appendChild(ul);
|
|
|
+ }
|
|
|
+ let inp = document.getElementById(`cb_${tree.channelId}`);
|
|
|
+ inp.onchange = function() {
|
|
|
+ plotEvents(tree, document.getElementById("graphs"), this.checked);
|
|
|
+ };
|
|
|
+ if(inp.checked) {
|
|
|
+ plotEvents(tree, document.getElementById("graphs"), true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(tree.events.length > 0)
|
|
|
+ inp.nextSibling.textContent = tree.events[0][2] != 0 ? tree.events[0][2] : '';
|
|
|
+
|
|
|
+ inp.nextSibling.nextSibling.innerHTML = formatHeadings(tree.name);
|
|
|
+
|
|
|
+ let prevLi = {};
|
|
|
+ let showSpacer = false;
|
|
|
for(const channel of tree.children) {
|
|
|
- if(channel.clientCount == 0)
|
|
|
+ if(channel.clientCount == 0) {
|
|
|
+ showSpacer = formatHeadings(channel.name, false).length == 1;
|
|
|
continue;
|
|
|
- let li = document.createElement('li');
|
|
|
- ul.appendChild(li);
|
|
|
+ }
|
|
|
+ let li = document.getElementById(`li_${channel.channelId}`);
|
|
|
+ if(!li) {
|
|
|
+ li = document.createElement('li');
|
|
|
+ li.id = `li_${channel.channelId}`;
|
|
|
+ if(prevLi.nextSibling)
|
|
|
+ ul.insertBefore(li, prevLi.nextSibling);
|
|
|
+ else
|
|
|
+ ul.appendChild(li);
|
|
|
+ }
|
|
|
+ if(showSpacer) {
|
|
|
+ li.classList.add("spacer");
|
|
|
+ showSpacer = false;
|
|
|
+ }
|
|
|
+ prevLi = li;
|
|
|
showTree(channel, li);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
function formatHeadings(str, html = true) {
|
|
|
const res = /(?:\[(.)spacer[^\]]*\])?(.*)/.exec(str);
|
|
|
if(!html)
|
|
@@ -94,13 +165,35 @@
|
|
|
return `<div class='channel'>${res[2]}</div>`;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- function plotEvents(tree, div) {
|
|
|
+
|
|
|
+ var plots = {};
|
|
|
+ var viewMin = 0;
|
|
|
+ var viewMax = 0;
|
|
|
+ function plotEvents(tree, div, visible = true) {
|
|
|
+ let placeholder = document.getElementById(`graph_${tree.channelId}`);
|
|
|
+ if(!placeholder) {
|
|
|
+
|
|
|
+ container = document.createElement("div");
|
|
|
+ container.classList.add("demo-container");
|
|
|
+ div.appendChild(container);
|
|
|
+
|
|
|
+ placeholder = document.createElement("div");
|
|
|
+ placeholder.classList.add("demo-placeholder");
|
|
|
+ placeholder.id = `graph_${tree.channelId}`;
|
|
|
+ container.appendChild(placeholder);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!document.getElementById(`cb_${tree.channelId}`).checked) {
|
|
|
+ placeholder.parentNode.style.display = 'none';
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ placeholder.parentNode.style.display = 'block';
|
|
|
+ }
|
|
|
let series = [];
|
|
|
function iterChilds(tree, prefix="") {
|
|
|
const data = tree.events.map(e => [e[1]*1000, e[2]]);
|
|
|
const name = prefix + formatHeadings(tree.name, false);
|
|
|
- if(data.length > 0 && (data.length != 1 || data[0][1] != 0)) {
|
|
|
+ if(data.length > 0 && (data.length != 2 || data[0][1] != 0)) {
|
|
|
series.push({
|
|
|
label: name,
|
|
|
data: data,
|
|
@@ -116,14 +209,66 @@
|
|
|
}
|
|
|
}
|
|
|
iterChilds(tree);
|
|
|
+ if(!plots[tree.channelId]) {
|
|
|
+ plots[tree.channelId] = $.plot(placeholder, series, {
|
|
|
+ xaxis: {
|
|
|
+ mode: "time",
|
|
|
+ timeBase: "milliseconds",
|
|
|
+ timezone: "browser",
|
|
|
+ min: viewMin ? viewMin : startDate.getTime(),
|
|
|
+ max: viewMax ? viewMax : endDate.getTime(),
|
|
|
+ zoomRange: [60000, null],
|
|
|
+ panRange: eventTimeRange
|
|
|
+ },
|
|
|
+ yaxis: {
|
|
|
+ zoomRange: false,
|
|
|
+ panRange: false
|
|
|
+ },
|
|
|
+ zoom: {
|
|
|
+ interactive: true
|
|
|
+ },
|
|
|
+ pan: {
|
|
|
+ interactive: true
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ position: 'nw'
|
|
|
+ }
|
|
|
+ });
|
|
|
+ $(placeholder).bind("plotpan plotzoom", function (event, plot) {
|
|
|
+ var axes = plot.getAxes();
|
|
|
+ viewMin = axes.xaxis.min;
|
|
|
+ viewMax = axes.xaxis.max;
|
|
|
+ for(const id in plots) {
|
|
|
+ plots[id].getOptions().xaxes[0].min = axes.xaxis.min;
|
|
|
+ plots[id].getOptions().xaxes[0].max = axes.xaxis.max;
|
|
|
+ plots[id].setupGrid();
|
|
|
+ plots[id].draw();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ plots[tree.channelId].getOptions().xaxis.panRange = eventTimeRange;
|
|
|
+ plots[tree.channelId].setData(series);
|
|
|
+ plots[tree.channelId].setupGrid();
|
|
|
+ plots[tree.channelId].draw();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- return $.plot(div, series, {
|
|
|
- xaxis: {
|
|
|
- mode: "time",
|
|
|
- timeBase: "milliseconds",
|
|
|
- timezone: "browser"
|
|
|
- }
|
|
|
- });
|
|
|
+ function updatePlots() {
|
|
|
+ viewMin = startDate.getTime();
|
|
|
+ viewMax = endDate.getTime();
|
|
|
+ for(const id in plots) {
|
|
|
+ let opt = plots[id].getXAxes()[0].options;
|
|
|
+ opt.min = viewMin;
|
|
|
+ opt.max = viewMax;
|
|
|
+ plots[id].setupGrid();
|
|
|
+ plots[id].draw();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function addDays(date, days) {
|
|
|
+ var result = new Date(date);
|
|
|
+ result.setDate(result.getDate() + days);
|
|
|
+ return result;
|
|
|
}
|
|
|
</script>
|
|
|
</body>
|