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 #128
selenium-screenshot-testing
Kevin Chung 2016-07-22 02:46:14 -04:00 committed by GitHub
parent e31b502729
commit b05f6152d3
107 changed files with 195 additions and 103 deletions

4
.gitignore vendored
View File

@ -53,8 +53,10 @@ docs/_build/
# PyBuilder
target/
.DS_Store
*.db
*.log
.idea/
CTFd/static/uploads
.ctfd_secret_key
.ctfd_secret_key

View File

@ -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

View File

@ -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,

View File

@ -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"

View File

@ -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

View File

@ -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):

View File

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 357 KiB

View File

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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()

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}>

View File

@ -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();

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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();

View File

@ -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();

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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()

Some files were not shown because too many files have changed in this diff Show More