index.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  5. <title>TS3 channel usage</title>
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <link href="stylesheet.css" rel="stylesheet" type="text/css">
  8. <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="flot/excanvas.min.js"></script><![endif]-->
  9. <script language="javascript" type="text/javascript" src="flot/jquery.min.js"></script>
  10. <script language="javascript" type="text/javascript" src="flot/jquery.flot.min.js"></script>
  11. <script language="javascript" type="text/javascript" src="flot/jquery.flot.stack.min.js"></script>
  12. <script language="javascript" type="text/javascript" src="flot/jquery.flot.time.min.js"></script>
  13. <script language="javascript" type="text/javascript" src="flot/jquery.flot.navigate.min.js"></script>
  14. <script language="javascript" type="text/javascript" src="flot/jquery.flot.resize.min.js"></script>
  15. </head>
  16. <body>
  17. <div class="wrapper">
  18. <header class="main-head">TS3 channel usage</header>
  19. <div id="graphs"></div>
  20. <div id="settings">
  21. <input type=button onclick="updateDate(addDays(startDate, -1))" value="-1 Day">
  22. <input type="date" onchange="updateDate(this.valueAsDate)" id="date">
  23. <input type=button onclick="updateDate(addDays(startDate, 1))" value="+1 Day">
  24. </div>
  25. <div id="tree"></div>
  26. </div>
  27. <script>
  28. var startDate, endDate;
  29. var eventTimeRange = [0, 0];
  30. function updateDate(newDate) {
  31. newDate.setHours(6, 0, 0, 0);
  32. if(newDate.getTime() > eventTimeRange[1])
  33. newDate = new Date(eventTimeRange[1]);
  34. if(newDate.getTime() < eventTimeRange[0])
  35. newDate = new Date(eventTimeRange[0]);
  36. newDate.setHours(6, 0, 0, 0);
  37. startDate = newDate;
  38. endDate = addDays(startDate, 1);
  39. document.getElementById('date').valueAsDate = startDate;
  40. updatePlots();
  41. }
  42. function update(init = false) {
  43. fetch("ajax.php").then(res=>res.json()).then(function(json) {
  44. console.log(json);
  45. const serverName = json.channels ? json.channels[0].serverName : "Server";
  46. let channelTree = {channelId: 0, name: serverName, id: 0};
  47. eventTimeRange[0] = json.events.reduce((min, e) => min && (min < e[1]) ? min : e[1]) * 1000 - 12*3600000;
  48. eventTimeRange[1] = json.events.reduce((max, e) => max && (max > e[1]) ? max : e[1]) * 1000 + 12*3600000;
  49. var dateInp = document.getElementById('date');
  50. dateInp.setAttribute('min', (new Date(eventTimeRange[0])).toISOString().split("T")[0]);
  51. dateInp.setAttribute('max', (new Date(eventTimeRange[1])).toISOString().split("T")[0]);
  52. if(init)
  53. updateDate(new Date());
  54. makeTree(channelTree, json.channels, json.events);
  55. showTree(channelTree, document.getElementById('tree'));
  56. if(init) {
  57. for(const channel of channelTree.children) {
  58. if(channel.activeCount == 0)
  59. continue;
  60. let inp = document.getElementById(`cb_${channel.channelId}`);
  61. inp.checked = true;
  62. plotEvents(channel, document.getElementById("graphs"), true);
  63. }
  64. }
  65. console.log(channelTree);
  66. });
  67. }
  68. update(true);
  69. setInterval(update, 30 * 1000);
  70. function makeTree(tree, channels, events) {
  71. tree.events = events.filter(e => e[0] == tree.id);
  72. if(tree.events.length > 0) {
  73. tree.events.push([tree.events[tree.events.length-1][0], (new Date()).getTime()/1000, tree.events[tree.events.length-1][2]]);
  74. }
  75. tree.children = channels.filter((ch) => ch.parentId == tree.channelId).sort((a, b) => a.position - b.position);
  76. tree.clientCount = tree.events.reduce((sum, e) => sum+e[2], 0) / (tree.events.length > 0 ? tree.events.length : 1);
  77. const recentActivity = tree.events.filter(e => e[1] > (new Date()).getTime()/1000 - 3600*24);
  78. tree.activeCount = recentActivity.length > 2 ? recentActivity.reduce((acc, val) => val[2] > acc ? val[2] : acc, 0) : 0;
  79. for(const child of tree.children) {
  80. makeTree(child, channels.filter( ch => ch.parentId != tree.channelId), events.filter( e => e[0] != tree.id));
  81. tree.clientCount += child.clientCount;
  82. tree.activeCount += child.activeCount;
  83. }
  84. }
  85. function showTree(tree, el) {
  86. let ul = document.getElementById(`ul_${tree.channelId}`);
  87. if(!ul) {
  88. let inp = document.createElement('input');
  89. inp.type = 'checkbox';
  90. inp.id = `cb_${tree.channelId}`;
  91. inp.classList.add('checkbox');
  92. el.appendChild(inp);
  93. let cnt = document.createElement('label');
  94. cnt.classList.add("count");
  95. cnt.htmlFor = `cb_${tree.channelId}`;
  96. el.appendChild(cnt);
  97. let label = document.createElement('label');
  98. label.classList.add("name");
  99. label.htmlFor = `cb_${tree.channelId}`;
  100. el.appendChild(label);
  101. ul = document.createElement('ul');
  102. ul.id = `ul_${tree.channelId}`;
  103. el.appendChild(ul);
  104. }
  105. let inp = document.getElementById(`cb_${tree.channelId}`);
  106. inp.onchange = function() {
  107. plotEvents(tree, document.getElementById("graphs"), this.checked);
  108. };
  109. if(inp.checked) {
  110. plotEvents(tree, document.getElementById("graphs"), true);
  111. }
  112. if(tree.events.length > 0)
  113. inp.nextSibling.textContent = tree.events[tree.events.length-1][2] != 0 ? tree.events[tree.events.length-1][2] : '';
  114. inp.nextSibling.nextSibling.innerHTML = "";
  115. inp.nextSibling.nextSibling.appendChild(formatHeadings(tree.name));
  116. let prevLi = {};
  117. let showSpacer = false;
  118. for(const channel of tree.children) {
  119. if(channel.clientCount == 0) {
  120. showSpacer = tree.id == 0;
  121. continue;
  122. }
  123. let li = document.getElementById(`li_${channel.channelId}`);
  124. if(!li) {
  125. li = document.createElement('li');
  126. li.id = `li_${channel.channelId}`;
  127. li.title = channel.description.replace(/\[\/?[^\]]+\]/g, "");
  128. if(prevLi.nextSibling)
  129. ul.insertBefore(li, prevLi.nextSibling);
  130. else
  131. ul.appendChild(li);
  132. }
  133. if(showSpacer) {
  134. let spacer = document.getElementById(`li_spacer_${channel.channelId}`);
  135. if(!spacer) {
  136. spacer = document.createElement('li');
  137. spacer.classList.add("spacer");
  138. spacer.id = `li_spacer_${channel.channelId}`;
  139. ul.insertBefore(spacer, li);
  140. }
  141. showSpacer = false;
  142. }
  143. prevLi = li;
  144. showTree(channel, li);
  145. }
  146. }
  147. function formatHeadings(str, html = true) {
  148. const res = /(?:\[(.?)spacer[^\]]*\])?(.*)/.exec(str);
  149. if(!html)
  150. return res[2];
  151. var div = document.createElement("div");
  152. div.classList.add("channel");
  153. var span = document.createElement("span");
  154. switch(res[1]) {
  155. case '*':
  156. res[2] = res[2].repeat(50);
  157. break;
  158. case 'l':
  159. div.classList.add("left");
  160. break;
  161. case 'c':
  162. div.classList.add("center");
  163. break;
  164. case 'r':
  165. div.classList.add("right");
  166. break;
  167. default:
  168. div.classList.add("normal");
  169. break;
  170. }
  171. span.textContent = res[2];
  172. div.appendChild(span);
  173. return div;
  174. }
  175. var plots = {};
  176. var viewMin = 0;
  177. var viewMax = 0;
  178. function plotEvents(tree, div, visible = true) {
  179. let placeholder = document.getElementById(`graph_${tree.channelId}`);
  180. if(!placeholder) {
  181. container = document.createElement("div");
  182. container.classList.add("demo-container");
  183. div.appendChild(container);
  184. placeholder = document.createElement("div");
  185. placeholder.classList.add("demo-placeholder");
  186. placeholder.id = `graph_${tree.channelId}`;
  187. container.appendChild(placeholder);
  188. }
  189. if(!document.getElementById(`cb_${tree.channelId}`).checked) {
  190. placeholder.parentNode.style.border = 'none';
  191. $(placeholder.parentNode).animate({height: 0, padding: 0, margin: 0, opacity: 0});
  192. return;
  193. } else {
  194. placeholder.parentNode.style.border = '1px solid #ddd';
  195. $(placeholder.parentNode).show(100).animate({
  196. height: "400px",
  197. padding: "20px 15px 15px 15px",
  198. margin: "0 auto 30px auto",
  199. opacity: 1
  200. });
  201. }
  202. let series = [];
  203. function iterChilds(tree, prefix="") {
  204. const data = tree.events.map(e => [e[1]*1000, e[2]]);
  205. const name = prefix + formatHeadings(tree.name, false);
  206. const maxClients = data.reduce((acc, val) => val[1] > acc ? val[1] : acc, 0);
  207. if(data.length > 0 && maxClients > 0) {
  208. series.push({
  209. label: name,
  210. data: data,
  211. lines: {
  212. show: true,
  213. fill: true,
  214. steps: true
  215. }
  216. });
  217. }
  218. for(const channel of tree.children) {
  219. iterChilds(channel, formatHeadings(tree.name, false) + ' / ');
  220. }
  221. }
  222. iterChilds(tree);
  223. if(!plots[tree.channelId]) {
  224. plots[tree.channelId] = $.plot(placeholder, series, {
  225. xaxis: {
  226. mode: "time",
  227. timeBase: "milliseconds",
  228. timezone: "browser",
  229. min: viewMin ? viewMin : startDate.getTime(),
  230. max: viewMax ? viewMax : endDate.getTime(),
  231. zoomRange: [60000, null],
  232. panRange: eventTimeRange
  233. },
  234. yaxis: {
  235. zoomRange: false,
  236. panRange: false,
  237. min: 0,
  238. tickDecimals: 0
  239. },
  240. zoom: {
  241. interactive: true
  242. },
  243. pan: {
  244. interactive: true
  245. },
  246. legend: {
  247. position: 'nw'
  248. }
  249. });
  250. plots[tree.channelId].hooks.drawOverlay.push(function(plot, cvs) {
  251. if(!plot) { return; }
  252. var cvsWidth = plot.width() / 2;
  253. var text = tree.name.replace(/\[\/?[^\]]+\]/g, "");
  254. cvs.font = "bold 16px Arial";
  255. cvs.fillStyle = "#666666";
  256. cvs.textAlign = 'center';
  257. cvs.fillText(text, cvsWidth, 30);
  258. return cvs;
  259. });
  260. $(placeholder).bind("plotpan plotzoom", function (event, plot) {
  261. var axes = plot.getAxes();
  262. viewMin = axes.xaxis.min;
  263. viewMax = axes.xaxis.max;
  264. for(const id in plots) {
  265. plots[id].getOptions().xaxes[0].min = axes.xaxis.min;
  266. plots[id].getOptions().xaxes[0].max = axes.xaxis.max;
  267. if(!document.getElementById(`cb_${id}`).checked)
  268. continue;
  269. plots[id].setupGrid();
  270. plots[id].draw();
  271. }
  272. });
  273. } else {
  274. plots[tree.channelId].getOptions().xaxis.panRange = eventTimeRange;
  275. plots[tree.channelId].setData(series);
  276. plots[tree.channelId].setupGrid();
  277. plots[tree.channelId].draw();
  278. }
  279. }
  280. function updatePlots() {
  281. viewMin = startDate.getTime();
  282. viewMax = endDate.getTime();
  283. for(const id in plots) {
  284. let opt = plots[id].getXAxes()[0].options;
  285. opt.min = viewMin;
  286. opt.max = viewMax;
  287. if(!document.getElementById(`cb_${id}`).checked)
  288. continue;
  289. plots[id].setupGrid();
  290. plots[id].draw();
  291. }
  292. }
  293. function addDays(date, days) {
  294. var result = new Date(date);
  295. result.setDate(result.getDate() + days);
  296. return result;
  297. }
  298. </script>
  299. </body>
  300. </html>