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