mirror of https://github.com/JohnHammond/CTFd.git
Getting awards feature ready
parent
b46b0c560d
commit
f4a2f165a2
|
@ -1,6 +1,6 @@
|
||||||
from flask import render_template, request, redirect, abort, jsonify, url_for, session, Blueprint
|
from flask import render_template, request, redirect, abort, jsonify, url_for, session, Blueprint
|
||||||
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, sendmail, rmdir
|
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, sendmail, rmdir
|
||||||
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
from itsdangerous import TimedSerializer, BadTimeSignature
|
from itsdangerous import TimedSerializer, BadTimeSignature
|
||||||
from sqlalchemy.sql import and_, or_, not_
|
from sqlalchemy.sql import and_, or_, not_
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
@ -380,11 +380,12 @@ def admin_team(teamid):
|
||||||
solve_ids = [s.chalid for s in solves]
|
solve_ids = [s.chalid for s in solves]
|
||||||
missing = Challenges.query.filter( not_(Challenges.id.in_(solve_ids) ) ).all()
|
missing = Challenges.query.filter( not_(Challenges.id.in_(solve_ids) ) ).all()
|
||||||
addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all()
|
addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all()
|
||||||
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.desc()).all()
|
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.asc()).all()
|
||||||
|
awards = Awards.query.filter_by(teamid=teamid).order_by(Awards.date.asc()).all()
|
||||||
score = user.score()
|
score = user.score()
|
||||||
place = user.place()
|
place = user.place()
|
||||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
||||||
place=place, wrong_keys=wrong_keys)
|
place=place, wrong_keys=wrong_keys, awards=awards)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
admin_user = request.form.get('admin', None)
|
admin_user = request.form.get('admin', None)
|
||||||
if admin_user:
|
if admin_user:
|
||||||
|
@ -498,6 +499,57 @@ def admin_scoreboard():
|
||||||
return render_template('admin/scoreboard.html', teams=teams)
|
return render_template('admin/scoreboard.html', teams=teams)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.route('/admin/teams/<teamid>/awards', methods=['GET'])
|
||||||
|
@admins_only
|
||||||
|
def admin_awards(teamid):
|
||||||
|
awards = Awards.query.filter_by(teamid=teamid).all()
|
||||||
|
|
||||||
|
awards_list = []
|
||||||
|
for award in awards:
|
||||||
|
awards_list.append({
|
||||||
|
'id':award.id,
|
||||||
|
'name':award.name,
|
||||||
|
'description':award.description,
|
||||||
|
'date':award.date,
|
||||||
|
'value':award.value,
|
||||||
|
'category':award.category,
|
||||||
|
'icon':award.icon
|
||||||
|
})
|
||||||
|
json_data = {'awards':awards_list}
|
||||||
|
return jsonify(json_data)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.route('/admin/awards/add', methods=['POST'])
|
||||||
|
@admins_only
|
||||||
|
def create_award():
|
||||||
|
try:
|
||||||
|
teamid = request.form['teamid']
|
||||||
|
name = request.form.get('name', 'Award')
|
||||||
|
value = request.form.get('value', 0)
|
||||||
|
award = Awards(teamid, name, value)
|
||||||
|
award.description = request.form.get('description')
|
||||||
|
award.category = request.form.get('category')
|
||||||
|
db.session.add(award)
|
||||||
|
db.session.commit()
|
||||||
|
return "1"
|
||||||
|
except Exception as e:
|
||||||
|
print e
|
||||||
|
return "0"
|
||||||
|
|
||||||
|
|
||||||
|
@admin.route('/admin/awards/<award_id>/delete', methods=['POST'])
|
||||||
|
@admins_only
|
||||||
|
def delete_award(award_id):
|
||||||
|
try:
|
||||||
|
award = Awards.query.filter_by(id=award_id).first()
|
||||||
|
db.session.delete(award)
|
||||||
|
db.session.commit()
|
||||||
|
return '1'
|
||||||
|
except Exception as e:
|
||||||
|
print e
|
||||||
|
return "0"
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/admin/scores')
|
@admin.route('/admin/scores')
|
||||||
@admins_only
|
@admins_only
|
||||||
def admin_scores():
|
def admin_scores():
|
||||||
|
|
|
@ -12,6 +12,7 @@ with open('.ctfd_secret_key', 'a+') as secret:
|
||||||
##### SERVER SETTINGS #####
|
##### SERVER SETTINGS #####
|
||||||
SECRET_KEY = key
|
SECRET_KEY = key
|
||||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db'
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db'
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
SESSION_TYPE = "filesystem"
|
SESSION_TYPE = "filesystem"
|
||||||
SESSION_FILE_DIR = "/tmp/flask_session"
|
SESSION_FILE_DIR = "/tmp/flask_session"
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
|
|
|
@ -142,8 +142,10 @@ class Teams(db.Model):
|
||||||
def score(self):
|
def score(self):
|
||||||
score = db.func.sum(Challenges.value).label('score')
|
score = db.func.sum(Challenges.value).label('score')
|
||||||
team = db.session.query(Solves.teamid, score).join(Teams).join(Challenges).filter(Teams.banned == None, Teams.id==self.id).group_by(Solves.teamid).first()
|
team = db.session.query(Solves.teamid, score).join(Teams).join(Challenges).filter(Teams.banned == None, Teams.id==self.id).group_by(Solves.teamid).first()
|
||||||
|
award_score = db.func.sum(Awards.value).label('award_score')
|
||||||
|
award = db.session.query(award_score).filter_by(teamid=self.id).first()
|
||||||
if team:
|
if team:
|
||||||
return team.score
|
return int(team.score or 0) + int(award.award_score or 0)
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,3 @@
|
||||||
|
|
||||||
//http://stackoverflow.com/a/2648463 - wizardry!
|
|
||||||
String.prototype.format = String.prototype.f = function() {
|
|
||||||
var s = this,
|
|
||||||
i = arguments.length;
|
|
||||||
|
|
||||||
while (i--) {
|
|
||||||
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
|
|
||||||
function htmlentities(string) {
|
|
||||||
return $('<div/>').text(string).html();
|
|
||||||
}
|
|
||||||
|
|
||||||
//http://stackoverflow.com/a/7616484
|
|
||||||
String.prototype.hashCode = function() {
|
|
||||||
var hash = 0, i, chr, len;
|
|
||||||
if (this.length == 0) return hash;
|
|
||||||
for (i = 0, len = this.length; i < len; i++) {
|
|
||||||
chr = this.charCodeAt(i);
|
|
||||||
hash = ((hash << 5) - hash) + chr;
|
|
||||||
hash |= 0; // Convert to 32bit integer
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
};
|
|
||||||
|
|
||||||
var challenges;
|
var challenges;
|
||||||
|
|
||||||
function loadchal(id) {
|
function loadchal(id) {
|
||||||
|
|
|
@ -1,28 +1,3 @@
|
||||||
//http://stackoverflow.com/a/2648463 - wizardry!
|
|
||||||
String.prototype.format = String.prototype.f = function() {
|
|
||||||
var s = this,
|
|
||||||
i = arguments.length;
|
|
||||||
|
|
||||||
while (i--) {
|
|
||||||
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
|
|
||||||
function colorhash (x) {
|
|
||||||
color = ""
|
|
||||||
for (var i = 20; i <= 60; i+=20){
|
|
||||||
x += i
|
|
||||||
x *= i
|
|
||||||
color += x.toString(16)
|
|
||||||
};
|
|
||||||
return "#" + color.substring(0, 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
function htmlentities(string) {
|
|
||||||
return $('<div/>').text(string).html();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatescores () {
|
function updatescores () {
|
||||||
$.get('/scores', function( data ) {
|
$.get('/scores', function( data ) {
|
||||||
teams = $.parseJSON(JSON.stringify(data));
|
teams = $.parseJSON(JSON.stringify(data));
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
//http://stackoverflow.com/a/1186309
|
||||||
|
$.fn.serializeObject = function()
|
||||||
|
{
|
||||||
|
var o = {};
|
||||||
|
var a = this.serializeArray();
|
||||||
|
$.each(a, function() {
|
||||||
|
if (o[this.name] !== undefined) {
|
||||||
|
if (!o[this.name].push) {
|
||||||
|
o[this.name] = [o[this.name]];
|
||||||
|
}
|
||||||
|
o[this.name].push(this.value || '');
|
||||||
|
} else {
|
||||||
|
o[this.name] = this.value || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return o;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//http://stackoverflow.com/a/2648463 - wizardry!
|
||||||
|
String.prototype.format = String.prototype.f = function() {
|
||||||
|
var s = this,
|
||||||
|
i = arguments.length;
|
||||||
|
|
||||||
|
while (i--) {
|
||||||
|
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
//http://stackoverflow.com/a/7616484
|
||||||
|
String.prototype.hashCode = function() {
|
||||||
|
var hash = 0, i, chr, len;
|
||||||
|
if (this.length == 0) return hash;
|
||||||
|
for (i = 0, len = this.length; i < len; i++) {
|
||||||
|
chr = this.charCodeAt(i);
|
||||||
|
hash = ((hash << 5) - hash) + chr;
|
||||||
|
hash |= 0; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
function colorhash (x) {
|
||||||
|
color = ""
|
||||||
|
for (var i = 20; i <= 60; i+=20){
|
||||||
|
x += i
|
||||||
|
x *= i
|
||||||
|
color += x.toString(16)
|
||||||
|
};
|
||||||
|
return "#" + color.substring(0, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
function htmlentities(string) {
|
||||||
|
return $('<div/>').text(string).html();
|
||||||
|
}
|
|
@ -27,7 +27,7 @@
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a href="/" class="navbar-brand">CTF</a>
|
<a href="/" class="navbar-brand">CTFd</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-collapse collapse" aria-expanded="false" style="height: 0px">
|
<div class="navbar-collapse collapse" aria-expanded="false" style="height: 0px">
|
||||||
<ul class="nav navbar-nav navbar-nav-right">
|
<ul class="nav navbar-nav navbar-nav-right">
|
||||||
|
|
|
@ -297,6 +297,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
<script src="/static/js/utils.js"></script>
|
||||||
<script src="/static/admin/js/multi-modal.js"></script>
|
<script src="/static/admin/js/multi-modal.js"></script>
|
||||||
<script src="/static/admin/js/chalboard.js"></script>
|
<script src="/static/admin/js/chalboard.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
<script src="/static/js/utils.js"></script>
|
||||||
<script src="/static/admin/js/team.js"></script>
|
<script src="/static/admin/js/team.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$('#delete-solve').click(function(e){
|
$('#delete-solve').click(function(e){
|
||||||
|
|
|
@ -13,7 +13,48 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
|
||||||
|
<div class="modal fade" id="create-award-modal" tabindex="-1" role="dialog" aria-labelledby="create-award-label">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="create-award-label">Create Award</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="award-create-form" method="POST" action="/admin/awards/add">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="award-name-input">Name</label>
|
||||||
|
<input type="text" class="form-control" id="award-name-input" name="name" placeholder="Enter award name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="award-value-input">Value</label>
|
||||||
|
<input type="number" class="form-control" id="award-value-input" name="value" placeholder="Enter award value">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="award-category-input">Category</label>
|
||||||
|
<input type="text" class="form-control" id="award-category-input" name="category" placeholder="Enter award category">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="award-description-input" class="control-label">Description</label>
|
||||||
|
<textarea id="award-description-input" class="form-control" name="description" rows="10" placeholder="Description of what the award is for"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="award-icon-input">Award Icon</label>
|
||||||
|
<input type="file" id="award-icon-input" name="icon">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" value="{{ team.id }}" name="teamid" id="teamid">
|
||||||
|
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" id="award-create-button" class="btn btn-primary">Create</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row container-fluid">
|
||||||
<div id="confirm" class="modal fade" tabindex="-1">
|
<div id="confirm" class="modal fade" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -70,6 +111,40 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<h3>Awards</h3>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><b>Name</b></td>
|
||||||
|
<td class="text-center"><b>Description</b></td>
|
||||||
|
<td class="text-center"><b>Date</b></td>
|
||||||
|
<td class="text-center"><b>Value</b></td>
|
||||||
|
<td class="text-center"><b>Category</b></td>
|
||||||
|
<td class="text-center"><b>Icon</b></td>
|
||||||
|
<td class="text-center"><b>Delete</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="awards-body">
|
||||||
|
{% for award in awards %}
|
||||||
|
<tr class="award-row">
|
||||||
|
<td class="text-center chal" id="{{ award.id }}">{{ award.name }}</td>
|
||||||
|
<td class="text-center">{{ award.description }}</td>
|
||||||
|
<td class="text-center solve-time"><script>document.write( moment({{ award.date|unix_time_millis }}).local().format('MMMM Do, h:mm:ss A'))</script></td>
|
||||||
|
<td class="text-center">{{ award.value }}</td>
|
||||||
|
<td class="text-center">{{ award.category }}</td>
|
||||||
|
<td class="text-center">{{ award.icon }}</td>
|
||||||
|
|
||||||
|
<td class="text-center"><i class="fa fa-times"></i></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row container-fluid">
|
||||||
|
<button type="button" data-toggle="modal" data-target="#create-award-modal" class="btn btn-primary pull-right">Create Award</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<h3>Solves</h3>
|
<h3>Solves</h3>
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -96,7 +171,6 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<h3>Missing</h3>
|
<h3>Missing</h3>
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -149,6 +223,7 @@
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="/static/js/vendor/moment.min.js"></script>
|
<script src="/static/js/vendor/moment.min.js"></script>
|
||||||
<script src="/static/js/vendor/plotly.min.js"></script>
|
<script src="/static/js/vendor/plotly.min.js"></script>
|
||||||
|
<script src="/static/js/utils.js"></script>
|
||||||
<script src="/static/admin/js/team.js"></script>
|
<script src="/static/admin/js/team.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$('#delete-solve').click(function (e) {
|
$('#delete-solve').click(function (e) {
|
||||||
|
@ -196,7 +271,7 @@
|
||||||
description = description.html()
|
description = description.html()
|
||||||
|
|
||||||
var action = '/admin/solves/' + team + '/' + chal + '/delete';
|
var action = '/admin/solves/' + team + '/' + chal + '/delete';
|
||||||
} else {
|
} else if (type == 'chal-wrong') {
|
||||||
var title = 'Delete Wrong Key';
|
var title = 'Delete Wrong Key';
|
||||||
var description = "<span>Are you sure you want to delete " +
|
var description = "<span>Are you sure you want to delete " +
|
||||||
"<strong>incorrect</strong> " +
|
"<strong>incorrect</strong> " +
|
||||||
|
@ -211,6 +286,11 @@
|
||||||
description = description.html()
|
description = description.html()
|
||||||
|
|
||||||
var action = '/admin/wrong_keys/' + team + '/' + chal + '/delete';
|
var action = '/admin/wrong_keys/' + team + '/' + chal + '/delete';
|
||||||
|
} else if (type == 'award-row') {
|
||||||
|
var title = 'Delete Award';
|
||||||
|
var description = "<span>Are you sure you want to delete the " +
|
||||||
|
"<strong>{0}</strong> award?</span>".format(chal_name);
|
||||||
|
var action = '/admin/awards/{0}/delete'.format(chal);
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = {
|
var msg = {
|
||||||
|
@ -253,5 +333,24 @@
|
||||||
|
|
||||||
load_confirm_modal(msg);
|
load_confirm_modal(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#award-create-form').submit(function(e){
|
||||||
|
$.post($(this).attr('action'), $(this).serialize(), function(res){
|
||||||
|
if (res == '1'){
|
||||||
|
var award = $('#award-create-form').serializeObject();
|
||||||
|
var award_text = '<td class="text-center">{0}</td>'.format(award.name) +
|
||||||
|
'<td class="text-center">{0}</td>'.format(award.description) +
|
||||||
|
'<td class="text-center solve-time">{0}</td>'.format(moment().local().format('MMMM Do, h:mm:ss A')) +
|
||||||
|
'<td class="text-center">{0}</td>'.format(award.value) +
|
||||||
|
'<td class="text-center">{0}</td>'.format(award.category) +
|
||||||
|
'<td class="text-center">{0}</td>'.format('None') +
|
||||||
|
'<td class="text-center"><i class="fa fa-times"></i></td>'
|
||||||
|
$('#awards-body').append(award_text);
|
||||||
|
$('#create-award-modal').modal('hide');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
<script src="/static/js/utils.js"></script>
|
||||||
<script src="/static/admin/js/team.js"></script>
|
<script src="/static/admin/js/team.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$('#delete-solve').click(function (e) {
|
$('#delete-solve').click(function (e) {
|
||||||
|
|
|
@ -154,6 +154,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
<script src="/static/js/utils.js"></script>
|
||||||
<script src="/static/js/chalboard.js"></script>
|
<script src="/static/js/chalboard.js"></script>
|
||||||
<script src="/static/js/style.js"></script>
|
<script src="/static/js/style.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -32,5 +32,6 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="/static/js/vendor/plotly.min.js"></script>
|
<script src="/static/js/vendor/plotly.min.js"></script>
|
||||||
|
<script src="/static/js/utils.js"></script>
|
||||||
<script src="/static/js/scoreboard.js"></script>
|
<script src="/static/js/scoreboard.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -61,5 +61,6 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="/static/js/vendor/plotly.min.js"></script>
|
<script src="/static/js/vendor/plotly.min.js"></script>
|
||||||
|
<script src="/static/js/utils.js"></script>
|
||||||
<script src="/static/js/team.js"></script>
|
<script src="/static/js/team.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue