Custom themes (#131)
* Adding preliminary custom themes * Fixing Windows compatibility https://github.com/pallets/jinja/issues/411 * Fixing template reloading and adding UI to change themes * Explicitly closing db connections * Themes now have custom static folders * Closes #128selenium-screenshot-testing
|
@ -53,8 +53,10 @@ docs/_build/
|
|||
# PyBuilder
|
||||
target/
|
||||
|
||||
.DS_Store
|
||||
|
||||
*.db
|
||||
*.log
|
||||
.idea/
|
||||
CTFd/static/uploads
|
||||
.ctfd_secret_key
|
||||
.ctfd_secret_key
|
||||
|
|
|
@ -3,14 +3,24 @@ from flask_sqlalchemy import SQLAlchemy
|
|||
from logging.handlers import RotatingFileHandler
|
||||
from flask_session import Session
|
||||
from sqlalchemy_utils import database_exists, create_database
|
||||
from jinja2 import FileSystemLoader, TemplateNotFound
|
||||
from utils import get_config, set_config
|
||||
import os
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
class ThemeLoader(FileSystemLoader):
|
||||
def get_source(self, environment, template):
|
||||
theme = get_config('ctf_theme')
|
||||
template = "/".join([theme, template])
|
||||
return super(ThemeLoader, self).get_source(environment, template)
|
||||
|
||||
|
||||
def create_app(config='CTFd.config'):
|
||||
app = Flask("CTFd")
|
||||
app = Flask(__name__)
|
||||
with app.app_context():
|
||||
app.config.from_object(config)
|
||||
app.jinja_loader = ThemeLoader(os.path.join(app.root_path, app.template_folder), followlinks=True)
|
||||
|
||||
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking
|
||||
|
||||
|
@ -23,6 +33,9 @@ def create_app(config='CTFd.config'):
|
|||
|
||||
app.db = db
|
||||
|
||||
if not get_config('ctf_theme'):
|
||||
set_config('ctf_theme', 'original')
|
||||
|
||||
#Session(app)
|
||||
|
||||
from CTFd.views import views
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
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, create_image, delete_image, run_image, container_status, container_ports, container_stop, container_start
|
||||
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, \
|
||||
set_config, sendmail, rmdir, create_image, delete_image, run_image, container_status, container_ports, \
|
||||
container_stop, container_start, get_themes
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.scoreboard import get_standings
|
||||
from itsdangerous import TimedSerializer, BadTimeSignature
|
||||
|
@ -85,6 +87,7 @@ def admin_config():
|
|||
mail_password = set_config("mail_password", request.form.get('mail_password', None))
|
||||
|
||||
ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
|
||||
ctf_theme = set_config("ctf_theme", request.form.get('ctf_theme', None))
|
||||
|
||||
mg_base_url = set_config("mg_base_url", request.form.get('mg_base_url', None))
|
||||
mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
|
||||
|
@ -101,9 +104,11 @@ def admin_config():
|
|||
db.session.add(db_end)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_config'))
|
||||
|
||||
ctf_name = get_config('ctf_name')
|
||||
ctf_theme = get_config('ctf_theme')
|
||||
max_tries = get_config('max_tries')
|
||||
|
||||
mail_server = get_config('mail_server')
|
||||
|
@ -150,8 +155,12 @@ def admin_config():
|
|||
end = datetime.datetime.fromtimestamp(float(end))
|
||||
end_days = calendar.monthrange(end.year, end.month)[1]
|
||||
|
||||
themes = get_themes()
|
||||
themes.remove(ctf_theme)
|
||||
|
||||
return render_template('admin/config.html',
|
||||
ctf_name=ctf_name,
|
||||
ctf_theme_config=ctf_theme,
|
||||
start=start,
|
||||
end=end,
|
||||
max_tries=max_tries,
|
||||
|
@ -172,7 +181,8 @@ def admin_config():
|
|||
months=months,
|
||||
curr_year=curr_year,
|
||||
start_days=start_days,
|
||||
end_days=end_days)
|
||||
end_days=end_days,
|
||||
themes=themes)
|
||||
|
||||
|
||||
@admin.route('/admin/css', methods=['GET', 'POST'])
|
||||
|
@ -208,10 +218,12 @@ def admin_pages(route):
|
|||
page.route = route
|
||||
page.html = html
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_pages'))
|
||||
page = Pages(route, html)
|
||||
db.session.add(page)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_pages'))
|
||||
pages = Pages.query.all()
|
||||
return render_template('admin/pages.html', routes=pages, css=get_config('css'))
|
||||
|
@ -223,6 +235,7 @@ def delete_page(pageroute):
|
|||
page = Pages.query.filter_by(route=pageroute).first()
|
||||
db.session.delete(page)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
|
@ -516,6 +529,7 @@ def ban(teamid):
|
|||
user = Teams.query.filter_by(id=teamid).first()
|
||||
user.banned = True
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_scoreboard'))
|
||||
|
||||
|
||||
|
@ -525,6 +539,7 @@ def unban(teamid):
|
|||
user = Teams.query.filter_by(id=teamid).first()
|
||||
user.banned = False
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect(url_for('admin.admin_scoreboard'))
|
||||
|
||||
|
||||
|
@ -537,6 +552,7 @@ def delete_team(teamid):
|
|||
Tracking.query.filter_by(team=teamid).delete()
|
||||
Teams.query.filter_by(id=teamid).delete()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
except DatabaseError:
|
||||
return '0'
|
||||
else:
|
||||
|
@ -603,6 +619,7 @@ def create_award():
|
|||
award.category = request.form.get('category')
|
||||
db.session.add(award)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return "1"
|
||||
except Exception as e:
|
||||
print e
|
||||
|
@ -616,6 +633,7 @@ def delete_award(award_id):
|
|||
award = Awards.query.filter_by(id=award_id).first()
|
||||
db.session.delete(award)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
except Exception as e:
|
||||
print e
|
||||
|
@ -693,14 +711,13 @@ def delete_wrong_key(teamid, chalid):
|
|||
wrong_key = WrongKeys.query.filter_by(teamid=teamid, chalid=chalid).first()
|
||||
db.session.delete(wrong_key)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin.route('/admin/statistics', methods=['GET'])
|
||||
@admins_only
|
||||
def admin_stats():
|
||||
db.session.commit()
|
||||
|
||||
teams_registered = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
|
||||
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
|
||||
|
@ -716,7 +733,8 @@ def admin_stats():
|
|||
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.commit()
|
||||
db.session.close()
|
||||
|
||||
return render_template('admin/statistics.html', team_count=teams_registered,
|
||||
|
|
|
@ -146,10 +146,6 @@ def chal(chalid):
|
|||
data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id']))
|
||||
print("[{0}] {1} submitted {2} with kpm {3}".format(*data))
|
||||
|
||||
# Hit max attempts
|
||||
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
|
||||
return "4" #too many tries on this challenge
|
||||
|
||||
# Anti-bruteforce / submitting keys too quickly
|
||||
if get_kpm(session['id']) > 10:
|
||||
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||
|
@ -157,7 +153,8 @@ def chal(chalid):
|
|||
db.session.commit()
|
||||
db.session.close()
|
||||
logger.warn("[{0}] {1} submitted {2} with kpm {3} [TOO FAST]".format(*data))
|
||||
return "3" # Submitting too fast
|
||||
# return "3" # Submitting too fast
|
||||
return jsonify({'status': '3', 'message': "You're submitting keys too fast. Slow down."})
|
||||
|
||||
solves = Solves.query.filter_by(teamid=session['id'], chalid=chalid).first()
|
||||
|
||||
|
@ -166,6 +163,15 @@ def chal(chalid):
|
|||
chal = Challenges.query.filter_by(id=chalid).first()
|
||||
key = str(request.form['key'].strip().lower())
|
||||
keys = json.loads(chal.flags)
|
||||
|
||||
# Hit max attempts
|
||||
max_tries = int(get_config("max_tries"))
|
||||
if fails >= max_tries > 0:
|
||||
return jsonify({
|
||||
'status': '0',
|
||||
'message': "You have 0 tries remaining"
|
||||
})
|
||||
|
||||
for x in keys:
|
||||
if x['type'] == 0: #static key
|
||||
print(x['flag'], key.strip().lower())
|
||||
|
@ -175,7 +181,8 @@ def chal(chalid):
|
|||
db.session.commit()
|
||||
db.session.close()
|
||||
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
|
||||
return "1" # key was correct
|
||||
# return "1" # key was correct
|
||||
return jsonify({'status':'1', 'message':'Correct'})
|
||||
elif x['type'] == 1: #regex
|
||||
res = re.match(str(x['flag']), key, re.IGNORECASE)
|
||||
if res and res.group() == key:
|
||||
|
@ -184,18 +191,29 @@ def chal(chalid):
|
|||
db.session.commit()
|
||||
db.session.close()
|
||||
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
|
||||
return "1" # key was correct
|
||||
# return "1" # key was correct
|
||||
return jsonify({'status': '1', 'message': 'Correct'})
|
||||
|
||||
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||
db.session.add(wrong)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data))
|
||||
return '0' # key was wrong
|
||||
# return '0' # key was wrong
|
||||
if max_tries:
|
||||
attempts_left = max_tries - fails
|
||||
tries_str = 'tries'
|
||||
if attempts_left == 1:
|
||||
tries_str = 'try'
|
||||
return jsonify({'status': '0', 'message': 'Incorrect. You have {} {} remaining.'.format(attempts_left, tries_str)})
|
||||
else:
|
||||
return jsonify({'status': '0', 'message': 'Incorrect'})
|
||||
|
||||
|
||||
# Challenge already solved
|
||||
else:
|
||||
logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data))
|
||||
return "2" # challenge was already solved
|
||||
# return "2" # challenge was already solved
|
||||
return jsonify({'status': '2', 'message': 'You already solved this'})
|
||||
else:
|
||||
return "-1"
|
||||
|
|
|
@ -19,6 +19,7 @@ SESSION_COOKIE_HTTPONLY = True
|
|||
PERMANENT_SESSION_LIFETIME = 604800 # 7 days in seconds
|
||||
HOST = ".ctfd.io"
|
||||
UPLOAD_FOLDER = os.path.normpath('static/uploads')
|
||||
TEMPLATES_AUTO_RELOAD = True
|
||||
TRUSTED_PROXIES = [
|
||||
'^127\.0\.0\.1$',
|
||||
## Remove the following proxies if you do not trust the local network
|
||||
|
|
|
@ -39,7 +39,7 @@ class Pages(db.Model):
|
|||
self.html = html
|
||||
|
||||
def __repr__(self):
|
||||
return "<Tag {0} for challenge {1}>".format(self.tag, self.chal)
|
||||
return "<Pages {0} for challenge {1}>".format(self.tag, self.chal)
|
||||
|
||||
|
||||
class Containers(db.Model):
|
||||
|
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 357 KiB |
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 231 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -62,34 +62,51 @@ function submitkey(chal, key, nonce) {
|
|||
key: key,
|
||||
nonce: nonce
|
||||
}, function (data) {
|
||||
if (data == -1){
|
||||
var result = $.parseJSON(JSON.stringify(data));
|
||||
|
||||
var result_message = $('#result-message');
|
||||
var result_notification = $('#result-notification');
|
||||
var answer_input = $("#answer-input");
|
||||
result_notification.removeClass();
|
||||
result_message.text(result.message);
|
||||
|
||||
if (result.status == -1){
|
||||
window.location="/login"
|
||||
return
|
||||
}
|
||||
else if (data == 0){ // Incorrect key
|
||||
$("#incorrect-key").slideDown();
|
||||
$("#answer-input").addClass("wrong");
|
||||
$("#answer-input").removeClass("correct");
|
||||
setTimeout(function() {
|
||||
$("#answer-input").removeClass("wrong");
|
||||
else if (result.status == 0){ // Incorrect key
|
||||
result_notification.addClass('alert alert-danger alert-dismissable');
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.removeClass("correct");
|
||||
answer_input.addClass("wrong");
|
||||
setTimeout(function () {
|
||||
answer_input.removeClass("wrong");
|
||||
}, 3000);
|
||||
}
|
||||
else if (data == 1){ // Challenge Solved
|
||||
$("#correct-key").slideDown();
|
||||
$('.chal-solves').text((parseInt($('.chal-solves').text().split(" ")[0]) + 1 + " Solves") )
|
||||
$("#answer-input").val("");
|
||||
$("#answer-input").removeClass("wrong");
|
||||
$("#answer-input").addClass("correct");
|
||||
else if (result.status == 1){ // Challenge Solved
|
||||
result_notification.addClass('alert alert-success alert-dismissable');
|
||||
result_notification.slideDown();
|
||||
|
||||
$('.chal-solves').text((parseInt($('.chal-solves').text().split(" ")[0]) + 1 + " Solves") );
|
||||
|
||||
answer_input.val("");
|
||||
answer_input.removeClass("wrong");
|
||||
answer_input.addClass("correct");
|
||||
}
|
||||
else if (data == 2){ // Challenge already solved
|
||||
$("#already-solved").slideDown();
|
||||
$("#answer-input").addClass("correct");
|
||||
else if (result.status == 2){ // Challenge already solved
|
||||
result_notification.addClass('alert alert-info alert-dismissable');
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("correct");
|
||||
}
|
||||
else if (data == 3){ // Keys per minute too high
|
||||
$("#too-fast").slideDown();
|
||||
$("#answer-input").addClass("wrong");
|
||||
else if (result.status == 3){ // Keys per minute too high
|
||||
result_notification.addClass('alert alert-warning alert-dismissable');
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("too-fast");
|
||||
setTimeout(function() {
|
||||
$("#answer-input").removeClass("wrong");
|
||||
answer_input.removeClass("too-fast");
|
||||
}, 3000);
|
||||
}
|
||||
marksolves()
|
|
@ -5,15 +5,15 @@
|
|||
<title>Admin Panel</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/font-awesome/css/font-awesome.min.css" />
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/css/style.css">
|
||||
<link href='{{ request.script_root }}/static/css/vendor/lato.css' rel='stylesheet' type='text/css'>
|
||||
<link href='{{ request.script_root }}/static/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/admin/css/style.css">
|
||||
<script src="{{ request.script_root }}/static/js/vendor/moment.min.js"></script>
|
||||
<link rel="shortcut icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/font-awesome/css/font-awesome.min.css" />
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/style.css">
|
||||
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/lato.css' rel='stylesheet' type='text/css'>
|
||||
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/{{ ctf_theme() }}/admin/css/style.css">
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/moment.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var script_root = "{{ request.script_root }}";
|
||||
</script>
|
||||
|
@ -53,9 +53,9 @@
|
|||
{% block content %} {% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/jquery.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/marked.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/bootstrap.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/jquery.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/marked.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/bootstrap.min.js"></script>
|
||||
{% block scripts %} {% endblock %}
|
||||
</body>
|
||||
|
|
@ -298,7 +298,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/multi-modal.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/chalboard.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/admin/js/multi-modal.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/admin/js/chalboard.js"></script>
|
||||
{% endblock %}
|
|
@ -21,6 +21,16 @@
|
|||
<input class="form-control" id='ctf_name' name='ctf_name' type='text' placeholder="CTF Name" {% if ctf_name is defined and ctf_name != None %}value="{{ ctf_name }}"{% endif %}>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ctf_theme">Theme:</label>
|
||||
<select class="form-control" id="ctf_theme" name="ctf_theme">
|
||||
<option>{{ ctf_theme_config }}</option>
|
||||
{% for theme in themes %}
|
||||
<option>{{ theme }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max_tries">Maximum Attempts Per Challenge (0 to disable):</label>
|
||||
<input class="form-control" id='max_tries' name='max_tries' type='text' placeholder="0" {% if max_tries is defined and max_tries != None %}value="{{ max_tries }}"{% endif %}>
|
|
@ -77,8 +77,8 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/team.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/admin/js/team.js"></script>
|
||||
<script>
|
||||
$('#delete-solve').click(function(e){
|
||||
e.preventDefault();
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/css/vendor/codemirror.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/codemirror.min.css">
|
||||
<style>
|
||||
.row-fluid { margin: 25px; padding-bottom: 25px; }
|
||||
</style>
|
||||
|
@ -38,7 +38,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/vendor/codemirror.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/codemirror.min.js"></script>
|
||||
<script>
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById("admin-pages-editor"), {
|
||||
lineNumbers: true,
|
|
@ -29,7 +29,7 @@
|
|||
display: block;
|
||||
}
|
||||
</style>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/plotly.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/plotly.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
// $.distint(array)
|
||||
// Unique elements in array
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/css/vendor/codemirror.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/codemirror.min.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -60,7 +60,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/vendor/codemirror.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/codemirror.min.js"></script>
|
||||
<script>
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById("pages-editor"), {
|
||||
lineNumbers: true,
|
|
@ -221,10 +221,10 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/vendor/moment.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/plotly.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/team.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/moment.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/plotly.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/admin/js/team.js"></script>
|
||||
<script>
|
||||
$('#delete-solve').click(function (e) {
|
||||
e.preventDefault();
|
|
@ -84,8 +84,8 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/admin/js/team.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/admin/js/team.js"></script>
|
||||
<script>
|
||||
$('#delete-solve').click(function (e) {
|
||||
e.preventDefault();
|
|
@ -4,16 +4,16 @@
|
|||
<title>{{ ctf_name() }}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" href="{{ request.script_root }}/static/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/css/vendor/font-awesome/css/font-awesome.min.css" />
|
||||
<link href='{{ request.script_root }}/static/css/vendor/lato.css' rel='stylesheet' type='text/css'>
|
||||
<link href='{{ request.script_root }}/static/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/css/style.css">
|
||||
<link rel="shortcut icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/font-awesome/css/font-awesome.min.css" />
|
||||
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/lato.css' rel='stylesheet' type='text/css'>
|
||||
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/raleway.css' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/user.css">
|
||||
{% block stylesheets %}{% endblock %}
|
||||
<script src="{{ request.script_root }}/static/js/vendor/moment.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/moment.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var script_root = "{{ request.script_root }}";
|
||||
</script>
|
||||
|
@ -63,9 +63,9 @@
|
|||
{% endblock %}
|
||||
|
||||
</div>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/jquery.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/marked.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/vendor/bootstrap.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/jquery.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/marked.min.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/bootstrap.min.js"></script>
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
</body>
|
|
@ -34,6 +34,11 @@
|
|||
.input--filled .wrong {
|
||||
border-color: rgb(255, 190, 190);
|
||||
}
|
||||
|
||||
.input-field:focus + .input-field,
|
||||
.input--filled .too-fast {
|
||||
border-color: rgb(252, 248, 227);
|
||||
}
|
||||
a, button {
|
||||
color: #74716D;
|
||||
text-decoration: none;
|
||||
|
@ -117,17 +122,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row notification-row">
|
||||
<div id="incorrect-key" class="alert alert-danger alert-dismissable" role="alert">
|
||||
<strong>Incorrect</strong>
|
||||
</div>
|
||||
<div id="correct-key" class="alert alert-success alert-dismissable" role="alert">
|
||||
<strong>Correct</strong>
|
||||
</div>
|
||||
<div id="already-solved" class="alert alert-info alert-dismissable" role="alert">
|
||||
<strong>You already solved this</strong>
|
||||
</div>
|
||||
<div id="too-fast" class="alert alert-warning alert-dismissable" role="alert">
|
||||
<strong>You're submitting keys too fast. Slow down.</strong>
|
||||
<div id="result-notification" class="alert alert-dismissable" role="alert">
|
||||
<strong id="result-message"></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -154,7 +150,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/chalboard.js"></script>
|
||||
<script src="{{ request.script_root }}/static/js/style.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/chalboard.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||
{% endblock %}
|
|
@ -44,6 +44,6 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/style.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||
{% endblock %}
|
||||
|
|
@ -67,6 +67,6 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/style.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||
{% endblock %}
|
||||
|
|
@ -132,5 +132,5 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/style.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||
{% endblock %}
|
|
@ -63,7 +63,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.script_root }}/static/js/style.js"></script>
|
||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||
<script>
|
||||
if (window.location.hash == "#frame"){
|
||||
$('.top-bar').hide()
|