mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 05:54:19 +01:00
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 imported
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -72,4 +72,4 @@ CTFd/uploads
|
||||
*.zip
|
||||
|
||||
# JS
|
||||
node_modules/
|
||||
node_modules/
|
||||
|
||||
@@ -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 (
|
||||
|
||||
83
CTFd/api/v1/tokens.py
Normal file
83
CTFd/api/v1/tokens.py
Normal file
@@ -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")
|
||||
|
||||
6
CTFd/exceptions/__init__.py
Normal file
6
CTFd/exceptions/__init__.py
Normal file
@@ -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>
|
||||
|
||||
23
CTFd/schemas/tokens.py
Normal file
23
CTFd/schemas/tokens.py
Normal file
@@ -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()
|
||||
|
||||
68
CTFd/themes/admin/assets/css/admin.scss
Normal file
68
CTFd/themes/admin/assets/css/admin.scss
Normal file
@@ -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;
|
||||
}
|
||||
30
CTFd/themes/admin/assets/css/includes/sticky-footer.css
Normal file
30
CTFd/themes/admin/assets/css/includes/sticky-footer.css
Normal file
@@ -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;*/
|
||||
}
|
||||
233
CTFd/themes/admin/assets/js/challenges/challenge.js
Normal file
233
CTFd/themes/admin/assets/js/challenges/challenge.js
Normal file
@@ -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;
|
||||
});
|
||||
});
|
||||
42
CTFd/themes/admin/assets/js/challenges/files.js
Normal file
42
CTFd/themes/admin/assets/js/challenges/files.js
Normal file
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
137
CTFd/themes/admin/assets/js/challenges/flags.js
Normal file
137
CTFd/themes/admin/assets/js/challenges/flags.js
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
145
CTFd/themes/admin/assets/js/challenges/hints.js
Normal file
145
CTFd/themes/admin/assets/js/challenges/hints.js
Normal file
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
75
CTFd/themes/admin/assets/js/challenges/new.js
Normal file
75
CTFd/themes/admin/assets/js/challenges/new.js
Normal file
@@ -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);
|
||||
});
|
||||
69
CTFd/themes/admin/assets/js/challenges/requirements.js
Normal file
69
CTFd/themes/admin/assets/js/challenges/requirements.js
Normal file
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
42
CTFd/themes/admin/assets/js/challenges/tags.js
Normal file
42
CTFd/themes/admin/assets/js/challenges/tags.js
Normal file
@@ -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("");
|
||||
}
|
||||
490
CTFd/themes/admin/assets/js/pages/challenge.js
Normal file
490
CTFd/themes/admin/assets/js/pages/challenge.js
Normal file
@@ -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);
|
||||
});
|
||||
298
CTFd/themes/admin/assets/js/pages/configs.js
Normal file
298
CTFd/themes/admin/assets/js/pages/configs.js
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
7
CTFd/themes/admin/assets/js/pages/events.js
Normal file
7
CTFd/themes/admin/assets/js/pages/events.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import events from "core/events";
|
||||
import CTFd from "core/CTFd";
|
||||
|
||||
$(() => {
|
||||
events(CTFd.config.urlRoot);
|
||||
});
|
||||
16
CTFd/themes/admin/assets/js/pages/main.js
Normal file
16
CTFd/themes/admin/assets/js/pages/main.js
Normal file
@@ -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);
|
||||
});
|
||||
51
CTFd/themes/admin/assets/js/pages/notifications.js
Normal file
51
CTFd/themes/admin/assets/js/pages/notifications.js
Normal file
@@ -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);
|
||||
});
|
||||
37
CTFd/themes/admin/assets/js/pages/pages.js
Normal file
37
CTFd/themes/admin/assets/js/pages/pages.js
Normal file
@@ -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);
|
||||
});
|
||||
20
CTFd/themes/admin/assets/js/pages/reset.js
Normal file
20
CTFd/themes/admin/assets/js/pages/reset.js
Normal file
@@ -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);
|
||||
});
|
||||
42
CTFd/themes/admin/assets/js/pages/scoreboard.js
Normal file
42
CTFd/themes/admin/assets/js/pages/scoreboard.js
Normal file
@@ -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);
|
||||
});
|
||||
222
CTFd/themes/admin/assets/js/pages/statistics.js
Normal file
222
CTFd/themes/admin/assets/js/pages/statistics.js
Normal file
@@ -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);
|
||||
});
|
||||
8
CTFd/themes/admin/assets/js/pages/style.js
Normal file
8
CTFd/themes/admin/assets/js/pages/style.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import $ from "jquery";
|
||||
import styles from "../styles";
|
||||
import times from "core/times";
|
||||
|
||||
$(() => {
|
||||
styles();
|
||||
times();
|
||||
});
|
||||
45
CTFd/themes/admin/assets/js/pages/submissions.js
Normal file
45
CTFd/themes/admin/assets/js/pages/submissions.js
Normal file
@@ -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);
|
||||
});
|
||||
422
CTFd/themes/admin/assets/js/pages/team.js
Normal file
422
CTFd/themes/admin/assets/js/pages/team.js
Normal file
@@ -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);
|
||||
});
|
||||
440
CTFd/themes/admin/assets/js/pages/user.js
Normal file
440
CTFd/themes/admin/assets/js/pages/user.js
Normal file
@@ -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);
|
||||
});
|
||||
1
CTFd/themes/admin/assets/js/pages/users.js
Normal file
1
CTFd/themes/admin/assets/js/pages/users.js
Normal file
@@ -0,0 +1 @@
|
||||
import "./main";
|
||||
51
CTFd/themes/admin/assets/js/styles.js
Normal file
51
CTFd/themes/admin/assets/js/styles.js
Normal file
@@ -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();
|
||||
});
|
||||
};
|
||||
4
CTFd/themes/admin/static/css/admin.dev.css
Normal file
4
CTFd/themes/admin/static/css/admin.dev.css
Normal file
@@ -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}
|
||||
|
||||
1
CTFd/themes/admin/static/css/admin.min.css
vendored
Normal file
1
CTFd/themes/admin/static/css/admin.min.css
vendored
Normal file
@@ -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;
|
||||
}
|
||||
2
CTFd/themes/admin/static/css/challenge-board.dev.css
Normal file
2
CTFd/themes/admin/static/css/challenge-board.dev.css
Normal file
@@ -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}
|
||||
|
||||
1
CTFd/themes/admin/static/css/challenge-board.min.css
vendored
Normal file
1
CTFd/themes/admin/static/css/challenge-board.min.css
vendored
Normal file
@@ -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();
|
||||
});
|
||||
146
CTFd/themes/admin/static/js/core.dev.js
Normal file
146
CTFd/themes/admin/static/js/core.dev.js
Normal file
File diff suppressed because one or more lines are too long
0
CTFd/themes/admin/static/js/core.min.js
vendored
Normal file
0
CTFd/themes/admin/static/js/core.min.js
vendored
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
15
CTFd/themes/admin/static/js/graphs.dev.js
Normal file
15
CTFd/themes/admin/static/js/graphs.dev.js
Normal file
File diff suppressed because one or more lines are too long
0
CTFd/themes/admin/static/js/graphs.min.js
vendored
Normal file
0
CTFd/themes/admin/static/js/graphs.min.js
vendored
Normal file
63
CTFd/themes/admin/static/js/helpers.dev.js
Normal file
63
CTFd/themes/admin/static/js/helpers.dev.js
Normal file
@@ -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?");
|
||||
|
||||
/***/ })
|
||||
|
||||
}]);
|
||||
1
CTFd/themes/admin/static/js/helpers.min.js
vendored
Normal file
1
CTFd/themes/admin/static/js/helpers.min.js
vendored
Normal file
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
229
CTFd/themes/admin/static/js/pages/challenge.dev.js
Normal file
229
CTFd/themes/admin/static/js/pages/challenge.dev.js
Normal file
File diff suppressed because one or more lines are too long
1
CTFd/themes/admin/static/js/pages/challenge.min.js
vendored
Normal file
1
CTFd/themes/admin/static/js/pages/challenge.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
169
CTFd/themes/admin/static/js/pages/configs.dev.js
Normal file
169
CTFd/themes/admin/static/js/pages/configs.dev.js
Normal file
File diff suppressed because one or more lines are too long
1
CTFd/themes/admin/static/js/pages/configs.min.js
vendored
Normal file
1
CTFd/themes/admin/static/js/pages/configs.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
169
CTFd/themes/admin/static/js/pages/editor.dev.js
Normal file
169
CTFd/themes/admin/static/js/pages/editor.dev.js
Normal file
File diff suppressed because one or more lines are too long
1
CTFd/themes/admin/static/js/pages/editor.min.js
vendored
Normal file
1
CTFd/themes/admin/static/js/pages/editor.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
155
CTFd/themes/admin/static/js/pages/main.dev.js
Normal file
155
CTFd/themes/admin/static/js/pages/main.dev.js
Normal file
@@ -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();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([]);
|
||||
1
CTFd/themes/admin/static/js/pages/main.min.js
vendored
Normal file
1
CTFd/themes/admin/static/js/pages/main.min.js
vendored
Normal file
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);
|
||||
// });
|
||||
// });
|
||||
169
CTFd/themes/admin/static/js/pages/notifications.dev.js
Normal file
169
CTFd/themes/admin/static/js/pages/notifications.dev.js
Normal file
@@ -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?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
||||
1
CTFd/themes/admin/static/js/pages/notifications.min.js
vendored
Normal file
1
CTFd/themes/admin/static/js/pages/notifications.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
169
CTFd/themes/admin/static/js/pages/pages.dev.js
Normal file
169
CTFd/themes/admin/static/js/pages/pages.dev.js
Normal file
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
1
CTFd/themes/admin/static/js/pages/pages.min.js
vendored
Normal file
1
CTFd/themes/admin/static/js/pages/pages.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
169
CTFd/themes/admin/static/js/pages/reset.dev.js
Normal file
169
CTFd/themes/admin/static/js/pages/reset.dev.js
Normal file
@@ -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?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user