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
|
*.zip
|
||||||
|
|
||||||
# JS
|
# JS
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ before_install:
|
|||||||
- python3.6 -m pip install black==19.3b0
|
- python3.6 -m pip install black==19.3b0
|
||||||
install:
|
install:
|
||||||
- pip install -r development.txt
|
- pip install -r development.txt
|
||||||
|
- yarn install --non-interactive
|
||||||
- yarn global add prettier@1.17.0
|
- yarn global add prettier@1.17.0
|
||||||
before_script:
|
before_script:
|
||||||
- psql -c 'create database ctfd;' -U postgres
|
- 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
|
2.1.5 / 2019-10-2
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,16 @@ from CTFd.utils.initialization import (
|
|||||||
init_logs,
|
init_logs,
|
||||||
init_events,
|
init_events,
|
||||||
)
|
)
|
||||||
|
from CTFd.utils.crypto import sha256
|
||||||
from CTFd.plugins import init_plugins
|
from CTFd.plugins import init_plugins
|
||||||
|
import datetime
|
||||||
|
|
||||||
# Hack to support Unicode in Python 2 properly
|
# Hack to support Unicode in Python 2 properly
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
reload(sys) # noqa: F821
|
reload(sys) # noqa: F821
|
||||||
sys.setdefaultencoding("utf-8")
|
sys.setdefaultencoding("utf-8")
|
||||||
|
|
||||||
__version__ = "2.1.5"
|
__version__ = "2.2.0"
|
||||||
|
|
||||||
|
|
||||||
class CTFdRequest(Request):
|
class CTFdRequest(Request):
|
||||||
@@ -50,6 +52,12 @@ class CTFdFlask(Flask):
|
|||||||
self.jinja_environment = SandboxedBaseEnvironment
|
self.jinja_environment = SandboxedBaseEnvironment
|
||||||
self.session_interface = CachingSessionInterface(key_prefix="session")
|
self.session_interface = CachingSessionInterface(key_prefix="session")
|
||||||
self.request_class = CTFdRequest
|
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)
|
Flask.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
def create_jinja_environment(self):
|
def create_jinja_environment(self):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from flask import render_template
|
|||||||
from CTFd.utils.decorators import admins_only
|
from CTFd.utils.decorators import admins_only
|
||||||
from CTFd.utils.updates import update_check
|
from CTFd.utils.updates import update_check
|
||||||
from CTFd.utils.modes import get_model
|
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
|
from CTFd.admin import admin
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +13,8 @@ def statistics():
|
|||||||
|
|
||||||
Model = get_model()
|
Model = get_model()
|
||||||
|
|
||||||
teams_registered = Model.query.count()
|
teams_registered = Teams.query.count()
|
||||||
|
users_registered = Users.query.count()
|
||||||
|
|
||||||
wrong_count = (
|
wrong_count = (
|
||||||
Fails.query.join(Model, Fails.account_id == Model.id)
|
Fails.query.join(Model, Fails.account_id == Model.id)
|
||||||
@@ -65,6 +66,7 @@ def statistics():
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"admin/statistics.html",
|
"admin/statistics.html",
|
||||||
|
user_count=users_registered,
|
||||||
team_count=teams_registered,
|
team_count=teams_registered,
|
||||||
ip_count=ip_count,
|
ip_count=ip_count,
|
||||||
wrong_count=wrong_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.notifications import notifications_namespace
|
||||||
from CTFd.api.v1.pages import pages_namespace
|
from CTFd.api.v1.pages import pages_namespace
|
||||||
from CTFd.api.v1.unlocks import unlocks_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")
|
api = Blueprint("api", __name__, url_prefix="/api/v1")
|
||||||
CTFd_API_v1 = Api(api, version="v1", doc=current_app.config.get("SWAGGER_UI"))
|
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(configs_namespace, "/configs")
|
||||||
CTFd_API_v1.add_namespace(pages_namespace, "/pages")
|
CTFd_API_v1.add_namespace(pages_namespace, "/pages")
|
||||||
CTFd_API_v1.add_namespace(unlocks_namespace, "/unlocks")
|
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()
|
Model = get_model()
|
||||||
|
|
||||||
if scores_visible() is True and accounts_visible() is True:
|
if scores_visible() is True and accounts_visible() is True:
|
||||||
solves = (
|
solves = Solves.query.join(Model, Solves.account_id == Model.id).filter(
|
||||||
Solves.query.join(Model, Solves.account_id == Model.id)
|
Solves.challenge_id == chal.id,
|
||||||
.filter(
|
Model.banned == False,
|
||||||
Solves.challenge_id == chal.id,
|
Model.hidden == False,
|
||||||
Model.banned == False,
|
|
||||||
Model.hidden == False,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only show solves that happened before freeze time if configured
|
# Only show solves that happened before freeze time if configured
|
||||||
@@ -391,8 +388,9 @@ class ChallengeAttempt(Resource):
|
|||||||
)
|
)
|
||||||
log(
|
log(
|
||||||
"submissions",
|
"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"),
|
submission=request_data["submission"].encode("utf-8"),
|
||||||
|
challenge_id=challenge_id,
|
||||||
kpm=kpm,
|
kpm=kpm,
|
||||||
)
|
)
|
||||||
# Submitting too fast
|
# Submitting too fast
|
||||||
@@ -437,8 +435,9 @@ class ChallengeAttempt(Resource):
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
"submissions",
|
"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"),
|
submission=request_data["submission"].encode("utf-8"),
|
||||||
|
challenge_id=challenge_id,
|
||||||
kpm=kpm,
|
kpm=kpm,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
@@ -454,8 +453,9 @@ class ChallengeAttempt(Resource):
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
"submissions",
|
"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"),
|
submission=request_data["submission"].encode("utf-8"),
|
||||||
|
challenge_id=challenge_id,
|
||||||
kpm=kpm,
|
kpm=kpm,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -487,8 +487,9 @@ class ChallengeAttempt(Resource):
|
|||||||
else:
|
else:
|
||||||
log(
|
log(
|
||||||
"submissions",
|
"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"),
|
submission=request_data["submission"].encode("utf-8"),
|
||||||
|
challenge_id=challenge_id,
|
||||||
kpm=kpm,
|
kpm=kpm,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ class NotificantionList(Resource):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
response = schema.dump(result.data)
|
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")
|
current_app.events_manager.publish(data=response.data, type="notification")
|
||||||
|
|
||||||
return {"success": True, "data": response.data}
|
return {"success": True, "data": response.data}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from flask import session, request, abort
|
from flask import session, request, abort
|
||||||
from flask_restplus import Namespace, Resource
|
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.teams import TeamSchema
|
||||||
from CTFd.schemas.submissions import SubmissionSchema
|
from CTFd.schemas.submissions import SubmissionSchema
|
||||||
from CTFd.schemas.awards import AwardSchema
|
from CTFd.schemas.awards import AwardSchema
|
||||||
@@ -68,6 +68,8 @@ class TeamPublic(Resource):
|
|||||||
if response.errors:
|
if response.errors:
|
||||||
return {"success": False, "errors": response.errors}, 400
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|
||||||
|
response.data["place"] = team.place
|
||||||
|
response.data["score"] = team.score
|
||||||
return {"success": True, "data": response.data}
|
return {"success": True, "data": response.data}
|
||||||
|
|
||||||
@admins_only
|
@admins_only
|
||||||
@@ -118,6 +120,8 @@ class TeamPrivate(Resource):
|
|||||||
if response.errors:
|
if response.errors:
|
||||||
return {"success": False, "errors": response.errors}, 400
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|
||||||
|
response.data["place"] = team.place
|
||||||
|
response.data["score"] = team.score
|
||||||
return {"success": True, "data": response.data}
|
return {"success": True, "data": response.data}
|
||||||
|
|
||||||
@authed_only
|
@authed_only
|
||||||
@@ -206,6 +210,12 @@ class TeamMembers(Resource):
|
|||||||
|
|
||||||
if user.team_id == team.id:
|
if user.team_id == team.id:
|
||||||
team.members.remove(user)
|
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()
|
db.session.commit()
|
||||||
else:
|
else:
|
||||||
return (
|
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.add(team)
|
||||||
db.session.commit()
|
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)
|
team.members.append(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|||||||
@@ -228,6 +228,9 @@ class Config(object):
|
|||||||
APPLICATION_ROOT:
|
APPLICATION_ROOT:
|
||||||
Specifies what path CTFd is mounted under. It can be used to run CTFd in a subdirectory.
|
Specifies what path CTFd is mounted under. It can be used to run CTFd in a subdirectory.
|
||||||
Example: /ctfd
|
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
|
REVERSE_PROXY = os.getenv("REVERSE_PROXY") or False
|
||||||
TEMPLATES_AUTO_RELOAD = not os.getenv("TEMPLATES_AUTO_RELOAD") # Defaults True
|
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
|
SWAGGER_UI = "/" if os.getenv("SWAGGER_UI") is not None else False # Defaults False
|
||||||
UPDATE_CHECK = not os.getenv("UPDATE_CHECK") # Defaults True
|
UPDATE_CHECK = not os.getenv("UPDATE_CHECK") # Defaults True
|
||||||
APPLICATION_ROOT = os.getenv("APPLICATION_ROOT") or "/"
|
APPLICATION_ROOT = os.getenv("APPLICATION_ROOT") or "/"
|
||||||
|
SERVER_SENT_EVENTS = not os.getenv("SERVER_SENT_EVENTS") # Defaults True
|
||||||
|
|
||||||
"""
|
"""
|
||||||
=== OAUTH ===
|
=== OAUTH ===
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from flask import current_app, Blueprint, Response, stream_with_context
|
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
|
from CTFd.utils.decorators import authed_only, ratelimit
|
||||||
|
|
||||||
events = Blueprint("events", __name__)
|
events = Blueprint("events", __name__)
|
||||||
@@ -13,4 +14,8 @@ def subscribe():
|
|||||||
for event in current_app.events_manager.subscribe():
|
for event in current_app.events_manager.subscribe():
|
||||||
yield str(event)
|
yield str(event)
|
||||||
|
|
||||||
|
enabled = get_app_config("SERVER_SENT_EVENTS")
|
||||||
|
if enabled is False:
|
||||||
|
return ("", 204)
|
||||||
|
|
||||||
return Response(gen(), mimetype="text/event-stream")
|
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
|
@property
|
||||||
def place(self):
|
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):
|
def get_solves(self, admin=False):
|
||||||
solves = Solves.query.filter_by(user_id=self.id)
|
solves = Solves.query.filter_by(user_id=self.id)
|
||||||
@@ -417,7 +422,12 @@ class Teams(db.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def place(self):
|
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):
|
def get_solves(self, admin=False):
|
||||||
member_ids = [member.id for member in self.members]
|
member_ids = [member.id for member in self.members]
|
||||||
@@ -631,6 +641,33 @@ class Configs(db.Model):
|
|||||||
super(Configs, self).__init__(**kwargs)
|
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()
|
@cache.memoize()
|
||||||
def get_config(key):
|
def get_config(key):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from CTFd.utils.config.pages import get_pages
|
|||||||
Menu = namedtuple("Menu", ["title", "route"])
|
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
|
Registers a directory to serve assets
|
||||||
|
|
||||||
@@ -28,15 +28,17 @@ def register_plugin_assets_directory(app, base_path, admins_only=False):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
base_path = base_path.strip("/")
|
base_path = base_path.strip("/")
|
||||||
|
if endpoint is None:
|
||||||
|
endpoint = base_path.replace("/", ".")
|
||||||
|
|
||||||
def assets_handler(path):
|
def assets_handler(path):
|
||||||
return send_from_directory(base_path, path)
|
return send_from_directory(base_path, path)
|
||||||
|
|
||||||
rule = "/" + base_path + "/<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
|
Registers an file path to be served by CTFd
|
||||||
|
|
||||||
@@ -46,6 +48,8 @@ def register_plugin_asset(app, asset_path, admins_only=False):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
asset_path = asset_path.strip("/")
|
asset_path = asset_path.strip("/")
|
||||||
|
if endpoint is None:
|
||||||
|
endpoint = asset_path.replace("/", ".")
|
||||||
|
|
||||||
def asset_handler():
|
def asset_handler():
|
||||||
return send_file(asset_path)
|
return send_file(asset_path)
|
||||||
@@ -53,7 +57,7 @@ def register_plugin_asset(app, asset_path, admins_only=False):
|
|||||||
if admins_only:
|
if admins_only:
|
||||||
asset_handler = admins_only_wrapper(asset_handler)
|
asset_handler = admins_only_wrapper(asset_handler)
|
||||||
rule = "/" + asset_path
|
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):
|
def override_template(*args, **kwargs):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<form method="POST" action="{{ script_root }}/admin/challenges/new" enctype="multipart/form-data">
|
<form method="POST" action="{{ script_root }}/admin/challenges/new" enctype="multipart/form-data">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>
|
<label>
|
||||||
Name<br>
|
Name:<br>
|
||||||
<small class="form-text text-muted">
|
<small class="form-text text-muted">
|
||||||
The name of your challenge
|
The name of your challenge
|
||||||
</small>
|
</small>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>
|
<label>
|
||||||
Category<br>
|
Category:<br>
|
||||||
<small class="form-text text-muted">
|
<small class="form-text text-muted">
|
||||||
The category of your challenge
|
The category of your challenge
|
||||||
</small>
|
</small>
|
||||||
@@ -22,10 +22,10 @@
|
|||||||
<ul class="nav nav-tabs" role="tablist" id="new-desc-edit">
|
<ul class="nav nav-tabs" role="tablist" id="new-desc-edit">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="#new-desc-write" aria-controls="home" role="tab"
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>
|
<label>
|
||||||
Value<br>
|
Value:<br>
|
||||||
<small class="form-text text-muted">
|
<small class="form-text text-muted">
|
||||||
This is how many points are rewarded for solving this challenge.
|
This is how many points are rewarded for solving this challenge.
|
||||||
</small>
|
</small>
|
||||||
|
|||||||
@@ -1,29 +1,39 @@
|
|||||||
// Markdown Preview
|
CTFd.plugin.run((_CTFd) => {
|
||||||
$('#desc-edit').on('shown.bs.tab', function (event) {
|
const $ = _CTFd.lib.$
|
||||||
if (event.target.hash == '#desc-preview') {
|
const md = _CTFd.lib.markdown()
|
||||||
var editor_value = $('#desc-editor').val();
|
$('a[href="#new-desc-preview"]').on('shown.bs.tab', function (event) {
|
||||||
$(event.target.hash).html(
|
if (event.target.hash == '#new-desc-preview') {
|
||||||
window.challenge.render(editor_value)
|
var editor_value = $('#new-desc-editor').val();
|
||||||
);
|
$(event.target.hash).html(
|
||||||
}
|
md.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();
|
// $('#desc-edit').on('shown.bs.tab', function (event) {
|
||||||
$(event.target.hash).html(
|
// if (event.target.hash == '#desc-preview') {
|
||||||
window.challenge.render(editor_value)
|
// var editor_value = $('#desc-editor').val();
|
||||||
);
|
// $(event.target.hash).html(
|
||||||
}
|
// window.challenge.render(editor_value)
|
||||||
});
|
// );
|
||||||
$("#solve-attempts-checkbox").change(function () {
|
// }
|
||||||
if (this.checked) {
|
// });
|
||||||
$('#solve-attempts-input').show();
|
// $('#new-desc-edit').on('shown.bs.tab', function (event) {
|
||||||
} else {
|
// if (event.target.hash == '#new-desc-preview') {
|
||||||
$('#solve-attempts-input').hide();
|
// var editor_value = $('#new-desc-editor').val();
|
||||||
$('#max_attempts').val('');
|
// $(event.target.hash).html(
|
||||||
}
|
// window.challenge.render(editor_value)
|
||||||
});
|
// );
|
||||||
|
// }
|
||||||
$(document).ready(function () {
|
// });
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
// $("#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>
|
<small class="form-text text-muted">Changes the state of the challenge (e.g. visible, hidden)</small>
|
||||||
</label>
|
</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="visible" {% if challenge.state == "visible" %}selected{% endif %}>Visible</option>
|
||||||
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
|
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -31,8 +31,7 @@
|
|||||||
<div class="challenge-hints hint-row row">
|
<div class="challenge-hints hint-row row">
|
||||||
{% for hint in hints %}
|
{% for hint in hints %}
|
||||||
<div class='col-md-12 hint-button-wrapper text-center mb-3'>
|
<div class='col-md-12 hint-button-wrapper text-center mb-3'>
|
||||||
<a class="btn btn-info btn-hint btn-block" href="javascript:;"
|
<a class="btn btn-info btn-hint btn-block load-hint" href="javascript:;" data-hint-id="{{ hint.id }}">
|
||||||
onclick="javascript:loadhint({{ hint.id }})">
|
|
||||||
{% if hint.content %}
|
{% if hint.content %}
|
||||||
<small>
|
<small>
|
||||||
View Hint
|
View Hint
|
||||||
|
|||||||
@@ -1,57 +1,40 @@
|
|||||||
window.challenge.data = undefined;
|
CTFd._internal.challenge.data = undefined
|
||||||
|
|
||||||
window.challenge.renderer = new markdownit({
|
CTFd._internal.challenge.renderer = CTFd.lib.markdown();
|
||||||
html: true,
|
|
||||||
linkify: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.challenge.preRender = function () {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
window.challenge.render = function (markdown) {
|
|
||||||
return window.challenge.renderer.render(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) {
|
CTFd._internal.challenge.postRender = function () { }
|
||||||
var challenge_id = parseInt($('#challenge-id').val());
|
|
||||||
var submission = $('#submission-input').val();
|
|
||||||
var url = "/api/v1/challenges/attempt";
|
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
if (preview) {
|
||||||
url += "?preview=true";
|
params['preview'] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var params = {
|
return CTFd.api.post_challenge_attempt(params, body).then(function (response) {
|
||||||
'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) {
|
|
||||||
if (response.status === 429) {
|
if (response.status === 429) {
|
||||||
// User was ratelimited but process response
|
// User was ratelimited but process response
|
||||||
return response.json();
|
return response
|
||||||
}
|
}
|
||||||
if (response.status === 403) {
|
if (response.status === 403) {
|
||||||
// User is not logged in or CTF is paused.
|
// User is not logged in or CTF is paused.
|
||||||
return response.json();
|
return response
|
||||||
}
|
}
|
||||||
return response.json();
|
return response
|
||||||
}).then(function (response) {
|
})
|
||||||
cb(response);
|
};
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -42,6 +42,42 @@ class DynamicValueChallenge(BaseChallenge):
|
|||||||
static_folder="assets",
|
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
|
@staticmethod
|
||||||
def create(request):
|
def create(request):
|
||||||
"""
|
"""
|
||||||
@@ -106,34 +142,7 @@ class DynamicValueChallenge(BaseChallenge):
|
|||||||
value = float(value)
|
value = float(value)
|
||||||
setattr(challenge, attr, value)
|
setattr(challenge, attr, value)
|
||||||
|
|
||||||
Model = get_model()
|
return DynamicValueChallenge.calculate_value(challenge)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(challenge):
|
def delete(challenge):
|
||||||
@@ -185,12 +194,10 @@ class DynamicValueChallenge(BaseChallenge):
|
|||||||
:param request: The request the user submitted
|
:param request: The request the user submitted
|
||||||
:return:
|
: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()
|
data = request.form or request.get_json()
|
||||||
submission = data["submission"].strip()
|
submission = data["submission"].strip()
|
||||||
|
|
||||||
Model = get_model()
|
|
||||||
|
|
||||||
solve = Solves(
|
solve = Solves(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
team_id=team.id if team else None,
|
team_id=team.id if team else None,
|
||||||
@@ -199,35 +206,9 @@ class DynamicValueChallenge(BaseChallenge):
|
|||||||
provided=submission,
|
provided=submission,
|
||||||
)
|
)
|
||||||
db.session.add(solve)
|
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.commit()
|
||||||
db.session.close()
|
|
||||||
|
DynamicValueChallenge.calculate_value(challenge)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fail(user, team, challenge, request):
|
def fail(user, team, challenge, request):
|
||||||
|
|||||||
@@ -16,34 +16,13 @@
|
|||||||
<input type="text" class="form-control chal-category" name="category" value="{{ challenge.category }}">
|
<input type="text" class="form-control chal-category" name="category" value="{{ challenge.category }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav nav-tabs" role="tablist" id="desc-edit">
|
<div class="form-group">
|
||||||
<li class="nav-item">
|
<label for="message-text" class="control-label">Message<br>
|
||||||
<a class="nav-link active" href="#desc-write" id="desc-write-link" aria-controls="home"
|
<small class="form-text text-muted">
|
||||||
role="tab" data-toggle="tab">
|
Use this to give a brief introduction to your challenge.
|
||||||
Write
|
</small>
|
||||||
</a>
|
</label>
|
||||||
</li>
|
<textarea id="desc-editor" class="form-control chal-desc-editor" name="description" rows="10">{{ challenge.description }}</textarea>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<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>
|
<small class="form-text text-muted">Changes the state of the challenge (e.g. visible, hidden)</small>
|
||||||
</label>
|
</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="visible" {% if challenge.state == "visible" %}selected{% endif %}>Visible</option>
|
||||||
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
|
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
|
||||||
</select>
|
</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({
|
CTFd._internal.challenge.renderer = CTFd.lib.markdown();
|
||||||
html: true,
|
|
||||||
linkify: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.challenge.preRender = function () {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
window.challenge.render = function (markdown) {
|
|
||||||
return window.challenge.renderer.render(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) {
|
CTFd._internal.challenge.postRender = function () { }
|
||||||
var challenge_id = parseInt($('#challenge-id').val());
|
|
||||||
var submission = $('#submission-input').val();
|
|
||||||
var url = "/api/v1/challenges/attempt";
|
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
if (preview) {
|
||||||
url += "?preview=true";
|
params['preview'] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var params = {
|
return CTFd.api.post_challenge_attempt(params, body).then(function (response) {
|
||||||
'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) {
|
|
||||||
if (response.status === 429) {
|
if (response.status === 429) {
|
||||||
// User was ratelimited but process response
|
// User was ratelimited but process response
|
||||||
return response.json();
|
return response
|
||||||
}
|
}
|
||||||
if (response.status === 403) {
|
if (response.status === 403) {
|
||||||
// User is not logged in or CTF is paused.
|
// User is not logged in or CTF is paused.
|
||||||
return response.json();
|
return response
|
||||||
}
|
}
|
||||||
return response.json();
|
return response
|
||||||
}).then(function (response) {
|
})
|
||||||
cb(response);
|
};
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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 Sensitive</option>
|
||||||
<option value="case_insensitive">Case Insensitive</option>
|
<option value="case_insensitive">Case Insensitive</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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 Sensitive</option>
|
||||||
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
|
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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 Sensitive</option>
|
||||||
<option value="case_insensitive">Case Insensitive</option>
|
<option value="case_insensitive">Case Insensitive</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<input type="text" class="form-control" name="content" value="{{ content }}">
|
<input type="text" class="form-control" name="content" value="{{ content }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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 Sensitive</option>
|
||||||
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
|
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
|
||||||
</select>
|
</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.models import db, Teams
|
||||||
from CTFd.utils.decorators import authed_only, ratelimit
|
from CTFd.utils.decorators import authed_only, ratelimit
|
||||||
from CTFd.utils.decorators.modes import require_team_mode
|
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.user import get_current_user
|
||||||
from CTFd.utils.crypto import verify_password
|
from CTFd.utils.crypto import verify_password
|
||||||
from CTFd.utils.decorators.visibility import (
|
from CTFd.utils.decorators.visibility import (
|
||||||
check_account_visibility,
|
check_account_visibility,
|
||||||
check_score_visibility,
|
check_score_visibility,
|
||||||
)
|
)
|
||||||
from CTFd.utils.helpers import get_errors
|
from CTFd.utils.helpers import get_errors, get_infos
|
||||||
|
|
||||||
teams = Blueprint("teams", __name__)
|
teams = Blueprint("teams", __name__)
|
||||||
|
|
||||||
@@ -44,14 +44,35 @@ def listing():
|
|||||||
@require_team_mode
|
@require_team_mode
|
||||||
@ratelimit(method="POST", limit=10, interval=5)
|
@ratelimit(method="POST", limit=10, interval=5)
|
||||||
def join():
|
def join():
|
||||||
|
infos = get_infos()
|
||||||
|
errors = get_errors()
|
||||||
if request.method == "GET":
|
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":
|
if request.method == "POST":
|
||||||
teamname = request.form.get("name")
|
teamname = request.form.get("name")
|
||||||
passphrase = request.form.get("password", "").strip()
|
passphrase = request.form.get("password", "").strip()
|
||||||
|
|
||||||
team = Teams.query.filter_by(name=teamname).first()
|
team = Teams.query.filter_by(name=teamname).first()
|
||||||
user = get_current_user()
|
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):
|
if team and verify_password(passphrase, team.password):
|
||||||
user.team_id = team.id
|
user.team_id = team.id
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -62,16 +83,27 @@ def join():
|
|||||||
|
|
||||||
return redirect(url_for("challenges.listing"))
|
return redirect(url_for("challenges.listing"))
|
||||||
else:
|
else:
|
||||||
errors = ["That information is incorrect"]
|
errors.append("That information is incorrect")
|
||||||
return render_template("teams/join_team.html", errors=errors)
|
return render_template("teams/join_team.html", infos=infos, errors=errors)
|
||||||
|
|
||||||
|
|
||||||
@teams.route("/teams/new", methods=["GET", "POST"])
|
@teams.route("/teams/new", methods=["GET", "POST"])
|
||||||
@authed_only
|
@authed_only
|
||||||
@require_team_mode
|
@require_team_mode
|
||||||
def new():
|
def new():
|
||||||
|
infos = get_infos()
|
||||||
|
errors = get_errors()
|
||||||
if request.method == "GET":
|
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":
|
elif request.method == "POST":
|
||||||
teamname = request.form.get("name")
|
teamname = request.form.get("name")
|
||||||
passphrase = request.form.get("password", "").strip()
|
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-weight: 400;
|
||||||
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
-webkit-appearance: none;
|
-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(
|
import "./main";
|
||||||
document.getElementById("admin-pages-editor"),
|
import "core/utils";
|
||||||
{
|
import $ from "jquery";
|
||||||
lineNumbers: true,
|
import CTFd from "core/CTFd";
|
||||||
lineWrapping: true,
|
import { default as helpers } from "core/helpers";
|
||||||
mode: "xml",
|
import CodeMirror from "codemirror";
|
||||||
htmlMode: true
|
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) {
|
function show_files(data) {
|
||||||
var list = $("#media-library-list");
|
var list = $("#media-library-list");
|
||||||
@@ -71,7 +127,7 @@ function show_files(data) {
|
|||||||
$("#media-item").show();
|
$("#media-item").show();
|
||||||
});
|
});
|
||||||
wrapper.append(link);
|
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-id", f.id);
|
||||||
wrapper.attr("data-filename", fname);
|
wrapper.attr("data-filename", fname);
|
||||||
list.append(wrapper);
|
list.append(wrapper);
|
||||||
@@ -95,7 +151,8 @@ function insert_at_cursor(editor, text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function submit_form() {
|
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 params = $("#page-edit").serializeJSON();
|
||||||
var target = "/api/v1/pages";
|
var target = "/api/v1/pages";
|
||||||
var method = "POST";
|
var method = "POST";
|
||||||
@@ -119,31 +176,41 @@ function submit_form() {
|
|||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
if (method === "PATCH" && response.success) {
|
if (method === "PATCH" && response.success) {
|
||||||
ezal({
|
ezToast({
|
||||||
title: "Saved",
|
title: "Saved",
|
||||||
body: "Your changes have been saved",
|
body: "Your changes have been saved"
|
||||||
button: "Okay"
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.location = script_root + "/admin/pages/" + response.data.id;
|
window.location =
|
||||||
|
CTFd.config.urlRoot + "/admin/pages/" + response.data.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function preview_page() {
|
function preview_page() {
|
||||||
editor.save(); // Save the CodeMirror data to the Textarea
|
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").attr("target", "_blank");
|
||||||
$("#page-edit").submit();
|
$("#page-edit").submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function upload_media() {
|
function upload_media() {
|
||||||
upload_files($("#media-library-upload"), function(data) {
|
helpers.files.upload($("#media-library-upload"), function(data) {
|
||||||
refresh_files();
|
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) {
|
$("#media-insert").click(function(e) {
|
||||||
var tag = "";
|
var tag = "";
|
||||||
try {
|
try {
|
||||||
@@ -171,7 +238,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
$("#media-delete").click(function(e) {
|
$("#media-delete").click(function(e) {
|
||||||
var file_id = $(this).attr("data-id");
|
var file_id = $(this).attr("data-id");
|
||||||
ezq({
|
ezQuery({
|
||||||
title: "Delete File?",
|
title: "Delete File?",
|
||||||
body: "Are you sure you want to delete this file?",
|
body: "Are you sure you want to delete this file?",
|
||||||
success: function() {
|
success: function() {
|
||||||
@@ -203,12 +270,15 @@ $(document).ready(function() {
|
|||||||
$("#media-button").click(function() {
|
$("#media-button").click(function() {
|
||||||
$("#media-library-list").empty();
|
$("#media-library-list").empty();
|
||||||
refresh_files(function() {
|
refresh_files(function() {
|
||||||
$("#media-modal").modal("show");
|
$("#media-modal").modal();
|
||||||
});
|
});
|
||||||
// get_page_files().then(function (data) {
|
});
|
||||||
// var files = data;
|
|
||||||
// console.log(files);
|
$(".media-upload-button").click(function() {
|
||||||
// $('#media-modal').modal();
|
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