Merge pull request #7 from mokaddem/skill_trophies
MISP Gamification - Addition of skill trophies and badgespull/18/head
|
@ -65,6 +65,31 @@ regularlyDays=7
|
|||
[HonorBadge]
|
||||
1=Has made at least one pull request on the MISP project
|
||||
2=Is a donator for the MISP project
|
||||
3=Has published content upvoted by the community
|
||||
4=Has published valuable content for the community
|
||||
5=Has published loads of valuable content for the community
|
||||
|
||||
[TrophyDifficulty]
|
||||
#represent the % of org that can have this rank. Rank 1 is ignored as only 1 org can have it.
|
||||
trophyMapping=[2, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10]
|
||||
|
||||
[HonorTrophy]
|
||||
0=No trophy
|
||||
1=Novice
|
||||
2=Beginner
|
||||
3=Intermediate
|
||||
4=Competent
|
||||
5=Experienced
|
||||
6=Talented
|
||||
7=Skilled
|
||||
8=Advanced
|
||||
9=Expert
|
||||
10=Pro
|
||||
11=Master
|
||||
12=Grand Master
|
||||
|
||||
[HonorTrophyCateg]
|
||||
categ=["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" ]
|
||||
|
||||
[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"]
|
||||
|
|
|
@ -22,6 +22,13 @@ class Contributor_helper:
|
|||
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))
|
||||
|
||||
self.trophyMapping = json.loads(self.cfg_org_rank.get('TrophyDifficulty', 'trophyMapping'))
|
||||
self.trophyNum = len(self.cfg_org_rank.options('HonorTrophy'))-1 #0 is not a trophy
|
||||
self.categories_in_trophy = json.loads(self.cfg_org_rank.get('HonorTrophyCateg', 'categ'))
|
||||
self.trophy_title = {}
|
||||
for trophyNum in range(0, len(self.cfg_org_rank.options('HonorTrophy'))): #get Num of trophy
|
||||
self.trophy_title[trophyNum] = self.cfg_org_rank.get('HonorTrophy', str(trophyNum))
|
||||
|
||||
#GLOBAL RANKING
|
||||
self.org_rank_maxLevel = self.cfg_org_rank.getint('rankTitle', 'maxLevel')
|
||||
self.org_rank = {}
|
||||
|
@ -125,11 +132,19 @@ class Contributor_helper:
|
|||
to_ret[i] = -1
|
||||
return {'rank': final_rank, 'status': to_ret, 'totPoints': self.getOrgContributionTotalPoints(org)}
|
||||
|
||||
def updateOrgContributionRank(self, orgName, pnts_to_add, action, contribType, eventTime, isLabeled):
|
||||
# return the awards given to the organisation
|
||||
def updateOrgContributionRank(self, orgName, pnts_to_add, action, contribType, eventTime, isLabeled, categ=""):
|
||||
ContributionStatus = self.getCurrentContributionStatus(orgName)
|
||||
oldContributionStatus = ContributionStatus['status']
|
||||
oldHonorBadges = self.getOrgHonorBadges(orgName)
|
||||
oldTrophy = self.getOrgTrophies(orgName)
|
||||
keyname = 'CONTRIB_ORG:{org}:{orgCateg}'
|
||||
# update total points
|
||||
totOrgPnts = self.serv_redis_db.incrby(keyname.format(org=orgName, orgCateg='points'), pnts_to_add)
|
||||
|
||||
#FIXME TEMPORARY, JUST TO TEST IF IT WORKS CORRECLTY
|
||||
self.giveTrophyPointsToOrg(orgName, categ, 1)
|
||||
|
||||
# update date variables
|
||||
if contribType == 'Attribute':
|
||||
attributeWeekCount = self.serv_redis_db.incrby(keyname.format(org=orgName, orgCateg='ATTR_WEEK_COUNT'), 1)
|
||||
|
@ -161,8 +176,6 @@ class Contributor_helper:
|
|||
regularlyDays = self.regularlyDays
|
||||
isRecent = (datetime.datetime.now() - eventTime).days > 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':
|
||||
|
@ -195,11 +208,42 @@ class Contributor_helper:
|
|||
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)
|
||||
|
||||
ContributionStatus = self.getCurrentContributionStatus(orgName)
|
||||
newContributionStatus = ContributionStatus['status']
|
||||
newHonorBadges = self.getOrgHonorBadges(orgName)
|
||||
newTrophy = self.getOrgTrophies(orgName)
|
||||
|
||||
# awards to publish
|
||||
awards_given = []
|
||||
for i in newContributionStatus.keys():
|
||||
if oldContributionStatus[i] < newContributionStatus[i] and i != ContributionStatus['rank']:
|
||||
awards_given.append(['contribution_status', i])
|
||||
|
||||
for badgeNum in newHonorBadges:
|
||||
if badgeNum not in oldHonorBadges:
|
||||
awards_given.append(['badge', badgeNum])
|
||||
|
||||
temp = {}
|
||||
for item in oldTrophy:
|
||||
categ = item['categ']
|
||||
rank = item['trophy_true_rank']
|
||||
temp[categ] = rank
|
||||
for item in newTrophy:
|
||||
categ = item['categ']
|
||||
rank = item['trophy_true_rank']
|
||||
try:
|
||||
oldCategRank = temp[categ]
|
||||
except KeyError:
|
||||
oldCategRank = 0
|
||||
if rank > oldCategRank:
|
||||
awards_given.append(['trophy', [categ, rank]])
|
||||
|
||||
return awards_given
|
||||
|
||||
''' HONOR BADGES '''
|
||||
def getOrgHonorBadges(self, org):
|
||||
keyname = 'CONTRIB_ORG:{org}:{orgCateg}'
|
||||
|
@ -218,6 +262,83 @@ class Contributor_helper:
|
|||
keyname = 'CONTRIB_ORG:{org}:{orgCateg}'
|
||||
self.serv_redis_db.delete(keyname.format(org=org, orgCateg='BADGE_'+str(badgeNum)))
|
||||
|
||||
''' TROPHIES '''
|
||||
def getOrgTrophies(self, org):
|
||||
self.getAllOrgsTrophyRanking()
|
||||
keyname = 'CONTRIB_TROPHY:{orgCateg}'
|
||||
trophy = []
|
||||
for categ in self.categories_in_trophy:
|
||||
key = keyname.format(orgCateg=categ)
|
||||
totNum = self.serv_redis_db.zcard(key)
|
||||
if totNum == 0:
|
||||
continue
|
||||
pos = self.serv_redis_db.zrank(key, org)
|
||||
if pos is None:
|
||||
continue
|
||||
trophy_rank = self.posToRankMapping(pos, totNum)
|
||||
trophy_Pnts = self.serv_redis_db.zscore(key, org)
|
||||
trophy.append({ 'categ': categ, 'trophy_points': trophy_Pnts, 'trophy_rank': trophy_rank, 'trophy_true_rank': trophy_rank, 'trophy_title': self.trophy_title[trophy_rank]})
|
||||
return trophy
|
||||
|
||||
def getOrgsTrophyRanking(self, categ):
|
||||
keyname = 'CONTRIB_TROPHY:{orgCateg}'
|
||||
res = self.serv_redis_db.zrange(keyname.format(orgCateg=categ), 0, -1, withscores=True, desc=True)
|
||||
res = [[org.decode('utf8'), score] for org, score in res]
|
||||
return res
|
||||
|
||||
def getAllOrgsTrophyRanking(self):
|
||||
dico_categ = {}
|
||||
for categ in self.categories_in_trophy:
|
||||
res = self.getOrgsTrophyRanking(categ)
|
||||
dico_categ[categ] = res
|
||||
|
||||
def posToRankMapping(self, pos, totNum):
|
||||
mapping = self.trophyMapping
|
||||
mapping_num = [math.ceil(float(float(totNum*i)/float(100))) for i in mapping]
|
||||
# print(pos, totNum)
|
||||
if pos == 0: #first
|
||||
position = 1
|
||||
else:
|
||||
temp_pos = pos
|
||||
counter = 1
|
||||
for num in mapping_num:
|
||||
if temp_pos < num:
|
||||
position = counter
|
||||
else:
|
||||
temp_pos -= num
|
||||
counter += 1
|
||||
return self.trophyNum+1 - position
|
||||
|
||||
def giveTrophyPointsToOrg(self, org, categ, points):
|
||||
keyname = 'CONTRIB_TROPHY:{orgCateg}'
|
||||
self.serv_redis_db.zincrby(keyname.format(orgCateg=categ), org, points)
|
||||
|
||||
def removeTrophyPointsFromOrg(self, org, categ, points):
|
||||
keyname = 'CONTRIB_TROPHY:{orgCateg}'
|
||||
self.serv_redis_db.zincrby(keyname.format(orgCateg=categ), org, -points)
|
||||
|
||||
''' AWARDS HELPER '''
|
||||
def getLastAwardsFromRedis(self):
|
||||
date = datetime.datetime.now()
|
||||
keyname = "CONTRIB_LAST_AWARDS"
|
||||
prev_days = 7
|
||||
topNum = self.MAX_NUMBER_OF_LAST_CONTRIBUTOR # default Num
|
||||
addedOrg = []
|
||||
data = []
|
||||
for curDate in util.getXPrevDaysSpan(date, prev_days):
|
||||
last_awards = self.getZrange(keyname, curDate, topNum)
|
||||
for dico_award, sec in last_awards:
|
||||
dico_award = json.loads(dico_award)
|
||||
org = dico_award['org']
|
||||
dic = {}
|
||||
dic['orgRank'] = self.getOrgContributionRank(org)['final_rank']
|
||||
dic['logo_path'] = self.getOrgLogoFromMISP(org)
|
||||
dic['org'] = org
|
||||
dic['epoch'] = sec
|
||||
dic['award'] = dico_award['award']
|
||||
data.append(dic)
|
||||
return data
|
||||
|
||||
''' MONTHLY CONTRIBUTION '''
|
||||
def getOrgPntFromRedis(self, org, date):
|
||||
keyCateg = 'CONTRIB_DAY'
|
||||
|
@ -381,7 +502,6 @@ class Contributor_helper:
|
|||
prev = i
|
||||
return { 'remainingPts': 0, 'stepPts': self.rankMultiplier**self.levelMax }
|
||||
|
||||
|
||||
''' '''
|
||||
''' TEST DATA '''
|
||||
''' '''
|
||||
|
@ -583,3 +703,12 @@ class Contributor_helper:
|
|||
else:
|
||||
honorBadge.append(0)
|
||||
return honorBadge
|
||||
|
||||
def TEST_getOrgTrophies(self, org):
|
||||
keyname = 'CONTRIB_ORG:{org}:{orgCateg}'
|
||||
trophy = []
|
||||
for categ in self.categories_in_trophy:
|
||||
key = keyname.format(org=org, orgCateg='TROPHY_'+categ)
|
||||
trophy_Pnts = random.randint(0,10)
|
||||
trophy.append({'categName': categ, 'rank': trophy_Pnts})
|
||||
return trophy
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
#!/usr/bin/env python3.5
|
||||
|
||||
import os, sys
|
||||
import os, sys, json
|
||||
import datetime, time
|
||||
import redis
|
||||
import configparser
|
||||
|
||||
import util
|
||||
import contributor_helper
|
||||
|
||||
ONE_DAY = 60*60*24
|
||||
configfile = os.path.join(os.environ['DASH_CONFIG'], 'config.cfg')
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(configfile)
|
||||
serv_log = redis.StrictRedis(
|
||||
host=cfg.get('RedisGlobal', 'host'),
|
||||
port=cfg.getint('RedisGlobal', 'port'),
|
||||
db=cfg.getint('RedisLog', 'db'))
|
||||
serv_redis_db = redis.StrictRedis(
|
||||
host=cfg.get('RedisGlobal', 'host'),
|
||||
port=cfg.getint('RedisGlobal', 'port'),
|
||||
db=cfg.getint('RedisDB', 'db'))
|
||||
CHANNEL_LASTAWARDS = cfg.get('RedisLog', 'channelLastAwards')
|
||||
|
||||
chelper = contributor_helper.Contributor_helper(serv_redis_db, cfg)
|
||||
|
||||
def publish_log(zmq_name, name, content, channel):
|
||||
to_send = { 'name': name, 'data': json.dumps(content), 'zmqName': zmq_name }
|
||||
serv_log.publish(channel, json.dumps(to_send))
|
||||
|
||||
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)
|
||||
org_trophy = chelper.getOrgTrophies(org)
|
||||
|
||||
os.system('clear')
|
||||
print()
|
||||
print("Organisation points: {}".format(org_pnts))
|
||||
print("Organisation contribution rank: {}".format(org_c_status['rank']))
|
||||
|
@ -39,23 +53,38 @@ 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()
|
||||
|
||||
print()
|
||||
print('''
|
||||
Organisation trophy:
|
||||
--------------------------''')
|
||||
for dic in org_trophy:
|
||||
categ = dic['categ']
|
||||
trophyRank = dic['trophy_true_rank']
|
||||
trophyPnts = dic['trophy_points']
|
||||
print("{}\t{} [{}]".format(categ, trophyRank, trophyPnts))
|
||||
print()
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
org = sys.argv[1]
|
||||
else:
|
||||
org = input('Enter the organisation name: ')
|
||||
|
||||
|
||||
printOrgInfo(org)
|
||||
|
||||
ContributionStatus = chelper.getCurrentContributionStatus(org)
|
||||
OLD_org_c_status = ContributionStatus['status']
|
||||
OLD_org_honor_badge = chelper.getOrgHonorBadges(org)
|
||||
OLD_org_trophy = chelper.getOrgTrophies(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)
|
||||
org_trophy = chelper.getOrgTrophies(org)
|
||||
|
||||
userRep = input("Enter the organisation RANK to give/remove to {} (<ENTER> to finish): ".format(org))
|
||||
if userRep == '':
|
||||
|
@ -67,7 +96,7 @@ def main():
|
|||
except:
|
||||
print('Not an integer')
|
||||
continue
|
||||
if rankNum < 1 and rankNum > chelper.org_rank_maxLevel:
|
||||
if rankNum < 1 or rankNum > chelper.org_rank_maxLevel:
|
||||
print('Not a valid rank')
|
||||
continue
|
||||
|
||||
|
@ -77,13 +106,14 @@ def main():
|
|||
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)
|
||||
org_trophy = chelper.getOrgTrophies(org)
|
||||
|
||||
userRep = input("Enter the organisation BADGE to give/remove to {} (<ENTER> to finish): ".format(org))
|
||||
if userRep == '':
|
||||
|
@ -106,6 +136,80 @@ def main():
|
|||
|
||||
printOrgInfo(org)
|
||||
|
||||
# trophy
|
||||
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)
|
||||
org_trophy = chelper.getOrgTrophies(org)
|
||||
|
||||
print()
|
||||
for i, categ in enumerate(chelper.categories_in_trophy):
|
||||
print("{}. {}".format(i, categ))
|
||||
userCateg = input("Enter the CATEGORY in which to add/remove trophy points: ")
|
||||
if userCateg == '':
|
||||
break
|
||||
try: #not int
|
||||
userCateg = int(userCateg)
|
||||
except:
|
||||
print('Not an integer')
|
||||
continue
|
||||
if userCateg < 1 and userCateg > len(chelper.categories_in_trophy):
|
||||
print('Not a valid rank')
|
||||
continue
|
||||
|
||||
categ = chelper.categories_in_trophy[userCateg]
|
||||
userRep = input("Enter the TROPHY POINTS to give/remove to {} (<ENTER> to finish) in {}: ".format(org, categ))
|
||||
if userRep == '':
|
||||
break
|
||||
else:
|
||||
# validate input
|
||||
try: #not int
|
||||
trophyPnts = int(userRep)
|
||||
except:
|
||||
print('Not an integer')
|
||||
continue
|
||||
|
||||
chelper.giveTrophyPointsToOrg(org, categ, trophyPnts)
|
||||
|
||||
printOrgInfo(org)
|
||||
|
||||
|
||||
now = datetime.datetime.now()
|
||||
nowSec = int(time.time())
|
||||
ContributionStatus = chelper.getCurrentContributionStatus(org)
|
||||
NEW_org_c_status = ContributionStatus['status']
|
||||
NEW_org_honor_badge = chelper.getOrgHonorBadges(org)
|
||||
NEW_org_trophy = chelper.getOrgTrophies(org)
|
||||
awards_given = []
|
||||
|
||||
for i in NEW_org_c_status.keys():
|
||||
if OLD_org_c_status[i] < NEW_org_c_status[i] and i != ContributionStatus['rank']:
|
||||
awards_given.append(['contribution_status', ContributionStatus['rank']])
|
||||
|
||||
for badgeNum in NEW_org_honor_badge:
|
||||
if badgeNum not in OLD_org_honor_badge:
|
||||
awards_given.append(['badge', badgeNum])
|
||||
|
||||
temp = {}
|
||||
for item in OLD_org_trophy:
|
||||
categ = item['categ']
|
||||
rank = item['trophy_true_rank']
|
||||
temp[categ] = rank
|
||||
|
||||
for item in NEW_org_trophy:
|
||||
categ = item['categ']
|
||||
rank = item['trophy_true_rank']
|
||||
if rank > temp[categ]:
|
||||
awards_given.append(['trophy', [categ, rank]])
|
||||
|
||||
for award in awards_given:
|
||||
# update awards given
|
||||
serv_redis_db.zadd('CONTRIB_LAST_AWARDS:'+util.getDateStrFormat(now), nowSec, json.dumps({'org': org, 'award': award, 'epoch': nowSec }))
|
||||
serv_redis_db.expire('CONTRIB_LAST_AWARDS:'+util.getDateStrFormat(now), ONE_DAY*7) #expire after 7 day
|
||||
# publish
|
||||
publish_log('GIVE_HONOR_ZMQ', 'CONTRIBUTION', {'org': org, 'award': award, 'epoch': nowSec }, CHANNEL_LASTAWARDS)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
|
67
server.py
|
@ -39,6 +39,9 @@ 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'))
|
||||
subscriber_lastAwards = redis_server_log.pubsub(ignore_subscribe_messages=True)
|
||||
subscriber_lastAwards.psubscribe(cfg.get('RedisLog', 'channelLastAwards'))
|
||||
|
||||
eventNumber = 0
|
||||
|
||||
##########
|
||||
|
@ -149,7 +152,7 @@ def geo():
|
|||
@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_str = [ s[0].upper() + s[1:].replace('_', ' ') for s in categ_list]
|
||||
categ_list_points = [contributor_helper.DICO_PNTS_REWARD[categ] for categ in categ_list]
|
||||
|
||||
org_rank = contributor_helper.org_rank
|
||||
|
@ -163,6 +166,15 @@ def contrib():
|
|||
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])
|
||||
|
||||
trophy_categ_list = contributor_helper.categories_in_trophy
|
||||
trophy_categ_list_str = [ s[0].upper() + s[1:].replace('_', ' ') for s in trophy_categ_list]
|
||||
trophy_title = contributor_helper.trophy_title
|
||||
trophy_title_str = []
|
||||
for i in range(contributor_helper.trophyNum+1):
|
||||
trophy_title_str.append(trophy_title[i])
|
||||
trophy_mapping = ["Top 1"] + [ str(x)+"%" for x in contributor_helper.trophyMapping] + [" "]
|
||||
trophy_mapping.reverse()
|
||||
|
||||
currOrg = request.args.get('org')
|
||||
if currOrg is None:
|
||||
currOrg = ""
|
||||
|
@ -179,9 +191,20 @@ def contrib():
|
|||
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,
|
||||
trophy_categ_list=json.dumps(trophy_categ_list),
|
||||
trophy_categ_list_id=trophy_categ_list,
|
||||
trophy_categ_list_str=trophy_categ_list_str,
|
||||
trophy_title=json.dumps(trophy_title),
|
||||
trophy_title_str=trophy_title_str,
|
||||
trophy_mapping=trophy_mapping,
|
||||
min_between_reload=cfg.getint('CONTRIB', 'min_between_reload')
|
||||
)
|
||||
|
||||
@app.route("/users")
|
||||
def users():
|
||||
return render_template('users.html',
|
||||
)
|
||||
|
||||
''' INDEX '''
|
||||
|
||||
@app.route("/_logs")
|
||||
|
@ -292,6 +315,10 @@ def getLastContributors():
|
|||
def getLastContributor():
|
||||
return Response(eventStreamLastContributor(), mimetype="text/event-stream")
|
||||
|
||||
@app.route("/_eventStreamAwards")
|
||||
def getLastStreamAwards():
|
||||
return Response(eventStreamAwards(), mimetype="text/event-stream")
|
||||
|
||||
def eventStreamLastContributor():
|
||||
for msg in subscriber_lastContrib.listen():
|
||||
content = msg['data'].decode('utf8')
|
||||
|
@ -303,6 +330,18 @@ def eventStreamLastContributor():
|
|||
to_return['epoch'] = epoch
|
||||
yield 'data: {}\n\n'.format(json.dumps(to_return))
|
||||
|
||||
def eventStreamAwards():
|
||||
for msg in subscriber_lastAwards.listen():
|
||||
content = msg['data'].decode('utf8')
|
||||
contentJson = json.loads(content)
|
||||
lastAwardJson = json.loads(contentJson['log'])
|
||||
org = lastAwardJson['org']
|
||||
to_return = contributor_helper.getContributorFromRedis(org)
|
||||
epoch = lastAwardJson['epoch']
|
||||
to_return['epoch'] = epoch
|
||||
to_return['award'] = lastAwardJson['award']
|
||||
yield 'data: {}\n\n'.format(json.dumps(to_return))
|
||||
|
||||
@app.route("/_getTopContributor")
|
||||
def getTopContributor(suppliedDate=None):
|
||||
if suppliedDate is None:
|
||||
|
@ -326,6 +365,15 @@ def getFameContributor():
|
|||
date = (datetime.datetime(today.year, today.month, 1) - datetime.timedelta(days=1))
|
||||
return getTopContributor(suppliedDate=date)
|
||||
|
||||
@app.route("/_getFameQualContributor")
|
||||
def getFameQualContributor():
|
||||
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():
|
||||
|
@ -348,6 +396,15 @@ def getCategPerContrib():
|
|||
|
||||
return jsonify(contributor_helper.getCategPerContribFromRedis(date))
|
||||
|
||||
@app.route("/_getLatestAwards")
|
||||
def getLatestAwards():
|
||||
try:
|
||||
date = datetime.datetime.fromtimestamp(float(request.args.get('date')))
|
||||
except:
|
||||
date = datetime.datetime.now()
|
||||
|
||||
return jsonify(contributor_helper.getLastAwardsFromRedis())
|
||||
|
||||
@app.route("/_getAllOrg")
|
||||
def getAllOrg():
|
||||
return jsonify(contributor_helper.getAllOrgFromRedis())
|
||||
|
@ -376,5 +433,13 @@ def getHonorBadges():
|
|||
org = ''
|
||||
return jsonify(contributor_helper.getOrgHonorBadges(org))
|
||||
|
||||
@app.route("/_getTrophies")
|
||||
def getTrophies():
|
||||
try:
|
||||
org = request.args.get('org')
|
||||
except:
|
||||
org = ''
|
||||
return jsonify(contributor_helper.getOrgTrophies(org))
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='localhost', port=8001, threaded=True)
|
||||
|
|
|
@ -16,8 +16,24 @@
|
|||
border: 1px solid #caccce;
|
||||
}
|
||||
|
||||
.circleBadgeSmall {
|
||||
width: 73px;
|
||||
height: 73px;
|
||||
text-align: center;
|
||||
border-radius: 38px;
|
||||
background-color: #e1e1e1;
|
||||
border: 1px solid #caccce;
|
||||
vertical-align: middle;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
|
||||
.circlBadgeNotAcquired { filter: grayscale(100%); }
|
||||
|
||||
.circlBadgeAcquired {
|
||||
box-shadow: 0px 0px 15px #0000ff
|
||||
/*box-shadow: 0px 0px 15px #0000ff*/
|
||||
box-shadow: 0px 0px 3px #1b6a92, 0px 0px 10px #2fa1db;
|
||||
}
|
||||
|
||||
.questionBadgeText {
|
||||
|
@ -59,7 +75,7 @@
|
|||
font-size: x-large;
|
||||
}
|
||||
|
||||
.popover-content {
|
||||
.popoverNoPadding {
|
||||
padding: 3px
|
||||
}
|
||||
|
||||
|
@ -79,6 +95,12 @@
|
|||
min-width: 45px;
|
||||
}
|
||||
|
||||
|
||||
#panelawards .dataTables_scrollBody {
|
||||
overflow-x:hidden !important;
|
||||
overflow-y:auto !important;
|
||||
}
|
||||
|
||||
.dataTables_filter > label {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
@ -123,6 +145,16 @@
|
|||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.col-lg-3 {
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.col-lg-9 {
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.col-lg-12 {
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
/* GLOB VAR */
|
||||
var allOrg = [];
|
||||
var datatableTop;
|
||||
var datatableFame;
|
||||
var datatableFameQuant;
|
||||
var refresh_speed = min_between_reload*60;
|
||||
var next_effect = new Date();
|
||||
var will_reload = $("#reloadCheckbox").is(':checked');
|
||||
var sec_before_reload = refresh_speed;
|
||||
var dataTop5Overtime;
|
||||
var plotLineChart
|
||||
var plotLineChart;
|
||||
var source_awards;
|
||||
var source_lastContrib;
|
||||
|
||||
/* CONFIG */
|
||||
var maxRank = 16;
|
||||
|
@ -14,7 +16,8 @@ var popOverOption = {
|
|||
trigger: "hover",
|
||||
html: true,
|
||||
placement: 'bottom',
|
||||
content: generateRankingSheet()
|
||||
content: generateRankingSheet(),
|
||||
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content popoverNoPadding"></div></div>'
|
||||
}
|
||||
var optionsLineChart = {
|
||||
series: {
|
||||
|
@ -65,6 +68,8 @@ var optionDatatable_light = {
|
|||
};
|
||||
var optionDatatable_top = jQuery.extend({}, optionDatatable_light)
|
||||
var optionDatatable_last = jQuery.extend({}, optionDatatable_light)
|
||||
optionDatatable_last["ordering"] = true;
|
||||
optionDatatable_last["order"] = [[ 0, "dec" ]];
|
||||
optionDatatable_last.columnDefs = [
|
||||
{ className: "small", "targets": [ 0 ] },
|
||||
{ className: "verticalAlign", "targets": [ 1 ] },
|
||||
|
@ -72,12 +77,7 @@ optionDatatable_last.columnDefs = [
|
|||
{ 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
|
||||
},
|
||||
{ className: "verticalAlign", "targets": [ 6 ] }
|
||||
]
|
||||
var optionDatatable_fame = jQuery.extend({}, optionDatatable_light)
|
||||
optionDatatable_fame.scrollY = '45vh';
|
||||
|
@ -86,7 +86,7 @@ var optionDatatable_Categ = {
|
|||
responsive: true,
|
||||
searching: true,
|
||||
"order": [[ 0, "desc" ]],
|
||||
scrollY: '38vh',
|
||||
scrollY: '35vh',
|
||||
"scrollX": true,
|
||||
scrollCollapse: true,
|
||||
paging: false,
|
||||
|
@ -97,6 +97,15 @@ var optionDatatable_Categ = {
|
|||
{ className: "centerCellPicOrgLogo", "targets": [ 4 ]}
|
||||
]
|
||||
};
|
||||
var optionDatatable_awards = jQuery.extend({}, optionDatatable_light);
|
||||
optionDatatable_awards["ordering"] = true;
|
||||
optionDatatable_awards["order"] = [[ 0, "dec" ]];
|
||||
optionDatatable_awards["scrollX"] = false;
|
||||
optionDatatable_awards["scrollY"] = "40vh";
|
||||
optionDatatable_awards.columnDefs = [
|
||||
{ className: "centerCellPicOrgLogo verticalAlign", "targets": [ 1 ] },
|
||||
{ className: "centerCellPicOrgLogo", "targets": [ 3 ] },
|
||||
];
|
||||
|
||||
var typeaheadOption = {
|
||||
source: function (query, process) {
|
||||
|
@ -170,11 +179,25 @@ function createImg(source, size) {
|
|||
return obj.outerHTML;
|
||||
}
|
||||
|
||||
function createTrophyImg(rank, size, categ) {
|
||||
var obj = document.createElement('img');
|
||||
obj.height = size;
|
||||
obj.width = size;
|
||||
obj.style.margin = 'auto';
|
||||
obj.src = url_baseTrophyLogo+rank+'.png';;
|
||||
obj.title = trophy_title[rank] + " in " + categ;
|
||||
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';
|
||||
if (!Array.isArray(array))
|
||||
array = [array];
|
||||
for (badgeNum of array) {
|
||||
var obj = document.createElement('img');
|
||||
obj.height = size;
|
||||
|
@ -313,8 +336,9 @@ function addLastFromJson(datatable, url) {
|
|||
|
||||
function addLastContributor(datatable, data, update) {
|
||||
var date = new Date(data.epoch*1000);
|
||||
date.toString = function() {return this.toTimeString().slice(0,-15) +' '+ this.toLocaleDateString(); };
|
||||
var to_add = [
|
||||
date.toTimeString().slice(0,-15) +' '+ date.toLocaleDateString(),
|
||||
date,
|
||||
data.pnts,
|
||||
getMonthlyRankIcon(data.rank),
|
||||
getOrgRankIcon(data.orgRank, 60),
|
||||
|
@ -324,15 +348,50 @@ function addLastContributor(datatable, data, update) {
|
|||
];
|
||||
if (update == undefined || update == false) {
|
||||
datatable.row.add(to_add);
|
||||
datatable.draw();
|
||||
} else if(update == true) {
|
||||
var row_added = false;
|
||||
datatable.rows().every( function() {
|
||||
if(this.data()[3] == data.org) {
|
||||
if($(this.data()[6])[0].text == data.org) {
|
||||
var node = $(datatable.row( this ).node());
|
||||
datatable.row( this ).data( to_add );
|
||||
if(next_effect <= new Date()) {
|
||||
node.effect("slide", 500);
|
||||
next_effect.setSeconds((new Date()).getSeconds() + 5);
|
||||
}
|
||||
row_added = true;
|
||||
}
|
||||
datatable.draw();
|
||||
});
|
||||
if (!row_added) {
|
||||
var node = $(datatable.row.add(to_add).draw().node());
|
||||
node.effect("slide", 700);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addAwards(datatableAwards, json, playAnim) {
|
||||
if(json.award[0] == 'contribution_status') {
|
||||
var award = getOrgRankIcon(json.award[1], 60);
|
||||
} else if (json.award[0] == 'badge') {
|
||||
var award = createHonorImg(json.award[1], 20);
|
||||
} else if (json.award[0] == 'trophy') {
|
||||
var categ = json.award[1][0];
|
||||
var award = createTrophyImg(json.award[1][1], 40, categ);
|
||||
}
|
||||
var date = new Date(json.epoch*1000);
|
||||
date.toString = function() {return this.toTimeString().slice(0,-15) +' '+ this.toLocaleDateString(); };
|
||||
var to_add = [
|
||||
date,
|
||||
createImg(json.logo_path, 32),
|
||||
createOrgLink(json.org),
|
||||
award,
|
||||
];
|
||||
var node = $(datatableAwards.row.add(to_add).draw().node());
|
||||
if(playAnim)
|
||||
node.effect("slide", 700);
|
||||
}
|
||||
|
||||
function updateProgressBar(org) {
|
||||
if(currOrg != org)
|
||||
return;
|
||||
|
@ -394,19 +453,23 @@ function updateProgressHeader(org) {
|
|||
// 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'); }
|
||||
var orgRowName = $(this.data()[5])[0].text;
|
||||
if(orgRowName == data.org) { row.classList.add('selectedOrgInTable'); } else { row.classList.remove('selectedOrgInTable'); }
|
||||
});
|
||||
datatableFame.rows().every( function() {
|
||||
datatableFameQuant.rows().every( function() {
|
||||
var row = this.node();
|
||||
if(this.data()[5] == data.org) { row.classList.add('selectedOrgInTable'); } else { row.classList.remove('selectedOrgInTable'); }
|
||||
var orgRowName = $(this.data()[5])[0].text;
|
||||
if(orgRowName == 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'); }
|
||||
var orgRowName = $(this.data()[5])[0].text;
|
||||
if(orgRowName == 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'); }
|
||||
var orgRowName = $(this.data()[6])[0].text;
|
||||
if(orgRowName == data.org) { row.classList.add('selectedOrgInTable'); } else { row.classList.remove('selectedOrgInTable'); }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -446,39 +509,43 @@ function updateProgressHeader(org) {
|
|||
$.getJSON( url_getHonorBadges+'?org='+org, function( data ) {
|
||||
for(var i=0; i<numberOfBadges; i++) { // remove
|
||||
$('#divBadge_'+(i+1)).removeClass('circlBadgeAcquired');
|
||||
$('#divBadge_'+(i+1)).addClass('circlBadgeNotAcquired');
|
||||
}
|
||||
for(var i=0; i<data.length; i++) { // add
|
||||
$('#divBadge_'+(i+1)).removeClass('circlBadgeNotAcquired');
|
||||
$('#divBadge_'+(data[i])).addClass('circlBadgeAcquired');
|
||||
}
|
||||
});
|
||||
|
||||
// set trophies if acquired
|
||||
$.getJSON( url_getTrophies+'?org='+org, function( data ) {
|
||||
var source = url_baseTrophyLogo+0+'.png'
|
||||
for(var i=0; i<trophy_categ_list.length; i++) { // remove
|
||||
categ = trophy_categ_list[i];
|
||||
$('#trophy_'+categ).attr('src', source);
|
||||
$('#trophy_'+categ).attr('title', "");
|
||||
try { // in case popover not created
|
||||
var pop = $('#trophy_'+categ).data('bs.popover');
|
||||
pop.destroy();
|
||||
} catch(err) {
|
||||
|
||||
}
|
||||
}
|
||||
setTimeout(function() { // avoid race condition with destroy
|
||||
for(var i=0; i<data.length; i++) { // add
|
||||
categ = data[i].categ;
|
||||
rank = data[i].trophy_true_rank;
|
||||
trophy_points = data[i].trophy_points
|
||||
source = url_baseTrophyLogo+rank+'.png'
|
||||
$('#trophy_'+categ).attr('src', source);
|
||||
$('#trophy_'+categ).attr('title', trophy_title[rank]);
|
||||
$('#trophy_'+categ).popover({title: trophy_title[rank], content: 'Level: '+rank+' ('+trophy_points+' points)', trigger: "hover", placement: "bottom"});
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
//update overtake points
|
||||
updateOvertakePnts();
|
||||
|
||||
//Add new data to linechart
|
||||
var flag_already_displayed = false;
|
||||
for(obj of dataTop5Overtime) { //check if already displayed
|
||||
if (obj.label == currOrg) {
|
||||
flag_already_displayed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!flag_already_displayed) {
|
||||
$.getJSON( url_getOrgOvertime+'?org='+org, function( data ) {
|
||||
var toPlot = dataTop5Overtime.slice(0); //cloning data
|
||||
// transform secs into date
|
||||
var new_data = [];
|
||||
for(list of data['data']) {
|
||||
new_data.push([list[0]*1000, list[1]]);
|
||||
}
|
||||
data['data'] = new_data;
|
||||
toPlot.push(data);
|
||||
|
||||
plotLineChart.setData(toPlot);
|
||||
plotLineChart.setupGrid();
|
||||
plotLineChart.draw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showOnlyOrg() {
|
||||
|
@ -497,7 +564,6 @@ function updateTimer() {
|
|||
if ($("#reloadCheckbox").is(':checked')) {
|
||||
sec_before_reload--;
|
||||
if (sec_before_reload < 1) {
|
||||
source_lastContrib.close();
|
||||
location.reload();
|
||||
} else {
|
||||
$('#labelRemainingTime').text(timeToString(sec_before_reload));
|
||||
|
@ -517,14 +583,23 @@ $(document).ready(function() {
|
|||
updateTimer();
|
||||
$('#orgName').typeahead(typeaheadOption);
|
||||
$('#btnCurrRank').popover(popOverOption);
|
||||
|
||||
$(window).on("beforeunload", function() {
|
||||
source_lastContrib.close();
|
||||
source_awards.close();
|
||||
});
|
||||
|
||||
datatableTop = $('#topContribTable').DataTable(optionDatatable_top);
|
||||
datatableFame = $('#fameTable').DataTable(optionDatatable_fame);
|
||||
datatableFameQuant = $('#fameTableQuantity').DataTable(optionDatatable_fame);
|
||||
datatableFameQual = $('#fameTableQuality').DataTable(optionDatatable_fame);
|
||||
datatableCateg = $('#categTable').DataTable(optionDatatable_Categ);
|
||||
datatableLast = $('#lastTable').DataTable(optionDatatable_last);
|
||||
datatableAwards = $('#awardTable').DataTable(optionDatatable_awards);
|
||||
// top contributors
|
||||
addToTableFromJson(datatableTop, url_getTopContributor);
|
||||
// hall of fame
|
||||
addToTableFromJson(datatableFame, url_getFameContributor);
|
||||
addToTableFromJson(datatableFameQuant, url_getFameContributor);
|
||||
addToTableFromJson(datatableFameQual, url_getFameQualContributor);
|
||||
// last contributors
|
||||
addLastFromJson(datatableLast, url_getLastContributor);
|
||||
// category per contributors
|
||||
|
@ -548,30 +623,30 @@ $(document).ready(function() {
|
|||
}
|
||||
datatableCateg.draw();
|
||||
});
|
||||
// top 5 contrib overtime
|
||||
$.getJSON( url_getTop5Overtime, function( data ) {
|
||||
// transform secs into date
|
||||
for(i in data){
|
||||
var new_data = [];
|
||||
for(list of data[i]['data']) {
|
||||
new_data.push([list[0]*1000, list[1]]);
|
||||
}
|
||||
data[i]['data'] = new_data;
|
||||
// latest awards
|
||||
$.getJSON( url_getLatestAwards, function( data ) {
|
||||
for (i in data) {
|
||||
addAwards(datatableAwards, data[i], false);
|
||||
}
|
||||
dataTop5Overtime = data;
|
||||
plotLineChart = $.plot("#divTop5Overtime", data, optionsLineChart);
|
||||
});
|
||||
|
||||
if(currOrg != "") // currOrg selected
|
||||
//FIXME: timeout used to wait that all datatables are draw.
|
||||
setTimeout( function() { updateProgressHeader(currOrg); }, 500);
|
||||
setTimeout( function() { updateProgressHeader(currOrg); }, 700);
|
||||
|
||||
source_lastContrib = new EventSource(url_eventStreamLastContributor);
|
||||
source_lastContrib.onmessage = function(event) {
|
||||
var json = jQuery.parseJSON( event.data );
|
||||
addLastContributor(datatableLast, json, true);
|
||||
datatableLast.draw();
|
||||
updateProgressBar(json.org);
|
||||
updateOvertakePnts();
|
||||
sec_before_reload = refresh_speed; //reset timer at each contribution
|
||||
};
|
||||
|
||||
source_awards = new EventSource(url_eventStreamAwards);
|
||||
source_awards.onmessage = function(event) {
|
||||
var json = jQuery.parseJSON( event.data );
|
||||
addAwards(datatableAwards, json, true);
|
||||
updateProgressHeader(currOrg);
|
||||
};
|
||||
});
|
||||
|
|
After Width: | Height: | Size: 242 B |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 27 KiB |
|
@ -46,8 +46,8 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Modal -->
|
||||
<div id="myModal" class="modal fade" role="dialog">
|
||||
<!-- Modal Rank -->
|
||||
<div id="myModalRank" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-lg" style="width: 1500px;">
|
||||
|
||||
<!-- Modal content-->
|
||||
|
@ -136,22 +136,6 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<table>
|
||||
<tbody>
|
||||
{% for item in org_honor_badge_title_list %}
|
||||
<tr style="height: 85px">
|
||||
<td>
|
||||
<div id="divBadge_{{ loop.index }}" class="circleBadge">
|
||||
<img height='64px' width='64px' style="margin-top: 5px;" src="{{ url_for('static', filename='pics/MISPHonorableIcons/1.svg')[:-5]}}{{ item[0] }}.svg" type='image/svg' style="margin: auto;"</img>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding-left: 15px;">{{ item[1] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -159,10 +143,101 @@
|
|||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal trophy -->
|
||||
<div id="myModalTrophy" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-lg" style="width: 1500px;">
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" style="float: left;">Trophies and badges</h3>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h4>Badges:</h4>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Badge</th>
|
||||
<th>Requirement</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id='bodyTableBadgeModal'>
|
||||
{% for item in org_honor_badge_title_list %}
|
||||
<tr>
|
||||
<td>
|
||||
<div id="divBadge_{{ loop.index }}" class="circleBadgeSmall circlBadgeNotAcquired">
|
||||
<img height='64px' width='64px' class="" style="margin-top: 3px;" src="{{ url_for('static', filename='pics/MISPHonorableIcons/1.svg')[:-5]}}{{ item[0] }}.svg" type='image/svg' style="margin: auto;"</img>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding-left: 15px;">{{ item[1] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p style="font-size: 18px; display: inline;">Trophies: </p><p style="display: inline;">Shows your skills in information sharing </p><i> (earned via upvotes or sightings from other organisation)</i>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for title in trophy_title_str %}
|
||||
<th>{{ title }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id='bodyTableTrophyModal'>
|
||||
<tr>
|
||||
{% for perc in trophy_mapping %}
|
||||
<td>{{ perc }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<tr>
|
||||
{% for title in trophy_title_str %}
|
||||
<td>
|
||||
<input type='image' style="display: block; margin-left: auto; margin-right: auto;" height="64" width="64" src="{{ url_for('static', filename='pics/MISPTrophy/'+loop.index0|string+'.png') }}">
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p style="font-size: 18px; display: inline;">Acquired trophies: </p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for categ in trophy_categ_list_str %}
|
||||
<th>{{ categ }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id='bodyTableTrophyModal'>
|
||||
<tr>
|
||||
{% for categ in trophy_categ_list_id %}
|
||||
<td>
|
||||
<input type='image' id='trophy_{{categ}}' style="display: block; margin-left: auto; margin-right: auto;" height="64" width="64" src="{{ url_for('static', filename='pics/MISPTrophy/0.png') }}">
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="wrapper">
|
||||
|
||||
<!-- Navigation -->
|
||||
|
@ -190,7 +265,8 @@
|
|||
<div class='textTopHeader' style="padding-top: 9px;">
|
||||
<input type="text" id="orgName" data-provide="typeahead" size="20" style="margin-bottom: 5px;">
|
||||
</div>
|
||||
<button type="button" class="questionBadgeDiv fa fa-question-circle" data-toggle="modal" data-target="#myModal"></button>
|
||||
<button type="button" class="questionBadgeDiv fa fa-question-circle" title="Ranking information" data-toggle="modal" data-target="#myModalRank"></button>
|
||||
<button type="button" class="questionBadgeDiv fa fa-trophy" title="Trophies and badges" data-toggle="modal" data-target="#myModalTrophy"></button>
|
||||
<button id="btnCurrRank" class='btn btn-default popOverBtn' data-container='body' data-toggle='popover' style="display: none; margin-left: 20px;" onclick="showOnlyOrg()">
|
||||
<object id='orgContributionRank' height=32 width=64 class="centerInBtn"></object>
|
||||
<strong id="orgText" class="centerInBtn"></strong>
|
||||
|
@ -204,7 +280,7 @@
|
|||
<div id="orgNextRankDiv" class='textTopHeader' style="padding-top: 0px; position: relative; width: 40px; height: 40px;"></div>
|
||||
</button>
|
||||
<div class='leftSepa textTopHeader'>
|
||||
<span class="label label-primary">
|
||||
<span class="label label-primary" style="padding: .5em;">
|
||||
<strong style="">Points to overtake</strong>
|
||||
<strong style="font-size: medium;" id='orgToOverTake'></strong>
|
||||
<strong style="">: </strong>
|
||||
|
@ -222,14 +298,14 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div style="height: 10px;"></div>
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-9">
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="panel panel-default" style="height: 100%;">
|
||||
<div class="panel-heading bg-info" style="font-weight: bold;">
|
||||
<i class="fa fa-asterisk " style="margin-right: 5px; color: #f0ad4e;"></i><b>Contributor ranking (monthly)</b>
|
||||
</div>
|
||||
<div id="panelRanking" class="panel-body" style="height: 100%;">
|
||||
<div class="panel-body" style="height: 100%;">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="topContribTable" class="table table-hover table-striped">
|
||||
|
@ -283,11 +359,12 @@
|
|||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="col-lg-6">
|
||||
<div class="panel panel-default" style="height: 100%;">
|
||||
<div class="panel-heading bg-info" style="font-weight: bold;">
|
||||
<i class="fa fa-th-list " style="margin-right: 5px; color: #f0ad4e;"></i><b>Contributors and categories (total)</b>
|
||||
</div>
|
||||
<div id="panelRanking" class="panel-body" style="height: 100%;">
|
||||
<div class="panel-body" style="height: 100%;">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="categTable" class="table table-hover table-striped">
|
||||
|
@ -314,20 +391,47 @@
|
|||
</div><!-- /.panel -->
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="panel panel-default" style="height: 100%;">
|
||||
<div class="panel-heading bg-info" style="font-weight: bold;">
|
||||
<i class="fa fa-trophy" style="margin-right: 5px; color: #d9534f;"></i><b>Latest awards</b>
|
||||
</div>
|
||||
<div id="panelawards" class="panel-body" style="height: 100%;">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="awardTable" class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th></th>
|
||||
<th>Org.</th>
|
||||
<th>Awards</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.panel-body -->
|
||||
</div><!-- /.panel -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div><!-- /.col-lg-8 -->
|
||||
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="col-lg-3">
|
||||
|
||||
<div class="panel panel-default" style="height: 100%;">
|
||||
<div class="panel-heading bg-info" style="font-weight: bold;">
|
||||
<i class="fa fa-star" style="margin-right: 5px; color: #f0ad4e;"></i><b>Hall Of Fame (previous month)</b>
|
||||
</div>
|
||||
<div id="panelRanking" class="panel-body" style="height: 100%;">
|
||||
<div class="panel-body" style="height: 100%;">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="fameTable" class="table table-hover table-striped" style="margin-bottom: 0px;">
|
||||
<table id="fameTableQuantity" class="table table-hover table-striped" style="margin-bottom: 0px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Points</th>
|
||||
|
@ -338,7 +442,7 @@
|
|||
<th>Organisation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id=fameTableBody>
|
||||
<tbody id=fameTableQuantityBody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -349,13 +453,29 @@
|
|||
|
||||
<div class="panel panel-default" style="height: 100%;">
|
||||
<div class="panel-heading bg-info" style="font-weight: bold;">
|
||||
<i class="fa fa-line-chart" style="margin-right: 5px; color: #f0ad4e;"></i><b>Top 5 Contributor overtime</b>
|
||||
<i class="fa fa-star" style="margin-right: 5px; color: #f0ad4e;"></i><b>Hall Of Fame - Quality (previous month)</b>
|
||||
</div>
|
||||
<div id="panelRanking" class="panel-body" style="height: 100%;">
|
||||
<div id="divTop5Overtime" style="height: 32vh"></div>
|
||||
<div class="panel-body" style="height: 100%;">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="fameTableQuality" class="table table-hover table-striped" style="margin-bottom: 0px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Points</th>
|
||||
<th>Prev. rank</th>
|
||||
<th>Org. rank</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>Organisation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id=fameTableQualityBody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.panel-body -->
|
||||
|
||||
</div><!-- /.panel -->
|
||||
|
||||
</div><!-- /.col-lg-4 -->
|
||||
|
@ -375,21 +495,28 @@
|
|||
/* URL */
|
||||
var url_getTopContributor = "{{ url_for('getTopContributor') }}";
|
||||
var url_getFameContributor = "{{ url_for('getFameContributor') }}";
|
||||
var url_getFameQualContributor = "{{ url_for('getFameQualContributor') }}";
|
||||
var url_getCategPerContrib = "{{ url_for('getCategPerContrib') }}";
|
||||
var url_getTop5Overtime = "{{ url_for('getTop5Overtime') }}";
|
||||
var url_getOrgOvertime = "{{ url_for('getOrgOvertime') }}";
|
||||
var url_getLastContributor = "{{ url_for('getLastContributors') }}";
|
||||
var url_getLatestAwards = "{{ url_for('getLatestAwards') }}";
|
||||
var url_eventStreamLastContributor = "{{ url_for('getLastContributor') }}";
|
||||
var url_eventStreamAwards = "{{ url_for('getLastStreamAwards') }}";
|
||||
var url_getAllOrg = "{{ url_for('getAllOrg') }}";
|
||||
var url_getOrgRank = "{{ url_for('getOrgRank') }}";
|
||||
var url_getContributionOrgStatus = "{{ url_for('getContributionOrgStatus') }}";
|
||||
var url_getHonorBadges = "{{ url_for('getHonorBadges') }}";
|
||||
var url_getTrophies = "{{ url_for('getTrophies')}}"
|
||||
|
||||
var url_baseRankMonthlyLogo = "{{ url_for('static', filename='pics/rankingMISPMonthly/1.svg') }}";
|
||||
url_baseRankMonthlyLogo = url_baseRankMonthlyLogo.substring(0, url_baseRankMonthlyLogo.length-5);
|
||||
var url_baseOrgRankLogo = "{{ url_for('static', filename='pics/rankingMISPOrg/1.svg') }}";
|
||||
url_baseOrgRankLogo = url_baseOrgRankLogo.substring(0, url_baseOrgRankLogo.length-5);
|
||||
var url_baseHonorLogo = "{{ url_for('static', filename='pics/MISPHonorableIcons/1.svg') }}";
|
||||
url_baseHonorLogo = url_baseHonorLogo.substring(0, url_baseHonorLogo.length-5);
|
||||
var url_baseTrophyLogo = "{{ url_for('static', filename='pics/MISPTrophy/1.png') }}";
|
||||
url_baseTrophyLogo = url_baseTrophyLogo.substring(0, url_baseTrophyLogo.length-5);
|
||||
|
||||
/* DATA FROM CONF */
|
||||
var currOrg = "{{ currOrg }}";
|
||||
|
@ -399,6 +526,8 @@
|
|||
var org_rank_obj = JSON.parse('{{ org_rank_json|safe }}');
|
||||
var org_honor_badge_title = JSON.parse('{{ org_honor_badge_title|safe }}');
|
||||
var numberOfBadges = {{ org_honor_badge_title_list|length }};
|
||||
var trophy_categ_list = JSON.parse('{{ trophy_categ_list|safe }}');
|
||||
var trophy_title = JSON.parse('{{ trophy_title|safe }}');
|
||||
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/contrib.js') }}"></script>
|
||||
|
|
|
@ -25,6 +25,7 @@ ONE_DAY = 60*60*24
|
|||
ZMQ_URL = cfg.get('RedisGlobal', 'zmq_url')
|
||||
CHANNEL = cfg.get('RedisLog', 'channel')
|
||||
CHANNEL_LASTCONTRIB = cfg.get('RedisLog', 'channelLastContributor')
|
||||
CHANNEL_LASTAWARDS = cfg.get('RedisLog', 'channelLastAwards')
|
||||
CHANNELDISP = cfg.get('RedisMap', 'channelDisp')
|
||||
CHANNEL_PROC = cfg.get('RedisMap', 'channelProc')
|
||||
PATH_TO_DB = cfg.get('RedisMap', 'pathMaxMindDB')
|
||||
|
@ -151,14 +152,22 @@ def handleContribution(zmq_name, org, contribType, categ, action, pntMultiplier=
|
|||
#CONTRIB_CATEG retain the contribution per category, not the point earned in this categ
|
||||
push_to_redis_zset('CONTRIB_CATEG', org, count=1, endSubkey=':'+noSpaceLower(categ))
|
||||
publish_log(zmq_name, 'CONTRIBUTION', {'org': org, 'categ': categ, 'action': action, 'epoch': nowSec }, channel=CHANNEL_LASTCONTRIB)
|
||||
else:
|
||||
categ = ""
|
||||
|
||||
serv_redis_db.sadd('CONTRIB_ALL_ORG', org)
|
||||
|
||||
serv_redis_db.zadd('CONTRIB_LAST:'+util.getDateStrFormat(now), nowSec, org)
|
||||
serv_redis_db.expire('CONTRIB_LAST:'+util.getDateStrFormat(now), ONE_DAY) #expire after 1 day
|
||||
serv_redis_db.expire('CONTRIB_LAST:'+util.getDateStrFormat(now), ONE_DAY*7) #expire after 7 day
|
||||
|
||||
contributor_helper.updateOrgContributionRank(org, pnts_to_add, action, contribType, eventTime=datetime.datetime.now(), isLabeled=isLabeled)
|
||||
awards_given = contributor_helper.updateOrgContributionRank(org, pnts_to_add, action, contribType, eventTime=datetime.datetime.now(), isLabeled=isLabeled, categ=noSpaceLower(categ))
|
||||
|
||||
for award in awards_given:
|
||||
# update awards given
|
||||
serv_redis_db.zadd('CONTRIB_LAST_AWARDS:'+util.getDateStrFormat(now), nowSec, json.dumps({'org': org, 'award': award, 'epoch': nowSec }))
|
||||
serv_redis_db.expire('CONTRIB_LAST_AWARDS:'+util.getDateStrFormat(now), ONE_DAY*7) #expire after 7 day
|
||||
# publish
|
||||
publish_log(zmq_name, 'CONTRIBUTION', {'org': org, 'award': award, 'epoch': nowSec }, channel=CHANNEL_LASTAWARDS)
|
||||
|
||||
|
||||
##############
|
||||
|
|