diff --git a/README.md b/README.md index 22eb688..7eb5adb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # MISP-Dashboard -A Dashboard showing live data and statistics from the MISP ZMQ +An experimental Dashboard showing live data and statistics from the MISP ZMQ ## Installation - Launch ```./install_dependencies.sh``` from the MISP-Dashboard directory @@ -27,3 +27,32 @@ optional arguments: -u ZMQURL, --url ZMQURL The URL to connect to ``` + +## License +Images and logos are handmade for: +- rankingMISPOrg/ +- rankingMISPMonthly/ +- MISPHonorableIcons/ + +Note that: +- Part of ```MISPHonorableIcons/1.svg``` comes from [octicons.github.com](https://octicons.github.com/icon/git-pull-request/) (CC0 - No Rights Reserved) +- Part of ```MISPHonorableIcons/2.svg``` comes from [Zeptozephyr](https://zeptozephyr.deviantart.com/art/Vectored-Portal-Icons-207347804) (CC0 - No Rights Reserved) + +``` +Copyright (C) 2017 CIRCL - Computer Incident Response Center Luxembourg (c/o smile, security made in Lëtzebuerg, Groupement d'Intérêt Economique) +Copyright (c) 2017 Sami Mokaddem + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +``` diff --git a/config/config.cfg b/config/config.cfg deleted file mode 100644 index 4d140cd..0000000 --- a/config/config.cfg +++ /dev/null @@ -1,44 +0,0 @@ -[Dashboard] -#hours -graph_log_refresh_rate = 1 -#sec -rotation_wait_time = 30 -max_img_rotation = 10 -hours_spanned = 48 -zoomlevel = 15 -item_to_plot = Attribute.category -# [1->12] -size_dashboard_left_width = 5 -size_openStreet_pannel_perc = 55 -size_world_pannel_perc = 35 - -[GEO] -#min -updateFrequency = 60 -zoomlevel = 11 -# ~meter -clusteringDistance = 10 - -[Log] -field_to_plot = Attribute.category -fieldname_order=["Event.id", "Attribute.Tag", "Attribute.category", "Attribute.type", ["Attribute.value", "Attribute.comment"]] -char_separator=|| - -[RedisGlobal] -host=localhost -port=6251 - -[RedisLog] -db=0 -channel=1 -#zmq_url=tcp://localhost:50000 -zmq_url=tcp://192.168.56.50:50000 - -[RedisMap] -db=1 -channelProc=CoordToProcess -channelDisp=PicToDisplay -pathMaxMindDB=./data/GeoLite2-City_20171003/GeoLite2-City.mmdb - -[RedisDB] -db=2 diff --git a/config/config.cfg.default b/config/config.cfg.default index 02c5c5c..51cf3bc 100644 --- a/config/config.cfg.default +++ b/config/config.cfg.default @@ -19,6 +19,17 @@ zoomlevel = 11 # ~meter clusteringDistance = 10 +[CONTRIB] +max_number_of_last_contributor = 10 +min_between_reload = 5 +#How much harder it gets to rank up (exponential multiplier) [1.5 -> +inf] +rankMultiplier = 2 +categories_in_datatable = ["internal_reference", "targeting_data", "antivirus_detection", "payload_delivery", "artifacts_dropped", "payload_installation", "persistence_mechanism", "network_activity", "payload_type", "attribution", "external_analysis", "financial_fraud", "support_Tool", "social_network", "person", "other" ] +default_pnts_per_contribution = 1 +# array of the form [[category, pntsRcv], ...] +pnts_per_contribution = [["payload_delivery", 1], ["artifact_dropped", 1], ["network_activity", 1]] +additional_help_text = ["Sightings multiplies earned points by 2", "Editing an attribute earns you the same as creating one"] + [Log] field_to_plot = Attribute.category fieldname_order=["Event.id", "Attribute.Tag", "Attribute.category", "Attribute.type", ["Attribute.value", "Attribute.comment"]] @@ -27,12 +38,15 @@ char_separator=|| [RedisGlobal] host=localhost port=6251 +#misp_web_url = http://192.168.56.50 +misp_web_url = http://localhost +#zmq_url=tcp://192.168.56.50:50000 +zmq_url=tcp://localhost:50000 [RedisLog] db=0 channel=1 -zmq_url=tcp://localhost:50000 -#zmq_url=tcp://192.168.56.50:50000 +channelLastContributor = lastContributor [RedisMap] db=1 diff --git a/config/ranking.cfg b/config/ranking.cfg new file mode 100644 index 0000000..8adeb93 --- /dev/null +++ b/config/ranking.cfg @@ -0,0 +1,70 @@ +[rankTitle] +maxLevel=16 +1=Ensign +2=Junior Lieutenant +3=Second Lieutenant +4=Lieutenant +5=Senior Lieutenant +6=Captain +7=Senior Captain +8=Major +9=Lieutenant Colonel +10=Colonel +11=Senior Colonel +12=Major General +13=Lieutenant General +14=General +15=Marshal +16=Grand General + +[rankRequirementsPnts] +1=2 +2=4 +3=8 +4=16 +5=32 +6=64 +7=128 +8=256 +9=512 +10=1024 +11=2048 +12=4096 +13=8192 +14=16384 +15=0 +16=0 + +[rankRequirementsMisc] +heavilyCount=10 +recentDays=31 +regularlyDays=7 + +[rankRequirementsText] +1=Contributing via sighting at least once a year +2=Contributing via attributes or objects to events at least once a year +3=Contributing via proposals or discussions at least once a year + +4=Contributing via sighting to recent events +5=Contributing proposals to recent events + +6=Contributing by creating new events at least once a year +7=Contributing by creating new events at least once a month +8=Regularly creating events +9=Regularly creating events with classification + +10=Heavily contributing via sighting to recent events +11=Heavily adding attributes or objects to recent events +12=Heavily contributing proposals to recent events +13=Heavily posting events +14=Heavily posting events with classification + +15=Heavily contributing quality content (delivered by instance administrators) +16=Honorable contributor (delivered by instance administrators) + +[HonorBadge] +1=Has made at least one pull request on the MISP project +2=Is a donator for the MISP project + +[additionalInfo] +textsArray=["Proposals means either edition, acceptation or rejection", "Recent events means event aged of one month at max", "Regularly means at least one per week" ,"Heavily means at least 10 per week", "Classification means correct tagging", "The contribution rank is set such that it equals to: rank=requirement_fulfilled-requirement_not_fulfilled"] diff --git a/contributor_helper.py b/contributor_helper.py new file mode 100644 index 0000000..c8009dc --- /dev/null +++ b/contributor_helper.py @@ -0,0 +1,585 @@ +import util +import math, random +import os +import configparser +import json +import datetime + +class Contributor_helper: + def __init__(self, serv_redis_db, cfg): + self.serv_redis_db = serv_redis_db + self.cfg = cfg + self.cfg_org_rank = configparser.ConfigParser() + self.cfg_org_rank.read(os.path.join(os.environ['DASH_CONFIG'], 'ranking.cfg')) + + #honorBadge + self.honorBadgeNum = len(self.cfg_org_rank.options('HonorBadge')) + self.heavilyCount = self.cfg_org_rank.getint('rankRequirementsMisc', 'heavilyCount') + self.recentDays = self.cfg_org_rank.getint('rankRequirementsMisc', 'recentDays') + self.regularlyDays = self.cfg_org_rank.getint('rankRequirementsMisc', 'regularlyDays') + + self.org_honor_badge_title = {} + for badgeNum in range(1, self.honorBadgeNum+1): #get Num of honorBadge + self.org_honor_badge_title[badgeNum] = self.cfg_org_rank.get('HonorBadge', str(badgeNum)) + + #GLOBAL RANKING + self.org_rank_maxLevel = self.cfg_org_rank.getint('rankTitle', 'maxLevel') + self.org_rank = {} + for rank in range(1, self.org_rank_maxLevel+1): + self.org_rank[rank] = self.cfg_org_rank.get('rankTitle', str(rank)) + self.org_rank_requirement_pnts = {} + for rank in range(1, self.org_rank_maxLevel+1): + self.org_rank_requirement_pnts[rank] = self.cfg_org_rank.getint('rankRequirementsPnts', str(rank)) + self.org_rank_requirement_text = {} + for rank in range(1, self.org_rank_maxLevel+1): + self.org_rank_requirement_text[rank] = self.cfg_org_rank.get('rankRequirementsText', str(rank)) + self.org_rank_additional_info = json.loads(self.cfg_org_rank.get('additionalInfo', 'textsArray')) + + #WEB STUFF + self.misp_web_url = cfg.get('RedisGlobal', 'misp_web_url') + self.MAX_NUMBER_OF_LAST_CONTRIBUTOR = cfg.getint('CONTRIB', 'max_number_of_last_contributor') + self.categories_in_datatable = json.loads(cfg.get('CONTRIB', 'categories_in_datatable')) + + #MONTHLY RANKING + self.default_pnts_per_contribution = json.loads(cfg.get('CONTRIB', 'default_pnts_per_contribution')) + temp = json.loads(cfg.get('CONTRIB', 'pnts_per_contribution')) + self.DICO_PNTS_REWARD = {} + for categ, pnts in temp: + self.DICO_PNTS_REWARD[categ] = pnts + # fill other categ with default points + for categ in self.categories_in_datatable: + if categ in self.DICO_PNTS_REWARD: + continue + else: + self.DICO_PNTS_REWARD[categ] = self.default_pnts_per_contribution + + self.rankMultiplier = cfg.getfloat('CONTRIB' ,'rankMultiplier') + self.levelMax = 16 + + + ''' HELPER ''' + def getOrgLogoFromMISP(self, org): + return "{}/img/orgs/{}.png".format(self.misp_web_url, org) + + def getZrange(self, keyCateg, date, topNum, endSubkey=""): + date_str = util.getDateStrFormat(date) + keyname = "{}:{}{}".format(keyCateg, date_str, endSubkey) + data = self.serv_redis_db.zrange(keyname, 0, topNum-1, desc=True, withscores=True) + data = [ [record[0].decode('utf8'), record[1]] for record in data ] + return data + + def addContributionToCateg(self, date, categ, org, count=1): + today_str = util.getDateStrFormat(date) + keyname = "CONTRIB_CATEG:{}:{}".format(today_str, categ) + self.serv_redis_db.zincrby(keyname, org, count) + + ''' CONTRIBUTION RANK ''' + def getOrgContributionTotalPoints(self, org): + keyname = 'CONTRIB_ORG:{org}:{orgCateg}' + pnts = self.serv_redis_db.get(keyname.format(org=org, orgCateg='points')) + if pnts is None: + pnts = 0 + else: + pnts = int(pnts.decode('utf8')) + return pnts + + # return: [final_rank, requirement_fulfilled, requirement_not_fulfilled] + def getOrgContributionRank(self, org): + keyname = 'CONTRIB_ORG:{org}:{orgCateg}' + final_rank = 0 + requirement_fulfilled = [] + requirement_not_fulfilled = [] + for i in range(1, self.org_rank_maxLevel+1): + key = keyname.format(org=org, orgCateg='CONTRIB_REQ_'+str(i)) + if self.serv_redis_db.get(key) is None: #non existing + requirement_not_fulfilled.append(i) + else: + requirement_fulfilled.append(i) + final_rank += 1 + #num_of_previous_req_not_fulfilled = len([x for x in requirement_not_fulfilled if x recentDays + + print("contribType: {}, action: {}".format(contribType, action)) + print("isLabeled: {}, isRecent: {}, totOrgPnts".format(isLabeled, isRecent, totOrgPnts)) + #update contribution Requirement + contrib = [] #[[contrib_level, contrib_ttl], [], ...] + if totOrgPnts >= self.org_rank_requirement_pnts[1] and contribType == 'Sighting': + #[contrib_level, contrib_ttl] + contrib.append([1, util.ONE_DAY*365]) + if totOrgPnts >= self.org_rank_requirement_pnts[2] and contribType == 'Attribute' or contribType == 'Object': + contrib.append([2, util.ONE_DAY*365]) + if totOrgPnts >= self.org_rank_requirement_pnts[3] and contribType == 'Proposal' or contribType == 'Discussion': + contrib.append([3, util.ONE_DAY*365]) + if totOrgPnts >= self.org_rank_requirement_pnts[4] and contribType == 'Sighting' and isRecent: + contrib.append([4, util.ONE_DAY*recentDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[5] and contribType == 'Proposal' and isRecent: + contrib.append([5, util.ONE_DAY*recentDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[6] and contribType == 'Event': + contrib.append([6, util.ONE_DAY*365]) + if totOrgPnts >= self.org_rank_requirement_pnts[7] and contribType == 'Event' and eventMonthCount>=1: + contrib.append([7, util.ONE_DAY*recentDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[8] and contribType == 'Event' and eventWeekCount>=1: + contrib.append([8, util.ONE_DAY*regularlyDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[9] and contribType == 'Event' and isLabeled: + contrib.append([9, util.ONE_DAY*regularlyDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[10] and contribType == 'Sighting' and sightingWeekCount>heavilyCount: + contrib.append([10, util.ONE_DAY*regularlyDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[11] and (contribType == 'Attribute' or contribType == 'Object') and attributeWeekCount>heavilyCount: + contrib.append([11, util.ONE_DAY*regularlyDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[12] and contribType == 'Proposal' and proposalWeekCount>heavilyCount: + contrib.append([12, util.ONE_DAY*regularlyDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[13] and contribType == 'Event' and eventWeekCount>heavilyCount: + contrib.append([13, util.ONE_DAY*regularlyDays]) + if totOrgPnts >= self.org_rank_requirement_pnts[14] and contribType == 'Event' and eventWeekCount>heavilyCount and isLabeled: + contrib.append([14, util.ONE_DAY*regularlyDays]) + + print([r for r, ttl in contrib]) + for rankReq, ttl in contrib: + self.serv_redis_db.set(keyname.format(org=orgName, orgCateg='CONTRIB_REQ_'+str(rankReq)), 1) + self.serv_redis_db.expire(keyname.format(org=orgName, orgCateg='CONTRIB_REQ_'+str(rankReq)), ttl) + + ''' HONOR BADGES ''' + def getOrgHonorBadges(self, org): + keyname = 'CONTRIB_ORG:{org}:{orgCateg}' + honorBadge = [] + for i in range(1, self.honorBadgeNum+1): + key = keyname.format(org=org, orgCateg='BADGE_'+str(i)) + if self.serv_redis_db.get(key) is not None: #existing + honorBadge.append(i) + return honorBadge + + def giveBadgeToOrg(self, org, badgeNum): + keyname = 'CONTRIB_ORG:{org}:{orgCateg}' + self.serv_redis_db.set(keyname.format(org=org, orgCateg='BADGE_'+str(badgeNum)), 1) + + def removeBadgeFromOrg(self, org, badgeNum): + keyname = 'CONTRIB_ORG:{org}:{orgCateg}' + self.serv_redis_db.delete(keyname.format(org=org, orgCateg='BADGE_'+str(badgeNum))) + + ''' MONTHLY CONTRIBUTION ''' + def getOrgPntFromRedis(self, org, date): + keyCateg = 'CONTRIB_DAY' + scoreSum = 0 + for curDate in util.getMonthSpan(date): + date_str = util.getDateStrFormat(curDate) + keyname = "{}:{}".format(keyCateg, date_str) + data = self.serv_redis_db.zscore(keyname, org) + if data is None: + data = 0 + scoreSum += data + return scoreSum + + def getOrgRankFromRedis(self, org, date): + ptns = self.getOrgPntFromRedis(org, date) + return self.getTrueRank(ptns) + + + def getLastContributorsFromRedis(self): + date = datetime.datetime.now() + keyname = "CONTRIB_LAST" + prev_days = 7 + topNum = self.MAX_NUMBER_OF_LAST_CONTRIBUTOR # default Num + addedOrg = [] + data = [] + for curDate in util.getXPrevDaysSpan(date, prev_days): + last_contrib_org = self.getZrange(keyname, curDate, topNum) + for org, sec in last_contrib_org: + if org in addedOrg: + continue + dic = {} + dic['rank'] = self.getOrgRankFromRedis(org, date) + dic['orgRank'] = self.getOrgContributionRank(org)['final_rank'] + dic['honorBadge'] = self.getOrgHonorBadges(org) + dic['logo_path'] = self.getOrgLogoFromMISP(org) + dic['org'] = org + dic['pnts'] = self.getOrgPntFromRedis(org, date) + dic['epoch'] = sec + data.append(dic) + addedOrg.append(org) + return data + + + def getContributorFromRedis(self, org): + date = datetime.datetime.now() + epoch = self.serv_redis_db.zscore("CONTRIB_LAST", org) + dic = {} + dic['rank'] = self.getOrgRankFromRedis(org, date) + dic['orgRank'] = self.getOrgContributionRank(org)['final_rank'] + dic['honorBadge'] = self.getOrgHonorBadges(org) + dic['logo_path'] = self.getOrgLogoFromMISP(org) + dic['org'] = org + dic['pnts'] = self.getOrgPntFromRedis(org, date) + dic['epoch'] = epoch + return dic + + def getTopContributorFromRedis(self, date): + orgDicoPnts = {} + for curDate in util.getMonthSpan(date): + keyCateg = "CONTRIB_DAY" + topNum = 0 # all + contrib_org = self.getZrange(keyCateg, curDate, topNum) + for org, pnts in contrib_org: + if org not in orgDicoPnts: + orgDicoPnts[org] = 0 + orgDicoPnts[org] += pnts + + data = [] + for org, pnts in orgDicoPnts.items(): + dic = {} + dic['rank'] = self.getTrueRank(pnts) + dic['orgRank'] = self.getOrgContributionRank(org)['final_rank'] + dic['honorBadge'] = self.getOrgHonorBadges(org) + dic['logo_path'] = self.getOrgLogoFromMISP(org) + dic['org'] = org + dic['pnts'] = pnts + data.append(dic) + data.sort(key=lambda x: x['pnts'], reverse=True) + + return data + + def getTop5OvertimeFromRedis(self): + data = [] + today = datetime.datetime.now() + topSortedOrg = self.getTopContributorFromRedis(today) #Get current top + # show current top 5 org points overtime (last 5 days) + for dic in topSortedOrg[0:5]: + org = dic['org'] + to_append = self.getOrgOvertime(org) + data.append(to_append) + return data + + def getOrgOvertime(self, org): + overtime = [] + today = datetime.datetime.today() + today = today.replace(hour=0, minute=0, second=0, microsecond=0) + for curDate in util.getXPrevDaysSpan(today, 7): + timestamp = util.getTimestamp(curDate) + keyname = 'CONTRIB_DAY:'+util.getDateStrFormat(curDate) + org_score = self.serv_redis_db.zscore(keyname, org) + if org_score is None: + org_score = 0 + overtime.append([timestamp, org_score]) + to_return = {'label': org, 'data': overtime} + return to_return + + def getCategPerContribFromRedis(self, date): + keyCateg = "CONTRIB_DAY" + topNum = 0 # all + contrib_org = self.getTopContributorFromRedis(date) + for dic in contrib_org: + org = dic['org'] + for categ in self.categories_in_datatable: + categ_score = 0 + for curDate in util.getMonthSpan(date): + keyname = 'CONTRIB_CATEG:'+util.getDateStrFormat(curDate)+':'+categ + temp = self.serv_redis_db.zscore(keyname, org) + if temp is None: + temp = 0 + categ_score += temp + dic[categ] = categ_score + return contrib_org + + + def getAllOrgFromRedis(self): + data = self.serv_redis_db.smembers('CONTRIB_ALL_ORG') + data = [x.decode('utf8') for x in data] + return data + + def getCurrentOrgRankFromRedis(self, org): + date = datetime.datetime.now() + points = self.getOrgPntFromRedis(org, date) + remainingPts = self.getRemainingPoints(points) + data = { + 'org': org, + 'points': points, + 'rank': self.getRankLevel(points), + 'orgRank': self.getOrgContributionRank(org)['final_rank'], + 'honorBadge': self.getOrgHonorBadges(org), + 'remainingPts': remainingPts['remainingPts'], + 'stepPts': remainingPts['stepPts'], + } + return data + + def getRankLevel(self, points): + if points == 0: + return 0 + elif points == 1: + return 1 + else: + return float("{:.2f}".format(math.log(points, self.rankMultiplier))) + + def getTrueRank(self, ptns): + return int(self.getRankLevel(ptns)) + + def getRemainingPoints(self, points): + prev = 0 + for i in [math.floor(self.rankMultiplier**x) for x in range(1,self.levelMax+1)]: + if prev <= points < i: + return { 'remainingPts': i-points, 'stepPts': prev } + prev = i + return { 'remainingPts': 0, 'stepPts': self.rankMultiplier**self.levelMax } + + + ''' ''' + ''' TEST DATA ''' + ''' ''' + + def TEST_getCategPerContribFromRedis(self, date): + data2 = [] + for d in range(15): + dic = {} + dic['rank'] = random.randint(1,self.levelMax) + dic['orgRank'] = random.randint(1,self.levelMax), + dic['honorBadge'] = [random.randint(1,2)], + dic['logo_path'] = 'logo' + dic['org'] = 'Org'+str(d) + dic['pnts'] = random.randint(1,2**self.levelMax) + for f in self.categories_in_datatable: + dic[f] = random.randint(0,1600) + data2.append(dic) + return data2 + + def TEST_getTop5OvertimeFromRedis(self): + import time + now = time.time() + data2 = [ + {'label': 'CIRCL', 'data': [[now, random.randint(1,50)], [now-util.ONE_DAY, random.randint(1,50)], [now-util.ONE_DAY*2, random.randint(1,50)], [now-util.ONE_DAY*3, random.randint(1,50)], [now-util.ONE_DAY*4, random.randint(1,50)]]}, + {'label': 'CASES', 'data': [[now, random.randint(1,50)], [now-util.ONE_DAY, random.randint(1,50)], [now-util.ONE_DAY*2, random.randint(1,50)], [now-util.ONE_DAY*3, random.randint(1,50)], [now-util.ONE_DAY*4, random.randint(1,50)]]}, + {'label': 'Org1', 'data': [[now, random.randint(1,50)], [now-util.ONE_DAY, random.randint(1,50)], [now-util.ONE_DAY*2, random.randint(1,50)], [now-util.ONE_DAY*3, random.randint(1,50)], [now-util.ONE_DAY*4, random.randint(1,50)]]}, + {'label': 'Org2', 'data': [[now, random.randint(1,50)], [now-util.ONE_DAY, random.randint(1,50)], [now-util.ONE_DAY*2, random.randint(1,50)], [now-util.ONE_DAY*3, random.randint(1,50)], [now-util.ONE_DAY*4, random.randint(1,50)]]}, + {'label': 'SMILE', 'data': [[now, random.randint(1,50)], [now-util.ONE_DAY, random.randint(1,50)], [now-util.ONE_DAY*2, random.randint(1,50)], [now-util.ONE_DAY*3, random.randint(1,50)], [now-util.ONE_DAY*4, random.randint(1,50)]]}, + ] + return data2 + + def TEST_getOrgOvertime(self, org): + import time + now = time.time() + data = [ + {'label': org, 'data': [[now, random.randint(1,30)], [now-util.ONE_DAY, random.randint(1,30)], [now-util.ONE_DAY*2, random.randint(1,30)], [now-util.ONE_DAY*3, random.randint(1,30)], [now-util.ONE_DAY*4, random.randint(1,40)]]} + ] + return data + + def TEST_getTopContributorFromRedis(self, date): + data2 = [ + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [1,2], + 'logo_path': self.getOrgLogoFromMISP('MISP'), + 'org': 'MISP', + 'pnts': random.randint(1,2**self.levelMax) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [1], + 'logo_path': 'logo1', + 'org': 'CIRCL', + 'pnts': random.randint(1,2**self.levelMax) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [2], + 'logo_path': 'logo2', + 'org': 'CASES', + 'pnts': random.randint(1,2**self.levelMax) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [], + 'logo_path': 'logo3', + 'org': 'SMILE', + 'pnts': random.randint(1,2**self.levelMax) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [], + 'logo_path': 'logo4', + 'org': 'ORG4', + 'pnts': random.randint(1,2**self.levelMax) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [], + 'logo_path': 'logo5', + 'org': 'ORG5', + 'pnts': random.randint(1,2**self.levelMax) + }, + ] + return data2*2 + + def TEST_getLastContributorsFromRedis(self): + import time + data2 = [ + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [1,2], + 'logo_path': self.getOrgLogoFromMISP('MISP'), + 'org': 'MISP', + 'pnts': random.randint(1,2**self.levelMax), + 'epoch': time.time() - random.randint(0, 10000) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [1], + 'logo_path': 'logo1', + 'org': 'CIRCL', + 'pnts': random.randint(1,2**self.levelMax), + 'epoch': time.time() - random.randint(0, 10000) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [2], + 'logo_path': 'logo2', + 'org': 'CASES', + 'pnts': random.randint(1,2**self.levelMax), + 'epoch': time.time() - random.randint(0, 10000) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [], + 'logo_path': 'logo3', + 'org': 'SMILE', + 'pnts': random.randint(1,2**self.levelMax), + 'epoch': time.time() - random.randint(0, 10000) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [], + 'logo_path': 'logo4', + 'org': 'ORG4', + 'pnts': random.randint(1,2**self.levelMax), + 'epoch': time.time() - random.randint(0, 10000) + }, + { + 'rank': random.randint(1,self.levelMax), + 'orgRank': random.randint(1,self.levelMax), + 'honorBadge': [], + 'logo_path': 'logo5', + 'org': 'ORG5', + 'pnts': random.randint(1,2**self.levelMax), + 'epoch': time.time() - random.randint(0, 10000) + }, + ] + return data2*2 + + def TEST_getAllOrgFromRedis(self): + data2 = ['CIRCL', 'CASES', 'SMILE' ,'ORG4' ,'ORG5', 'SUPER HYPER LONG ORGINZATION NAME', 'Org3', 'MISP'] + return data2 + + def TEST_getCurrentOrgRankFromRedis(self, org): + date = datetime.datetime.now() + points = random.randint(1,2**self.levelMax) + remainingPts = self.getRemainingPoints(points) + data = { + 'org': org, + 'points': points, + 'rank': self.getRankLevel(points), + 'remainingPts': remainingPts['remainingPts'], + 'stepPts': remainingPts['stepPts'], + } + return data + + def TEST_getCurrentContributionStatus(self, org): + num = random.randint(1, self.org_rank_maxLevel) + requirement_fulfilled = [x for x in range(1,num+1)] + requirement_not_fulfilled = [x for x in range(num,self.org_rank_maxLevel+1-num)] + + num2 = random.randint(1, self.org_rank_maxLevel) + if num2 < num-1: + to_swap = requirement_fulfilled[num2] + del requirement_fulfilled[num2] + requirement_not_fulfilled = [to_swap] + requirement_not_fulfilled + + final_rank = len(requirement_fulfilled) + to_ret = {} + for i in range(1, self.org_rank_maxLevel+1): + if i in requirement_fulfilled: + to_ret[i] = 1 + elif i in requirement_not_fulfilled and i<=final_rank: + to_ret[i] = 0 + else: + to_ret[i] = -1 + return {'rank': final_rank, 'status': to_ret, 'totPoints': random.randint(2**final_rank, 2**self.org_rank_maxLevel*4)} + + def TEST_getOrgHonorBadges(self, org): + keyname = 'CONTRIB_ORG:{org}:{orgCateg}' + honorBadge = [] + for i in range(1, self.honorBadgeNum+1): + key = keyname.format(org=org, orgCateg='BADGE_'+str(i)) + if random.randint(0,1) == 1: #existing + honorBadge.append(1) + else: + honorBadge.append(0) + return honorBadge diff --git a/give_honors_to_org.py b/give_honors_to_org.py new file mode 100755 index 0000000..87f7e11 --- /dev/null +++ b/give_honors_to_org.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3.5 + +import os, sys +import redis +import configparser + +import contributor_helper + +configfile = os.path.join(os.environ['DASH_CONFIG'], 'config.cfg') +cfg = configparser.ConfigParser() +cfg.read(configfile) +serv_redis_db = redis.StrictRedis( + host=cfg.get('RedisGlobal', 'host'), + port=cfg.getint('RedisGlobal', 'port'), + db=cfg.getint('RedisDB', 'db')) + +chelper = contributor_helper.Contributor_helper(serv_redis_db, cfg) + +def printOrgInfo(org): + org_pnts = chelper.getOrgContributionTotalPoints(org) + org_c_rank = chelper.getOrgContributionRank(org) + org_c_status = chelper.getCurrentContributionStatus(org) + org_honor_badge = chelper.getOrgHonorBadges(org) + + print() + print("Organisation points: {}".format(org_pnts)) + print("Organisation contribution rank: {}".format(org_c_status['rank'])) + print(''' +Organisation contribution rank: +-------------------------------''') + for rank in range(1, chelper.org_rank_maxLevel+1): + acq = 'x' if org_c_status['status'][rank] == 1 else ' ' + print("{}.\t[{}]\t{}\t{}".format(rank, acq, chelper.org_rank_requirement_pnts[rank], chelper.org_rank_requirement_text[rank])) + + print() + print(''' +Organisation honor badges: +--------------------------''') + for badgeNum, text in chelper.org_honor_badge_title.items(): + acq = 'x' if badgeNum in org_honor_badge else ' ' + print("{}.\t[{}]\t{}".format(badgeNum, acq, text)) + print() + + +def main(): + if len(sys.argv) > 1: + org = sys.argv[1] + else: + org = input('Enter the organisation name: ') + + printOrgInfo(org) + + # ranks + while True: + org_pnts = chelper.getOrgContributionTotalPoints(org) + org_c_rank = chelper.getOrgContributionRank(org) + org_c_status = chelper.getCurrentContributionStatus(org) + org_honor_badge = chelper.getOrgHonorBadges(org) + + userRep = input("Enter the organisation RANK to give/remove to {} ( to finish): ".format(org)) + if userRep == '': + break + else: + # validate input + try: #not int + rankNum = int(userRep) + except: + print('Not an integer') + continue + if rankNum < 1 and rankNum > chelper.org_rank_maxLevel: + print('Not a valid rank') + continue + + if org_c_status['status'][rankNum] == 1: #remove rank + chelper.removeContribRankFromOrg(org, rankNum) + else: + chelper.giveContribRankToOrg(org, rankNum) + + printOrgInfo(org) + + # badges + while True: + org_pnts = chelper.getOrgContributionTotalPoints(org) + org_c_rank = chelper.getOrgContributionRank(org) + org_c_status = chelper.getCurrentContributionStatus(org) + org_honor_badge = chelper.getOrgHonorBadges(org) + + userRep = input("Enter the organisation BADGE to give/remove to {} ( to finish): ".format(org)) + if userRep == '': + break + else: + # validate input + try: #not int + badgeNum = int(userRep) + except: + print('Not an integer') + continue + if badgeNum < 1 and badgeNum > chelper.honorBadgeNum: + print('Not a valid rank') + continue + + if badgeNum in org_honor_badge: #remove badge + chelper.removeBadgeFromOrg(org, badgeNum) + else: + chelper.giveBadgeToOrg(org, badgeNum) + + printOrgInfo(org) + +if __name__ == '__main__': + main() + diff --git a/install_dependencies.sh b/install_dependencies.sh index 60cc0bc..b8bfe64 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -17,13 +17,11 @@ fi pip3 install -U pip argparse redis zmq geoip2 flask ## config -if [ ! -f config/config.cfg ]; then - cp config/config.cfg.sample config/config.cfg -fi +cp -i config/config.cfg.default config/config.cfg ## Web stuff pushd static/ -mkdir -p css fonts +mkdir -p css fonts js popd mkdir -p temp @@ -33,14 +31,16 @@ wget http://www.misp-project.org/assets/images/misp-small.png -O static/pics/MIS JQVERSION="3.2.1" wget http://code.jquery.com/jquery-${JQVERSION}.min.js -O ./static/js/jquery.min.js +# jquery flot FLOTVERSION="0.8.3" wget http://www.flotcharts.org/downloads/flot-${FLOTVERSION}.zip -O ./temp/flot-${FLOTVERSION}.zip unzip -o temp/flot-${FLOTVERSION}.zip -d temp/ mv temp/flot/jquery.flot.js ./static/js mv temp/flot/jquery.flot.pie.min.js ./static/js mv temp/flot/jquery.flot.resize.js ./static/js +mv temp/flot/jquery.flot.time.js ./static/js - +# jquery UI JQUERYUIVERSION="1.12.1" wget https://jqueryui.com/resources/download/jquery-ui-${JQUERYUIVERSION}.zip -O temp/jquery-ui.zip unzip -o temp/jquery-ui.zip -d temp/ @@ -64,6 +64,8 @@ unzip -o temp/${SBADMIN_VERSION}-2.zip -d temp/ mv temp/startbootstrap-sb-admin-2-${SBADMIN_VERSION}/dist/js/* ./static/js/ mv temp/startbootstrap-sb-admin-2-${SBADMIN_VERSION}/dist/css/* ./static/css/ mv temp/startbootstrap-sb-admin-2-${SBADMIN_VERSION}/bower_components/font-awesome/fonts/* ./static/fonts +mv temp/startbootstrap-sb-admin-2-${SBADMIN_VERSION}/bower_components/font-awesome/css/* ./static/css +mv temp/startbootstrap-sb-admin-2-${SBADMIN_VERSION}/bower_components/bootstrap/fonts/* ./static/fonts # leaflet LEAFLET_VERSION="1.2.0" @@ -82,9 +84,22 @@ mv temp/jquery-jvectormap-2.0.3.css ./static/css mv temp/jquery-jvectormap-2.0.3.min.js ./static/js wget http://jvectormap.com/js/jquery-jvectormap-world-mill.js -O ./static/js/jquery-jvectormap-world-mill.js +# maxmind DB mkdir -p data pushd data wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz -O GeoLite2-City.tar.gz tar xvfz GeoLite2-City.tar.gz rm -rf GeoLite2-City.tar.gz +popd + +# DataTable +DATATABLE_VERSION="1.10.16" +wget https://cdn.datatables.net/${DATATABLE_VERSION}/js/jquery.dataTables.min.js -O ./static/js/jquery.dataTables.min.js +wget https://cdn.datatables.net/${DATATABLE_VERSION}/css/dataTables.bootstrap.css -O ./static/css/dataTables.bootstrap.css +wget https://cdn.datatables.net/${DATATABLE_VERSION}/js/dataTables.bootstrap.js -O ./static/js/dataTables.bootstrap.js + +#typeahead +git clone https://github.com/bassjobsen/Bootstrap-3-Typeahead.git temp/Bootstrap-3-Typeahead +mv temp/Bootstrap-3-Typeahead/bootstrap3-typeahead.min.js ./static/js + rm -rf ./temp diff --git a/server.py b/server.py index b0b3de7..aede14a 100755 --- a/server.py +++ b/server.py @@ -9,6 +9,9 @@ from time import sleep, strftime import datetime import os +import util +import contributor_helper + configfile = os.path.join(os.environ['DASH_CONFIG'], 'config.cfg') cfg = configparser.ConfigParser() cfg.read(configfile) @@ -28,12 +31,21 @@ serv_redis_db = redis.StrictRedis( port=cfg.getint('RedisGlobal', 'port'), db=cfg.getint('RedisDB', 'db')) +contributor_helper = contributor_helper.Contributor_helper(serv_redis_db, cfg) + subscriber_log = redis_server_log.pubsub(ignore_subscribe_messages=True) subscriber_log.psubscribe(cfg.get('RedisLog', 'channel')) subscriber_map = redis_server_map.pubsub(ignore_subscribe_messages=True) subscriber_map.psubscribe(cfg.get('RedisMap', 'channelDisp')) +subscriber_lastContrib = redis_server_log.pubsub(ignore_subscribe_messages=True) +subscriber_lastContrib.psubscribe(cfg.get('RedisLog', 'channelLastContributor')) eventNumber = 0 +########## +## UTIL ## +########## + +''' INDEX ''' class LogItem(): FIELDNAME_ORDER = [] @@ -91,13 +103,19 @@ class EventMessage(): to_ret = { 'log': self.feed, 'feedName': self.feedName, 'zmqName': self.zmqName } return 'data: {}\n\n'.format(json.dumps(to_ret)) -def getZrange(keyCateg, date, topNum): - date_str = str(date.year)+str(date.month)+str(date.day) - keyname = "{}:{}".format(keyCateg, date_str) - data = serv_redis_db.zrange(keyname, 0, 5, desc=True, withscores=True) - data = [ [record[0].decode('utf8'), record[1]] for record in data ] +''' GENERAL ''' +def getZrange(keyCateg, date, topNum, endSubkey=""): + date_str = util.getDateStrFormat(date) + keyname = "{}:{}{}".format(keyCateg, date_str, endSubkey) + data = serv_redis_db.zrange(keyname, 0, topNum-1, desc=True, withscores=True) + data = [ [record[0].decode('utf8'), record[1]] for record in data ] return data +########### +## ROUTE ## +########### + +''' MAIN ROUTE ''' @app.route("/") def index(): @@ -108,7 +126,7 @@ def index(): "{:.0f}".format(cfg.getint('Dashboard' ,'size_world_pannel_perc')/100*ratioCorrection), "{:.0f}".format((100-cfg.getint('Dashboard' ,'size_world_pannel_perc'))/100*ratioCorrection) ] - return render_template('index.html', + return render_template('index.html', pannelSize=pannelSize, size_dashboard_width=[cfg.getint('Dashboard' ,'size_dashboard_left_width'), 12-cfg.getint('Dashboard', 'size_dashboard_left_width')], itemToPlot=cfg.get('Dashboard', 'item_to_plot'), @@ -128,6 +146,68 @@ def geo(): default_updateFrequency=cfg.getint('GEO' ,'updateFrequency') ) +@app.route("/contrib") +def contrib(): + categ_list = contributor_helper.categories_in_datatable + categ_list_str = [ s[0].upper() + s[1:].replace('_', ' ') for s in contributor_helper.categories_in_datatable] + categ_list_points = [contributor_helper.DICO_PNTS_REWARD[categ] for categ in categ_list] + + org_rank = contributor_helper.org_rank + org_rank_requirement_pnts = contributor_helper.org_rank_requirement_pnts + org_rank_requirement_text = contributor_helper.org_rank_requirement_text + org_rank_list = [[rank, title, org_rank_requirement_pnts[rank], org_rank_requirement_text[rank]] for rank, title in org_rank.items()] + org_rank_list.sort(key=lambda x: x[0]) + org_rank_additional_text = contributor_helper.org_rank_additional_info + + org_honor_badge_title = contributor_helper.org_honor_badge_title + org_honor_badge_title_list = [ [num, text] for num, text in contributor_helper.org_honor_badge_title.items()] + org_honor_badge_title_list.sort(key=lambda x: x[0]) + + currOrg = request.args.get('org') + if currOrg is None: + currOrg = "" + return render_template('contrib.html', + currOrg=currOrg, + rankMultiplier=contributor_helper.rankMultiplier, + default_pnts_per_contribution=contributor_helper.default_pnts_per_contribution, + additional_help_text=json.loads(cfg.get('CONTRIB', 'additional_help_text')), + categ_list=json.dumps(categ_list), + categ_list_str=categ_list_str, + categ_list_points=categ_list_points, + org_rank_json=json.dumps(org_rank), + org_rank_list=org_rank_list, + org_rank_additional_text=org_rank_additional_text, + org_honor_badge_title=json.dumps(org_honor_badge_title), + org_honor_badge_title_list=org_honor_badge_title_list, + min_between_reload=cfg.getint('CONTRIB', 'min_between_reload') + ) + +''' INDEX ''' + +@app.route("/_logs") +def logs(): + return Response(event_stream_log(), mimetype="text/event-stream") + +@app.route("/_maps") +def maps(): + return Response(event_stream_maps(), mimetype="text/event-stream") + +@app.route("/_get_log_head") +def getLogHead(): + return json.dumps(LogItem('').get_head_row()) + +def event_stream_log(): + for msg in subscriber_log.listen(): + content = msg['data'] + yield EventMessage(content).to_json() + +def event_stream_maps(): + for msg in subscriber_map.listen(): + content = msg['data'].decode('utf8') + yield 'data: {}\n\n'.format(content) + +''' GEO ''' + @app.route("/_getTopCoord") def getTopCoord(): try: @@ -146,7 +226,7 @@ def getHitMap(): except: date = datetime.datetime.now() keyCateg = "GEO_COUNTRY" - topNum = -1 # default Num + topNum = 0 # all data = getZrange(keyCateg, date, topNum) return jsonify(data) @@ -174,7 +254,7 @@ def getCoordsByRadius(): delta = dateEnd - dateStart for i in range(delta.days+1): correctDatetime = dateStart + datetime.timedelta(days=i) - date_str = str(correctDatetime.year)+str(correctDatetime.month)+str(correctDatetime.day) + date_str = util.getDateStrFormat(correctDatetime) keyCateg = 'GEO_RAD' keyname = "{}:{}".format(keyCateg, date_str) res = serv_redis_db.georadius(keyname, centerLon, centerLat, radius, unit='km', withcoord=True) @@ -202,27 +282,99 @@ def getCoordsByRadius(): return jsonify(to_return) -@app.route("/_logs") -def logs(): - return Response(event_stream_log(), mimetype="text/event-stream") +''' CONTRIB ''' -@app.route("/_maps") -def maps(): - return Response(event_stream_maps(), mimetype="text/event-stream") +@app.route("/_getLastContributors") +def getLastContributors(): + return jsonify(contributor_helper.getLastContributorsFromRedis()) -@app.route("/_get_log_head") -def getLogHead(): - return json.dumps(LogItem('').get_head_row()) +@app.route("/_eventStreamLastContributor") +def getLastContributor(): + return Response(eventStreamLastContributor(), mimetype="text/event-stream") -def event_stream_log(): - for msg in subscriber_log.listen(): - content = msg['data'] - yield EventMessage(content).to_json() - -def event_stream_maps(): - for msg in subscriber_map.listen(): +def eventStreamLastContributor(): + for msg in subscriber_lastContrib.listen(): content = msg['data'].decode('utf8') - yield 'data: {}\n\n'.format(content) + contentJson = json.loads(content) + lastContribJson = json.loads(contentJson['log']) + org = lastContribJson['org'] + to_return = contributor_helper.getContributorFromRedis(org) + epoch = lastContribJson['epoch'] + to_return['epoch'] = epoch + yield 'data: {}\n\n'.format(json.dumps(to_return)) + +@app.route("/_getTopContributor") +def getTopContributor(suppliedDate=None): + if suppliedDate is None: + try: + date = datetime.datetime.fromtimestamp(float(request.args.get('date'))) + except: + date = datetime.datetime.now() + else: + date = suppliedDate + + data = contributor_helper.getTopContributorFromRedis(date) + return jsonify(data) + +@app.route("/_getFameContributor") +def getFameContributor(): + try: + date = datetime.datetime.fromtimestamp(float(request.args.get('date'))) + except: + today = datetime.datetime.now() + # get previous month + date = (datetime.datetime(today.year, today.month, 1) - datetime.timedelta(days=1)) + return getTopContributor(suppliedDate=date) + + +@app.route("/_getTop5Overtime") +def getTop5Overtime(): + return jsonify(contributor_helper.getTop5OvertimeFromRedis()) + +@app.route("/_getOrgOvertime") +def getOrgOvertime(): + try: + org = request.args.get('org') + except: + org = '' + return jsonify(contributor_helper.getOrgOvertime(org)) + +@app.route("/_getCategPerContrib") +def getCategPerContrib(): + try: + date = datetime.datetime.fromtimestamp(float(request.args.get('date'))) + except: + date = datetime.datetime.now() + + return jsonify(contributor_helper.getCategPerContribFromRedis(date)) + +@app.route("/_getAllOrg") +def getAllOrg(): + return jsonify(contributor_helper.getAllOrgFromRedis()) + +@app.route("/_getOrgRank") +def getOrgRank(): + try: + org = request.args.get('org') + except: + org = '' + return jsonify(contributor_helper.getCurrentOrgRankFromRedis(org)) + +@app.route("/_getContributionOrgStatus") +def getContributionOrgStatus(): + try: + org = request.args.get('org') + except: + org = '' + return jsonify(contributor_helper.getCurrentContributionStatus(org)) + +@app.route("/_getHonorBadges") +def getHonorBadges(): + try: + org = request.args.get('org') + except: + org = '' + return jsonify(contributor_helper.getOrgHonorBadges(org)) if __name__ == '__main__': app.run(host='localhost', port=8001, threaded=True) diff --git a/static/css/ranking.css b/static/css/ranking.css new file mode 100644 index 0000000..a5a617a --- /dev/null +++ b/static/css/ranking.css @@ -0,0 +1,154 @@ +.ranking { + position: relative; + height: auto; +} + +.successCell { + background-color: #dff0d8 !important +} + +.circleBadge { + width: 80px; + height: 80px; + text-align: center; + border-radius: 65px; + background-color: #e1e1e1; + border: 1px solid #caccce; +} + +.circlBadgeAcquired { + box-shadow: 0px 0px 15px #0000ff +} + +.questionBadgeText { + left: -15%; + position: relative; + bottom: 18%; +} + +.orgRankClass { + top: 6px; + position: relative; +} + +.questionBadgeDiv { + float: left; + width: 40px; + height: 40px; + background-color: #286090; + font-size: xx-large; + border-radius: 50% 50% 50% 50%; + color: white; + top: 4px; + position: relative; + left: 1%; +} + +.selectedOrgInTable { + background-color: #fff6c2 !important; + color: black; +} + +.centerInBtn { + float: left; + text-align: center; + vertical-align: middle; + line-height: 40px; + margin-right: 10px; + font-size: x-large; +} + +.popover-content { + padding: 3px +} + +.popOverBtn { + float: left; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 8px; + height: 46px; + margin-top: 2px; + margin-bottom: 2px; + display: none; +} + +.table > tbody > tr > td.centerCell { + text-align: center; + min-width: 45px; +} + +.dataTables_filter > label { + margin-bottom: 0px; +} + +.table > tbody > tr > td.centerCellPicOrgLogo { + padding: 5px; + text-align: left; +} + +.table > tbody > tr > td.verticalAlign { + vertical-align: middle; +} + +.table > tbody > tr > td.centerCellPicOrgRank { + padding: 0px; + text-align: left; +} + +.progress { + width: 150px; +} + +.panel-body { + padding: 0px; +} + .panel { + margin-bottom: 10px; + } + +.col-lg-6 { + padding-right: 5px; + padding-left: 5px; +} + +.col-lg-8 { + padding-right: 5px; + padding-left: 5px; +} + +.col-lg-4 { + padding-right: 5px; + padding-left: 5px; +} + +.col-lg-12 { + padding-right: 5px; + padding-left: 5px; +} + +.leftSepa { + border-left-color: rgb(221, 221, 221); + border-left-style: solid; + border-left-width: 2px; + padding-left: 8px; +} + +.textTopHeader { + height: 50px; + text-align: center; + margin-left: 8px; + float: left; + padding-top: 15px; +} + +.center { + display:block; + margin-left:auto; + margin-right:auto; +} + +small { + font-size: 100%; + font-weight: bold; +} diff --git a/static/js/contrib.js b/static/js/contrib.js new file mode 100644 index 0000000..4f9ee03 --- /dev/null +++ b/static/js/contrib.js @@ -0,0 +1,577 @@ +/* GLOB VAR */ +var allOrg = []; +var datatableTop; +var datatableFame; +var refresh_speed = min_between_reload*60; +var will_reload = $("#reloadCheckbox").is(':checked'); +var sec_before_reload = refresh_speed; +var dataTop5Overtime; +var plotLineChart + +/* CONFIG */ +var maxRank = 16; +var popOverOption = { + trigger: "hover", + html: true, + placement: 'bottom', + content: generateRankingSheet() +} +var optionsLineChart = { + series: { + shadowSize: 0 , + lines: { + fill: true, + fillColor: { + colors: [ { opacity: 1 }, { opacity: 0.1 } ] + } + } + }, + colors: ["#2F4F4F", "#778899", "#696969", "#A9A9A9", "#D3D3D3", "#337ab7"], + points: { show: true }, + lines: { show: true, fill: true }, + grid: { + tickColor: "#dddddd", + borderWidth: 0 + }, + legend: { + show: true, + position: "nw" + }, + xaxis: { + mode: "time", + timeformat: "%m/%d", + minTickSize: [1, "day"] + } +}; +var optionDatatable_light = { + responsive: true, + searching: false, + ordering: false, + scrollY: '30vh', + scrollCollapse: true, + paging: false, + "language": { + "lengthMenu": "", + "info": "", + "infoFiltered": "", + "infoEmpty": "", + }, + "info": false, + "columnDefs": [ + { className: "centerCellPicOrgRank", "targets": [ 2 ] }, + { className: "centerCellPicOrgLogo", "targets": [ 3 ] }, + { className: "centerCellPicOrgLogo", "targets": [ 4 ] } + ] +}; +var optionDatatable_top = jQuery.extend({}, optionDatatable_light) +var optionDatatable_last = jQuery.extend({}, optionDatatable_light) +optionDatatable_last.columnDefs = [ + { className: "small", "targets": [ 0 ] }, + { className: "verticalAlign", "targets": [ 1 ] }, + { className: "centerCellPicOrgRank verticalAlign", "targets": [ 2 ] }, + { className: "centerCellPicOrgLogo", "targets": [ 3 ] }, + { className: "centerCellPicOrgLogo verticalAlign", "targets": [ 4 ] }, + { className: "centerCellPicOrgLogo verticalAlign", "targets": [ 5 ] }, + { className: "verticalAlign", "targets": [ 6 ] }, + { 'orderData':[6], 'targets': [0] }, + { + 'targets': [6], + 'searchable': false + }, +] +var optionDatatable_fame = jQuery.extend({}, optionDatatable_light) +optionDatatable_fame.scrollY = '45vh'; + +var optionDatatable_Categ = { + responsive: true, + searching: true, + "order": [[ 0, "desc" ]], + scrollY: '38vh', + "scrollX": true, + scrollCollapse: true, + paging: false, + "info": false, + "columnDefs": [ + { className: "centerCellPicOrgRank", "targets": [ 2 ] }, + { className: "centerCellPicOrgLogo", "targets": [ 3 ], 'searchable': false, 'sortable': false }, + { className: "centerCellPicOrgLogo", "targets": [ 4 ]} + ] +}; + +var typeaheadOption = { + source: function (query, process) { + if (allOrg.length == 0) { // caching + return $.getJSON(url_getAllOrg, function (data) { + allOrg = data; + return process(data); + }); + } else { + return process(allOrg); + } + }, + updater: function(org) { + updateProgressHeader(org); + } +} + +/* FUNCTIONS */ +function getMonthlyRankIcon(rank, size, header) { + if (rank > 16) { + var rankLogoPath = url_baseRankMonthlyLogo+0+'.svg'; + } else { + var rankLogoPath = url_baseRankMonthlyLogo+rank+'.svg'; + } + var img = document.createElement('img'); + img.src = rankLogoPath; + if(size == undefined) { + img.height = 26; + img.width = 26; + } else { + if (header) { + img.height = size; + img.width = size; + img.style.position = 'absolute'; + img.style.top = '0'; + img.style.bottom = '0'; + img.style.margin = 'auto'; + img.style.left = '0px'; + } else { + img.height = size; + img.width = size; + } + } + return img.outerHTML; +} + +function getOrgRankIcon(rank, size) { + if (rank > 16) { + var rankLogoPath = url_baseOrgRankLogo+0+'.svg'; + } else { + var rankLogoPath = url_baseOrgRankLogo+rank+'.svg'; + } + var obj = document.createElement('img'); + obj.height = size/2; + obj.width = size; + obj.src = rankLogoPath; + obj.type = "image/svg" + obj.title = org_rank_obj[rank]; + obj.classList.add('orgRankClass') + return obj.outerHTML; +} + +function createImg(source, size) { + var obj = document.createElement('img'); + obj.height = size; + obj.width = size; + obj.style.margin = 'auto'; + obj.src = source; + obj.type = "image/png" + obj.alt = "" + return obj.outerHTML; +} + +function createHonorImg(array, size) { + size = 32; + var div = document.createElement('div'); + div.style.boxShadow = '0px 0px 5px #00000099'; + div.style.backgroundColor = '#e1e1e1'; + for (badgeNum of array) { + var obj = document.createElement('img'); + obj.height = size; + obj.width = size; + obj.style.margin = 'auto'; + obj.title = org_honor_badge_title[badgeNum]; + obj.src = url_baseHonorLogo+badgeNum+'.svg'; + div.appendChild(obj); + } + div.style.width = 32*array.length+'px'; + div.style.borderRadius = '15px'; + return div.outerHTML; +} + +function createOrgLink(org) { + var a = document.createElement('a'); + a.innerHTML = org; + a.href = "?org="+org; + return a.outerHTML; +} + +function generateRankingSheet(rank, rankDec, stepPnt, pnt, Rpnt) { + var Cpnt = pnt - stepPnt; + var Tpnt = Cpnt + Rpnt; + var OuterDiv = document.createElement('div'); + var gdiv = document.createElement('div'); + gdiv.id = "globalDiv"; + gdiv.style.float = 'left'; + //progressBar + var div = document.createElement('div'); + div.classList.add('progress'); + var pb_length = 187; + div.style.width = pb_length+'px'; //HARDCODED... + div.style.marginBottom = '0px'; + var div1 = document.createElement('div') + div1.classList.add('progress-bar') + div1.style.width = 100*(Cpnt)/Tpnt+'%'; + div1.innerHTML = ""+Cpnt+""; + div.appendChild(div1); + var div1 = document.createElement('div') + div1.classList.add('progress-bar', 'progress-bar-warning') + div1.style.width = 100*(Rpnt)/Tpnt+'%' + div1.innerHTML = ""+Rpnt+""; + div.appendChild(div1); + gdiv.appendChild(div); + // table + var table = document.createElement('table'); + table.classList.add('table', 'table-striped'); + table.style.marginBottom = '5px'; + //head + var thead = document.createElement('thead'); + var tr = document.createElement('tr'); + var th = document.createElement('th'); + th.innerHTML = "Rank"; + tr.appendChild(th); + var th = document.createElement('th'); + th.innerHTML = "Requirement (CP)"; + tr.appendChild(th); + thead.appendChild(tr); + //body + var tbody = document.createElement('tbody'); + for (var i=1; i<=maxRank; i++) { + var tr = document.createElement('tr'); + var td1 = document.createElement('td'); + td1.innerHTML = getMonthlyRankIcon(i, 40); + td1.style.padding = "2px"; + var td2 = document.createElement('td'); + td2.innerHTML = Math.floor(Math.pow(rankMultiplier, i)); + td2.style.padding = "2px"; + tr.style.textAlign = "center"; + if (i == rank) { // current org rank + tr.style.backgroundColor = "#337ab7"; + tr.style.color = "white"; + } else if (i == rank+1) { + tr.style.backgroundColor = "#f0ad4e"; + tr.style.color = "white"; + } + tr.appendChild(td1); + tr.appendChild(td2); + tbody.appendChild(tr); + } + table.appendChild(thead); + table.appendChild(tbody); + gdiv.appendChild(table); + OuterDiv.appendChild(gdiv); + // Tot nbr points + var tableHeight = 720; //HARDCODED... + var div = document.createElement('div'); + div.classList.add('progress'); + div.style.width = '20px'; + div.style.height = tableHeight+'px'; //HARDCODED... + div.style.display = 'flex' + div.style.float = 'left'; + div.style.marginTop = '56px'; + div.style.marginBottom = '0px'; + div.style.marginLeft = '2px'; + var div1 = document.createElement('div') + div1.classList.add('progress-bar', 'progress-bar-success', 'progress-bar-striped') + div1.style.height = ((rank+rankDec)*100/16)+'%'; + div1.style.width = '100%'; + div.appendChild(div1); + OuterDiv.appendChild(div); + + return OuterDiv.outerHTML; +} + +function addToTableFromJson(datatable, url) { + $.getJSON( url, function( data ) { + for (i in data) { + var row = data[i]; + i = parseInt(i); + var to_add = [ + row.pnts, + getMonthlyRankIcon(row.rank), + getOrgRankIcon(row.orgRank, 60), + createHonorImg(row.honorBadge, 20), + createImg(row.logo_path, 32), + createOrgLink(row.org) + ]; + datatable.row.add(to_add); + } + datatable.draw(); + }); +} + +function addLastFromJson(datatable, url) { + $.getJSON( url, function( data ) { + for (i in data) { + var row = data[i]; + i = parseInt(i); + addLastContributor(datatable, row); + } + datatable.draw(); + }); +} + +function addLastContributor(datatable, data, update) { + var date = new Date(data.epoch*1000); + var to_add = [ + date.toTimeString().slice(0,-15) +' '+ date.toLocaleDateString(), + data.pnts, + getMonthlyRankIcon(data.rank), + getOrgRankIcon(data.orgRank, 60), + createHonorImg(data.honorBadge, 20), + createImg(data.logo_path, 32), + createOrgLink(data.org), + ]; + if (update == undefined || update == false) { + datatable.row.add(to_add); + } else if(update == true) { + datatable.rows().every( function() { + if(this.data()[3] == data.org) { + datatable.row( this ).data( to_add ); + } + }); + } +} + +function updateProgressBar(org) { + if(currOrg != org) + return; + $.getJSON( url_getOrgRank+'?org='+org, function( data ) { + var rank = Math.floor(data.rank); + var rankDec = data.rank-rank; + var popoverRank = $('#btnCurrRank').data('bs.popover'); + popoverRank.options.content = generateRankingSheet(rank, rankDec, data.stepPts, data.points, data.remainingPts); + $('#orgRankDiv').html(getMonthlyRankIcon(rank, 40, true)); + $('#orgNextRankDiv').html(getMonthlyRankIcon(rank+1, 40, true)); + if (data.rank > 16){ + $('#progressBarDiv').width(1*150); //150 is empty bar width + } else { + $('#progressBarDiv').width((data.rank - rank)*150); //150 is empty bar width + } + }); +} + +function updateOvertakePnts() { + var prevOrgName = ""; + var prevOrgPnts = 0; + datatableTop.rows().every( function() { + var row = this.node(); + var orgRowName = $(this.data()[5])[0].text; // contained in + var orgRowPnts = this.data()[0] + if(orgRowName == currOrg) { + if(prevOrgName == ""){ //already first + $('#orgToOverTake').text(orgRowName); + $('#pntsToOvertakeNext').text(0); + } else { + $('#orgToOverTake').text(prevOrgName); + $('#pntsToOvertakeNext').text(parseInt(prevOrgPnts)-orgRowPnts); + } + } else { + prevOrgName = orgRowName; + prevOrgPnts = orgRowPnts; + } + }); +} + +function updateProgressHeader(org) { + currOrg = org; + // get Org rank + $.getJSON( url_getOrgRank+'?org='+org, function( data ) { + datatableTop.draw(); + var rank = Math.floor(data.rank); + var rankDec = data.rank-rank; + $('#btnCurrRank').show(); + $('#orgText').text(data.org); + var popoverRank = $('#btnCurrRank').data('bs.popover'); + popoverRank.options.content = generateRankingSheet(rank, rankDec, data.stepPts, data.points, data.remainingPts); + $('#orgRankDiv').html(getMonthlyRankIcon(rank, 40, true)); + $('#orgNextRankDiv').html(getMonthlyRankIcon(rank+1, 40, true)); + if (data.rank > 16){ + $('#progressBarDiv').width(1*150); //150 is empty bar width + } else { + $('#progressBarDiv').width((data.rank - rank)*150); //150 is empty bar width + } + // update color in other dataTables + datatableTop.rows().every( function() { + var row = this.node(); + if(this.data()[5] == data.org) { row.classList.add('selectedOrgInTable'); } else { row.classList.remove('selectedOrgInTable'); } + }); + datatableFame.rows().every( function() { + var row = this.node(); + if(this.data()[5] == data.org) { row.classList.add('selectedOrgInTable'); } else { row.classList.remove('selectedOrgInTable'); } + }); + datatableCateg.rows().every( function() { + var row = this.node(); + if(this.data()[5] == data.org) { row.classList.add('selectedOrgInTable'); } else { row.classList.remove('selectedOrgInTable'); } + }); + datatableLast.rows().every( function() { + var row = this.node(); + if(this.data()[6] == data.org) { row.classList.add('selectedOrgInTable'); } else { row.classList.remove('selectedOrgInTable'); } + }); + }); + + // colorize row contribution rank help + $.getJSON( url_getContributionOrgStatus+'?org='+org, function( data ) { + var status = data['status']; + var curContributionOrgRank = data['rank']; + var totNumPoints = data['totPoints'] + $('#orgTotNumOfPoint').text(totNumPoints); + if (curContributionOrgRank == 0) { + $('#orgContributionRank').attr('data', ''); + } else { + $('#orgContributionRank').attr('data', url_baseOrgRankLogo+curContributionOrgRank+'.svg'); + } + for (var row of $('#bodyTablerankingModal')[0].children) { + row = $(row); + var firstCell = $(row.children()[0]); + var rank = row.data('rank'); + //remove all classes + row.removeClass("warning"); + row.removeClass("danger"); + row.removeClass("success"); + firstCell.removeClass("successCell"); + //add correct class + if(status[rank] == 0){ + row.addClass("danger"); + } else if(status[rank] == 1) { + row.addClass("warning"); + } + if(rank == curContributionOrgRank) { + firstCell.addClass("successCell"); + } + } + }); + + // colorize badge if acquired + $.getJSON( url_getHonorBadges+'?org='+org, function( data ) { + for(var i=0; i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/static/pics/MISPHonorableIcons/2.svg b/static/pics/MISPHonorableIcons/2.svg new file mode 100644 index 0000000..65bcf30 --- /dev/null +++ b/static/pics/MISPHonorableIcons/2.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/0.svg b/static/pics/rankingMISPMonthly/0.svg new file mode 100644 index 0000000..0c94467 --- /dev/null +++ b/static/pics/rankingMISPMonthly/0.svg @@ -0,0 +1,56 @@ + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/static/pics/rankingMISPMonthly/1.svg b/static/pics/rankingMISPMonthly/1.svg new file mode 100644 index 0000000..30b660d --- /dev/null +++ b/static/pics/rankingMISPMonthly/1.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/10.svg b/static/pics/rankingMISPMonthly/10.svg new file mode 100644 index 0000000..111b538 --- /dev/null +++ b/static/pics/rankingMISPMonthly/10.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/11.svg b/static/pics/rankingMISPMonthly/11.svg new file mode 100644 index 0000000..c20e6ea --- /dev/null +++ b/static/pics/rankingMISPMonthly/11.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/12.svg b/static/pics/rankingMISPMonthly/12.svg new file mode 100644 index 0000000..3f06c82 --- /dev/null +++ b/static/pics/rankingMISPMonthly/12.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/13.svg b/static/pics/rankingMISPMonthly/13.svg new file mode 100644 index 0000000..9d693a6 --- /dev/null +++ b/static/pics/rankingMISPMonthly/13.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/14.svg b/static/pics/rankingMISPMonthly/14.svg new file mode 100644 index 0000000..ee05961 --- /dev/null +++ b/static/pics/rankingMISPMonthly/14.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/15.svg b/static/pics/rankingMISPMonthly/15.svg new file mode 100644 index 0000000..85c9b8b --- /dev/null +++ b/static/pics/rankingMISPMonthly/15.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/16.svg b/static/pics/rankingMISPMonthly/16.svg new file mode 100644 index 0000000..ca87603 --- /dev/null +++ b/static/pics/rankingMISPMonthly/16.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/2.svg b/static/pics/rankingMISPMonthly/2.svg new file mode 100644 index 0000000..5ae41d7 --- /dev/null +++ b/static/pics/rankingMISPMonthly/2.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/3.svg b/static/pics/rankingMISPMonthly/3.svg new file mode 100644 index 0000000..ee2fd85 --- /dev/null +++ b/static/pics/rankingMISPMonthly/3.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/4.svg b/static/pics/rankingMISPMonthly/4.svg new file mode 100644 index 0000000..60bb037 --- /dev/null +++ b/static/pics/rankingMISPMonthly/4.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/5.svg b/static/pics/rankingMISPMonthly/5.svg new file mode 100644 index 0000000..73bb589 --- /dev/null +++ b/static/pics/rankingMISPMonthly/5.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/6.svg b/static/pics/rankingMISPMonthly/6.svg new file mode 100644 index 0000000..27b8dd2 --- /dev/null +++ b/static/pics/rankingMISPMonthly/6.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/7.svg b/static/pics/rankingMISPMonthly/7.svg new file mode 100644 index 0000000..8be4ea2 --- /dev/null +++ b/static/pics/rankingMISPMonthly/7.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/8.svg b/static/pics/rankingMISPMonthly/8.svg new file mode 100644 index 0000000..1e167f9 --- /dev/null +++ b/static/pics/rankingMISPMonthly/8.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/9.svg b/static/pics/rankingMISPMonthly/9.svg new file mode 100644 index 0000000..ea467c6 --- /dev/null +++ b/static/pics/rankingMISPMonthly/9.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/static/pics/rankingMISPMonthly/png/0.png b/static/pics/rankingMISPMonthly/png/0.png new file mode 100644 index 0000000..d7b0c8f Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/0.png differ diff --git a/static/pics/rankingMISPMonthly/png/1.png b/static/pics/rankingMISPMonthly/png/1.png new file mode 100644 index 0000000..7e9de7b Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/1.png differ diff --git a/static/pics/rankingMISPMonthly/png/10.png b/static/pics/rankingMISPMonthly/png/10.png new file mode 100644 index 0000000..ae0c891 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/10.png differ diff --git a/static/pics/rankingMISPMonthly/png/11.png b/static/pics/rankingMISPMonthly/png/11.png new file mode 100644 index 0000000..bec4cfb Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/11.png differ diff --git a/static/pics/rankingMISPMonthly/png/12.png b/static/pics/rankingMISPMonthly/png/12.png new file mode 100644 index 0000000..ac70cb6 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/12.png differ diff --git a/static/pics/rankingMISPMonthly/png/13.png b/static/pics/rankingMISPMonthly/png/13.png new file mode 100644 index 0000000..8d7d9e4 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/13.png differ diff --git a/static/pics/rankingMISPMonthly/png/14.png b/static/pics/rankingMISPMonthly/png/14.png new file mode 100644 index 0000000..f5c4737 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/14.png differ diff --git a/static/pics/rankingMISPMonthly/png/15.png b/static/pics/rankingMISPMonthly/png/15.png new file mode 100644 index 0000000..d6ab57b Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/15.png differ diff --git a/static/pics/rankingMISPMonthly/png/16.png b/static/pics/rankingMISPMonthly/png/16.png new file mode 100644 index 0000000..ac58d71 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/16.png differ diff --git a/static/pics/rankingMISPMonthly/png/2.png b/static/pics/rankingMISPMonthly/png/2.png new file mode 100644 index 0000000..40bdb83 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/2.png differ diff --git a/static/pics/rankingMISPMonthly/png/3.png b/static/pics/rankingMISPMonthly/png/3.png new file mode 100644 index 0000000..2e91754 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/3.png differ diff --git a/static/pics/rankingMISPMonthly/png/4.png b/static/pics/rankingMISPMonthly/png/4.png new file mode 100644 index 0000000..e1a30e9 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/4.png differ diff --git a/static/pics/rankingMISPMonthly/png/5.png b/static/pics/rankingMISPMonthly/png/5.png new file mode 100644 index 0000000..55c9e6f Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/5.png differ diff --git a/static/pics/rankingMISPMonthly/png/6.png b/static/pics/rankingMISPMonthly/png/6.png new file mode 100644 index 0000000..8b93d88 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/6.png differ diff --git a/static/pics/rankingMISPMonthly/png/7.png b/static/pics/rankingMISPMonthly/png/7.png new file mode 100644 index 0000000..16dbf2c Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/7.png differ diff --git a/static/pics/rankingMISPMonthly/png/8.png b/static/pics/rankingMISPMonthly/png/8.png new file mode 100644 index 0000000..7db4f25 Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/8.png differ diff --git a/static/pics/rankingMISPMonthly/png/9.png b/static/pics/rankingMISPMonthly/png/9.png new file mode 100644 index 0000000..d485f3b Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/9.png differ diff --git a/static/pics/rankingMISPMonthly/png/?.png b/static/pics/rankingMISPMonthly/png/?.png new file mode 100644 index 0000000..c46c64c Binary files /dev/null and b/static/pics/rankingMISPMonthly/png/?.png differ diff --git a/static/pics/rankingMISPOrg/1.svg b/static/pics/rankingMISPOrg/1.svg new file mode 100644 index 0000000..93003c5 --- /dev/null +++ b/static/pics/rankingMISPOrg/1.svg @@ -0,0 +1,83 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/10.svg b/static/pics/rankingMISPOrg/10.svg new file mode 100644 index 0000000..d052c96 --- /dev/null +++ b/static/pics/rankingMISPOrg/10.svg @@ -0,0 +1,102 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/11.svg b/static/pics/rankingMISPOrg/11.svg new file mode 100644 index 0000000..04b95fc --- /dev/null +++ b/static/pics/rankingMISPOrg/11.svg @@ -0,0 +1,102 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/12.svg b/static/pics/rankingMISPOrg/12.svg new file mode 100644 index 0000000..fd021d1 --- /dev/null +++ b/static/pics/rankingMISPOrg/12.svg @@ -0,0 +1,102 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/13.svg b/static/pics/rankingMISPOrg/13.svg new file mode 100644 index 0000000..69c7b7d --- /dev/null +++ b/static/pics/rankingMISPOrg/13.svg @@ -0,0 +1,102 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/14.svg b/static/pics/rankingMISPOrg/14.svg new file mode 100644 index 0000000..abcfb78 --- /dev/null +++ b/static/pics/rankingMISPOrg/14.svg @@ -0,0 +1,94 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/15.svg b/static/pics/rankingMISPOrg/15.svg new file mode 100644 index 0000000..ff4f422 --- /dev/null +++ b/static/pics/rankingMISPOrg/15.svg @@ -0,0 +1,124 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/16.svg b/static/pics/rankingMISPOrg/16.svg new file mode 100644 index 0000000..670e901 --- /dev/null +++ b/static/pics/rankingMISPOrg/16.svg @@ -0,0 +1,116 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/2.svg b/static/pics/rankingMISPOrg/2.svg new file mode 100644 index 0000000..46c1436 --- /dev/null +++ b/static/pics/rankingMISPOrg/2.svg @@ -0,0 +1,93 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/3.svg b/static/pics/rankingMISPOrg/3.svg new file mode 100644 index 0000000..ffdff77 --- /dev/null +++ b/static/pics/rankingMISPOrg/3.svg @@ -0,0 +1,103 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/4.svg b/static/pics/rankingMISPOrg/4.svg new file mode 100644 index 0000000..41b5ac2 --- /dev/null +++ b/static/pics/rankingMISPOrg/4.svg @@ -0,0 +1,103 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/5.svg b/static/pics/rankingMISPOrg/5.svg new file mode 100644 index 0000000..cdb7d84 --- /dev/null +++ b/static/pics/rankingMISPOrg/5.svg @@ -0,0 +1,113 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/6.svg b/static/pics/rankingMISPOrg/6.svg new file mode 100644 index 0000000..a8fb9a1 --- /dev/null +++ b/static/pics/rankingMISPOrg/6.svg @@ -0,0 +1,113 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/7.svg b/static/pics/rankingMISPOrg/7.svg new file mode 100644 index 0000000..c6fd30b --- /dev/null +++ b/static/pics/rankingMISPOrg/7.svg @@ -0,0 +1,123 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/8.svg b/static/pics/rankingMISPOrg/8.svg new file mode 100644 index 0000000..221c803 --- /dev/null +++ b/static/pics/rankingMISPOrg/8.svg @@ -0,0 +1,123 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/pics/rankingMISPOrg/9.svg b/static/pics/rankingMISPOrg/9.svg new file mode 100644 index 0000000..585c48b --- /dev/null +++ b/static/pics/rankingMISPOrg/9.svg @@ -0,0 +1,123 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/contrib.html b/templates/contrib.html new file mode 100644 index 0000000..949806a --- /dev/null +++ b/templates/contrib.html @@ -0,0 +1,408 @@ + + + + + + + + + + + MISP live dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+
+
+ Contributor ranking (monthly) +
+
+ +
+ + + + + + + + + + + + + +
PointsCur. rankOrg. rankOrganisation
+
+ +
+ +
+
+ +
+
+
+ Last contributors +
+
+ +
+ + + + + + + + + + + + + + +
DatePointsCur. rankOrg. rankOrganisation
+
+
+ +
+
+ +
+
+
+ Contributors and categories (total) +
+
+ +
+ + + + + + + + + + {% for categ in categ_list_str %} + + {% endfor %} + + + + +
PointsCur. rankOrg. rankOrganisation{{ categ }}
+
+ +
+ +
+
+ + +
+ + +
+ +
+
+ Hall Of Fame (previous month) +
+
+ +
+ + + + + + + + + + + + + +
PointsPrev. rankOrg. rankOrganisation
+
+ +
+ +
+ +
+
+ Top 5 Contributor overtime +
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/templates/geo.html b/templates/geo.html index 50302b2..2dfbfde 100644 --- a/templates/geo.html +++ b/templates/geo.html @@ -58,7 +58,7 @@ } .textTopHeader { - height: 50px; + height: 50px; text-align: center; margin-left: 8px; float: left; @@ -90,6 +90,7 @@ small {
@@ -184,7 +185,7 @@ small {
- +
Hit map @@ -198,7 +199,7 @@ small {
Geospatial information - Dates: + Dates: @@ -225,7 +226,7 @@ small { -