dbcf25b9aafe0a6e3b1ce5cacbbbcf66bf9e0573
[squid-reports.git] / web / squid.js
1 urlbase="./api.php"
2
3 refresh = 500
4 online_history = 60
5
6 graph_colors = [
7 "salmon", "lightcoral", "crimson", "red", "darkred", "orangered",
8 "gold", "orange", "yellow", "darkkhaki", "lime", "green", "greenyellow",
9 "springgreen", "olive", "cyan", "turquoise", "teal", "lightskyblue",
10 "dodgerblue", "royalblue", "blue", "navy", "violet", "fuchsia", 
11 "darkviolet", "purple", "deeppink", "gray", "darkslategray", "sandybrown",
12 "goldenrod", "chocolate", "saddlebrown", "maroon", "rosybrown", "sienna", 
13 "brown", "dimgray", "mediumvioletred", "indigo", "orchid", "mediumpurple", 
14 "steelblue", "powderblue", "lightseagreen", "cadetblue", "aquamarine", 
15 "olivedrab", "chartreuse", "darkolivegreen", "tomato", "firebrick" 
16 ]
17
18 currentState=""
19
20 templates = {}
21 columns = {}
22
23 cats = {}
24 reps = {}
25
26 data = []
27
28 current_filter = {}
29
30 dictionaries = {}
31
32 current_rep = ''
33
34 online_traffic = null
35 online_connections = null
36 online_hosts = []
37
38 timer = null
39
40 assigned_colors = []
41
42 var d = new Date();
43 var curr_day = d.getDate();
44 var curr_month = d.getMonth() + 1;
45 var curr_year = d.getFullYear();
46 today = curr_year + "-" + curr_month + "-" + curr_day;
47
48 date_from = today
49 date_to = today
50
51 function UrlParams(params) {
52
53   var out = [];
54
55   for (var key in params) {
56     if (params.hasOwnProperty(key)) {
57       out.push(key + '=' + encodeURIComponent(params[key]));
58     }
59   }
60
61   return out.join('&');
62
63 }
64
65 function Macro(template, values) {
66   return templates[template].replace(/\$(\w+)\;/g,function (s,name) {
67       return values[name]
68     })
69 }
70
71 function ColumnMacro(column, values, macro = "column"){
72   var columnrec = columns[column]
73   var templatestr
74   if (columnrec) {
75     if (columnrec.template_name) {
76       macro = columnrec.template_name
77     }
78     templatestr = columnrec.template
79   }
80   if (templatestr) {
81     return templatestr.replace(/\$(\w+)\;/g,function (s,name) {
82       return values[name]
83     })
84   } else {
85     return Macro(macro,{VALUE:values[column]})
86   }
87 }
88
89 function HeaderMacro(column,macro = "header-column"){
90   var columnrec = columns[column]
91   if (columnrec) {
92     var alias = columns[column].alias;
93   } else {
94     var alias = ''
95   }
96   if (!(alias)) { alias = column }
97   return Macro(macro,{VALUE:alias})
98 }
99
100 function GetApi(onfinish,method,params) {
101
102   var req = new XMLHttpRequest();
103
104   req.onreadystatechange = function () {
105     if (this.readyState != 4) return; 
106     if (this.status != 200) {
107       setTimeout(OnLoad,30000);
108       return;
109     }
110     res = JSON.parse(this.responseText);
111     onfinish(res);
112   };
113
114   var url  = urlbase+"?method="+method;
115
116   if (params) {
117   
118     url = url + '&' + UrlParams(params)
119   
120   }
121
122   req.open("GET", url, true);
123   req.withCredentials = true;
124   req.send();
125
126 }
127
128 function UpdatePageProps(props) {
129
130   var logo = document.getElementById("brand");
131   logo.innerText = props["site-header"];
132
133   cats = props["cats"]
134
135   for (var i in res["columns"]) { 
136     columns[res["columns"][i]["name"]] = {alias:res["columns"][i]["alias"],template:res["columns"][i]["template"],template_name:res["columns"][i]["template_name"]}
137   }
138
139   for (var i in res["templates"]) { 
140     var mnemo = res["templates"][i]["mnemo"]
141     var body = res["templates"][i]["body"]
142     templates[mnemo] = body
143   }
144
145   dictionaries = res["dictionaries"]
146  
147   menuInnerHTML = ""
148   for (var cat in cats) {
149     category = cats[cat]
150     innerHTML = ""
151     for (rep in category["reps"]) {
152       report = category["reps"][rep]
153       reps[report["mnemo"]] = report
154       reptext = Macro("menuitem",{ MNEMO:report["mnemo"], NAME:report["name"], DESCR:report["description"] })
155       innerHTML = innerHTML + reptext
156     }
157     grouptxt = Macro("menugroup",{ MNEMO:category["mnemo"], NAME:category["name"], DESCR:category["description"], MENUITEM: innerHTML})
158     menuInnerHTML = menuInnerHTML + grouptxt
159   }
160   menuInnerHTML = Macro("menuonline",{}) + menuInnerHTML
161   menuHTML = Macro("menu", { MENUGROUP: menuInnerHTML} )
162   var Left = document.getElementById("menu");
163   Left.innerHTML = menuHTML
164
165   Online();
166   
167 }
168
169 function SetDates() {
170   var inp = document.getElementById("date-from")
171   inp.value = date_from
172   inp.max = date_to
173
174   var inp = document.getElementById("date-to")
175   inp.value = date_to
176   inp.min = date_from
177
178   var inp = document.getElementById("report-button")
179   if (current_rep) {
180     inp.disabled = false
181   } else {
182     inp.disabled = true
183   }
184 }
185
186 function OnLoad() {
187
188   GetApi(UpdatePageProps,"get-base-config",null)
189   SetDates();
190
191 }
192
193 function ShowHide(id) {
194   var content = document.getElementById(id);
195   if (content.style.display === "block") {
196     content.style.display = "none";
197   } else {
198     content.style.display = "block";
199   }
200 }
201
202 function MergeTR(tr,th, macro = "column") {
203   var str = ""
204   for (i in th) {
205     if (!(th[i].startsWith('_'))) {
206       str = str + ColumnMacro(th[i],tr,macro) 
207     }
208   }
209   return str
210 }
211
212 function MergeTH(th, macro = "header-column") {
213   var str = ""
214   for (i in th) {
215     if (!(th[i].startsWith('_'))) {
216       str = str + HeaderMacro(th[i])  
217     }
218   }
219   return str
220 }
221
222 function ProduceRep(res) {
223
224   current_filter = res["filter"]
225   if (!current_filter) { current_filter = {} }
226   dictionary = res["dictionary"]
227
228   header_template = res["header"]
229   has_total = res["has_total"]
230
231   innerHTML = ""
232   data = res["data"]
233   ii=0
234
235   if (has_total == "1") {
236     total = data.pop()
237   }
238   
239   for (i in data) {
240     row_data = data[i]
241     table_row = Macro("table-row",{DATA:MergeTR(row_data,dictionary)})
242     innerHTML = innerHTML + table_row
243   }
244
245   headerHTML = Macro("header-row",{DATA:MergeTH(dictionary)})
246   
247   if (has_total == "1") {
248     totalHTML = Macro("total-row",{DATA:MergeTR(total,dictionary)})
249     reportHTML = Macro("report-table-total",{HEADER:headerHTML,LINES:innerHTML,TOTAL:totalHTML})
250   } else {
251     reportHTML = Macro("report-table",{HEADER:headerHTML,LINES:innerHTML})
252   }
253   var body = document.getElementById("report-body") 
254   body.innerHTML = reportHTML;
255
256   if (reps[current_rep].graph_x) {
257     DisplayGraph(true)
258     var config = PrepareGraphDataset(data,reps[current_rep].graph_x,reps[current_rep].graph_y,reps[current_rep].graph_series)
259     config.options.responsive = true
260     
261     config.options.scales = {
262       xAxes: [{
263         scaleLabel: {
264           display: true,
265         }
266       }],
267       yAxes: [{
268         stacked: true,
269         scaleLabel: {
270           display: true,
271         },
272         ticks: {
273           suggestedMin: 0,    // minimum will be 0, unless there is a lower value.
274         }
275       }]
276     }
277     DrawGraph(config)
278   } else {
279     DisplayGraph(false)
280   }
281
282 }
283
284 function DrawGraph(config) {
285   var ctx = document.getElementById('canvas').getContext('2d')
286   var div = document.getElementById("report-graph");
287   canvas.width = div.style.width;
288   canvas.height = div.style.height;
289   window.Graph = new Chart(ctx, config);
290 }
291
292 function AssignColor(key) {
293   if (assigned_colors[key]) {
294     return assigned_colors[key]
295   }
296   var rand_color 
297   if (graph_colors.length) { 
298     var idx = Math.floor(Math.random() * graph_colors.length)
299     rand_color = graph_colors[idx]
300     graph_colors.splice(idx,1)
301   } else {
302     rand_color = "darkgray"
303   }
304   assigned_colors[key] = rand_color
305   return rand_color
306 }
307
308 function PrepareGraphDataset(data,graph_x,graph_y,graph_series) {
309   
310   var xvals = []
311   var series = []
312
313   var values = []
314
315   for (i in data) {
316     rec = data[i]
317     for (key in rec) {
318       if (key == graph_x) {
319         if (!xvals.includes(Number(rec[key]))) {
320           xvals.push(Number(rec[key]))
321         }
322       }
323       if (key == graph_series) {
324         if (!series.includes(rec[key])) {
325           series.push(rec[key])
326         }
327       }
328     }
329   }
330   
331   xvals.sort(function(a,b) { return a-b; })
332   series.sort()
333
334   for (var i in series) {
335     values[i] = {}
336     values[i].data = []
337     values[i].label = series[i]
338     values[i].fill = true
339     values[i].borderColor = AssignColor(values[i].label)
340     values[i].backgroundColor = AssignColor(values[i].label)
341     for (var j in xvals) {
342       values[i].data[j] = 0
343     } 
344   }
345
346   for (var k in data) {
347     rec = data[k]
348     xval = null
349     yval = null
350     dataset = null
351     for (key in rec) {
352       if (key == graph_x) {
353         xval = Number(rec[key])
354       }
355       if (key == graph_y) {
356         yval = Number(rec[key])
357       }
358       if (key == graph_series) {
359         dataset = rec[key]
360       }
361     }
362     var j = xvals.indexOf(xval)
363     var i = series.indexOf(dataset)
364     values[i].data[j] = yval 
365   }
366
367   return  {
368       type: 'line',
369       data: {
370         labels: xvals,
371         datasets: values,
372       },
373       options: {
374       }
375     }
376
377   
378 }
379
380 function AliasByName(dict,name) {
381   for (i in dict) {
382     if (dict[i].name == name) { return dict[i].alias }
383   }
384   return name
385 }
386
387 function AddTraffic(label,b) {
388   var rec
389   for (i in online_traffic) {
390     if (online_traffic[i].label == label) {
391       rec = online_traffic[i]
392       break
393     }
394   }
395   if (!rec) {
396     rec = { label: label, borderColor: AssignColor(label), backgroundColor: AssignColor(label), data: new Array(online_history).fill(0) }
397     online_traffic.push(rec)
398   }
399   rec.data[rec.data.length-1] += b
400 }
401
402 function ProduceOnline(res) {
403
404   if (!online_traffic) {
405     online_traffic = []
406     config = {
407       type: 'line',
408       data: {
409         labels: new Array(online_history).fill('*'),
410         datasets: online_traffic,
411       },
412       options: {
413         animation: {
414           duration: 0
415         },
416         responsive: true,
417         scales: {
418           xAxes: [{
419             scaleLabel: {
420               display: true,
421             }
422           }],
423           yAxes: [{
424             stacked: true,
425             ticks: {
426                 suggestedMin: 0,    // minimum will be 0, unless there is a lower value.
427             },
428             scaleLabel: {
429               display: true,
430             }
431           }]
432         }
433       }
434     }
435     DrawGraph(config) 
436   }
437   
438   for (i = online_traffic.length-1; i>=0; i--) {
439     online_traffic[i].data.shift()
440     online_traffic[i].data.push(0)
441     if (Math.max.apply(null,online_traffic[i].data) == 0) {
442       online_traffic.splice(i,1)
443     }
444   }
445   
446   dictionary = res["dictionary"]
447
448   header_template = res["header"]
449
450   innerHTML = ""
451   data = res["data"]
452   ii=0
453
454   var new_online_connections = []
455
456   dictionary.unshift("useralias")
457   for (i in data) {
458     row_data = data[i]
459     user = row_data["_user"]
460     username = AliasByName(dictionaries['user_id'],user)
461     row_data["useralias"] = username
462     
463     host = row_data["host"]
464     hostname = AliasByName(dictionaries['host_id'],host)
465     row_data["host"] = hostname
466     
467     table_row = Macro("table-row",{DATA:MergeTR(row_data,dictionary)})
468     innerHTML = innerHTML + table_row
469
470     var bytes = Number(row_data['bytes'])
471     
472     var idx = row_data["_ip"]+':'+row_data["_port"]  
473     var last_bytes 
474     
475     if (online_connections) {
476       last_bytes = online_connections[idx]
477       if (!last_bytes) {
478         last_bytes = 0
479       }
480     } else {
481       last_bytes = bytes
482     }
483     
484     new_online_connections[idx] = bytes
485     
486     AddTraffic(username,bytes-last_bytes)
487   }
488   
489   online_connections = new_online_connections
490
491   headerHTML = Macro("header-row",{DATA:MergeTH(dictionary)})
492   
493   reportHTML = Macro("report-table",{HEADER:headerHTML,LINES:innerHTML})
494
495   var body = document.getElementById("report-body") 
496   body.innerHTML = reportHTML;
497
498   timer = setTimeout(Online,refresh)
499  
500   window.Graph.update()
501
502 }
503
504 function CancelRefresh() {
505   if (timer) {
506     clearTimeout(timer)
507     timer = null
508   }
509 }
510
511 function ShowRep(id) {
512   CancelRefresh()
513   current_filter = {}
514   var header = document.getElementById("report-name") 
515   header.innerText = reps[id]["name"] 
516   var body = document.getElementById("report-body") 
517   body.innerText = "Отчет загружается..." 
518   current_rep = id
519   UpdateDates()
520   Rerun()
521 }
522
523 function ShowFilteredRep(id,filter) {
524   CancelRefresh()
525   for (i in filter) {
526     current_filter[i] = filter[i]
527   }
528   var header = document.getElementById("report-name") 
529   header.innerText = reps[id]["name"] 
530   var body = document.getElementById("report-body") 
531   body.innerText = "Отчет загружается..." 
532   current_rep = id
533   UpdateDates()
534   Rerun()
535 }
536
537 function FilterSelect(name,dict,value) {
538   var str = '<select onchange="SetFilter(\''+name+'\',this);">'
539   for (key in dict) {
540     if (dict[key].id==value) {
541       str = str + '<option selected value="'+dict[key].id+'">'+dict[key].name+"</option>"
542     } else {
543       str = str + '<option value="'+dict[key].id+'">'+dict[key].name+"</option>"
544     }
545   }
546   str = str + "</select>" + Macro("clear-filter",{NAME:name})
547   return str
548 }
549
550 function Filter() {
551   var str = ""
552   for (key in current_filter) {
553     var dict = dictionaries[key]
554     if(dict) {
555       str = str + FilterSelect(key,dict,current_filter[key])
556     } else {
557       str = str + Macro("filter-display",{NAME:HeaderMacro(key),VALUE:current_filter[key]}) + Macro("clear-filter",{NAME:key})
558     }
559   }
560   return str
561 }
562
563 function RefreshFilterPane() {
564   var filter = document.getElementById("report-dates")
565   if (current_rep) {
566     filter.style.display = "block";
567   } else {
568     filter.style.display = "none";
569   } 
570   var filter = document.getElementById("filter") 
571   if (Object.keys(current_filter).length === 0) {
572     filter.style.display = "none";
573     filter.innerHTML = ''
574   } else {
575     filter.style.display = "inline-block";
576     filter.innerHTML = Filter()
577   }
578 }
579
580 function Rerun() {
581   online_traffic = null
582   var parameters = JSON.parse(JSON.stringify(current_filter))
583   parameters["mnemo"] = current_rep
584   parameters["date_from"] = date_from
585   parameters["date_to"] = date_to
586   GetApi(ProduceRep,"report",parameters);
587   var filter = document.getElementById("filter") 
588   RefreshFilterPane();
589 }
590
591 function Online() {
592   timer = null
593   RefreshFilterPane()
594   var header = document.getElementById("report-name") 
595   header.innerText = "Активные соединения" 
596   GetApi(ProduceOnline,"online",{});
597   DisplayGraph(true)
598 }
599
600 function UpdateDates() {
601   var inp = document.getElementById("date-from")
602   date_from = inp.value
603   var inp = document.getElementById("date-to")
604   date_to = inp.value
605
606   SetDates();
607
608 }
609
610 function SetFilter(name,select) {
611   current_filter[name] = select.value;
612 }
613
614 function ClearFilter(name) {
615   delete current_filter[name];
616   RefreshFilterPane();
617 }
618
619 function DisplayGraph(on) {
620   var selector = document.getElementById("page-selector") 
621   if (on) {
622     selector.style.display="block"
623   } else {
624     selector.style.display="none"
625     Display('report-body')
626   }
627 }
628
629 function Display(id) {
630   var elements = document.getElementsByClassName('report-block');
631   for (i=0; i<elements.length; i++) {
632     if (elements[i].id == id) {
633       elements[i].style.display = "block"
634     } else {
635       elements[i].style.display = "none"
636     }
637   }
638 }