mirror of https://github.com/JohnHammond/CTFd.git
2.2.0 (#1188)
2.2.0 / 2019-12-22 ================== ## Notice 2.2.0 focuses on updating the front end of CTFd to use more modern programming practices and changes some aspects of core CTFd design. If your current installation is using a custom theme or custom plugin with ***any*** kind of JavaScript, it is likely that you will need to upgrade that theme/plugin to be useable with v2.2.0. **General** * Team size limits can now be enforced from the configuration panel * Access tokens functionality for API usage * Admins can now choose how to deliver their notifications * Toast (new default) * Alert * Background * Sound On / Sound Off * There is now a notification counter showing how many unread notifications were received * Setup has been redesigned to have multiple steps * Added Description * Added Start time and End time, * Added MajorLeagueCyber integration * Added Theme and color selection * Fixes issue where updating dynamic challenges could change the value to an incorrect value * Properly use a less restrictive regex to validate email addresses * Bump Python dependencies to latest working versions * Admins can now give awards to team members from the team's admin panel page **API** * Team member removals (`DELETE /api/v1/teams/[team_id]/members`) from the admin panel will now delete the removed members's Submissions, Awards, Unlocks **Admin Panel** * Admins can now user a color input box to specify a theme color which is injected as part of the CSS configuration. Theme developers can use this CSS value to change colors and styles accordingly. * Challenge updates will now alert you if the challenge doesn't have a flag * Challenge entry now allows you to upload files and enter simple flags from the initial challenge creation page **Themes** * Significant JavaScript and CSS rewrite to use ES6, Webpack, yarn, and babel * Theme asset specially generated URLs * Static theme assets are now loaded with either .dev.extension or .min.extension depending on production or development (i.e. debug server) * Static theme assets are also given a `d` GET parameter that changes per server start. Used to bust browser caches. * Use `defer` for script tags to not block page rendering * Only show the MajorLeagueCyber button if configured in configuration * The admin panel now links to https://help.ctfd.io/ in the top right * Create an `ezToast()` function to use [Bootstrap's toasts](https://getbootstrap.com/docs/4.3/components/toasts/) * The user-facing navbar now features icons * Awards shown on a user's profile can now have award icons * The default MarkdownIt render created by CTFd will now open links in new tabs * Country flags can now be shown on the user pages **Deployment** * Switch `Dockerfile` from `python:2.7-alpine` to `python:3.7-alpine` * Add `SERVER_SENT_EVENTS` config value to control whether Notifications are enabled * Challenge ID is now recorded in the submission log **Plugins** * Add an endpoint parameter to `register_plugin_assets_directory()` and `register_plugin_asset()` to control what endpoint Flask uses for the added route **Miscellaneous** * `CTFd.utils.email.sendmail()` now allows the caller to specify subject as an argument * The subject allows for injecting custom variable via the new `CTFd.utils.formatters.safe_format()` function * Admin user information is now error checked during setup * Added yarn to the toolchain and the yarn dev, yarn build, yarn verify, and yarn clean scripts * Prevent old CTFd imports from being importedbulk-clear-sessions 2.2.0
parent
6d192a7c14
commit
b8d0f80d01
|
@ -28,6 +28,7 @@ before_install:
|
|||
- python3.6 -m pip install black==19.3b0
|
||||
install:
|
||||
- pip install -r development.txt
|
||||
- yarn install --non-interactive
|
||||
- yarn global add prettier@1.17.0
|
||||
before_script:
|
||||
- psql -c 'create database ctfd;' -U postgres
|
||||
|
|
63
CHANGELOG.md
63
CHANGELOG.md
|
@ -1,3 +1,66 @@
|
|||
2.2.0 / 2019-12-22
|
||||
==================
|
||||
|
||||
## Notice
|
||||
2.2.0 focuses on updating the front end of CTFd to use more modern programming practices and changes some aspects of core CTFd design. If your current installation is using a custom theme or custom plugin with ***any*** kind of JavaScript, it is likely that you will need to upgrade that theme/plugin to be useable with v2.2.0.
|
||||
|
||||
**General**
|
||||
* Team size limits can now be enforced from the configuration panel
|
||||
* Access tokens functionality for API usage
|
||||
* Admins can now choose how to deliver their notifications
|
||||
* Toast (new default)
|
||||
* Alert
|
||||
* Background
|
||||
* Sound On / Sound Off
|
||||
* There is now a notification counter showing how many unread notifications were received
|
||||
* Setup has been redesigned to have multiple steps
|
||||
* Added Description
|
||||
* Added Start time and End time,
|
||||
* Added MajorLeagueCyber integration
|
||||
* Added Theme and color selection
|
||||
* Fixes issue where updating dynamic challenges could change the value to an incorrect value
|
||||
* Properly use a less restrictive regex to validate email addresses
|
||||
* Bump Python dependencies to latest working versions
|
||||
* Admins can now give awards to team members from the team's admin panel page
|
||||
|
||||
**API**
|
||||
* Team member removals (`DELETE /api/v1/teams/[team_id]/members`) from the admin panel will now delete the removed members's Submissions, Awards, Unlocks
|
||||
|
||||
**Admin Panel**
|
||||
* Admins can now user a color input box to specify a theme color which is injected as part of the CSS configuration. Theme developers can use this CSS value to change colors and styles accordingly.
|
||||
* Challenge updates will now alert you if the challenge doesn't have a flag
|
||||
* Challenge entry now allows you to upload files and enter simple flags from the initial challenge creation page
|
||||
|
||||
**Themes**
|
||||
* Significant JavaScript and CSS rewrite to use ES6, Webpack, yarn, and babel
|
||||
* Theme asset specially generated URLs
|
||||
* Static theme assets are now loaded with either .dev.extension or .min.extension depending on production or development (i.e. debug server)
|
||||
* Static theme assets are also given a `d` GET parameter that changes per server start. Used to bust browser caches.
|
||||
* Use `defer` for script tags to not block page rendering
|
||||
* Only show the MajorLeagueCyber button if configured in configuration
|
||||
* The admin panel now links to https://help.ctfd.io/ in the top right
|
||||
* Create an `ezToast()` function to use [Bootstrap's toasts](https://getbootstrap.com/docs/4.3/components/toasts/)
|
||||
* The user-facing navbar now features icons
|
||||
* Awards shown on a user's profile can now have award icons
|
||||
* The default MarkdownIt render created by CTFd will now open links in new tabs
|
||||
* Country flags can now be shown on the user pages
|
||||
|
||||
**Deployment**
|
||||
* Switch `Dockerfile` from `python:2.7-alpine` to `python:3.7-alpine`
|
||||
* Add `SERVER_SENT_EVENTS` config value to control whether Notifications are enabled
|
||||
* Challenge ID is now recorded in the submission log
|
||||
|
||||
**Plugins**
|
||||
* Add an endpoint parameter to `register_plugin_assets_directory()` and `register_plugin_asset()` to control what endpoint Flask uses for the added route
|
||||
|
||||
**Miscellaneous**
|
||||
* `CTFd.utils.email.sendmail()` now allows the caller to specify subject as an argument
|
||||
* The subject allows for injecting custom variable via the new `CTFd.utils.formatters.safe_format()` function
|
||||
* Admin user information is now error checked during setup
|
||||
* Added yarn to the toolchain and the yarn dev, yarn build, yarn verify, and yarn clean scripts
|
||||
* Prevent old CTFd imports from being imported
|
||||
|
||||
|
||||
2.1.5 / 2019-10-2
|
||||
=================
|
||||
|
||||
|
|
|
@ -21,14 +21,16 @@ from CTFd.utils.initialization import (
|
|||
init_logs,
|
||||
init_events,
|
||||
)
|
||||
from CTFd.utils.crypto import sha256
|
||||
from CTFd.plugins import init_plugins
|
||||
import datetime
|
||||
|
||||
# Hack to support Unicode in Python 2 properly
|
||||
if sys.version_info[0] < 3:
|
||||
reload(sys) # noqa: F821
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = "2.1.5"
|
||||
__version__ = "2.2.0"
|
||||
|
||||
|
||||
class CTFdRequest(Request):
|
||||
|
@ -50,6 +52,12 @@ class CTFdFlask(Flask):
|
|||
self.jinja_environment = SandboxedBaseEnvironment
|
||||
self.session_interface = CachingSessionInterface(key_prefix="session")
|
||||
self.request_class = CTFdRequest
|
||||
|
||||
# Store server start time
|
||||
self.start_time = datetime.datetime.utcnow()
|
||||
|
||||
# Create generally unique run identifier
|
||||
self.run_id = sha256(str(self.start_time))[0:8]
|
||||
Flask.__init__(self, *args, **kwargs)
|
||||
|
||||
def create_jinja_environment(self):
|
||||
|
|
|
@ -2,7 +2,7 @@ from flask import render_template
|
|||
from CTFd.utils.decorators import admins_only
|
||||
from CTFd.utils.updates import update_check
|
||||
from CTFd.utils.modes import get_model
|
||||
from CTFd.models import db, Solves, Challenges, Fails, Tracking
|
||||
from CTFd.models import db, Solves, Challenges, Fails, Tracking, Teams, Users
|
||||
from CTFd.admin import admin
|
||||
|
||||
|
||||
|
@ -13,7 +13,8 @@ def statistics():
|
|||
|
||||
Model = get_model()
|
||||
|
||||
teams_registered = Model.query.count()
|
||||
teams_registered = Teams.query.count()
|
||||
users_registered = Users.query.count()
|
||||
|
||||
wrong_count = (
|
||||
Fails.query.join(Model, Fails.account_id == Model.id)
|
||||
|
@ -65,6 +66,7 @@ def statistics():
|
|||
|
||||
return render_template(
|
||||
"admin/statistics.html",
|
||||
user_count=users_registered,
|
||||
team_count=teams_registered,
|
||||
ip_count=ip_count,
|
||||
wrong_count=wrong_count,
|
||||
|
|
|
@ -16,6 +16,7 @@ from CTFd.api.v1.config import configs_namespace
|
|||
from CTFd.api.v1.notifications import notifications_namespace
|
||||
from CTFd.api.v1.pages import pages_namespace
|
||||
from CTFd.api.v1.unlocks import unlocks_namespace
|
||||
from CTFd.api.v1.tokens import tokens_namespace
|
||||
|
||||
api = Blueprint("api", __name__, url_prefix="/api/v1")
|
||||
CTFd_API_v1 = Api(api, version="v1", doc=current_app.config.get("SWAGGER_UI"))
|
||||
|
@ -35,3 +36,4 @@ CTFd_API_v1.add_namespace(notifications_namespace, "/notifications")
|
|||
CTFd_API_v1.add_namespace(configs_namespace, "/configs")
|
||||
CTFd_API_v1.add_namespace(pages_namespace, "/pages")
|
||||
CTFd_API_v1.add_namespace(unlocks_namespace, "/unlocks")
|
||||
CTFd_API_v1.add_namespace(tokens_namespace, "/tokens")
|
||||
|
|
|
@ -260,13 +260,10 @@ class Challenge(Resource):
|
|||
Model = get_model()
|
||||
|
||||
if scores_visible() is True and accounts_visible() is True:
|
||||
solves = (
|
||||
Solves.query.join(Model, Solves.account_id == Model.id)
|
||||
.filter(
|
||||
Solves.challenge_id == chal.id,
|
||||
Model.banned == False,
|
||||
Model.hidden == False,
|
||||
)
|
||||
solves = Solves.query.join(Model, Solves.account_id == Model.id).filter(
|
||||
Solves.challenge_id == chal.id,
|
||||
Model.banned == False,
|
||||
Model.hidden == False,
|
||||
)
|
||||
|
||||
# Only show solves that happened before freeze time if configured
|
||||
|
@ -391,8 +388,9 @@ class ChallengeAttempt(Resource):
|
|||
)
|
||||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} with kpm {kpm} [TOO FAST]",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [TOO FAST]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
# Submitting too fast
|
||||
|
@ -437,8 +435,9 @@ class ChallengeAttempt(Resource):
|
|||
|
||||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} with kpm {kpm} [CORRECT]",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [CORRECT]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
return {
|
||||
|
@ -454,8 +453,9 @@ class ChallengeAttempt(Resource):
|
|||
|
||||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} with kpm {kpm} [WRONG]",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [WRONG]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
|
||||
|
@ -487,8 +487,9 @@ class ChallengeAttempt(Resource):
|
|||
else:
|
||||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} with kpm {kpm} [ALREADY SOLVED]",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [ALREADY SOLVED]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
return {
|
||||
|
|
|
@ -34,6 +34,13 @@ class NotificantionList(Resource):
|
|||
db.session.commit()
|
||||
|
||||
response = schema.dump(result.data)
|
||||
|
||||
# Grab additional settings
|
||||
notif_type = req.get("type", "alert")
|
||||
notif_sound = req.get("sound", True)
|
||||
response.data["type"] = notif_type
|
||||
response.data["sound"] = notif_sound
|
||||
|
||||
current_app.events_manager.publish(data=response.data, type="notification")
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from flask import session, request, abort
|
||||
from flask_restplus import Namespace, Resource
|
||||
from CTFd.models import db, Users, Teams
|
||||
from CTFd.models import db, Users, Teams, Submissions, Awards, Unlocks
|
||||
from CTFd.schemas.teams import TeamSchema
|
||||
from CTFd.schemas.submissions import SubmissionSchema
|
||||
from CTFd.schemas.awards import AwardSchema
|
||||
|
@ -68,6 +68,8 @@ class TeamPublic(Resource):
|
|||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
response.data["place"] = team.place
|
||||
response.data["score"] = team.score
|
||||
return {"success": True, "data": response.data}
|
||||
|
||||
@admins_only
|
||||
|
@ -118,6 +120,8 @@ class TeamPrivate(Resource):
|
|||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
response.data["place"] = team.place
|
||||
response.data["score"] = team.score
|
||||
return {"success": True, "data": response.data}
|
||||
|
||||
@authed_only
|
||||
|
@ -206,6 +210,12 @@ class TeamMembers(Resource):
|
|||
|
||||
if user.team_id == team.id:
|
||||
team.members.remove(user)
|
||||
|
||||
# Remove information that links the user to the team
|
||||
Submissions.query.filter_by(user_id=user.id).delete()
|
||||
Awards.query.filter_by(user_id=user.id).delete()
|
||||
Unlocks.query.filter_by(user_id=user.id).delete()
|
||||
|
||||
db.session.commit()
|
||||
else:
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
from flask import request, session
|
||||
from flask_restplus import Namespace, Resource
|
||||
from CTFd.models import db, Tokens
|
||||
from CTFd.utils.user import get_current_user, is_admin
|
||||
from CTFd.schemas.tokens import TokenSchema
|
||||
from CTFd.utils.security.auth import generate_user_token
|
||||
from CTFd.utils.decorators import require_verified_emails, authed_only
|
||||
import datetime
|
||||
|
||||
tokens_namespace = Namespace("tokens", description="Endpoint to retrieve Tokens")
|
||||
|
||||
|
||||
@tokens_namespace.route("")
|
||||
class TokenList(Resource):
|
||||
@require_verified_emails
|
||||
@authed_only
|
||||
def get(self):
|
||||
user = get_current_user()
|
||||
tokens = Tokens.query.filter_by(user_id=user.id)
|
||||
response = TokenSchema(view=["id", "type", "expiration"], many=True).dump(
|
||||
tokens
|
||||
)
|
||||
|
||||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
|
||||
@require_verified_emails
|
||||
@authed_only
|
||||
def post(self):
|
||||
req = request.get_json()
|
||||
expiration = req.get("expiration")
|
||||
if expiration:
|
||||
expiration = datetime.datetime.strptime(expiration, "%Y-%m-%d")
|
||||
|
||||
user = get_current_user()
|
||||
token = generate_user_token(user, expiration=expiration)
|
||||
|
||||
# Explicitly use admin view so that user's can see the value of their token
|
||||
schema = TokenSchema(view="admin")
|
||||
response = schema.dump(token)
|
||||
|
||||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
|
||||
|
||||
@tokens_namespace.route("/<token_id>")
|
||||
@tokens_namespace.param("token_id", "A Token ID")
|
||||
class TokenDetail(Resource):
|
||||
@require_verified_emails
|
||||
@authed_only
|
||||
def get(self, token_id):
|
||||
if is_admin():
|
||||
token = Tokens.query.filter_by(id=token_id).first_or_404()
|
||||
else:
|
||||
token = Tokens.query.filter_by(
|
||||
id=token_id, user_id=session["id"]
|
||||
).first_or_404()
|
||||
|
||||
schema = TokenSchema(view=session.get("type", "user"))
|
||||
response = schema.dump(token)
|
||||
|
||||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
|
||||
@require_verified_emails
|
||||
@authed_only
|
||||
def delete(self, token_id):
|
||||
if is_admin():
|
||||
token = Tokens.query.filter_by(id=token_id).first_or_404()
|
||||
else:
|
||||
user = get_current_user()
|
||||
token = Tokens.query.filter_by(id=token_id, user_id=user.id).first_or_404()
|
||||
db.session.delete(token)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
return {"success": True}
|
|
@ -400,6 +400,15 @@ def oauth_redirect():
|
|||
db.session.add(team)
|
||||
db.session.commit()
|
||||
|
||||
team_size_limit = get_config("team_size", default=0)
|
||||
if team_size_limit and len(team.members) >= team_size_limit:
|
||||
plural = "" if team_size_limit == 1 else "s"
|
||||
size_error = "Teams are limited to {limit} member{plural}.".format(
|
||||
limit=team_size_limit, plural=plural
|
||||
)
|
||||
error_for(endpoint="auth.login", message=size_error)
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
team.members.append(user)
|
||||
db.session.commit()
|
||||
|
||||
|
|
|
@ -228,6 +228,9 @@ class Config(object):
|
|||
APPLICATION_ROOT:
|
||||
Specifies what path CTFd is mounted under. It can be used to run CTFd in a subdirectory.
|
||||
Example: /ctfd
|
||||
|
||||
SERVER_SENT_EVENTS:
|
||||
Specifies whether or not to enable to server-sent events based Notifications system.
|
||||
"""
|
||||
REVERSE_PROXY = os.getenv("REVERSE_PROXY") or False
|
||||
TEMPLATES_AUTO_RELOAD = not os.getenv("TEMPLATES_AUTO_RELOAD") # Defaults True
|
||||
|
@ -237,6 +240,7 @@ class Config(object):
|
|||
SWAGGER_UI = "/" if os.getenv("SWAGGER_UI") is not None else False # Defaults False
|
||||
UPDATE_CHECK = not os.getenv("UPDATE_CHECK") # Defaults True
|
||||
APPLICATION_ROOT = os.getenv("APPLICATION_ROOT") or "/"
|
||||
SERVER_SENT_EVENTS = not os.getenv("SERVER_SENT_EVENTS") # Defaults True
|
||||
|
||||
"""
|
||||
=== OAUTH ===
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from flask import current_app, Blueprint, Response, stream_with_context
|
||||
from CTFd.utils import get_app_config
|
||||
from CTFd.utils.decorators import authed_only, ratelimit
|
||||
|
||||
events = Blueprint("events", __name__)
|
||||
|
@ -13,4 +14,8 @@ def subscribe():
|
|||
for event in current_app.events_manager.subscribe():
|
||||
yield str(event)
|
||||
|
||||
enabled = get_app_config("SERVER_SENT_EVENTS")
|
||||
if enabled is False:
|
||||
return ("", 204)
|
||||
|
||||
return Response(gen(), mimetype="text/event-stream")
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class UserNotFoundException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UserTokenExpiredException(Exception):
|
||||
pass
|
|
@ -281,7 +281,12 @@ class Users(db.Model):
|
|||
|
||||
@property
|
||||
def place(self):
|
||||
return self.get_place(admin=False)
|
||||
from CTFd.utils.config.visibility import scores_visible
|
||||
|
||||
if scores_visible():
|
||||
return self.get_place(admin=False)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_solves(self, admin=False):
|
||||
solves = Solves.query.filter_by(user_id=self.id)
|
||||
|
@ -417,7 +422,12 @@ class Teams(db.Model):
|
|||
|
||||
@property
|
||||
def place(self):
|
||||
return self.get_place(admin=False)
|
||||
from CTFd.utils.config.visibility import scores_visible
|
||||
|
||||
if scores_visible():
|
||||
return self.get_place(admin=False)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_solves(self, admin=False):
|
||||
member_ids = [member.id for member in self.members]
|
||||
|
@ -631,6 +641,33 @@ class Configs(db.Model):
|
|||
super(Configs, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class Tokens(db.Model):
|
||||
__tablename__ = "tokens"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
type = db.Column(db.String(32))
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="CASCADE"))
|
||||
created = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||
expiration = db.Column(
|
||||
db.DateTime,
|
||||
default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30),
|
||||
)
|
||||
value = db.Column(db.String(128), unique=True)
|
||||
|
||||
user = db.relationship("Users", foreign_keys="Tokens.user_id", lazy="select")
|
||||
|
||||
__mapper_args__ = {"polymorphic_on": type}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Tokens, self).__init__(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Token %r>" % self.id
|
||||
|
||||
|
||||
class UserTokens(Tokens):
|
||||
__mapper_args__ = {"polymorphic_identity": "user"}
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def get_config(key):
|
||||
"""
|
||||
|
|
|
@ -18,7 +18,7 @@ from CTFd.utils.config.pages import get_pages
|
|||
Menu = namedtuple("Menu", ["title", "route"])
|
||||
|
||||
|
||||
def register_plugin_assets_directory(app, base_path, admins_only=False):
|
||||
def register_plugin_assets_directory(app, base_path, admins_only=False, endpoint=None):
|
||||
"""
|
||||
Registers a directory to serve assets
|
||||
|
||||
|
@ -28,15 +28,17 @@ def register_plugin_assets_directory(app, base_path, admins_only=False):
|
|||
:return:
|
||||
"""
|
||||
base_path = base_path.strip("/")
|
||||
if endpoint is None:
|
||||
endpoint = base_path.replace("/", ".")
|
||||
|
||||
def assets_handler(path):
|
||||
return send_from_directory(base_path, path)
|
||||
|
||||
rule = "/" + base_path + "/<path:path>"
|
||||
app.add_url_rule(rule=rule, endpoint=base_path, view_func=assets_handler)
|
||||
app.add_url_rule(rule=rule, endpoint=endpoint, view_func=assets_handler)
|
||||
|
||||
|
||||
def register_plugin_asset(app, asset_path, admins_only=False):
|
||||
def register_plugin_asset(app, asset_path, admins_only=False, endpoint=None):
|
||||
"""
|
||||
Registers an file path to be served by CTFd
|
||||
|
||||
|
@ -46,6 +48,8 @@ def register_plugin_asset(app, asset_path, admins_only=False):
|
|||
:return:
|
||||
"""
|
||||
asset_path = asset_path.strip("/")
|
||||
if endpoint is None:
|
||||
endpoint = asset_path.replace("/", ".")
|
||||
|
||||
def asset_handler():
|
||||
return send_file(asset_path)
|
||||
|
@ -53,7 +57,7 @@ def register_plugin_asset(app, asset_path, admins_only=False):
|
|||
if admins_only:
|
||||
asset_handler = admins_only_wrapper(asset_handler)
|
||||
rule = "/" + asset_path
|
||||
app.add_url_rule(rule=rule, endpoint=asset_path, view_func=asset_handler)
|
||||
app.add_url_rule(rule=rule, endpoint=endpoint, view_func=asset_handler)
|
||||
|
||||
|
||||
def override_template(*args, **kwargs):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<form method="POST" action="{{ script_root }}/admin/challenges/new" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Name<br>
|
||||
Name:<br>
|
||||
<small class="form-text text-muted">
|
||||
The name of your challenge
|
||||
</small>
|
||||
|
@ -11,7 +11,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Category<br>
|
||||
Category:<br>
|
||||
<small class="form-text text-muted">
|
||||
The category of your challenge
|
||||
</small>
|
||||
|
@ -22,10 +22,10 @@
|
|||
<ul class="nav nav-tabs" role="tablist" id="new-desc-edit">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#new-desc-write" aria-controls="home" role="tab"
|
||||
data-toggle="tab">Write</a>
|
||||
data-toggle="tab" tabindex="-1">Write</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#new-desc-preview" aria-controls="home" role="tab" data-toggle="tab">Preview</a>
|
||||
<a class="nav-link" href="#new-desc-preview" aria-controls="home" role="tab" data-toggle="tab" tabindex="-1">Preview</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Value<br>
|
||||
Value:<br>
|
||||
<small class="form-text text-muted">
|
||||
This is how many points are rewarded for solving this challenge.
|
||||
</small>
|
||||
|
|
|
@ -1,29 +1,39 @@
|
|||
// Markdown Preview
|
||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#desc-preview') {
|
||||
var editor_value = $('#desc-editor').val();
|
||||
$(event.target.hash).html(
|
||||
window.challenge.render(editor_value)
|
||||
);
|
||||
}
|
||||
});
|
||||
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#new-desc-preview') {
|
||||
var editor_value = $('#new-desc-editor').val();
|
||||
$(event.target.hash).html(
|
||||
window.challenge.render(editor_value)
|
||||
);
|
||||
}
|
||||
});
|
||||
$("#solve-attempts-checkbox").change(function () {
|
||||
if (this.checked) {
|
||||
$('#solve-attempts-input').show();
|
||||
} else {
|
||||
$('#solve-attempts-input').hide();
|
||||
$('#max_attempts').val('');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
CTFd.plugin.run((_CTFd) => {
|
||||
const $ = _CTFd.lib.$
|
||||
const md = _CTFd.lib.markdown()
|
||||
$('a[href="#new-desc-preview"]').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#new-desc-preview') {
|
||||
var editor_value = $('#new-desc-editor').val();
|
||||
$(event.target.hash).html(
|
||||
md.render(editor_value)
|
||||
);
|
||||
}
|
||||
});
|
||||
// $('#desc-edit').on('shown.bs.tab', function (event) {
|
||||
// if (event.target.hash == '#desc-preview') {
|
||||
// var editor_value = $('#desc-editor').val();
|
||||
// $(event.target.hash).html(
|
||||
// window.challenge.render(editor_value)
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// $('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||
// if (event.target.hash == '#new-desc-preview') {
|
||||
// var editor_value = $('#new-desc-editor').val();
|
||||
// $(event.target.hash).html(
|
||||
// window.challenge.render(editor_value)
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// $("#solve-attempts-checkbox").change(function () {
|
||||
// if (this.checked) {
|
||||
// $('#solve-attempts-input').show();
|
||||
// } else {
|
||||
// $('#solve-attempts-input').hide();
|
||||
// $('#max_attempts').val('');
|
||||
// }
|
||||
// });
|
||||
// $(document).ready(function () {
|
||||
// $('[data-toggle="tooltip"]').tooltip();
|
||||
// });
|
||||
})
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<small class="form-text text-muted">Changes the state of the challenge (e.g. visible, hidden)</small>
|
||||
</label>
|
||||
|
||||
<select class="form-control" name="state">
|
||||
<select class="form-control custom-select" name="state">
|
||||
<option value="visible" {% if challenge.state == "visible" %}selected{% endif %}>Visible</option>
|
||||
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
|
||||
</select>
|
||||
|
|
|
@ -31,8 +31,7 @@
|
|||
<div class="challenge-hints hint-row row">
|
||||
{% for hint in hints %}
|
||||
<div class='col-md-12 hint-button-wrapper text-center mb-3'>
|
||||
<a class="btn btn-info btn-hint btn-block" href="javascript:;"
|
||||
onclick="javascript:loadhint({{ hint.id }})">
|
||||
<a class="btn btn-info btn-hint btn-block load-hint" href="javascript:;" data-hint-id="{{ hint.id }}">
|
||||
{% if hint.content %}
|
||||
<small>
|
||||
View Hint
|
||||
|
|
|
@ -1,57 +1,40 @@
|
|||
window.challenge.data = undefined;
|
||||
CTFd._internal.challenge.data = undefined
|
||||
|
||||
window.challenge.renderer = new markdownit({
|
||||
html: true,
|
||||
linkify: true,
|
||||
});
|
||||
|
||||
window.challenge.preRender = function () {
|
||||
|
||||
};
|
||||
|
||||
window.challenge.render = function (markdown) {
|
||||
return window.challenge.renderer.render(markdown);
|
||||
};
|
||||
CTFd._internal.challenge.renderer = CTFd.lib.markdown();
|
||||
|
||||
|
||||
window.challenge.postRender = function () {
|
||||
CTFd._internal.challenge.preRender = function () { }
|
||||
|
||||
};
|
||||
CTFd._internal.challenge.render = function (markdown) {
|
||||
return CTFd._internal.challenge.renderer.render(markdown)
|
||||
}
|
||||
|
||||
|
||||
window.challenge.submit = function (cb, preview) {
|
||||
var challenge_id = parseInt($('#challenge-id').val());
|
||||
var submission = $('#submission-input').val();
|
||||
var url = "/api/v1/challenges/attempt";
|
||||
CTFd._internal.challenge.postRender = function () { }
|
||||
|
||||
|
||||
CTFd._internal.challenge.submit = function (preview) {
|
||||
var challenge_id = parseInt(CTFd.lib.$('#challenge-id').val())
|
||||
var submission = CTFd.lib.$('#submission-input').val()
|
||||
|
||||
var body = {
|
||||
'challenge_id': challenge_id,
|
||||
'submission': submission,
|
||||
}
|
||||
var params = {}
|
||||
if (preview) {
|
||||
url += "?preview=true";
|
||||
params['preview'] = true
|
||||
}
|
||||
|
||||
var params = {
|
||||
'challenge_id': challenge_id,
|
||||
'submission': submission
|
||||
};
|
||||
|
||||
CTFd.fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
}).then(function (response) {
|
||||
return CTFd.api.post_challenge_attempt(params, body).then(function (response) {
|
||||
if (response.status === 429) {
|
||||
// User was ratelimited but process response
|
||||
return response.json();
|
||||
return response
|
||||
}
|
||||
if (response.status === 403) {
|
||||
// User is not logged in or CTF is paused.
|
||||
return response.json();
|
||||
return response
|
||||
}
|
||||
return response.json();
|
||||
}).then(function (response) {
|
||||
cb(response);
|
||||
});
|
||||
return response
|
||||
})
|
||||
};
|
|
@ -42,6 +42,42 @@ class DynamicValueChallenge(BaseChallenge):
|
|||
static_folder="assets",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def calculate_value(cls, challenge):
|
||||
Model = get_model()
|
||||
|
||||
solve_count = (
|
||||
Solves.query.join(Model, Solves.account_id == Model.id)
|
||||
.filter(
|
||||
Solves.challenge_id == challenge.id,
|
||||
Model.hidden == False,
|
||||
Model.banned == False,
|
||||
)
|
||||
.count()
|
||||
)
|
||||
|
||||
# If the solve count is 0 we shouldn't manipulate the solve count to
|
||||
# let the math update back to normal
|
||||
if solve_count != 0:
|
||||
# We subtract -1 to allow the first solver to get max point value
|
||||
solve_count -= 1
|
||||
|
||||
# It is important that this calculation takes into account floats.
|
||||
# Hence this file uses from __future__ import division
|
||||
value = (
|
||||
((challenge.minimum - challenge.initial) / (challenge.decay ** 2))
|
||||
* (solve_count ** 2)
|
||||
) + challenge.initial
|
||||
|
||||
value = math.ceil(value)
|
||||
|
||||
if value < challenge.minimum:
|
||||
value = challenge.minimum
|
||||
|
||||
challenge.value = value
|
||||
db.session.commit()
|
||||
return challenge
|
||||
|
||||
@staticmethod
|
||||
def create(request):
|
||||
"""
|
||||
|
@ -106,34 +142,7 @@ class DynamicValueChallenge(BaseChallenge):
|
|||
value = float(value)
|
||||
setattr(challenge, attr, value)
|
||||
|
||||
Model = get_model()
|
||||
|
||||
solve_count = (
|
||||
Solves.query.join(Model, Solves.account_id == Model.id)
|
||||
.filter(
|
||||
Solves.challenge_id == challenge.id,
|
||||
Model.hidden == False,
|
||||
Model.banned == False,
|
||||
)
|
||||
.count()
|
||||
)
|
||||
|
||||
# It is important that this calculation takes into account floats.
|
||||
# Hence this file uses from __future__ import division
|
||||
value = (
|
||||
((challenge.minimum - challenge.initial) / (challenge.decay ** 2))
|
||||
* (solve_count ** 2)
|
||||
) + challenge.initial
|
||||
|
||||
value = math.ceil(value)
|
||||
|
||||
if value < challenge.minimum:
|
||||
value = challenge.minimum
|
||||
|
||||
challenge.value = value
|
||||
|
||||
db.session.commit()
|
||||
return challenge
|
||||
return DynamicValueChallenge.calculate_value(challenge)
|
||||
|
||||
@staticmethod
|
||||
def delete(challenge):
|
||||
|
@ -185,12 +194,10 @@ class DynamicValueChallenge(BaseChallenge):
|
|||
:param request: The request the user submitted
|
||||
:return:
|
||||
"""
|
||||
chal = DynamicChallenge.query.filter_by(id=challenge.id).first()
|
||||
challenge = DynamicChallenge.query.filter_by(id=challenge.id).first()
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
|
||||
Model = get_model()
|
||||
|
||||
solve = Solves(
|
||||
user_id=user.id,
|
||||
team_id=team.id if team else None,
|
||||
|
@ -199,35 +206,9 @@ class DynamicValueChallenge(BaseChallenge):
|
|||
provided=submission,
|
||||
)
|
||||
db.session.add(solve)
|
||||
|
||||
solve_count = (
|
||||
Solves.query.join(Model, Solves.account_id == Model.id)
|
||||
.filter(
|
||||
Solves.challenge_id == challenge.id,
|
||||
Model.hidden == False,
|
||||
Model.banned == False,
|
||||
)
|
||||
.count()
|
||||
)
|
||||
|
||||
# We subtract -1 to allow the first solver to get max point value
|
||||
solve_count -= 1
|
||||
|
||||
# It is important that this calculation takes into account floats.
|
||||
# Hence this file uses from __future__ import division
|
||||
value = (
|
||||
((chal.minimum - chal.initial) / (chal.decay ** 2)) * (solve_count ** 2)
|
||||
) + chal.initial
|
||||
|
||||
value = math.ceil(value)
|
||||
|
||||
if value < chal.minimum:
|
||||
value = chal.minimum
|
||||
|
||||
chal.value = value
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
DynamicValueChallenge.calculate_value(challenge)
|
||||
|
||||
@staticmethod
|
||||
def fail(user, team, challenge, request):
|
||||
|
|
|
@ -16,34 +16,13 @@
|
|||
<input type="text" class="form-control chal-category" name="category" value="{{ challenge.category }}">
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="desc-edit">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#desc-write" id="desc-write-link" aria-controls="home"
|
||||
role="tab" data-toggle="tab">
|
||||
Write
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#desc-preview" aria-controls="home" role="tab" data-toggle="tab">
|
||||
Preview
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="desc-write">
|
||||
<div class="form-group">
|
||||
<label for="message-text" class="control-label">Message<br>
|
||||
<small class="form-text text-muted">
|
||||
Use this to give a brief introduction to your challenge.
|
||||
</small>
|
||||
</label>
|
||||
<textarea id="desc-editor" class="form-control chal-desc-editor" name="description"
|
||||
rows="10">{{ challenge.description }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane content" id="desc-preview"
|
||||
style="height:214px; overflow-y: scroll;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message-text" class="control-label">Message<br>
|
||||
<small class="form-text text-muted">
|
||||
Use this to give a brief introduction to your challenge.
|
||||
</small>
|
||||
</label>
|
||||
<textarea id="desc-editor" class="form-control chal-desc-editor" name="description" rows="10">{{ challenge.description }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -98,7 +77,7 @@
|
|||
<small class="form-text text-muted">Changes the state of the challenge (e.g. visible, hidden)</small>
|
||||
</label>
|
||||
|
||||
<select class="form-control" name="state">
|
||||
<select class="form-control custom-select" name="state">
|
||||
<option value="visible" {% if challenge.state == "visible" %}selected{% endif %}>Visible</option>
|
||||
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
|
||||
</select>
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
$('#submit-key').click(function (e) {
|
||||
submitkey($('#chalid').val(), $('#answer').val())
|
||||
});
|
||||
|
||||
$('#submit-keys').click(function (e) {
|
||||
e.preventDefault();
|
||||
$('#update-keys').modal('hide');
|
||||
});
|
||||
|
||||
$('#limit_max_attempts').change(function() {
|
||||
if(this.checked) {
|
||||
$('#chal-attempts-group').show();
|
||||
} else {
|
||||
$('#chal-attempts-group').hide();
|
||||
$('#chal-attempts-input').val('');
|
||||
}
|
||||
});
|
||||
|
||||
// Markdown Preview
|
||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#desc-preview') {
|
||||
var editor_value = $('#desc-editor').val();
|
||||
$(event.target.hash).html(
|
||||
window.challenge.render(editor_value)
|
||||
);
|
||||
}
|
||||
});
|
||||
$('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||
if (event.target.hash == '#new-desc-preview') {
|
||||
var editor_value = $('#new-desc-editor').val();
|
||||
$(event.target.hash).html(
|
||||
window.challenge.render(editor_value)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function loadchal(id, update) {
|
||||
$.get(script_root + '/admin/chal/' + id, function(obj){
|
||||
$('#desc-write-link').click(); // Switch to Write tab
|
||||
if (typeof update === 'undefined')
|
||||
$('#update-challenge').modal();
|
||||
});
|
||||
}
|
||||
|
||||
function openchal(id){
|
||||
loadchal(id);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
|
@ -1,57 +1,40 @@
|
|||
window.challenge.data = undefined;
|
||||
CTFd._internal.challenge.data = undefined
|
||||
|
||||
window.challenge.renderer = new markdownit({
|
||||
html: true,
|
||||
linkify: true,
|
||||
});
|
||||
|
||||
window.challenge.preRender = function () {
|
||||
|
||||
};
|
||||
|
||||
window.challenge.render = function (markdown) {
|
||||
return window.challenge.renderer.render(markdown);
|
||||
};
|
||||
CTFd._internal.challenge.renderer = CTFd.lib.markdown();
|
||||
|
||||
|
||||
window.challenge.postRender = function () {
|
||||
CTFd._internal.challenge.preRender = function () { }
|
||||
|
||||
};
|
||||
CTFd._internal.challenge.render = function (markdown) {
|
||||
return CTFd._internal.challenge.renderer.render(markdown)
|
||||
}
|
||||
|
||||
|
||||
window.challenge.submit = function (cb, preview) {
|
||||
var challenge_id = parseInt($('#challenge-id').val());
|
||||
var submission = $('#submission-input').val();
|
||||
var url = "/api/v1/challenges/attempt";
|
||||
CTFd._internal.challenge.postRender = function () { }
|
||||
|
||||
|
||||
CTFd._internal.challenge.submit = function (preview) {
|
||||
var challenge_id = parseInt(CTFd.lib.$('#challenge-id').val())
|
||||
var submission = CTFd.lib.$('#submission-input').val()
|
||||
|
||||
var body = {
|
||||
'challenge_id': challenge_id,
|
||||
'submission': submission,
|
||||
}
|
||||
var params = {}
|
||||
if (preview) {
|
||||
url += "?preview=true";
|
||||
params['preview'] = true
|
||||
}
|
||||
|
||||
var params = {
|
||||
'challenge_id': challenge_id,
|
||||
'submission': submission
|
||||
};
|
||||
|
||||
CTFd.fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
}).then(function (response) {
|
||||
return CTFd.api.post_challenge_attempt(params, body).then(function (response) {
|
||||
if (response.status === 429) {
|
||||
// User was ratelimited but process response
|
||||
return response.json();
|
||||
return response
|
||||
}
|
||||
if (response.status === 403) {
|
||||
// User is not logged in or CTF is paused.
|
||||
return response.json();
|
||||
return response
|
||||
}
|
||||
return response.json();
|
||||
}).then(function (response) {
|
||||
cb(response);
|
||||
});
|
||||
return response
|
||||
})
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="data">
|
||||
<select class="form-control custom-select col-md-6" name="data">
|
||||
<option value="">Case Sensitive</option>
|
||||
<option value="case_insensitive">Case Insensitive</option>
|
||||
</select>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="data">
|
||||
<select class="form-control custom-select col-md-6" name="data">
|
||||
<option value="">Case Sensitive</option>
|
||||
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
|
||||
</select>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="data">
|
||||
<select class="form-control custom-select col-md-6" name="data">
|
||||
<option value="">Case Sensitive</option>
|
||||
<option value="case_insensitive">Case Insensitive</option>
|
||||
</select>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="data">
|
||||
<select class="form-control custom-select col-md-6" name="data">
|
||||
<option value="">Case Sensitive</option>
|
||||
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
|
||||
</select>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
from CTFd.models import ma, Tokens
|
||||
from CTFd.utils import string_types
|
||||
|
||||
|
||||
class TokenSchema(ma.ModelSchema):
|
||||
class Meta:
|
||||
model = Tokens
|
||||
include_fk = True
|
||||
dump_only = ("id", "expiration", "type")
|
||||
|
||||
views = {
|
||||
"admin": ["id", "type", "user_id", "created", "expiration", "value"],
|
||||
"user": ["id", "type", "created", "expiration"],
|
||||
}
|
||||
|
||||
def __init__(self, view=None, *args, **kwargs):
|
||||
if view:
|
||||
if isinstance(view, string_types):
|
||||
kwargs["only"] = self.views[view]
|
||||
elif isinstance(view, list):
|
||||
kwargs["only"] = view
|
||||
|
||||
super(TokenSchema, self).__init__(*args, **kwargs)
|
|
@ -2,14 +2,14 @@ from flask import render_template, request, redirect, url_for, Blueprint
|
|||
from CTFd.models import db, Teams
|
||||
from CTFd.utils.decorators import authed_only, ratelimit
|
||||
from CTFd.utils.decorators.modes import require_team_mode
|
||||
from CTFd.utils import config
|
||||
from CTFd.utils import config, get_config
|
||||
from CTFd.utils.user import get_current_user
|
||||
from CTFd.utils.crypto import verify_password
|
||||
from CTFd.utils.decorators.visibility import (
|
||||
check_account_visibility,
|
||||
check_score_visibility,
|
||||
)
|
||||
from CTFd.utils.helpers import get_errors
|
||||
from CTFd.utils.helpers import get_errors, get_infos
|
||||
|
||||
teams = Blueprint("teams", __name__)
|
||||
|
||||
|
@ -44,14 +44,35 @@ def listing():
|
|||
@require_team_mode
|
||||
@ratelimit(method="POST", limit=10, interval=5)
|
||||
def join():
|
||||
infos = get_infos()
|
||||
errors = get_errors()
|
||||
if request.method == "GET":
|
||||
return render_template("teams/join_team.html")
|
||||
team_size_limit = get_config("team_size", default=0)
|
||||
if team_size_limit:
|
||||
plural = "" if team_size_limit == 1 else "s"
|
||||
infos.append(
|
||||
"Teams are limited to {limit} member{plural}".format(
|
||||
limit=team_size_limit, plural=plural
|
||||
)
|
||||
)
|
||||
return render_template("teams/join_team.html", infos=infos, errors=errors)
|
||||
|
||||
if request.method == "POST":
|
||||
teamname = request.form.get("name")
|
||||
passphrase = request.form.get("password", "").strip()
|
||||
|
||||
team = Teams.query.filter_by(name=teamname).first()
|
||||
user = get_current_user()
|
||||
|
||||
team_size_limit = get_config("team_size", default=0)
|
||||
if team_size_limit and len(team.members) >= team_size_limit:
|
||||
errors.append(
|
||||
"{name} has already reached the team size limit of {limit}".format(
|
||||
name=team.name, limit=team_size_limit
|
||||
)
|
||||
)
|
||||
return render_template("teams/join_team.html", infos=infos, errors=errors)
|
||||
|
||||
if team and verify_password(passphrase, team.password):
|
||||
user.team_id = team.id
|
||||
db.session.commit()
|
||||
|
@ -62,16 +83,27 @@ def join():
|
|||
|
||||
return redirect(url_for("challenges.listing"))
|
||||
else:
|
||||
errors = ["That information is incorrect"]
|
||||
return render_template("teams/join_team.html", errors=errors)
|
||||
errors.append("That information is incorrect")
|
||||
return render_template("teams/join_team.html", infos=infos, errors=errors)
|
||||
|
||||
|
||||
@teams.route("/teams/new", methods=["GET", "POST"])
|
||||
@authed_only
|
||||
@require_team_mode
|
||||
def new():
|
||||
infos = get_infos()
|
||||
errors = get_errors()
|
||||
if request.method == "GET":
|
||||
return render_template("teams/new_team.html")
|
||||
team_size_limit = get_config("team_size", default=0)
|
||||
if team_size_limit:
|
||||
plural = "" if team_size_limit == 1 else "s"
|
||||
infos.append(
|
||||
"Teams are limited to {limit} member{plural}".format(
|
||||
limit=team_size_limit, plural=plural
|
||||
)
|
||||
)
|
||||
|
||||
return render_template("teams/new_team.html", infos=infos, errors=errors)
|
||||
elif request.method == "POST":
|
||||
teamname = request.form.get("name")
|
||||
passphrase = request.form.get("password", "").strip()
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
@import "includes/sticky-footer.css";
|
||||
|
||||
#score-graph {
|
||||
height: 450px;
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#solves-graph {
|
||||
display: block;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
#keys-pie-graph {
|
||||
height: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#categories-pie-graph {
|
||||
height: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#solve-percentages-graph {
|
||||
height: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-decoration {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.no-decoration:hover {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.table td,
|
||||
.table th {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
position: relative;
|
||||
display: block;
|
||||
/*padding: 0.8em;*/
|
||||
border-radius: 0;
|
||||
/*background: #f0f0f0;*/
|
||||
/*color: #aaa;*/
|
||||
font-weight: 400;
|
||||
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
tr[data-href] {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -55,4 +55,12 @@
|
|||
font-weight: 400;
|
||||
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-appearance: none;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
#challenge-window .form-control:focus {
|
||||
background-color: transparent;
|
||||
border-color: #a3d39c;
|
||||
box-shadow: 0 0 0 0.2rem #a3d39c;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* Sticky footer styles
|
||||
-------------------------------------------------- */
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 60px; /* Margin bottom by footer height */
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
|
||||
/* prevent scrollbars from showing on pages that don't use the full page height */
|
||||
bottom: 1px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
/* Set the fixed height of the footer here */
|
||||
height: 60px;
|
||||
|
||||
/* Override line-height from core because we have two lines in the admin panel */
|
||||
line-height: normal !important;
|
||||
|
||||
/* Avoid covering things */
|
||||
z-index: -20;
|
||||
|
||||
/*background-color: #f5f5f5;*/
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
import $ from "jquery";
|
||||
import { ezToast } from "core/ezq";
|
||||
import CTFd from "core/CTFd";
|
||||
import nunjucks from "nunjucks";
|
||||
|
||||
function renderSubmissionResponse(response, cb) {
|
||||
const result = response.data;
|
||||
|
||||
const result_message = $("#result-message");
|
||||
const result_notification = $("#result-notification");
|
||||
const answer_input = $("#submission-input");
|
||||
result_notification.removeClass();
|
||||
result_message.text(result.message);
|
||||
|
||||
if (result.status === "authentication_required") {
|
||||
window.location =
|
||||
CTFd.config.urlRoot +
|
||||
"/login?next=" +
|
||||
CTFd.config.urlRoot +
|
||||
window.location.pathname +
|
||||
window.location.hash;
|
||||
return;
|
||||
} else if (result.status === "incorrect") {
|
||||
// Incorrect key
|
||||
result_notification.addClass(
|
||||
"alert alert-danger alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.removeClass("correct");
|
||||
answer_input.addClass("wrong");
|
||||
setTimeout(function() {
|
||||
answer_input.removeClass("wrong");
|
||||
}, 3000);
|
||||
} else if (result.status === "correct") {
|
||||
// Challenge Solved
|
||||
result_notification.addClass(
|
||||
"alert alert-success alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
$(".challenge-solves").text(
|
||||
parseInt(
|
||||
$(".challenge-solves")
|
||||
.text()
|
||||
.split(" ")[0]
|
||||
) +
|
||||
1 +
|
||||
" Solves"
|
||||
);
|
||||
|
||||
answer_input.val("");
|
||||
answer_input.removeClass("wrong");
|
||||
answer_input.addClass("correct");
|
||||
} else if (result.status === "already_solved") {
|
||||
// Challenge already solved
|
||||
result_notification.addClass(
|
||||
"alert alert-info alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("correct");
|
||||
} else if (result.status === "paused") {
|
||||
// CTF is paused
|
||||
result_notification.addClass(
|
||||
"alert alert-warning alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
} else if (result.status === "ratelimited") {
|
||||
// Keys per minute too high
|
||||
result_notification.addClass(
|
||||
"alert alert-warning alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("too-fast");
|
||||
setTimeout(function() {
|
||||
answer_input.removeClass("too-fast");
|
||||
}, 3000);
|
||||
}
|
||||
setTimeout(function() {
|
||||
$(".alert").slideUp();
|
||||
$("#submit-key").removeClass("disabled-button");
|
||||
$("#submit-key").prop("disabled", false);
|
||||
}, 3000);
|
||||
|
||||
if (cb) {
|
||||
cb(result);
|
||||
}
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".preview-challenge").click(function(event) {
|
||||
window.challenge = new Object();
|
||||
$.get(CTFd.config.urlRoot + "/api/v1/challenges/" + CHALLENGE_ID, function(
|
||||
response
|
||||
) {
|
||||
const challenge_data = response.data;
|
||||
challenge_data["solves"] = null;
|
||||
|
||||
$.getScript(
|
||||
CTFd.config.urlRoot + challenge_data.type_data.scripts.view,
|
||||
function() {
|
||||
$.get(
|
||||
CTFd.config.urlRoot + challenge_data.type_data.templates.view,
|
||||
function(template_data) {
|
||||
$("#challenge-window").empty();
|
||||
const template = nunjucks.compile(template_data);
|
||||
window.challenge.data = challenge_data;
|
||||
window.challenge.preRender();
|
||||
|
||||
challenge_data["description"] = window.challenge.render(
|
||||
challenge_data["description"]
|
||||
);
|
||||
challenge_data["script_root"] = CTFd.config.urlRoot;
|
||||
|
||||
$("#challenge-window").append(template.render(challenge_data));
|
||||
|
||||
$(".challenge-solves").click(function(event) {
|
||||
getsolves($("#challenge-id").val());
|
||||
});
|
||||
$(".nav-tabs a").click(function(event) {
|
||||
event.preventDefault();
|
||||
$(this).tab("show");
|
||||
});
|
||||
|
||||
// Handle modal toggling
|
||||
$("#challenge-window").on("hide.bs.modal", function(event) {
|
||||
$("#submission-input").removeClass("wrong");
|
||||
$("#submission-input").removeClass("correct");
|
||||
$("#incorrect-key").slideUp();
|
||||
$("#correct-key").slideUp();
|
||||
$("#already-solved").slideUp();
|
||||
$("#too-fast").slideUp();
|
||||
});
|
||||
|
||||
$("#submit-key").click(function(event) {
|
||||
event.preventDefault();
|
||||
$("#submit-key").addClass("disabled-button");
|
||||
$("#submit-key").prop("disabled", true);
|
||||
window.challenge.submit(function(data) {
|
||||
renderSubmissionResponse(data);
|
||||
}, true);
|
||||
// Preview passed as true
|
||||
});
|
||||
|
||||
$("#submission-input").keyup(function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
$("#submit-key").click();
|
||||
}
|
||||
});
|
||||
|
||||
$(".input-field").bind({
|
||||
focus: function() {
|
||||
$(this)
|
||||
.parent()
|
||||
.addClass("input--filled");
|
||||
$label = $(this).siblings(".input-label");
|
||||
},
|
||||
blur: function() {
|
||||
if ($(this).val() === "") {
|
||||
$(this)
|
||||
.parent()
|
||||
.removeClass("input--filled");
|
||||
$label = $(this).siblings(".input-label");
|
||||
$label.removeClass("input--hide");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.challenge.postRender();
|
||||
window.location.replace(
|
||||
window.location.href.split("#")[0] + "#preview"
|
||||
);
|
||||
|
||||
$("#challenge-window").modal();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-challenge").click(function(event) {
|
||||
ezQuery({
|
||||
title: "Delete Challenge",
|
||||
body: "Are you sure you want to delete {0}".format(
|
||||
"<strong>" + htmlentities(CHALLENGE_NAME) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "DELETE"
|
||||
}).then(function(response) {
|
||||
if (response.success) {
|
||||
window.location = CTFd.config.urlRoot + "/admin/challenges";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#challenge-update-container > form").submit(function(event) {
|
||||
event.preventDefault();
|
||||
const params = $(event.target).serializeJSON(true);
|
||||
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
}).then(function(data) {
|
||||
if (data.success) {
|
||||
ezToast({
|
||||
title: "Success",
|
||||
body: "Your challenge has been updated!"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (window.location.hash) {
|
||||
let hash = window.location.hash.replace("<>[]'\"", "");
|
||||
$('nav a[href="' + hash + '"]').tab("show");
|
||||
}
|
||||
|
||||
$(".nav-tabs a").click(function(event) {
|
||||
$(this).tab("show");
|
||||
window.location.hash = this.hash;
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import { default as helpers } from "core/helpers";
|
||||
import { ezQuery } from "core/ezq";
|
||||
|
||||
export function addFile(event) {
|
||||
event.preventDefault();
|
||||
let form = event.target;
|
||||
let data = {
|
||||
challenge: CHALLENGE_ID,
|
||||
type: "challenge"
|
||||
};
|
||||
helpers.files.upload(form, data, function(response) {
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 700);
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteFile(event) {
|
||||
const file_id = $(this).attr("file-id");
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
ezQuery({
|
||||
title: "Delete Files",
|
||||
body: "Are you sure you want to delete this file?",
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/files/" + file_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import nunjucks from "nunjucks";
|
||||
import { ezQuery } from "core/ezq";
|
||||
|
||||
export function deleteFlag(event) {
|
||||
event.preventDefault();
|
||||
const flag_id = $(this).attr("flag-id");
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Flag",
|
||||
body: "Are you sure you want to delete this flag?",
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/flags/" + flag_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function addFlagModal(event) {
|
||||
$.get(CTFd.config.urlRoot + "/api/v1/flags/types", function(response) {
|
||||
const data = response.data;
|
||||
const flag_type_select = $("#flags-create-select");
|
||||
flag_type_select.empty();
|
||||
|
||||
let option = $("<option> -- </option>");
|
||||
flag_type_select.append(option);
|
||||
|
||||
for (const key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
option = $(
|
||||
"<option value='{0}'>{1}</option>".format(key, data[key].name)
|
||||
);
|
||||
flag_type_select.append(option);
|
||||
}
|
||||
}
|
||||
$("#flag-edit-modal").modal();
|
||||
});
|
||||
|
||||
$("#flag-edit-modal form").submit(function(event) {
|
||||
event.preventDefault();
|
||||
const params = $(this).serializeJSON(true);
|
||||
params["challenge"] = CHALLENGE_ID;
|
||||
CTFd.fetch("/api/v1/flags", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
$("#flag-edit-modal").modal();
|
||||
}
|
||||
|
||||
export function editFlagModal(event) {
|
||||
event.preventDefault();
|
||||
const flag_id = $(this).attr("flag-id");
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
$.get(CTFd.config.urlRoot + "/api/v1/flags/" + flag_id, function(response) {
|
||||
const data = response.data;
|
||||
$.get(CTFd.config.urlRoot + data.templates.update, function(template_data) {
|
||||
$("#edit-flags form").empty();
|
||||
$("#edit-flags form").off();
|
||||
|
||||
const template = nunjucks.compile(template_data);
|
||||
$("#edit-flags form").append(template.render(data));
|
||||
|
||||
$("#edit-flags form").submit(function(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#edit-flags form").serializeJSON();
|
||||
|
||||
CTFd.fetch("/api/v1/flags/" + flag_id, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$(row)
|
||||
.find(".flag-content")
|
||||
.text(response.data.content);
|
||||
$("#edit-flags").modal("toggle");
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#edit-flags").modal();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function flagTypeSelect(event) {
|
||||
event.preventDefault();
|
||||
const flag_type_name = $(this)
|
||||
.find("option:selected")
|
||||
.text();
|
||||
|
||||
$.get(CTFd.config.urlRoot + "/api/v1/flags/types/" + flag_type_name, function(
|
||||
response
|
||||
) {
|
||||
const data = response.data;
|
||||
$.get(CTFd.config.urlRoot + data.templates.create, function(template_data) {
|
||||
const template = nunjucks.compile(template_data);
|
||||
$("#create-keys-entry-div").html(template.render());
|
||||
$("#create-keys-button-div").show();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import { ezQuery, ezAlert } from "core/ezq";
|
||||
|
||||
function hint(id) {
|
||||
return CTFd.fetch("/api/v1/hints/" + id + "?preview=true", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadhint(hintid) {
|
||||
const md = CTFd.lib.markdown();
|
||||
|
||||
hint(hintid).then(function(response) {
|
||||
if (response.data.content) {
|
||||
ezAlert({
|
||||
title: "Hint",
|
||||
body: md.render(response.data.content),
|
||||
button: "Got it!"
|
||||
});
|
||||
} else {
|
||||
ezAlert({
|
||||
title: "Error",
|
||||
body: "Error loading hint!",
|
||||
button: "OK"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function showHintModal(event) {
|
||||
event.preventDefault();
|
||||
$("#hint-edit-modal form")
|
||||
.find("input, textarea")
|
||||
.val("");
|
||||
|
||||
// Markdown Preview
|
||||
$("#new-hint-edit").on("shown.bs.tab", function(event) {
|
||||
if (event.target.hash == "#hint-preview") {
|
||||
const renderer = CTFd.lib.markdown();
|
||||
const editor_value = $("#hint-write textarea").val();
|
||||
$(event.target.hash).html(renderer.render(editor_value));
|
||||
}
|
||||
});
|
||||
|
||||
$("#hint-edit-modal").modal();
|
||||
}
|
||||
|
||||
export function showEditHintModal(event) {
|
||||
event.preventDefault();
|
||||
const hint_id = $(this).attr("hint-id");
|
||||
|
||||
CTFd.fetch("/api/v1/hints/" + hint_id + "?preview=true", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$("#hint-edit-form input[name=content],textarea[name=content]").val(
|
||||
response.data.content
|
||||
);
|
||||
$("#hint-edit-form input[name=cost]").val(response.data.cost);
|
||||
$("#hint-edit-form input[name=id]").val(response.data.id);
|
||||
|
||||
// Markdown Preview
|
||||
$("#new-hint-edit").on("shown.bs.tab", function(event) {
|
||||
if (event.target.hash == "#hint-preview") {
|
||||
const renderer = CTFd.lib.markdown();
|
||||
const editor_value = $("#hint-write textarea").val();
|
||||
$(event.target.hash).html(renderer.render(editor_value));
|
||||
}
|
||||
});
|
||||
|
||||
$("#hint-edit-modal").modal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteHint(event) {
|
||||
event.preventDefault();
|
||||
const hint_id = $(this).attr("hint-id");
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
ezQuery({
|
||||
title: "Delete Hint",
|
||||
body: "Are you sure you want to delete this hint?",
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/hints/" + hint_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function editHint(event) {
|
||||
event.preventDefault();
|
||||
const params = $(this).serializeJSON(true);
|
||||
params["challenge"] = CHALLENGE_ID;
|
||||
|
||||
const method = "POST";
|
||||
const url = "/api/v1/hints";
|
||||
if (params.id) {
|
||||
method = "PATCH";
|
||||
url = "/api/v1/hints/" + params.id;
|
||||
}
|
||||
CTFd.fetch(url, {
|
||||
method: method,
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
// TODO: Refresh hints on submit.
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
|
||||
window.challenge = new Object();
|
||||
|
||||
function loadChalTemplate(challenge) {
|
||||
$.getScript(CTFd.config.urlRoot + challenge.scripts.view, function() {
|
||||
$.get(CTFd.config.urlRoot + challenge.templates.create, function(
|
||||
template_data
|
||||
) {
|
||||
const template = nunjucks.compile(template_data);
|
||||
$("#create-chal-entry-div").html(
|
||||
template.render({
|
||||
nonce: CTFd.config.csrfNonce,
|
||||
script_root: CTFd.config.urlRoot
|
||||
})
|
||||
);
|
||||
|
||||
$.getScript(CTFd.config.urlRoot + challenge.scripts.create, function() {
|
||||
$("#create-chal-entry-div form").submit(function(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#create-chal-entry-div form").serializeJSON();
|
||||
CTFd.fetch("/api/v1/challenges", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
}).then(function(response) {
|
||||
if (response.success) {
|
||||
window.location =
|
||||
CTFd.config.urlRoot + "/admin/challenges/" + response.data.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$.get(CTFd.config.urlRoot + "/api/v1/challenges/types", function(response) {
|
||||
$("#create-chals-select").empty();
|
||||
const data = response.data;
|
||||
const chal_type_amt = Object.keys(data).length;
|
||||
if (chal_type_amt > 1) {
|
||||
const option = "<option> -- </option>";
|
||||
$("#create-chals-select").append(option);
|
||||
for (const key in data) {
|
||||
const challenge = data[key];
|
||||
const option = $("<option/>");
|
||||
option.attr("value", challenge.type);
|
||||
option.text(challenge.name);
|
||||
option.data("meta", challenge);
|
||||
$("#create-chals-select").append(option);
|
||||
}
|
||||
$("#create-chals-select-div").show();
|
||||
} else if (chal_type_amt == 1) {
|
||||
const key = Object.keys(data)[0];
|
||||
$("#create-chals-select").empty();
|
||||
loadChalTemplate(data[key]);
|
||||
}
|
||||
});
|
||||
|
||||
function createChallenge(event) {
|
||||
const challenge = $(this)
|
||||
.find("option:selected")
|
||||
.data("meta");
|
||||
loadChalTemplate(challenge);
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$("#create-chals-select").change(createChallenge);
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
|
||||
export function addRequirement(event) {
|
||||
event.preventDefault();
|
||||
const requirements = $("#prerequisite-add-form").serializeJSON();
|
||||
|
||||
// Shortcut if there's no prerequisite
|
||||
if (!requirements["prerequisite"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
CHALLENGE_REQUIREMENTS.prerequisites.push(
|
||||
parseInt(requirements["prerequisite"])
|
||||
);
|
||||
|
||||
const params = {
|
||||
requirements: CHALLENGE_REQUIREMENTS
|
||||
};
|
||||
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
// TODO: Make this refresh requirements
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteRequirement(event) {
|
||||
const challenge_id = $(this).attr("challenge-id");
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
CHALLENGE_REQUIREMENTS.prerequisites.pop(challenge_id);
|
||||
|
||||
const params = {
|
||||
requirements: CHALLENGE_REQUIREMENTS
|
||||
};
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
|
||||
export function deleteTag(event) {
|
||||
const $elem = $(this);
|
||||
const tag_id = $elem.attr("tag-id");
|
||||
|
||||
CTFd.api.delete_tag({ tagId: tag_id }).then(response => {
|
||||
if (response.success) {
|
||||
$elem.parent().remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function addTag(event) {
|
||||
if (event.keyCode != 13) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $elem = $(this);
|
||||
|
||||
const tag = $elem.val();
|
||||
const params = {
|
||||
value: tag,
|
||||
challenge: CHALLENGE_ID
|
||||
};
|
||||
|
||||
CTFd.api.post_tag_list({}, params).then(response => {
|
||||
if (response.success) {
|
||||
const tpl =
|
||||
"<span class='badge badge-primary mx-1 challenge-tag'>" +
|
||||
"<span>{0}</span>" +
|
||||
"<a class='btn-fa delete-tag' tag-id='{1}'>×</a></span>";
|
||||
const tag = $(tpl.format(response.data.value, response.data.id));
|
||||
$("#challenge-tags").append(tag);
|
||||
// TODO: tag deletion not working on freshly created tags
|
||||
tag.click(deleteTag);
|
||||
}
|
||||
});
|
||||
|
||||
$elem.val("");
|
||||
}
|
|
@ -0,0 +1,490 @@
|
|||
import "./main";
|
||||
import "core/utils";
|
||||
import $ from "jquery";
|
||||
import "bootstrap/js/dist/tab";
|
||||
import CTFd from "core/CTFd";
|
||||
import { htmlEntities } from "core/utils";
|
||||
import { ezQuery, ezAlert, ezToast } from "core/ezq";
|
||||
import nunjucks from "nunjucks";
|
||||
import { default as helpers } from "core/helpers";
|
||||
import { addFile, deleteFile } from "../challenges/files";
|
||||
import { addTag, deleteTag } from "../challenges/tags";
|
||||
import { addRequirement, deleteRequirement } from "../challenges/requirements";
|
||||
import {
|
||||
showHintModal,
|
||||
editHint,
|
||||
deleteHint,
|
||||
showEditHintModal
|
||||
} from "../challenges/hints";
|
||||
import {
|
||||
addFlagModal,
|
||||
editFlagModal,
|
||||
deleteFlag,
|
||||
flagTypeSelect
|
||||
} from "../challenges/flags";
|
||||
|
||||
const md = CTFd.lib.markdown();
|
||||
|
||||
const displayHint = data => {
|
||||
ezAlert({
|
||||
title: "Hint",
|
||||
body: md.render(data.content),
|
||||
button: "Got it!"
|
||||
});
|
||||
};
|
||||
|
||||
const loadHint = id => {
|
||||
CTFd.api.get_hint({ hintId: id, preview: true }).then(response => {
|
||||
if (response.data.content) {
|
||||
displayHint(response.data);
|
||||
return;
|
||||
}
|
||||
displayUnlock(id);
|
||||
});
|
||||
};
|
||||
|
||||
function renderSubmissionResponse(response, cb) {
|
||||
var result = response.data;
|
||||
|
||||
var result_message = $("#result-message");
|
||||
var result_notification = $("#result-notification");
|
||||
var answer_input = $("#submission-input");
|
||||
result_notification.removeClass();
|
||||
result_message.text(result.message);
|
||||
|
||||
if (result.status === "authentication_required") {
|
||||
window.location =
|
||||
CTFd.config.urlRoot +
|
||||
"/login?next=" +
|
||||
CTFd.config.urlRoot +
|
||||
window.location.pathname +
|
||||
window.location.hash;
|
||||
return;
|
||||
} else if (result.status === "incorrect") {
|
||||
// Incorrect key
|
||||
result_notification.addClass(
|
||||
"alert alert-danger alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.removeClass("correct");
|
||||
answer_input.addClass("wrong");
|
||||
setTimeout(function() {
|
||||
answer_input.removeClass("wrong");
|
||||
}, 3000);
|
||||
} else if (result.status === "correct") {
|
||||
// Challenge Solved
|
||||
result_notification.addClass(
|
||||
"alert alert-success alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
$(".challenge-solves").text(
|
||||
parseInt(
|
||||
$(".challenge-solves")
|
||||
.text()
|
||||
.split(" ")[0]
|
||||
) +
|
||||
1 +
|
||||
" Solves"
|
||||
);
|
||||
|
||||
answer_input.val("");
|
||||
answer_input.removeClass("wrong");
|
||||
answer_input.addClass("correct");
|
||||
} else if (result.status === "already_solved") {
|
||||
// Challenge already solved
|
||||
result_notification.addClass(
|
||||
"alert alert-info alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("correct");
|
||||
} else if (result.status === "paused") {
|
||||
// CTF is paused
|
||||
result_notification.addClass(
|
||||
"alert alert-warning alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
} else if (result.status === "ratelimited") {
|
||||
// Keys per minute too high
|
||||
result_notification.addClass(
|
||||
"alert alert-warning alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("too-fast");
|
||||
setTimeout(function() {
|
||||
answer_input.removeClass("too-fast");
|
||||
}, 3000);
|
||||
}
|
||||
setTimeout(function() {
|
||||
$(".alert").slideUp();
|
||||
$("#submit-key").removeClass("disabled-button");
|
||||
$("#submit-key").prop("disabled", false);
|
||||
}, 3000);
|
||||
|
||||
if (cb) {
|
||||
cb(result);
|
||||
}
|
||||
}
|
||||
|
||||
function loadChalTemplate(challenge) {
|
||||
CTFd._internal.challenge = {};
|
||||
$.getScript(CTFd.config.urlRoot + challenge.scripts.view, function() {
|
||||
$.get(CTFd.config.urlRoot + challenge.templates.create, function(
|
||||
template_data
|
||||
) {
|
||||
const template = nunjucks.compile(template_data);
|
||||
$("#create-chal-entry-div").html(
|
||||
template.render({
|
||||
nonce: CTFd.config.csrfNonce,
|
||||
script_root: CTFd.config.urlRoot
|
||||
})
|
||||
);
|
||||
|
||||
$.getScript(CTFd.config.urlRoot + challenge.scripts.create, function() {
|
||||
$("#create-chal-entry-div form").submit(function(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#create-chal-entry-div form").serializeJSON();
|
||||
CTFd.fetch("/api/v1/challenges", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$("#challenge-create-options #challenge_id").val(
|
||||
response.data.id
|
||||
);
|
||||
$("#challenge-create-options").modal();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleChallengeOptions(event) {
|
||||
event.preventDefault();
|
||||
var params = $(event.target).serializeJSON(true);
|
||||
let flag_params = {
|
||||
challenge_id: params.challenge_id,
|
||||
content: params.flag || "",
|
||||
type: params.flag_type,
|
||||
data: params.flag_data ? params.flag_data : ""
|
||||
};
|
||||
// Define a save_challenge function
|
||||
let save_challenge = function() {
|
||||
CTFd.fetch("/api/v1/challenges/" + params.challenge_id, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
state: params.state
|
||||
})
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
setTimeout(function() {
|
||||
window.location =
|
||||
CTFd.config.urlRoot + "/admin/challenges/" + params.challenge_id;
|
||||
}, 700);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
// Save flag
|
||||
new Promise(function(resolve, reject) {
|
||||
if (flag_params.content.length == 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
CTFd.fetch("/api/v1/flags", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(flag_params)
|
||||
}).then(function(response) {
|
||||
resolve(response.json());
|
||||
});
|
||||
}),
|
||||
// Upload files
|
||||
new Promise(function(resolve, reject) {
|
||||
let form = event.target;
|
||||
let data = {
|
||||
challenge: params.challenge_id,
|
||||
type: "challenge"
|
||||
};
|
||||
let filepath = $(form.elements["file"]).val();
|
||||
if (filepath) {
|
||||
helpers.files.upload(form, data);
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
]).then(responses => {
|
||||
save_challenge();
|
||||
});
|
||||
}
|
||||
|
||||
function createChallenge(event) {
|
||||
const challenge = $(this)
|
||||
.find("option:selected")
|
||||
.data("meta");
|
||||
if (challenge === undefined) {
|
||||
$("#create-chal-entry-div").empty();
|
||||
return;
|
||||
}
|
||||
loadChalTemplate(challenge);
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".preview-challenge").click(function(e) {
|
||||
window.challenge = new Object();
|
||||
CTFd._internal.challenge = {};
|
||||
$.get(CTFd.config.urlRoot + "/api/v1/challenges/" + CHALLENGE_ID, function(
|
||||
response
|
||||
) {
|
||||
const challenge = CTFd._internal.challenge;
|
||||
var challenge_data = response.data;
|
||||
challenge_data["solves"] = null;
|
||||
|
||||
$.getScript(
|
||||
CTFd.config.urlRoot + challenge_data.type_data.scripts.view,
|
||||
function() {
|
||||
$.get(
|
||||
CTFd.config.urlRoot + challenge_data.type_data.templates.view,
|
||||
function(template_data) {
|
||||
$("#challenge-window").empty();
|
||||
var template = nunjucks.compile(template_data);
|
||||
// window.challenge.data = challenge_data;
|
||||
// window.challenge.preRender();
|
||||
challenge.data = challenge_data;
|
||||
challenge.preRender();
|
||||
|
||||
challenge_data["description"] = challenge.render(
|
||||
challenge_data["description"]
|
||||
);
|
||||
challenge_data["script_root"] = CTFd.config.urlRoot;
|
||||
|
||||
$("#challenge-window").append(template.render(challenge_data));
|
||||
|
||||
$(".challenge-solves").click(function(e) {
|
||||
getsolves($("#challenge-id").val());
|
||||
});
|
||||
$(".nav-tabs a").click(function(e) {
|
||||
e.preventDefault();
|
||||
$(this).tab("show");
|
||||
});
|
||||
|
||||
// Handle modal toggling
|
||||
$("#challenge-window").on("hide.bs.modal", function(event) {
|
||||
$("#submission-input").removeClass("wrong");
|
||||
$("#submission-input").removeClass("correct");
|
||||
$("#incorrect-key").slideUp();
|
||||
$("#correct-key").slideUp();
|
||||
$("#already-solved").slideUp();
|
||||
$("#too-fast").slideUp();
|
||||
});
|
||||
|
||||
$(".load-hint").on("click", function(event) {
|
||||
loadHint($(this).data("hint-id"));
|
||||
});
|
||||
|
||||
$("#submit-key").click(function(e) {
|
||||
e.preventDefault();
|
||||
$("#submit-key").addClass("disabled-button");
|
||||
$("#submit-key").prop("disabled", true);
|
||||
CTFd._internal.challenge
|
||||
.submit(true)
|
||||
.then(renderSubmissionResponse);
|
||||
// Preview passed as true
|
||||
});
|
||||
|
||||
$("#submission-input").keyup(function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
$("#submit-key").click();
|
||||
}
|
||||
});
|
||||
|
||||
$(".input-field").bind({
|
||||
focus: function() {
|
||||
$(this)
|
||||
.parent()
|
||||
.addClass("input--filled");
|
||||
$label = $(this).siblings(".input-label");
|
||||
},
|
||||
blur: function() {
|
||||
if ($(this).val() === "") {
|
||||
$(this)
|
||||
.parent()
|
||||
.removeClass("input--filled");
|
||||
$label = $(this).siblings(".input-label");
|
||||
$label.removeClass("input--hide");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
challenge.postRender();
|
||||
window.location.replace(
|
||||
window.location.href.split("#")[0] + "#preview"
|
||||
);
|
||||
|
||||
$("#challenge-window").modal();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-challenge").click(function(e) {
|
||||
ezQuery({
|
||||
title: "Delete Challenge",
|
||||
body: "Are you sure you want to delete {0}".format(
|
||||
"<strong>" + htmlEntities(CHALLENGE_NAME) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location = CTFd.config.urlRoot + "/admin/challenges";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#challenge-update-container > form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var params = $(e.target).serializeJSON(true);
|
||||
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID + "/flags", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
let update_challenge = function() {
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
ezToast({
|
||||
title: "Success",
|
||||
body: "Your challenge has been updated!"
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// Check if the challenge doesn't have any flags before marking visible
|
||||
if (response.data.length === 0 && params.state === "visible") {
|
||||
ezQuery({
|
||||
title: "Missing Flags",
|
||||
body:
|
||||
"This challenge does not have any flags meaning it is unsolveable. Are you sure you'd like to update this challenge?",
|
||||
success: update_challenge
|
||||
});
|
||||
} else {
|
||||
update_challenge();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#challenge-create-options form").submit(handleChallengeOptions);
|
||||
|
||||
$(".nav-tabs a").click(function(e) {
|
||||
$(this).tab("show");
|
||||
window.location.hash = this.hash;
|
||||
});
|
||||
|
||||
if (window.location.hash) {
|
||||
let hash = window.location.hash.replace("<>[]'\"", "");
|
||||
$('nav a[href="' + hash + '"]').tab("show");
|
||||
}
|
||||
|
||||
$("#tags-add-input").keyup(addTag);
|
||||
$(".delete-tag").click(deleteTag);
|
||||
|
||||
$("#prerequisite-add-form").submit(addRequirement);
|
||||
$(".delete-requirement").click(deleteRequirement);
|
||||
|
||||
$("#file-add-form").submit(addFile);
|
||||
$(".delete-file").click(deleteFile);
|
||||
|
||||
$("#hint-add-button").click(showHintModal);
|
||||
$(".delete-hint").click(deleteHint);
|
||||
$(".edit-hint").click(showEditHintModal);
|
||||
$("#hint-edit-form").submit(editHint);
|
||||
|
||||
$("#flag-add-button").click(addFlagModal);
|
||||
$(".delete-flag").click(deleteFlag);
|
||||
$("#flags-create-select").change(flagTypeSelect);
|
||||
$(".edit-flag").click(editFlagModal);
|
||||
|
||||
$.get(CTFd.config.urlRoot + "/api/v1/challenges/types", function(response) {
|
||||
$("#create-chals-select").empty();
|
||||
const data = response.data;
|
||||
const chal_type_amt = Object.keys(data).length;
|
||||
if (chal_type_amt > 1) {
|
||||
const option = "<option> -- </option>";
|
||||
$("#create-chals-select").append(option);
|
||||
for (const key in data) {
|
||||
const challenge = data[key];
|
||||
const option = $("<option/>");
|
||||
option.attr("value", challenge.type);
|
||||
option.text(challenge.name);
|
||||
option.data("meta", challenge);
|
||||
$("#create-chals-select").append(option);
|
||||
}
|
||||
$("#create-chals-select-div").show();
|
||||
$("#create-chals-select").val("standard");
|
||||
loadChalTemplate(data["standard"]);
|
||||
} else if (chal_type_amt == 1) {
|
||||
const key = Object.keys(data)[0];
|
||||
$("#create-chals-select").empty();
|
||||
loadChalTemplate(data[key]);
|
||||
}
|
||||
});
|
||||
|
||||
$("#create-chals-select").change(createChallenge);
|
||||
});
|
|
@ -0,0 +1,298 @@
|
|||
import "./main";
|
||||
import "core/utils";
|
||||
import "bootstrap/js/dist/tab";
|
||||
import Moment from "moment-timezone";
|
||||
import moment from "moment-timezone";
|
||||
import CTFd from "core/CTFd";
|
||||
import { default as helpers } from "core/helpers";
|
||||
import $ from "jquery";
|
||||
import { ezQuery, ezProgressBar } from "core/ezq";
|
||||
|
||||
function loadTimestamp(place, timestamp) {
|
||||
if (typeof timestamp == "string") {
|
||||
timestamp = parseInt(timestamp, 10);
|
||||
}
|
||||
const m = Moment(timestamp * 1000);
|
||||
$("#" + place + "-month").val(m.month() + 1); // Months are zero indexed (http://momentjs.com/docs/#/get-set/month/)
|
||||
$("#" + place + "-day").val(m.date());
|
||||
$("#" + place + "-year").val(m.year());
|
||||
$("#" + place + "-hour").val(m.hour());
|
||||
$("#" + place + "-minute").val(m.minute());
|
||||
loadDateValues(place);
|
||||
}
|
||||
|
||||
function loadDateValues(place) {
|
||||
const month = $("#" + place + "-month").val();
|
||||
const day = $("#" + place + "-day").val();
|
||||
const year = $("#" + place + "-year").val();
|
||||
const hour = $("#" + place + "-hour").val();
|
||||
const minute = $("#" + place + "-minute").val();
|
||||
const timezone = $("#" + place + "-timezone").val();
|
||||
|
||||
const utc = convertDateToMoment(month, day, year, hour, minute);
|
||||
if (isNaN(utc.unix())) {
|
||||
$("#" + place).val("");
|
||||
$("#" + place + "-local").val("");
|
||||
$("#" + place + "-zonetime").val("");
|
||||
} else {
|
||||
$("#" + place).val(utc.unix());
|
||||
$("#" + place + "-local").val(
|
||||
utc.local().format("dddd, MMMM Do YYYY, h:mm:ss a zz")
|
||||
);
|
||||
$("#" + place + "-zonetime").val(
|
||||
utc.tz(timezone).format("dddd, MMMM Do YYYY, h:mm:ss a zz")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function convertDateToMoment(month, day, year, hour, minute) {
|
||||
let month_num = month.toString();
|
||||
if (month_num.length == 1) {
|
||||
month_num = "0" + month_num;
|
||||
}
|
||||
|
||||
let day_str = day.toString();
|
||||
if (day_str.length == 1) {
|
||||
day_str = "0" + day_str;
|
||||
}
|
||||
|
||||
let hour_str = hour.toString();
|
||||
if (hour_str.length == 1) {
|
||||
hour_str = "0" + hour_str;
|
||||
}
|
||||
|
||||
let min_str = minute.toString();
|
||||
if (min_str.length == 1) {
|
||||
min_str = "0" + min_str;
|
||||
}
|
||||
|
||||
// 2013-02-08 24:00
|
||||
const date_string =
|
||||
year.toString() +
|
||||
"-" +
|
||||
month_num +
|
||||
"-" +
|
||||
day_str +
|
||||
" " +
|
||||
hour_str +
|
||||
":" +
|
||||
min_str +
|
||||
":00";
|
||||
return Moment(date_string, Moment.ISO_8601);
|
||||
}
|
||||
|
||||
function updateConfigs(event) {
|
||||
event.preventDefault();
|
||||
const obj = $(this).serializeJSON();
|
||||
const params = {};
|
||||
|
||||
if (obj.mail_useauth === false) {
|
||||
obj.mail_username = null;
|
||||
obj.mail_password = null;
|
||||
} else {
|
||||
if (obj.mail_username === "") {
|
||||
delete obj.mail_username;
|
||||
}
|
||||
if (obj.mail_password === "") {
|
||||
delete obj.mail_password;
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(obj).forEach(function(x) {
|
||||
if (obj[x] === "true") {
|
||||
params[x] = true;
|
||||
} else if (obj[x] === "false") {
|
||||
params[x] = false;
|
||||
} else {
|
||||
params[x] = obj[x];
|
||||
}
|
||||
});
|
||||
|
||||
CTFd.api.patch_config_list({}, params).then(response => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function uploadLogo(event) {
|
||||
event.preventDefault();
|
||||
let form = event.target;
|
||||
helpers.files.upload(form, {}, function(response) {
|
||||
const f = response.data[0];
|
||||
const params = {
|
||||
value: f.location
|
||||
};
|
||||
CTFd.fetch("/api/v1/configs/ctf_logo", {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
ezAlert({
|
||||
title: "Error!",
|
||||
body: "Logo uploading failed!",
|
||||
button: "Okay"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeLogo() {
|
||||
ezQuery({
|
||||
title: "Remove logo",
|
||||
body: "Are you sure you'd like to remove the CTF logo?",
|
||||
success: function() {
|
||||
const params = {
|
||||
value: null
|
||||
};
|
||||
CTFd.api
|
||||
.patch_config({ configKey: "ctf_logo" }, params)
|
||||
.then(response => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importConfig(event) {
|
||||
event.preventDefault();
|
||||
let import_file = document.getElementById("import-file").files[0];
|
||||
|
||||
let form_data = new FormData();
|
||||
form_data.append("backup", import_file);
|
||||
form_data.append("nonce", CTFd.config.csrfNonce);
|
||||
|
||||
let pg = ezProgressBar({
|
||||
width: 0,
|
||||
title: "Upload Progress"
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: CTFd.config.urlRoot + "/admin/import",
|
||||
type: "POST",
|
||||
data: form_data,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
statusCode: {
|
||||
500: function(resp) {
|
||||
console.log(resp.responseText);
|
||||
alert(resp.responseText);
|
||||
}
|
||||
},
|
||||
xhr: function() {
|
||||
let xhr = $.ajaxSettings.xhr();
|
||||
xhr.upload.onprogress = function(e) {
|
||||
if (e.lengthComputable) {
|
||||
let width = (e.loaded / e.total) * 100;
|
||||
pg = ezProgressBar({
|
||||
target: pg,
|
||||
width: width
|
||||
});
|
||||
}
|
||||
};
|
||||
return xhr;
|
||||
},
|
||||
success: function(data) {
|
||||
pg = ezProgressBar({
|
||||
target: pg,
|
||||
width: 100
|
||||
});
|
||||
setTimeout(function() {
|
||||
pg.modal("hide");
|
||||
}, 500);
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 700);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportConfig(event) {
|
||||
event.preventDefault();
|
||||
const href = CTFd.config.urlRoot + "/admin/export";
|
||||
window.location.href = $(this).attr("href");
|
||||
}
|
||||
|
||||
function showTab(event) {
|
||||
window.location.hash = this.hash;
|
||||
}
|
||||
|
||||
function insertTimezones(target) {
|
||||
let current = $("<option>").text(moment.tz.guess());
|
||||
$(target).append(current);
|
||||
let tz_names = moment.tz.names();
|
||||
for (let i = 0; i < tz_names.length; i++) {
|
||||
let tz = $("<option>").text(tz_names[i]);
|
||||
$(target).append(tz);
|
||||
}
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".config-section > form:not(.form-upload)").submit(updateConfigs);
|
||||
$("#logo-upload").submit(uploadLogo);
|
||||
$("#remove-logo").click(removeLogo);
|
||||
$("#export-button").click(exportConfig);
|
||||
$("#import-button").click(importConfig);
|
||||
$(".nav-pills a").click(showTab);
|
||||
$("#config-color-update").click(function() {
|
||||
const hex_code = $("#config-color-picker").val();
|
||||
const user_css = $("#css-editor").val();
|
||||
let new_css;
|
||||
if (user_css.lenth) {
|
||||
let css_vars = `theme-color: ${hex_code};`;
|
||||
new_css = user_css.replace(/theme-color: (.*);/, css_vars);
|
||||
} else {
|
||||
new_css =
|
||||
`:root {--theme-color: ${hex_code};}\n` +
|
||||
`.navbar{background-color: var(--theme-color) !important;}\n` +
|
||||
`.jumbotron{background-color: var(--theme-color) !important;}\n`;
|
||||
}
|
||||
$("#css-editor").val(new_css);
|
||||
});
|
||||
|
||||
$(".start-date").change(function() {
|
||||
loadDateValues("start");
|
||||
});
|
||||
$(".end-date").change(function() {
|
||||
loadDateValues("end");
|
||||
});
|
||||
$(".freeze-date").change(function() {
|
||||
loadDateValues("freeze");
|
||||
});
|
||||
|
||||
let hash = window.location.hash;
|
||||
if (hash) {
|
||||
hash = hash.replace("<>[]'\"", "");
|
||||
$('ul.nav a[href="' + hash + '"]').tab("show");
|
||||
}
|
||||
|
||||
const start = $("#start").val();
|
||||
const end = $("#end").val();
|
||||
const freeze = $("#freeze").val();
|
||||
|
||||
if (start) {
|
||||
loadTimestamp("start", start);
|
||||
}
|
||||
if (end) {
|
||||
loadTimestamp("end", end);
|
||||
}
|
||||
if (freeze) {
|
||||
loadTimestamp("freeze", freeze);
|
||||
}
|
||||
|
||||
// Toggle username and password based on stored value
|
||||
$("#mail_useauth")
|
||||
.change(function() {
|
||||
$("#mail_username_password").toggle(this.checked);
|
||||
})
|
||||
.change();
|
||||
|
||||
insertTimezones($("#start-timezone"));
|
||||
insertTimezones($("#end-timezone"));
|
||||
insertTimezones($("#freeze-timezone"));
|
||||
});
|
|
@ -1,12 +1,68 @@
|
|||
var editor = CodeMirror.fromTextArea(
|
||||
document.getElementById("admin-pages-editor"),
|
||||
{
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: "xml",
|
||||
htmlMode: true
|
||||
}
|
||||
);
|
||||
import "./main";
|
||||
import "core/utils";
|
||||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import { default as helpers } from "core/helpers";
|
||||
import CodeMirror from "codemirror";
|
||||
import { ezQuery, ezToast } from "core/ezq";
|
||||
|
||||
function get_filetype_icon_class(filename) {
|
||||
var mapping = {
|
||||
// Image Files
|
||||
png: "fa-file-image",
|
||||
jpg: "fa-file-image",
|
||||
jpeg: "fa-file-image",
|
||||
gif: "fa-file-image",
|
||||
bmp: "fa-file-image",
|
||||
svg: "fa-file-image",
|
||||
|
||||
// Text Files
|
||||
txt: "fa-file-alt",
|
||||
|
||||
// Video Files
|
||||
mov: "fa-file-video",
|
||||
mp4: "fa-file-video",
|
||||
wmv: "fa-file-video",
|
||||
flv: "fa-file-video",
|
||||
mkv: "fa-file-video",
|
||||
avi: "fa-file-video",
|
||||
|
||||
// PDF Files
|
||||
pdf: "fa-file-pdf",
|
||||
|
||||
// Audio Files
|
||||
mp3: "fa-file-sound",
|
||||
wav: "fa-file-sound",
|
||||
aac: "fa-file-sound",
|
||||
|
||||
// Archive Files
|
||||
zip: "fa-file-archive",
|
||||
gz: "fa-file-archive",
|
||||
tar: "fa-file-archive",
|
||||
"7z": "fa-file-archive",
|
||||
rar: "fa-file-archive",
|
||||
|
||||
// Code Files
|
||||
py: "fa-file-code",
|
||||
c: "fa-file-code",
|
||||
cpp: "fa-file-code",
|
||||
html: "fa-file-code",
|
||||
js: "fa-file-code",
|
||||
rb: "fa-file-code",
|
||||
go: "fa-file-code"
|
||||
};
|
||||
|
||||
var ext = filename.split(".").pop();
|
||||
return mapping[ext];
|
||||
}
|
||||
|
||||
function get_page_files() {
|
||||
return CTFd.fetch("/api/v1/files?type=page", {
|
||||
credentials: "same-origin"
|
||||
}).then(function(response) {
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
|
||||
function show_files(data) {
|
||||
var list = $("#media-library-list");
|
||||
|
@ -71,7 +127,7 @@ function show_files(data) {
|
|||
$("#media-item").show();
|
||||
});
|
||||
wrapper.append(link);
|
||||
wrapper.attr("data-location", script_root + "/files/" + f.location);
|
||||
wrapper.attr("data-location", CTFd.config.urlRoot + "/files/" + f.location);
|
||||
wrapper.attr("data-id", f.id);
|
||||
wrapper.attr("data-filename", fname);
|
||||
list.append(wrapper);
|
||||
|
@ -95,7 +151,8 @@ function insert_at_cursor(editor, text) {
|
|||
}
|
||||
|
||||
function submit_form() {
|
||||
editor.save(); // Save the CodeMirror data to the Textarea
|
||||
// Save the CodeMirror data to the Textarea
|
||||
window.editor.save();
|
||||
var params = $("#page-edit").serializeJSON();
|
||||
var target = "/api/v1/pages";
|
||||
var method = "POST";
|
||||
|
@ -119,31 +176,41 @@ function submit_form() {
|
|||
})
|
||||
.then(function(response) {
|
||||
if (method === "PATCH" && response.success) {
|
||||
ezal({
|
||||
ezToast({
|
||||
title: "Saved",
|
||||
body: "Your changes have been saved",
|
||||
button: "Okay"
|
||||
body: "Your changes have been saved"
|
||||
});
|
||||
} else {
|
||||
window.location = script_root + "/admin/pages/" + response.data.id;
|
||||
window.location =
|
||||
CTFd.config.urlRoot + "/admin/pages/" + response.data.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function preview_page() {
|
||||
editor.save(); // Save the CodeMirror data to the Textarea
|
||||
$("#page-edit").attr("action", script_root + "/admin/pages/preview");
|
||||
$("#page-edit").attr("action", CTFd.config.urlRoot + "/admin/pages/preview");
|
||||
$("#page-edit").attr("target", "_blank");
|
||||
$("#page-edit").submit();
|
||||
}
|
||||
|
||||
function upload_media() {
|
||||
upload_files($("#media-library-upload"), function(data) {
|
||||
helpers.files.upload($("#media-library-upload"), function(data) {
|
||||
refresh_files();
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(() => {
|
||||
window.editor = CodeMirror.fromTextArea(
|
||||
document.getElementById("admin-pages-editor"),
|
||||
{
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: "xml",
|
||||
htmlMode: true
|
||||
}
|
||||
);
|
||||
|
||||
$("#media-insert").click(function(e) {
|
||||
var tag = "";
|
||||
try {
|
||||
|
@ -171,7 +238,7 @@ $(document).ready(function() {
|
|||
|
||||
$("#media-delete").click(function(e) {
|
||||
var file_id = $(this).attr("data-id");
|
||||
ezq({
|
||||
ezQuery({
|
||||
title: "Delete File?",
|
||||
body: "Are you sure you want to delete this file?",
|
||||
success: function() {
|
||||
|
@ -203,12 +270,15 @@ $(document).ready(function() {
|
|||
$("#media-button").click(function() {
|
||||
$("#media-library-list").empty();
|
||||
refresh_files(function() {
|
||||
$("#media-modal").modal("show");
|
||||
$("#media-modal").modal();
|
||||
});
|
||||
// get_page_files().then(function (data) {
|
||||
// var files = data;
|
||||
// console.log(files);
|
||||
// $('#media-modal').modal();
|
||||
// });
|
||||
});
|
||||
|
||||
$(".media-upload-button").click(function() {
|
||||
upload_media();
|
||||
});
|
||||
|
||||
$(".preview-page").click(function() {
|
||||
preview_page();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
import $ from "jquery";
|
||||
import events from "core/events";
|
||||
import CTFd from "core/CTFd";
|
||||
|
||||
$(() => {
|
||||
events(CTFd.config.urlRoot);
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import events from "core/events";
|
||||
import times from "core/times";
|
||||
import styles from "../styles";
|
||||
import { default as helpers } from "core/helpers";
|
||||
|
||||
CTFd.init(window.init);
|
||||
window.CTFd = CTFd;
|
||||
window.helpers = helpers;
|
||||
|
||||
$(() => {
|
||||
styles();
|
||||
times();
|
||||
events(CTFd.config.urlRoot);
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import "./main";
|
||||
import "core/utils";
|
||||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import { ezQuery, ezAlert } from "core/ezq";
|
||||
|
||||
function submit(event) {
|
||||
event.preventDefault();
|
||||
const $form = $(this);
|
||||
const params = $form.serializeJSON();
|
||||
|
||||
// Disable button after click
|
||||
$form.find("button[type=submit]").attr("disabled", true);
|
||||
|
||||
CTFd.api.post_notification_list({}, params).then(response => {
|
||||
// Admin should also see the notification sent out
|
||||
setTimeout(function() {
|
||||
$form.find("button[type=submit]").attr("disabled", false);
|
||||
}, 1000);
|
||||
if (!response.success) {
|
||||
ezAlert({
|
||||
title: "Error",
|
||||
body: "Could not send notification. Please try again.",
|
||||
button: "OK"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteNotification(event) {
|
||||
event.preventDefault();
|
||||
const $elem = $(this);
|
||||
const id = $elem.data("notif-id");
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Notification",
|
||||
body: "Are you sure you want to delete this notification?",
|
||||
success: function() {
|
||||
CTFd.api.delete_notification({ notificationId: id }).then(response => {
|
||||
if (response.success) {
|
||||
$elem.parent().remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$("#notifications_form").submit(submit);
|
||||
$(".delete-notification").click(deleteNotification);
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import "./main";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import { htmlEntities } from "core/utils";
|
||||
import { ezQuery } from "core/ezq";
|
||||
|
||||
function deletePage(event) {
|
||||
const elem = $(this);
|
||||
const name = elem.attr("page-route");
|
||||
const page_id = elem.attr("page-id");
|
||||
ezQuery({
|
||||
title: "Delete " + htmlEntities(name),
|
||||
body: "Are you sure you want to delete {0}?".format(
|
||||
"<strong>" + htmlEntities(name) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/pages/" + page_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
elem
|
||||
.parent()
|
||||
.parent()
|
||||
.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".delete-page").click(deletePage);
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import "./main";
|
||||
import $ from "jquery";
|
||||
import { ezQuery } from "core/ezq";
|
||||
|
||||
function reset(event) {
|
||||
event.preventDefault();
|
||||
ezQuery({
|
||||
title: "Reset CTF?",
|
||||
body: "Are you sure you want to reset your CTFd instance?",
|
||||
success: function() {
|
||||
$("#reset-ctf-form")
|
||||
.off("submit")
|
||||
.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$("#reset-ctf-form").submit(reset);
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import "./main";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
|
||||
const api_func = {
|
||||
users: (x, y) => CTFd.api.patch_user_public({ userId: x }, y),
|
||||
teams: (x, y) => CTFd.api.patch_team_public({ teamId: x }, y)
|
||||
};
|
||||
|
||||
function toggleAccount() {
|
||||
const $btn = $(this);
|
||||
const id = $btn.data("account-id");
|
||||
const state = $btn.data("state");
|
||||
let hidden = undefined;
|
||||
if (state === "visible") {
|
||||
hidden = true;
|
||||
} else if (state === "hidden") {
|
||||
hidden = false;
|
||||
}
|
||||
|
||||
const params = {
|
||||
hidden: hidden
|
||||
};
|
||||
|
||||
api_func[CTFd.config.userMode](id, params).then(response => {
|
||||
if (response.success) {
|
||||
if (hidden) {
|
||||
$btn.data("state", "hidden");
|
||||
$btn.addClass("btn-danger").removeClass("btn-success");
|
||||
$btn.text("Hidden");
|
||||
} else {
|
||||
$btn.data("state", "visible");
|
||||
$btn.addClass("btn-success").removeClass("btn-danger");
|
||||
$btn.text("Visible");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".scoreboard-toggle").click(toggleAccount);
|
||||
});
|
|
@ -0,0 +1,222 @@
|
|||
import "./main";
|
||||
import "core/utils";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import Plotly from "plotly.js-basic-dist";
|
||||
import { createGraph, updateGraph } from "core/graphs";
|
||||
|
||||
const graph_configs = {
|
||||
"#solves-graph": {
|
||||
layout: annotations => ({
|
||||
title: "Solve Counts",
|
||||
annotations: annotations,
|
||||
xaxis: {
|
||||
title: "Challenge Name"
|
||||
},
|
||||
yaxis: {
|
||||
title: "Amount of Solves"
|
||||
}
|
||||
}),
|
||||
fn: () => "CTFd_solves_" + new Date().toISOString().slice(0, 19),
|
||||
data: () => CTFd.api.get_challenge_solve_statistics(),
|
||||
format: response => {
|
||||
const data = response.data;
|
||||
const chals = [];
|
||||
const counts = [];
|
||||
const annotations = [];
|
||||
const solves = {};
|
||||
for (let c = 0; c < data.length; c++) {
|
||||
solves[data[c]["id"]] = {
|
||||
name: data[c]["name"],
|
||||
solves: data[c]["solves"]
|
||||
};
|
||||
}
|
||||
|
||||
const solves_order = Object.keys(solves).sort(function(a, b) {
|
||||
return solves[b].solves - solves[a].solves;
|
||||
});
|
||||
|
||||
$.each(solves_order, function(key, value) {
|
||||
chals.push(solves[value].name);
|
||||
counts.push(solves[value].solves);
|
||||
const result = {
|
||||
x: solves[value].name,
|
||||
y: solves[value].solves,
|
||||
text: solves[value].solves,
|
||||
xanchor: "center",
|
||||
yanchor: "bottom",
|
||||
showarrow: false
|
||||
};
|
||||
annotations.push(result);
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
type: "bar",
|
||||
x: chals,
|
||||
y: counts,
|
||||
text: counts,
|
||||
orientation: "v"
|
||||
},
|
||||
annotations
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
"#keys-pie-graph": {
|
||||
layout: () => ({
|
||||
title: "Submission Percentages"
|
||||
}),
|
||||
fn: () => "CTFd_submissions_" + new Date().toISOString().slice(0, 19),
|
||||
data: () => CTFd.api.get_submission_property_counts({ column: "type" }),
|
||||
format: response => {
|
||||
const data = response.data;
|
||||
const solves = data["correct"];
|
||||
const fails = data["incorrect"];
|
||||
|
||||
return [
|
||||
{
|
||||
values: [solves, fails],
|
||||
labels: ["Correct", "Incorrect"],
|
||||
marker: {
|
||||
colors: ["rgb(0, 209, 64)", "rgb(207, 38, 0)"]
|
||||
},
|
||||
text: ["Solves", "Fails"],
|
||||
hole: 0.4,
|
||||
type: "pie"
|
||||
},
|
||||
null
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
"#categories-pie-graph": {
|
||||
layout: () => ({
|
||||
title: "Category Breakdown"
|
||||
}),
|
||||
data: () => CTFd.api.get_challenge_property_counts({ column: "category" }),
|
||||
fn: () => "CTFd_categories_" + new Date().toISOString().slice(0, 19),
|
||||
format: response => {
|
||||
const data = response.data;
|
||||
|
||||
const categories = [];
|
||||
const count = [];
|
||||
|
||||
for (let category in data) {
|
||||
if (data.hasOwnProperty(category)) {
|
||||
categories.push(category);
|
||||
count.push(data[category]);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
categories.push(data[i].category);
|
||||
count.push(data[i].count);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
values: count,
|
||||
labels: categories,
|
||||
hole: 0.4,
|
||||
type: "pie"
|
||||
},
|
||||
null
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
"#solve-percentages-graph": {
|
||||
layout: annotations => ({
|
||||
title: "Solve Percentages per Challenge",
|
||||
xaxis: {
|
||||
title: "Challenge Name"
|
||||
},
|
||||
yaxis: {
|
||||
title: "Percentage of {0} (%)".format(
|
||||
CTFd.config.userMode.charAt(0).toUpperCase() +
|
||||
CTFd.config.userMode.slice(1)
|
||||
),
|
||||
range: [0, 100]
|
||||
},
|
||||
annotations: annotations
|
||||
}),
|
||||
data: () => CTFd.api.get_challenge_solve_percentages(),
|
||||
fn: () =>
|
||||
"CTFd_challenge_percentages_" + new Date().toISOString().slice(0, 19),
|
||||
format: response => {
|
||||
const data = response.data;
|
||||
|
||||
const names = [];
|
||||
const percents = [];
|
||||
|
||||
const annotations = [];
|
||||
|
||||
for (let key in data) {
|
||||
names.push(data[key].name);
|
||||
percents.push(data[key].percentage * 100);
|
||||
|
||||
const result = {
|
||||
x: data[key].name,
|
||||
y: data[key].percentage * 100,
|
||||
text: Math.round(data[key].percentage * 100) + "%",
|
||||
xanchor: "center",
|
||||
yanchor: "bottom",
|
||||
showarrow: false
|
||||
};
|
||||
annotations.push(result);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
type: "bar",
|
||||
x: names,
|
||||
y: percents,
|
||||
orientation: "v"
|
||||
},
|
||||
annotations
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const config = {
|
||||
displaylogo: false,
|
||||
responsive: true
|
||||
};
|
||||
|
||||
const createGraphs = () => {
|
||||
for (let key in graph_configs) {
|
||||
const cfg = graph_configs[key];
|
||||
|
||||
const $elem = $(key);
|
||||
$elem.empty();
|
||||
$elem[0].fn = cfg.fn();
|
||||
|
||||
cfg
|
||||
.data()
|
||||
.then(cfg.format)
|
||||
.then(([data, annotations]) => {
|
||||
Plotly.newPlot($elem[0], [data], cfg.layout(annotations), config);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function updateGraphs() {
|
||||
for (let key in graph_configs) {
|
||||
const cfg = graph_configs[key];
|
||||
const $elem = $(key);
|
||||
cfg
|
||||
.data()
|
||||
.then(cfg.format)
|
||||
.then(([data, annotations]) => {
|
||||
// FIXME: Pass annotations
|
||||
Plotly.react($elem[0], [data], cfg.layout(annotations), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(() => {
|
||||
createGraphs();
|
||||
setInterval(updateGraphs, 300000);
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import $ from "jquery";
|
||||
import styles from "../styles";
|
||||
import times from "core/times";
|
||||
|
||||
$(() => {
|
||||
styles();
|
||||
times();
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import "./main";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import { htmlEntities } from "core/utils";
|
||||
import { ezQuery } from "core/ezq";
|
||||
|
||||
function deleteCorrectSubmission(event) {
|
||||
const key_id = $(this).data("submission-id");
|
||||
const $elem = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
const chal_name = $elem
|
||||
.find(".chal")
|
||||
.text()
|
||||
.trim();
|
||||
const team_name = $elem
|
||||
.find(".team")
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Submission",
|
||||
body: "Are you sure you want to delete correct submission from {0} for challenge {1}".format(
|
||||
"<strong>" + htmlEntities(team_name) + "</strong>",
|
||||
"<strong>" + htmlEntities(chal_name) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.api
|
||||
.delete_submission({ submissionId: key_id })
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".delete-correct-submission").click(deleteCorrectSubmission);
|
||||
});
|
|
@ -0,0 +1,422 @@
|
|||
import "./main";
|
||||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import { htmlEntities } from "core/utils";
|
||||
import { ezQuery, ezBadge } from "core/ezq";
|
||||
import { createGraph, updateGraph } from "core/graphs";
|
||||
|
||||
function createTeam(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#team-info-create-form").serializeJSON(true);
|
||||
|
||||
CTFd.fetch("/api/v1/teams", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
const team_id = response.data.id;
|
||||
window.location = CTFd.config.urlRoot + "/admin/teams/" + team_id;
|
||||
} else {
|
||||
$("#team-info-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#team-info-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
const i = $("#team-info-form").find("input[name={0}]".format(key));
|
||||
const input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateTeam(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#team-info-edit-form").serializeJSON(true);
|
||||
|
||||
CTFd.fetch("/api/v1/teams/" + TEAM_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
$("#team-info-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#team-info-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
const i = $("#team-info-form").find("input[name={0}]".format(key));
|
||||
const input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const api_funcs = {
|
||||
team: [
|
||||
x => CTFd.api.get_team_solves({ teamId: x }),
|
||||
x => CTFd.api.get_team_fails({ teamId: x }),
|
||||
x => CTFd.api.get_team_awards({ teamId: x })
|
||||
],
|
||||
user: [
|
||||
x => CTFd.api.get_user_solves({ userId: x }),
|
||||
x => CTFd.api.get_user_fails({ userId: x }),
|
||||
x => CTFd.api.get_user_awards({ userId: x })
|
||||
]
|
||||
};
|
||||
|
||||
const createGraphs = (type, id, name, account_id) => {
|
||||
let [solves_func, fails_func, awards_func] = api_funcs[type];
|
||||
|
||||
Promise.all([
|
||||
solves_func(account_id),
|
||||
fails_func(account_id),
|
||||
awards_func(account_id)
|
||||
]).then(responses => {
|
||||
createGraph(
|
||||
"score_graph",
|
||||
"#score-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
createGraph(
|
||||
"category_breakdown",
|
||||
"#categories-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
createGraph(
|
||||
"solve_percentages",
|
||||
"#keys-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const updateGraphs = (type, id, name, account_id) => {
|
||||
let [solves_func, fails_func, awards_func] = api_funcs[type];
|
||||
|
||||
Promise.all([
|
||||
solves_func(account_id),
|
||||
fails_func(account_id),
|
||||
awards_func(account_id)
|
||||
]).then(responses => {
|
||||
updateGraph(
|
||||
"score_graph",
|
||||
"#score-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
updateGraph(
|
||||
"category_breakdown",
|
||||
"#categories-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
updateGraph(
|
||||
"solve_percentages",
|
||||
"#keys-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
$(() => {
|
||||
$("#team-captain-form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
const params = $("#team-captain-form").serializeJSON(true);
|
||||
|
||||
CTFd.fetch("/api/v1/teams/" + TEAM_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
$("#team-captain-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#team-captain-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
const i = $("#team-captain-form").find(
|
||||
"select[name={0}]".format(key)
|
||||
);
|
||||
const input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".edit-team").click(function(e) {
|
||||
$("#team-info-edit-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$(".edit-captain").click(function(e) {
|
||||
$("#team-captain-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$(".award-team").click(function(e) {
|
||||
$("#team-award-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$("#user-award-form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
const params = $("#user-award-form").serializeJSON(true);
|
||||
params["user_id"] = $("#award-member-input").val();
|
||||
|
||||
$("#user-award-form > #results").empty();
|
||||
|
||||
if (!params["user_id"]) {
|
||||
$("#user-award-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: "Please select a team member"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
params["user_id"] = parseInt(params["user_id"]);
|
||||
|
||||
CTFd.fetch("/api/v1/awards", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
$("#user-award-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#user-award-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
const i = $("#user-award-form").find("input[name={0}]".format(key));
|
||||
const input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-member").click(function(e) {
|
||||
e.preventDefault();
|
||||
const member_id = $(this).attr("member-id");
|
||||
const member_name = $(this).attr("member-name");
|
||||
|
||||
const params = {
|
||||
user_id: member_id
|
||||
};
|
||||
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezQuery({
|
||||
title: "Remove Member",
|
||||
body: "Are you sure you want to remove {0} from {1}? <br><br><strong>All of their challenges solves, attempts, awards, and unlocked hints will also be deleted!</strong>".format(
|
||||
"<strong>" + htmlEntities(member_name) + "</strong>",
|
||||
"<strong>" + htmlEntities(TEAM_NAME) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/teams/" + TEAM_ID + "/members", {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-team").click(function(e) {
|
||||
ezQuery({
|
||||
title: "Delete Team",
|
||||
body: "Are you sure you want to delete {0}".format(
|
||||
"<strong>" + htmlEntities(TEAM_NAME) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/teams/" + TEAM_ID, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location = CTFd.config.urlRoot + "/admin/teams";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-submission").click(function(e) {
|
||||
e.preventDefault();
|
||||
const submission_id = $(this).attr("submission-id");
|
||||
const submission_type = $(this).attr("submission-type");
|
||||
const submission_challenge = $(this).attr("submission-challenge");
|
||||
|
||||
const body = "<span>Are you sure you want to delete <strong>{0}</strong> submission from <strong>{1}</strong> for <strong>{2}</strong>?</span>".format(
|
||||
htmlEntities(submission_type),
|
||||
htmlEntities(TEAM_NAME),
|
||||
htmlEntities(submission_challenge)
|
||||
);
|
||||
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Submission",
|
||||
body: body,
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/submissions/" + submission_id, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-award").click(function(e) {
|
||||
e.preventDefault();
|
||||
const award_id = $(this).attr("award-id");
|
||||
const award_name = $(this).attr("award-name");
|
||||
|
||||
const body = "<span>Are you sure you want to delete the <strong>{0}</strong> award from <strong>{1}</strong>?".format(
|
||||
htmlEntities(award_name),
|
||||
htmlEntities(TEAM_NAME)
|
||||
);
|
||||
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Award",
|
||||
body: body,
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/awards/" + award_id, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#team-info-create-form").submit(createTeam);
|
||||
|
||||
$("#team-info-edit-form").submit(updateTeam);
|
||||
|
||||
let type, id, name, account_id;
|
||||
({ type, id, name, account_id } = window.stats_data);
|
||||
|
||||
createGraphs(type, id, name, account_id);
|
||||
setInterval(() => {
|
||||
updateGraphs(type, id, name, account_id);
|
||||
}, 300000);
|
||||
});
|
|
@ -0,0 +1,440 @@
|
|||
import "./main";
|
||||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import { htmlEntities } from "core/utils";
|
||||
import { ezQuery, ezBadge } from "core/ezq";
|
||||
import { createGraph, updateGraph } from "core/graphs";
|
||||
|
||||
function createUser(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#user-info-create-form").serializeJSON(true);
|
||||
|
||||
CTFd.fetch("/api/v1/users", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
const user_id = response.data.id;
|
||||
window.location = CTFd.config.urlRoot + "/admin/users/" + user_id;
|
||||
} else {
|
||||
$("#user-info-create-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#user-info-create-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
const i = $("#user-info-form").find("input[name={0}]".format(key));
|
||||
const input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#user-info-edit-form").serializeJSON(true);
|
||||
|
||||
CTFd.fetch("/api/v1/users/" + USER_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
$("#user-info-edit-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#user-info-edit-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
const i = $("#user-info-edit-form").find(
|
||||
"input[name={0}]".format(key)
|
||||
);
|
||||
const input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteUser(event) {
|
||||
event.preventDefault();
|
||||
ezQuery({
|
||||
title: "Delete User",
|
||||
body: "Are you sure you want to delete {0}".format(
|
||||
"<strong>" + htmlEntities(USER_NAME) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/users/" + USER_ID, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location = CTFd.config.urlRoot + "/admin/users";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function awardUser(event) {
|
||||
event.preventDefault();
|
||||
const params = $("#user-award-form").serializeJSON(true);
|
||||
params["user_id"] = USER_ID;
|
||||
|
||||
CTFd.fetch("/api/v1/awards", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
$("#user-award-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#user-award-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
const i = $("#user-award-form").find("input[name={0}]".format(key));
|
||||
const input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function emailUser(event) {
|
||||
event.preventDefault();
|
||||
var params = $("#user-mail-form").serializeJSON(true);
|
||||
CTFd.fetch("/api/v1/users/" + USER_ID + "/email", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$("#user-mail-form > #results").append(
|
||||
ezBadge({
|
||||
type: "success",
|
||||
body: "E-Mail sent successfully!"
|
||||
})
|
||||
);
|
||||
$("#user-mail-form")
|
||||
.find("input[type=text], textarea")
|
||||
.val("");
|
||||
} else {
|
||||
$("#user-mail-form > #results").empty();
|
||||
Object.keys(response.errors).forEach(function(key, index) {
|
||||
$("#user-mail-form > #results").append(
|
||||
ezBadge({
|
||||
type: "error",
|
||||
body: response.errors[key]
|
||||
})
|
||||
);
|
||||
var i = $("#user-mail-form").find(
|
||||
"input[name={0}], textarea[name={0}]".format(key)
|
||||
);
|
||||
var input = $(i);
|
||||
input.addClass("input-filled-invalid");
|
||||
input.removeClass("input-filled-valid");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteUserSubmission(event) {
|
||||
event.preventDefault();
|
||||
const submission_id = $(this).attr("submission-id");
|
||||
const submission_type = $(this).attr("submission-type");
|
||||
const submission_challenge = $(this).attr("submission-challenge");
|
||||
|
||||
const body = "<span>Are you sure you want to delete <strong>{0}</strong> submission from <strong>{1}</strong> for <strong>{2}</strong>?</span>".format(
|
||||
htmlEntities(submission_type),
|
||||
htmlEntities(USER_NAME),
|
||||
htmlEntities(submission_challenge)
|
||||
);
|
||||
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Submission",
|
||||
body: body,
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/submissions/" + submission_id, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteUserAward(event) {
|
||||
event.preventDefault();
|
||||
const award_id = $(this).attr("award-id");
|
||||
const award_name = $(this).attr("award-name");
|
||||
|
||||
const body = "<span>Are you sure you want to delete the <strong>{0}</strong> award from <strong>{1}</strong>?".format(
|
||||
htmlEntities(award_name),
|
||||
htmlEntities(USER_NAME)
|
||||
);
|
||||
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Award",
|
||||
body: body,
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/awards/" + award_id, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function correctUserSubmission(event) {
|
||||
event.preventDefault();
|
||||
const challenge_id = $(this).attr("challenge-id");
|
||||
const challenge_name = $(this).attr("challenge-name");
|
||||
const row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
const body = "<span>Are you sure you want to mark <strong>{0}</strong> solved for from <strong>{1}</strong>?".format(
|
||||
htmlEntities(challenge_name),
|
||||
htmlEntities(USER_NAME)
|
||||
);
|
||||
|
||||
const params = {
|
||||
provided: "MARKED AS SOLVED BY ADMIN",
|
||||
user_id: USER_ID,
|
||||
team_id: TEAM_ID,
|
||||
challenge_id: challenge_id,
|
||||
type: "correct"
|
||||
};
|
||||
|
||||
ezQuery({
|
||||
title: "Mark Correct",
|
||||
body: body,
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/submissions", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
// TODO: Refresh missing and solves instead of reloading
|
||||
row.remove();
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const api_funcs = {
|
||||
team: [
|
||||
x => CTFd.api.get_team_solves({ teamId: x }),
|
||||
x => CTFd.api.get_team_fails({ teamId: x }),
|
||||
x => CTFd.api.get_team_awards({ teamId: x })
|
||||
],
|
||||
user: [
|
||||
x => CTFd.api.get_user_solves({ userId: x }),
|
||||
x => CTFd.api.get_user_fails({ userId: x }),
|
||||
x => CTFd.api.get_user_awards({ userId: x })
|
||||
]
|
||||
};
|
||||
|
||||
const createGraphs = (type, id, name, account_id) => {
|
||||
let [solves_func, fails_func, awards_func] = api_funcs[type];
|
||||
|
||||
Promise.all([
|
||||
solves_func(account_id),
|
||||
fails_func(account_id),
|
||||
awards_func(account_id)
|
||||
]).then(responses => {
|
||||
createGraph(
|
||||
"score_graph",
|
||||
"#score-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
createGraph(
|
||||
"category_breakdown",
|
||||
"#categories-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
createGraph(
|
||||
"solve_percentages",
|
||||
"#keys-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const updateGraphs = (type, id, name, account_id) => {
|
||||
let [solves_func, fails_func, awards_func] = api_funcs[type];
|
||||
|
||||
Promise.all([
|
||||
solves_func(account_id),
|
||||
fails_func(account_id),
|
||||
awards_func(account_id)
|
||||
]).then(responses => {
|
||||
updateGraph(
|
||||
"score_graph",
|
||||
"#score-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
updateGraph(
|
||||
"category_breakdown",
|
||||
"#categories-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
updateGraph(
|
||||
"solve_percentages",
|
||||
"#keys-pie-graph",
|
||||
responses,
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
account_id
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
$(() => {
|
||||
$(".delete-user").click(deleteUser);
|
||||
|
||||
$(".edit-user").click(function(event) {
|
||||
$("#user-info-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$(".award-user").click(function(event) {
|
||||
$("#user-award-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$(".email-user").click(function(event) {
|
||||
$("#user-email-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$("#user-mail-form").submit(emailUser);
|
||||
|
||||
$(".delete-submission").click(deleteUserSubmission);
|
||||
$(".delete-award").click(deleteUserAward);
|
||||
$(".correct-submission").click(correctUserSubmission);
|
||||
|
||||
$("#user-info-create-form").submit(createUser);
|
||||
|
||||
$("#user-info-edit-form").submit(updateUser);
|
||||
$("#user-award-form").submit(awardUser);
|
||||
|
||||
let type, id, name, account_id;
|
||||
({ type, id, name, account_id } = window.stats_data);
|
||||
|
||||
createGraphs(type, id, name, account_id);
|
||||
setInterval(() => {
|
||||
updateGraphs(type, id, name, account_id);
|
||||
}, 300000);
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
import "./main";
|
|
@ -0,0 +1,51 @@
|
|||
import "bootstrap/dist/js/bootstrap.bundle";
|
||||
import $ from "jquery";
|
||||
|
||||
export default () => {
|
||||
$(".form-control").bind({
|
||||
focus: function() {
|
||||
$(this).addClass("input-filled-valid");
|
||||
},
|
||||
blur: function() {
|
||||
if ($(this).val() === "") {
|
||||
$(this).removeClass("input-filled-valid");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(".modal").on("show.bs.modal", function(e) {
|
||||
$(".form-control").each(function() {
|
||||
if ($(this).val()) {
|
||||
$(this).addClass("input-filled-valid");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$(".form-control").each(function() {
|
||||
if ($(this).val()) {
|
||||
$(this).addClass("input-filled-valid");
|
||||
}
|
||||
});
|
||||
|
||||
$("tr[data-href]").click(function() {
|
||||
var sel = getSelection().toString();
|
||||
if (!sel) {
|
||||
var href = $(this).attr("data-href");
|
||||
if (href) {
|
||||
window.location = href;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$("tr[data-href] a, tr[data-href] button").click(function(e) {
|
||||
// TODO: This is a hack to allow modal close buttons to work
|
||||
if (!$(this).attr("data-dismiss")) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
html{position:relative;min-height:100%}body{margin-bottom:60px}.footer{position:absolute;bottom:1px;width:100%;height:60px;line-height:normal !important;z-index:-20}
|
||||
|
||||
#score-graph{height:450px;display:block;clear:both}#solves-graph{display:block;height:350px}#keys-pie-graph{height:400px;display:block}#categories-pie-graph{height:400px;display:block}#solve-percentages-graph{height:400px;display:block}.no-decoration{color:inherit !important;text-decoration:none !important}.no-decoration:hover{color:inherit !important;text-decoration:none !important}.table td,.table th{vertical-align:inherit}pre{white-space:pre-wrap;margin:0;padding:0}.form-control{position:relative;display:block;border-radius:0;font-weight:400;font-family:"Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;-webkit-appearance:none}tbody tr:hover{background-color:rgba(0,0,0,0.1) !important}tr[data-href]{cursor:pointer}
|
||||
|
|
@ -0,0 +1 @@
|
|||
html{position:relative;min-height:100%}body{margin-bottom:60px}.footer{position:absolute;bottom:1px;width:100%;height:60px;line-height:normal!important;z-index:-20}#score-graph{height:450px;display:block;clear:both}#solves-graph{display:block;height:350px}#categories-pie-graph,#keys-pie-graph,#solve-percentages-graph{height:400px;display:block}.no-decoration,.no-decoration:hover{color:inherit!important;text-decoration:none!important}.table td,.table th{vertical-align:inherit}pre{white-space:pre-wrap;margin:0;padding:0}.form-control{position:relative;display:block;border-radius:0;font-weight:400;font-family:Avenir Next,Helvetica Neue,Helvetica,Arial,sans-serif;-webkit-appearance:none}tbody tr:hover{background-color:rgba(0,0,0,.1)!important}tr[data-href]{cursor:pointer}
|
|
@ -1,200 +0,0 @@
|
|||
html,
|
||||
body,
|
||||
.container {
|
||||
font-family: "Lato", "LatoOffline", sans-serif;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: "Raleway", "RalewayOffline", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #337ab7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table > thead > tr > td {
|
||||
/* Remove border line from thead of all tables */
|
||||
/* It can overlap with other element styles */
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.table td,
|
||||
.table th {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fa-spin.spinner {
|
||||
margin-top: 225px;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.spinner-error {
|
||||
padding-top: 20vh;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.jumbotron {
|
||||
/*background-color: #343a40;*/
|
||||
/*color: #FFF;*/
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
position: relative;
|
||||
display: block;
|
||||
/*padding: 0.8em;*/
|
||||
border-radius: 0;
|
||||
/*background: #f0f0f0;*/
|
||||
/*color: #aaa;*/
|
||||
font-weight: 400;
|
||||
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
background-color: transparent;
|
||||
border-color: #a3d39c;
|
||||
box-shadow: 0 0 0 0.2rem #a3d39c;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
.input-filled-valid {
|
||||
background-color: transparent;
|
||||
border-color: #a3d39c;
|
||||
box-shadow: 0 0 0 0.2rem #a3d39c;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
.input-filled-invalid {
|
||||
background-color: transparent;
|
||||
border-color: #d46767;
|
||||
box-shadow: 0 0 0 0.2rem #d46767;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
.btn-outlined.btn-theme {
|
||||
background: none;
|
||||
color: #545454;
|
||||
border-color: #545454;
|
||||
border: 3px solid;
|
||||
}
|
||||
|
||||
.btn-outlined {
|
||||
border-radius: 0;
|
||||
-webkit-transition: all 0.3s;
|
||||
-moz-transition: all 0.3s;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn {
|
||||
letter-spacing: 1px;
|
||||
text-decoration: none;
|
||||
-moz-user-select: none;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 700;
|
||||
padding: 8px 20px;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background-color: #5b7290 !important;
|
||||
border-color: #5b7290 !important;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background-color: #5b7290 !important;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 0 !important;
|
||||
padding: 0.8em;
|
||||
}
|
||||
|
||||
#score-graph {
|
||||
height: 450px;
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#solves-graph {
|
||||
display: block;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
#keys-pie-graph {
|
||||
height: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#categories-pie-graph {
|
||||
height: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#solve-percentages-graph {
|
||||
height: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-decoration {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.no-decoration:hover {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-fa {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cursor-help {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
-webkit-border-radius: 0 !important;
|
||||
-moz-border-radius: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.text-break {
|
||||
/* TODO: This is .text-break cloned from Bootstrap 4.3 with a fix for browsers not supporting break-word. Remove later. */
|
||||
word-break: break-all !important;
|
||||
word-break: break-word !important;
|
||||
overflow-wrap: break-word !important;
|
||||
}
|
||||
|
||||
.fa-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
.chal-desc{padding-left:30px;padding-right:30px;font-size:14px}.chal-desc img{max-width:100%;height:auto}.modal-content{border-radius:0px;max-width:1000px;padding:1em;margin:0 auto}.btn-info{background-color:#5b7290 !important}.badge-info{background-color:#5b7290 !important}.challenge-button{box-shadow:3px 3px 3px grey}.solved-challenge{background-color:#37d63e !important;opacity:0.4;border:none}.corner-button-check{margin-top:-10px;margin-right:25px;position:absolute;right:0}.key-submit .btn{height:51px}#challenge-window .form-control{position:relative;display:block;padding:0.8em;border-radius:0;background:#f0f0f0;color:#aaa;font-weight:400;font-family:"Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;-webkit-appearance:none;height:auto !important}#challenge-window .form-control:focus{background-color:transparent;border-color:#a3d39c;box-shadow:0 0 0 0.2rem #a3d39c;transition:background-color 0.3s, border-color 0.3s}
|
||||
|
|
@ -0,0 +1 @@
|
|||
.chal-desc{padding-left:30px;padding-right:30px;font-size:14px}.chal-desc img{max-width:100%;height:auto}.modal-content{border-radius:0;max-width:1000px;padding:1em;margin:0 auto}.badge-info,.btn-info{background-color:#5b7290!important}.challenge-button{box-shadow:3px 3px 3px grey}.solved-challenge{background-color:#37d63e!important;opacity:.4;border:none}.corner-button-check{margin-top:-10px;margin-right:25px;position:absolute;right:0}.key-submit .btn{height:51px}#challenge-window .form-control{position:relative;display:block;padding:.8em;border-radius:0;background:#f0f0f0;color:#aaa;font-weight:400;font-family:Avenir Next,Helvetica Neue,Helvetica,Arial,sans-serif;-webkit-appearance:none;height:auto!important}#challenge-window .form-control:focus{background-color:transparent;border-color:#a3d39c;box-shadow:0 0 0 .2rem #a3d39c;transition:background-color .3s,border-color .3s}
|
|
@ -1,19 +0,0 @@
|
|||
/* Sticky footer styles
|
||||
-------------------------------------------------- */
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 60px; /* Margin bottom by footer height */
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 1px; /* prevent scrollbars from showing on pages that don't use the full page height */
|
||||
width: 100%;
|
||||
height: 60px; /* Set the fixed height of the footer here */
|
||||
/*line-height: 60px; !* Vertically center the text there *!*/
|
||||
/*background-color: #f5f5f5;*/
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
tr[data-href] {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
function renderSubmissionResponse(response, cb) {
|
||||
var result = response.data;
|
||||
|
||||
var result_message = $("#result-message");
|
||||
var result_notification = $("#result-notification");
|
||||
var answer_input = $("#submission-input");
|
||||
result_notification.removeClass();
|
||||
result_message.text(result.message);
|
||||
|
||||
if (result.status === "authentication_required") {
|
||||
window.location =
|
||||
script_root +
|
||||
"/login?next=" +
|
||||
script_root +
|
||||
window.location.pathname +
|
||||
window.location.hash;
|
||||
return;
|
||||
} else if (result.status === "incorrect") {
|
||||
// Incorrect key
|
||||
result_notification.addClass(
|
||||
"alert alert-danger alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.removeClass("correct");
|
||||
answer_input.addClass("wrong");
|
||||
setTimeout(function() {
|
||||
answer_input.removeClass("wrong");
|
||||
}, 3000);
|
||||
} else if (result.status === "correct") {
|
||||
// Challenge Solved
|
||||
result_notification.addClass(
|
||||
"alert alert-success alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
$(".challenge-solves").text(
|
||||
parseInt(
|
||||
$(".challenge-solves")
|
||||
.text()
|
||||
.split(" ")[0]
|
||||
) +
|
||||
1 +
|
||||
" Solves"
|
||||
);
|
||||
|
||||
answer_input.val("");
|
||||
answer_input.removeClass("wrong");
|
||||
answer_input.addClass("correct");
|
||||
} else if (result.status === "already_solved") {
|
||||
// Challenge already solved
|
||||
result_notification.addClass(
|
||||
"alert alert-info alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("correct");
|
||||
} else if (result.status === "paused") {
|
||||
// CTF is paused
|
||||
result_notification.addClass(
|
||||
"alert alert-warning alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
} else if (result.status === "ratelimited") {
|
||||
// Keys per minute too high
|
||||
result_notification.addClass(
|
||||
"alert alert-warning alert-dismissable text-center"
|
||||
);
|
||||
result_notification.slideDown();
|
||||
|
||||
answer_input.addClass("too-fast");
|
||||
setTimeout(function() {
|
||||
answer_input.removeClass("too-fast");
|
||||
}, 3000);
|
||||
}
|
||||
setTimeout(function() {
|
||||
$(".alert").slideUp();
|
||||
$("#submit-key").removeClass("disabled-button");
|
||||
$("#submit-key").prop("disabled", false);
|
||||
}, 3000);
|
||||
|
||||
if (cb) {
|
||||
cb(result);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(".preview-challenge").click(function(e) {
|
||||
window.challenge = new Object();
|
||||
$.get(script_root + "/api/v1/challenges/" + CHALLENGE_ID, function(
|
||||
response
|
||||
) {
|
||||
var challenge_data = response.data;
|
||||
challenge_data["solves"] = null;
|
||||
|
||||
$.getScript(
|
||||
script_root + challenge_data.type_data.scripts.view,
|
||||
function() {
|
||||
$.get(script_root + challenge_data.type_data.templates.view, function(
|
||||
template_data
|
||||
) {
|
||||
$("#challenge-window").empty();
|
||||
var template = nunjucks.compile(template_data);
|
||||
window.challenge.data = challenge_data;
|
||||
window.challenge.preRender();
|
||||
|
||||
challenge_data["description"] = window.challenge.render(
|
||||
challenge_data["description"]
|
||||
);
|
||||
challenge_data["script_root"] = script_root;
|
||||
|
||||
$("#challenge-window").append(template.render(challenge_data));
|
||||
|
||||
$(".challenge-solves").click(function(e) {
|
||||
getsolves($("#challenge-id").val());
|
||||
});
|
||||
$(".nav-tabs a").click(function(e) {
|
||||
e.preventDefault();
|
||||
$(this).tab("show");
|
||||
});
|
||||
|
||||
// Handle modal toggling
|
||||
$("#challenge-window").on("hide.bs.modal", function(event) {
|
||||
$("#submission-input").removeClass("wrong");
|
||||
$("#submission-input").removeClass("correct");
|
||||
$("#incorrect-key").slideUp();
|
||||
$("#correct-key").slideUp();
|
||||
$("#already-solved").slideUp();
|
||||
$("#too-fast").slideUp();
|
||||
});
|
||||
|
||||
$("#submit-key").click(function(e) {
|
||||
e.preventDefault();
|
||||
$("#submit-key").addClass("disabled-button");
|
||||
$("#submit-key").prop("disabled", true);
|
||||
window.challenge.submit(function(data) {
|
||||
renderSubmissionResponse(data);
|
||||
}, true);
|
||||
// Preview passed as true
|
||||
});
|
||||
|
||||
$("#submission-input").keyup(function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
$("#submit-key").click();
|
||||
}
|
||||
});
|
||||
|
||||
$(".input-field").bind({
|
||||
focus: function() {
|
||||
$(this)
|
||||
.parent()
|
||||
.addClass("input--filled");
|
||||
$label = $(this).siblings(".input-label");
|
||||
},
|
||||
blur: function() {
|
||||
if ($(this).val() === "") {
|
||||
$(this)
|
||||
.parent()
|
||||
.removeClass("input--filled");
|
||||
$label = $(this).siblings(".input-label");
|
||||
$label.removeClass("input--hide");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.challenge.postRender();
|
||||
window.location.replace(
|
||||
window.location.href.split("#")[0] + "#preview"
|
||||
);
|
||||
|
||||
$("#challenge-window").modal();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-challenge").click(function(e) {
|
||||
ezq({
|
||||
title: "Delete Challenge",
|
||||
body: "Are you sure you want to delete {0}".format(
|
||||
"<strong>" + htmlentities(CHALLENGE_NAME) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location = script_root + "/admin/challenges";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#challenge-update-container > form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var params = $(e.target).serializeJSON(true);
|
||||
console.log(params);
|
||||
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
ezal({
|
||||
title: "Success",
|
||||
body: "Your challenge has been updated!",
|
||||
button: "OK"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (window.location.hash) {
|
||||
let hash = window.location.hash.replace("<>[]'\"", "");
|
||||
$('nav a[href="' + hash + '"]').tab("show");
|
||||
}
|
||||
|
||||
$(".nav-tabs a").click(function(e) {
|
||||
$(this).tab("show");
|
||||
window.location.hash = this.hash;
|
||||
});
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
$(document).ready(function() {});
|
|
@ -1,75 +0,0 @@
|
|||
$(document).ready(function() {
|
||||
$("#file-add-form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var formData = new FormData(e.target);
|
||||
formData.append("nonce", csrf_nonce);
|
||||
formData.append("challenge", CHALLENGE_ID);
|
||||
formData.append("type", "challenge");
|
||||
var pg = ezpg({
|
||||
width: 0,
|
||||
title: "Upload Progress"
|
||||
});
|
||||
$.ajax({
|
||||
url: script_root + "/api/v1/files",
|
||||
data: formData,
|
||||
type: "POST",
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
xhr: function() {
|
||||
var xhr = $.ajaxSettings.xhr();
|
||||
xhr.upload.onprogress = function(e) {
|
||||
if (e.lengthComputable) {
|
||||
var width = (e.loaded / e.total) * 100;
|
||||
pg = ezpg({
|
||||
target: pg,
|
||||
width: width
|
||||
});
|
||||
}
|
||||
};
|
||||
return xhr;
|
||||
},
|
||||
success: function(data) {
|
||||
// TODO: Refresh files on submit
|
||||
e.target.reset();
|
||||
|
||||
// Refresh modal
|
||||
pg = ezpg({
|
||||
target: pg,
|
||||
width: 100
|
||||
});
|
||||
setTimeout(function() {
|
||||
pg.modal("hide");
|
||||
}, 500);
|
||||
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 700);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-file").click(function(e) {
|
||||
var file_id = $(this).attr("file-id");
|
||||
var row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
ezq({
|
||||
title: "Delete Files",
|
||||
body: "Are you sure you want to delete this file?",
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/files/" + file_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,134 +0,0 @@
|
|||
$(document).ready(function() {
|
||||
$("#flag-add-button").click(function(e) {
|
||||
$.get(script_root + "/api/v1/flags/types", function(response) {
|
||||
var data = response.data;
|
||||
var flag_type_select = $("#flags-create-select");
|
||||
flag_type_select.empty();
|
||||
|
||||
var option = "<option> -- </option>";
|
||||
flag_type_select.append(option);
|
||||
|
||||
for (var key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
option = "<option value='{0}'>{1}</option>".format(
|
||||
key,
|
||||
data[key].name
|
||||
);
|
||||
flag_type_select.append(option);
|
||||
}
|
||||
}
|
||||
$("#flag-edit-modal").modal();
|
||||
});
|
||||
|
||||
$("#flag-edit-modal form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var params = $(this).serializeJSON(true);
|
||||
params["challenge"] = CHALLENGE_ID;
|
||||
CTFd.fetch("/api/v1/flags", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
$("#flag-edit-modal").modal();
|
||||
});
|
||||
|
||||
$("#flags-create-select").change(function(e) {
|
||||
e.preventDefault();
|
||||
var flag_type_name = $(this)
|
||||
.find("option:selected")
|
||||
.text();
|
||||
|
||||
$.get(script_root + "/api/v1/flags/types/" + flag_type_name, function(
|
||||
response
|
||||
) {
|
||||
var data = response.data;
|
||||
$.get(script_root + data.templates.create, function(template_data) {
|
||||
var template = nunjucks.compile(template_data);
|
||||
$("#create-keys-entry-div").html(template.render());
|
||||
$("#create-keys-button-div").show();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(".edit-flag").click(function(e) {
|
||||
e.preventDefault();
|
||||
var flag_id = $(this).attr("flag-id");
|
||||
var row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
$.get(script_root + "/api/v1/flags/" + flag_id, function(response) {
|
||||
var data = response.data;
|
||||
$.get(script_root + data.templates.update, function(template_data) {
|
||||
$("#edit-flags form").empty();
|
||||
|
||||
var template = nunjucks.compile(template_data);
|
||||
$("#edit-flags form").append(template.render(data));
|
||||
|
||||
$("#edit-flags form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var params = $("#edit-flags form").serializeJSON();
|
||||
|
||||
CTFd.fetch("/api/v1/flags/" + flag_id, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$(row)
|
||||
.find(".flag-content")
|
||||
.text(response.data.content);
|
||||
$("#edit-flags").modal("toggle");
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#edit-flags").modal();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-flag").click(function(e) {
|
||||
e.preventDefault();
|
||||
var flag_id = $(this).attr("flag-id");
|
||||
var row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
ezq({
|
||||
title: "Delete Flag",
|
||||
body: "Are you sure you want to delete this flag?",
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/flags/" + flag_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,157 +0,0 @@
|
|||
function hint(id) {
|
||||
return CTFd.fetch("/api/v1/hints/" + id + "?preview=true", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then(function(response) {
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
|
||||
function loadhint(hintid) {
|
||||
var md = window.markdownit({
|
||||
html: true,
|
||||
linkify: true
|
||||
});
|
||||
|
||||
hint(hintid).then(function(response) {
|
||||
if (response.data.content) {
|
||||
ezal({
|
||||
title: "Hint",
|
||||
body: md.render(response.data.content),
|
||||
button: "Got it!"
|
||||
});
|
||||
} else {
|
||||
ezal({
|
||||
title: "Error",
|
||||
body: "Error loading hint!",
|
||||
button: "OK"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#hint-add-button").click(function(e) {
|
||||
$("#hint-edit-modal form")
|
||||
.find("input, textarea")
|
||||
.val("");
|
||||
|
||||
// Markdown Preview
|
||||
$("#new-hint-edit").on("shown.bs.tab", function(event) {
|
||||
console.log(event.target.hash);
|
||||
if (event.target.hash == "#hint-preview") {
|
||||
console.log(event.target.hash);
|
||||
var renderer = window.markdownit({
|
||||
html: true,
|
||||
linkify: true
|
||||
});
|
||||
var editor_value = $("#hint-write textarea").val();
|
||||
$(event.target.hash).html(renderer.render(editor_value));
|
||||
}
|
||||
});
|
||||
|
||||
$("#hint-edit-modal").modal();
|
||||
});
|
||||
|
||||
$(".delete-hint").click(function(e) {
|
||||
e.preventDefault();
|
||||
var hint_id = $(this).attr("hint-id");
|
||||
var row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
ezq({
|
||||
title: "Delete Hint",
|
||||
body: "Are you sure you want to delete this hint?",
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/hints/" + hint_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".edit-hint").click(function(e) {
|
||||
e.preventDefault();
|
||||
var hint_id = $(this).attr("hint-id");
|
||||
|
||||
CTFd.fetch("/api/v1/hints/" + hint_id + "?preview=true", {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$("#hint-edit-form input[name=content],textarea[name=content]").val(
|
||||
response.data.content
|
||||
);
|
||||
$("#hint-edit-form input[name=cost]").val(response.data.cost);
|
||||
$("#hint-edit-form input[name=id]").val(response.data.id);
|
||||
|
||||
// Markdown Preview
|
||||
$("#new-hint-edit").on("shown.bs.tab", function(event) {
|
||||
console.log(event.target.hash);
|
||||
if (event.target.hash == "#hint-preview") {
|
||||
console.log(event.target.hash);
|
||||
var renderer = new markdownit({
|
||||
html: true,
|
||||
linkify: true
|
||||
});
|
||||
var editor_value = $("#hint-write textarea").val();
|
||||
$(event.target.hash).html(renderer.render(editor_value));
|
||||
}
|
||||
});
|
||||
|
||||
$("#hint-edit-modal").modal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#hint-edit-form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var params = $(this).serializeJSON(true);
|
||||
params["challenge"] = CHALLENGE_ID;
|
||||
|
||||
var method = "POST";
|
||||
var url = "/api/v1/hints";
|
||||
if (params.id) {
|
||||
method = "PATCH";
|
||||
url = "/api/v1/hints/" + params.id;
|
||||
}
|
||||
CTFd.fetch(url, {
|
||||
method: method,
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
// TODO: Refresh hints on submit.
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,69 +0,0 @@
|
|||
$.ajaxSetup({ cache: false });
|
||||
|
||||
window.challenge = new Object();
|
||||
|
||||
function load_chal_template(challenge) {
|
||||
$.getScript(script_root + challenge.scripts.view, function() {
|
||||
console.log("loaded renderer");
|
||||
$.get(script_root + challenge.templates.create, function(template_data) {
|
||||
var template = nunjucks.compile(template_data);
|
||||
$("#create-chal-entry-div").html(
|
||||
template.render({ nonce: nonce, script_root: script_root })
|
||||
);
|
||||
$.getScript(script_root + challenge.scripts.create, function() {
|
||||
console.log("loaded");
|
||||
$("#create-chal-entry-div form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var params = $("#create-chal-entry-div form").serializeJSON();
|
||||
CTFd.fetch("/api/v1/challenges", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location =
|
||||
script_root + "/admin/challenges/" + response.data.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$.get(script_root + "/api/v1/challenges/types", function(response) {
|
||||
$("#create-chals-select").empty();
|
||||
var data = response.data;
|
||||
var chal_type_amt = Object.keys(data).length;
|
||||
if (chal_type_amt > 1) {
|
||||
var option = "<option> -- </option>";
|
||||
$("#create-chals-select").append(option);
|
||||
for (var key in data) {
|
||||
var challenge = data[key];
|
||||
var option = $("<option/>");
|
||||
option.attr("value", challenge.type);
|
||||
option.text(challenge.name);
|
||||
option.data("meta", challenge);
|
||||
$("#create-chals-select").append(option);
|
||||
}
|
||||
$("#create-chals-select-div").show();
|
||||
} else if (chal_type_amt == 1) {
|
||||
var key = Object.keys(data)[0];
|
||||
$("#create-chals-select").empty();
|
||||
load_chal_template(data[key]);
|
||||
}
|
||||
});
|
||||
$("#create-chals-select").change(function() {
|
||||
var challenge = $(this)
|
||||
.find("option:selected")
|
||||
.data("meta");
|
||||
load_chal_template(challenge);
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
$(document).ready(function() {
|
||||
$("#prerequisite-add-form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var requirements = $("#prerequisite-add-form").serializeJSON();
|
||||
CHALLENGE_REQUIREMENTS.prerequisites.push(
|
||||
parseInt(requirements["prerequisite"])
|
||||
);
|
||||
|
||||
var params = {
|
||||
requirements: CHALLENGE_REQUIREMENTS
|
||||
};
|
||||
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
// TODO: Make this refresh requirements
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-requirement").click(function(e) {
|
||||
var challenge_id = $(this).attr("challenge-id");
|
||||
var row = $(this)
|
||||
.parent()
|
||||
.parent();
|
||||
|
||||
CHALLENGE_REQUIREMENTS.prerequisites.pop(challenge_id);
|
||||
|
||||
var params = {
|
||||
requirements: CHALLENGE_REQUIREMENTS
|
||||
};
|
||||
|
||||
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
function delete_tag(elem) {
|
||||
var elem = $(elem);
|
||||
var tag_id = elem.attr("tag-id");
|
||||
|
||||
CTFd.fetch("/api/v1/tags/" + tag_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$(elem)
|
||||
.parent()
|
||||
.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#tags-add-input").keyup(function(e) {
|
||||
if (e.keyCode == 13) {
|
||||
var tag = $("#tags-add-input").val();
|
||||
var params = {
|
||||
value: tag,
|
||||
challenge: CHALLENGE_ID
|
||||
};
|
||||
|
||||
CTFd.fetch("/api/v1/tags", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
var tpl =
|
||||
"<span class='badge badge-primary mx-1 challenge-tag'>" +
|
||||
"<span>{0}</span>" +
|
||||
"<a class='btn-fa delete-tag' tag-id='{1}' onclick='delete_tag(this)'>×</a></span>";
|
||||
tag = tpl.format(response.data.value, response.data.id);
|
||||
$("#challenge-tags").append(tag);
|
||||
}
|
||||
});
|
||||
|
||||
$("#tags-add-input").val("");
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,317 +0,0 @@
|
|||
var months = {
|
||||
January: 1,
|
||||
February: 2,
|
||||
March: 3,
|
||||
April: 4,
|
||||
May: 5,
|
||||
June: 6,
|
||||
July: 7,
|
||||
August: 8,
|
||||
September: 9,
|
||||
October: 10,
|
||||
November: 11,
|
||||
December: 12
|
||||
};
|
||||
|
||||
function load_timestamp(place, timestamp) {
|
||||
if (typeof timestamp == "string") {
|
||||
var timestamp = parseInt(timestamp);
|
||||
}
|
||||
var m = moment(timestamp * 1000);
|
||||
console.log("Loading " + place);
|
||||
console.log(timestamp);
|
||||
console.log(m.toISOString());
|
||||
console.log(m.unix());
|
||||
var month = $("#" + place + "-month").val(m.month() + 1); // Months are zero indexed (http://momentjs.com/docs/#/get-set/month/)
|
||||
var day = $("#" + place + "-day").val(m.date());
|
||||
var year = $("#" + place + "-year").val(m.year());
|
||||
var hour = $("#" + place + "-hour").val(m.hour());
|
||||
var minute = $("#" + place + "-minute").val(m.minute());
|
||||
load_date_values(place);
|
||||
}
|
||||
|
||||
function load_date_values(place) {
|
||||
var month = $("#" + place + "-month").val();
|
||||
var day = $("#" + place + "-day").val();
|
||||
var year = $("#" + place + "-year").val();
|
||||
var hour = $("#" + place + "-hour").val();
|
||||
var minute = $("#" + place + "-minute").val();
|
||||
var timezone = $("#" + place + "-timezone").val();
|
||||
|
||||
var utc = convert_date_to_moment(month, day, year, hour, minute, timezone);
|
||||
if (isNaN(utc.unix())) {
|
||||
$("#" + place).val("");
|
||||
$("#" + place + "-local").val("");
|
||||
$("#" + place + "-zonetime").val("");
|
||||
} else {
|
||||
$("#" + place).val(utc.unix());
|
||||
$("#" + place + "-local").val(
|
||||
utc.local().format("dddd, MMMM Do YYYY, h:mm:ss a zz")
|
||||
);
|
||||
$("#" + place + "-zonetime").val(
|
||||
utc.tz(timezone).format("dddd, MMMM Do YYYY, h:mm:ss a zz")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function convert_date_to_moment(month, day, year, hour, minute, timezone) {
|
||||
var month_num = month.toString();
|
||||
if (month_num.length == 1) {
|
||||
var month_num = "0" + month_num;
|
||||
}
|
||||
|
||||
var day_str = day.toString();
|
||||
if (day_str.length == 1) {
|
||||
day_str = "0" + day_str;
|
||||
}
|
||||
|
||||
var hour_str = hour.toString();
|
||||
if (hour_str.length == 1) {
|
||||
hour_str = "0" + hour_str;
|
||||
}
|
||||
|
||||
var min_str = minute.toString();
|
||||
if (min_str.length == 1) {
|
||||
min_str = "0" + min_str;
|
||||
}
|
||||
|
||||
// 2013-02-08 24:00
|
||||
var date_string =
|
||||
year.toString() +
|
||||
"-" +
|
||||
month_num +
|
||||
"-" +
|
||||
day_str +
|
||||
" " +
|
||||
hour_str +
|
||||
":" +
|
||||
min_str +
|
||||
":00";
|
||||
var m = moment(date_string, moment.ISO_8601);
|
||||
return m;
|
||||
}
|
||||
|
||||
function update_configs(obj) {
|
||||
var target = "/api/v1/configs";
|
||||
var method = "PATCH";
|
||||
|
||||
var params = {};
|
||||
|
||||
if (obj.mail_useauth === false) {
|
||||
obj.mail_username = null;
|
||||
obj.mail_password = null;
|
||||
} else {
|
||||
if (obj.mail_username === "") {
|
||||
delete obj.mail_username;
|
||||
}
|
||||
if (obj.mail_password === "") {
|
||||
delete obj.mail_password;
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(obj).forEach(function(x) {
|
||||
if (obj[x] === "true") {
|
||||
params[x] = true;
|
||||
} else if (obj[x] === "false") {
|
||||
params[x] = false;
|
||||
} else {
|
||||
params[x] = obj[x];
|
||||
}
|
||||
});
|
||||
|
||||
CTFd.fetch(target, {
|
||||
method: method,
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function upload_logo(form) {
|
||||
upload_files(form, function(response) {
|
||||
var upload = response.data[0];
|
||||
if (upload.location) {
|
||||
var params = {
|
||||
value: upload.location
|
||||
};
|
||||
CTFd.fetch("/api/v1/configs/ctf_logo", {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
ezal({
|
||||
title: "Error!",
|
||||
body: "Logo uploading failed!",
|
||||
button: "Okay"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function remove_logo() {
|
||||
ezq({
|
||||
title: "Remove logo",
|
||||
body: "Are you sure you'd like to remove the CTF logo?",
|
||||
success: function() {
|
||||
var params = {
|
||||
value: null
|
||||
};
|
||||
CTFd.fetch("/api/v1/configs/ctf_logo", {
|
||||
method: "PATCH",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$(".config-section > form:not(.form-upload)").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var obj = $(this).serializeJSON();
|
||||
update_configs(obj);
|
||||
});
|
||||
|
||||
$("#logo-upload").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var form = e.target;
|
||||
upload_logo(form);
|
||||
});
|
||||
|
||||
$(".start-date").change(function() {
|
||||
load_date_values("start");
|
||||
});
|
||||
|
||||
$(".end-date").change(function() {
|
||||
load_date_values("end");
|
||||
});
|
||||
|
||||
$(".freeze-date").change(function() {
|
||||
load_date_values("freeze");
|
||||
});
|
||||
|
||||
$("#export-button").click(function(e) {
|
||||
e.preventDefault();
|
||||
var href = script_root + "/admin/export";
|
||||
window.location.href = $("#export-button").attr("href");
|
||||
});
|
||||
|
||||
$("#import-button").click(function(e) {
|
||||
e.preventDefault();
|
||||
var import_file = document.getElementById("import-file").files[0];
|
||||
|
||||
var form_data = new FormData();
|
||||
form_data.append("backup", import_file);
|
||||
form_data.append("nonce", csrf_nonce);
|
||||
|
||||
var pg = ezpg({
|
||||
width: 0,
|
||||
title: "Upload Progress"
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: script_root + "/admin/import",
|
||||
type: "POST",
|
||||
data: form_data,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
statusCode: {
|
||||
500: function(resp) {
|
||||
console.log(resp.responseText);
|
||||
alert(resp.responseText);
|
||||
}
|
||||
},
|
||||
xhr: function() {
|
||||
var xhr = $.ajaxSettings.xhr();
|
||||
xhr.upload.onprogress = function(e) {
|
||||
if (e.lengthComputable) {
|
||||
var width = (e.loaded / e.total) * 100;
|
||||
pg = ezpg({
|
||||
target: pg,
|
||||
width: width
|
||||
});
|
||||
}
|
||||
};
|
||||
return xhr;
|
||||
},
|
||||
success: function(data) {
|
||||
// Refresh modal
|
||||
pg = ezpg({
|
||||
target: pg,
|
||||
width: 100
|
||||
});
|
||||
setTimeout(function() {
|
||||
pg.modal("hide");
|
||||
}, 500);
|
||||
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 700);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var hash = window.location.hash;
|
||||
if (hash) {
|
||||
hash = hash.replace("<>[]'\"", "");
|
||||
$('ul.nav a[href="' + hash + '"]').tab("show");
|
||||
}
|
||||
|
||||
$(".nav-pills a").click(function(e) {
|
||||
$(this).tab("show");
|
||||
window.location.hash = this.hash;
|
||||
});
|
||||
|
||||
var start = $("#start").val();
|
||||
var end = $("#end").val();
|
||||
var freeze = $("#freeze").val();
|
||||
|
||||
if (start) {
|
||||
load_timestamp("start", start);
|
||||
}
|
||||
if (end) {
|
||||
load_timestamp("end", end);
|
||||
}
|
||||
if (freeze) {
|
||||
load_timestamp("freeze", freeze);
|
||||
}
|
||||
|
||||
// Toggle username and password based on stored value
|
||||
$("#mail_useauth")
|
||||
.change(function() {
|
||||
$("#mail_username_password").toggle(this.checked);
|
||||
})
|
||||
.change();
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -1,46 +0,0 @@
|
|||
function upload_files(form, cb) {
|
||||
if (form instanceof jQuery) {
|
||||
form = form[0];
|
||||
}
|
||||
var formData = new FormData(form);
|
||||
formData.append("nonce", csrf_nonce);
|
||||
var pg = ezpg({
|
||||
width: 0,
|
||||
title: "Upload Progress"
|
||||
});
|
||||
$.ajax({
|
||||
url: script_root + "/api/v1/files",
|
||||
data: formData,
|
||||
type: "POST",
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
xhr: function() {
|
||||
var xhr = $.ajaxSettings.xhr();
|
||||
xhr.upload.onprogress = function(e) {
|
||||
if (e.lengthComputable) {
|
||||
var width = (e.loaded / e.total) * 100;
|
||||
pg = ezpg({
|
||||
target: pg,
|
||||
width: width
|
||||
});
|
||||
}
|
||||
};
|
||||
return xhr;
|
||||
},
|
||||
success: function(data) {
|
||||
// Refresh modal
|
||||
pg = ezpg({
|
||||
target: pg,
|
||||
width: 100
|
||||
});
|
||||
setTimeout(function() {
|
||||
pg.modal("hide");
|
||||
}, 500);
|
||||
|
||||
if (cb) {
|
||||
cb(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,63 @@
|
|||
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["helpers"],{
|
||||
|
||||
/***/ "./CTFd/themes/core/assets/js/helpers.js":
|
||||
/*!***********************************************!*\
|
||||
!*** ./CTFd/themes/core/assets/js/helpers.js ***!
|
||||
\***********************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _ezq = _interopRequireDefault(__webpack_require__(/*! ./ezq */ \"./CTFd/themes/core/assets/js/ezq.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\nvar files = {\n upload: function upload(form, extra_data, cb) {\n var CTFd = window.CTFd;\n\n if (form instanceof _jquery.default) {\n form = form[0];\n }\n\n var formData = new FormData(form);\n formData.append(\"nonce\", CTFd.config.csrfNonce);\n\n for (var _i = 0, _Object$entries = Object.entries(extra_data); _i < _Object$entries.length; _i++) {\n var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),\n key = _Object$entries$_i[0],\n value = _Object$entries$_i[1];\n\n formData.append(key, value);\n }\n\n var pg = _ezq.default.ezProgressBar({\n width: 0,\n title: \"Upload Progress\"\n });\n\n _jquery.default.ajax({\n url: CTFd.config.urlRoot + \"/api/v1/files\",\n data: formData,\n type: \"POST\",\n cache: false,\n contentType: false,\n processData: false,\n xhr: function xhr() {\n var xhr = _jquery.default.ajaxSettings.xhr();\n\n xhr.upload.onprogress = function (e) {\n if (e.lengthComputable) {\n var width = e.loaded / e.total * 100;\n pg = _ezq.default.ezProgressBar({\n target: pg,\n width: width\n });\n }\n };\n\n return xhr;\n },\n success: function success(data) {\n form.reset();\n pg = _ezq.default.ezProgressBar({\n target: pg,\n width: 100\n });\n setTimeout(function () {\n pg.modal(\"hide\");\n }, 500);\n\n if (cb) {\n cb(data);\n }\n }\n });\n }\n};\nvar helpers = {\n files: files,\n ezq: _ezq.default\n};\nvar _default = helpers;\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/core/assets/js/helpers.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./node_modules/markdown-it/lib/helpers/index.js":
|
||||
/*!*******************************************************!*\
|
||||
!*** ./node_modules/markdown-it/lib/helpers/index.js ***!
|
||||
\*******************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("// Just a shortcut for bulk export\n\n\nexports.parseLinkLabel = __webpack_require__(/*! ./parse_link_label */ \"./node_modules/markdown-it/lib/helpers/parse_link_label.js\");\nexports.parseLinkDestination = __webpack_require__(/*! ./parse_link_destination */ \"./node_modules/markdown-it/lib/helpers/parse_link_destination.js\");\nexports.parseLinkTitle = __webpack_require__(/*! ./parse_link_title */ \"./node_modules/markdown-it/lib/helpers/parse_link_title.js\");\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/index.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./node_modules/markdown-it/lib/helpers/parse_link_destination.js":
|
||||
/*!************************************************************************!*\
|
||||
!*** ./node_modules/markdown-it/lib/helpers/parse_link_destination.js ***!
|
||||
\************************************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("// Parse link destination\n//\n\n\nvar unescapeAll = __webpack_require__(/*! ../common/utils */ \"./node_modules/markdown-it/lib/common/utils.js\").unescapeAll;\n\nmodule.exports = function parseLinkDestination(str, pos, max) {\n var code,\n level,\n lines = 0,\n start = pos,\n result = {\n ok: false,\n pos: 0,\n lines: 0,\n str: ''\n };\n\n if (str.charCodeAt(pos) === 0x3C\n /* < */\n ) {\n pos++;\n\n while (pos < max) {\n code = str.charCodeAt(pos);\n\n if (code === 0x0A\n /* \\n */\n ) {\n return result;\n }\n\n if (code === 0x3E\n /* > */\n ) {\n result.pos = pos + 1;\n result.str = unescapeAll(str.slice(start + 1, pos));\n result.ok = true;\n return result;\n }\n\n if (code === 0x5C\n /* \\ */\n && pos + 1 < max) {\n pos += 2;\n continue;\n }\n\n pos++;\n } // no closing '>'\n\n\n return result;\n } // this should be ... } else { ... branch\n\n\n level = 0;\n\n while (pos < max) {\n code = str.charCodeAt(pos);\n\n if (code === 0x20) {\n break;\n } // ascii control characters\n\n\n if (code < 0x20 || code === 0x7F) {\n break;\n }\n\n if (code === 0x5C\n /* \\ */\n && pos + 1 < max) {\n pos += 2;\n continue;\n }\n\n if (code === 0x28\n /* ( */\n ) {\n level++;\n }\n\n if (code === 0x29\n /* ) */\n ) {\n if (level === 0) {\n break;\n }\n\n level--;\n }\n\n pos++;\n }\n\n if (start === pos) {\n return result;\n }\n\n if (level !== 0) {\n return result;\n }\n\n result.str = unescapeAll(str.slice(start, pos));\n result.lines = lines;\n result.pos = pos;\n result.ok = true;\n return result;\n};\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/parse_link_destination.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./node_modules/markdown-it/lib/helpers/parse_link_label.js":
|
||||
/*!******************************************************************!*\
|
||||
!*** ./node_modules/markdown-it/lib/helpers/parse_link_label.js ***!
|
||||
\******************************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("// Parse link label\n//\n// this function assumes that first character (\"[\") already matches;\n// returns the end of the label\n//\n\n\nmodule.exports = function parseLinkLabel(state, start, disableNested) {\n var level,\n found,\n marker,\n prevPos,\n labelEnd = -1,\n max = state.posMax,\n oldPos = state.pos;\n state.pos = start + 1;\n level = 1;\n\n while (state.pos < max) {\n marker = state.src.charCodeAt(state.pos);\n\n if (marker === 0x5D\n /* ] */\n ) {\n level--;\n\n if (level === 0) {\n found = true;\n break;\n }\n }\n\n prevPos = state.pos;\n state.md.inline.skipToken(state);\n\n if (marker === 0x5B\n /* [ */\n ) {\n if (prevPos === state.pos - 1) {\n // increase level if we find text `[`, which is not a part of any token\n level++;\n } else if (disableNested) {\n state.pos = oldPos;\n return -1;\n }\n }\n }\n\n if (found) {\n labelEnd = state.pos;\n } // restore old state\n\n\n state.pos = oldPos;\n return labelEnd;\n};\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/parse_link_label.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./node_modules/markdown-it/lib/helpers/parse_link_title.js":
|
||||
/*!******************************************************************!*\
|
||||
!*** ./node_modules/markdown-it/lib/helpers/parse_link_title.js ***!
|
||||
\******************************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("// Parse link title\n//\n\n\nvar unescapeAll = __webpack_require__(/*! ../common/utils */ \"./node_modules/markdown-it/lib/common/utils.js\").unescapeAll;\n\nmodule.exports = function parseLinkTitle(str, pos, max) {\n var code,\n marker,\n lines = 0,\n start = pos,\n result = {\n ok: false,\n pos: 0,\n lines: 0,\n str: ''\n };\n\n if (pos >= max) {\n return result;\n }\n\n marker = str.charCodeAt(pos);\n\n if (marker !== 0x22\n /* \" */\n && marker !== 0x27\n /* ' */\n && marker !== 0x28\n /* ( */\n ) {\n return result;\n }\n\n pos++; // if opening marker is \"(\", switch it to closing marker \")\"\n\n if (marker === 0x28) {\n marker = 0x29;\n }\n\n while (pos < max) {\n code = str.charCodeAt(pos);\n\n if (code === marker) {\n result.pos = pos + 1;\n result.lines = lines;\n result.str = unescapeAll(str.slice(start + 1, pos));\n result.ok = true;\n return result;\n } else if (code === 0x0A) {\n lines++;\n } else if (code === 0x5C\n /* \\ */\n && pos + 1 < max) {\n pos++;\n\n if (str.charCodeAt(pos) === 0x0A) {\n lines++;\n }\n }\n\n pos++;\n }\n\n return result;\n};\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/parse_link_title.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
}]);
|
|
@ -0,0 +1 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{"./CTFd/themes/core/assets/js/helpers.js":function(e,r,o){Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var p=t(o("./node_modules/jquery/dist/jquery.js")),f=t(o("./CTFd/themes/core/assets/js/ezq.js"));function t(e){return e&&e.__esModule?e:{default:e}}function c(e,r){return function(e){if(Array.isArray(e))return e}(e)||function(e,r){var o=[],t=!0,n=!1,s=void 0;try{for(var i,a=e[Symbol.iterator]();!(t=(i=a.next()).done)&&(o.push(i.value),!r||o.length!==r);t=!0);}catch(e){n=!0,s=e}finally{try{t||null==a.return||a.return()}finally{if(n)throw s}}return o}(e,r)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}var n={files:{upload:function(r,e,o){var t=window.CTFd;r instanceof p.default&&(r=r[0]);var n=new FormData(r);n.append("nonce",t.config.csrfNonce);for(var s=0,i=Object.entries(e);s<i.length;s++){var a=c(i[s],2),l=a[0],d=a[1];n.append(l,d)}var u=f.default.ezProgressBar({width:0,title:"Upload Progress"});p.default.ajax({url:t.config.urlRoot+"/api/v1/files",data:n,type:"POST",cache:!1,contentType:!1,processData:!1,xhr:function(){var e=p.default.ajaxSettings.xhr();return e.upload.onprogress=function(e){if(e.lengthComputable){var r=e.loaded/e.total*100;u=f.default.ezProgressBar({target:u,width:r})}},e},success:function(e){r.reset(),u=f.default.ezProgressBar({target:u,width:100}),setTimeout(function(){u.modal("hide")},500),o&&o(e)}})}},ezq:f.default};r.default=n},"./node_modules/markdown-it/lib/helpers/index.js":function(e,r,o){r.parseLinkLabel=o("./node_modules/markdown-it/lib/helpers/parse_link_label.js"),r.parseLinkDestination=o("./node_modules/markdown-it/lib/helpers/parse_link_destination.js"),r.parseLinkTitle=o("./node_modules/markdown-it/lib/helpers/parse_link_title.js")},"./node_modules/markdown-it/lib/helpers/parse_link_destination.js":function(e,r,o){var a=o("./node_modules/markdown-it/lib/common/utils.js").unescapeAll;e.exports=function(e,r,o){var t,n,s=r,i={ok:!1,pos:0,lines:0,str:""};if(60===e.charCodeAt(r)){for(r++;r<o;){if(10===(t=e.charCodeAt(r)))return i;if(62===t)return i.pos=r+1,i.str=a(e.slice(s+1,r)),i.ok=!0,i;92===t&&r+1<o?r+=2:r++}return i}for(n=0;r<o&&32!==(t=e.charCodeAt(r))&&!(t<32||127===t);)if(92===t&&r+1<o)r+=2;else{if(40===t&&n++,41===t){if(0===n)break;n--}r++}return s===r||0!==n||(i.str=a(e.slice(s,r)),i.lines=0,i.pos=r,i.ok=!0),i}},"./node_modules/markdown-it/lib/helpers/parse_link_label.js":function(e,r,o){e.exports=function(e,r,o){var t,n,s,i,a=-1,l=e.posMax,d=e.pos;for(e.pos=r+1,t=1;e.pos<l;){if(93===(s=e.src.charCodeAt(e.pos))&&0===--t){n=!0;break}if(i=e.pos,e.md.inline.skipToken(e),91===s)if(i===e.pos-1)t++;else if(o)return e.pos=d,-1}return n&&(a=e.pos),e.pos=d,a}},"./node_modules/markdown-it/lib/helpers/parse_link_title.js":function(e,r,o){var l=o("./node_modules/markdown-it/lib/common/utils.js").unescapeAll;e.exports=function(e,r,o){var t,n,s=0,i=r,a={ok:!1,pos:0,lines:0,str:""};if(o<=r)return a;if(34!==(n=e.charCodeAt(r))&&39!==n&&40!==n)return a;for(r++,40===n&&(n=41);r<o;){if((t=e.charCodeAt(r))===n)return a.pos=r+1,a.lines=s,a.str=l(e.slice(i+1,r)),a.ok=!0,a;10===t?s++:92===t&&r+1<o&&(r++,10===e.charCodeAt(r)&&s++),r++}return a}}}]);
|
|
@ -1,3 +0,0 @@
|
|||
$(document).ready(function() {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
|
@ -1,51 +0,0 @@
|
|||
$(document).ready(function() {
|
||||
$("#notifications_form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var form = $("#notifications_form");
|
||||
var params = form.serializeJSON();
|
||||
|
||||
CTFd.fetch("/api/v1/notifications", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-notification").click(function(e) {
|
||||
e.preventDefault();
|
||||
var elem = $(this);
|
||||
var notif_id = elem.attr("notif-id");
|
||||
|
||||
ezq({
|
||||
title: "Delete Notification",
|
||||
body: "Are you sure you want to delete this notification?",
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/notifications/" + notif_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
elem.parent().remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,155 @@
|
|||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // install a JSONP callback for chunk loading
|
||||
/******/ function webpackJsonpCallback(data) {
|
||||
/******/ var chunkIds = data[0];
|
||||
/******/ var moreModules = data[1];
|
||||
/******/ var executeModules = data[2];
|
||||
/******/
|
||||
/******/ // add "moreModules" to the modules object,
|
||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||
/******/ var moduleId, chunkId, i = 0, resolves = [];
|
||||
/******/ for(;i < chunkIds.length; i++) {
|
||||
/******/ chunkId = chunkIds[i];
|
||||
/******/ if(installedChunks[chunkId]) {
|
||||
/******/ resolves.push(installedChunks[chunkId][0]);
|
||||
/******/ }
|
||||
/******/ installedChunks[chunkId] = 0;
|
||||
/******/ }
|
||||
/******/ for(moduleId in moreModules) {
|
||||
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
||||
/******/ modules[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
|
||||
/******/
|
||||
/******/ while(resolves.length) {
|
||||
/******/ resolves.shift()();
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // add entry modules from loaded chunk to deferred list
|
||||
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
|
||||
/******/
|
||||
/******/ // run deferred modules when all chunks ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ };
|
||||
/******/ function checkDeferredModules() {
|
||||
/******/ var result;
|
||||
/******/ for(var i = 0; i < deferredModules.length; i++) {
|
||||
/******/ var deferredModule = deferredModules[i];
|
||||
/******/ var fulfilled = true;
|
||||
/******/ for(var j = 1; j < deferredModule.length; j++) {
|
||||
/******/ var depId = deferredModule[j];
|
||||
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
|
||||
/******/ }
|
||||
/******/ if(fulfilled) {
|
||||
/******/ deferredModules.splice(i--, 1);
|
||||
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ return result;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // object to store loaded and loading chunks
|
||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||
/******/ // Promise = chunk loading, 0 = chunk loaded
|
||||
/******/ var installedChunks = {
|
||||
/******/ "pages/main": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ var deferredModules = [];
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = function(exports) {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "/themes/admin/static/js";
|
||||
/******/
|
||||
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
|
||||
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
|
||||
/******/ jsonpArray.push = webpackJsonpCallback;
|
||||
/******/ jsonpArray = jsonpArray.slice();
|
||||
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
|
||||
/******/ var parentJsonpFunction = oldJsonpFunction;
|
||||
/******/
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/main.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([]);
|
File diff suppressed because one or more lines are too long
|
@ -1,64 +0,0 @@
|
|||
function get_filetype_icon_class(filename) {
|
||||
var mapping = {
|
||||
// Image Files
|
||||
png: "fa-file-image",
|
||||
jpg: "fa-file-image",
|
||||
jpeg: "fa-file-image",
|
||||
gif: "fa-file-image",
|
||||
bmp: "fa-file-image",
|
||||
svg: "fa-file-image",
|
||||
|
||||
// Text Files
|
||||
txt: "fa-file-alt",
|
||||
|
||||
// Video Files
|
||||
mov: "fa-file-video",
|
||||
mp4: "fa-file-video",
|
||||
wmv: "fa-file-video",
|
||||
flv: "fa-file-video",
|
||||
mkv: "fa-file-video",
|
||||
avi: "fa-file-video",
|
||||
|
||||
// PDF Files
|
||||
pdf: "fa-file-pdf",
|
||||
|
||||
// Audio Files
|
||||
mp3: "fa-file-sound",
|
||||
wav: "fa-file-sound",
|
||||
aac: "fa-file-sound",
|
||||
|
||||
// Archive Files
|
||||
zip: "fa-file-archive",
|
||||
gz: "fa-file-archive",
|
||||
tar: "fa-file-archive",
|
||||
"7z": "fa-file-archive",
|
||||
rar: "fa-file-archive",
|
||||
|
||||
// Code Files
|
||||
py: "fa-file-code",
|
||||
c: "fa-file-code",
|
||||
cpp: "fa-file-code",
|
||||
html: "fa-file-code",
|
||||
js: "fa-file-code",
|
||||
rb: "fa-file-code",
|
||||
go: "fa-file-code"
|
||||
};
|
||||
|
||||
var ext = filename.split(".").pop();
|
||||
return mapping[ext];
|
||||
}
|
||||
|
||||
function get_page_files() {
|
||||
return CTFd.fetch("/api/v1/files?type=page", {
|
||||
credentials: "same-origin"
|
||||
}).then(function(response) {
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
|
||||
// .
|
||||
// then(function (data) {
|
||||
// data.map(function (f) {
|
||||
// console.log(f);
|
||||
// });
|
||||
// });
|
|
@ -0,0 +1,169 @@
|
|||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // install a JSONP callback for chunk loading
|
||||
/******/ function webpackJsonpCallback(data) {
|
||||
/******/ var chunkIds = data[0];
|
||||
/******/ var moreModules = data[1];
|
||||
/******/ var executeModules = data[2];
|
||||
/******/
|
||||
/******/ // add "moreModules" to the modules object,
|
||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||
/******/ var moduleId, chunkId, i = 0, resolves = [];
|
||||
/******/ for(;i < chunkIds.length; i++) {
|
||||
/******/ chunkId = chunkIds[i];
|
||||
/******/ if(installedChunks[chunkId]) {
|
||||
/******/ resolves.push(installedChunks[chunkId][0]);
|
||||
/******/ }
|
||||
/******/ installedChunks[chunkId] = 0;
|
||||
/******/ }
|
||||
/******/ for(moduleId in moreModules) {
|
||||
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
||||
/******/ modules[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
|
||||
/******/
|
||||
/******/ while(resolves.length) {
|
||||
/******/ resolves.shift()();
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // add entry modules from loaded chunk to deferred list
|
||||
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
|
||||
/******/
|
||||
/******/ // run deferred modules when all chunks ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ };
|
||||
/******/ function checkDeferredModules() {
|
||||
/******/ var result;
|
||||
/******/ for(var i = 0; i < deferredModules.length; i++) {
|
||||
/******/ var deferredModule = deferredModules[i];
|
||||
/******/ var fulfilled = true;
|
||||
/******/ for(var j = 1; j < deferredModule.length; j++) {
|
||||
/******/ var depId = deferredModule[j];
|
||||
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
|
||||
/******/ }
|
||||
/******/ if(fulfilled) {
|
||||
/******/ deferredModules.splice(i--, 1);
|
||||
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ return result;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // object to store loaded and loading chunks
|
||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||
/******/ // Promise = chunk loading, 0 = chunk loaded
|
||||
/******/ var installedChunks = {
|
||||
/******/ "pages/notifications": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ var deferredModules = [];
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = function(exports) {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "/themes/admin/static/js";
|
||||
/******/
|
||||
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
|
||||
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
|
||||
/******/ jsonpArray.push = webpackJsonpCallback;
|
||||
/******/ jsonpArray = jsonpArray.slice();
|
||||
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
|
||||
/******/ var parentJsonpFunction = oldJsonpFunction;
|
||||
/******/
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/notifications.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ "./CTFd/themes/admin/assets/js/pages/notifications.js":
|
||||
/*!************************************************************!*\
|
||||
!*** ./CTFd/themes/admin/assets/js/pages/notifications.js ***!
|
||||
\************************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\n__webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction submit(event) {\n event.preventDefault();\n var $form = (0, _jquery.default)(this);\n var params = $form.serializeJSON(); // Disable button after click\n\n $form.find(\"button[type=submit]\").attr(\"disabled\", true);\n\n _CTFd.default.api.post_notification_list({}, params).then(function (response) {\n // Admin should also see the notification sent out\n setTimeout(function () {\n $form.find(\"button[type=submit]\").attr(\"disabled\", false);\n }, 1000);\n\n if (!response.success) {\n (0, _ezq.ezAlert)({\n title: \"Error\",\n body: \"Could not send notification. Please try again.\",\n button: \"OK\"\n });\n }\n });\n}\n\nfunction deleteNotification(event) {\n event.preventDefault();\n var $elem = (0, _jquery.default)(this);\n var id = $elem.data(\"notif-id\");\n (0, _ezq.ezQuery)({\n title: \"Delete Notification\",\n body: \"Are you sure you want to delete this notification?\",\n success: function success() {\n _CTFd.default.api.delete_notification({\n notificationId: id\n }).then(function (response) {\n if (response.success) {\n $elem.parent().remove();\n }\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#notifications_form\").submit(submit);\n (0, _jquery.default)(\".delete-notification\").click(deleteNotification);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/notifications.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,169 @@
|
|||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // install a JSONP callback for chunk loading
|
||||
/******/ function webpackJsonpCallback(data) {
|
||||
/******/ var chunkIds = data[0];
|
||||
/******/ var moreModules = data[1];
|
||||
/******/ var executeModules = data[2];
|
||||
/******/
|
||||
/******/ // add "moreModules" to the modules object,
|
||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||
/******/ var moduleId, chunkId, i = 0, resolves = [];
|
||||
/******/ for(;i < chunkIds.length; i++) {
|
||||
/******/ chunkId = chunkIds[i];
|
||||
/******/ if(installedChunks[chunkId]) {
|
||||
/******/ resolves.push(installedChunks[chunkId][0]);
|
||||
/******/ }
|
||||
/******/ installedChunks[chunkId] = 0;
|
||||
/******/ }
|
||||
/******/ for(moduleId in moreModules) {
|
||||
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
||||
/******/ modules[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
|
||||
/******/
|
||||
/******/ while(resolves.length) {
|
||||
/******/ resolves.shift()();
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // add entry modules from loaded chunk to deferred list
|
||||
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
|
||||
/******/
|
||||
/******/ // run deferred modules when all chunks ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ };
|
||||
/******/ function checkDeferredModules() {
|
||||
/******/ var result;
|
||||
/******/ for(var i = 0; i < deferredModules.length; i++) {
|
||||
/******/ var deferredModule = deferredModules[i];
|
||||
/******/ var fulfilled = true;
|
||||
/******/ for(var j = 1; j < deferredModule.length; j++) {
|
||||
/******/ var depId = deferredModule[j];
|
||||
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
|
||||
/******/ }
|
||||
/******/ if(fulfilled) {
|
||||
/******/ deferredModules.splice(i--, 1);
|
||||
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ return result;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // object to store loaded and loading chunks
|
||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||
/******/ // Promise = chunk loading, 0 = chunk loaded
|
||||
/******/ var installedChunks = {
|
||||
/******/ "pages/pages": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ var deferredModules = [];
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = function(exports) {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "/themes/admin/static/js";
|
||||
/******/
|
||||
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
|
||||
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
|
||||
/******/ jsonpArray.push = webpackJsonpCallback;
|
||||
/******/ jsonpArray = jsonpArray.slice();
|
||||
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
|
||||
/******/ var parentJsonpFunction = oldJsonpFunction;
|
||||
/******/
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/pages.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ "./CTFd/themes/admin/assets/js/pages/pages.js":
|
||||
/*!****************************************************!*\
|
||||
!*** ./CTFd/themes/admin/assets/js/pages/pages.js ***!
|
||||
\****************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _utils = __webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction deletePage(event) {\n var elem = (0, _jquery.default)(this);\n var name = elem.attr(\"page-route\");\n var page_id = elem.attr(\"page-id\");\n (0, _ezq.ezQuery)({\n title: \"Delete \" + (0, _utils.htmlEntities)(name),\n body: \"Are you sure you want to delete {0}?\".format(\"<strong>\" + (0, _utils.htmlEntities)(name) + \"</strong>\"),\n success: function success() {\n _CTFd.default.fetch(\"/api/v1/pages/\" + page_id, {\n method: \"DELETE\"\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n if (response.success) {\n elem.parent().parent().remove();\n }\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".delete-page\").click(deletePage);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/pages.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
|
@ -1,29 +0,0 @@
|
|||
$(document).ready(function() {
|
||||
$(".delete-page").click(function() {
|
||||
var elem = $(this);
|
||||
var name = elem.attr("page-route");
|
||||
var page_id = elem.attr("page-id");
|
||||
ezq({
|
||||
title: "Delete " + name,
|
||||
body: "Are you sure you want to delete {0}?".format(
|
||||
"<strong>" + htmlentities(name) + "</strong>"
|
||||
),
|
||||
success: function() {
|
||||
CTFd.fetch("/api/v1/pages/" + page_id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
elem
|
||||
.parent()
|
||||
.parent()
|
||||
.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,169 @@
|
|||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // install a JSONP callback for chunk loading
|
||||
/******/ function webpackJsonpCallback(data) {
|
||||
/******/ var chunkIds = data[0];
|
||||
/******/ var moreModules = data[1];
|
||||
/******/ var executeModules = data[2];
|
||||
/******/
|
||||
/******/ // add "moreModules" to the modules object,
|
||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||
/******/ var moduleId, chunkId, i = 0, resolves = [];
|
||||
/******/ for(;i < chunkIds.length; i++) {
|
||||
/******/ chunkId = chunkIds[i];
|
||||
/******/ if(installedChunks[chunkId]) {
|
||||
/******/ resolves.push(installedChunks[chunkId][0]);
|
||||
/******/ }
|
||||
/******/ installedChunks[chunkId] = 0;
|
||||
/******/ }
|
||||
/******/ for(moduleId in moreModules) {
|
||||
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
||||
/******/ modules[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
|
||||
/******/
|
||||
/******/ while(resolves.length) {
|
||||
/******/ resolves.shift()();
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // add entry modules from loaded chunk to deferred list
|
||||
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
|
||||
/******/
|
||||
/******/ // run deferred modules when all chunks ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ };
|
||||
/******/ function checkDeferredModules() {
|
||||
/******/ var result;
|
||||
/******/ for(var i = 0; i < deferredModules.length; i++) {
|
||||
/******/ var deferredModule = deferredModules[i];
|
||||
/******/ var fulfilled = true;
|
||||
/******/ for(var j = 1; j < deferredModule.length; j++) {
|
||||
/******/ var depId = deferredModule[j];
|
||||
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
|
||||
/******/ }
|
||||
/******/ if(fulfilled) {
|
||||
/******/ deferredModules.splice(i--, 1);
|
||||
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ return result;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // object to store loaded and loading chunks
|
||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||
/******/ // Promise = chunk loading, 0 = chunk loaded
|
||||
/******/ var installedChunks = {
|
||||
/******/ "pages/reset": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ var deferredModules = [];
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = function(exports) {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "/themes/admin/static/js";
|
||||
/******/
|
||||
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
|
||||
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
|
||||
/******/ jsonpArray.push = webpackJsonpCallback;
|
||||
/******/ jsonpArray = jsonpArray.slice();
|
||||
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
|
||||
/******/ var parentJsonpFunction = oldJsonpFunction;
|
||||
/******/
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/reset.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ "./CTFd/themes/admin/assets/js/pages/reset.js":
|
||||
/*!****************************************************!*\
|
||||
!*** ./CTFd/themes/admin/assets/js/pages/reset.js ***!
|
||||
\****************************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction reset(event) {\n event.preventDefault();\n (0, _ezq.ezQuery)({\n title: \"Reset CTF?\",\n body: \"Are you sure you want to reset your CTFd instance?\",\n success: function success() {\n (0, _jquery.default)(\"#reset-ctf-form\").off(\"submit\").submit();\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#reset-ctf-form\").submit(reset);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/reset.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue