diff --git a/CTFd/admin.py b/CTFd/admin.py index 279f1fb..2725e1c 100644 --- a/CTFd/admin.py +++ b/CTFd/admin.py @@ -4,6 +4,7 @@ from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, Wrong from itsdangerous import TimedSerializer, BadTimeSignature from sqlalchemy.sql import and_, or_, not_ from sqlalchemy.sql.expression import union_all +from sqlalchemy.sql.functions import coalesce from werkzeug.utils import secure_filename from socket import inet_aton, inet_ntoa from passlib.hash import bcrypt_sha256 @@ -18,6 +19,8 @@ import json import datetime import calendar +from scoreboard import get_standings + admin = Blueprint('admin', __name__) @@ -276,12 +279,12 @@ def delete_container(container_id): def new_container(): name = request.form.get('name') if set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'): - return redirect('/admin/containers') + return redirect(url_for('admin.list_container')) buildfile = request.form.get('buildfile') files = request.files.getlist('files[]') create_image(name=name, buildfile=buildfile, files=files) run_image(name) - return redirect('/admin/containers') + return redirect(url_for('admin.list_container')) @@ -291,7 +294,7 @@ def admin_chals(): if request.method == 'POST': chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category', 'hidden').order_by(Challenges.value).all() - teams_with_points = db.session.query(Solves.teamid, Teams.name).join(Teams).filter( + teams_with_points = db.session.query(Solves.teamid).join(Teams).filter( Teams.banned == False).group_by( Solves.teamid).count() @@ -427,7 +430,7 @@ def admin_teams(page): page_start = results_per_page * ( page - 1 ) page_end = results_per_page * ( page - 1 ) + results_per_page - teams = Teams.query.slice(page_start, page_end).all() + teams = Teams.query.order_by(Teams.id.asc()).slice(page_start, page_end).all() count = db.session.query(db.func.count(Teams.id)).first()[0] pages = int(count / results_per_page) + (count % results_per_page > 0) return render_template('admin/teams.html', teams=teams, pages=pages, curr_page=page) @@ -442,7 +445,9 @@ def admin_team(teamid): solves = Solves.query.filter_by(teamid=teamid).all() solve_ids = [s.chalid for s in solves] 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 = db.session.query(Tracking.ip, db.func.max(Tracking.date)) \ + .filter_by(team=teamid) \ + .group_by(Tracking.ip).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() @@ -452,10 +457,12 @@ def admin_team(teamid): elif request.method == 'POST': admin_user = request.form.get('admin', None) if admin_user: - admin_user = 1 if admin_user == "true" else 0 + admin_user = True if admin_user == 'true' else False user.admin = admin_user + # Set user.banned to hide admins from scoreboard user.banned = admin_user db.session.commit() + db.session.close() return jsonify({'data': ['success']}) name = request.form.get('name', None) @@ -545,35 +552,21 @@ def admin_graph(graph_type): json_data['categories'].append({'category':category, 'count':count}) return jsonify(json_data) elif graph_type == "solves": - solves = Solves.query.join(Teams).filter(Teams.banned == False).add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all() + solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \ + .join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \ + .group_by(Solves.chalid).subquery() + solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \ + .join(Challenges, solves_sub.columns.chalid == Challenges.id).all() json_data = {} - for chal, count in solves: - json_data[chal.chal.name] = count + for chal, count, name in solves: + json_data[name] = count return jsonify(json_data) @admin.route('/admin/scoreboard') @admins_only def admin_scoreboard(): - score = db.func.sum(Challenges.value).label('score') - scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), Teams.banned.label('banned'), score, Solves.date.label('date')) \ - .join(Teams) \ - .join(Challenges) \ - .group_by(Solves.teamid) - - awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), Teams.banned.label('banned'), - db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \ - .filter(Teams.id == Awards.teamid) \ - .group_by(Teams.id) - - results = union_all(scores, awards).alias('results') - - standings = db.session.query(results.columns.teamid, results.columns.name, results.columns.banned, - db.func.sum(results.columns.score).label('score')) \ - .group_by(results.columns.teamid) \ - .order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \ - .all() - db.session.close() + standings = get_standings(admin=True) return render_template('admin/scoreboard.html', teams=standings) @@ -711,9 +704,18 @@ def admin_stats(): wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0] solve_count = db.session.query(db.func.count(Solves.id)).first()[0] challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0] - most_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves DESC').first() - least_solved_chal = Challenges.query.add_columns(db.func.count(Solves.chalid).label('solves')).outerjoin(Solves).group_by(Challenges.id).order_by('solves ASC').first() - + + solves_raw = db.func.count(Solves.chalid).label('solves_raw') + solves_sub = db.session.query(Solves.chalid, solves_raw) \ + .group_by(Solves.chalid).subquery() + solves_cnt = coalesce(solves_sub.columns.solves_raw, 0).label('solves_cnt') + most_solved_chal = Challenges.query.add_columns(solves_cnt) \ + .outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \ + .order_by(solves_cnt.desc()).first() + least_solved_chal = Challenges.query.add_columns(solves_cnt) \ + .outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \ + .order_by(solves_cnt.asc()).first() + db.session.close() return render_template('admin/statistics.html', team_count=teams_registered, diff --git a/CTFd/auth.py b/CTFd/auth.py index 9609aff..238cedd 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -172,4 +172,4 @@ def login(): def logout(): if authed(): session.clear() - return redirect('/') + return redirect(url_for('views.static_html')) diff --git a/CTFd/challenges.py b/CTFd/challenges.py index f01bd7c..8a0a75e 100644 --- a/CTFd/challenges.py +++ b/CTFd/challenges.py @@ -20,7 +20,7 @@ def challenges_view(): if view_after_ctf(): pass else: - return redirect('/') + return redirect(url_for('views.static_html')) if get_config('verify_emails') and not is_verified(): return redirect(url_for('auth.confirm_user')) if can_view_challenges(): @@ -36,7 +36,7 @@ def chals(): if view_after_ctf(): pass else: - return redirect('/') + return redirect(url_for('views.static_html')) if can_view_challenges(): chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all() @@ -56,10 +56,13 @@ def chals(): @challenges.route('/chals/solves') def chals_per_solves(): if can_view_challenges(): - solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all() + solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves')).join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).group_by(Solves.chalid).subquery() + solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \ + .join(Challenges, solves_sub.columns.chalid == Challenges.id).all() json = {} - for chal, count in solves: - json[chal.chal.name] = count + for chal, count, name in solves: + json[name] = count + db.session.close() return jsonify(json) return redirect(url_for('auth.login', next='chals/solves')) diff --git a/CTFd/models.py b/CTFd/models.py index 6be6705..b95266f 100644 --- a/CTFd/models.py +++ b/CTFd/models.py @@ -180,7 +180,7 @@ class Solves(db.Model): id = db.Column(db.Integer, primary_key=True) chalid = db.Column(db.Integer, db.ForeignKey('challenges.id')) teamid = db.Column(db.Integer, db.ForeignKey('teams.id')) - ip = db.Column(db.Integer) + ip = db.Column(db.BigInteger) flag = db.Column(db.Text) date = db.Column(db.DateTime, default=datetime.datetime.utcnow) team = db.relationship('Teams', foreign_keys="Solves.teamid", lazy='joined') diff --git a/CTFd/scoreboard.py b/CTFd/scoreboard.py index 84ed516..4b49952 100644 --- a/CTFd/scoreboard.py +++ b/CTFd/scoreboard.py @@ -5,31 +5,37 @@ from sqlalchemy.sql.expression import union_all scoreboard = Blueprint('scoreboard', __name__) +def get_standings(admin=False, count=None): + score = db.func.sum(Challenges.value).label('score') + date = db.func.max(Solves.date).label('date') + scores = db.session.query(Solves.teamid.label('teamid'), score, date).join(Challenges).group_by(Solves.teamid) + awards = db.session.query(Awards.teamid.label('teamid'), db.func.sum(Awards.value).label('score'), db.func.max(Awards.date).label('date')) \ + .group_by(Awards.teamid) + results = union_all(scores, awards).alias('results') + sumscores = db.session.query(results.columns.teamid, db.func.sum(results.columns.score).label('score'), db.func.max(results.columns.date).label('date')) \ + .group_by(results.columns.teamid).subquery() + if admin: + standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), Teams.banned, sumscores.columns.score) \ + .join(sumscores, Teams.id == sumscores.columns.teamid) \ + .order_by(sumscores.columns.score.desc(), sumscores.columns.date) + else: + standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), sumscores.columns.score) \ + .join(sumscores, Teams.id == sumscores.columns.teamid) \ + .filter(Teams.banned == False) \ + .order_by(sumscores.columns.score.desc(), sumscores.columns.date) + if count is not None: + standings = standings_query.all() + else: + standings = standings_query.limit(count).all() + db.session.close() + return standings + @scoreboard.route('/scoreboard') def scoreboard_view(): if get_config('view_scoreboard_if_authed') and not authed(): return redirect(url_for('auth.login', next=request.path)) - score = db.func.sum(Challenges.value).label('score') - scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \ - .join(Teams) \ - .join(Challenges) \ - .filter(Teams.banned == False) \ - .group_by(Solves.teamid) - - awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), - db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \ - .filter(Teams.id == Awards.teamid) \ - .group_by(Teams.id) - - results = union_all(scores, awards).alias('results') - - standings = db.session.query(results.columns.teamid, results.columns.name, - db.func.sum(results.columns.score).label('score')) \ - .group_by(results.columns.teamid) \ - .order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \ - .all() - db.session.close() + standings = get_standings() return render_template('scoreboard.html', teams=standings) @@ -37,25 +43,7 @@ def scoreboard_view(): def scores(): if get_config('view_scoreboard_if_authed') and not authed(): return redirect(url_for('auth.login', next=request.path)) - score = db.func.sum(Challenges.value).label('score') - scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \ - .join(Teams) \ - .join(Challenges) \ - .filter(Teams.banned == False) \ - .group_by(Solves.teamid) - - awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), db.func.sum(Awards.value).label('score'), Awards.date.label('date'))\ - .filter(Teams.id==Awards.teamid)\ - .group_by(Teams.id) - - results = union_all(scores, awards).alias('results') - - standings = db.session.query(results.columns.teamid, results.columns.name, db.func.sum(results.columns.score).label('score'))\ - .group_by(results.columns.teamid)\ - .order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date))\ - .all() - - db.session.close() + standings = get_standings() json = {'standings':[]} for i, x in enumerate(standings): json['standings'].append({'pos':i+1, 'id':x.teamid, 'team':x.name,'score':int(x.score)}) @@ -74,26 +62,7 @@ def topteams(count): count = 10 json = {'scores':{}} - - score = db.func.sum(Challenges.value).label('score') - scores = db.session.query(Solves.teamid.label('teamid'), Teams.name.label('name'), score, Solves.date.label('date')) \ - .join(Teams) \ - .join(Challenges) \ - .filter(Teams.banned == False) \ - .group_by(Solves.teamid) - - awards = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), - db.func.sum(Awards.value).label('score'), Awards.date.label('date')) \ - .filter(Teams.id == Awards.teamid) \ - .group_by(Teams.id) - - results = union_all(scores, awards).alias('results') - - standings = db.session.query(results.columns.teamid, results.columns.name, - db.func.sum(results.columns.score).label('score')) \ - .group_by(results.columns.teamid) \ - .order_by(db.func.sum(results.columns.score).desc(), db.func.max(results.columns.date)) \ - .limit(count).all() + standings = get_standings(count=count) for team in standings: solves = Solves.query.filter_by(teamid=team.teamid).all() diff --git a/CTFd/static/admin/js/chalboard.js b/CTFd/static/admin/js/chalboard.js index fe98133..567ce1f 100644 --- a/CTFd/static/admin/js/chalboard.js +++ b/CTFd/static/admin/js/chalboard.js @@ -46,7 +46,7 @@ function loadchal(id, update) { } function submitkey(chal, key) { - $.post("/admin/chal/" + chal, { + $.post(script_root + "/admin/chal/" + chal, { key: key, nonce: $('#nonce').val() }, function (data) { @@ -55,7 +55,7 @@ function submitkey(chal, key) { } function loadkeys(chal){ - $.get('/admin/keys/' + chal, function(data){ + $.get(script_root + '/admin/keys/' + chal, function(data){ $('#keys-chal').val(chal); keys = $.parseJSON(JSON.stringify(data)); keys = keys['keys']; @@ -84,7 +84,7 @@ function updatekeys(){ $('#current-keys input[name*="key_type"]:checked').each(function(){ vals.push($(this).val()); }) - $.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()}) + $.post(script_root + '/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()}) loadchal(chal, true) $('#update-keys').modal('hide'); } @@ -93,7 +93,7 @@ function loadtags(chal){ $('#tags-chal').val(chal) $('#current-tags').empty() $('#chal-tags').empty() - $.get('/admin/tags/'+chal, function(data){ + $.get(script_root + '/admin/tags/'+chal, function(data){ tags = $.parseJSON(JSON.stringify(data)) tags = tags['tags'] for (var i = 0; i < tags.length; i++) { @@ -108,11 +108,11 @@ function loadtags(chal){ } function deletetag(tagid){ - $.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()}); + $.post(script_root + '/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()}); } function deletechal(chalid){ - $.post('/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid}); + $.post(script_root + '/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid}); } function updatetags(){ @@ -121,13 +121,13 @@ function updatetags(){ $('#chal-tags > span > span').each(function(i, e){ tags.push($(e).text()) }); - $.post('/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()}) + $.post(script_root + '/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()}) loadchal(chal) } function loadfiles(chal){ - $('#update-files form').attr('action', '/admin/files/'+chal) - $.get('/admin/files/' + chal, function(data){ + $('#update-files form').attr('action', script_root+'/admin/files/'+chal) + $.get(script_root + '/admin/files/' + chal, function(data){ $('#files-chal').val(chal) files = $.parseJSON(JSON.stringify(data)); files = files['files'] @@ -141,7 +141,7 @@ function loadfiles(chal){ } function deletefile(chal, file, elem){ - $.post('/admin/files/' + chal,{ + $.post(script_root + '/admin/files/' + chal,{ 'nonce': $('#nonce').val(), 'method': 'delete', 'file': file @@ -155,7 +155,7 @@ function deletefile(chal, file, elem){ function loadchals(){ $('#challenges').empty(); - $.post("/admin/chals", { + $.post(script_root + "/admin/chals", { 'nonce': $('#nonce').val() }, function (data) { categories = []; @@ -207,7 +207,7 @@ $('#submit-tags').click(function (e) { $('#delete-chal form').submit(function(e){ e.preventDefault(); - $.post('/admin/chal/delete', $(this).serialize(), function(data){ + $.post(script_root + '/admin/chal/delete', $(this).serialize(), function(data){ console.log(data) if (data){ loadchals(); diff --git a/CTFd/static/admin/js/team.js b/CTFd/static/admin/js/team.js index cc8bfda..ebafed3 100644 --- a/CTFd/static/admin/js/team.js +++ b/CTFd/static/admin/js/team.js @@ -16,7 +16,7 @@ function scoregraph () { var times = [] var scores = [] var teamname = $('#team-id').text() - $.get('/admin/solves/'+teamid(), function( data ) { + $.get(script_root + '/admin/solves/'+teamid(), function( data ) { var solves = $.parseJSON(JSON.stringify(data)); solves = solves['solves']; @@ -48,7 +48,7 @@ function scoregraph () { function keys_percentage_graph(){ // Solves and Fails pie chart - $.get('/admin/fails/'+teamid(), function(data){ + $.get(script_root + '/admin/fails/'+teamid(), function(data){ var res = $.parseJSON(JSON.stringify(data)); var solves = res['solves']; var fails = res['fails']; @@ -75,7 +75,7 @@ function keys_percentage_graph(){ } function category_breakdown_graph(){ - $.get('/admin/solves/'+teamid(), function(data){ + $.get(script_root + '/admin/solves/'+teamid(), function(data){ var solves = $.parseJSON(JSON.stringify(data)); solves = solves['solves']; @@ -126,4 +126,4 @@ window.onresize = function () { Plotly.Plots.resize(document.getElementById('keys-pie-graph')); Plotly.Plots.resize(document.getElementById('categories-pie-graph')); Plotly.Plots.resize(document.getElementById('score-graph')); -}; \ No newline at end of file +}; diff --git a/CTFd/static/js/chalboard.js b/CTFd/static/js/chalboard.js index c69a15e..100f3be 100644 --- a/CTFd/static/js/chalboard.js +++ b/CTFd/static/js/chalboard.js @@ -58,7 +58,7 @@ $("#answer-input").keyup(function(event){ function submitkey(chal, key, nonce) { $('#submit-key').addClass("disabled-button"); $('#submit-key').prop('disabled', true); - $.post("/chal/" + chal, { + $.post(script_root + "/chal/" + chal, { key: key, nonce: nonce }, function (data) { @@ -103,7 +103,7 @@ function submitkey(chal, key, nonce) { } function marksolves() { - $.get('/solves', function (data) { + $.get(script_root + '/solves', function (data) { solves = $.parseJSON(JSON.stringify(data)); for (var i = solves['solves'].length - 1; i >= 0; i--) { id = solves['solves'][i].chalid; @@ -118,7 +118,7 @@ function marksolves() { } function updatesolves(){ - $.get('/chals/solves', function (data) { + $.get(script_root + '/chals/solves', function (data) { solves = $.parseJSON(JSON.stringify(data)); chals = Object.keys(solves); @@ -133,7 +133,7 @@ function updatesolves(){ } function getsolves(id){ - $.get('/chal/'+id+'/solves', function (data) { + $.get(script_root + '/chal/'+id+'/solves', function (data) { var teams = data['teams']; var box = $('#chal-solves-names'); box.empty(); @@ -147,8 +147,7 @@ function getsolves(id){ } function loadchals() { - - $.get("/chals", function (data) { + $.get(script_root + "/chals", function (data) { var categories = []; challenges = $.parseJSON(JSON.stringify(data)); diff --git a/CTFd/static/js/scoreboard.js b/CTFd/static/js/scoreboard.js index e062f5a..eafeb37 100644 --- a/CTFd/static/js/scoreboard.js +++ b/CTFd/static/js/scoreboard.js @@ -1,5 +1,5 @@ function updatescores () { - $.get('/scores', function( data ) { + $.get(script_root + '/scores', function( data ) { teams = $.parseJSON(JSON.stringify(data)); $('#scoreboard > tbody').empty() for (var i = 0; i < teams['standings'].length; i++) { @@ -23,7 +23,7 @@ function UTCtoDate(utc){ return d; } function scoregraph () { - $.get('/top/10', function( data ) { + $.get(script_root + '/top/10', function( data ) { var scores = $.parseJSON(JSON.stringify(data)); scores = scores['scores']; if (Object.keys(scores).length == 0 ){ @@ -72,4 +72,4 @@ scoregraph(); window.onresize = function () { Plotly.Plots.resize(document.getElementById('score-graph')); -}; \ No newline at end of file +}; diff --git a/CTFd/static/js/team.js b/CTFd/static/js/team.js index 07fb28e..f0b4491 100644 --- a/CTFd/static/js/team.js +++ b/CTFd/static/js/team.js @@ -25,7 +25,7 @@ function scoregraph() { var times = [] var scores = [] var teamname = $('#team-id').text() - $.get('/solves/' + teamid(), function (data) { + $.get(script_root + '/solves/' + teamid(), function (data) { var solves = $.parseJSON(JSON.stringify(data)); solves = solves['solves']; @@ -57,7 +57,7 @@ function scoregraph() { function keys_percentage_graph() { // Solves and Fails pie chart - $.get('/fails/' + teamid(), function (data) { + $.get(script_root + '/fails/' + teamid(), function (data) { var res = $.parseJSON(JSON.stringify(data)); var solves = res['solves']; var fails = res['fails']; @@ -85,7 +85,7 @@ function keys_percentage_graph() { } function category_breakdown_graph() { - $.get('/solves/' + teamid(), function (data) { + $.get(script_root + '/solves/' + teamid(), function (data) { var solves = $.parseJSON(JSON.stringify(data)); solves = solves['solves']; @@ -136,4 +136,4 @@ window.onresize = function () { Plotly.Plots.resize(document.getElementById('keys-pie-graph')); Plotly.Plots.resize(document.getElementById('categories-pie-graph')); Plotly.Plots.resize(document.getElementById('score-graph')); -}; \ No newline at end of file +}; diff --git a/CTFd/static/js/utils.js b/CTFd/static/js/utils.js index 784b3df..5908ad4 100644 --- a/CTFd/static/js/utils.js +++ b/CTFd/static/js/utils.js @@ -52,4 +52,4 @@ function colorhash (x) { function htmlentities(string) { return $('
').text(string).html(); -} \ No newline at end of file +} diff --git a/CTFd/templates/admin/base.html b/CTFd/templates/admin/base.html index 75ea779..574bdf4 100644 --- a/CTFd/templates/admin/base.html +++ b/CTFd/templates/admin/base.html @@ -5,15 +5,18 @@ Admin Panel - - - - - - - - - + + + + + + + + + + {% block stylesheets %} {% endblock %} @@ -27,20 +30,20 @@ - CTFd + CTFd
@@ -50,9 +53,9 @@ {% block content %} {% endblock %} - - - + + + {% block scripts %} {% endblock %} diff --git a/CTFd/templates/admin/chals.html b/CTFd/templates/admin/chals.html index fa470fb..74470cd 100644 --- a/CTFd/templates/admin/chals.html +++ b/CTFd/templates/admin/chals.html @@ -31,7 +31,7 @@

New Challenge