Remove platform stuff.
49
.gitignore
vendored
|
@ -1,49 +0,0 @@
|
|||
.vagrant
|
||||
.secret_key
|
||||
.bundle/config
|
||||
logs/
|
||||
files/
|
||||
pfp/
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Text Backup Files #
|
||||
*.s[a-w][a-z]
|
||||
*~
|
||||
\#*\#
|
||||
.\#*
|
||||
|
||||
# OS X Files #
|
||||
.DS_Store
|
||||
._*
|
||||
.Spotlight*
|
||||
.Trashes
|
||||
|
||||
# Windows Files #
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
a.out
|
||||
*.project
|
||||
*.class
|
||||
*.pyc
|
26
Vagrantfile
vendored
|
@ -1,26 +0,0 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
# config.vm.box_check_update = false
|
||||
|
||||
config.vm.network :forwarded_port, guest: 80, host: 8080, auto_correct: true
|
||||
config.vm.network :forwarded_port, guest: 8000, host: 8000, auto_correct: true
|
||||
config.vm.network :forwarded_port, guest: 27017, host: 27017, auto_correct: true
|
||||
|
||||
config.vm.synced_folder "server", "/home/vagrant/server"
|
||||
config.vm.synced_folder "scripts", "/home/vagrant/scripts"
|
||||
config.vm.synced_folder "web", "/srv/http/ctf"
|
||||
|
||||
config.vm.provision :shell, :path => "scripts/setup.sh"
|
||||
config.ssh.forward_agent = true
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.customize ["modifyvm", :id, "--memory", "2048"]
|
||||
end
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
|
||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
|
||||
end
|
||||
end
|
41
ctf.nginx
|
@ -1,41 +0,0 @@
|
|||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server ipv6only=on;
|
||||
sendfile off;
|
||||
|
||||
root /srv/http/ctf;
|
||||
index index.html index.htm;
|
||||
|
||||
server_name localhost;
|
||||
error_page 404 /index.html;
|
||||
|
||||
# location / {
|
||||
# try_files $uri $uri/ =404;
|
||||
# }
|
||||
|
||||
# Put all the pages here so Angular doesn't fail.
|
||||
location ~^/(about|chat|help|learn|login|logout|profile|register|scoreboard|settings|team|forgot)$ {
|
||||
default_type text/html;
|
||||
try_files /index.html /index.html;
|
||||
}
|
||||
location ~^/admin/(problems|settings|stats|teams)$ {
|
||||
default_type text/html;
|
||||
try_files /index.html /index.html;
|
||||
}
|
||||
location ~^/(profile|team|forgot)/(.*)$ {
|
||||
default_type text/html;
|
||||
try_files /index.html /index.html;
|
||||
}
|
||||
|
||||
location /static {
|
||||
alias /srv/http/static;
|
||||
}
|
||||
|
||||
location ~ /api {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
13
deploy
|
@ -1,13 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Stopping the server..."
|
||||
pkill gunicorn
|
||||
sudo service nginx stop
|
||||
tmux kill-session -t ctf 2> /dev/null
|
||||
|
||||
sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
|
||||
|
||||
echo "Starting the server..."
|
||||
cd /home/vagrant/server
|
||||
sudo service nginx start
|
||||
tmux new-session -s ctf -d 'tmux set remain-on-exit on && gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'
|
|
@ -1 +0,0 @@
|
|||
Help! I started growing this array culture, but it outgrew its petri dish! Using standard array syntax (either JavaScript or Python), indicate which element of the array contains the value ${value}.
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"pid": "cancer",
|
||||
"title": "Cancer",
|
||||
"hint": "No hint!",
|
||||
"category": "Miscellaneous",
|
||||
"autogen": true,
|
||||
"programming": false,
|
||||
"value": 100,
|
||||
"bonus": 0,
|
||||
"threshold": 0,
|
||||
"weightmap": { }
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
from multiprocessing import cpu_count
|
||||
|
||||
bind = "0.0.0.0:8000"
|
||||
workers = cpu_count() * 2 + 1
|
|
@ -1,9 +0,0 @@
|
|||
Flask
|
||||
mysql-python
|
||||
Flask-SQLAlchemy
|
||||
SQLAlchemy
|
||||
gunicorn
|
||||
requests
|
||||
voluptuous
|
||||
Pillow
|
||||
markdown2
|
|
@ -1,31 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
MYSQL_ROOT_PASSWORD="i_hate_passwords"
|
||||
|
||||
echo "Updating system..."
|
||||
apt-get -y update
|
||||
apt-get -y upgrade
|
||||
|
||||
echo "Preparing for MySQL installation..."
|
||||
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password $MYSQL_ROOT_PASSWORD"
|
||||
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $MYSQL_ROOT_PASSWORD"
|
||||
|
||||
echo "Installing dependencies..."
|
||||
apt-get -y install python
|
||||
apt-get -y install python-pip libjpeg-dev
|
||||
apt-get -y install python-dev libmysqlclient-dev
|
||||
apt-get -y install nginx
|
||||
apt-get -y install mysql-server
|
||||
apt-get -y install tmux
|
||||
|
||||
echo "Installing pip dependencies..."
|
||||
pip install -r /vagrant/scripts/requirements.txt
|
||||
|
||||
echo "PATH=$PATH:/vagrant" >> /etc/profile
|
||||
source /etc/profile
|
||||
cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
|
||||
rm /etc/nginx/sites-*/default
|
||||
|
||||
sudo service nginx restart
|
||||
|
||||
mysql -u root -p"$MYSQL_ROOT_PASSWORD" -e "CREATE DATABASE easyctf;"
|
|
@ -1,8 +0,0 @@
|
|||
import admin
|
||||
import logger
|
||||
import models
|
||||
import problem
|
||||
import user
|
||||
import stats
|
||||
import team
|
||||
import utils
|
|
@ -1,45 +0,0 @@
|
|||
from flask import Blueprint, jsonify
|
||||
from decorators import admins_only, api_wrapper
|
||||
from models import db, Problems, Files
|
||||
from schemas import verify_to_schema, check
|
||||
|
||||
import cPickle as pickle
|
||||
|
||||
blueprint = Blueprint("admin", __name__)
|
||||
|
||||
@blueprint.route("/problems/list", methods=["GET"])
|
||||
@api_wrapper
|
||||
@admins_only
|
||||
def problem_data():
|
||||
problems = Problems.query.order_by(Problems.value).all()
|
||||
problems_return = [ ]
|
||||
for problem in problems:
|
||||
problems_return.append({
|
||||
"pid": problem.pid,
|
||||
"title": problem.title,
|
||||
"category": problem.category,
|
||||
"description": problem.description,
|
||||
"hint": problem.hint,
|
||||
"value": problem.value,
|
||||
"threshold": problem.threshold,
|
||||
"weightmap": problem.weightmap
|
||||
})
|
||||
problems_return.sort(key=lambda prob: prob["value"])
|
||||
return { "success": 1, "problems": problems_return }
|
||||
|
||||
"""
|
||||
@blueprint.route("/problems/submit", methods=["POST"])
|
||||
@api_wrapper
|
||||
@admins_only
|
||||
def problem_submit():
|
||||
params = utils.flat_multi(request.form)
|
||||
verify_to_schema(UserSchema, params)
|
||||
|
||||
title = params.get("title")
|
||||
|
||||
ProblemSubmissionSchema = Schema({
|
||||
Required("title"): check(
|
||||
([str, Length(min=4, max=64)], "The title should be between 4 and 64 characters long."),
|
||||
),
|
||||
}, extra=True)
|
||||
"""
|
|
@ -1,68 +0,0 @@
|
|||
from functools import wraps
|
||||
from flask import abort, request, session, make_response
|
||||
|
||||
import json
|
||||
import traceback
|
||||
|
||||
import utils
|
||||
|
||||
class WebException(Exception): pass
|
||||
class InternalException(Exception): pass
|
||||
response_header = { "Content-Type": "application/json; charset=utf-8" }
|
||||
|
||||
def api_wrapper(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwds):
|
||||
if request.method == "POST":
|
||||
try:
|
||||
token = str(session.pop("csrf_token"))
|
||||
provided_token = str(request.form.get("csrf_token"))
|
||||
if not token or token != provided_token:
|
||||
raise Exception
|
||||
except Exception, e:
|
||||
response = make_response(json.dumps({ "success": 0, "message": "Token has been tampered with." }), 403, response_header)
|
||||
token = utils.generate_string()
|
||||
response.set_cookie("csrf_token", token)
|
||||
session["csrf_token"] = token
|
||||
return response
|
||||
|
||||
web_result = {}
|
||||
response = 200
|
||||
try:
|
||||
web_result = f(*args, **kwds)
|
||||
except WebException as error:
|
||||
response = 200
|
||||
web_result = { "success": 0, "message": str(error) }
|
||||
except Exception as error:
|
||||
response = 200
|
||||
traceback.print_exc()
|
||||
web_result = { "success": 0, "message": "Something went wrong! Please notify us about this immediately.", "error": [ str(error), traceback.format_exc() ] }
|
||||
result = (json.dumps(web_result), response, response_header)
|
||||
response = make_response(result)
|
||||
|
||||
# Setting CSRF token
|
||||
if "csrf_token" not in session:
|
||||
token = utils.generate_string()
|
||||
response.set_cookie("csrf_token", token)
|
||||
session["csrf_token"] = token
|
||||
|
||||
return response
|
||||
return wrapper
|
||||
|
||||
def login_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not user.is_logged_in():
|
||||
return { "success": 0, "message": "Not logged in." }
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
import user # Must go below api_wrapper to prevent import loops
|
||||
|
||||
def admins_only(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not user.is_admin():
|
||||
return { "success": 0, "message": "Not authorized." }
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
|
@ -1,34 +0,0 @@
|
|||
import datetime
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import pkgutil
|
||||
|
||||
import api
|
||||
|
||||
NOTSET = 0
|
||||
DEBUG = 10
|
||||
INFO = 20
|
||||
WARNING = 30
|
||||
ERROR = 40
|
||||
CRITICAL = 50
|
||||
|
||||
def initialize_logs():
|
||||
def create_logger(name):
|
||||
new_logger = logging.getLogger(name)
|
||||
new_logger.setLevel(logging.INFO)
|
||||
|
||||
base = os.path.dirname(__file__).strip("api")
|
||||
log_path = os.path.join(base, "logs")
|
||||
if not os.path.exists(log_path):
|
||||
os.mkdir(log_path)
|
||||
|
||||
log_handler = logging.handlers.RotatingFileHandler(os.path.join(log_path, name + ".log"))
|
||||
new_logger.addHandler(log_handler)
|
||||
for importer, modname, ispkg in pkgutil.walk_packages(path="../api"):
|
||||
create_logger(modname)
|
||||
|
||||
def log(logname, message, level=INFO):
|
||||
logger = logging.getLogger(logname)
|
||||
message = "[%s] %s" % (datetime.datetime.now().strftime("%m/%d/%Y %X"), message)
|
||||
logger.log(level, message)
|
|
@ -1,212 +0,0 @@
|
|||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
|
||||
import time
|
||||
import traceback
|
||||
import utils
|
||||
import cPickle as pickle
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Users(db.Model):
|
||||
uid = db.Column(db.Integer, unique=True, primary_key=True)
|
||||
tid = db.Column(db.Integer)
|
||||
name = db.Column(db.String(64))
|
||||
username = db.Column(db.String(64), unique=True)
|
||||
username_lower = db.Column(db.String(64), unique=True)
|
||||
email = db.Column(db.String(64), unique=True)
|
||||
password = db.Column(db.String(128))
|
||||
admin = db.Column(db.Boolean)
|
||||
utype = db.Column(db.Integer)
|
||||
tid = db.Column(db.Integer)
|
||||
registertime = db.Column(db.Integer)
|
||||
reset_token = db.Column(db.String(64))
|
||||
|
||||
def __init__(self, name, username, email, password, utype=1):
|
||||
self.name = name
|
||||
self.username = username
|
||||
self.username_lower = username.lower()
|
||||
self.email = email.lower()
|
||||
self.password = utils.hash_password(password)
|
||||
self.utype = utype
|
||||
self.admin = False
|
||||
self.registertime = int(time.time())
|
||||
|
||||
def get_invitations(self):
|
||||
invitations = db.session.query(TeamInvitations).filter_by(rtype=0, toid=self.uid).all()
|
||||
result = [ ]
|
||||
for inv in invitations:
|
||||
team = db.session.query(Teams).filter_by(tid=inv.frid).first()
|
||||
result.append({
|
||||
"team": team.teamname,
|
||||
"tid": team.tid
|
||||
})
|
||||
return result
|
||||
|
||||
class Teams(db.Model):
|
||||
tid = db.Column(db.Integer, primary_key=True)
|
||||
teamname = db.Column(db.String(64), unique=True)
|
||||
teamname_lower = db.Column(db.String(64), unique=True)
|
||||
school = db.Column(db.Text)
|
||||
owner = db.Column(db.Integer)
|
||||
observer = db.Column(db.Boolean)
|
||||
|
||||
def __init__(self, teamname, school, owner, observer):
|
||||
self.teamname = teamname
|
||||
self.teamname_lower = teamname.lower()
|
||||
self.school = school
|
||||
self.owner = owner
|
||||
self.observer = observer
|
||||
|
||||
def get_members(self):
|
||||
members = [ ]
|
||||
for member in Users.query.filter_by(tid=self.tid).all():
|
||||
members.append({
|
||||
"username": member.username,
|
||||
"name": member.name,
|
||||
"captain": member.uid == self.owner,
|
||||
"type": member.utype,
|
||||
"admin": member.admin == True,
|
||||
"observer": member.utype == 3
|
||||
})
|
||||
return members
|
||||
|
||||
def points(self):
|
||||
""" TODO: Implement scoring with Bonus Points """
|
||||
return 0
|
||||
|
||||
def place(self, ranked=True):
|
||||
# score = db.func.sum(Problems.value).label("score")
|
||||
# quickest = db.func.max(Solves.date).label("quickest")
|
||||
# teams = db.session.query(Solves.tid).join(Teams).join(Problems).filter().group_by(Solves.tid).order_by(score.desc(), quickest).all()
|
||||
teams = [ self.tid ]
|
||||
try:
|
||||
i = teams.index((self.tid,)) + 1
|
||||
k = i % 10
|
||||
return (i, "%d%s" % (i, "tsnrhtdd"[(i / 10 % 10 != 1) * (k < 4) * k::4]))
|
||||
except ValueError:
|
||||
return (-1, "--")
|
||||
|
||||
def get_invitation_requests(self, frid=None):
|
||||
if frid is not None:
|
||||
req = db.session.query(TeamInvitations).filter_by(rtype=1, frid=frid, toid=self.tid).first()
|
||||
if req is None:
|
||||
return None
|
||||
else:
|
||||
user = db.session.query(Users).filter_by(uid=req.frid).first()
|
||||
return { "username": user.username, "name": user.name, "uid": user.uid }
|
||||
result = [ ]
|
||||
requests = db.session.query(TeamInvitations).filter_by(rtype=1, toid=self.tid).all()
|
||||
for req in requests:
|
||||
user = db.session.query(Users).filter_by(uid=req.frid).first()
|
||||
result.append({
|
||||
"username": user.username,
|
||||
"name": user.name,
|
||||
"uid": user.uid
|
||||
})
|
||||
return result
|
||||
|
||||
def get_pending_invitations(self, toid=None):
|
||||
if toid is not None:
|
||||
invitation = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid, toid=toid).first()
|
||||
if invitation is None:
|
||||
return None
|
||||
else:
|
||||
user = db.session.query(Users).filter_by(uid=invitation.toid).first()
|
||||
return { "username": user.username, "name": user.name, "uid": user.uid }
|
||||
result = [ ]
|
||||
invitations = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid).all()
|
||||
for invitation in invitations:
|
||||
user = db.session.query(Users).filter_by(uid=invitation.toid).first()
|
||||
result.append({
|
||||
"username": user.username,
|
||||
"name": user.name,
|
||||
"uid": user.uid
|
||||
})
|
||||
return result
|
||||
|
||||
def is_observer(self):
|
||||
members = self.get_members()
|
||||
for member in members:
|
||||
if member["observer"] == True or member["admin"] == True:
|
||||
return True
|
||||
return False
|
||||
|
||||
class Problems(db.Model):
|
||||
pid = db.Column(db.String(128), primary_key=True, autoincrement=False)
|
||||
title = db.Column(db.String(128))
|
||||
category = db.Column(db.String(128))
|
||||
description = db.Column(db.Text)
|
||||
value = db.Column(db.Integer)
|
||||
hint = db.Column(db.Text)
|
||||
autogen = db.Column(db.Boolean)
|
||||
bonus = db.Column(db.Integer)
|
||||
threshold = db.Column(db.Integer)
|
||||
weightmap = db.Column(db.PickleType)
|
||||
|
||||
def __init__(self, pid, title, category, description, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}):
|
||||
self.pid = pid
|
||||
self.title = title
|
||||
self.category = category
|
||||
self.description = description
|
||||
self.value = value
|
||||
self.hint = hint
|
||||
self.autogen = autogen
|
||||
self.bonus = bonus
|
||||
self.threshold = threshold
|
||||
self.weightmap = weightmap
|
||||
|
||||
class Files(db.Model):
|
||||
fid = db.Column(db.Integer, primary_key=True)
|
||||
pid = db.Column(db.Integer)
|
||||
location = db.Column(db.Text)
|
||||
|
||||
def __init__(self, pid, location):
|
||||
self.pid = pid
|
||||
self.location = location
|
||||
|
||||
class Solves(db.Model):
|
||||
__table_args__ = (db.UniqueConstraint("pid", "tid"), {})
|
||||
sid = db.Column(db.Integer, primary_key=True)
|
||||
pid = db.Column(db.Integer)
|
||||
tid = db.Column(db.Integer)
|
||||
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
|
||||
correct = db.Column(db.Boolean)
|
||||
flag = db.Column(db.Text)
|
||||
|
||||
def __init__(self, pid, tid, flag, correct):
|
||||
self.pid = pid
|
||||
self.tid = tid
|
||||
self.flag = flag
|
||||
self.correct = correct
|
||||
|
||||
class LoginTokens(db.Model):
|
||||
sid = db.Column(db.String(64), unique=True, primary_key=True)
|
||||
uid = db.Column(db.Integer)
|
||||
username = db.Column(db.String(32))
|
||||
active = db.Column(db.Boolean)
|
||||
issued = db.Column(db.Integer)
|
||||
expiry = db.Column(db.Integer)
|
||||
ua = db.Column(db.String(128))
|
||||
ip = db.Column(db.String(16))
|
||||
|
||||
def __init__(self, uid, username, expiry=int(time.time()), active=True, ua=None, ip=None):
|
||||
self.sid = utils.generate_string()
|
||||
self.uid = uid
|
||||
self.username = username
|
||||
self.issued = int(time.time())
|
||||
self.expiry = expiry
|
||||
self.active = active
|
||||
self.ua = ua
|
||||
self.ip = ip
|
||||
|
||||
class TeamInvitations(db.Model):
|
||||
rid = db.Column(db.Integer, primary_key=True)
|
||||
rtype = db.Column(db.Integer)
|
||||
frid = db.Column(db.Integer)
|
||||
toid = db.Column(db.Integer)
|
||||
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
|
||||
|
||||
def __init__(self, rtype, frid, toid):
|
||||
self.rtype = rtype
|
||||
self.frid = frid
|
||||
self.toid = toid
|
|
@ -1,163 +0,0 @@
|
|||
import hashlib
|
||||
import logger
|
||||
import os
|
||||
|
||||
from flask import Blueprint, jsonify, session, request
|
||||
from flask import current_app as app
|
||||
from werkzeug import secure_filename
|
||||
|
||||
from models import db, Files, Problems, Solves, Teams
|
||||
from decorators import admins_only, api_wrapper, login_required, InternalException, WebException
|
||||
|
||||
blueprint = Blueprint("problem", __name__)
|
||||
|
||||
@blueprint.route("/add", methods=["POST"])
|
||||
@admins_only
|
||||
@api_wrapper
|
||||
def problem_add():
|
||||
name = request.form["name"]
|
||||
category = request.form["category"]
|
||||
description = request.form["description"]
|
||||
hint = request.form["problem-hint"]
|
||||
flag = request.form["flag"]
|
||||
value = request.form["value"]
|
||||
|
||||
name_exists = Problems.query.filter_by(name=name).first()
|
||||
if name_exists:
|
||||
raise WebException("Problem name already taken.")
|
||||
problem = Problems(name, category, description, hint, flag, value)
|
||||
db.session.add(problem)
|
||||
db.session.commit()
|
||||
|
||||
files = request.files.getlist("files[]")
|
||||
for _file in files:
|
||||
filename = secure_filename(_file.filename)
|
||||
|
||||
if len(filename) == 0:
|
||||
continue
|
||||
|
||||
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
|
||||
|
||||
_file.save(file_path)
|
||||
db_file = Files(problem.pid, "/".join(file_path.split("/")[2:]))
|
||||
db.session.add(db_file)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/delete", methods=["POST"])
|
||||
@admins_only
|
||||
@api_wrapper
|
||||
def problem_delete():
|
||||
pid = request.form["pid"]
|
||||
problem = Problems.query.filter_by(pid=pid).first()
|
||||
if problem:
|
||||
Solves.query.filter_by(pid=pid).delete()
|
||||
Problems.query.filter_by(pid=pid).delete()
|
||||
db.session.commit()
|
||||
return { "success": 1, "message": "Success!" }
|
||||
raise WebException("Problem does not exist!")
|
||||
|
||||
@blueprint.route("/update", methods=["POST"])
|
||||
@admins_only
|
||||
@api_wrapper
|
||||
def problem_update():
|
||||
pid = request.form["pid"]
|
||||
name = request.form["name"]
|
||||
category = request.form["category"]
|
||||
description = request.form["description"]
|
||||
hint = request.form["hint"]
|
||||
flag = request.form["flag"]
|
||||
disabled = request.form.get("disabled", 0)
|
||||
value = request.form["value"]
|
||||
|
||||
problem = Problems.query.filter_by(pid=pid).first()
|
||||
if problem:
|
||||
problem.name = name
|
||||
problem.category = category
|
||||
problem.description = description
|
||||
problem.hint = hint
|
||||
problem.flag = flag
|
||||
problem.disabled = disabled
|
||||
problem.value = value
|
||||
|
||||
db.session.add(problem)
|
||||
db.session.commit()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
raise WebException("Problem does not exist!")
|
||||
|
||||
@blueprint.route("/submit", methods=["POST"])
|
||||
@api_wrapper
|
||||
@login_required
|
||||
def problem_submit():
|
||||
pid = request.form["pid"]
|
||||
flag = request.form["flag"]
|
||||
tid = session["tid"]
|
||||
|
||||
problem = Problems.query.filter_by(pid=pid).first()
|
||||
team = Teams.query.filter_by(tid=tid).first()
|
||||
if problem:
|
||||
if flag == problem.flag:
|
||||
solve = Solves(pid, tid)
|
||||
team.score += problem.value
|
||||
problem.solves += 1
|
||||
db.session.add(solve)
|
||||
db.session.add(team)
|
||||
db.session.add(problem)
|
||||
db.session.commit()
|
||||
|
||||
logger.log(__name__, logger.WARNING, "%s has solved %s by submitting %s" % (team.name, problem.name, flag))
|
||||
return { "success": 1, "message": "Correct!" }
|
||||
|
||||
else:
|
||||
logger.log(__name__, logger.WARNING, "%s has incorrectly submitted %s to %s" % (team.name, flag, problem.name))
|
||||
raise WebException("Incorrect.")
|
||||
|
||||
else:
|
||||
raise WebException("Problem does not exist!")
|
||||
|
||||
@blueprint.route("/data", methods=["POST"])
|
||||
#@api_wrapper # Disable atm due to json serialization issues: will fix
|
||||
@login_required
|
||||
def problem_data():
|
||||
problems = Problems.query.add_columns("pid", "name", "category", "description", "hint", "value", "solves").order_by(Problems.value).filter_by(disabled=False).all()
|
||||
jason = []
|
||||
|
||||
for problem in problems:
|
||||
problem_files = [ str(_file.location) for _file in Files.query.filter_by(pid=int(problem.pid)).all() ]
|
||||
jason.append({"pid": problem[1], "name": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "files": problem_files})
|
||||
|
||||
return jsonify(data=jason)
|
||||
|
||||
def insert_problem(data, force=False):
|
||||
with app.app_context():
|
||||
if len(list(get_problem(pid=data["pid"]).all())) > 0:
|
||||
if force == True:
|
||||
_problem = Problems.query.filter_by(pid=data["pid"]).first()
|
||||
db.session.delete(_problem)
|
||||
db.session.commit()
|
||||
else:
|
||||
raise InternalException("Problem already exists.")
|
||||
|
||||
insert = Problems(data["pid"], data["title"], data["category"], data["description"], data["value"])
|
||||
if "hint" in data: insert.hint = data["hint"]
|
||||
if "autogen" in data: insert.autogen = data["autogen"]
|
||||
if "bonus" in data: insert.bonus = data["bonus"]
|
||||
if "threshold" in data: insert.threshold = data["threshold"]
|
||||
if "weightmap" in data: insert.weightmap = data["weightmap"]
|
||||
db.session.add(insert)
|
||||
db.session.commit()
|
||||
|
||||
return True
|
||||
|
||||
def get_problem(title=None, pid=None):
|
||||
match = {}
|
||||
if title != None:
|
||||
match.update({ "title": title })
|
||||
elif pid != None:
|
||||
match.update({ "pid": pid })
|
||||
with app.app_context():
|
||||
result = Problems.query.filter_by(**match)
|
||||
return result
|
|
@ -1,24 +0,0 @@
|
|||
import api
|
||||
import re
|
||||
|
||||
from voluptuous import Required, Length, Schema, Invalid, MultipleInvalid
|
||||
from decorators import WebException
|
||||
|
||||
def check(*callback_tuples):
|
||||
def v(value):
|
||||
for callbacks, msg in callback_tuples:
|
||||
for callback in callbacks:
|
||||
try:
|
||||
result = callback(value)
|
||||
if not result and type(result) == bool:
|
||||
raise Invalid(msg)
|
||||
except Exception:
|
||||
raise WebException(msg)
|
||||
return value
|
||||
return v
|
||||
|
||||
def verify_to_schema(schema, data):
|
||||
try:
|
||||
schema(data)
|
||||
except MultipleInvalid as error:
|
||||
raise WebException(str(error))
|
|
@ -1,25 +0,0 @@
|
|||
from flask import Blueprint, request, session
|
||||
from flask import current_app as app
|
||||
|
||||
from decorators import api_wrapper
|
||||
|
||||
import team
|
||||
|
||||
blueprint = Blueprint("stats", __name__)
|
||||
|
||||
@blueprint.route("/scoreboard")
|
||||
@api_wrapper
|
||||
def all_teams_stats():
|
||||
teams = team.get_team().all()
|
||||
result = [ ]
|
||||
count = 0
|
||||
for _team in teams:
|
||||
count += 1
|
||||
result.append({
|
||||
"rank": count,
|
||||
"teamname": _team.teamname,
|
||||
"tid": _team.tid,
|
||||
"school": _team.school,
|
||||
"points": _team.points()
|
||||
})
|
||||
return { "success": 1, "scoreboard": result }
|
|
@ -1,329 +0,0 @@
|
|||
from flask import Blueprint, request, session
|
||||
from flask import current_app as app
|
||||
from voluptuous import Schema, Length, Required
|
||||
|
||||
from models import db, Teams, Users, TeamInvitations
|
||||
from decorators import api_wrapper, login_required, WebException
|
||||
from schemas import verify_to_schema, check
|
||||
|
||||
import user
|
||||
import utils
|
||||
|
||||
blueprint = Blueprint("team", __name__)
|
||||
|
||||
###############
|
||||
# TEAM ROUTES #
|
||||
###############
|
||||
|
||||
@blueprint.route("/create", methods=["POST"])
|
||||
@api_wrapper
|
||||
@login_required
|
||||
def team_create():
|
||||
params = utils.flat_multi(request.form)
|
||||
_user = user.get_user().first()
|
||||
if (_user.tid is not None and _user.tid >= 0) or get_team(owner=_user.uid).first() is not None:
|
||||
raise WebException("You're already in a team!")
|
||||
|
||||
verify_to_schema(TeamSchema, params)
|
||||
teamname = params.get("teamname")
|
||||
school = params.get("school")
|
||||
|
||||
team = Teams(teamname, school, _user.uid, _user.utype != 1)
|
||||
tid = team.tid
|
||||
with app.app_context():
|
||||
db.session.add(team)
|
||||
db.session.commit()
|
||||
Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid })
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
session["tid"] = team.tid
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/delete", methods=["POST"])
|
||||
@api_wrapper
|
||||
@login_required
|
||||
def team_delete():
|
||||
username = session["username"]
|
||||
tid = session["tid"]
|
||||
team = Teams.query.filter_by(tid=tid).first()
|
||||
usr = Users.query.filter_by(username=username).first()
|
||||
owner = team.owner
|
||||
if usr.uid == owner or usr.admin:
|
||||
with app.app_context():
|
||||
for member in Users.query.filter_by(tid=tid).all():
|
||||
member.tid = -1
|
||||
db.session.add(member)
|
||||
db.session.delete(team)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
session.pop("tid")
|
||||
return { "success": 1, "message": "Success!" }
|
||||
else:
|
||||
raise WebException("Not authorized.")
|
||||
|
||||
@blueprint.route("/remove_member", methods=["POST"])
|
||||
@api_wrapper
|
||||
@login_required
|
||||
def team_remove_member():
|
||||
username = session["username"]
|
||||
tid = session["tid"]
|
||||
team = Teams.query.filter_by(tid=tid).first()
|
||||
usr = Users.query.filter_by(username=username).first()
|
||||
owner = team.owner
|
||||
if usr.uid == owner or usr.admin:
|
||||
params = utils.flat_multi(request.form)
|
||||
user_to_remove = Users.query.filter_by(username=params.get("user"))
|
||||
user_to_remove.tid = -1
|
||||
with app.app_context():
|
||||
db.session.add(user_to_remove)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return { "success": 1, "message": "Success!" }
|
||||
else:
|
||||
raise WebException("Not authorized.")
|
||||
|
||||
@blueprint.route("/invite", methods=["POST"])
|
||||
@api_wrapper
|
||||
@login_required
|
||||
def team_invite():
|
||||
params = utils.flat_multi(request.form)
|
||||
_user = user.get_user().first()
|
||||
if not user.in_team(_user):
|
||||
raise WebException("You must be in a team!")
|
||||
_team = get_team(tid=_user.tid).first()
|
||||
if _user.uid != _team.owner:
|
||||
raise WebException("You must be the captain of your team to invite members!")
|
||||
|
||||
new_member = params.get("new_member")
|
||||
if new_member is None:
|
||||
raise WebException("Please provide a username!")
|
||||
_user2 = user.get_user(username_lower=new_member.lower()).first()
|
||||
if _user2 is None:
|
||||
raise WebException("User doesn't exist!")
|
||||
if _user2.tid > 0:
|
||||
raise WebException("This user is already a part of a team!")
|
||||
|
||||
if _team.get_pending_invitations(toid=_user2.uid) is not None:
|
||||
raise WebException("You've already invited this member!")
|
||||
|
||||
req = TeamInvitations(0, _team.tid, _user2.uid)
|
||||
with app.app_context():
|
||||
db.session.add(req)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/invite/rescind", methods=["POST"])
|
||||
@api_wrapper
|
||||
@login_required
|
||||
def team_invite_rescind():
|
||||
params = utils.flat_multi(request.form)
|
||||
_user = user.get_user().first()
|
||||
if not user.in_team(_user):
|
||||
raise WebException("You must be in a team!")
|
||||
_team = get_team(tid=_user.tid).first()
|
||||
if _user.uid != _team.owner:
|
||||
raise WebException("You must be the captain of your team to rescind invitations!")
|
||||
|
||||
uid = params.get("uid")
|
||||
if uid is None:
|
||||
raise WebException("Please provide a user.")
|
||||
invitation = TeamInvitations.query.filter_by(rtype=0, frid=_team.tid, toid=uid).first()
|
||||
if invitation is None:
|
||||
raise WebException("Invitation doesn't exist.")
|
||||
|
||||
with app.app_context():
|
||||
db.session.delete(invitation)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/invite/request", methods=["POST"])
|
||||
@api_wrapper
|
||||
@login_required
|
||||
def team_invite_request():
|
||||
params = utils.flat_multi(request.form)
|
||||
_user = user.get_user().first()
|
||||
if user.in_team(_user):
|
||||
raise WebException("You're already in a team!")
|
||||
|
||||
tid = params.get("tid")
|
||||
_team = get_team(tid=tid).first()
|
||||
if _team is None:
|
||||
raise WebException("Team not found.")
|
||||
|
||||
if _team.get_invitation_requests(frid=_user.uid) is not None:
|
||||
raise WebException("You've already requested to join this team!")
|
||||
|
||||
req = TeamInvitations(1, _user.uid, _team.tid)
|
||||
with app.app_context():
|
||||
db.session.add(req)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/invite/accept", methods=["POST"])
|
||||
@api_wrapper
|
||||
def team_accept_invite():
|
||||
params = utils.flat_multi(request.form)
|
||||
_user = user.get_user().first()
|
||||
if user.in_team(_user):
|
||||
raise WebException("You're already in a team!")
|
||||
|
||||
tid = params.get("tid")
|
||||
_team = get_team(tid=tid).first()
|
||||
if _team is None:
|
||||
raise WebException("Team not found.")
|
||||
|
||||
if len(_team.get_members()) >= 5:
|
||||
raise WebException("This team is full.")
|
||||
|
||||
invitation = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user.uid).first()
|
||||
if invitation is None:
|
||||
raise WebException("Invitation doesn't exist.")
|
||||
|
||||
with app.app_context():
|
||||
_user = Users.query.filter_by(uid=_user.uid).first()
|
||||
_user.tid = tid
|
||||
db.session.delete(invitation)
|
||||
invitation2 = TeamInvitations.query.filter_by(rtype=1, frid=_user.uid, toid=tid).first()
|
||||
if invitation2 is not None:
|
||||
db.session.delete(invitation2)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/invite/request/accept", methods=["POST"])
|
||||
@api_wrapper
|
||||
def team_accept_invite_request():
|
||||
params = utils.flat_multi(request.form)
|
||||
_user = user.get_user().first()
|
||||
if not user.in_team(_user):
|
||||
raise WebException("You must be in a team!")
|
||||
_team = get_team(tid=_user.tid).first()
|
||||
tid = _team.tid
|
||||
if _user.uid != _team.owner:
|
||||
raise WebException("You must be the captain of your team to rescind invitations!")
|
||||
|
||||
if len(_team.get_members()) >= 5:
|
||||
raise WebException("Your team is full.")
|
||||
|
||||
uid = params.get("uid")
|
||||
_user2 = user.get_user(uid=uid).first()
|
||||
if user.in_team(_user2):
|
||||
raise WebException("This user is already in a team!")
|
||||
|
||||
invitation = TeamInvitations.query.filter_by(rtype=1, frid=_user2.uid, toid=tid).first()
|
||||
if invitation is None:
|
||||
raise WebException("Invitation doesn't exist.")
|
||||
|
||||
with app.app_context():
|
||||
_user2 = Users.query.filter_by(uid=_user2.uid).first()
|
||||
_user2.tid = tid
|
||||
db.session.delete(invitation)
|
||||
invitation2 = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user2.uid).first()
|
||||
if invitation2 is not None:
|
||||
db.session.delete(invitation2)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/info", methods=["GET"])
|
||||
@api_wrapper
|
||||
def team_info():
|
||||
logged_in = user.is_logged_in()
|
||||
in_team = False
|
||||
owner = False
|
||||
_user = None
|
||||
teamdata = { }
|
||||
search = { }
|
||||
teamname = utils.flat_multi(request.args).get("teamname")
|
||||
if teamname:
|
||||
search.update({ "teamname_lower": teamname.lower() })
|
||||
if logged_in:
|
||||
_user = user.get_user().first()
|
||||
if user.in_team(_user):
|
||||
if "teamname_lower" not in search:
|
||||
search.update({ "tid": _user.tid })
|
||||
in_team = True
|
||||
if bool(search) != False:
|
||||
team = get_team(**search).first()
|
||||
teamdata = get_team_info(**search)
|
||||
if logged_in:
|
||||
in_team = teamdata["tid"] == _user.tid
|
||||
owner = teamdata["captain"] == _user.uid
|
||||
teamdata["in_team"] = in_team
|
||||
if in_team:
|
||||
teamdata["is_owner"] = owner
|
||||
if owner:
|
||||
teamdata["pending_invitations"] = team.get_pending_invitations()
|
||||
teamdata["invitation_requests"] = team.get_invitation_requests()
|
||||
else:
|
||||
if logged_in:
|
||||
teamdata["invited"] = team.get_pending_invitations(toid=_user.uid) is not None
|
||||
teamdata["requested"] = team.get_invitation_requests(frid=_user.uid) is not None
|
||||
else:
|
||||
if logged_in:
|
||||
teamdata["invitations"] = _user.get_invitations()
|
||||
return { "success": 1, "team": teamdata }
|
||||
|
||||
##################
|
||||
# TEAM FUNCTIONS #
|
||||
##################
|
||||
|
||||
__check_teamname = lambda teamname: get_team(teamname_lower=teamname.lower()).first() is None
|
||||
|
||||
TeamSchema = Schema({
|
||||
Required("teamname"): check(
|
||||
([str, Length(min=4, max=32)], "Your teamname should be between 4 and 32 characters long."),
|
||||
([utils.__check_ascii], "Please only use ASCII characters in your teamname."),
|
||||
([__check_teamname], "This teamname is taken, did you forget your password?")
|
||||
),
|
||||
Required("school"): check(
|
||||
([str, Length(min=4, max=40)], "Your school name should be between 4 and 40 characters long."),
|
||||
([utils.__check_ascii], "Please only use ASCII characters in your school name."),
|
||||
),
|
||||
}, extra=True)
|
||||
|
||||
def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None):
|
||||
team = get_team(tid=tid, teamname=teamname, teamname_lower=teamname_lower, owner=owner).first()
|
||||
if team is None:
|
||||
raise WebException("Team not found.")
|
||||
|
||||
place_number, place = team.place()
|
||||
result = {
|
||||
"tid": team.tid,
|
||||
"teamname": team.teamname,
|
||||
"school": team.school,
|
||||
"place": place,
|
||||
"place_number": place_number,
|
||||
"points": team.points(),
|
||||
"members": team.get_members(),
|
||||
"captain": team.owner,
|
||||
"observer": team.is_observer()
|
||||
}
|
||||
return result
|
||||
|
||||
def get_team(tid=None, teamname=None, teamname_lower=None, owner=None):
|
||||
match = {}
|
||||
if teamname != None:
|
||||
match.update({ "teamname": teamname })
|
||||
elif teamname_lower != None:
|
||||
match.update({ "teamname_lower": teamname_lower })
|
||||
elif tid != None:
|
||||
match.update({ "tid": tid })
|
||||
elif owner != None:
|
||||
match.update({ "owner": owner })
|
||||
elif user.is_logged_in():
|
||||
_user = user.get_user().first()
|
||||
if _user.tid is not None:
|
||||
match.update({ "tid": _user.tid })
|
||||
with app.app_context():
|
||||
result = Teams.query.filter_by(**match)
|
||||
return result
|
|
@ -1,334 +0,0 @@
|
|||
from flask import Blueprint, make_response, session, request, redirect, url_for, send_file, abort
|
||||
from werkzeug import secure_filename
|
||||
from flask import current_app as app
|
||||
from voluptuous import Schema, Length, Required
|
||||
|
||||
from models import db, LoginTokens, Users
|
||||
from decorators import api_wrapper, WebException
|
||||
from schemas import verify_to_schema, check
|
||||
|
||||
import datetime
|
||||
import logger, logging
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import team
|
||||
import utils
|
||||
|
||||
from PIL import Image
|
||||
|
||||
###############
|
||||
# USER ROUTES #
|
||||
###############
|
||||
|
||||
blueprint = Blueprint("user", __name__)
|
||||
|
||||
@blueprint.route("/forgot", methods=["POST"])
|
||||
@blueprint.route("/forgot/<token>", methods=["GET", "POST"])
|
||||
@api_wrapper
|
||||
def user_forgot_password(token=None):
|
||||
params = utils.flat_multi(request.form)
|
||||
if token is not None:
|
||||
user = get_user(reset_token=token).first()
|
||||
if user is None:
|
||||
raise WebException("Invalid reset token.")
|
||||
|
||||
# We are viewing the actual reset form
|
||||
if request.method == "GET":
|
||||
return { "success": 1, "message": ""}
|
||||
|
||||
# Submission of actual reset form
|
||||
if request.method == "POST":
|
||||
password = params.get("password")
|
||||
confirm_password = params.get("confirm_password")
|
||||
if password != confirm_password:
|
||||
raise WebException("Passwords do not match.")
|
||||
else:
|
||||
user.password = utils.hash_password(password)
|
||||
user.reset_token = None
|
||||
current_session = db.session.object_session(user)
|
||||
current_session.add(user)
|
||||
current_session.commit()
|
||||
return { "success": 1, "message": "Success!" }
|
||||
else:
|
||||
email = params.get("email").lower()
|
||||
|
||||
user = get_user(email=email).first()
|
||||
if user is None:
|
||||
raise WebException("User with that email does not exist.")
|
||||
|
||||
token = utils.generate_string(length=64)
|
||||
user.reset_token = token
|
||||
current_session = db.session.object_session(user)
|
||||
current_session.add(user)
|
||||
current_session.commit()
|
||||
|
||||
reset_link = "%s/forgot/%s" % ("127.0.0.1:8000", token)
|
||||
subject = "EasyCTF password reset"
|
||||
body = """%s,\n\nA request to reset your EasyCTF password has been made. If you did not request this password reset, you may safely ignore this email and delete it.\n\nYou may reset your password by clicking this link or pasting it to your browser.\n\n%s\n\nThis link can only be used once, and will lead you to a page where you can reset your password.\n\nGood luck!\n\n- The EasyCTF Team""" % (user.username, reset_link)
|
||||
response = utils.send_email(email, subject, body).json()
|
||||
if "Queued" in response["message"]:
|
||||
return { "success": 1, "message": "Email sent to %s" % email }
|
||||
else:
|
||||
raise WebException(response["message"])
|
||||
|
||||
@blueprint.route("/register", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_register():
|
||||
params = utils.flat_multi(request.form)
|
||||
|
||||
name = params.get("name")
|
||||
email = params.get("email")
|
||||
username = params.get("username")
|
||||
password = params.get("password")
|
||||
password_confirm = params.get("password_confirm")
|
||||
utype = int(params.get("type"))
|
||||
|
||||
if password != password_confirm:
|
||||
raise WebException("Passwords do not match.")
|
||||
verify_to_schema(UserSchema, params)
|
||||
|
||||
user = Users(name, username, email, password, utype)
|
||||
with app.app_context():
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
utils.generate_identicon(email, user.uid)
|
||||
|
||||
logger.log(__name__, "%s registered with %s" % (name.encode("utf-8"), email.encode("utf-8")))
|
||||
login_user(username, password)
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/logout", methods=["GET"])
|
||||
@api_wrapper
|
||||
def user_logout():
|
||||
sid = session["sid"]
|
||||
username = session["username"]
|
||||
with app.app_context():
|
||||
expired = LoginTokens.query.filter_by(username=username).all()
|
||||
for expired_token in expired: db.session.delete(expired_token)
|
||||
db.session.commit()
|
||||
session.clear()
|
||||
|
||||
@blueprint.route("/login", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_login():
|
||||
params = utils.flat_multi(request.form)
|
||||
|
||||
username = params.get("username")
|
||||
password = params.get("password")
|
||||
|
||||
result = login_user(username, password)
|
||||
if result != True:
|
||||
raise WebException("Please check if your username/password are correct.")
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/status", methods=["GET"])
|
||||
@api_wrapper
|
||||
def user_status():
|
||||
logged_in = is_logged_in()
|
||||
result = {
|
||||
"success": 1,
|
||||
"logged_in": logged_in,
|
||||
"admin": is_admin(),
|
||||
"competition": is_admin(),
|
||||
"in_team": in_team(get_user()),
|
||||
"username": session["username"] if logged_in else "",
|
||||
}
|
||||
if logged_in:
|
||||
result["has_team"] = in_team(get_user().first())
|
||||
|
||||
return result
|
||||
|
||||
@blueprint.route("/info", methods=["GET"])
|
||||
@api_wrapper
|
||||
def user_info():
|
||||
logged_in = is_logged_in()
|
||||
username = utils.flat_multi(request.args).get("username")
|
||||
if username is None:
|
||||
if logged_in:
|
||||
username = session["username"]
|
||||
if username is None:
|
||||
raise WebException("No user specified.")
|
||||
me = False if not("username" in session) else username.lower() == session["username"].lower()
|
||||
user = get_user(username_lower=username.lower()).first()
|
||||
if user is None:
|
||||
raise WebException("User not found.")
|
||||
|
||||
show_email = me if logged_in else False
|
||||
user_in_team = in_team(user)
|
||||
userdata = {
|
||||
"user_found": True,
|
||||
"name": user.name,
|
||||
"username": user.username,
|
||||
"type": ["Student", "Instructor", "Observer"][user.utype - 1],
|
||||
"admin": user.admin,
|
||||
"registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z",
|
||||
"me": me,
|
||||
"show_email": show_email,
|
||||
"in_team": user_in_team,
|
||||
"uid": user.uid
|
||||
}
|
||||
if show_email:
|
||||
userdata["email"] = user.email
|
||||
if user_in_team:
|
||||
userdata["team"] = team.get_team_info(tid=user.tid)
|
||||
if me and not(user_in_team):
|
||||
invitations = user.get_invitations()
|
||||
userdata["invitations"] = invitations
|
||||
return { "success": 1, "user": userdata }
|
||||
|
||||
@blueprint.route("/avatar/<uid>", methods=["GET"])
|
||||
def user_avatar(uid):
|
||||
uid = int(uid)
|
||||
try:
|
||||
return send_file("pfp/%d.png" % uid, mimetype="image/png")
|
||||
except:
|
||||
user = get_user(uid=uid).first()
|
||||
if user is not None:
|
||||
utils.generate_identicon(user.email, user.uid)
|
||||
return send_file("pfp/%d.png" % uid, mimetype="image/png")
|
||||
return abort(404)
|
||||
|
||||
@blueprint.route("/avatar/upload", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_avatar_upload():
|
||||
logged_in = is_logged_in()
|
||||
if not logged_in:
|
||||
raise WebException("You're not logged in.")
|
||||
|
||||
_user = get_user().first()
|
||||
f = request.files["file"]
|
||||
if f is None:
|
||||
raise WebException("Please upload something.")
|
||||
|
||||
fname = "/tmp/" + secure_filename(utils.generate_string())
|
||||
f.save(fname)
|
||||
|
||||
try:
|
||||
pfp = "pfp/%d.png" % _user.uid
|
||||
os.remove(pfp)
|
||||
im = Image.open(fname)
|
||||
im = im.resize((256, 256), Image.ANTIALIAS)
|
||||
im.save(open(pfp, "w"), "PNG")
|
||||
return { "success": 1, "message": "Uploaded!" }
|
||||
except Exception, e:
|
||||
raise WebException(str(e))
|
||||
|
||||
@blueprint.route("/avatar/remove", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_avatar_remove():
|
||||
logged_in = is_logged_in()
|
||||
if not logged_in:
|
||||
raise WebException("You're not logged in.")
|
||||
_user = get_user().first()
|
||||
|
||||
try:
|
||||
pfp = "pfp/%d.png" % _user.uid
|
||||
os.remove(pfp)
|
||||
return { "success": 1, "message": "Removed!" }
|
||||
except Exception, e:
|
||||
raise WebException(str(e))
|
||||
|
||||
##################
|
||||
# USER FUNCTIONS #
|
||||
##################
|
||||
|
||||
__check_username = lambda username: get_user(username_lower=username.lower()).first() is None
|
||||
__check_email = lambda email: get_user(email=email.lower()).first() is None
|
||||
|
||||
UserSchema = Schema({
|
||||
Required("email"): check(
|
||||
([str, Length(min=4, max=128)], "Your email should be between 4 and 128 characters long."),
|
||||
([__check_email], "Someone already registered this email."),
|
||||
([utils.__check_email_format], "Please enter a legit email.")
|
||||
),
|
||||
Required("name"): check(
|
||||
([str, Length(min=4, max=128)], "Your name should be between 4 and 128 characters long.")
|
||||
),
|
||||
Required("username"): check(
|
||||
([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."),
|
||||
([utils.__check_alphanumeric], "Please only use alphanumeric characters in your username."),
|
||||
([__check_username], "This username is taken, did you forget your password?")
|
||||
),
|
||||
Required("password"): check(
|
||||
([str, Length(min=4, max=64)], "Your password should be between 4 and 64 characters long."),
|
||||
([utils.__check_ascii], "Please only use ASCII characters in your password."),
|
||||
),
|
||||
Required("type"): check(
|
||||
([str, lambda x: x.isdigit()], "Please use the online form.")
|
||||
),
|
||||
"notify": str
|
||||
}, extra=True)
|
||||
|
||||
def login_user(username, password):
|
||||
user = get_user(username_lower=username.lower()).first()
|
||||
if user is None: return False
|
||||
correct = utils.check_password(user.password, password)
|
||||
if not correct: return False
|
||||
|
||||
useragent = request.headers.get("User-Agent")
|
||||
ip = request.remote_addr
|
||||
|
||||
with app.app_context():
|
||||
expired = LoginTokens.query.filter_by(username=username).all()
|
||||
for expired_token in expired: db.session.delete(expired_token)
|
||||
|
||||
token = LoginTokens(user.uid, user.username, ua=useragent, ip=ip)
|
||||
db.session.add(token)
|
||||
db.session.commit()
|
||||
|
||||
session["sid"] = token.sid
|
||||
session["username"] = token.username
|
||||
session["admin"] = user.admin == True
|
||||
if user.tid is not None and user.tid >= 0:
|
||||
session["tid"] = user.tid
|
||||
|
||||
return True
|
||||
|
||||
def is_logged_in():
|
||||
if not("sid" in session and "username" in session): return False
|
||||
sid = session["sid"]
|
||||
username = session["username"]
|
||||
token = LoginTokens.query.filter_by(sid=sid).first()
|
||||
if token is None: return False
|
||||
|
||||
useragent = request.headers.get("User-Agent")
|
||||
ip = request.remote_addr
|
||||
|
||||
if token.username != username: return False
|
||||
if token.ua != useragent: return False
|
||||
return True
|
||||
|
||||
def get_user(username=None, username_lower=None, email=None, uid=None, reset_token=None):
|
||||
match = {}
|
||||
if username != None:
|
||||
match.update({ "username": username })
|
||||
elif username_lower != None:
|
||||
match.update({ "username_lower": username_lower })
|
||||
elif uid != None:
|
||||
match.update({ "uid": uid })
|
||||
elif email != None:
|
||||
match.update({ "email": email })
|
||||
elif is_logged_in():
|
||||
match.update({ "username": session["username"] })
|
||||
elif reset_token != None:
|
||||
match.update({ "reset_token": reset_token })
|
||||
with app.app_context():
|
||||
result = Users.query.filter_by(**match)
|
||||
return result
|
||||
|
||||
def is_admin():
|
||||
return is_logged_in() and "admin" in session and session["admin"]
|
||||
|
||||
def in_team(user):
|
||||
return hasattr(user, "tid") and user.tid >= 0
|
||||
|
||||
def validate_captcha(form):
|
||||
if "captcha_response" not in form:
|
||||
return False
|
||||
captcha_response = form["captcha_response"]
|
||||
data = {"secret": "6Lc4xhMTAAAAACFaG2NyuKoMdZQtSa_1LI76BCEu", "response": captcha_response}
|
||||
response = requests.post("https://www.google.com/recaptcha/api/siteverify", data=data)
|
||||
return response.json()["success"]
|
|
@ -1,102 +0,0 @@
|
|||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import requests
|
||||
import string
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from flask import current_app as app
|
||||
from functools import wraps
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
__check_email_format = lambda email: re.match(".+@.+\..{2,}", email) is not None
|
||||
__check_ascii = lambda s: all(c in string.printable for c in s)
|
||||
__check_alphanumeric = lambda s: all(c in string.digits + string.ascii_uppercase + string.ascii_lowercase for c in s)
|
||||
|
||||
def hash_password(s):
|
||||
return generate_password_hash(s)
|
||||
|
||||
def check_password(hashed_password, try_password):
|
||||
return check_password_hash(hashed_password, try_password)
|
||||
|
||||
def generate_string(length=32, alpha=string.hexdigits):
|
||||
return "".join([random.choice(alpha) for x in range(length)])
|
||||
|
||||
def unix_time_millis(dt):
|
||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||
return (dt - epoch).total_seconds() * 1000.0
|
||||
|
||||
def get_time_since_epoch():
|
||||
return unix_time_millis(datetime.datetime.now())
|
||||
|
||||
def flat_multi(multidict):
|
||||
flat = {}
|
||||
for key, values in multidict.items():
|
||||
value = values[0] if type(values) == list and len(values) == 1 else values
|
||||
flat[key] = value.encode("utf-8")
|
||||
return flat
|
||||
|
||||
def send_email(recipient, subject, body):
|
||||
api_key = app.config["MG_API_KEY"]
|
||||
data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]),
|
||||
"to": recipient,
|
||||
"subject": subject,
|
||||
"text": body
|
||||
}
|
||||
auth = ("api", api_key)
|
||||
return requests.post("https://api.mailgun.net/v3/%s/messages" % (app.config["MG_HOST"]), auth=auth, data=data)
|
||||
|
||||
def generate_identicon(email, filename):
|
||||
email = email.strip().lower()
|
||||
h = hashlib.sha1(email).hexdigest()
|
||||
size = 256
|
||||
margin = 0.08
|
||||
baseMargin = int(size * margin)
|
||||
cell = int((size - baseMargin * 2.0) / 5)
|
||||
margin = int((size - cell * 5.0) / 2)
|
||||
image = Image.new("RGB", (size, size))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
def hsl2rgb(h, s, b):
|
||||
h *= 6
|
||||
s1 = []
|
||||
s *= b if b < 0.5 else 1-b
|
||||
b += s
|
||||
s1.append(b)
|
||||
s1.append(b - h % 1 * s * 2)
|
||||
s *= 2
|
||||
b -= s
|
||||
s1.append(b)
|
||||
s1.append(b)
|
||||
s1.append(b + h % 1 * s)
|
||||
s1.append(b + s)
|
||||
|
||||
return [
|
||||
s1[~~h % 6], s1[(h|16) % 6], s1[(h|8) % 6]
|
||||
]
|
||||
|
||||
rgb = hsl2rgb(int(h[-7:], 16) & 0xfffffff, 0.5, 0.7)
|
||||
bg = (255, 255, 255)
|
||||
fg = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
|
||||
|
||||
# print fg, bg
|
||||
draw.rectangle([(0, 0), (size, size)], fill=bg)
|
||||
|
||||
for i in range(15):
|
||||
c = bg if int(h[i], 16) % 2 == 1 else fg
|
||||
if i < 5:
|
||||
draw.rectangle([(2*cell + margin, i*cell + margin), (3*cell + margin, (i+1)*cell + margin)], fill=c)
|
||||
elif i < 10:
|
||||
draw.rectangle([(1*cell + margin, (i-5)*cell + margin), (2*cell + margin, (i-4)*cell + margin)], fill=c)
|
||||
draw.rectangle([(3*cell + margin, (i-5)*cell + margin), (4*cell + margin, (i-4)*cell + margin)], fill=c)
|
||||
elif i < 15:
|
||||
draw.rectangle([(0*cell + margin, (i-10)*cell + margin), (1*cell + margin, (i-9)*cell + margin)], fill=c)
|
||||
draw.rectangle([(4*cell + margin, (i-10)*cell + margin), (5*cell + margin, (i-9)*cell + margin)], fill=c)
|
||||
|
||||
image.save(open("pfp/%s.png" % filename, "w"), "PNG")
|
||||
return
|
120
server/app.py
|
@ -1,120 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from flask import Flask, send_file
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
import api
|
||||
import config
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from api.decorators import api_wrapper
|
||||
|
||||
app.config.from_object(config)
|
||||
|
||||
if not os.path.exists(app.config["UPLOAD_FOLDER"]):
|
||||
os.makedirs(app.config["UPLOAD_FOLDER"])
|
||||
if not os.path.exists("pfp"):
|
||||
os.makedirs("pfp")
|
||||
|
||||
with app.app_context():
|
||||
from api.models import db, Files, Teams, Problems, Solves, Users
|
||||
db.init_app(app)
|
||||
db.create_all()
|
||||
|
||||
app.db = db
|
||||
|
||||
app.secret_key = config.SECRET_KEY
|
||||
|
||||
app.register_blueprint(api.admin.blueprint, url_prefix="/api/admin")
|
||||
app.register_blueprint(api.problem.blueprint, url_prefix="/api/problem")
|
||||
app.register_blueprint(api.stats.blueprint, url_prefix="/api/stats")
|
||||
app.register_blueprint(api.team.blueprint, url_prefix="/api/team")
|
||||
app.register_blueprint(api.user.blueprint, url_prefix="/api/user")
|
||||
api.logger.initialize_logs()
|
||||
|
||||
@app.route("/api")
|
||||
@api_wrapper
|
||||
def api_main():
|
||||
return { "success": 1, "message": "The API is online." }
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return send_file("../web/index.html")
|
||||
|
||||
def run(args=None):
|
||||
with app.app_context():
|
||||
try:
|
||||
keyword_args = dict(args._get_kwargs())
|
||||
app.debug = keyword_args["debug"] if "debug" in keyword_args else False
|
||||
except: pass
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
def load_problems(args):
|
||||
keyword_args = dict(args._get_kwargs())
|
||||
force = keyword_args["force"] if "force" in keyword_args else False
|
||||
|
||||
if not os.path.exists(config.PROBLEM_DIR):
|
||||
api.logger.log("api.problem.log", "Problems directory doesn't exist.")
|
||||
return
|
||||
|
||||
for (dirpath, dirnames, filenames) in os.walk(config.PROBLEM_DIR):
|
||||
if "problem.json" in filenames:
|
||||
json_file = os.path.join(dirpath, "problem.json")
|
||||
contents = open(json_file).read()
|
||||
try:
|
||||
data = json.loads(contents)
|
||||
except ValueError as e:
|
||||
api.logger.log("api.problem.log", "Invalid JSON format in file {filename} ({exception})".format(filename=json_file, exception=e))
|
||||
continue
|
||||
if not isinstance(data, dict):
|
||||
api.logger.log("api.problem.log", "{filename} is not a dict.".format(filename=json_file))
|
||||
continue
|
||||
|
||||
missing_keys = []
|
||||
for key in ["pid", "title", "category", "value"]:
|
||||
if key not in data:
|
||||
missing_keys.append(key)
|
||||
if len(missing_keys) > 0:
|
||||
api.logger.log("api.problem.log", "{filename} is missing the following keys: {keys}".format(filename=json_file, keys=", ".join(missing_keys)))
|
||||
continue
|
||||
|
||||
relative_path = os.path.relpath(dirpath, config.PROBLEM_DIR)
|
||||
data["description"] = open(os.path.join(dirpath, "description.md"), "r").read()
|
||||
api.logger.log("api.problem.log", "Found problem '{}'".format(data["title"]))
|
||||
with app.app_context():
|
||||
try:
|
||||
api.problem.insert_problem(data, force=force)
|
||||
except Exception as e:
|
||||
api.logger.log("api.problem.log", "Problem '{}' was not added to the database. Error: {}".format(data["title"], e))
|
||||
api.logger.log("api.problem.log", "{}".format(traceback.format_exc()))
|
||||
|
||||
api.logger.log("api.problem.log", "Finished.")
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description="EasyCTF Server Management")
|
||||
|
||||
subparser = parser.add_subparsers(help="Select one of the following actions.")
|
||||
parser_problems = subparser.add_parser("problems", help="Manage problems.")
|
||||
subparser_problems = parser_problems.add_subparsers(help="Select one of the following actions.")
|
||||
parser_problems_load = subparser_problems.add_parser("load", help="Load all problems into database.")
|
||||
parser_problems_load.add_argument("-f", "--force", action="store_true", help="Force overwrite problems.", default=False)
|
||||
parser_problems_load.set_defaults(func=load_problems)
|
||||
|
||||
parser_run = subparser.add_parser("run", help="Run the server.")
|
||||
parser_run.add_argument("-d", "--debug", action="store_true", help="Run the server in debug mode.", default=False)
|
||||
parser_run.set_defaults(func=run)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if "func" in args:
|
||||
args.func(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,27 +0,0 @@
|
|||
import os
|
||||
|
||||
secret = open(".secret_key", "a+b")
|
||||
contents = secret.read()
|
||||
if not contents:
|
||||
key = os.urandom(128)
|
||||
secret.write(key)
|
||||
secret.flush()
|
||||
else:
|
||||
key = contents
|
||||
secret.close()
|
||||
|
||||
SECRET_KEY = key
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = "mysql://root:i_hate_passwords@localhost/easyctf"
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
UPLOAD_FOLDER = os.path.normpath("../web/files")
|
||||
|
||||
CTF_BEGIN = 0 # To be used later
|
||||
CTF_END = 0 # To be used later
|
||||
|
||||
MG_HOST = ""
|
||||
MG_API_KEY = ""
|
||||
ADMIN_EMAIL = ""
|
||||
|
||||
PROBLEM_DIR = "../problems"
|
158
web/css/c3.css
|
@ -1,158 +0,0 @@
|
|||
/*-- Chart --*/
|
||||
.c3 svg {
|
||||
font: 10px sans-serif; }
|
||||
|
||||
.c3 path, .c3 line {
|
||||
fill: none;
|
||||
stroke: #000; }
|
||||
|
||||
.c3 text {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none; }
|
||||
|
||||
.c3-legend-item-tile, .c3-xgrid-focus, .c3-ygrid, .c3-event-rect, .c3-bars path {
|
||||
shape-rendering: crispEdges; }
|
||||
|
||||
.c3-chart-arc path {
|
||||
stroke: #fff; }
|
||||
|
||||
.c3-chart-arc text {
|
||||
fill: #fff;
|
||||
font-size: 13px; }
|
||||
|
||||
/*-- Axis --*/
|
||||
/*-- Grid --*/
|
||||
.c3-grid line {
|
||||
stroke: #aaa; }
|
||||
|
||||
.c3-grid text {
|
||||
fill: #aaa; }
|
||||
|
||||
.c3-xgrid, .c3-ygrid {
|
||||
stroke-dasharray: 3 3; }
|
||||
|
||||
/*-- Text on Chart --*/
|
||||
.c3-text.c3-empty {
|
||||
fill: #808080;
|
||||
font-size: 2em; }
|
||||
|
||||
/*-- Line --*/
|
||||
.c3-line {
|
||||
stroke-width: 1px; }
|
||||
|
||||
/*-- Point --*/
|
||||
.c3-circle._expanded_ {
|
||||
stroke-width: 1px;
|
||||
stroke: white; }
|
||||
|
||||
.c3-selected-circle {
|
||||
fill: white;
|
||||
stroke-width: 2px; }
|
||||
|
||||
/*-- Bar --*/
|
||||
.c3-bar {
|
||||
stroke-width: 0; }
|
||||
|
||||
.c3-bar._expanded_ {
|
||||
fill-opacity: 0.75; }
|
||||
|
||||
/*-- Focus --*/
|
||||
.c3-target.c3-focused {
|
||||
opacity: 1; }
|
||||
|
||||
.c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
|
||||
stroke-width: 2px; }
|
||||
|
||||
.c3-target.c3-defocused {
|
||||
opacity: 0.3 !important; }
|
||||
|
||||
/*-- Region --*/
|
||||
.c3-region {
|
||||
fill: steelblue;
|
||||
fill-opacity: 0.1; }
|
||||
|
||||
/*-- Brush --*/
|
||||
.c3-brush .extent {
|
||||
fill-opacity: 0.1; }
|
||||
|
||||
/*-- Select - Drag --*/
|
||||
/*-- Legend --*/
|
||||
.c3-legend-item {
|
||||
font-size: 12px; }
|
||||
|
||||
.c3-legend-item-hidden {
|
||||
opacity: 0.15; }
|
||||
|
||||
.c3-legend-background {
|
||||
opacity: 0.75;
|
||||
fill: white;
|
||||
stroke: lightgray;
|
||||
stroke-width: 1; }
|
||||
|
||||
/*-- Tooltip --*/
|
||||
.c3-tooltip-container {
|
||||
z-index: 10; }
|
||||
|
||||
.c3-tooltip {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
background-color: #fff;
|
||||
empty-cells: show;
|
||||
-webkit-box-shadow: 7px 7px 12px -9px #777777;
|
||||
-moz-box-shadow: 7px 7px 12px -9px #777777;
|
||||
box-shadow: 7px 7px 12px -9px #777777;
|
||||
opacity: 0.9; }
|
||||
|
||||
.c3-tooltip tr {
|
||||
border: 1px solid #CCC; }
|
||||
|
||||
.c3-tooltip th {
|
||||
background-color: #aaa;
|
||||
font-size: 14px;
|
||||
padding: 2px 5px;
|
||||
text-align: left;
|
||||
color: #FFF; }
|
||||
|
||||
.c3-tooltip td {
|
||||
font-size: 13px;
|
||||
padding: 3px 6px;
|
||||
background-color: #fff;
|
||||
border-left: 1px dotted #999; }
|
||||
|
||||
.c3-tooltip td > span {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 6px; }
|
||||
|
||||
.c3-tooltip td.value {
|
||||
text-align: right; }
|
||||
|
||||
/*-- Area --*/
|
||||
.c3-area {
|
||||
stroke-width: 0;
|
||||
opacity: 0.2; }
|
||||
|
||||
/*-- Arc --*/
|
||||
.c3-chart-arcs-title {
|
||||
dominant-baseline: middle;
|
||||
font-size: 1.3em; }
|
||||
|
||||
.c3-chart-arcs .c3-chart-arcs-background {
|
||||
fill: #e0e0e0;
|
||||
stroke: none; }
|
||||
|
||||
.c3-chart-arcs .c3-chart-arcs-gauge-unit {
|
||||
fill: #000;
|
||||
font-size: 16px; }
|
||||
|
||||
.c3-chart-arcs .c3-chart-arcs-gauge-max {
|
||||
fill: #777; }
|
||||
|
||||
.c3-chart-arcs .c3-chart-arcs-gauge-min {
|
||||
fill: #777; }
|
||||
|
||||
.c3-chart-arc .c3-gauge-value {
|
||||
fill: #000;
|
||||
/* font-size: 28px !important;*/ }
|
|
@ -1,31 +0,0 @@
|
|||
@font-face {
|
||||
font-family: "Proxima Nova";
|
||||
src: url("/fonts/ProximaNova.woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Proxima Nova";
|
||||
src: url("/fonts/ProximaNovaBold.woff2");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Proxima Nova", Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 15px;
|
||||
> .tab-pane {
|
||||
display: none;
|
||||
}
|
||||
> .active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.NO_HOVER_UNDERLINE_DAMMIT:hover, .NO_HOVER_UNDERLINE_DAMMIT:focus, .NO_HOVER_UNDERLINE_DAMMIT:active {
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
#avatar {
|
||||
border: 1px solid rgb(221, 221, 221);
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
@import url(https://fonts.googleapis.com/css?family=Exo+2);
|
||||
.heading1 {
|
||||
font-size: 75px !important;
|
||||
color: #62AC5B !important;
|
||||
font-family: "Exo 2" !important;
|
||||
}
|
||||
.charts {
|
||||
width:100%;
|
||||
border:4px double black;
|
||||
}
|
||||
.charted {
|
||||
width:5em;
|
||||
height:5em;
|
||||
}
|
||||
#map {
|
||||
height:45em;
|
||||
background-image: url(../images/placeholdermap.jpg);
|
||||
background-size: cover;
|
||||
width:97%;
|
||||
margin-right:auto;
|
||||
margin-left:auto;
|
||||
}
|
||||
::-webkit-input-placeholder, :-moz-placeholder, ::-moz-placeholder, :-ms-input-placeholder {
|
||||
color: #999999;
|
||||
}
|
||||
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
|
||||
background-color:#0066CC;
|
||||
}
|
||||
#menubox {
|
||||
border:none;
|
||||
}
|
||||
.pointvalue {
|
||||
float:right;
|
||||
}
|
||||
.panel.panel-info {
|
||||
margin: 2.5em;
|
||||
}
|
||||
.paragraph {
|
||||
width:50em;
|
||||
text-align: justify;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
}
|
||||
.prizes {
|
||||
width:100%;
|
||||
}
|
||||
#hint {
|
||||
background:#0080FF;
|
||||
border-color:#0180FF;
|
||||
}
|
||||
#check {
|
||||
border-color:#449D44;
|
||||
}
|
||||
.probfile {
|
||||
display:inline;
|
||||
color:#62AC5B;
|
||||
margin-right:1em;
|
||||
margin-left:1em;
|
||||
}
|
||||
.filelink, .filelink:hover {
|
||||
text-decoration:none;
|
||||
}
|
||||
#style1 {
|
||||
background-color: #0080FF;
|
||||
margin: -0.1em;
|
||||
}
|
||||
#style1 .dropdown-menu {
|
||||
background-color: #1a8dff;
|
||||
}
|
||||
#email, #password {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.style4 {
|
||||
margin-left: 10em;
|
||||
margin-right: 10em;
|
||||
}
|
||||
.icn {
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
}
|
||||
.style2 {
|
||||
margin: 0px 0px 0px 0px;
|
||||
padding: 0px 0px 0px 0px;
|
||||
}
|
||||
.style3 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
#question-text {
|
||||
height: 4em;
|
||||
}
|
||||
#question-title {
|
||||
background-color: #D9EDF7;
|
||||
text-align: center;
|
||||
padding: 0px 0px 0px 0px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.intro {
|
||||
text-align: center;
|
||||
}
|
||||
.column {
|
||||
max-width: 450px;
|
||||
}
|
||||
.mainbody {
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
.ui.menu .item img.logo {
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
.main.container {
|
||||
margin-top: 7em;
|
||||
}
|
||||
.wireframe {
|
||||
margin-top: 2em;
|
||||
}
|
||||
.ui.footer.segment {
|
||||
margin: 5em 0em 0em;
|
||||
padding: 5em 0em;
|
||||
}
|
||||
.heading2, table {
|
||||
color: #226C1B;
|
||||
}
|
||||
li {
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
li:hover {
|
||||
background-color: #0066cc;
|
||||
}
|
||||
tr {
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #DDDDDD;
|
||||
}
|
||||
li a, .navbar-brand {
|
||||
color: white !important;
|
||||
}
|
||||
#imp-files {
|
||||
margin-right: auto;
|
||||
padding-left: 2em;
|
||||
}
|
||||
@-webkit-keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.fade_in {
|
||||
opacity: 0;
|
||||
-webkit-animation: fadeIn ease-in-out 1;
|
||||
-moz-animation: fadeIn ease-in-out 1;
|
||||
animation: fadeIn ease-in-out 1;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
-moz-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
-webkit-animation-duration: 1s;
|
||||
-moz-animation-duration: 1s;
|
||||
animation-duration: 1s;
|
||||
}
|
||||
.modal {
|
||||
text-align: center;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.modal:before {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
content: " ";
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.modal-dialog {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
Before Width: | Height: | Size: 516 B |
Before Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 293 B |
Before Width: | Height: | Size: 592 B |
Before Width: | Height: | Size: 458 B |
Before Width: | Height: | Size: 461 B |
Before Width: | Height: | Size: 349 B |
Before Width: | Height: | Size: 430 B |
Before Width: | Height: | Size: 335 B |
Before Width: | Height: | Size: 466 B |
|
@ -1,35 +0,0 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="theme-color" content="#FA6900">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="EasyCTF is a national, online, high school hacking competition run by students. EasyCTF aims to open the door to computer science and cybersecurity for students all around the country.">
|
||||
<meta name="keywords" content="ctf, capture the flag, competition, high school, hacking, students, teachers, computers, computer science, education, flag, capture, school, cryptography, web, forensics, steganography">
|
||||
<meta name="subject" content="Computer Science Education">
|
||||
<meta name="copyright" content="EasyCTF Team">
|
||||
<meta name="language" content="EN">
|
||||
<meta name="robots" content="Index, NoFollow">
|
||||
<meta name="revised" content="Monday, December 21st, 2015, 21:00">
|
||||
<meta name="abstract" content="Online capture the flag competition for high school students">
|
||||
<meta name="topic" content="Computer Science Education">
|
||||
<meta name="summary" content="Computer science education online hacking capture the flag competition for high school students">
|
||||
<meta name="classification" content="Education">
|
||||
<meta name="author" content="Thomas Gerot, Fox Wilson, Jared Zell, James Wang, Charles, Jacob Wood, Michael Zhang">
|
||||
<meta name="designer" content="Thomas Gerot, Charles">
|
||||
<meta name="reply-to" content="team@easyctf.com">
|
||||
<meta name="owner" content="EasyCTF Team">
|
||||
<meta name="url" content="http://easyctf.com">
|
||||
<meta name="identifier-URL" content="http://easyctf.com">
|
||||
<meta name="pagename" content="index.html">
|
||||
<meta name="category" content="Education">
|
||||
<meta name="coverage" content="worldwide">
|
||||
<meta name="distribution" content="global">
|
||||
<meta name="rating" content="general">
|
||||
<meta name="revisit-after" content="3 days">
|
||||
<meta name="subtitle" content="High School Hacking Competition">
|
||||
<meta name="target" content="all">
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<meta name="MobileOptimized" content="320">
|
||||
<meta name="date" content="Dec. 21, 2015">
|
||||
<meta name="search_date" content="Dec. 21, 2015">
|
||||
<meta name="medium" content="Online Competition">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
|
@ -1,96 +0,0 @@
|
|||
<html ng-app="easyctf">
|
||||
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="utf-8">
|
||||
<title>EasyCTF 2016</title>
|
||||
<link href="css/c3.css" rel="stylesheet" type="text/css">
|
||||
<script src="js/d3.v3.min.js" charset="utf-8"></script>
|
||||
<script src="js/c3.min.js"></script>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" integrity="sha384-yNuQMX46Gcak2eQsUzmBYgJ3eBeWYNKhnjyiBqLd1vvtE9kuMtgw6bjwN8J0JauQ" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/css/bootstrap-select.css" integrity="sha384-X6BCz/5YN8CudtAN21w+htVJP5lJNqXP0VBfbGzrX7A/FhjpPIoMtiRRHxRYiKU6" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/css/easyctf.css" />
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js" integrity="sha384-r1y8TJcloKTvouxnYsi4PJAx+nHNr90ibsEn3zznzDzWBN9X3o3kbHLSgcIPtzAp" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-rc.0/angular-route.min.js" integrity="sha384-9MZDoFf10trgrfsQOs9GJhf/mP/sh5weVp3FDSi8h/4TEaV6dloEDkpxGTaOmAs6" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/js/bootstrap-select.min.js" integrity="sha384-1qZEXZBmj54fSiiWT8bZQGEpCumJWDrAoEqMdg6N5bTTLCkU5RXoNeUsKWekRYob" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.3/jquery.timeago.min.js" integrity="sha384-Bap3DetwPgo4GEFvaIDVSIrz5G0mUAUsfCUcEsi+JrrNu7dyj3gBmuAG4hDIGg/4" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/smooth-scroll/7.1.1/js/smooth-scroll.min.js" integrity="sha384-bznoxhRX5dRiE60JhQSru8t7g2RPG9lwqvyut8sjFFWmsAlp+R38e7DiATv1YyIu" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js" integrity="sha384-tSi+YsgNwyohDGfW/VhY51IK3RKAPYDcj1sNXJ16oRAyDP++K0NCzSCUW78EMFmf" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.2/ace.js" integrity="sha384-niN+bRlaXMBoq/f5L4zKOEYGxuD0YRGBocto9LP2fB1UiMfxrymATRN4GqjVUt6J" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/js/epiceditor.min.js" integrity="sha384-jV5EM0s4BWbqIJxNwLbyMfgHMQWPkSn2oytRUMPSne2YaT5TAYcl7R0m1c1XsfAb" crossorigin="anonymous"></script>
|
||||
<script src="/js/easyctf.js"></script>
|
||||
<script src="/js/admin.js"></script>
|
||||
</head>
|
||||
|
||||
<body ng-controller="mainController">
|
||||
<div id="site-message" style="margin: 0"></div>
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="main-navbar" aria-expanded="false">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">EasyCTF</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/scoreboard">Scoreboard</a></li>
|
||||
<li><a href="/learn">Learn</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==false">
|
||||
<li><a href="/register">Register</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==true">
|
||||
<div ng-show="config.navbar['competition_started']==true">
|
||||
</div>
|
||||
<li><a href="/chat">Chat</a></li>
|
||||
<li class="dropdown" ng-show="config.navbar['competition']==true">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Competition <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/problems">Problems</a></li>
|
||||
<li><a href="/programming">Programming</a></li>
|
||||
<li><a href="/shell">Shell</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-show="config.navbar['competition']!=true&&config.navbar['in_team']!=true"><a href="/findteam">Find Team</a></li>
|
||||
<li class="dropdown" ng-show="config.navbar['admin']==true">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/admin/stats">Statistics</a></li>
|
||||
<li><a href="/admin/problems">Problem Editor</a></li>
|
||||
<li><a href="/admin/teams">Team Management</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="/admin/settings">CTF Settings</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ config.navbar["username"] }} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/profile">Profile</a></li>
|
||||
<li><a href="/team">Team</a></li>
|
||||
<li><a href="/help">Help</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="/settings">Settings</a></li>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="mainContent" class="container">
|
||||
<div ng-view></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,5 +0,0 @@
|
|||
$(document).ready(function() {
|
||||
$(".panel-title > a[data-toggle=collapse]").click(function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
|
@ -1,121 +0,0 @@
|
|||
function render_problems() {
|
||||
$.post("/api/admin/problem/data", {
|
||||
}, function(data) {
|
||||
data = data["data"];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
files = data[i]["files"];
|
||||
var checked = "";
|
||||
if (data[i]["disabled"]) {
|
||||
checked = "checked";
|
||||
}
|
||||
problem =
|
||||
`<div class="panel panel-info">
|
||||
<form method="POST" onsubmit="return false;">
|
||||
<input type="hidden" name="pid" value="` + data[i]["pid"] + `">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="name" placeholder="Name" autocomplete="on" class="form-control" value="` + data[i]["name"] + `">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="category" placeholder="Category" autocomplete="on" class="form-control" value="` + data[i]["category"] + `">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<textarea type="text" name="description" placeholder="Description" autocomplete="on" class="form-control">` + data[i]["description"] + `</textarea>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="flag" placeholder="Flag" autocomplete="off" class="form-control" value="` + data[i]["flag"] + `">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="hint" placeholder="Hint" autocomplete="off" class="form-control" value="` + data[i]["hint"] + `">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<input type="number" name="value" placeholder="Value" autocomplete="off" class="form-control-number" value="` + data[i]["value"] + `">
|
||||
<label><input type="checkbox" name="disabled" value="1"` + checked + `>Disabled</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">`
|
||||
|
||||
for (var j = 0; j < files.length; j++) {
|
||||
file_name = files[j].split("/").pop();
|
||||
problem +=
|
||||
`<a href="` + files[j] + `" class="filelink" target="_blank">
|
||||
<h4 class="probfile">` + file_name + `</h4>
|
||||
</a>`
|
||||
}
|
||||
|
||||
problem += `<br>
|
||||
<div id="hint_` + data[i]["pid"] + `" style="display:none">` + data[i]["hint"] + `</div>
|
||||
<div class="row" id="status_` + data[i]["pid"] + `"></div><br>
|
||||
<input class="btn btn-success" type="submit" name="update" value="Update">
|
||||
<input class="btn btn-danger" name="delete-modal" type="button" data-toggle="modal" data-target="#delete-modal" value="Delete">
|
||||
</div></form></div>`
|
||||
$("#problems").append(problem);
|
||||
}
|
||||
$("[name=update]").click(function(e) {
|
||||
var problem = $(this).parents("form:first");
|
||||
var pid = $("input[name=pid]", problem).val();
|
||||
var name = $("input[name=name]", problem).val();
|
||||
var description = $("textarea[name=description]", problem).val();
|
||||
var hint = $("input[name=hint]", problem).val();
|
||||
var category = $("input[name=category]", problem).val();
|
||||
var value = $("input[name=value]", problem).val();
|
||||
var flag = $("input[name=flag]", problem).val();
|
||||
var disabled = $("input[name=disabled]", problem).prop("checked") ? 1 : 0;
|
||||
update_problem(pid, name, category, description, hint, flag, disabled, value);
|
||||
});
|
||||
$("[name=delete-modal]").click(function(e) {
|
||||
var problem = $(this).parents("form:first");
|
||||
var pid = $("input[name=pid]", problem).val();
|
||||
var div = $(this).closest("div.panel");
|
||||
$("#delete").off().click(function(e) {
|
||||
delete_problem(pid, div);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function update_problem(pid, name, category, description, hint, flag, disabled, value) {
|
||||
$.post("/api/problem/update", {
|
||||
pid: pid,
|
||||
name: name,
|
||||
category: category,
|
||||
description: description,
|
||||
hint: hint,
|
||||
flag: flag,
|
||||
disabled: disabled,
|
||||
value: value
|
||||
}, function(data) {
|
||||
if (data.success == 1) {
|
||||
display_message("status_" + pid, "success", data.message, function() {});
|
||||
} else {
|
||||
display_message("status_" + pid, "danger", data.message, function() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delete_problem(pid, div) {
|
||||
$.post("/api/problem/delete", {
|
||||
pid: pid
|
||||
}, function(data) {
|
||||
if (data.success == 1) {
|
||||
display_message("delete_status", "success", data.message, function() {
|
||||
div.slideUp("normal", function() {
|
||||
$(this).remove();
|
||||
$("#delete-modal").modal("hide");
|
||||
} );
|
||||
});
|
||||
} else {
|
||||
display_message("delete_status", "warning", data.message, function() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
render_problems();
|
||||
});
|
5
web/js/c3.min.js
vendored
5
web/js/d3.v3.min.js
vendored
|
@ -1,423 +0,0 @@
|
|||
var app = angular.module("easyctf", [ "ngRoute" ]);
|
||||
|
||||
app.config(["$compileProvider", function($compileProvider) {
|
||||
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|javascript):/);
|
||||
}]);
|
||||
app.config(function($routeProvider, $locationProvider) {
|
||||
$routeProvider.when("/", {
|
||||
templateUrl: "pages/home.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/about", {
|
||||
templateUrl: "pages/about.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/chat", {
|
||||
templateUrl: "pages/chat.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/learn", {
|
||||
templateUrl: "pages/learn.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/login", {
|
||||
templateUrl: "pages/login.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/logout", {
|
||||
templateUrl: "pages/blank.html",
|
||||
controller: "logoutController"
|
||||
})
|
||||
.when("/profile", {
|
||||
templateUrl: "pages/profile.html",
|
||||
controller: "profileController"
|
||||
})
|
||||
.when("/profile/:username", {
|
||||
templateUrl: "pages/profile.html",
|
||||
controller: "profileController"
|
||||
})
|
||||
.when("/register", {
|
||||
templateUrl: "pages/register.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/scoreboard", {
|
||||
templateUrl: "pages/scoreboard.html",
|
||||
controller: "scoreboardController"
|
||||
})
|
||||
.when("/settings", {
|
||||
templateUrl: "pages/settings.html",
|
||||
controller: "settingsController"
|
||||
})
|
||||
.when("/forgot", {
|
||||
templateUrl: "pages/forgot.html",
|
||||
controller: "resetController"
|
||||
})
|
||||
.when("/forgot/:token", {
|
||||
templateUrl: "pages/forgot.html",
|
||||
controller: "resetController"
|
||||
})
|
||||
.when("/team", {
|
||||
templateUrl: "pages/team.html",
|
||||
controller: "teamController"
|
||||
})
|
||||
.when("/team/:teamname", {
|
||||
templateUrl: "pages/team.html",
|
||||
controller: "teamController"
|
||||
})
|
||||
.when("/admin/problems", {
|
||||
templateUrl: "pages/admin/problems.html",
|
||||
controller: "adminProblemsController"
|
||||
})
|
||||
.otherwise({
|
||||
templateUrl: "pages/404.html",
|
||||
controller: "mainController"
|
||||
});
|
||||
$locationProvider.html5Mode(true);
|
||||
});
|
||||
|
||||
function api_call(method, url, data, callback_success, callback_fail) {
|
||||
if (method.toLowerCase() == "post") {
|
||||
data["csrf_token"] = $.cookie("csrf_token");
|
||||
}
|
||||
$.ajax({
|
||||
"type": method,
|
||||
"datatype": "json",
|
||||
"data": data,
|
||||
"url": url,
|
||||
"cache": false
|
||||
}).done(callback_success).fail(callback_fail);
|
||||
}
|
||||
|
||||
function permanent_message(containerId, alertType, message, callback) {
|
||||
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\" style=\"margin:0;\">" + message + "</div>");
|
||||
$("#" + containerId).hide().slideDown("fast", "swing");
|
||||
};
|
||||
|
||||
function display_message(containerId, alertType, message, callback) {
|
||||
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>");
|
||||
$("#" + containerId).hide().slideDown("fast", "swing", function() {
|
||||
window.setTimeout(function () {
|
||||
$("#" + containerId).slideUp("fast", "swing", callback);
|
||||
}, message.length * 55);
|
||||
});
|
||||
};
|
||||
|
||||
app.controller("mainController", ["$scope", "$http", function($scope, $http) {
|
||||
$scope.config = { navbar: { } };
|
||||
$scope.timestamp = Date.now();
|
||||
api_call("GET", "/api/user/status", {}, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
delete result["success"];
|
||||
$scope.config.navbar = result;
|
||||
$scope.$emit("loginStatus");
|
||||
} else {
|
||||
$scope.config.navbar.logged_in = false;
|
||||
}
|
||||
$scope.$apply();
|
||||
}, function() {
|
||||
$scope.config.navbar.logged_in = false;
|
||||
$scope.$apply();
|
||||
permanent_message("site-message", "danger", "<div class='container'>The EasyCTF API server is currently down. We're working to fix this error right away. Follow <a href='http://twitter.com/easyctf' target='_blank'>@easyctf</a> for status updates.</div>");
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("logoutController", function() {
|
||||
api_call("GET", "/api/user/logout", {}, function(result) {
|
||||
location.href = "/";
|
||||
});
|
||||
});
|
||||
|
||||
app.controller("profileController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
|
||||
var data = { };
|
||||
if ("username" in $routeParams) data["username"] = $routeParams["username"];
|
||||
$controller("mainController", { $scope: $scope });
|
||||
api_call("GET", "/api/user/info", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.user = result["user"];
|
||||
}
|
||||
$scope.$apply();
|
||||
$(".timeago").timeago();
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("loginController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
$scope.$on("loginStatus", function() {
|
||||
if ($scope.config["navbar"].logged_in != true) {
|
||||
location.href = "/login";
|
||||
return;
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("teamController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
|
||||
var data = { };
|
||||
if ("teamname" in $routeParams) {
|
||||
data["teamname"] = $routeParams["teamname"];
|
||||
} else {
|
||||
$controller("loginController", { $scope: $scope });
|
||||
}
|
||||
api_call("GET", "/api/team/info", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.team = result["team"];
|
||||
}
|
||||
$scope.$apply();
|
||||
$(".timeago").timeago();
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("scoreboardController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
api_call("GET", "/api/stats/scoreboard", { }, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.scoreboard = result["scoreboard"];
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("resetController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
|
||||
var data = { };
|
||||
$scope.token = false;
|
||||
data["csrf_token"] = $.cookie("csrf_token");
|
||||
if ("token" in $routeParams) {
|
||||
$scope.token = true;
|
||||
token = $routeParams["token"];
|
||||
api_call("GET", "/api/user/forgot/" + token, data, function(data) {
|
||||
$scope.body = data["message"];
|
||||
$scope.success = data["success"]
|
||||
$scope.$apply();
|
||||
});
|
||||
} else {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
}
|
||||
}]);
|
||||
|
||||
app.controller("adminController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
$scope.$on("loginStatus", function() {
|
||||
if ($scope.config["navbar"].logged_in != true) {
|
||||
location.href = "/login";
|
||||
return;
|
||||
}
|
||||
if ($scope.config["navbar"].admin != true) {
|
||||
location.href = "/profile";
|
||||
return;
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("adminProblemsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("adminController", { $scope: $scope });
|
||||
api_call("GET", "/api/admin/problems/list", {}, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.problems = result["problems"];
|
||||
} else {
|
||||
$scope.problems = [];
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("settingsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("loginController", { $scope: $scope });
|
||||
api_call("GET", "/api/user/info", {}, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.user = result["user"];
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
}]);
|
||||
|
||||
$.fn.serializeObject = function() {
|
||||
var a, o;
|
||||
o = {};
|
||||
a = this.serializeArray();
|
||||
$.each(a, function() {
|
||||
if (o[this.name]) {
|
||||
if (!o[this.name].push) {
|
||||
o[this.name] = [o[this.name]];
|
||||
}
|
||||
return o[this.name].push(this.value || "");
|
||||
} else {
|
||||
return o[this.name] = this.value || "";
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
// register page
|
||||
|
||||
var register_form = function() {
|
||||
var input = "#register_form input";
|
||||
var data = $("#register_form").serializeObject();
|
||||
$(input).attr("disabled", "disabled");
|
||||
api_call("POST", "/api/user/register", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.href = "/profile";
|
||||
} else {
|
||||
display_message("register_msg", "danger", result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
}
|
||||
}, function(jqXHR, status, error) {
|
||||
var result = jqXHR["responseText"];
|
||||
display_message("register_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// password reset
|
||||
var request_reset_form = function() {
|
||||
var data = $("#request_reset_form").serializeObject();
|
||||
$(input).attr("disabled", "disabled");
|
||||
api_call("POST", "/api/user/forgot", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
display_message("reset_msg", "success", result["message"]);
|
||||
} else {
|
||||
display_message("reset_msg", "danger", result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
}
|
||||
}, function(jqXHR, status, error) {
|
||||
var result = jqXHR["responseText"];
|
||||
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var reset_form = function() {
|
||||
var data = $("#reset_form").serializeObject();
|
||||
data["csrf_token"] = $.cookie("csrf_token");
|
||||
var url = window.location.href;
|
||||
var token = url.substr(url.lastIndexOf("/")+1);
|
||||
$(input).attr("disabled", "disabled");
|
||||
api_call("POST", "/api/user/forgot/" + token, data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
display_message("reset_msg", "success", result["message"], function() {
|
||||
location.href = "/login";
|
||||
});
|
||||
} else {
|
||||
display_message("reset_msg", "danger", result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
}
|
||||
}, function(jqXHR, status, error) {
|
||||
var result = jqXHR["responseText"];
|
||||
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// login page
|
||||
|
||||
var login_form = function() {
|
||||
var input = "#login_form input";
|
||||
var data = $("#login_form").serializeObject();
|
||||
$(input).attr("disabled", "disabled");
|
||||
api_call("POST", "/api/user/login", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.href = "/profile";
|
||||
} else {
|
||||
display_message("login_msg", "danger", result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
}
|
||||
}, function(jqXHR, status, error) {
|
||||
var result = jqXHR["responseText"];
|
||||
display_message("login_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// team page
|
||||
|
||||
var create_team = function() {
|
||||
var input = "#create_team input";
|
||||
var data = $("#create_team").serializeObject();
|
||||
$(input).attr("disabled", "disabled");
|
||||
api_call("POST", "/api/team/create", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
} else {
|
||||
display_message("create_team_msg", "danger", result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
}
|
||||
}, function(jqXHR, status, error) {
|
||||
var result = JSON.parse(jqXHR["responseText"]);
|
||||
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var add_member = function() {
|
||||
var input = "#add_member input";
|
||||
var data = $("#add_member").serializeObject();
|
||||
$(input).attr("disabled", "disabled");
|
||||
api_call("POST", "/api/team/invite", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
} else {
|
||||
$(input).removeAttr("disabled");
|
||||
}
|
||||
}, function(jqXHR, status, error) {
|
||||
var result = JSON.parse(jqXHR["responseText"]);
|
||||
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
|
||||
$(input).removeAttr("disabled");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var rescind_invitation = function(uid) {
|
||||
var input = "#add_member input";
|
||||
var data = { "uid": uid };
|
||||
api_call("POST", "/api/team/invite/rescind", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var request_invitation = function(tid) {
|
||||
var input = "#add_member input";
|
||||
var data = { "tid": tid };
|
||||
api_call("POST", "/api/team/invite/request", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var accept_invitation = function(tid) {
|
||||
var data = { "tid": tid };
|
||||
api_call("POST", "/api/team/invite/accept", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var accept_invitation_request = function(uid) {
|
||||
var data = { "uid": uid };
|
||||
api_call("POST", "/api/team/invite/request/accept", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// settings page
|
||||
|
||||
var remove_profile_picture = function() {
|
||||
api_call("POST", "/api/user/avatar/remove", { }, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
}
|
||||
});
|
||||
};
|
2899
web/js/epiceditor.js
5
web/js/epiceditor.min.js
vendored
|
@ -1,44 +0,0 @@
|
|||
function render_problems() {
|
||||
$.post("/api/problem/data", {
|
||||
}, function(data) {
|
||||
data = data["data"];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
files = data[i]["files"];
|
||||
problem =
|
||||
`<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">` + data[i]["name"] + ` | ` + data[i]["category"] + `<span style="float: right">` + data[i]["value"] + ` points</span></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>` + data[i]["description"] + `</p>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Flag">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-success" id="hint" type="button" onclick="show_hint(\'` + data[i]["pid"] + `\');">Hint</button>
|
||||
<button class="btn btn-success" type="button">Submit!</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">`
|
||||
|
||||
for (var j = 0; j < files.length; j++) {
|
||||
file_name = files[j].split("/").pop();
|
||||
problem +=
|
||||
`<a href="` + files[j] + `" class="filelink" target="_blank">
|
||||
<h4 class="probfile">` + file_name + `</h4>
|
||||
</a>`
|
||||
}
|
||||
|
||||
problem += `<br>
|
||||
<div id="hint_` + data[i]["pid"] + `" style="display:none">` + data[i]["hint"] + `</div>
|
||||
</div></div>`
|
||||
$("#problems").append(problem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function show_hint(pid) {
|
||||
$("#hint_" + pid).slideToggle(120, "swing");
|
||||
}
|
||||
|
||||
$(document).ready( render_problems() );
|
|
@ -1,45 +0,0 @@
|
|||
var chart = c3.generate({
|
||||
data: {
|
||||
x: 'x',
|
||||
columns: [
|
||||
['x', '2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'],
|
||||
['Team Thomas', 30, 250, 380, 500, 620, 740],
|
||||
]
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
type: 'timeseries',
|
||||
tick: {
|
||||
format: '%Y-%m-%d'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(function() {
|
||||
chart.load({
|
||||
columns: [
|
||||
['Team Charles', 100, 210, 320, 430, 540, 650]
|
||||
]
|
||||
});
|
||||
}, 1000);
|
||||
setTimeout(function() {
|
||||
chart.load({
|
||||
columns: [
|
||||
['Team Zach', 10, 120, 230, 340, 450, 560]
|
||||
]
|
||||
});
|
||||
}, 2000);
|
||||
setTimeout(function() {
|
||||
chart.load({
|
||||
columns: [
|
||||
['Team Michael', 0, 80, 190, 300, 410, 520]
|
||||
]
|
||||
});
|
||||
}, 3000);
|
||||
setTimeout(function() {
|
||||
chart.load({
|
||||
columns: [
|
||||
['Team James', 0, 100, 250, 300, 450, 600]
|
||||
]
|
||||
});
|
||||
}, 4000);
|
|
@ -1,5 +0,0 @@
|
|||
<div class="page-header">
|
||||
<h1>404: Page Not Found</h1>
|
||||
</div>
|
||||
|
||||
<p>Ok, ok, here's your flag: <code>bm8gZWFzdGVyIGVnZyBoZXJl</code>.</p>
|
|
@ -1,93 +0,0 @@
|
|||
<script type="text/javascript">
|
||||
var chart1 = c3.generate({
|
||||
colors: {
|
||||
data1: '#1F77B4',
|
||||
data2: '#62AC5B',
|
||||
},
|
||||
data: {
|
||||
columns: [
|
||||
['US High School/Middle School ', 33.33],
|
||||
['Non-US/Non-Student', 66.66],
|
||||
],
|
||||
type: 'donut',
|
||||
onclick: function(d, i) {
|
||||
console.log("onclick", d, i);
|
||||
},
|
||||
onmouseover: function(d, i) {
|
||||
console.log("onmouseover", d, i);
|
||||
},
|
||||
onmouseout: function(d, i) {
|
||||
console.log("onmouseout", d, i);
|
||||
}
|
||||
},
|
||||
donut: {
|
||||
title: "Competitors"
|
||||
},
|
||||
bindto: "#chart1"
|
||||
});
|
||||
/*var chart2 = c3.generate({
|
||||
colors: {
|
||||
data1: '#1F77B4',
|
||||
data2: '#62AC5B',
|
||||
},
|
||||
data: {
|
||||
columns: [
|
||||
['Solved ', 33.33],
|
||||
['Unsolved', 66.66],
|
||||
],
|
||||
type: 'donut',
|
||||
onclick: function(d, i) {
|
||||
console.log("onclick", d, i);
|
||||
},
|
||||
onmouseover: function(d, i) {
|
||||
console.log("onmouseover", d, i);
|
||||
},
|
||||
onmouseout: function(d, i) {
|
||||
console.log("onmouseout", d, i);
|
||||
}
|
||||
},
|
||||
donut: {
|
||||
title: "Silly Ceasar"
|
||||
},
|
||||
bindto: "#chart2"
|
||||
});
|
||||
var chart3 = c3.generate({
|
||||
colors: {
|
||||
data1: '#1F77B4',
|
||||
data2: '#62AC5B',
|
||||
},
|
||||
data: {
|
||||
columns: [
|
||||
['Solved ', 16],
|
||||
['Unsolved', 25],
|
||||
],
|
||||
type: 'donut',
|
||||
onclick: function(d, i) {
|
||||
console.log("onclick", d, i);
|
||||
},
|
||||
onmouseover: function(d, i) {
|
||||
console.log("onmouseover", d, i);
|
||||
},
|
||||
onmouseout: function(d, i) {
|
||||
console.log("onmouseout", d, i);
|
||||
}
|
||||
},
|
||||
donut: {
|
||||
title: "Hard Binary"
|
||||
},
|
||||
bindto: "#chart3"
|
||||
});*/
|
||||
</script>
|
||||
<div class="fade_in text-center">
|
||||
<h1 class="heading1">About</h1>
|
||||
|
||||
<iframe frameborder="0" scrolling="no" marginheight="0" marginwidth="0" width="1000em" height="500em" src="https://maps.google.com/maps?hl=en&q=Lebanon, Kansas&ie=UTF8&t=hybrid&z=3&iwloc=B&output=embed"></iframe>
|
||||
|
||||
<br>
|
||||
<div id="chart1"></div>
|
||||
<br>
|
||||
<!--<div id="chart2"></div>
|
||||
<br>
|
||||
<div id="chart3"></div>-->
|
||||
<br>
|
||||
</div>
|
|
@ -1,71 +0,0 @@
|
|||
<div class="page-header">
|
||||
<h1>Problem Editor</h1>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.ace_editor {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.epiceditor-edit-mode {
|
||||
border: 1px solid #999 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="row">
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-pills nav-stacked col-md-3">
|
||||
<li class="active"><a data-target="#new" data-toggle="tab">New</a></li>
|
||||
<li ng-repeat="problem in problems"><a data-target="#problem_{{ problem['pid'] }}" data-toggle="tab">{{ problem["title"] }} ({{ problem["value"] }} points)</a></li>
|
||||
</ul>
|
||||
<div class="tab-content col-md-9">
|
||||
<div class="tab-pane active" id="new">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">New Problem</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" ng-repeat="problem in problems" id="problem_{{ problem['pid'] }}">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">{{ problem["title"] }}</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
/*$(".selectpicker").selectpicker();
|
||||
var config = {
|
||||
toolbar: [
|
||||
{ name: "basicstyles", items: [ "Bold", "Italic", "Underline" ] },
|
||||
{ name: "links", items: [ "Link" ] },
|
||||
{ name: "paragraph", items: [ "NumberedList", "BulletedList", "-", "Outdent", "Indent", "-", "Blockquote" ] },
|
||||
{ name: "tools", items: [ "Maximize" ] },
|
||||
{ name: "document", items: [ "Source" ] },
|
||||
]
|
||||
};
|
||||
var editor = new EpicEditor({
|
||||
container: "description",
|
||||
theme: {
|
||||
base: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/base/epiceditor.css",
|
||||
preview: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/preview/github.css",
|
||||
editor: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/editor/epic-light.css"
|
||||
},
|
||||
button: {
|
||||
bar: "show"
|
||||
}
|
||||
}).load();
|
||||
var new_grader = ace.edit("new_grader");
|
||||
new_grader.setTheme("ace/theme/tomorrow");
|
||||
new_grader.getSession().setMode("ace/mode/python");*/
|
||||
});
|
||||
</script>
|
|
@ -1,6 +0,0 @@
|
|||
<div class="fade_in text-center">
|
||||
<h1 class="heading1">EasyCTF IRC</h1>
|
||||
<div class="irc">
|
||||
<iframe src="https://kiwiirc.com/client/irc.kiwiirc.com/?&theme=mini#easyctf" style="border: none; width:100%; height:70%; border-radius:1em;"></iframe>
|
||||
</div>
|
||||
</div>
|
|
@ -1,92 +0,0 @@
|
|||
<div class="container">
|
||||
<p> </p>
|
||||
<div ng-switch on="token">
|
||||
<div ng-switch-when="true">
|
||||
{{ body }}
|
||||
<div ng-switch on="success">
|
||||
<div ng-switch-when="1">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Reset Password</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" onsubmit="reset_form(); return false;" id="reset_form">
|
||||
<fieldset>
|
||||
<div id="reset_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<div class="row">
|
||||
<p>Reset your password here</p>
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="password"><small>Password</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="password" required name="password" id="password" placeholder="New password" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="confirm_password"><small>Confirm Password</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="password" required name="confirm_password" id="confirm_password" placeholder="Confirm new password" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
|
||||
<input type="submit" class="btn btn-success btn-lg" value="Reset password" />
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-default>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Reset Password</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" onsubmit="request_reset_form(); return false;" id="request_reset_form">
|
||||
<fieldset>
|
||||
<div id="reset_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<div class="row">
|
||||
<p>Enter the email you used to sign up with, and we'll send you an an email with a link to reset your password.</p>
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="email"><small>Email</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="email" required name="email" id="email" placeholder="Email" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
|
||||
<input type="submit" class="btn btn-success btn-lg" value="Send email" />
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
<div class="fade_in text-center">
|
||||
<br>
|
||||
<h1 class="heading1">EasyCTF 3</h1>
|
||||
<br>
|
||||
</div>
|
|
@ -1,54 +0,0 @@
|
|||
<div class="container">
|
||||
<p> </p>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Login</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" onsubmit="login_form(); return false;" id="login_form">
|
||||
<fieldset>
|
||||
<div id="login_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="username"><small>Username</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="username" id="username" placeholder="Username" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="password"><small>Password</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="password" required name="password" id="password" placeholder="Password" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
|
||||
<input type="submit" class="btn btn-success btn-lg" value="Login" />
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<small>
|
||||
<a href="/forgot">Forgot Password?</a>
|
||||
</small>
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
<!--
|
||||
Adding files:
|
||||
1) put file in /files
|
||||
2) create an "<a href="/files/example.txt" target="_blank"><h4>Example File</h4></a>"
|
||||
3) remove these instructions (:P)
|
||||
-->
|
||||
<div class="fade_in">
|
||||
<h1 class="heading1 text-center">Problems</h1>
|
||||
<div id="problems"></div>
|
||||
</div>
|
||||
<script src="js/problems.js">
|
|
@ -1,115 +0,0 @@
|
|||
<div ng-show="user['user_found']==true">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div style="width:100%;max-width:100%;">
|
||||
<img src="/api/user/avatar/{{ user.uid }}?{{ timestamp }}" id="avatar" style="width:100%;max-height:256px;" />
|
||||
<small style="display:block;text-align:right;" ng-show="user['me']==true"><a href="/settings#profile">Edit Picture</a></small>
|
||||
</div>
|
||||
<h2 style="margin:0px;font-weight:bold;font-size:2em;">{{ user.name }}</h2>
|
||||
<small style="display:block;font-size:1.5em;color:#999;">@{{ user.username }}</small>
|
||||
<hr>
|
||||
<div>
|
||||
<i class="fa fa-fw fa-user"></i>
|
||||
{{ user.type }}
|
||||
<div class="label label-info" ng-show="user['admin']==true">ADMIN</div>
|
||||
</div>
|
||||
<div ng-show="user['show_email']==true">
|
||||
<i class="fa fa-fw fa-envelope"></i>
|
||||
<a style="color:#666;" href="mailto:{{ user.email }}">
|
||||
<span id="email">{{ user.email }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<i class="fa fa-fw fa-clock-o"></i>
|
||||
Joined <time class="timeago" datetime="{{ user.registertime }}"></time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li role="presentation"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity</a></li>
|
||||
<a href="/settings" class="btn btn-primary" style="float:right;" ng-show="user['me']==true"><i class="fa fa-fw fa-pencil"></i> Edit Profile</a>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="profile">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Team Information</h4>
|
||||
</div>
|
||||
<div class="table-responsive" ng-show="user['in_team']==true">
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th>Team Name</th>
|
||||
<td><a ng-href="/team/{{ user.team['teamname'] }}">{{ user.team['teamname'] }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>School</th>
|
||||
<td>{{ user.team['school'] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Points</th>
|
||||
<td>{{ user.team['points'] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<td>{{ user.team['place'] }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-body" ng-show="user['in_team']!=true">
|
||||
<div class="alert alert-success" ng-show="user['me']==true && user['invitations'].length>0">You have {{ user['invitations'].length }} invitation{{ user['invitations'].length==1 ? "" : "s" }}! <a href="/team">View »</a></div>
|
||||
<p>{{ user['me']==true ? "You're" : "This user is" }} not a part of a team.</p>
|
||||
<a href="/team" class="btn btn-primary" ng-show="user['me']==true">Join or create one now »</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Statistics</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
Hi.
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Top Solves</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
Hi.
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Achievements</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
Hi.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="activity">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="user['user_found']!=true">
|
||||
<div class="page-header">
|
||||
<h1>User Not Found</h1>
|
||||
</div>
|
||||
|
||||
<p>The user you were looking for doesn't exist. Check to make sure you've spelled the name right.</p>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("ul[role=tablist]").tab();
|
||||
$("a[role=tab]").click(function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -1,11 +0,0 @@
|
|||
<div class="fade_in text-center">
|
||||
<h1 class="heading1">Programming</h1>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Problems Avalible: NaN?</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h3>Insert Programming Client Here</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,103 +0,0 @@
|
|||
<div class="container">
|
||||
<p> </p>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Register</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" onsubmit="register_form(); return false;" id="register_form">
|
||||
<fieldset>
|
||||
<div id="register_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<p>Register your individual account here. Make sure that members of your team also register accounts. You'll be able to create teams and add members after you register!</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="name"><small>Your Name</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="name" id="name" placeholder="Your Name" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="email"><small>Email</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="email" required name="email" id="email" placeholder="michael@example.com" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="username"><small>Username</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="username" id="username" placeholder="Username" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="password"><small>Password</small></label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="password" required name="password" id="password" placeholder="Password" autocomplete="off" />
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="password" required name="password_confirm" id="password_confirm" placeholder="Confirm Password" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="type"><small>Who are you?</small></label>
|
||||
<div class="col-sm-12">
|
||||
<select name="type" id="type" class="selectpicker" data-width="100%">
|
||||
<option value="1">US Middle/High School Student</option>
|
||||
<option value="2">Middle/High School Teacher</option>
|
||||
<option value="3">Non-US/Non-Student/Observer</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12"><small>Receive email notifications about future CTFs?</small></label>
|
||||
<div class="col-sm-12">
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<label class="btn btn-default active" id="notify_btn">
|
||||
<input type="checkbox" autocomplete="off" checked name="notify" id="notify"><span id="notify_box" class="fa fa-fw fa-check-square"></span> Get notified about future CTFs
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<input type="submit" class="btn btn-primary btn-lg" value="Register" />
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(".selectpicker").selectpicker();
|
||||
var checkedClass = "fa fa-check-square";
|
||||
var uncheckedClass = "fa fa-square";
|
||||
$("#notify_btn").click(function() {
|
||||
if($("#notify").is(":checked")) {
|
||||
$("#notify_box").removeClass(checkedClass);
|
||||
$("#notify_box").addClass(uncheckedClass);
|
||||
} else {
|
||||
$("#notify_box").removeClass(uncheckedClass);
|
||||
$("#notify_box").addClass(checkedClass);
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -1,43 +0,0 @@
|
|||
<div class="fade_in text-center">
|
||||
<h1 class="heading1"> Info </h1>
|
||||
|
||||
<h2 class="heading2">Rules</h2>
|
||||
<p class="paragraph">
|
||||
Below lie the rules regarding EasyCTF. These rules have been specifically formulated in order to preserve the integrity of the competition for every participant. These rules exist only serve as a auxillary to your own common sense. One of EasyCTF's core
|
||||
goals is to gauge your team's understanding of the relevant computer science principles. If we wanted to measure the magnitude of your teacher's knowledge of computer science, we would attend a local CSTA meeting. Due to the nature of the competition's
|
||||
content, the whole of the world wide web is free game. Do not, however, use forums or other services in which the problem is forwarded to unregistered member of EasyCTF. Sharing flags is obviously against rules, and will not be permitted. Do not
|
||||
confuse EasyCTF with an attack-defense CTF platform; if you attack either the EasyCTF infrastructure or another team, you will be subject to complete team explusion, as well as the possibility of legal pursuance. All decisions made by an administrator
|
||||
from the EasyCTF Team are final.
|
||||
</p>
|
||||
|
||||
<h2 class="heading2">Flag Format</h2>
|
||||
<p class="paragraph">
|
||||
Similar to previous years, our flags will be formatted like so:
|
||||
<code>EasyCTF{example_flag}</code>. Although our magical judge programs are expecting only the text in between the curly braces (
|
||||
<code>example_flag</code>), EasyCTF will ignore the
|
||||
<code>EasyCTF{}</code> and mark your submission correct or incorrect. Rapid, repeated guesses (brute forcing) will serve as sufficient grounds for team penalization. As forementioned, sharing of flags outside members of your own team can, and will have
|
||||
severe penalties or explusion from the CTF competition. Finally, creating writeups for your solved problems is highly encouraged. However, for the integrity of the competition, writeups may not be posted before the end of the competition. After
|
||||
the competition has successfuly ended, the EasyCTF team will post writeups for all of the problems.
|
||||
</p>
|
||||
|
||||
<h2 class="heading2">Scoring</h2>
|
||||
<p class="paragraph">In EasyCTF, every problem has an assigned point value. This point value is assigned based on the difficulty or the extent of time required to solve the problem. Your team's total score is the sum of the points you obtain from every problem you solve.
|
||||
A few problems may have speed bonuses, meaning that the first solve on a problem will earn a bonus percent of the original problem value. These bonus points will also count toward your team total. Here is a screenshot from the problems page to
|
||||
demonstrate. Teams with higher points will outrank teams with lower points. Should a tie occur between two teams, the time of the last submission will be used to break the tie. For example, if two teams are tied at 100 points, the team that reached
|
||||
100 points first will outrank the team that reached 100 points afterwards.</p>
|
||||
|
||||
<h2 class="heading2">Eligibility and Observer Accounts/Teams</h2>
|
||||
<p class="paragraph">EasyCTF is targeted at students enrolled in high schools or middle schools across the United States. Therefore, only students who are enrolled in high schools or middle schools in the United States are eligible for prizes. The EasyCTF Team will verify
|
||||
the winning teams meet these conditions; if a winning team does not meet these conditions, then the prizes will be given to the next highest team. If you don't live in the United States, or you're not a high school or middle school student, you
|
||||
are still encouraged to compete, but under an Observer account. Teams with at least one Observer member will be considered an Observer team. Observer teams still appear in the scoreboard, but are not eligible for prizes. Teachers also wishing
|
||||
to take part in the CTF may also create an Observer team.</p>
|
||||
|
||||
<h2 class="heading2">Prizes</h2>
|
||||
<p class="prizes"><b>1st Place: $512</b>
|
||||
<br>2nd Place: $256
|
||||
<br>3rd Place: $128
|
||||
<br>Top 20: Laptop Stickers</p>
|
||||
<br>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
|
@ -1,22 +0,0 @@
|
|||
<div class="page-header">
|
||||
<h1>Scoreboard</h1>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Team Name</th>
|
||||
<th>School</th>
|
||||
<th>Points</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="team in scoreboard">
|
||||
<td>{{ team['rank'] }}</td>
|
||||
<td><a ng-href="/team/{{ team['teamname'] }}">{{ team['teamname'] }}</a></td>
|
||||
<td>{{ team['school'] }}</td>
|
||||
<td>{{ team['points'] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -1,192 +0,0 @@
|
|||
<style>
|
||||
.btn-file {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.btn-file input[type=file] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
font-size: 100px;
|
||||
text-align: right;
|
||||
filter: alpha(opacity=0);
|
||||
opacity: 0;
|
||||
background: red;
|
||||
cursor: inherit;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Account Settings</h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<a href="#profile" class="list-group-item" data-scroll>Profile</a>
|
||||
<a href="#email" class="list-group-item" data-scroll>Email</a>
|
||||
<a href="#security" class="list-group-item" data-scroll>Security</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<section id="profile">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Public Profile</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<label>Profile Picture</label>
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<img src="/api/user/avatar/{{ user.uid }}?{{ timestamp }}" id="avatar" style="width:100%;max-height:256px;" />
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-primary btn-file" id="file_upload">
|
||||
<i class="fa fa-fw fa-upload"></i> Upload new picture
|
||||
<input type="file" name="file" id="file" accept="image/*" />
|
||||
</div>
|
||||
<a class="btn btn-default" href="javascript:remove_profile_picture();">
|
||||
<i class="fa fa-fw fa-trash"></i> Remove profile picture
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p><i>If you remove your picture, an automatically generated <a href="http://en.wikipedia.org/wiki/Identicon" target="_blank">identicon</a> will be used.</i></p>
|
||||
</div1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="email">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Email</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>Hi.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Email Preferences</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>Hi.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="security">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Change Password</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>Hi.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Active Sessions</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>Hi.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
smoothScroll.init({
|
||||
speed: 480,
|
||||
easing: "easeOutCubic",
|
||||
offset: $("#navbar").outerHeight(),
|
||||
updateURL: false
|
||||
});
|
||||
var dataURItoBlob = function(dataURI) {
|
||||
var byteString = atob(dataURI.split(",")[1]);
|
||||
var mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
|
||||
var ab = new ArrayBuffer(byteString.length);
|
||||
var ia = new Uint8Array(ab);
|
||||
for (var i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
var blob = new Blob([ ab ], { type: mimeString });
|
||||
return blob;
|
||||
}
|
||||
var MAX_SIZE = 256;
|
||||
var uploadListener = function(e) {
|
||||
try {
|
||||
$("#file_upload").attr("disabled", "disabled");
|
||||
$("#file_upload").html("<i class='fa fa-fw fa-circle-o-notch fa-spin'></i> Uploading...");
|
||||
var file = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = (function(_file) {
|
||||
return function(progress) {
|
||||
var img = document.createElement("img");
|
||||
img.src = progress.target.result;
|
||||
var width = img.width;
|
||||
var height = img.height;
|
||||
|
||||
if (width > height) {
|
||||
if (width > MAX_SIZE) {
|
||||
height *= MAX_SIZE / width;
|
||||
width = MAX_SIZE;
|
||||
}
|
||||
} else {
|
||||
if (height > MAX_SIZE) {
|
||||
width *= MAX_SIZE / height;
|
||||
height = MAX_SIZE;
|
||||
}
|
||||
}
|
||||
img.cwidth = width;
|
||||
img.cheight = height;
|
||||
|
||||
var data = new FormData();
|
||||
data.append("file", dataURItoBlob(img.src));
|
||||
data.append("csrf_token", $.cookie("csrf_token"));
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/user/avatar/upload",
|
||||
data: data,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
}).done(function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$("#file_upload").removeClass("btn-primary");
|
||||
$("#file_upload").addClass("btn-success");
|
||||
$("#file_upload").html("<i class='fa fa-fw fa-check'></i> Done! Reloading...");
|
||||
location.reload(true);
|
||||
}
|
||||
}).fail(function() {
|
||||
$("#file_upload").removeClass("btn-primary");
|
||||
$("#file_upload").addClass("btn-danger");
|
||||
$("#file_upload").html("<i class='fa fa-fw fa-ban'></i> Error. Reloading...");
|
||||
location.reload(true);
|
||||
});
|
||||
};
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
$("#file_upload").removeClass("btn-primary");
|
||||
$("#file_upload").addClass("btn-danger");
|
||||
$("#file_upload").html("<i class='fa fa-fw fa-ban'></i> Error. Reloading...");
|
||||
location.reload(true);
|
||||
}
|
||||
};
|
||||
$(document).ready(function() {
|
||||
if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
|
||||
console.log("Your browser doesn't support file upload.");
|
||||
} else {
|
||||
document.getElementById("file").addEventListener("change", uploadListener, false);
|
||||
};
|
||||
});
|
||||
</script>
|
|
@ -1,11 +0,0 @@
|
|||
<div class="fade_in">
|
||||
<h1 class="heading1">Shell</h1>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Username: (insert username) | Password: (insert password)</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h3>Insert Shell Here</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,224 +0,0 @@
|
|||
<style>
|
||||
.editable {
|
||||
outline: none;
|
||||
border: 1px solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
.editable:hover {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.editable:focus {
|
||||
border: 1px solid #999;
|
||||
background-color: #FFF;
|
||||
}
|
||||
.padded {
|
||||
display: inline-block;
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div ng-show="team['tid'] >= 0">
|
||||
<div class="jumbotron">
|
||||
<center>
|
||||
<div ng-show="team['in_team']==true">
|
||||
<div ng-show="team['is_owner']==true">
|
||||
<h1><span data-toggle="tooltip" data-placement="top" title="Click to edit team name." id="teamname_edit" class="padded editable" contenteditable>{{ team['teamname'] }}</span></h1>
|
||||
<h4><i class="fa fa-fw fa-university"></i> <span data-toggle="tooltip" data-placement="top" title="Click to edit school." id="school_edit" class="padded editable" contenteditable>{{ team['school'] || 'Add School' }}</span></h4>
|
||||
</div>
|
||||
<div ng-show="team['is_owner']==false">
|
||||
<h1><span class="padded">{{ team['teamname'] }}</span></h1>
|
||||
<h4><i class="fa fa-fw fa-university"></i> <span class="padded">{{ team['school'] || 'Unknown Affiliation' }}</span></h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label label-success">
|
||||
<i class="fa fa-fw fa-flag"></i>
|
||||
I'm in the team!
|
||||
</div>
|
||||
<div class="label label-warning" ng-show="team['observer']==true">
|
||||
<i class="fa fa-fw fa-globe"></i>
|
||||
OBSERVER
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="team['in_team']!=true">
|
||||
<h1><span class="padded">{{ team['teamname'] }}</span></h1>
|
||||
<h4><i class="fa fa-fw fa-university"></i> <span class="padded">{{ team['school'] || 'Unknown Affiliation' }}</span></h4>
|
||||
<div class="row">
|
||||
<div class="label label-warning" ng-show="team['observer']==true">
|
||||
<i class="fa fa-fw fa-globe"></i>
|
||||
OBSERVER
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Team Members</h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item" ng-repeat="member in team['members']">
|
||||
<h4 class="list-group-item-heading" style="display:inline-block;">{{ member['name'] }}</h4>
|
||||
<div class="label label-info" ng-show="member['captain']==true">Owner</div>
|
||||
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer" ng-show="team['is_owner']==true">
|
||||
<form id="add_member" onsubmit="add_member(); return false;" style="margin: 0;">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="new_member" name="new_member" placeholder="Add member..." autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-success" type="submit"> <i class="fa fa-fw fa-plus"></i> </button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer" ng-show="team['in_team']!=true && config.navbar['logged_in']==true">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<a ng-href="javascript:request_invitation({{ team['tid'] }});" class="btn btn-primary col-xs-12" ng-show="team['invited']!=true && team['requested']!=true"><i class="fa fa-fw fa-plus"></i> Join this team</a>
|
||||
<a class="btn btn-primary col-xs-12 disabled" ng-show="team['invited']!=true && team['requested']==true"><i class="fa fa-fw fa-check"></i> Sent Request</a>
|
||||
<a ng-href="javascript:accept_invitation({{ team['tid'] }});" class="btn btn-success col-xs-12" ng-show="team['invited']==true"><i class="fa fa-fw fa-check"></i> Accept Invitation</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default" ng-show="team['is_owner']==true && team['pending_invitations'].length > 0">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Pending Invitations</h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item" ng-repeat="member in team['pending_invitations']" href="/profile/{{ member['username'] }}">
|
||||
<a class="badge" ng-href="javascript:rescind_invitation({{ member['uid'] }});"><i class="fa fa-fw fa-times"></i></a>
|
||||
<h4 class="list-group-item-heading">{{ member['name'] }}</h4>
|
||||
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default" ng-show="team['is_owner']==true && team['invitation_requests'].length > 0">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Invitation Requests</h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item" ng-repeat="member in team['invitation_requests']" href="/profile/{{ member['username'] }}">
|
||||
<a class="badge" ng-href="javascript:accept_invitation_request({{ member['uid'] }});"><i class="fa fa-fw fa-check"></i></a>
|
||||
<h4 class="list-group-item-heading">{{ member['name'] }}</h4>
|
||||
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li role="presentation"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="profile">
|
||||
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="activity">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="!(team['tid'] >= 0) && config.navbar['logged_in']==true">
|
||||
<div class="page-header">
|
||||
<h1>Team</h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="page-header">
|
||||
<h3>New Team</h3>
|
||||
</div>
|
||||
<p>To participate in EasyCTF, you must be on a <b>team</b>. If you'd like to go solo, just create a team by yourself. Read about team eligibility in the <a href="/rules">rules</a>.</p>
|
||||
|
||||
<form class="form-horizontal" onsubmit="create_team(); return false;" id="create_team">
|
||||
<fieldset>
|
||||
<div id="create_team_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Create a Team</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" onsubmit="login_form(); return false;" id="login_form">
|
||||
<fieldset>
|
||||
<div id="login_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="teamname"><small>Team Name</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="teamname" id="teamname" placeholder="Create a team name..." autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="school"><small>School Name</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="school" id="school" placeholder="School Name" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<input type="submit" class="btn btn-success btn-lg" value="Create Team" />
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="page-header">
|
||||
<h3>Invitations</h3>
|
||||
</div>
|
||||
<p ng-show="team['invitations'].length==0">You need an invitation to join another team. If you'd like to request to be a member of their team, go to their team page and click the Request button.</p>
|
||||
<div ng-show="team['invitations'].length>0" class="list-group">
|
||||
<div class="list-group-item" ng-repeat="invitation in team['invitations']">
|
||||
<a ng-href="/team/{{ invitation['team'] }}">{{ invitation['team'] }}</a>
|
||||
<a ng-href="javascript:accept_invitation({{ invitation['tid'] }});" class="badge">Accept »</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="!(team['tid'] >= 0) && config.navbar['logged_in']!=true">
|
||||
<div class="page-header">
|
||||
<h1>Team Not Found</h1>
|
||||
</div>
|
||||
|
||||
<p>The team you were looking for doesn't exist. Check to make sure you've spelled the name right.</p>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$("#teamname_edit").on("keypress", function(e) {
|
||||
if (e.keyCode == 13) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
$(document).ready(function() {
|
||||
$("[data-toggle=tooltip]").tooltip();
|
||||
$("ul[role=tablist]").tab();
|
||||
$("a[role=tab]").click(function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -1,18 +0,0 @@
|
|||
<div class="fade_in text-center">
|
||||
<h1 class="heading1">News / Updates</h1>
|
||||
<h4>This is where updates about the competition and clarifications about the problems will be posted.
|
||||
<br>Make sure you check this page frequently during the competition!</h4>
|
||||
<a class="twitter-timeline" href="https://twitter.com/easyctf" data-widget-id="681250456908120064">Tweets by @easyctf</a>
|
||||
<script>
|
||||
! function(d, s, id) {
|
||||
var js, fjs = d.getElementsByTagName(s)[0],
|
||||
p = /^http:/.test(d.location) ? 'http' : 'https';
|
||||
if (!d.getElementById(id)) {
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = p + "://platform.twitter.com/widgets.js";
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
}
|
||||
}(document, "script", "twitter-wjs");
|
||||
</script>
|
||||
</div>
|
|
@ -1,70 +0,0 @@
|
|||
html, body, iframe, div {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#epiceditor-utilbar {
|
||||
position:fixed;
|
||||
bottom:10px;
|
||||
right:10px;
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button {
|
||||
display:block;
|
||||
float:left;
|
||||
width:30px;
|
||||
height:30px;
|
||||
border:none;
|
||||
background:none;
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button.epiceditor-toggle-preview-btn {
|
||||
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAf9JREFUeNrsVu1twjAQLZmAEcIGYYKGDWACkgkSkPiQ+CifAoEgyQTQDegEpROQDZoRMgH0HToj1yIglKr90Vg62T6f/e4d9gu50+n09BctlwFnwP8TOAgCA10VRr2ZELaHhbBXx3HCVMC+71voXmD6g4Qi2NB13e1DwAAkZhtmmKYRcxsJhHeBPc8bMMufbDU0PxF4vV4TSyth8477csI6lTV/a71er9tioonBarXaIAmLErliNWyqoM8nrJPpHFNLWLcI4xvj5XKpMo2ZgcvzIvs+75S0wKwPPB/CnpWXsG00Gra2WCwshekOVoC9Sb6IGN1ge2HNsWK+B0iJqxAL5oSpYeDJJW02mxVYLAWSGfDtebylA68Bc4wh+ahK5PcxLh6PR5GUpym/iTOfz89PqNVqhRI4iQf1/o174HNMVYDSGeTDmXQ3znogCGrtdpsYVBhER1aH2Wzm8iE7UR74DMTWGNxUQWmNYqTEzq8APoo9sJ8wKoR5eU7T6VQVjZAvx4YvDJWt1Ol0QsTqkppF8EW8/12OhTnSpT2LCe2/KiCTyUQVkJgPuwgb6XG32w05Xui4q0imLLNDxA/uSuZ4PNaZqZlSsejDYfd6veihj8RoNDK5XOUHAen3Dfr9/j7VZxEJ6AwuxCCvhMTM7oNAARhl/0Ay4Az419qXAAMAfBdK7281j6YAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button.epiceditor-toggle-edit-btn {
|
||||
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNrsV+lt4lAQNtEWAB2QCtZ0ABWsqYB1BU5AgDgkQOISl9cV4FQQtgLYCuKtIN4K4gogM9H3pMmLTTh24c+O9An7+XnOb8aP1G63M64hN8aV5GqGvxyyyfO8Y3S6hDtc2wTfcZyLRPwdvxEbPSviIwjIRtO49s9ONSRLMIGvwkBI+EPYEEqyQmcZdl2Xo3BgcJ90xPUGDh1veLFYWCBK9oQ6P4BgiXVO6fUjg5zCJcE6kVxsLEN4QTlWzO5yuRwlGp7P55zOxz1RRqK2SfKD8BvOG4IHxUqlEnxop9lsZpITa0KWndGwIeQIXswzHbynpK2xzjXbeBfxdDrlhbUWjYyuiJS9fBJxgL3PItKsprNQrVaD1GQyYUVP2oYelDziPoPnT5+k2QajleEC3usI/exM7oYiXor0hpxS8qhLv5FIVVrcBwkp5ueclbS27qNMvkj7kg3nZX1qtVqAaSUN5OGUwn2M4RUb3263plh700E6I7wTKn1suCAW3PF4zL1r1Ov1SBhXZLEJFqGTQCq5N1BZIp3s+DOi5fXcG7lGo1Ea5DIFSWzcq7a4R6uYqJmlkSqHoeGKeq+w905MtGKj0Yje9TE5ID9pqictQQwmXTfh82cIJ0NML0d0QY8MdhMn13A4zENB0hAJEYn6EnGL3MZ0hsyG3Ww2g70jU8lgMOhqHicJz+KfovVkz3J5/FardfhBgDZzS90SeoJ8cXjQJlUAEmZUCx30kYiTfr9vgFRc72+ChCHSzNH+Qgk+fA7b7fZZJ5AAiIRhT4zUv3/Y07LiaPW9yPE2L5jrI/p/d7wVEZe0U8bJkvr/F+ZS8irAAIorRozUvI0gAAAAAElFTkSuQmCC);
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button.epiceditor-fullscreen-btn {
|
||||
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAY5JREFUeNrsV8tthDAQhYgCtoV04BKghK2En5AQBw4IITjwq2RLgBJcwpawHZAxMpGDYGwTFPaQkUbG1sybj58NmNM0GVfIh3GRXBbYEid932N9d1zXHWWAGAb4m+9VsUA0H5SubKkKIGA4qyUC2qKBxSCe541HKln7dV2nVbHRtq0Nw7CXGNtz/jzwqjZ5odtqTOagQRC82KRpGkcS/L2Ok7lXZV3Xp7Q6DMNR2mqNthMhKXLqzcUCc6Wgn3wU1wlX1PboHs8tjaLoyVtLT7JFK2ZZM6CZvWyE+X1Voaj3la3DMA63epGqqm4wfyCBH8xmz1+Z1WVZ2vyqU2HvHtv9OI4PsRpjL91YV2a7pcB8onmOifYFUhSFLQCTnQtkDpokyYv73JBtcAQsA3zGTXJBEgNXgrF3CcrBZGwnC+4uqxHnH+zN8/ybvexZwvZNhlsHbrt5CyCgDtu1XosUe58K4kuOF9EKnKYp20eVrxDUJssyreM0bDg4kIw0EfCbftvqQ6KKYf7/wvyVfAkwAMjqoFSaWbrBAAAAAElFTkSuQmCC);
|
||||
}
|
||||
|
||||
@media
|
||||
only screen and (-webkit-min-device-pixel-ratio: 2),
|
||||
only screen and ( min--moz-device-pixel-ratio: 2),
|
||||
only screen and ( -o-min-device-pixel-ratio: 2/1),
|
||||
only screen and ( min-device-pixel-ratio: 2),
|
||||
only screen and ( min-resolution: 192dpi),
|
||||
only screen and ( min-resolution: 2dppx) {
|
||||
#epiceditor-utilbar button.epiceditor-toggle-preview-btn {
|
||||
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAABrVBMVEWIiIiHh4eIiIiJiYmEhISEhISEhISMjIyNjY2MjIyNjY2MjIyFhYWDg4OFhYWKioqKioqBgYGJiYmJiYmJiYmKioqKioqFhYWIiIiJiYmIiIiJiYmIiIiGhoaIiIiGhoaIiIiHh4eHh4eGhoaFhYWKioqFhYWEhISFhYWEhISDg4ONjY2NjY2MjIyHh4eMjIyLi4uCgoKCgoKCgoKBgYGBgYGOjo6Ojo6BgYGPj4+Ojo6Hh4eNjY2Pj4+BgYGNjY2BgYGHh4eQkJCNjY2FhYWHh4eDg4OQkJCQkJCAgICCgoKAgICAgICQkJCQkJCQkJCAgICQkJCAgICQkJCFhYWHh4eHh4eGhoaKioqGhoaMjIyDg4OEhISDg4OEhISDg4OEhISKioqBgYGJiYmKioqKioqJiYmMjIyEhISCgoKGhoaLi4uPj4+Pj4+BgYGKioqDg4OGhoaGhoaMjIyHh4eEhISEhISDg4OIiIiJiYmGhoaHh4eJiYkAAACAgICFhYWQkJCIiIiEhISLi4uDg4OHh4eGhoaCgoKBgYGJiYmMjIyOjo6KioqNjY2Pj4+o6Lr0AAAAfnRSTlPfvxDPEL8gv5/Pr3AQ3++vn8+/cCCA34/vj8/vMJ+vgJ+vYN/fMIBAv4/v38/v39+PgBBg74DPgGAwMHDvrzBQj1AggJ8wIHC/IM9gjxBQgO+vv89Qz0C/70Bgz89ggIDvYN/fII8wIHDvMJ+/QBBAMBDPnxBgML9AEI/vQACT8cYwAAACXElEQVR42u2WV1caURRGJ73H9J7YW+y99957ARREURAFQRFEOFGMkybxN+eec2cWUwBnrTwli/1yvvnm7PvEuoMAf0FGzsj/nDwxOTUzdyoxNzM1OWFU7h0aPtUxPNRrQPZ0XKagw3OF7Nm9TMOuJ43cQmpavSWV3HRhgKakcmvjhSEaW/VyV/tvg7R3aeU+9ULZ/fLEQ/ndMvXbPrV88EvBvQdOMCsLMzjNd5TFgSTr3SpWOCuUTYWTVVV6m+Sdr0qqqVGxw6pqXUPyZlziFaU9Vi3HVSyzag+D/YlcbXLZLm+85AsOgMK4hkIABz/YkSVVdpS3fnKevQCIYgCcGqKslGd0g3dbIIR5fP8cZCkMdOANWeSLEJZklt5StxEWcmLIuw+AHGE+YiEWE67jGxnlO8xv8OGTEBGRRSACmNtYyBUjgcCCKJPLqjYMAeD04ENEGIjQ7AGikuUFNhdF8Vogj0T5bDyqEjh55AwI4N7/hmRTe4zRxEM+ZuKYFSYeEP9HzPtuEFjm9pKf9W5KQLbKhSVMbkymfHL90i+s/wR5PM9iCaYiLOcLTogCrKEIYwkLD19T25/4bR+unSG3bkOQwiG1QZfV6gryBaqL5c01XCCZ9lbOCOvNUpouUOGishSK+dpKUHMZ2M6Jz7ZHNEO+hOoLUWVZZROx6a8hn+VcRWh1EOtBUuhcPiy+pBdg3fZ3DaOj2ma7LtXVW1uj0XVqTW2aS9/bUP8jJfUN3is+N97mp8nV9Wavka9kZ/e6zuzuNP59Hhkbn53+QkzPjo+NZP6TZOSM/L/LfwAJ69Ba3fXmtwAAAABJRU5ErkJggg==);
|
||||
background-size: 30px 30px;
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button.epiceditor-toggle-edit-btn {
|
||||
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAACNFBMVEWIiIiHh4eIiIiGhoaEhISDg4OCgoKDg4OMjIyNjY2MjIyMjIyLi4uMjIyKioqCgoKNjY2Ojo6Dg4ONjY2FhYWFhYWCgoKKioqKioqLi4uLi4uMjIyLi4uFhYWKioqKioqEhISJiYmJiYmEhISEhISFhYWEhISGhoaHh4eJiYmNjY2Hh4eKioqDg4ODg4OCgoKOjo6Ojo6Dg4OOjo6BgYGOjo6AgICCgoKNjY2Dg4OPj4+BgYGBgYGAgICOjo6BgYGOjo6AgICOjo6BgYGBgYGAgICPj4+QkJCQkJCQkJCQkJCPj4+NjY2IiIiIiIiFhYWFhYWEhISEhISGhoaDg4OGhoaDg4OHh4eDg4OCgoKMjIyLi4uLi4uKioqNjY2KioqNjY2NjY2NjY2Dg4OKioqGhoaJiYmIiIiGhoaFhYWEhISFhYWFhYWOjo6Pj4+FhYWGhoaPj4+Hh4eMjIyMjIyMjIyJiYmIiIiIiIiOjo6Ojo6CgoKBgYGPj4+QkJCHh4eHh4eEhISIiIiDg4OEhISDg4ODg4OLi4uHh4eEhISMjIyFhYWGhoaHh4eOjo6IiIiKioqKioqMjIyGhoaIiIiJiYmIiIiFhYWGhoaGhoaHh4eKioqJiYmLi4uIiIiGhoaJiYmHh4eGhoaGhoaJiYmFhYWKioqPj4+IiIiGhoYAAACAgICCgoKQkJCPj4+BgYGDg4OFhYWHh4eOjo6JiYmGhoaLi4uEhISIiIiNjY2MjIyKioqRGRgVAAAAq3RSTlNQz7+Pz4AQr4/vIK+Av99QICBQzyCv37/PcM+f378QgHAwgIBgnxBQMK+PgFCf34Bwn0BQv2Ag7zBwEFDvj4CAv1DvYK/f398gUICA39+fYHAwQHAQn78gz0DfYO8wgO+/YECPIK9gz89Av8/f31AwgK9A72AwIGCAMM+PICCvrxCfr+/fYCAgYO9QgGBQjyCPYM8QcM8wEEAgn58Qn+8w7+/fv0DvQEBA7wCxres+AAAC50lEQVR42u2XVUMbQRSFU3d3d3d3d8EpUKRoKbS40+LuDsEtQOFQKA2FEvhz3XsnS5IlJLB9a/s95J49M99sXpJsNPgD/st/oZwxJJGhTn4+RLxQJ78k9xnUyU+HJV6pk6OGiXR1ciy5sZizfPMrwzl9mIiak5x6/sLnUZmH9+9eqqAQCftyakXkqFUq7MpX6JbW2WBHDnxtmJUAIMiGfD3AYINArDdsCppNfmewxQqx4aRVObFm0ibLAW+aNYkz5ZL4SdusRIkI8SVKOcFyp/cu5ftYA6ySc4Kl3DZuxs4dhfAZV+CDQtNFm0lWuLsBFPoqXF9g9bjSZrlqypwqAC1TClqAtYprIVfLjT+f0gfAXyn7oY9GNZ/KiWVumX17OYYAIUp3O+AnDu7bZqxOk5zU+ZOpPwD0UABNBaEAaPYACBVCZ5Ik14vlg5ClVjFpC0NZ6lplGa0nxN2gSZkg2vtB9FOmKDUNCyemcTStMXXcpmh4yGUl5ToAORMOaGiflhtkoRKCfq41yTw+GFsHKeeIRUfwwbwKOo/eIATi3GQNivREVxyIZsqeADL1+swuvZEiAJ4UmsEUsVEODRAnNp2iulzekrVAbyJLPrYctJTJ7nGQjI7uMULXBIBjIyQWUWLeAGik0E39sQGKYU0QMmp1vGnADSjj0EFtk5uOjuKzOtgok8r34rxasMzEjDFhjdDJNsE7u2VXh9oYDgNllp/n8IgfzJbwXhq9zlRu5soZWtFFl/Zy8SkaljK0R6inJTEinLQo5aSFE889kkqUWvsCdM37ZcnHYnrNBhably5QyoJDtFuJK1xMF3mHgVlkHM2e4eYB02Xxfts/NwXBuSMWLIG7sTmbb/+Hzj3fy1wuQD6N3DMX5/g0VHDDQ3aXAR4jXsEb5/co9fbLN2KdlJbO/zmM5a0qH+KukXxOnfzoO5GmTr5M7mOoktP4xrfUyffIvQ118pNBiTvq5AeDxNV//W+CFX4Doxg1lHC8FNUAAAAASUVORK5CYII=);
|
||||
background-size: 30px 30px;
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button.epiceditor-fullscreen-btn {
|
||||
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAA5FBMVEWPj4+JiYmJiYmCgoKGhoaPj4+Li4uEhISAgICNjY2Li4uNjY2QkJCFhYWEhISLi4uHh4eHh4eIiIiHh4eGhoaHh4eEhISOjo6EhISJiYmFhYWJiYmFhYWNjY2NjY2Hh4eOjo6CgoKGhoaKioqKioqJiYmKioqDg4ODg4OBgYGGhoaKioqKioqLi4uMjIyMjIyIiIiIiIiMjIyOjo6GhoaDg4ONjY2EhISPj4+CgoIAAACJiYmHh4eIiIiFhYWCgoKPj4+Li4uAgICKioqEhISGhoaBgYGOjo6MjIyQkJCNjY2Dg4O2Jy/OAAAAO3RSTlMQz0DPEHDPz0DvMDBAQBBAQBAQz88wMM/v7zAw788Q7xAQQM9AEBDPEEAwMO/v7zDvMEBA70BAQEBAAAENmbYAAAG1SURBVHja7dftM0JREMdxD5GkEEqKpBJRSc8rQq7Q////2Dnm3t85jtkTmmGG75t7Z3c/t9fN0Tf6x1/Dz0ZrZGZf/BJ8a9QjM/vCwks9Px5aBcslC+P3nPVmi8dcczrcHHM2flug1CHRYcoYNd0YFlrAL1yHqPOC9g9IdbAfjHAjYFjoT+FI1MfRiAM/cZdEl0+oVidVvRaMcAOMgJWGBUYSVhrWjdfvjKqrq1Vzsu7Cy1Vo7XXZhYuj0ahwfHY+sjo/Oy7woyhitkTQsESsZawstG6VlvDCfIlUmfSVVjpDqtL8goBLbKFhsRcwalxcB100CDkxLLQbJxKwpvb3At7Y2iRuJzd4V26HuM2tDQkPWMOamu1Awkeetx2qtDy/lvZaCW173pGIWetA/xBbF+ZgiVgjGcdutLJ7xO1l9VnMiWGhJdzl4vx4CNpN+ifJXUy7RPEuZ2C1AIY1NG5kHI4Dx8MynnBtovYkqHzi25OyP8ONiKFh3djQsCIecn2i/lBvMU+UXxwi3HyE832jvD0RsLuP8CN3Oh0+feRmixE+g7CdLb43WiEz++Jb+M//x/gB/ArCl0G4nsFuEQAAAABJRU5ErkJggg==);
|
||||
background-size: 30px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button:last-child {
|
||||
margin-left:15px;
|
||||
}
|
||||
|
||||
#epiceditor-utilbar button:hover {
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.epiceditor-edit-mode #epiceditor-utilbar button.epiceditor-toggle-edit-btn {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.epiceditor-preview-mode #epiceditor-utilbar button.epiceditor-toggle-preview-btn {
|
||||
display:none;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
html { padding:10px; }
|
||||
|
||||
body {
|
||||
border:0;
|
||||
background:rgb(41,41,41);
|
||||
font-family:monospace;
|
||||
font-size:14px;
|
||||
padding:10px;
|
||||
color:#ddd;
|
||||
line-height:1.35em;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
html { padding:10px; }
|
||||
|
||||
body {
|
||||
border:0;
|
||||
background:#fcfcfc;
|
||||
font-family:monospace;
|
||||
font-size:14px;
|
||||
padding:10px;
|
||||
line-height:1.35em;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
body {
|
||||
font-family: Georgia, "Times New Roman", Times, serif;
|
||||
line-height: 1.5;
|
||||
font-size: 87.5%;
|
||||
word-wrap: break-word;
|
||||
margin: 2em;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 1.0em 0 0.5em;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.357em;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.143em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 1.2em;
|
||||
}
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
||||
img {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f2f2f2;
|
||||
background-color: rgba(40, 40, 0, 0.06);
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f2f2f2;
|
||||
background-color: rgba(40, 40, 0, 0.06);
|
||||
margin: 10px 0;
|
||||
overflow: hidden;
|
||||
padding: 15px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
pre code {
|
||||
font-size: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
background: #f7f7f7;
|
||||
border-left: 1px solid #bbb;
|
||||
font-style: italic;
|
||||
margin: 1.5em 10px;
|
||||
padding: 0.5em 10px;
|
||||
}
|
||||
|
||||
blockquote:before {
|
||||
color: #bbb;
|
||||
content: "\201C";
|
||||
font-size: 3em;
|
||||
line-height: 0.1em;
|
||||
margin-right: 0.2em;
|
||||
vertical-align: -.4em;
|
||||
}
|
||||
|
||||
blockquote:after {
|
||||
color: #bbb;
|
||||
content: "\201D";
|
||||
font-size: 3em;
|
||||
line-height: 0.1em;
|
||||
vertical-align: -.45em;
|
||||
}
|
||||
|
||||
blockquote > p:first-child {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
table {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
border: 0;
|
||||
border-spacing: 0;
|
||||
font-size: 0.857em;
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table table {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
table tr th {
|
||||
background: #757575;
|
||||
background: rgba(0, 0, 0, 0.51);
|
||||
border-bottom-style: none;
|
||||
}
|
||||
|
||||
table tr th,
|
||||
table tr th a,
|
||||
table tr th a:hover {
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table tbody tr th {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr td,
|
||||
tr th {
|
||||
padding: 4px 9px;
|
||||
border: 1px solid #fff;
|
||||
text-align: left; /* LTR */
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background: #e4e4e4;
|
||||
background: rgba(0, 0, 0, 0.105);
|
||||
}
|
||||
|
||||
tr,
|
||||
tr:nth-child(even) {
|
||||
background: #efefef;
|
||||
background: rgba(0, 0, 0, 0.063);
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0071B3;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: #018fe2;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #23aeff;
|
||||
}
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:active,
|
||||
a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
@ -1,368 +0,0 @@
|
|||
html { padding:0 10px; }
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
background:#fff;
|
||||
}
|
||||
|
||||
#epiceditor-wrapper{
|
||||
background:white;
|
||||
}
|
||||
|
||||
#epiceditor-preview{
|
||||
padding-top:10px;
|
||||
padding-bottom:10px;
|
||||
font-family: Helvetica,arial,freesans,clean,sans-serif;
|
||||
font-size:13px;
|
||||
line-height:1.6;
|
||||
}
|
||||
|
||||
#epiceditor-preview>*:first-child{
|
||||
margin-top:0!important;
|
||||
}
|
||||
|
||||
#epiceditor-preview>*:last-child{
|
||||
margin-bottom:0!important;
|
||||
}
|
||||
|
||||
#epiceditor-preview a{
|
||||
color:#4183C4;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
#epiceditor-preview a:hover{
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
#epiceditor-preview h1,
|
||||
#epiceditor-preview h2,
|
||||
#epiceditor-preview h3,
|
||||
#epiceditor-preview h4,
|
||||
#epiceditor-preview h5,
|
||||
#epiceditor-preview h6{
|
||||
margin:20px 0 10px;
|
||||
padding:0;
|
||||
font-weight:bold;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
|
||||
#epiceditor-preview h1 tt,
|
||||
#epiceditor-preview h1 code,
|
||||
#epiceditor-preview h2 tt,
|
||||
#epiceditor-preview h2 code,
|
||||
#epiceditor-preview h3 tt,
|
||||
#epiceditor-preview h3 code,
|
||||
#epiceditor-preview h4 tt,
|
||||
#epiceditor-preview h4 code,
|
||||
#epiceditor-preview h5 tt,
|
||||
#epiceditor-preview h5 code,
|
||||
#epiceditor-preview h6 tt,
|
||||
#epiceditor-preview h6 code{
|
||||
font-size:inherit;
|
||||
}
|
||||
|
||||
#epiceditor-preview h1{
|
||||
font-size:28px;
|
||||
color:#000;
|
||||
}
|
||||
|
||||
#epiceditor-preview h2{
|
||||
font-size:24px;
|
||||
border-bottom:1px solid #ccc;
|
||||
color:#000;
|
||||
}
|
||||
|
||||
#epiceditor-preview h3{
|
||||
font-size:18px;
|
||||
}
|
||||
|
||||
#epiceditor-preview h4{
|
||||
font-size:16px;
|
||||
}
|
||||
|
||||
#epiceditor-preview h5{
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
#epiceditor-preview h6{
|
||||
color:#777;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
#epiceditor-preview p,
|
||||
#epiceditor-preview blockquote,
|
||||
#epiceditor-preview ul,
|
||||
#epiceditor-preview ol,
|
||||
#epiceditor-preview dl,
|
||||
#epiceditor-preview li,
|
||||
#epiceditor-preview table,
|
||||
#epiceditor-preview pre{
|
||||
margin:15px 0;
|
||||
}
|
||||
|
||||
#epiceditor-preview hr{
|
||||
background:transparent url('../../images/modules/pulls/dirty-shade.png') repeat-x 0 0;
|
||||
border:0 none;
|
||||
color:#ccc;
|
||||
height:4px;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview>h2:first-child,
|
||||
#epiceditor-preview>h1:first-child,
|
||||
#epiceditor-preview>h1:first-child+h2,
|
||||
#epiceditor-preview>h3:first-child,
|
||||
#epiceditor-preview>h4:first-child,
|
||||
#epiceditor-preview>h5:first-child,
|
||||
#epiceditor-preview>h6:first-child{
|
||||
margin-top:0;
|
||||
padding-top:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview h1+p,
|
||||
#epiceditor-preview h2+p,
|
||||
#epiceditor-preview h3+p,
|
||||
#epiceditor-preview h4+p,
|
||||
#epiceditor-preview h5+p,
|
||||
#epiceditor-preview h6+p{
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview li p.first{
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
#epiceditor-preview ul,
|
||||
#epiceditor-preview ol{
|
||||
padding-left:30px;
|
||||
}
|
||||
|
||||
#epiceditor-preview ul li>:first-child,
|
||||
#epiceditor-preview ol li>:first-child{
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview ul li>:last-child,
|
||||
#epiceditor-preview ol li>:last-child{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl{
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl dt{
|
||||
font-size:14px;
|
||||
font-weight:bold;
|
||||
font-style:italic;
|
||||
padding:0;
|
||||
margin:15px 0 5px;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl dt:first-child{
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl dt>:first-child{
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl dt>:last-child{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl dd{
|
||||
margin:0 0 15px;
|
||||
padding:0 15px;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl dd>:first-child{
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview dl dd>:last-child{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview blockquote{
|
||||
border-left:4px solid #DDD;
|
||||
padding:0 15px;
|
||||
color:#777;
|
||||
}
|
||||
|
||||
#epiceditor-preview blockquote>:first-child{
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview blockquote>:last-child{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview table{
|
||||
padding:0;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
#epiceditor-preview table tr{
|
||||
border-top:1px solid #ccc;
|
||||
background-color:#fff;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview table tr:nth-child(2n){
|
||||
background-color:#f8f8f8;
|
||||
}
|
||||
|
||||
#epiceditor-preview table tr th{
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#epiceditor-preview table tr th,
|
||||
#epiceditor-preview table tr td{
|
||||
border:1px solid #ccc;
|
||||
text-align:left;
|
||||
margin:0;
|
||||
padding:6px 13px;
|
||||
}
|
||||
|
||||
#epiceditor-preview table tr th>:first-child,
|
||||
#epiceditor-preview table tr td>:first-child{
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview table tr th>:last-child,
|
||||
#epiceditor-preview table tr td>:last-child{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
#epiceditor-preview img{
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.frame{
|
||||
display:block;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.frame>span{
|
||||
border:1px solid #ddd;
|
||||
display:block;
|
||||
float:left;
|
||||
overflow:hidden;
|
||||
margin:13px 0 0;
|
||||
padding:7px;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.frame span img{
|
||||
display:block;
|
||||
float:left;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.frame span span{
|
||||
clear:both;
|
||||
color:#333;
|
||||
display:block;
|
||||
padding:5px 0 0;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.align-center{
|
||||
display:block;
|
||||
overflow:hidden;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.align-center>span{
|
||||
display:block;
|
||||
overflow:hidden;
|
||||
margin:13px auto 0;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.align-center span img{
|
||||
margin:0 auto;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.align-right{
|
||||
display:block;
|
||||
overflow:hidden;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.align-right>span{
|
||||
display:block;
|
||||
overflow:hidden;
|
||||
margin:13px 0 0;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.align-right span img{
|
||||
margin:0;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.float-left{
|
||||
display:block;
|
||||
margin-right:13px;
|
||||
overflow:hidden;
|
||||
float:left;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.float-left span{
|
||||
margin:13px 0 0;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.float-right{
|
||||
display:block;
|
||||
margin-left:13px;
|
||||
overflow:hidden;
|
||||
float:right;
|
||||
}
|
||||
|
||||
#epiceditor-preview span.float-right>span{
|
||||
display:block;
|
||||
overflow:hidden;
|
||||
margin:13px auto 0;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
#epiceditor-preview code,
|
||||
#epiceditor-preview tt{
|
||||
margin:0 2px;
|
||||
padding:0 5px;
|
||||
white-space:nowrap;
|
||||
border:1px solid #eaeaea;
|
||||
background-color:#f8f8f8;
|
||||
border-radius:3px;
|
||||
}
|
||||
|
||||
#epiceditor-preview pre>code{
|
||||
margin:0;
|
||||
padding:0;
|
||||
white-space:pre;
|
||||
border:none;
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
#epiceditor-preview .highlight pre,
|
||||
#epiceditor-preview pre{
|
||||
background-color:#f8f8f8;
|
||||
border:1px solid #ccc;
|
||||
font-size:13px;
|
||||
line-height:19px;
|
||||
overflow:auto;
|
||||
padding:6px 10px;
|
||||
border-radius:3px;
|
||||
}
|
||||
|
||||
#epiceditor-preview pre code,
|
||||
#epiceditor-preview pre tt{
|
||||
background-color:transparent;
|
||||
border:none;
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
html { padding:0 10px; }
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:10px 0;
|
||||
background:#000;
|
||||
}
|
||||
|
||||
#epiceditor-preview h1,
|
||||
#epiceditor-preview h2,
|
||||
#epiceditor-preview h3,
|
||||
#epiceditor-preview h4,
|
||||
#epiceditor-preview h5,
|
||||
#epiceditor-preview h6,
|
||||
#epiceditor-preview p,
|
||||
#epiceditor-preview blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#epiceditor-preview {
|
||||
background:#000;
|
||||
font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: #ccc;
|
||||
}
|
||||
#epiceditor-preview a {
|
||||
color: #fff;
|
||||
}
|
||||
#epiceditor-preview a:hover {
|
||||
color: #00ff00;
|
||||
text-decoration: none;
|
||||
}
|
||||
#epiceditor-preview a img {
|
||||
border: none;
|
||||
}
|
||||
#epiceditor-preview p {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
#epiceditor-preview h1,
|
||||
#epiceditor-preview h2,
|
||||
#epiceditor-preview h3,
|
||||
#epiceditor-preview h4,
|
||||
#epiceditor-preview h5,
|
||||
#epiceditor-preview h6 {
|
||||
color: #cdcdcd;
|
||||
line-height: 36px;
|
||||
}
|
||||
#epiceditor-preview h1 {
|
||||
margin-bottom: 18px;
|
||||
font-size: 30px;
|
||||
}
|
||||
#epiceditor-preview h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
#epiceditor-preview h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
#epiceditor-preview h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
#epiceditor-preview h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
#epiceditor-preview h6 {
|
||||
font-size: 13px;
|
||||
}
|
||||
#epiceditor-preview hr {
|
||||
margin: 0 0 19px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
#epiceditor-preview blockquote {
|
||||
padding: 13px 13px 21px 15px;
|
||||
margin-bottom: 18px;
|
||||
font-family:georgia,serif;
|
||||
font-style: italic;
|
||||
}
|
||||
#epiceditor-preview blockquote:before {
|
||||
content:"\201C";
|
||||
font-size:40px;
|
||||
margin-left:-10px;
|
||||
font-family:georgia,serif;
|
||||
color:#eee;
|
||||
}
|
||||
#epiceditor-preview blockquote p {
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 18px;
|
||||
margin-bottom: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
#epiceditor-preview code, #epiceditor-preview pre {
|
||||
font-family: Monaco, Andale Mono, Courier New, monospace;
|
||||
}
|
||||
#epiceditor-preview code {
|
||||
background-color: #000;
|
||||
color: #f92672;
|
||||
padding: 1px 3px;
|
||||
font-size: 12px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#epiceditor-preview pre {
|
||||
display: block;
|
||||
padding: 14px;
|
||||
color:#66d9ef;
|
||||
margin: 0 0 18px;
|
||||
line-height: 16px;
|
||||
font-size: 11px;
|
||||
border: 1px solid #d9d9d9;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
#epiceditor-preview pre code {
|
||||
background-color: #000;
|
||||
color:#ccc;
|
||||
font-size: 11px;
|
||||
padding: 0;
|
||||
}
|