TONS of work.
* Got stats page working * Got main LevelUp page working * A lot of stat processing stuff
This commit is contained in:
		
							
								
								
									
										11
									
								
								assets/css/pure-min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								assets/css/pure-min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -211,6 +211,60 @@ aside { | ||||
|   background: #1f8dd6; | ||||
| } | ||||
|  | ||||
| .hidden { | ||||
|   display: none; | ||||
| } | ||||
|  | ||||
| .bordered { | ||||
|   border: 1px solid #888; | ||||
|   border-radius: 3px; | ||||
| } | ||||
|  | ||||
| .levelup-user { | ||||
|   border-radius: 3px; | ||||
|   border: 1px solid #888; | ||||
|   margin: 10px; | ||||
|   overflow: hidden; | ||||
|   height: 75px; | ||||
|   background: #AAA; | ||||
|   padding: 5px; | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| .levelup-username { | ||||
|   font-size: 2em; | ||||
|   color: #FFF; | ||||
|   background: #777; | ||||
|   border-radius: 3px; | ||||
|   border-top: 1px solid black; | ||||
|   border-left: 1px solid black; | ||||
|   border-right: 1px solid white; | ||||
|   border-bottom: 1px solid white; | ||||
|   padding: 5px; | ||||
|   position: absolute; | ||||
|   top: 5%; | ||||
|   right: 1%; | ||||
| } | ||||
| .levelup-level { | ||||
|   font-size: 7em; | ||||
|   margin-right: 5em; | ||||
|   color: #000; | ||||
|   position: absolute; | ||||
|   left: 1%; | ||||
|   top: 35%; | ||||
|   color: #FFF; | ||||
| } | ||||
| .levelup-smalllevel { | ||||
|   position: absolute; | ||||
|   bottom: 20%; | ||||
|   right: 1%; | ||||
|   color: #000; | ||||
| } | ||||
| .levelup-xp { | ||||
|   position: absolute; | ||||
|   bottom: 1%; | ||||
|   right: 1%; | ||||
| } | ||||
| /* -- Responsive Styles (Media Queries) ------------------------------------- */ | ||||
|  | ||||
| /* | ||||
|   | ||||
							
								
								
									
										276
									
								
								assets/js/B.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								assets/js/B.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,276 @@ | ||||
| function B(els, attrs) { | ||||
|   // Turn 'this' into an array of passed in elements. | ||||
|   function B(els) { | ||||
|     if(typeof els === "string") { | ||||
|       els = this.brb.create(els); | ||||
|     } | ||||
|     for(var i = 0; i < els.length; i++) { | ||||
|       this[i] = els[i]; | ||||
|     } | ||||
|     this.length = els.length; | ||||
| //    return this; | ||||
|   } | ||||
|  | ||||
|   // Map a function to all elements in 'this' | ||||
|   B.prototype.map = function(callback) { | ||||
|     var results = [], i = 0; | ||||
|     for(;i<this.length;i++){ | ||||
|       results.push(callback.call(this,this[i],i)); | ||||
|     } | ||||
|     return results; | ||||
|   }; | ||||
|  | ||||
|   // Foreach through all elements in 'this' | ||||
|   B.prototype.forEach = function(callback) { | ||||
|     this.map(callback); | ||||
|     return this; | ||||
|   }; | ||||
|  | ||||
|   // Map a function to the first element in 'this' | ||||
|   B.prototype.mapOne = function(callback) { | ||||
|     var m = this.map(callback); | ||||
|     return m.length > 1 ? m[0] : m; | ||||
|   }; | ||||
|  | ||||
|   // Update css for each element in 'this' | ||||
|   B.prototype.css = function(css_opt_var, css_opt_val) { | ||||
|     if(typeof css_opt_var !== "string") { | ||||
|       for(css_var in css_opt_var) { | ||||
|         this.forEach(function(el){el.style[css_var]=css_opt_var[css_var];}); | ||||
|       } | ||||
|       return this; | ||||
|     } else { | ||||
|       if(typeof css_opt_val !== "undefined") { | ||||
|         return this.forEach(function(el){el.style[css_opt_var]=css_opt_val;}); | ||||
|       } else { | ||||
|         return this.mapOne(function(el){return el.style[css_opt_var];}); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   // Update the innerText for each element in 'this' | ||||
|   B.prototype.html = function(text) { | ||||
|     if(typeof text !== "undefined") { | ||||
|       return this.forEach(function(el){el.innerHTML=text;}); | ||||
|     } else { | ||||
|       return this.mapOne(function(el){return el.innerHTML;}); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   // Check if the first element has the requested class | ||||
|   B.prototype.hasClass = function(cls) { | ||||
|     return this.mapOne(function(el){ | ||||
|       return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Add a class to each element in 'this' | ||||
|   B.prototype.addClass = function(classes) { | ||||
|     var className = ""; | ||||
|     if(typeof classes !== "string") { | ||||
|       for(var i=0;i<classes.length;i++) { | ||||
|         className+=" "+classes[i]; | ||||
|       } | ||||
|     } else { | ||||
|       className=" "+classes; | ||||
|     } | ||||
|     return this.forEach(function(el){el.className+=className;}); | ||||
|   }; | ||||
|  | ||||
|   // Remove a class from each element in 'this' | ||||
|   B.prototype.removeClass = function(remove_class) { | ||||
|     return this.forEach(function(el){ | ||||
|       var cs = el.className.split(" "), i; | ||||
|       while((i=cs.indexOf(remove_class))>-1){ | ||||
|         cs = cs.slice(0,i).concat(cs.slice(++i)); | ||||
|       } | ||||
|       el.className=cs.join(" "); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   // Set an attribute for each element in 'this' | ||||
|   B.prototype.attr = function(attr,val){ | ||||
|     if(typeof val!=="undefined"){ | ||||
|       if(this[0].tagName=="INPUT" && attr.toUpperCase()=="VALUE") { | ||||
|         // If we're setting the 'VALUE' then it's actually .value | ||||
|         return this.forEach(function(el){ | ||||
|           el.value=val; | ||||
|         }); | ||||
|       } else { | ||||
|         // Otherwise use .setAttribute | ||||
|         return this.forEach(function(el){ | ||||
|           el.setAttribute(attr,val); | ||||
|         }); | ||||
|       } | ||||
|     } else { | ||||
|       // And getting the value | ||||
|       if(this[0].tagName=="INPUT" && attr.toUpperCase()=="VALUE") { | ||||
|         return this.mapOne(function(el){ | ||||
|           return el.value; | ||||
|         }); | ||||
|       } else { | ||||
|         return this.mapOne(function(el){ | ||||
|           return el.getAttribute(attr); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   // Actually set a value on each element (can be done with attr too.) | ||||
|   B.prototype.val = function(new_val) { | ||||
|     if(typeof new_val!=="undefined"){ | ||||
|       return this.forEach(function(el){ | ||||
|         el.value = new_val; | ||||
|       }); | ||||
|     } else { | ||||
|       // Just retrieve the value for the first element | ||||
|       return this.mapOne(function(el) { | ||||
|         return el.value; | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Append an element to the DOM after each element in 'this' | ||||
|   B.prototype.append = function(els) { | ||||
|     this.forEach(function(parEl, i) { | ||||
|       els.forEach(function(childEl) { | ||||
|         if(i>0) { | ||||
|           childEl=childEl.cloneNode(true); | ||||
|         } | ||||
|         parEl.appendChild(childEl); | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   // Prepend an element to the DOM before each element in 'this' | ||||
|   B.prototype.prepend = function(els) { | ||||
|     return this.forEach(function(parEl, i) { | ||||
|       for(var j = els.length-1; j>-1; j--) { | ||||
|         childEl=(i>0)?els[j].cloneNode(true):els[j]; | ||||
|         parEl.insertBefore(childEl, parEl.firstChild); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   // Remove all elements in 'this' from the DOM | ||||
|   B.prototype.remove = function() { | ||||
|     return this.forEach(function(el){ | ||||
|       return el.parentNode.removeChild(el); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   // Find children that match selector | ||||
|   B.prototype.children = function(selector) { | ||||
|     var results = []; | ||||
|     this.forEach(function(el) { | ||||
|       var sub_r = el.querySelectorAll(selector); | ||||
|       for(var i = 0; i < sub_r.length; i++) { | ||||
|         results.push(sub_r[i]); | ||||
|       } | ||||
|     }); | ||||
|     return results; | ||||
|   } | ||||
|  | ||||
|   B.prototype.firstChild = function(selector) { | ||||
|     return this.children(selector)[0]; | ||||
|   } | ||||
|  | ||||
|   // Add an event listener to each element in 'this' | ||||
|   B.prototype.on = (function(){ | ||||
|     // Browser compatibility... | ||||
|     if(document.addEventListener) { | ||||
|       return function(evt,fn) { | ||||
|         return this.forEach(function(el){ | ||||
|           el.addEventListener(evt, fn, false); | ||||
|         }); | ||||
|       }; | ||||
|     } else if(document.attachEvent) { | ||||
|       return function(evt,fn) { | ||||
|         return this.forEach(function(el){ | ||||
|           el.attachEvent("on"+evt,fn); | ||||
|         }); | ||||
|       }; | ||||
|     } else { | ||||
|       return function(evt, fn) { | ||||
|         return this.forEach(function(el){ | ||||
|           el["on"+evt]=fn; | ||||
|         }); | ||||
|       }; | ||||
|     } | ||||
|   }()); | ||||
|  | ||||
|   // Disable event listeners on elements in 'this' | ||||
|   B.prototype.off = (function(){ | ||||
|     // Browser compatibility... | ||||
|     if(document.removeEventListener) { | ||||
|       return function(evt, fn) { | ||||
|         return this.forEach(function(el) { | ||||
|           el.removeEventListener(evt, fn, false); | ||||
|         }); | ||||
|       }; | ||||
|     } else if(document.detachEvent) { | ||||
|       return function(evt, fn) { | ||||
|         return this.forEach(function(el) { | ||||
|           el.detachEvent("on"+evt, fn); | ||||
|         }); | ||||
|       }; | ||||
|     } else { | ||||
|       return function(evt, fn) { | ||||
|         return this.forEach(function(el){ | ||||
|           el["on"+evt]=null; | ||||
|         }); | ||||
|       }; | ||||
|     } | ||||
|   }()); | ||||
|  | ||||
|   // The actual framework object, yay! | ||||
|   var brb = { | ||||
|     // Get an element | ||||
|     get: function(selector) { | ||||
|       var els; | ||||
|       if(typeof selector === "string") { | ||||
|         els = document.querySelectorAll(selector); | ||||
|       } else if(selector.length) { | ||||
|         els = selector; | ||||
|       } else { | ||||
|         els = [selector]; | ||||
|       } | ||||
|       return new B(els); | ||||
|     }, | ||||
|     // Create a new element | ||||
|     create: function(tagName, attrs) { | ||||
|       var el = new B([document.createElement(tagName)]); | ||||
|       // Set attributes on new element | ||||
|       if(attrs) { | ||||
|         if(attrs.className) { | ||||
|           // Classes | ||||
|           el.addClass(attrs.className); | ||||
|           delete attrs.classname; | ||||
|         } | ||||
|         if(attrs.text) { | ||||
|           // Text | ||||
|           el.text(attrs.text); | ||||
|           delete attrs.text; | ||||
|         } | ||||
|         for(var key in attrs) { | ||||
|           // All other Attributes | ||||
|           if(attrs.hasOwnProperty(key)) { | ||||
|             el.attr(key, attrs[key]); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       return el; | ||||
|     } | ||||
|   }; | ||||
|   if(els.match) { | ||||
|     var match_tags = els.match(/<([^>\s\/]*)\s?\/?>/); | ||||
|   } | ||||
|   if(match_tags && match_tags.length > 0) { | ||||
|     // It's a 'create tag' command | ||||
|     return brb.create(match_tags[1], attrs); | ||||
|   } else { | ||||
|     // Just search for matches | ||||
|     return brb.get(els); | ||||
|   } | ||||
| }; | ||||
| // And there is our minimalist framework. Who needs jquery? | ||||
							
								
								
									
										101
									
								
								assets/js/levelup_main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								assets/js/levelup_main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| document.addEventListener("DOMContentLoaded", function(event) { | ||||
|   sortByAlpha(); | ||||
|   buildList(); | ||||
|   B('#btnAlphaSort').on('click', function() { | ||||
|     if(sortMode == "ALPHA") { | ||||
|       sortMode = "ALPHA-REV"; | ||||
|       B('#btnAlphaSort>i').removeClass('fa-sort-asc').addClass('fa-sort-desc'); | ||||
|     } else { | ||||
|       sortMode = "ALPHA"; | ||||
|       B('#btnAlphaSort>i').removeClass('fa-sort-desc').addClass('fa-sort-asc'); | ||||
|     } | ||||
|     sortUsers(); | ||||
|     buildList(); | ||||
|   }); | ||||
|   B('#btnXpSort').on('click', function() { | ||||
|     if(sortMode == "XP") { | ||||
|       sortMode = "XP-REV"; | ||||
|       B('#btnXpSort>i').removeClass('fa-sort-desc').addClass('fa-sort-asc'); | ||||
|     } else { | ||||
|       sortMode = "XP"; | ||||
|       B('#btnXpSort>i').removeClass('fa-sort-asc').addClass('fa-sort-desc'); | ||||
|     } | ||||
|     sortUsers(); | ||||
|     buildList(); | ||||
|   }); | ||||
| }) | ||||
|  | ||||
| var sortMode = "ALPHA"; | ||||
|  | ||||
| function clearList() { | ||||
|   B(".levelup-user").remove(); | ||||
| } | ||||
|  | ||||
| function sortUsers() { | ||||
|   switch(sortMode) { | ||||
|     case "ALPHA-REV": sortByAlphaRev(); break; | ||||
|     case "XP": sortByXP(); break; | ||||
|     case "XP-REV": sortByXPRev(); break; | ||||
|     default: sortByAlpha(); break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| function sortByXP() { | ||||
|   levelUpStats.users.sort(function(a, b){ | ||||
|     if(a.xp > b.xp) { return -1; } | ||||
|     if(a.xp < b.xp) { return 1; } | ||||
|     return 0; | ||||
|   }); | ||||
| } | ||||
| function sortByXPRev() { | ||||
|   levelUpStats.users.sort(function(a, b){ | ||||
|     if(a.xp > b.xp) { return 1; } | ||||
|     if(a.xp < b.xp) { return -1; } | ||||
|     return 0; | ||||
|   }); | ||||
| } | ||||
| function sortByAlpha() { | ||||
|   levelUpStats.users.sort(function(a, b){ | ||||
|     if(a.name > b.name) { return 1; } | ||||
|     if(a.name < b.name) { return -1; } | ||||
|     return 0; | ||||
|   }); | ||||
| } | ||||
| function sortByAlphaRev() { | ||||
|   levelUpStats.users.sort(function(a, b){ | ||||
|     if(a.name > b.name) { return -1; } | ||||
|     if(a.name < b.name) { return 1; } | ||||
|     return 0; | ||||
|   }); | ||||
| } | ||||
|  | ||||
| function buildList() { | ||||
|   clearList(); | ||||
|   for(var i = 0; i < levelUpStats.users.length; i++) { | ||||
|     // Figure out the users current level & percentage through it. | ||||
|     var xp = levelUpStats.users[i].xp, | ||||
|         toNext = 100, | ||||
|         tstLvl = 1; | ||||
|  | ||||
|     while(xp - toNext > 0) { | ||||
|       xp -= toNext; | ||||
|       tstLvl++; | ||||
|       toNext = tstLvl*100; | ||||
|     } | ||||
|     var donePct = (xp / toNext)*100, | ||||
|         userLevel = tstLvl, | ||||
|         userName = levelUpStats.users[i].name, | ||||
|         idName = userName.replace('.','_'); | ||||
|      | ||||
|     B("#userXpList").append(B('<div>').attr('id',"levelup-user-"+idName).addClass('levelup-user pure-u-1 pure-u-md-1-3').attr('data-username',userName)); | ||||
|     var userNameSpan = B("<span>").attr('id','levelup-user-'+idName+'-name').addClass('levelup-username'); | ||||
|     B("#levelup-user-"+idName).append(B("<div>").attr('id','levelup-user-'+idName+'-level').addClass('levelup-level').html(userLevel)); | ||||
|     B("#levelup-user-"+idName).append(userNameSpan.html(userName)); | ||||
|     B("#levelup-user-"+idName).append(B("<div>").attr('id','levelup-user-'+idName+'-smalllevel').addClass('levelup-smalllevel').html("Level "+userLevel)); | ||||
|     B("#levelup-user-"+idName).append(B("<div>").attr('id','levelup-user-'+idName+'-xp').addClass('levelup-xp').html("("+xp+"/"+toNext+")")); | ||||
|  | ||||
|     B('.levelup-user').on('click', function() { | ||||
|       console.log("Load User Profile: "+B(this).attr('data-username')); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @@ -1,11 +1,52 @@ | ||||
| document.addEventListener("DOMContentLoaded", function(event) { | ||||
|   B("#btnChannelStats").on('click', function(e) { | ||||
|     B(this).addClass('pure-button-disabled'); | ||||
|     B('#btnMessageStats').removeClass('pure-button-disabled'); | ||||
|     B('#btnUserStats').removeClass('pure-button-disabled'); | ||||
|     B('#channelStats').removeClass('hidden'); | ||||
|     B('#userStats').addClass('hidden'); | ||||
|     B('#messageStats').addClass('hidden'); | ||||
|   }); | ||||
|  | ||||
|   B('#btnMessageStats').on('click', function(e) { | ||||
|     B(this).addClass('pure-button-disabled'); | ||||
|     B('#btnChannelStats').removeClass('pure-button-disabled'); | ||||
|     B('#channelStats').addClass('hidden'); | ||||
|     B('#btnUserStats').removeClass('pure-button-disabled'); | ||||
|     B('#userStats').addClass('hidden'); | ||||
|     B('#messageStats').removeClass('hidden'); | ||||
|   }); | ||||
|  | ||||
|   B('#btnUserStats').on('click', function(e) { | ||||
|     B(this).addClass('pure-button-disabled'); | ||||
|     B('#btnChannelStats').removeClass('pure-button-disabled'); | ||||
|     B('#channelStats').addClass('hidden'); | ||||
|     B('#btnMessageStats').removeClass('pure-button-disabled'); | ||||
|     B('#messageStats').addClass('hidden'); | ||||
|     B('#userStats').removeClass('hidden'); | ||||
|   }); | ||||
|  | ||||
|   B('#btnUsrHrlyStats').on('click', function(e) { | ||||
|     B(this).addClass('pure-button-disabled'); | ||||
|     B('#btnUsrDowStats').removeClass('pure-button-disabled'); | ||||
|     B('#userHourlyStats').removeClass('hidden'); | ||||
|     B('#userDowStats').addClass('hidden'); | ||||
|   }); | ||||
|  | ||||
|   B('#btnUsrDowStats').on('click', function(e) { | ||||
|     B(this).addClass('pure-button-disabled'); | ||||
|     B('#btnUsrHrlyStats').removeClass('pure-button-disabled'); | ||||
|     B('#userHourlyStats').addClass('hidden'); | ||||
|     B('#userDowStats').removeClass('hidden'); | ||||
|   }); | ||||
|  | ||||
|   var main_channel_names = ['general','random'], | ||||
|       main_member_numbers = [], | ||||
|       main_message_numbers = [], | ||||
|       other_channel_names = [], | ||||
|       other_member_numbers = [], | ||||
|       other_message_numbers = []; | ||||
|   for(var i = 0; i < stats.channels.length; i++){ | ||||
|   for(var i = 0; i < stats.channels.length; i++) { | ||||
|     if(stats.channels[i].name == "general") { | ||||
|       main_member_numbers[0] = stats.channels[i].member_count; | ||||
|       main_message_numbers[0] = stats.channels[i].message_count; | ||||
| @@ -101,13 +142,212 @@ document.addEventListener("DOMContentLoaded", function(event) { | ||||
|       name: 'Messages', | ||||
|       color: 'rgba(126,86,134,0.9)', | ||||
|       data: other_message_numbers, | ||||
|       pointPadding: 0.3 | ||||
|       pointPadding: 0.0 | ||||
|     },{ | ||||
|       name: 'Members', | ||||
|       color: 'rgba(165,170,217,1)', | ||||
|       data: other_member_numbers, | ||||
|       pointPadding: 0.4, | ||||
|       pointPadding: 0.2, | ||||
|       yAxis: 1 | ||||
|     }] | ||||
|   }); | ||||
|  | ||||
|   var msg1 = [{name: 'Messages', data: []}]; | ||||
|   for(var i = 0; i < 24; i++) { | ||||
|     msg1[0].data.push(stats.messages.hours[i]); | ||||
|   } | ||||
|   var msgHourlyChart = new Highcharts.Chart({ | ||||
|     chart: { | ||||
|       renderTo: 'msgHourlyStats' | ||||
|     }, | ||||
|     title: { | ||||
|       text: 'Hourly Stats' | ||||
|     }, | ||||
|     xAxis: { | ||||
|       categories: [ | ||||
|           '00:00', '01:00', '02:00', '03:00', '04:00', '05:00', | ||||
|           '06:00', '07:00', '08:00', '09:00', '10:00', '11:00', | ||||
|           '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', | ||||
|           '18:00', '19:00', '20:00', '21:00', '22:00', '23:00' | ||||
|       ] | ||||
|     }, | ||||
|     yAxis: {  | ||||
|       title: { text: '' }, | ||||
|       plotLines: [{ | ||||
|         value: 0, | ||||
|         width: 1, | ||||
|         color: '#808080' | ||||
|       }] | ||||
|     }, | ||||
|     series: msg1 | ||||
|   }); | ||||
|  | ||||
|   var msg2 = [{name: 'Messages', data: []}]; | ||||
|   msg2[0].data.push(stats.messages.dow['Sun']); | ||||
|   msg2[0].data.push(stats.messages.dow['Mon']); | ||||
|   msg2[0].data.push(stats.messages.dow['Tue']); | ||||
|   msg2[0].data.push(stats.messages.dow['Wed']); | ||||
|   msg2[0].data.push(stats.messages.dow['Thu']); | ||||
|   msg2[0].data.push(stats.messages.dow['Fri']); | ||||
|   msg2[0].data.push(stats.messages.dow['Sat']); | ||||
|  | ||||
|   var msgDowChart = new Highcharts.Chart({ | ||||
|     chart: { | ||||
|       renderTo: 'msgDowStats' | ||||
|     }, | ||||
|     title: { | ||||
|       text: 'Day of Week Stats' | ||||
|     }, | ||||
|     xAxis: { | ||||
|       categories: [ | ||||
|         'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' | ||||
|       ] | ||||
|     }, | ||||
|     yAxis: { | ||||
|       title: { text: '' }, | ||||
|       plotLines: [{ | ||||
|         value: 0, | ||||
|         width: 1, | ||||
|         color: '#808080' | ||||
|       }] | ||||
|     }, | ||||
|     series: msg2 | ||||
|   }); | ||||
|  | ||||
|    | ||||
|   var numCharts = 0, | ||||
|       usrHourlyCharts = [], | ||||
|       usrDowCharts = [], | ||||
|       maxMessages = 0; | ||||
|   stats.users.sort(function(a, b){ | ||||
|     if(a.name < b.name) { return -1; } | ||||
|     if(a.name > b.name) { return 1; } | ||||
|     return 0; | ||||
|   }); | ||||
|  | ||||
|   for(var i = 0; i < stats.users.length; i++) { | ||||
|     for(var j = 0; j < 24; j++) { | ||||
|       if(stats.users[i].messages.hours[j] > maxMessages) { | ||||
|         maxMessages = stats.users[i].messages.hours[j]; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   while((numCharts*10) < stats.users.length) { | ||||
|     var usrList = [], | ||||
|         usrHourlyActivity = [], | ||||
|         usrDowActivity = []; | ||||
|     for(var i = (numCharts*10); i < 10+(numCharts*10); i++) { | ||||
|       if(i < stats.users.length) { | ||||
|         usrList.push(stats.users[i].name); | ||||
|         for(var j = 0; j < 24; j++) { | ||||
|           usrHourlyActivity.push({ | ||||
|             x: j, | ||||
|             y: (i - (numCharts*10)), | ||||
|             value: stats.users[i].messages.hours[j] | ||||
|           }); | ||||
|         } | ||||
|         dowArr=['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; | ||||
|         for(var dowI = 0; dowI < 7; dowI++) { | ||||
|           usrDowActivity.push({ | ||||
|             x: dowI, | ||||
|             y: (i - (numCharts*10)), | ||||
|             value: stats.users[i].messages.dow[dowArr[dowI]] | ||||
|           }); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     B("#userHourlyStats").append(B("<div>").attr('id','usrHourlyStats-'+numCharts)); | ||||
|     B("#userDowStats").append(B("<div>").attr('id','usrDowStats-'+numCharts)); | ||||
|  | ||||
|     usrHourlyCharts.push(new Highcharts.Chart({ | ||||
|       chart: { | ||||
|         type: 'heatmap', | ||||
|         renderTo: 'usrHourlyStats-'+numCharts | ||||
|       }, | ||||
|       title: { | ||||
|         text: 'Hourly Activity by User' | ||||
|       }, | ||||
|       xAxis: { | ||||
|         categories: [ | ||||
|           '00:00','01:00','02:00','03:00','04:00','05:00', | ||||
|           '06:00','07:00','08:00','09:00','10:00','11:00', | ||||
|           '12:00','13:00','14:00','15:00','16:00','17:00', | ||||
|           '18:00','19:00','20:00','21:00','22:00','23:00' | ||||
|         ] | ||||
|       }, | ||||
|       yAxis: { | ||||
|         categories: usrList, | ||||
|         title: null | ||||
|       }, | ||||
|       colorAxis: { | ||||
|         stops: [ | ||||
|           [0, '#3060cf'], | ||||
|           [0.5, '#fffbbc'], | ||||
|           [0.9, '#c4463a'] | ||||
|         ], | ||||
|         min: 0, | ||||
|         max: maxMessages | ||||
|       }, | ||||
|       series: [{ | ||||
|         borderWidth: 1, | ||||
|         data: usrHourlyActivity, | ||||
|         name: "Messages", | ||||
|         tooltip: { | ||||
|           pointFormatter: function() { | ||||
|             if(this.x < 10) { | ||||
|               return this.series.yAxis.categories[this.y] + " - 0"+this.x+":00 ("+this.value+" messages)"; | ||||
|             } | ||||
|             return this.series.yAxis.categories[this.y] + " - "+this.x+":00 ("+this.value+" messages)"; | ||||
|           } | ||||
|         } | ||||
|       }] | ||||
|     })); | ||||
|  | ||||
|     usrDowCharts.push(new Highcharts.Chart({ | ||||
|       chart: { | ||||
|         type: 'heatmap', | ||||
|         renderTo: 'usrDowStats-'+numCharts | ||||
|       }, | ||||
|       title: { | ||||
|         text: 'Day Of Week Activity by User' | ||||
|       }, | ||||
|       xAxis: { | ||||
|         categories: [ | ||||
|           'Sun','Mon','Tue','Wed','Thu','Fri','Sat' | ||||
|         ] | ||||
|       }, | ||||
|       yAxis: { | ||||
|         categories: usrList, | ||||
|         title: null | ||||
|       }, | ||||
|       colorAxis: { | ||||
|         stops: [ | ||||
|           [0, '#3060cf'], | ||||
|           [0.5, '#fffbbc'], | ||||
|           [0.9, '#c4463a'] | ||||
|         ], | ||||
|         min: 0, | ||||
|         max: maxMessages | ||||
|       }, | ||||
|       series: [{ | ||||
|         borderWidth: 1, | ||||
|         data: usrDowActivity, | ||||
|         name: "Messages", | ||||
|         tooltip: { | ||||
|           pointFormatter: function() { | ||||
|             if(this.x < 10) { | ||||
|               return this.series.yAxis.categories[this.y] + " - 0"+this.x+":00 ("+this.value+" messages)"; | ||||
|             } | ||||
|             return this.series.yAxis.categories[this.y] + " - "+this.x+":00 ("+this.value+" messages)"; | ||||
|           } | ||||
|         } | ||||
|       }] | ||||
|     })); | ||||
|     numCharts++; | ||||
|   } | ||||
|  | ||||
|   B('#userStats').addClass('hidden'); | ||||
|   B('#messageStats').addClass('hidden'); | ||||
|   B('#userDowStats').addClass('hidden'); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										40
									
								
								levelup_model.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								levelup_model.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/boltdb/bolt" | ||||
| ) | ||||
|  | ||||
| func getAllLevelUpChannelXp(user string) map[string]int { | ||||
| 	openDatabase() | ||||
| 	ret := make(map[string]int) | ||||
| 	// First, get a list of all levelup stats | ||||
| 	db.Update(func(tx *bolt.Tx) error { | ||||
| 		var b, uB, uSB *bolt.Bucket | ||||
| 		var err error | ||||
|  | ||||
| 		b = tx.Bucket([]byte("users")) | ||||
| 		if b == nil { | ||||
| 			return fmt.Errorf("Unable to open 'users' bucket") | ||||
| 		} | ||||
| 		if uB = b.Bucket([]byte(user)); uB != nil { | ||||
| 			if uSB = uB.Bucket([]byte("stats")); uSB != nil { | ||||
| 				return uSB.ForEach(func(k, v []byte) error { | ||||
| 					if strings.HasPrefix(string(k), "levelup-") { | ||||
| 						mapKey := strings.Replace(string(k), "levelup-", "", -1) | ||||
| 						if mapKey != "xp" { | ||||
| 							ret[mapKey], _ = strconv.Atoi(string(v)) | ||||
| 						} | ||||
| 					} | ||||
| 					return nil | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 		return err | ||||
| 	}) | ||||
| 	closeDatabase() | ||||
| 	return ret | ||||
| } | ||||
| @@ -88,31 +88,54 @@ func (p *generalStatProcessor) GetName() string { | ||||
| func (p *generalStatProcessor) GetStatKeys() []string { | ||||
| 	return []string{ | ||||
| 		"bot-message", | ||||
| 		//"bot-reaction-*", | ||||
| 		"channel-message", | ||||
| 		//"channel-reaction-*", | ||||
| 		"message-hour-*", | ||||
| 		"message-dow-*", | ||||
| 		"message-dom-*", | ||||
| 		//"reaction-*-hour-*", | ||||
| 		//"reaction-*-dow-*", | ||||
| 		//"reaction-*-dom-*", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *generalStatProcessor) ProcessMessage(m *Message) { | ||||
| 	incrementUserStat(m.User, "message-hour-"+m.Time.Format("15")) | ||||
| 	incrementUserStat(m.User, "message-dow-"+m.Time.Format("Mon")) | ||||
| 	incrementUserStat(m.User, "message-dom-"+m.Time.Format("02")) | ||||
| 	if m.Type == "message" { | ||||
| 		incrementUserStat(m.User, "message-hour-"+m.Time.Format("15")) | ||||
| 		incrementUserStat(m.User, "message-dow-"+m.Time.Format("Mon")) | ||||
| 		incrementUserStat(m.User, "message-dom-"+m.Time.Format("02")) | ||||
| 		//} else if m.Type == "reaction_added" { | ||||
| 		//incrementUserStat(m.User, "reaction-"+m.Name+"-hour-"+m.Time.Format("15")) | ||||
| 		//incrementUserStat(m.User, "reaction-"+m.Name+"-dow-"+m.Time.Format("Mon")) | ||||
| 		//incrementUserStat(m.User, "reaction-"+m.Name+"-dom-"+m.Time.Format("02")) | ||||
| 	} | ||||
| } | ||||
| func (p *generalStatProcessor) ProcessBotMessage(m *Message) {} | ||||
|  | ||||
| func (p *generalStatProcessor) ProcessUserMessage(m *Message) { | ||||
| 	incrementUserStat(m.User, "bot-message") | ||||
| 	if m.Type == "message" { | ||||
| 		incrementUserStat(m.User, "bot-message") | ||||
| 		//} else if m.Type == "reaction_added" { | ||||
| 		//	incrementUserStat(m.User, "bot-reaction-"+m.Name) | ||||
| 	} | ||||
| } | ||||
| func (p *generalStatProcessor) ProcessBotUserMessage(m *Message) {} | ||||
|  | ||||
| func (p *generalStatProcessor) ProcessChannelMessage(m *Message) { | ||||
| 	incrementUserStat(m.User, "channel-message") | ||||
| 	if m.Type == "message" { | ||||
| 		incrementUserStat(m.User, "channel-message") | ||||
|  | ||||
| 	incrementChannelStat(m.Channel, "message-hour-"+m.Time.Format("15")) | ||||
| 	incrementChannelStat(m.Channel, "message-dow-"+m.Time.Format("Mon")) | ||||
| 	incrementChannelStat(m.Channel, "message-dom-"+m.Time.Format("02")) | ||||
| 		incrementChannelStat(m.Channel, "message-hour-"+m.Time.Format("15")) | ||||
| 		incrementChannelStat(m.Channel, "message-dow-"+m.Time.Format("Mon")) | ||||
| 		incrementChannelStat(m.Channel, "message-dom-"+m.Time.Format("02")) | ||||
| 		//} else if m.Type == "reaction_added" { | ||||
| 		//	incrementUserStat(m.User, "channel-reaction-"+m.Name) | ||||
|  | ||||
| 		//	incrementChannelStat(m.Channel, "reaction-"+m.Name+"-hour-"+m.Time.Format("15")) | ||||
| 		//	incrementChannelStat(m.Channel, "reaction-"+m.Name+"-dow-"+m.Time.Format("Mon")) | ||||
| 		//	incrementChannelStat(m.Channel, "reaction-"+m.Name+"-dom-"+m.Time.Format("02")) | ||||
| 	} | ||||
| } | ||||
| func (p *generalStatProcessor) ProcessBotChannelMessage(m *Message) {} | ||||
|  | ||||
| @@ -149,15 +172,27 @@ func (wm *generalWebModule) GetBottomMenuEntries() []menuItem { | ||||
| func (wm *generalWebModule) handleStats(w http.ResponseWriter, req *http.Request) { | ||||
| 	initRequest(w, req) | ||||
|  | ||||
| 	type ChannelStat struct { | ||||
| 	type ChannelStats struct { | ||||
| 		Name         string | ||||
| 		MemberCount  int | ||||
| 		MessageCount int | ||||
| 	} | ||||
| 	type MessageStats struct { | ||||
| 		Hours map[int]int | ||||
| 		Dow   map[string]int | ||||
| 	} | ||||
| 	type UserStats struct { | ||||
| 		Name     string | ||||
| 		Hours    map[int]int | ||||
| 		Dow      map[string]int | ||||
| 		Messages int | ||||
| 	} | ||||
| 	type StatData struct { | ||||
| 		TotalChannelMessages int | ||||
| 		TotalChannels        int | ||||
| 		ChannelStats         []ChannelStat | ||||
| 		ChannelStats         []ChannelStats | ||||
| 		UserStats            []UserStats | ||||
| 		MessageStats         MessageStats | ||||
| 		Error                string | ||||
| 	} | ||||
|  | ||||
| @@ -167,22 +202,79 @@ func (wm *generalWebModule) handleStats(w http.ResponseWriter, req *http.Request | ||||
|  | ||||
| 	openDatabase() | ||||
| 	chanlst := getChannelList() | ||||
| 	var chanstats []ChannelStat | ||||
| 	userlst := getUserList() | ||||
| 	var chanstats []ChannelStats | ||||
| 	var userstats []UserStats | ||||
| 	s.MessageStats.Hours = make(map[int]int) | ||||
| 	for i := 0; i < 24; i++ { | ||||
| 		s.MessageStats.Hours[i] = 0 | ||||
| 	} | ||||
| 	s.MessageStats.Dow = make(map[string]int) | ||||
| 	for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { | ||||
| 		s.MessageStats.Dow[k] = 0 | ||||
| 	} | ||||
|  | ||||
| 	for _, k := range chanlst { | ||||
| 		chanstats = append(chanstats, ChannelStat{ | ||||
| 		chanstats = append(chanstats, ChannelStats{ | ||||
| 			Name:         getChannelName(k), | ||||
| 			MemberCount:  getChannelMemberCount(k), | ||||
| 			MessageCount: getChannelMessageCount(k), | ||||
| 		}) | ||||
| 		for i := 0; i < 24; i++ { | ||||
| 			if hrI, err := getChannelStat(k, fmt.Sprintf("message-hour-%0d", i)); err == nil { | ||||
| 				s.MessageStats.Hours[i] = s.MessageStats.Hours[i] + hrI | ||||
| 			} | ||||
| 		} | ||||
| 		for _, dow := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { | ||||
| 			if dowI, err := getChannelStat(k, fmt.Sprintf("message-dow-%s", dow)); err == nil { | ||||
| 				s.MessageStats.Dow[dow] = s.MessageStats.Dow[dow] + dowI | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	for _, k := range userlst { | ||||
| 		usrName := getUserName(k) | ||||
| 		if usrName == "stat_bot" || usrName == "bot" { | ||||
| 			continue | ||||
| 		} | ||||
| 		usrHourStats := make(map[int]int) | ||||
| 		usrDowStats := make(map[string]int) | ||||
| 		var chanMsg int | ||||
| 		var err error | ||||
| 		if chanMsg, err = getUserStat(k, "channel-message"); err != nil { | ||||
| 			chanMsg = 0 | ||||
| 		} | ||||
| 		for i := 0; i < 24; i++ { | ||||
| 			if hrI, err := getUserStat(k, fmt.Sprintf("message-hour-%0d", i)); err == nil { | ||||
| 				usrHourStats[i] = hrI | ||||
| 			} else { | ||||
| 				usrHourStats[i] = 0 | ||||
| 			} | ||||
| 		} | ||||
| 		for _, dow := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { | ||||
| 			if dowI, err := getUserStat(k, fmt.Sprintf("message-dow-%s", dow)); err == nil { | ||||
| 				usrDowStats[dow] = dowI | ||||
| 			} else { | ||||
| 				usrDowStats[dow] = 0 | ||||
| 			} | ||||
| 		} | ||||
| 		userstats = append(userstats, UserStats{ | ||||
| 			Name:     usrName, | ||||
| 			Messages: chanMsg, | ||||
| 			Hours:    usrHourStats, | ||||
| 			Dow:      usrDowStats, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	s.ChannelStats = chanstats | ||||
| 	s.UserStats = userstats | ||||
| 	s.TotalChannelMessages = getTotalChannelMsgCount() | ||||
| 	s.TotalChannels = len(chanlst) | ||||
|  | ||||
| 	closeDatabase() | ||||
|  | ||||
| 	sc := "var stats = {totalchannelmessages:" | ||||
| 	sc = fmt.Sprintf("%s%d,", sc, s.TotalChannelMessages) | ||||
| 	sc = sc + "\"channels\":[" | ||||
| 	sc = sc + "channels:[" | ||||
| 	for _, k := range s.ChannelStats { | ||||
| 		sc = fmt.Sprintf("%s{name:\"%s\",member_count:%d,message_count:%d},", | ||||
| 			sc, | ||||
| @@ -193,7 +285,46 @@ func (wm *generalWebModule) handleStats(w http.ResponseWriter, req *http.Request | ||||
| 	} | ||||
| 	// Trim the last , | ||||
| 	sc = sc[:len(sc)-1] | ||||
| 	sc = sc + "]" | ||||
| 	sc = sc + "]," | ||||
| 	sc = sc + "users:[" | ||||
| 	for _, usr := range s.UserStats { | ||||
| 		sc = fmt.Sprintf("%s{name:\"%s\",message_count:%d,", | ||||
| 			sc, | ||||
| 			usr.Name, | ||||
| 			usr.Messages, | ||||
| 		) | ||||
| 		sc = sc + "messages:{" | ||||
| 		sc = sc + "hours:{" | ||||
| 		for i, k := range usr.Hours { | ||||
| 			sc = sc + fmt.Sprintf("%d:%d,", i, k) | ||||
| 		} | ||||
| 		sc = sc[:len(sc)-1] | ||||
| 		sc = sc + "}," | ||||
| 		sc = sc + "dow:{" | ||||
| 		for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { | ||||
| 			sc = sc + fmt.Sprintf("%s:%d,", k, usr.Dow[k]) | ||||
| 		} | ||||
| 		sc = sc[:len(sc)-1] | ||||
| 		sc = sc + "}}" | ||||
| 		sc = sc + "}," | ||||
| 	} | ||||
| 	// Trim the last , | ||||
| 	sc = sc[:len(sc)-1] | ||||
| 	sc = sc + "]," | ||||
| 	// Get all of the message stats | ||||
| 	sc = sc + "messages:{" | ||||
| 	sc = sc + "hours:{" | ||||
| 	for i, k := range s.MessageStats.Hours { | ||||
| 		sc = sc + fmt.Sprintf("%d:%d,", i, k) | ||||
| 	} | ||||
| 	sc = sc[:len(sc)-1] | ||||
| 	sc = sc + "}," | ||||
| 	sc = sc + "dow:{" | ||||
| 	for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { | ||||
| 		sc = sc + fmt.Sprintf("%s:%d,", k, s.MessageStats.Dow[k]) | ||||
| 	} | ||||
| 	sc = sc[:len(sc)-1] | ||||
| 	sc = sc + "}}" | ||||
| 	sc = sc + "};" | ||||
|  | ||||
| 	addToInlineScript(sc) | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| package main | ||||
|  | ||||
| import "net/http" | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type levelUpStatProcessor struct{} | ||||
|  | ||||
| @@ -52,7 +55,7 @@ func (wm *levelUpWebModule) GetName() string { | ||||
| } | ||||
| func (wm *levelUpWebModule) GetRoutes() map[string]func(http.ResponseWriter, *http.Request) { | ||||
| 	ret := make(map[string]func(http.ResponseWriter, *http.Request)) | ||||
| 	//ret["/levelup/"] = wm. | ||||
| 	ret["/levelup/"] = wm.handleLevelUpGeneral | ||||
| 	return ret | ||||
| } | ||||
| func (wm *levelUpWebModule) Register() { | ||||
| @@ -71,6 +74,55 @@ func (wm *levelUpWebModule) GetBottomMenuEntries() []menuItem { | ||||
| } | ||||
|  | ||||
| func (wm *levelUpWebModule) handleLevelUpGeneral(w http.ResponseWriter, req *http.Request) { | ||||
| 	initRequest(w, req) | ||||
| 	type UserLevelUpStats struct { | ||||
| 		Name         string | ||||
| 		Xp           int | ||||
| 		ChannelStats map[string]int | ||||
| 	} | ||||
| 	type StatData struct { | ||||
| 		UserStats []UserLevelUpStats | ||||
| 	} | ||||
| 	openDatabase() | ||||
| 	userLst := getUserList() | ||||
| 	var userStats []UserLevelUpStats | ||||
| 	for _, k := range userLst { | ||||
| 		usrName := getUserName(k) | ||||
| 		if usrName == "stat_bot" || usrName == "bot" { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		xpVal, _ := getUserStat(k, "levelup-xp") | ||||
| 		userStats = append(userStats, UserLevelUpStats{ | ||||
| 			Name:         usrName, | ||||
| 			Xp:           xpVal, | ||||
| 			ChannelStats: getAllLevelUpChannelXp(k), | ||||
| 		}) | ||||
| 	} | ||||
| 	closeDatabase() | ||||
| 	site.TemplateData = StatData{UserStats: userStats} | ||||
|  | ||||
| 	initRequest(w, req) | ||||
| 	setMenuItemActive("LevelUp!") | ||||
|  | ||||
| 	sc := "var levelUpStats = {" | ||||
| 	sc = sc + "users:[" | ||||
| 	for _, k := range userStats { | ||||
| 		sc = sc + "{" | ||||
| 		sc = sc + "name:\"" + k.Name + "\"," | ||||
| 		sc = fmt.Sprintf("%sxp:%d,", sc, k.Xp) | ||||
| 		sc = sc + "channels:[" | ||||
| 		if len(k.ChannelStats) > 0 { | ||||
| 			for chK, chV := range k.ChannelStats { | ||||
| 				sc = fmt.Sprintf("%s{name:\"%s\",xp:%d},", sc, chK, chV) | ||||
| 			} | ||||
| 			sc = sc[:len(sc)-1] | ||||
| 		} | ||||
| 		sc = sc + "]}," | ||||
| 	} | ||||
| 	sc = sc[:len(sc)-1] | ||||
| 	sc = sc + "]};" | ||||
| 	addToInlineScript(sc) | ||||
| 	site.Scripts = append(site.Scripts, "/assets/js/levelup_main.js") | ||||
|  | ||||
| 	showPage("levelup-main.html", site, w) | ||||
| } | ||||
|   | ||||
| @@ -87,10 +87,8 @@ func processMessage(slack *Slack, m *Message) { | ||||
| 			writeToLog(string(mb) + "\n") | ||||
| 		} | ||||
| 	*/ | ||||
| 	// TODO: Handle reaction_added messages | ||||
| 	// TODO: Handle reaction_removed messages | ||||
|  | ||||
| 	if m.Type == "message" { | ||||
| 	if m.Type == "message" || m.Type == "reaction_added" { | ||||
| 		var err error | ||||
| 		var usr *User | ||||
|  | ||||
|   | ||||
| @@ -398,7 +398,6 @@ func addChannelStat(channel string, key string, addVal int) error { | ||||
| 	openDatabase() | ||||
| 	v, err := getChannelStat(channel, key) | ||||
| 	err = saveChannelStat(channel, key, v+addVal) | ||||
| 	v, err = getChannelStat(channel, key) | ||||
| 	closeDatabase() | ||||
| 	return err | ||||
| } | ||||
| @@ -476,6 +475,9 @@ func getChannelStat(channel string, key string) (int, error) { | ||||
| 				ret, err = bktGetInt(chSB, key) | ||||
| 				return err | ||||
| 			} | ||||
| 			fmt.Println("Unable to find channel stats bucket: " + string(channel)) | ||||
| 		} else { | ||||
| 			fmt.Println("Unable to find channel bucket: " + string(channel)) | ||||
| 		} | ||||
| 		return err | ||||
|  | ||||
| @@ -828,6 +830,45 @@ func getAllUsersStats() (map[string]int, error) { | ||||
| 	return ret, err | ||||
| } | ||||
|  | ||||
| func getUserList() []string { | ||||
| 	openDatabase() | ||||
| 	// First build channel list | ||||
| 	var users []string | ||||
| 	db.Update(func(tx *bolt.Tx) error { | ||||
| 		b := tx.Bucket([]byte("users")) | ||||
| 		return b.ForEach(func(k, v []byte) error { | ||||
| 			users = append(users, string(k)) | ||||
| 			return nil | ||||
| 		}) | ||||
| 	}) | ||||
| 	closeDatabase() | ||||
| 	return users | ||||
| } | ||||
|  | ||||
| func getUserName(uid string) string { | ||||
| 	var ret string | ||||
| 	openDatabase() | ||||
| 	db.View(func(tx *bolt.Tx) error { | ||||
| 		var b, uB, uIB *bolt.Bucket | ||||
| 		var err error | ||||
|  | ||||
| 		b = tx.Bucket([]byte("users")) | ||||
| 		if b == nil { | ||||
| 			return fmt.Errorf("Error opening 'users' bucket") | ||||
| 		} | ||||
| 		if uB = b.Bucket([]byte(uid)); uB == nil { | ||||
| 			return fmt.Errorf("Error opening user bucket (%s)", uid) | ||||
| 		} | ||||
| 		if uIB = uB.Bucket([]byte("info")); uIB == nil { | ||||
| 			return fmt.Errorf("Error opening user info bucket (%s/info)", uid) | ||||
| 		} | ||||
| 		ret, err = bktGetString(uIB, "name") | ||||
| 		return err | ||||
| 	}) | ||||
| 	closeDatabase() | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func getChannelMessageCount(channel string) int { | ||||
| 	var ret int | ||||
| 	openDatabase() | ||||
|   | ||||
| @@ -71,6 +71,7 @@ func statWebMain(slack *Slack) { | ||||
| 	http.Handle("/assets/", http.StripPrefix("/assets/", assetHandler)) | ||||
|  | ||||
| 	registerWebModule(new(generalWebModule)) | ||||
| 	registerWebModule(new(levelUpWebModule)) | ||||
|  | ||||
| 	http.Handle("/", r) | ||||
| 	go func() { | ||||
| @@ -87,6 +88,9 @@ func initRequest(w http.ResponseWriter, req *http.Request) { | ||||
|  | ||||
| 	site.Scripts = make([]string, 0, 0) | ||||
| 	site.Scripts = append(site.Scripts, "/assets/js/highcharts.js") | ||||
| 	site.Scripts = append(site.Scripts, "/assets/js/B.js") | ||||
|  | ||||
| 	site.InlineScript = "" | ||||
|  | ||||
| 	site.Menu = make([]menuItem, 0, 0) | ||||
| 	site.BottomMenu = make([]menuItem, 0, 0) | ||||
|   | ||||
							
								
								
									
										8
									
								
								templates/levelup-main.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								templates/levelup-main.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <div> | ||||
|   <h2>Level Up!</h2> | ||||
| </div> | ||||
|  | ||||
| <button id="btnAlphaSort" class="pure-button success">Sort by Alpha <i class="fa fa-sort-asc"></i></button> | ||||
| <button id="btnXpSort" class="pure-button success">Sort by XP<i class="fa fa-sort-desc"></i> </button> | ||||
| <div id="userXpList" class="pure-g"> | ||||
| </div> | ||||
| @@ -1,6 +1,24 @@ | ||||
|   <div> | ||||
|     {{ .TemplateData.Error }} | ||||
|   </div> | ||||
|   <div id="mainStatsBarChart"></div> | ||||
|   <hr /> | ||||
|   <div id="statsBarChart"></div> | ||||
|   <div class="bordered"> | ||||
|     <button id="btnChannelStats" class="pure-button success pure-button-disabled">Channel Stats</button> | ||||
|     <button id="btnUserStats" class="pure-button success">User Stats</button> | ||||
|     <button id="btnMessageStats" class="pure-button success">Message Stats</button> | ||||
|     <div id="channelStats" class="bordered"> | ||||
|       <div id="mainStatsBarChart"></div> | ||||
|       <hr /> | ||||
|       <div id="statsBarChart"></div> | ||||
|     </div> | ||||
|     <div id="userStats" class="bordered"> | ||||
|       <button id="btnUsrHrlyStats" class="pure-button success pure-button-disabled">Hourly</button> | ||||
|       <button id="btnUsrDowStats" class="pure-button success">Day of Week</button> | ||||
|       <div id="userHourlyStats"></div> | ||||
|       <div id="userDowStats"></div>   | ||||
|     </div> | ||||
|     <div id="messageStats" class="bordered"> | ||||
|       <div id="msgHourlyStats"></div>   | ||||
|       <hr /> | ||||
|       <div id="msgDowStats"></div>   | ||||
|     </div> | ||||
|   </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user