mirror of https://github.com/JohnHammond/CTFd.git
Merge pull request #1361 from CTFd/2.4.0-dev
2.4.0 / 2020-05-04 ================== **General** * Cache user and team attributes and use those perform certain page operations intead of going to the database for data * After modifying the user/team attributes you should call the appropriate cache clearing function (clear_user_session/clear_team_session) * Cache user IPs for the last hour to avoid hitting the database on every authenticated page view * Update the user IP's last seen value at least every hour or on every non-GET request * Replace `flask_restplus` with `flask_restx` * Remove `datafreeze`, `normality`, and `banal` dependencies in favor of in-repo solutions to exporting database **Admin Panel** * Add bulk selection and deletion for Users, Teams, Scoreboard, Challenges, Submissions * Make some Admin tables sortable by table headers * Create a score distribution graph in the statistics page * Make instance reset more granular to allow for choosing to reset Accounts, Submissions, Challenges, Pages, and/or Notificatoins * Properly update challenge visibility after updating challenge * Show total possible points in Statistics page * Add searching for Users, Teams, Challenges, Submissions * Move User IP addresses into a modal * Move Team IP addresses into a modal * Show User website in a user page button * Show Team website in a team page button * Make the Pages editor use proper HTML syntax highlighting * Theme header and footer editors now use CodeMirror * Make default CodeMirror font-size 12px * Stop storing last action via location hash and switch to using sessionStorage **Themes** * Make page selection a select and option instead of having a lot of page links * Add the JSEnum class to create constants that can be accessed from webpack. Generate constants with `python manage.py build jsenums` * Add the JinjaEnum class to inject constants into the Jinja environment to access from themes * Update jQuery to 3.5.0 to resolve potential security issue * Add some new CSS utilities (`.min-vh-*` and `.opacity-*`) * Change some rows to have a minimum height so they don't render oddly without data * Deprecate `.spinner-error` CSS class * Deprecate accessing the type variable to check user role. Instead you should use `is_admin()` **Miscellaneous** * Enable foreign key enforcement for SQLite. Only really matters for the debug server. * Remove the duplicated `get_config` from `CTFd.models` * Fix possible email sending issues in Python 3 by using `EmailMessage` * Dont set User type in the user side session. Instead it should be set in the new user attributes * Fix flask-profiler and bump dependency to 1.8.1 * Switch to using the `Faker` library for `populate.py` instead of hardcoded data * Add a `yarn lint` command to run eslint on JS files * Always insert the current CTFd version at the end of the import process * Fix issue where files could not be downloaded on Windowsbulk-clear-sessions
commit
bd4900b896
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
};
|
|
@ -73,3 +73,6 @@ CTFd/uploads
|
|||
|
||||
# JS
|
||||
node_modules/
|
||||
|
||||
# Flask Profiler files
|
||||
flask_profiler.sql
|
||||
|
|
50
CHANGELOG.md
50
CHANGELOG.md
|
@ -1,3 +1,53 @@
|
|||
2.4.0 / 2020-05-04
|
||||
==================
|
||||
|
||||
**General**
|
||||
* Cache user and team attributes and use those perform certain page operations intead of going to the database for data
|
||||
* After modifying the user/team attributes you should call the appropriate cache clearing function (clear_user_session/clear_team_session)
|
||||
* Cache user IPs for the last hour to avoid hitting the database on every authenticated page view
|
||||
* Update the user IP's last seen value at least every hour or on every non-GET request
|
||||
* Replace `flask_restplus` with `flask_restx`
|
||||
* Remove `datafreeze`, `normality`, and `banal` dependencies in favor of in-repo solutions to exporting database
|
||||
|
||||
**Admin Panel**
|
||||
* Add bulk selection and deletion for Users, Teams, Scoreboard, Challenges, Submissions
|
||||
* Make some Admin tables sortable by table headers
|
||||
* Create a score distribution graph in the statistics page
|
||||
* Make instance reset more granular to allow for choosing to reset Accounts, Submissions, Challenges, Pages, and/or Notificatoins
|
||||
* Properly update challenge visibility after updating challenge
|
||||
* Show total possible points in Statistics page
|
||||
* Add searching for Users, Teams, Challenges, Submissions
|
||||
* Move User IP addresses into a modal
|
||||
* Move Team IP addresses into a modal
|
||||
* Show User website in a user page button
|
||||
* Show Team website in a team page button
|
||||
* Make the Pages editor use proper HTML syntax highlighting
|
||||
* Theme header and footer editors now use CodeMirror
|
||||
* Make default CodeMirror font-size 12px
|
||||
* Stop storing last action via location hash and switch to using sessionStorage
|
||||
|
||||
**Themes**
|
||||
* Make page selection a select and option instead of having a lot of page links
|
||||
* Add the JSEnum class to create constants that can be accessed from webpack. Generate constants with `python manage.py build jsenums`
|
||||
* Add the JinjaEnum class to inject constants into the Jinja environment to access from themes
|
||||
* Update jQuery to 3.5.0 to resolve potential security issue
|
||||
* Add some new CSS utilities (`.min-vh-*` and `.opacity-*`)
|
||||
* Change some rows to have a minimum height so they don't render oddly without data
|
||||
* Deprecate `.spinner-error` CSS class
|
||||
* Deprecate accessing the type variable to check user role. Instead you should use `is_admin()`
|
||||
|
||||
**Miscellaneous**
|
||||
* Enable foreign key enforcement for SQLite. Only really matters for the debug server.
|
||||
* Remove the duplicated `get_config` from `CTFd.models`
|
||||
* Fix possible email sending issues in Python 3 by using `EmailMessage`
|
||||
* Dont set User type in the user side session. Instead it should be set in the new user attributes
|
||||
* Fix flask-profiler and bump dependency to 1.8.1
|
||||
* Switch to using the `Faker` library for `populate.py` instead of hardcoded data
|
||||
* Add a `yarn lint` command to run eslint on JS files
|
||||
* Always insert the current CTFd version at the end of the import process
|
||||
* Fix issue where files could not be downloaded on Windows
|
||||
|
||||
|
||||
2.3.3 / 2020-04-12
|
||||
==================
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ if sys.version_info[0] < 3:
|
|||
reload(sys) # noqa: F821
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = "2.3.3"
|
||||
__version__ = "2.4.0"
|
||||
|
||||
|
||||
class CTFdRequest(Request):
|
||||
|
@ -179,6 +179,19 @@ def create_app(config="CTFd.config.Config"):
|
|||
|
||||
# Alembic sqlite support is lacking so we should just create_all anyway
|
||||
if url.drivername.startswith("sqlite"):
|
||||
# Enable foreign keys for SQLite. This must be before the
|
||||
# db.create_all call because tests use the in-memory SQLite
|
||||
# database (each connection, including db creation, is a new db).
|
||||
# https://docs.sqlalchemy.org/en/13/dialects/sqlite.html#foreign-key-support
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
|
||||
@event.listens_for(Engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
db.create_all()
|
||||
stamp_latest_revision()
|
||||
else:
|
||||
|
|
|
@ -14,10 +14,13 @@ from flask import (
|
|||
url_for,
|
||||
)
|
||||
|
||||
from CTFd.cache import cache, clear_config
|
||||
from CTFd.cache import cache, clear_config, clear_standings, clear_pages
|
||||
from CTFd.models import (
|
||||
Awards,
|
||||
Challenges,
|
||||
Configs,
|
||||
Notifications,
|
||||
Pages,
|
||||
Solves,
|
||||
Submissions,
|
||||
Teams,
|
||||
|
@ -34,6 +37,7 @@ from CTFd.utils.exports import export_ctf as export_ctf_util
|
|||
from CTFd.utils.exports import import_ctf as import_ctf_util
|
||||
from CTFd.utils.helpers import get_errors
|
||||
from CTFd.utils.security.auth import logout_user
|
||||
from CTFd.utils.uploads import delete_file
|
||||
from CTFd.utils.user import is_admin
|
||||
|
||||
admin = Blueprint("admin", __name__)
|
||||
|
@ -176,19 +180,60 @@ def config():
|
|||
@admins_only
|
||||
def reset():
|
||||
if request.method == "POST":
|
||||
# Truncate Users, Teams, Submissions, Solves, Notifications, Awards, Unlocks, Tracking
|
||||
Tracking.query.delete()
|
||||
Solves.query.delete()
|
||||
Submissions.query.delete()
|
||||
Awards.query.delete()
|
||||
Unlocks.query.delete()
|
||||
Users.query.delete()
|
||||
Teams.query.delete()
|
||||
set_config("setup", False)
|
||||
require_setup = False
|
||||
logout = False
|
||||
next_url = url_for("admin.statistics")
|
||||
|
||||
data = request.form
|
||||
|
||||
if data.get("pages"):
|
||||
_pages = Pages.query.all()
|
||||
for p in _pages:
|
||||
for f in p.files:
|
||||
delete_file(file_id=f.id)
|
||||
|
||||
Pages.query.delete()
|
||||
|
||||
if data.get("notifications"):
|
||||
Notifications.query.delete()
|
||||
|
||||
if data.get("challenges"):
|
||||
_challenges = Challenges.query.all()
|
||||
for c in _challenges:
|
||||
for f in c.files:
|
||||
delete_file(file_id=f.id)
|
||||
Challenges.query.delete()
|
||||
|
||||
if data.get("accounts"):
|
||||
Users.query.delete()
|
||||
Teams.query.delete()
|
||||
require_setup = True
|
||||
logout = True
|
||||
|
||||
if data.get("submissions"):
|
||||
Solves.query.delete()
|
||||
Submissions.query.delete()
|
||||
Awards.query.delete()
|
||||
Unlocks.query.delete()
|
||||
Tracking.query.delete()
|
||||
|
||||
if require_setup:
|
||||
set_config("setup", False)
|
||||
cache.clear()
|
||||
logout_user()
|
||||
next_url = url_for("views.setup")
|
||||
|
||||
db.session.commit()
|
||||
cache.clear()
|
||||
logout_user()
|
||||
|
||||
clear_pages()
|
||||
clear_standings()
|
||||
clear_config()
|
||||
|
||||
if logout is True:
|
||||
cache.clear()
|
||||
logout_user()
|
||||
|
||||
db.session.close()
|
||||
return redirect(url_for("views.setup"))
|
||||
return redirect(next_url)
|
||||
|
||||
return render_template("admin/reset.html")
|
||||
|
|
|
@ -2,7 +2,7 @@ import os
|
|||
|
||||
import six
|
||||
from flask import current_app as app
|
||||
from flask import render_template, render_template_string, url_for
|
||||
from flask import render_template, render_template_string, request, url_for
|
||||
|
||||
from CTFd.admin import admin
|
||||
from CTFd.models import Challenges, Flags, Solves
|
||||
|
@ -14,8 +14,26 @@ from CTFd.utils.decorators import admins_only
|
|||
@admin.route("/admin/challenges")
|
||||
@admins_only
|
||||
def challenges_listing():
|
||||
challenges = Challenges.query.all()
|
||||
return render_template("admin/challenges/challenges.html", challenges=challenges)
|
||||
q = request.args.get("q")
|
||||
field = request.args.get("field")
|
||||
filters = []
|
||||
|
||||
if q:
|
||||
# The field exists as an exposed column
|
||||
if Challenges.__mapper__.has_property(field):
|
||||
filters.append(getattr(Challenges, field).like("%{}%".format(q)))
|
||||
|
||||
query = Challenges.query.filter(*filters).order_by(Challenges.id.asc())
|
||||
challenges = query.all()
|
||||
total = query.count()
|
||||
|
||||
return render_template(
|
||||
"admin/challenges/challenges.html",
|
||||
challenges=challenges,
|
||||
total=total,
|
||||
q=q,
|
||||
field=field,
|
||||
)
|
||||
|
||||
|
||||
@admin.route("/admin/challenges/<int:challenge_id>")
|
||||
|
|
|
@ -31,6 +31,13 @@ def statistics():
|
|||
|
||||
challenge_count = Challenges.query.count()
|
||||
|
||||
total_points = (
|
||||
Challenges.query.with_entities(db.func.sum(Challenges.value).label("sum"))
|
||||
.filter_by(state="visible")
|
||||
.first()
|
||||
.sum
|
||||
) or 0
|
||||
|
||||
ip_count = Tracking.query.with_entities(Tracking.ip).distinct().count()
|
||||
|
||||
solves_sub = (
|
||||
|
@ -73,6 +80,7 @@ def statistics():
|
|||
wrong_count=wrong_count,
|
||||
solve_count=solve_count,
|
||||
challenge_count=challenge_count,
|
||||
total_points=total_points,
|
||||
solve_data=solve_data,
|
||||
most_solved=most_solved,
|
||||
least_solved=least_solved,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask import render_template, request
|
||||
from flask import render_template, request, url_for
|
||||
|
||||
from CTFd.admin import admin
|
||||
from CTFd.models import Challenges, Submissions
|
||||
|
@ -10,16 +10,21 @@ from CTFd.utils.modes import get_model
|
|||
@admin.route("/admin/submissions/<submission_type>")
|
||||
@admins_only
|
||||
def submissions_listing(submission_type):
|
||||
filters = {}
|
||||
filters_by = {}
|
||||
if submission_type:
|
||||
filters["type"] = submission_type
|
||||
filters_by["type"] = submission_type
|
||||
filters = []
|
||||
|
||||
curr_page = abs(int(request.args.get("page", 1, type=int)))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (curr_page - 1)
|
||||
page_end = results_per_page * (curr_page - 1) + results_per_page
|
||||
sub_count = Submissions.query.filter_by(**filters).count()
|
||||
page_count = int(sub_count / results_per_page) + (sub_count % results_per_page > 0)
|
||||
q = request.args.get("q")
|
||||
field = request.args.get("field")
|
||||
page = abs(request.args.get("page", 1, type=int))
|
||||
|
||||
if q:
|
||||
submissions = []
|
||||
if Submissions.__mapper__.has_property(
|
||||
field
|
||||
): # The field exists as an exposed column
|
||||
filters.append(getattr(Submissions, field).like("%{}%".format(q)))
|
||||
|
||||
Model = get_model()
|
||||
|
||||
|
@ -34,18 +39,27 @@ def submissions_listing(submission_type):
|
|||
Challenges.name.label("challenge_name"),
|
||||
Model.name.label("team_name"),
|
||||
)
|
||||
.filter_by(**filters)
|
||||
.filter_by(**filters_by)
|
||||
.filter(*filters)
|
||||
.join(Challenges)
|
||||
.join(Model)
|
||||
.order_by(Submissions.date.desc())
|
||||
.slice(page_start, page_end)
|
||||
.all()
|
||||
.paginate(page=page, per_page=50)
|
||||
)
|
||||
|
||||
args = dict(request.args)
|
||||
args.pop("page", 1)
|
||||
|
||||
return render_template(
|
||||
"admin/submissions.html",
|
||||
submissions=submissions,
|
||||
page_count=page_count,
|
||||
curr_page=curr_page,
|
||||
prev_page=url_for(
|
||||
request.endpoint, type=submission_type, page=submissions.prev_num, **args
|
||||
),
|
||||
next_page=url_for(
|
||||
request.endpoint, type=submission_type, page=submissions.next_num, **args
|
||||
),
|
||||
type=submission_type,
|
||||
q=q,
|
||||
field=field,
|
||||
)
|
||||
|
|
|
@ -1,64 +1,40 @@
|
|||
from flask import render_template, request
|
||||
from flask import render_template, request, url_for
|
||||
from sqlalchemy.sql import not_
|
||||
|
||||
from CTFd.admin import admin
|
||||
from CTFd.models import Challenges, Teams, Tracking, db
|
||||
from CTFd.models import Challenges, Teams, Tracking
|
||||
from CTFd.utils.decorators import admins_only
|
||||
from CTFd.utils.helpers import get_errors
|
||||
|
||||
|
||||
@admin.route("/admin/teams")
|
||||
@admins_only
|
||||
def teams_listing():
|
||||
page = abs(request.args.get("page", 1, type=int))
|
||||
q = request.args.get("q")
|
||||
field = request.args.get("field")
|
||||
page = abs(request.args.get("page", 1, type=int))
|
||||
filters = []
|
||||
|
||||
if q:
|
||||
field = request.args.get("field")
|
||||
teams = []
|
||||
errors = get_errors()
|
||||
if field == "id":
|
||||
if q.isnumeric():
|
||||
teams = Teams.query.filter(Teams.id == q).order_by(Teams.id.asc()).all()
|
||||
else:
|
||||
teams = []
|
||||
errors.append("Your ID search term is not numeric")
|
||||
elif field == "name":
|
||||
teams = (
|
||||
Teams.query.filter(Teams.name.like("%{}%".format(q)))
|
||||
.order_by(Teams.id.asc())
|
||||
.all()
|
||||
)
|
||||
elif field == "email":
|
||||
teams = (
|
||||
Teams.query.filter(Teams.email.like("%{}%".format(q)))
|
||||
.order_by(Teams.id.asc())
|
||||
.all()
|
||||
)
|
||||
elif field == "affiliation":
|
||||
teams = (
|
||||
Teams.query.filter(Teams.affiliation.like("%{}%".format(q)))
|
||||
.order_by(Teams.id.asc())
|
||||
.all()
|
||||
)
|
||||
return render_template(
|
||||
"admin/teams/teams.html",
|
||||
teams=teams,
|
||||
pages=0,
|
||||
curr_page=None,
|
||||
q=q,
|
||||
field=field,
|
||||
)
|
||||
# The field exists as an exposed column
|
||||
if Teams.__mapper__.has_property(field):
|
||||
filters.append(getattr(Teams, field).like("%{}%".format(q)))
|
||||
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
teams = (
|
||||
Teams.query.filter(*filters)
|
||||
.order_by(Teams.id.asc())
|
||||
.paginate(page=page, per_page=50)
|
||||
)
|
||||
|
||||
args = dict(request.args)
|
||||
args.pop("page", 1)
|
||||
|
||||
teams = Teams.query.order_by(Teams.id.asc()).slice(page_start, page_end).all()
|
||||
count = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
||||
return render_template(
|
||||
"admin/teams/teams.html", teams=teams, pages=pages, curr_page=page
|
||||
"admin/teams/teams.html",
|
||||
teams=teams,
|
||||
prev_page=url_for(request.endpoint, page=teams.prev_num, **args),
|
||||
next_page=url_for(request.endpoint, page=teams.next_num, **args),
|
||||
q=q,
|
||||
field=field,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,75 +1,51 @@
|
|||
from flask import render_template, request
|
||||
from flask import render_template, request, url_for
|
||||
from sqlalchemy.sql import not_
|
||||
|
||||
from CTFd.admin import admin
|
||||
from CTFd.models import Challenges, Tracking, Users, db
|
||||
from CTFd.models import Challenges, Tracking, Users
|
||||
from CTFd.utils import get_config
|
||||
from CTFd.utils.decorators import admins_only
|
||||
from CTFd.utils.helpers import get_errors
|
||||
from CTFd.utils.modes import TEAMS_MODE
|
||||
|
||||
|
||||
@admin.route("/admin/users")
|
||||
@admins_only
|
||||
def users_listing():
|
||||
page = abs(request.args.get("page", 1, type=int))
|
||||
q = request.args.get("q")
|
||||
if q:
|
||||
field = request.args.get("field")
|
||||
users = []
|
||||
errors = get_errors()
|
||||
if field == "id":
|
||||
if q.isnumeric():
|
||||
users = Users.query.filter(Users.id == q).order_by(Users.id.asc()).all()
|
||||
else:
|
||||
users = []
|
||||
errors.append("Your ID search term is not numeric")
|
||||
elif field == "name":
|
||||
users = (
|
||||
Users.query.filter(Users.name.like("%{}%".format(q)))
|
||||
.order_by(Users.id.asc())
|
||||
.all()
|
||||
)
|
||||
elif field == "email":
|
||||
users = (
|
||||
Users.query.filter(Users.email.like("%{}%".format(q)))
|
||||
.order_by(Users.id.asc())
|
||||
.all()
|
||||
)
|
||||
elif field == "affiliation":
|
||||
users = (
|
||||
Users.query.filter(Users.affiliation.like("%{}%".format(q)))
|
||||
.order_by(Users.id.asc())
|
||||
.all()
|
||||
)
|
||||
elif field == "ip":
|
||||
users = (
|
||||
Users.query.join(Tracking, Users.id == Tracking.user_id)
|
||||
.filter(Tracking.ip.like("%{}%".format(q)))
|
||||
.order_by(Users.id.asc())
|
||||
.all()
|
||||
)
|
||||
field = request.args.get("field")
|
||||
page = abs(request.args.get("page", 1, type=int))
|
||||
filters = []
|
||||
users = []
|
||||
|
||||
return render_template(
|
||||
"admin/users/users.html",
|
||||
users=users,
|
||||
pages=0,
|
||||
curr_page=None,
|
||||
q=q,
|
||||
field=field,
|
||||
if q:
|
||||
# The field exists as an exposed column
|
||||
if Users.__mapper__.has_property(field):
|
||||
filters.append(getattr(Users, field).like("%{}%".format(q)))
|
||||
|
||||
if q and field == "ip":
|
||||
users = (
|
||||
Users.query.join(Tracking, Users.id == Tracking.user_id)
|
||||
.filter(Tracking.ip.like("%{}%".format(q)))
|
||||
.order_by(Users.id.asc())
|
||||
.paginate(page=page, per_page=50)
|
||||
)
|
||||
else:
|
||||
users = (
|
||||
Users.query.filter(*filters)
|
||||
.order_by(Users.id.asc())
|
||||
.paginate(page=page, per_page=50)
|
||||
)
|
||||
|
||||
page = abs(int(page))
|
||||
results_per_page = 50
|
||||
page_start = results_per_page * (page - 1)
|
||||
page_end = results_per_page * (page - 1) + results_per_page
|
||||
|
||||
users = Users.query.order_by(Users.id.asc()).slice(page_start, page_end).all()
|
||||
count = db.session.query(db.func.count(Users.id)).first()[0]
|
||||
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
||||
args = dict(request.args)
|
||||
args.pop("page", 1)
|
||||
|
||||
return render_template(
|
||||
"admin/users/users.html", users=users, pages=pages, curr_page=page
|
||||
"admin/users/users.html",
|
||||
users=users,
|
||||
prev_page=url_for(request.endpoint, page=users.prev_num, **args),
|
||||
next_page=url_for(request.endpoint, page=users.next_num, **args),
|
||||
q=q,
|
||||
field=field,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import Blueprint, current_app
|
||||
from flask_restplus import Api
|
||||
from flask_restx import Api
|
||||
|
||||
from CTFd.api.v1.awards import awards_namespace
|
||||
from CTFd.api.v1.challenges import challenges_namespace
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import clear_standings
|
||||
from CTFd.utils.config import is_teams_mode
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import datetime
|
||||
|
||||
from flask import abort, request, url_for
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
from sqlalchemy.sql import and_
|
||||
|
||||
from CTFd.cache import clear_standings
|
||||
|
@ -378,7 +378,7 @@ class ChallengeAttempt(Resource):
|
|||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [TOO FAST]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
submission=request_data.get("submission", "").encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
|
@ -425,7 +425,7 @@ class ChallengeAttempt(Resource):
|
|||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [CORRECT]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
submission=request_data.get("submission", "").encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
|
@ -443,7 +443,7 @@ class ChallengeAttempt(Resource):
|
|||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [WRONG]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
submission=request_data.get("submission", "").encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
|
@ -477,7 +477,7 @@ class ChallengeAttempt(Resource):
|
|||
log(
|
||||
"submissions",
|
||||
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [ALREADY SOLVED]",
|
||||
submission=request_data["submission"].encode("utf-8"),
|
||||
submission=request_data.get("submission", "").encode("utf-8"),
|
||||
challenge_id=challenge_id,
|
||||
kpm=kpm,
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import clear_config, clear_standings
|
||||
from CTFd.models import Configs, db
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.models import Files, db
|
||||
from CTFd.schemas.files import FileSchema
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.models import Flags, db
|
||||
from CTFd.plugins.flags import FLAG_CLASSES, get_flag_class
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.models import Hints, HintUnlocks, db
|
||||
from CTFd.schemas.hints import HintSchema
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import current_app, request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.models import Notifications, db
|
||||
from CTFd.schemas.notifications import NotificationSchema
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import clear_pages
|
||||
from CTFd.models import Pages, db
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import cache, make_cache_key
|
||||
from CTFd.models import Awards, Solves, Teams
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask_restplus import Namespace
|
||||
from flask_restx import Namespace
|
||||
|
||||
statistics_namespace = Namespace(
|
||||
"statistics", description="Endpoint to retrieve Statistics"
|
||||
|
@ -8,3 +8,4 @@ from CTFd.api.v1.statistics import challenges # noqa: F401
|
|||
from CTFd.api.v1.statistics import submissions # noqa: F401
|
||||
from CTFd.api.v1.statistics import teams # noqa: F401
|
||||
from CTFd.api.v1.statistics import users # noqa: F401
|
||||
from CTFd.api.v1.statistics import scores # noqa: F401
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask_restplus import Resource
|
||||
from flask_restx import Resource
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.sql import or_
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
from collections import defaultdict
|
||||
|
||||
from flask_restx import Resource
|
||||
|
||||
from CTFd.api.v1.statistics import statistics_namespace
|
||||
from CTFd.models import db, Challenges
|
||||
from CTFd.utils.decorators import admins_only
|
||||
from CTFd.utils.scores import get_standings
|
||||
|
||||
|
||||
@statistics_namespace.route("/scores/distribution")
|
||||
class ScoresDistribution(Resource):
|
||||
@admins_only
|
||||
def get(self):
|
||||
challenge_count = Challenges.query.count() or 1
|
||||
total_points = (
|
||||
Challenges.query.with_entities(db.func.sum(Challenges.value).label("sum"))
|
||||
.filter_by(state="visible")
|
||||
.first()
|
||||
.sum
|
||||
) or 0
|
||||
# Convert Decimal() to int in some database backends for Python 2
|
||||
total_points = int(total_points)
|
||||
|
||||
# Divide score by challenges to get brackets with explicit floor division
|
||||
bracket_size = total_points // challenge_count
|
||||
|
||||
# Get standings
|
||||
standings = get_standings(admin=True)
|
||||
|
||||
# Iterate over standings and increment the count for each bracket for each standing within that bracket
|
||||
bottom, top = 0, bracket_size
|
||||
count = 1
|
||||
brackets = defaultdict(lambda: 0)
|
||||
for t in reversed(standings):
|
||||
if ((t.score >= bottom) and (t.score <= top)) or t.score <= 0:
|
||||
brackets[top] += 1
|
||||
else:
|
||||
count += 1
|
||||
bottom, top = (bracket_size, (bracket_size * count))
|
||||
brackets[top] += 1
|
||||
|
||||
return {"success": True, "data": {"brackets": brackets}}
|
|
@ -1,4 +1,4 @@
|
|||
from flask_restplus import Resource
|
||||
from flask_restx import Resource
|
||||
from sqlalchemy import func
|
||||
|
||||
from CTFd.api.v1.statistics import statistics_namespace
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask_restplus import Resource
|
||||
from flask_restx import Resource
|
||||
|
||||
from CTFd.api.v1.statistics import statistics_namespace
|
||||
from CTFd.models import Teams
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask_restplus import Resource
|
||||
from flask_restx import Resource
|
||||
from sqlalchemy import func
|
||||
|
||||
from CTFd.api.v1.statistics import statistics_namespace
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import clear_standings
|
||||
from CTFd.models import Submissions, db
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.models import Tags, db
|
||||
from CTFd.schemas.tags import TagSchema
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import copy
|
||||
|
||||
from flask import abort, request, session
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import clear_standings
|
||||
from CTFd.cache import clear_standings, clear_team_session, clear_user_session
|
||||
from CTFd.models import Awards, Submissions, Teams, Unlocks, Users, db
|
||||
from CTFd.schemas.awards import AwardSchema
|
||||
from CTFd.schemas.submissions import SubmissionSchema
|
||||
|
@ -13,7 +13,7 @@ from CTFd.utils.decorators.visibility import (
|
|||
check_account_visibility,
|
||||
check_score_visibility,
|
||||
)
|
||||
from CTFd.utils.user import get_current_team, is_admin
|
||||
from CTFd.utils.user import get_current_team, get_current_user_type, is_admin
|
||||
|
||||
teams_namespace = Namespace("teams", description="Endpoint to retrieve Teams")
|
||||
|
||||
|
@ -23,7 +23,8 @@ class TeamList(Resource):
|
|||
@check_account_visibility
|
||||
def get(self):
|
||||
teams = Teams.query.filter_by(hidden=False, banned=False)
|
||||
view = copy.deepcopy(TeamSchema.views.get(session.get("type", "user")))
|
||||
user_type = get_current_user_type(fallback="user")
|
||||
view = copy.deepcopy(TeamSchema.views.get(user_type))
|
||||
view.remove("members")
|
||||
response = TeamSchema(view=view, many=True).dump(teams)
|
||||
|
||||
|
@ -35,7 +36,8 @@ class TeamList(Resource):
|
|||
@admins_only
|
||||
def post(self):
|
||||
req = request.get_json()
|
||||
view = TeamSchema.views.get(session.get("type", "self"))
|
||||
user_type = get_current_user_type()
|
||||
view = TeamSchema.views.get(user_type)
|
||||
schema = TeamSchema(view=view)
|
||||
response = schema.load(req)
|
||||
|
||||
|
@ -63,7 +65,8 @@ class TeamPublic(Resource):
|
|||
if (team.banned or team.hidden) and is_admin() is False:
|
||||
abort(404)
|
||||
|
||||
view = TeamSchema.views.get(session.get("type", "user"))
|
||||
user_type = get_current_user_type(fallback="user")
|
||||
view = TeamSchema.views.get(user_type)
|
||||
schema = TeamSchema(view=view)
|
||||
response = schema.dump(team)
|
||||
|
||||
|
@ -88,25 +91,31 @@ class TeamPublic(Resource):
|
|||
|
||||
response = schema.dump(response.data)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
clear_team_session(team_id=team.id)
|
||||
clear_standings()
|
||||
|
||||
db.session.close()
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
|
||||
@admins_only
|
||||
def delete(self, team_id):
|
||||
team = Teams.query.filter_by(id=team_id).first_or_404()
|
||||
team_id = team.id
|
||||
|
||||
for member in team.members:
|
||||
member.team_id = None
|
||||
clear_user_session(user_id=member.id)
|
||||
|
||||
db.session.delete(team)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
clear_team_session(team_id=team_id)
|
||||
clear_standings()
|
||||
|
||||
db.session.close()
|
||||
|
||||
return {"success": True}
|
||||
|
||||
|
||||
|
@ -147,7 +156,7 @@ class TeamPrivate(Resource):
|
|||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
db.session.commit()
|
||||
|
||||
clear_team_session(team_id=team.id)
|
||||
response = TeamSchema("self").dump(response.data)
|
||||
db.session.close()
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import datetime
|
||||
|
||||
from flask import request, session
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.models import Tokens, db
|
||||
from CTFd.schemas.tokens import TokenSchema
|
||||
from CTFd.utils.decorators import authed_only, require_verified_emails
|
||||
from CTFd.utils.security.auth import generate_user_token
|
||||
from CTFd.utils.user import get_current_user, is_admin
|
||||
from CTFd.utils.user import get_current_user, get_current_user_type, is_admin
|
||||
|
||||
tokens_namespace = Namespace("tokens", description="Endpoint to retrieve Tokens")
|
||||
|
||||
|
@ -62,7 +62,8 @@ class TokenDetail(Resource):
|
|||
id=token_id, user_id=session["id"]
|
||||
).first_or_404()
|
||||
|
||||
schema = TokenSchema(view=session.get("type", "user"))
|
||||
user_type = get_current_user_type(fallback="user")
|
||||
schema = TokenSchema(view=user_type)
|
||||
response = schema.dump(token)
|
||||
|
||||
if response.errors:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask import request
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import clear_standings
|
||||
from CTFd.models import Unlocks, db, get_class_by_tablename
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from flask import abort, request, session
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask import abort, request
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.cache import clear_standings
|
||||
from CTFd.cache import clear_standings, clear_user_session
|
||||
from CTFd.models import (
|
||||
Awards,
|
||||
Notifications,
|
||||
|
@ -22,7 +22,7 @@ from CTFd.utils.decorators.visibility import (
|
|||
check_score_visibility,
|
||||
)
|
||||
from CTFd.utils.email import sendmail, user_created_notification
|
||||
from CTFd.utils.user import get_current_user, is_admin
|
||||
from CTFd.utils.user import get_current_user, get_current_user_type, is_admin
|
||||
|
||||
users_namespace = Namespace("users", description="Endpoint to retrieve Users")
|
||||
|
||||
|
@ -80,7 +80,8 @@ class UserPublic(Resource):
|
|||
if (user.banned or user.hidden) and is_admin() is False:
|
||||
abort(404)
|
||||
|
||||
response = UserSchema(view=session.get("type", "user")).dump(user)
|
||||
user_type = get_current_user_type(fallback="user")
|
||||
response = UserSchema(view=user_type).dump(user)
|
||||
|
||||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
@ -106,6 +107,7 @@ class UserPublic(Resource):
|
|||
|
||||
db.session.close()
|
||||
|
||||
clear_user_session(user_id=user_id)
|
||||
clear_standings()
|
||||
|
||||
return {"success": True, "data": response}
|
||||
|
@ -122,6 +124,7 @@ class UserPublic(Resource):
|
|||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
clear_user_session(user_id=user_id)
|
||||
clear_standings()
|
||||
|
||||
return {"success": True}
|
||||
|
@ -148,6 +151,7 @@ class UserPrivate(Resource):
|
|||
|
||||
db.session.commit()
|
||||
|
||||
clear_user_session(user_id=user.id)
|
||||
response = schema.dump(response.data)
|
||||
db.session.close()
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ from itsdangerous.exc import BadSignature, BadTimeSignature, SignatureExpired
|
|||
from CTFd.models import Teams, Users, db
|
||||
from CTFd.utils import config, email, get_app_config, get_config
|
||||
from CTFd.utils import user as current_user
|
||||
from CTFd.cache import clear_user_session, clear_team_session
|
||||
from CTFd.utils import validators
|
||||
from CTFd.utils.config import is_teams_mode
|
||||
from CTFd.utils.config.integrations import mlc_registration
|
||||
|
@ -57,6 +58,7 @@ def confirm(data=None):
|
|||
name=user.name,
|
||||
)
|
||||
db.session.commit()
|
||||
clear_user_session(user_id=user.id)
|
||||
email.successful_registration_notification(user.email)
|
||||
db.session.close()
|
||||
if current_user.authed():
|
||||
|
@ -126,6 +128,7 @@ def reset_password(data=None):
|
|||
|
||||
user.password = password
|
||||
db.session.commit()
|
||||
clear_user_session(user_id=user.id)
|
||||
log(
|
||||
"logins",
|
||||
format="[{date}] {ip} - successful password reset for {name}",
|
||||
|
@ -411,6 +414,7 @@ def oauth_redirect():
|
|||
team = Teams(name=team_name, oauth_id=team_id, captain_id=user.id)
|
||||
db.session.add(team)
|
||||
db.session.commit()
|
||||
clear_team_session(team_id=team.id)
|
||||
|
||||
team_size_limit = get_config("team_size", default=0)
|
||||
if team_size_limit and len(team.members) >= team_size_limit:
|
||||
|
@ -428,6 +432,7 @@ def oauth_redirect():
|
|||
user.oauth_id = user_id
|
||||
user.verified = True
|
||||
db.session.commit()
|
||||
clear_user_session(user_id=user.id)
|
||||
|
||||
login_user(user)
|
||||
|
||||
|
|
|
@ -44,3 +44,21 @@ def clear_pages():
|
|||
|
||||
cache.delete_memoized(get_pages)
|
||||
cache.delete_memoized(get_page)
|
||||
|
||||
|
||||
def clear_user_recent_ips(user_id):
|
||||
from CTFd.utils.user import get_user_recent_ips
|
||||
|
||||
cache.delete_memoized(get_user_recent_ips, user_id=user_id)
|
||||
|
||||
|
||||
def clear_user_session(user_id):
|
||||
from CTFd.utils.user import get_user_attrs
|
||||
|
||||
cache.delete_memoized(get_user_attrs, user_id=user_id)
|
||||
|
||||
|
||||
def clear_team_session(team_id):
|
||||
from CTFd.utils.user import get_team_attrs
|
||||
|
||||
cache.delete_memoized(get_team_attrs, team_id=team_id)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
from enum import Enum
|
||||
from flask import current_app
|
||||
|
||||
JS_ENUMS = {}
|
||||
|
||||
|
||||
class RawEnum(Enum):
|
||||
"""
|
||||
This is a customized enum class which should be used with a mixin.
|
||||
The mixin should define the types of each member.
|
||||
|
||||
For example:
|
||||
|
||||
class Colors(str, RawEnum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
BLUE = "blue"
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
return str(self._value_)
|
||||
|
||||
@classmethod
|
||||
def keys(cls):
|
||||
return list(cls.__members__.keys())
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return list(cls.__members__.values())
|
||||
|
||||
@classmethod
|
||||
def test(cls, value):
|
||||
try:
|
||||
return bool(cls(value))
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def JSEnum(cls):
|
||||
"""
|
||||
This is a decorator used to gather all Enums which should be shared with
|
||||
the CTFd front end. The JS_Enums dictionary can be taken be a script and
|
||||
compiled into a JavaScript file for use by frontend assets. JS_Enums
|
||||
should not be passed directly into Jinja. A JinjaEnum is better for that.
|
||||
"""
|
||||
if cls.__name__ not in JS_ENUMS:
|
||||
JS_ENUMS[cls.__name__] = dict(cls.__members__)
|
||||
else:
|
||||
raise KeyError("{} was already defined as a JSEnum".format(cls.__name__))
|
||||
return cls
|
||||
|
||||
|
||||
def JinjaEnum(cls):
|
||||
"""
|
||||
This is a decorator used to inject the decorated Enum into Jinja globals
|
||||
which allows you to access it from the front end. If you need to access
|
||||
an Enum from JS, a better tool to use is the JSEnum decorator.
|
||||
"""
|
||||
if cls.__name__ not in current_app.jinja_env.globals:
|
||||
current_app.jinja_env.globals[cls.__name__] = cls
|
||||
else:
|
||||
raise KeyError("{} was already defined as a JinjaEnum".format(cls.__name__))
|
||||
return cls
|
|
@ -0,0 +1,20 @@
|
|||
from collections import namedtuple
|
||||
|
||||
TeamAttrs = namedtuple(
|
||||
"TeamAttrs",
|
||||
[
|
||||
"id",
|
||||
"oauth_id",
|
||||
"name",
|
||||
"email",
|
||||
"secret",
|
||||
"website",
|
||||
"affiliation",
|
||||
"country",
|
||||
"bracket",
|
||||
"hidden",
|
||||
"banned",
|
||||
"captain_id",
|
||||
"created",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
from collections import namedtuple
|
||||
|
||||
UserAttrs = namedtuple(
|
||||
"UserAttrs",
|
||||
[
|
||||
"id",
|
||||
"oauth_id",
|
||||
"name",
|
||||
"email",
|
||||
"type",
|
||||
"secret",
|
||||
"website",
|
||||
"affiliation",
|
||||
"country",
|
||||
"bracket",
|
||||
"hidden",
|
||||
"banned",
|
||||
"verified",
|
||||
"team_id",
|
||||
"created",
|
||||
],
|
||||
)
|
|
@ -1,12 +1,10 @@
|
|||
import datetime
|
||||
|
||||
import six
|
||||
from flask_marshmallow import Marshmallow
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import column_property, validates
|
||||
|
||||
from CTFd.cache import cache
|
||||
from CTFd.utils.crypto import hash_password
|
||||
from CTFd.utils.humanize.numbers import ordinalize
|
||||
|
||||
|
@ -142,6 +140,8 @@ class Awards(db.Model):
|
|||
|
||||
@hybrid_property
|
||||
def account_id(self):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
user_mode = get_config("user_mode")
|
||||
if user_mode == "teams":
|
||||
return self.team_id
|
||||
|
@ -259,6 +259,8 @@ class Users(db.Model):
|
|||
|
||||
@hybrid_property
|
||||
def account_id(self):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
user_mode = get_config("user_mode")
|
||||
if user_mode == "teams":
|
||||
return self.team_id
|
||||
|
@ -291,6 +293,8 @@ class Users(db.Model):
|
|||
return None
|
||||
|
||||
def get_solves(self, admin=False):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
solves = Solves.query.filter_by(user_id=self.id)
|
||||
freeze = get_config("freeze")
|
||||
if freeze and admin is False:
|
||||
|
@ -299,6 +303,8 @@ class Users(db.Model):
|
|||
return solves.all()
|
||||
|
||||
def get_fails(self, admin=False):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
fails = Fails.query.filter_by(user_id=self.id)
|
||||
freeze = get_config("freeze")
|
||||
if freeze and admin is False:
|
||||
|
@ -307,6 +313,8 @@ class Users(db.Model):
|
|||
return fails.all()
|
||||
|
||||
def get_awards(self, admin=False):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
awards = Awards.query.filter_by(user_id=self.id)
|
||||
freeze = get_config("freeze")
|
||||
if freeze and admin is False:
|
||||
|
@ -432,6 +440,8 @@ class Teams(db.Model):
|
|||
return None
|
||||
|
||||
def get_solves(self, admin=False):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
member_ids = [member.id for member in self.members]
|
||||
|
||||
solves = Solves.query.filter(Solves.user_id.in_(member_ids)).order_by(
|
||||
|
@ -446,6 +456,8 @@ class Teams(db.Model):
|
|||
return solves.all()
|
||||
|
||||
def get_fails(self, admin=False):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
member_ids = [member.id for member in self.members]
|
||||
|
||||
fails = Fails.query.filter(Fails.user_id.in_(member_ids)).order_by(
|
||||
|
@ -460,6 +472,8 @@ class Teams(db.Model):
|
|||
return fails.all()
|
||||
|
||||
def get_awards(self, admin=False):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
member_ids = [member.id for member in self.members]
|
||||
|
||||
awards = Awards.query.filter(Awards.user_id.in_(member_ids)).order_by(
|
||||
|
@ -523,6 +537,8 @@ class Submissions(db.Model):
|
|||
|
||||
@hybrid_property
|
||||
def account_id(self):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
user_mode = get_config("user_mode")
|
||||
if user_mode == "teams":
|
||||
return self.team_id
|
||||
|
@ -531,6 +547,8 @@ class Submissions(db.Model):
|
|||
|
||||
@hybrid_property
|
||||
def account(self):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
user_mode = get_config("user_mode")
|
||||
if user_mode == "teams":
|
||||
return self.team
|
||||
|
@ -600,6 +618,8 @@ class Unlocks(db.Model):
|
|||
|
||||
@hybrid_property
|
||||
def account_id(self):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
user_mode = get_config("user_mode")
|
||||
if user_mode == "teams":
|
||||
return self.team_id
|
||||
|
@ -668,22 +688,3 @@ class Tokens(db.Model):
|
|||
|
||||
class UserTokens(Tokens):
|
||||
__mapper_args__ = {"polymorphic_identity": "user"}
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def get_config(key):
|
||||
"""
|
||||
This should be a direct clone of its implementation in utils. It is used to avoid a circular import.
|
||||
"""
|
||||
config = Configs.query.filter_by(key=key).first()
|
||||
if config and config.value:
|
||||
value = config.value
|
||||
if value and value.isdigit():
|
||||
return int(value)
|
||||
elif value and isinstance(value, six.string_types):
|
||||
if value.lower() == "true":
|
||||
return True
|
||||
elif value.lower() == "false":
|
||||
return False
|
||||
else:
|
||||
return value
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from flask import Blueprint, redirect, render_template, request, url_for
|
||||
|
||||
from CTFd.cache import clear_user_session, clear_team_session
|
||||
from CTFd.models import Teams, db
|
||||
from CTFd.utils import config, get_config
|
||||
from CTFd.utils.crypto import verify_password
|
||||
|
@ -63,7 +64,6 @@ def join():
|
|||
passphrase = request.form.get("password", "").strip()
|
||||
|
||||
team = Teams.query.filter_by(name=teamname).first()
|
||||
user = get_current_user()
|
||||
|
||||
if team and verify_password(passphrase, team.password):
|
||||
team_size_limit = get_config("team_size", default=0)
|
||||
|
@ -77,6 +77,7 @@ def join():
|
|||
"teams/join_team.html", infos=infos, errors=errors
|
||||
)
|
||||
|
||||
user = get_current_user()
|
||||
user.team_id = team.id
|
||||
db.session.commit()
|
||||
|
||||
|
@ -84,6 +85,9 @@ def join():
|
|||
team.captain_id = user.id
|
||||
db.session.commit()
|
||||
|
||||
clear_user_session(user_id=user.id)
|
||||
clear_team_session(team_id=team.id)
|
||||
|
||||
return redirect(url_for("challenges.listing"))
|
||||
else:
|
||||
errors.append("That information is incorrect")
|
||||
|
@ -130,6 +134,10 @@ def new():
|
|||
|
||||
user.team_id = team.id
|
||||
db.session.commit()
|
||||
|
||||
clear_user_session(user_id=user.id)
|
||||
clear_team_session(team_id=team.id)
|
||||
|
||||
return redirect(url_for("challenges.listing"))
|
||||
|
||||
|
||||
|
|
|
@ -66,3 +66,11 @@ tbody tr:hover {
|
|||
tr[data-href] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort-col {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
@import "~codemirror/lib/codemirror.css";
|
||||
.CodeMirror {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
|
@ -220,14 +220,4 @@ $(() => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -407,8 +407,23 @@ $(() => {
|
|||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$(".challenge-state").text(response.data.state);
|
||||
switch (response.data.state) {
|
||||
case "visible":
|
||||
$(".challenge-state")
|
||||
.removeClass("badge-danger")
|
||||
.addClass("badge-success");
|
||||
break;
|
||||
case "hidden":
|
||||
$(".challenge-state")
|
||||
.removeClass("badge-success")
|
||||
.addClass("badge-danger");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ezToast({
|
||||
title: "Success",
|
||||
body: "Your challenge has been updated!"
|
||||
|
@ -432,16 +447,6 @@ $(() => {
|
|||
|
||||
$("#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);
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import "./main";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import { ezAlert, ezQuery } from "core/ezq";
|
||||
|
||||
function deleteSelectedChallenges(event) {
|
||||
let challengeIDs = $("input[data-challenge-id]:checked").map(function() {
|
||||
return $(this).data("challenge-id");
|
||||
});
|
||||
let target = challengeIDs.length === 1 ? "challenge" : "challenges";
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Challenges",
|
||||
body: `Are you sure you want to delete ${challengeIDs.length} ${target}?`,
|
||||
success: function() {
|
||||
const reqs = [];
|
||||
for (var chalID of challengeIDs) {
|
||||
reqs.push(
|
||||
CTFd.fetch(`/api/v1/challenges/${chalID}`, {
|
||||
method: "DELETE"
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bulkEditChallenges(event) {
|
||||
let challengeIDs = $("input[data-challenge-id]:checked").map(function() {
|
||||
return $(this).data("challenge-id");
|
||||
});
|
||||
|
||||
ezAlert({
|
||||
title: "Edit Challenges",
|
||||
body: $(`
|
||||
<form id="challenges-bulk-edit">
|
||||
<div class="form-group">
|
||||
<label>Category</label>
|
||||
<input type="text" name="category" data-initial="" value="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Value</label>
|
||||
<input type="number" name="value" data-initial="" value="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>State</label>
|
||||
<select name="state" data-initial="">
|
||||
<option value="">--</option>
|
||||
<option value="visible">Visible</option>
|
||||
<option value="hidden">Hidden</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
`),
|
||||
button: "Submit",
|
||||
success: function() {
|
||||
let data = $("#challenges-bulk-edit").serializeJSON(true);
|
||||
const reqs = [];
|
||||
for (var chalID of challengeIDs) {
|
||||
reqs.push(
|
||||
CTFd.fetch(`/api/v1/challenges/${chalID}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$("#challenges-delete-button").click(deleteSelectedChallenges);
|
||||
$("#challenges-edit-button").click(bulkEditChallenges);
|
||||
});
|
|
@ -7,6 +7,8 @@ import CTFd from "core/CTFd";
|
|||
import { default as helpers } from "core/helpers";
|
||||
import $ from "jquery";
|
||||
import { ezQuery, ezProgressBar } from "core/ezq";
|
||||
import CodeMirror from "codemirror";
|
||||
import "codemirror/mode/htmlmixed/htmlmixed.js";
|
||||
|
||||
function loadTimestamp(place, timestamp) {
|
||||
if (typeof timestamp == "string") {
|
||||
|
@ -218,10 +220,6 @@ function exportConfig(event) {
|
|||
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);
|
||||
|
@ -233,6 +231,20 @@ function insertTimezones(target) {
|
|||
}
|
||||
|
||||
$(() => {
|
||||
CodeMirror.fromTextArea(document.getElementById("theme-header"), {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: "htmlmixed",
|
||||
htmlMode: true
|
||||
});
|
||||
|
||||
CodeMirror.fromTextArea(document.getElementById("theme-footer"), {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: "htmlmixed",
|
||||
htmlMode: true
|
||||
});
|
||||
|
||||
insertTimezones($("#start-timezone"));
|
||||
insertTimezones($("#end-timezone"));
|
||||
insertTimezones($("#freeze-timezone"));
|
||||
|
@ -242,7 +254,6 @@ $(() => {
|
|||
$("#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 = $("#theme-header").val();
|
||||
|
@ -271,12 +282,6 @@ $(() => {
|
|||
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();
|
||||
|
|
|
@ -4,6 +4,7 @@ import $ from "jquery";
|
|||
import CTFd from "core/CTFd";
|
||||
import { default as helpers } from "core/helpers";
|
||||
import CodeMirror from "codemirror";
|
||||
import "codemirror/mode/htmlmixed/htmlmixed.js";
|
||||
import { ezQuery, ezToast } from "core/ezq";
|
||||
|
||||
function get_filetype_icon_class(filename) {
|
||||
|
@ -206,7 +207,7 @@ $(() => {
|
|||
{
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: "xml",
|
||||
mode: "htmlmixed",
|
||||
htmlMode: true
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,37 +1,33 @@
|
|||
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");
|
||||
function deleteSelectedUsers(event) {
|
||||
let pageIDs = $("input[data-page-id]:checked").map(function() {
|
||||
return $(this).data("page-id");
|
||||
});
|
||||
let target = pageIDs.length === 1 ? "page" : "pages";
|
||||
|
||||
ezQuery({
|
||||
title: "Delete " + htmlEntities(name),
|
||||
body: "Are you sure you want to delete {0}?".format(
|
||||
"<strong>" + htmlEntities(name) + "</strong>"
|
||||
),
|
||||
title: "Delete Pages",
|
||||
body: `Are you sure you want to delete ${pageIDs.length} ${target}?`,
|
||||
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();
|
||||
}
|
||||
});
|
||||
const reqs = [];
|
||||
for (var pageID of pageIDs) {
|
||||
reqs.push(
|
||||
CTFd.fetch(`/api/v1/pages/${pageID}`, {
|
||||
method: "DELETE"
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".delete-page").click(deletePage);
|
||||
$("#pages-delete-button").click(deleteSelectedUsers);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import "./main";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import { ezAlert, ezQuery } from "core/ezq";
|
||||
|
||||
const api_func = {
|
||||
users: (x, y) => CTFd.api.patch_user_public({ userId: x }, y),
|
||||
|
@ -37,6 +38,48 @@ function toggleAccount() {
|
|||
});
|
||||
}
|
||||
|
||||
function toggleSelectedAccounts(accountIDs, action) {
|
||||
const params = {
|
||||
hidden: action === "hidden" ? true : false
|
||||
};
|
||||
const reqs = [];
|
||||
for (var accId of accountIDs) {
|
||||
reqs.push(api_func[CTFd.config.userMode](accId, params));
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function bulkToggleAccounts(event) {
|
||||
let accountIDs = $("input[data-account-id]:checked").map(function() {
|
||||
return $(this).data("account-id");
|
||||
});
|
||||
|
||||
ezAlert({
|
||||
title: "Toggle Visibility",
|
||||
body: $(`
|
||||
<form id="scoreboard-bulk-edit">
|
||||
<div class="form-group">
|
||||
<label>Visibility</label>
|
||||
<select name="visibility" data-initial="">
|
||||
<option value="">--</option>
|
||||
<option value="visible">Visible</option>
|
||||
<option value="hidden">Hidden</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
`),
|
||||
button: "Submit",
|
||||
success: function() {
|
||||
let data = $("#scoreboard-bulk-edit").serializeJSON(true);
|
||||
let state = data.visibility;
|
||||
toggleSelectedAccounts(accountIDs, state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".scoreboard-toggle").click(toggleAccount);
|
||||
$("#scoreboard-edit-button").click(bulkToggleAccounts);
|
||||
});
|
||||
|
|
|
@ -177,6 +177,59 @@ const graph_configs = {
|
|||
annotations
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
"#score-distribution-graph": {
|
||||
layout: annotations => ({
|
||||
title: "Score Distribution",
|
||||
xaxis: {
|
||||
title: "Score Bracket",
|
||||
showticklabels: true,
|
||||
type: "category"
|
||||
},
|
||||
yaxis: {
|
||||
title: "Number of {0}".format(
|
||||
CTFd.config.userMode.charAt(0).toUpperCase() +
|
||||
CTFd.config.userMode.slice(1)
|
||||
)
|
||||
},
|
||||
annotations: annotations
|
||||
}),
|
||||
data: () =>
|
||||
CTFd.fetch("/api/v1/statistics/scores/distribution").then(function(
|
||||
response
|
||||
) {
|
||||
return response.json();
|
||||
}),
|
||||
fn: () =>
|
||||
"CTFd_score_distribution_" + new Date().toISOString().slice(0, 19),
|
||||
format: response => {
|
||||
const data = response.data.brackets;
|
||||
const keys = [];
|
||||
const brackets = [];
|
||||
const sizes = [];
|
||||
|
||||
for (let key in data) {
|
||||
keys.push(parseInt(key));
|
||||
}
|
||||
keys.sort((a, b) => a - b);
|
||||
|
||||
let start = "<0";
|
||||
keys.map(key => {
|
||||
brackets.push("{0} - {1}".format(start, key));
|
||||
sizes.push(data[key]);
|
||||
start = key;
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
type: "bar",
|
||||
x: brackets,
|
||||
y: sizes,
|
||||
orientation: "v"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -40,6 +40,28 @@ function deleteCorrectSubmission(event) {
|
|||
});
|
||||
}
|
||||
|
||||
function deleteSelectedSubmissions(event) {
|
||||
let submissionIDs = $("input[data-submission-id]:checked").map(function() {
|
||||
return $(this).data("submission-id");
|
||||
});
|
||||
let target = submissionIDs.length === 1 ? "submission" : "submissions";
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Submissions",
|
||||
body: `Are you sure you want to delete ${submissionIDs.length} ${target}?`,
|
||||
success: function() {
|
||||
const reqs = [];
|
||||
for (var subId of submissionIDs) {
|
||||
reqs.push(CTFd.api.delete_submission({ submissionId: subId }));
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$(".delete-correct-submission").click(deleteCorrectSubmission);
|
||||
$("#submission-delete-button").click(deleteSelectedSubmissions);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import "./main";
|
|||
import $ from "jquery";
|
||||
import CTFd from "core/CTFd";
|
||||
import { htmlEntities } from "core/utils";
|
||||
import { ezQuery, ezBadge } from "core/ezq";
|
||||
import { ezAlert, ezQuery, ezBadge } from "core/ezq";
|
||||
import { createGraph, updateGraph } from "core/graphs";
|
||||
|
||||
function createTeam(event) {
|
||||
|
@ -80,6 +80,132 @@ function updateTeam(event) {
|
|||
});
|
||||
}
|
||||
|
||||
function deleteSelectedSubmissions(event, target) {
|
||||
let submissions;
|
||||
let type;
|
||||
let title;
|
||||
switch (target) {
|
||||
case "solves":
|
||||
submissions = $("input[data-submission-type=correct]:checked");
|
||||
type = "solve";
|
||||
title = "Solves";
|
||||
break;
|
||||
case "fails":
|
||||
submissions = $("input[data-submission-type=incorrect]:checked");
|
||||
type = "fail";
|
||||
title = "Fails";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
let submissionIDs = submissions.map(function() {
|
||||
return $(this).data("submission-id");
|
||||
});
|
||||
let target_string = submissionIDs.length === 1 ? type : type + "s";
|
||||
|
||||
ezQuery({
|
||||
title: `Delete ${title}`,
|
||||
body: `Are you sure you want to delete ${
|
||||
submissionIDs.length
|
||||
} ${target_string}?`,
|
||||
success: function() {
|
||||
const reqs = [];
|
||||
for (var subId of submissionIDs) {
|
||||
reqs.push(CTFd.api.delete_submission({ submissionId: subId }));
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteSelectedAwards(event) {
|
||||
let awardIDs = $("input[data-award-id]:checked").map(function() {
|
||||
return $(this).data("award-id");
|
||||
});
|
||||
let target = awardIDs.length === 1 ? "award" : "awards";
|
||||
|
||||
ezQuery({
|
||||
title: `Delete Awards`,
|
||||
body: `Are you sure you want to delete ${awardIDs.length} ${target}?`,
|
||||
success: function() {
|
||||
const reqs = [];
|
||||
for (var awardID of awardIDs) {
|
||||
let req = CTFd.fetch("/api/v1/awards/" + awardID, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
reqs.push(req);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function solveSelectedMissingChallenges(event) {
|
||||
event.preventDefault();
|
||||
let challengeIDs = $("input[data-missing-challenge-id]:checked").map(
|
||||
function() {
|
||||
return $(this).data("missing-challenge-id");
|
||||
}
|
||||
);
|
||||
let target = challengeIDs.length === 1 ? "challenge" : "challenges";
|
||||
|
||||
ezQuery({
|
||||
title: `Mark Correct`,
|
||||
body: `Are you sure you want to mark ${
|
||||
challengeIDs.length
|
||||
} challenges correct for ${htmlEntities(TEAM_NAME)}?`,
|
||||
success: function() {
|
||||
ezAlert({
|
||||
title: `User Attribution`,
|
||||
body: `
|
||||
Which user on ${htmlEntities(TEAM_NAME)} solved these challenges?
|
||||
<div class="pb-3" id="query-team-member-solve">
|
||||
${$("#team-member-select").html()}
|
||||
</div>
|
||||
`,
|
||||
button: "Mark Correct",
|
||||
success: function() {
|
||||
const USER_ID = $("#query-team-member-solve > select").val();
|
||||
const reqs = [];
|
||||
for (var challengeID of challengeIDs) {
|
||||
let params = {
|
||||
provided: "MARKED AS SOLVED BY ADMIN",
|
||||
user_id: USER_ID,
|
||||
team_id: TEAM_ID,
|
||||
challenge_id: challengeID,
|
||||
type: "correct"
|
||||
};
|
||||
|
||||
let req = CTFd.fetch("/api/v1/submissions", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
reqs.push(req);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const api_funcs = {
|
||||
team: [
|
||||
x => CTFd.api.get_team_solves({ teamId: x }),
|
||||
|
@ -221,6 +347,10 @@ $(() => {
|
|||
$("#team-award-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$(".addresses-team").click(function(event) {
|
||||
$("#team-addresses-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$("#user-award-form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
const params = $("#user-award-form").serializeJSON(true);
|
||||
|
@ -331,82 +461,20 @@ $(() => {
|
|||
});
|
||||
});
|
||||
|
||||
$(".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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
$("#solves-delete-button").click(function(e) {
|
||||
deleteSelectedSubmissions(e, "solves");
|
||||
});
|
||||
|
||||
$(".delete-award").click(function(e) {
|
||||
e.preventDefault();
|
||||
const award_id = $(this).attr("award-id");
|
||||
const award_name = $(this).attr("award-name");
|
||||
$("#fails-delete-button").click(function(e) {
|
||||
deleteSelectedSubmissions(e, "fails");
|
||||
});
|
||||
|
||||
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)
|
||||
);
|
||||
$("#awards-delete-button").click(function(e) {
|
||||
deleteSelectedAwards(e);
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
$("#missing-solve-button").click(function(e) {
|
||||
solveSelectedMissingChallenges(e);
|
||||
});
|
||||
|
||||
$("#team-info-create-form").submit(createTeam);
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import "./main";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import { ezAlert, ezQuery } from "core/ezq";
|
||||
|
||||
function deleteSelectedTeams(event) {
|
||||
let teamIDs = $("input[data-team-id]:checked").map(function() {
|
||||
return $(this).data("team-id");
|
||||
});
|
||||
let target = teamIDs.length === 1 ? "team" : "teams";
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Teams",
|
||||
body: `Are you sure you want to delete ${teamIDs.length} ${target}?`,
|
||||
success: function() {
|
||||
const reqs = [];
|
||||
for (var teamID of teamIDs) {
|
||||
reqs.push(
|
||||
CTFd.fetch(`/api/v1/teams/${teamID}`, {
|
||||
method: "DELETE"
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bulkEditTeams(event) {
|
||||
let teamIDs = $("input[data-team-id]:checked").map(function() {
|
||||
return $(this).data("team-id");
|
||||
});
|
||||
|
||||
ezAlert({
|
||||
title: "Edit Teams",
|
||||
body: $(`
|
||||
<form id="teams-bulk-edit">
|
||||
<div class="form-group">
|
||||
<label>Banned</label>
|
||||
<select name="banned" data-initial="">
|
||||
<option value="">--</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Hidden</label>
|
||||
<select name="hidden" data-initial="">
|
||||
<option value="">--</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
`),
|
||||
button: "Submit",
|
||||
success: function() {
|
||||
let data = $("#teams-bulk-edit").serializeJSON(true);
|
||||
const reqs = [];
|
||||
for (var teamID of teamIDs) {
|
||||
reqs.push(
|
||||
CTFd.fetch(`/api/v1/teams/${teamID}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$("#teams-delete-button").click(deleteSelectedTeams);
|
||||
$("#teams-edit-button").click(bulkEditTeams);
|
||||
});
|
|
@ -189,128 +189,115 @@ function emailUser(event) {
|
|||
});
|
||||
}
|
||||
|
||||
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");
|
||||
function deleteSelectedSubmissions(event, target) {
|
||||
let submissions;
|
||||
let type;
|
||||
let title;
|
||||
switch (target) {
|
||||
case "solves":
|
||||
submissions = $("input[data-submission-type=correct]:checked");
|
||||
type = "solve";
|
||||
title = "Solves";
|
||||
break;
|
||||
case "fails":
|
||||
submissions = $("input[data-submission-type=incorrect]:checked");
|
||||
type = "fail";
|
||||
title = "Fails";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
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();
|
||||
let submissionIDs = submissions.map(function() {
|
||||
return $(this).data("submission-id");
|
||||
});
|
||||
let target_string = submissionIDs.length === 1 ? type : type + "s";
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Submission",
|
||||
body: body,
|
||||
title: `Delete ${title}`,
|
||||
body: `Are you sure you want to delete ${
|
||||
submissionIDs.length
|
||||
} ${target_string}?`,
|
||||
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();
|
||||
}
|
||||
});
|
||||
const reqs = [];
|
||||
for (var subId of submissionIDs) {
|
||||
reqs.push(CTFd.api.delete_submission({ submissionId: subId }));
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
function deleteSelectedAwards(event) {
|
||||
let awardIDs = $("input[data-award-id]:checked").map(function() {
|
||||
return $(this).data("award-id");
|
||||
});
|
||||
let target = awardIDs.length === 1 ? "award" : "awards";
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Award",
|
||||
body: body,
|
||||
title: `Delete Awards`,
|
||||
body: `Are you sure you want to delete ${awardIDs.length} ${target}?`,
|
||||
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();
|
||||
const reqs = [];
|
||||
for (var awardID of awardIDs) {
|
||||
let req = CTFd.fetch("/api/v1/awards/" + awardID, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
reqs.push(req);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function correctUserSubmission(event) {
|
||||
function solveSelectedMissingChallenges(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)
|
||||
let challengeIDs = $("input[data-missing-challenge-id]:checked").map(
|
||||
function() {
|
||||
return $(this).data("missing-challenge-id");
|
||||
}
|
||||
);
|
||||
|
||||
const params = {
|
||||
provided: "MARKED AS SOLVED BY ADMIN",
|
||||
user_id: USER_ID,
|
||||
team_id: TEAM_ID,
|
||||
challenge_id: challenge_id,
|
||||
type: "correct"
|
||||
};
|
||||
let target = challengeIDs.length === 1 ? "challenge" : "challenges";
|
||||
|
||||
ezQuery({
|
||||
title: "Mark Correct",
|
||||
body: body,
|
||||
title: `Mark Correct`,
|
||||
body: `Are you sure you want to mark ${
|
||||
challengeIDs.length
|
||||
} correct for ${htmlEntities(USER_NAME)}?`,
|
||||
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 reqs = [];
|
||||
for (var challengeID of challengeIDs) {
|
||||
let params = {
|
||||
provided: "MARKED AS SOLVED BY ADMIN",
|
||||
user_id: USER_ID,
|
||||
team_id: TEAM_ID,
|
||||
challenge_id: challengeID,
|
||||
type: "correct"
|
||||
};
|
||||
|
||||
let req = CTFd.fetch("/api/v1/submissions", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
reqs.push(req);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -419,11 +406,27 @@ $(() => {
|
|||
$("#user-email-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$(".addresses-user").click(function(event) {
|
||||
$("#user-addresses-modal").modal("toggle");
|
||||
});
|
||||
|
||||
$("#user-mail-form").submit(emailUser);
|
||||
|
||||
$(".delete-submission").click(deleteUserSubmission);
|
||||
$(".delete-award").click(deleteUserAward);
|
||||
$(".correct-submission").click(correctUserSubmission);
|
||||
$("#solves-delete-button").click(function(e) {
|
||||
deleteSelectedSubmissions(e, "solves");
|
||||
});
|
||||
|
||||
$("#fails-delete-button").click(function(e) {
|
||||
deleteSelectedSubmissions(e, "fails");
|
||||
});
|
||||
|
||||
$("#awards-delete-button").click(function(e) {
|
||||
deleteSelectedAwards(e);
|
||||
});
|
||||
|
||||
$("#missing-solve-button").click(function(e) {
|
||||
solveSelectedMissingChallenges(e);
|
||||
});
|
||||
|
||||
$("#user-info-create-form").submit(createUser);
|
||||
|
||||
|
|
|
@ -1 +1,88 @@
|
|||
import "./main";
|
||||
import CTFd from "core/CTFd";
|
||||
import $ from "jquery";
|
||||
import { ezAlert, ezQuery } from "core/ezq";
|
||||
|
||||
function deleteSelectedUsers(event) {
|
||||
let userIDs = $("input[data-user-id]:checked").map(function() {
|
||||
return $(this).data("user-id");
|
||||
});
|
||||
let target = userIDs.length === 1 ? "user" : "users";
|
||||
|
||||
ezQuery({
|
||||
title: "Delete Users",
|
||||
body: `Are you sure you want to delete ${userIDs.length} ${target}?`,
|
||||
success: function() {
|
||||
const reqs = [];
|
||||
for (var userID of userIDs) {
|
||||
reqs.push(
|
||||
CTFd.fetch(`/api/v1/users/${userID}`, {
|
||||
method: "DELETE"
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bulkEditUsers(event) {
|
||||
let userIDs = $("input[data-user-id]:checked").map(function() {
|
||||
return $(this).data("user-id");
|
||||
});
|
||||
|
||||
ezAlert({
|
||||
title: "Edit Users",
|
||||
body: $(`
|
||||
<form id="users-bulk-edit">
|
||||
<div class="form-group">
|
||||
<label>Verified</label>
|
||||
<select name="verified" data-initial="">
|
||||
<option value="">--</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Banned</label>
|
||||
<select name="banned" data-initial="">
|
||||
<option value="">--</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Hidden</label>
|
||||
<select name="hidden" data-initial="">
|
||||
<option value="">--</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
`),
|
||||
button: "Submit",
|
||||
success: function() {
|
||||
let data = $("#users-bulk-edit").serializeJSON(true);
|
||||
const reqs = [];
|
||||
for (var userID of userIDs) {
|
||||
reqs.push(
|
||||
CTFd.fetch(`/api/v1/users/${userID}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(reqs).then(responses => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$("#users-delete-button").click(deleteSelectedUsers);
|
||||
$("#users-edit-button").click(bulkEditUsers);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "bootstrap/dist/js/bootstrap.bundle";
|
||||
import { makeSortableTables } from "core/utils";
|
||||
import $ from "jquery";
|
||||
|
||||
export default () => {
|
||||
|
@ -45,6 +46,27 @@ export default () => {
|
|||
return false;
|
||||
});
|
||||
|
||||
$("[data-checkbox]").click(function(e) {
|
||||
if ($(e.target).is("input[type=checkbox]")) {
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
let checkbox = $(this).find("input[type=checkbox]");
|
||||
// Doing it this way with an event allows data-checkbox-all to work
|
||||
checkbox.click();
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
|
||||
$("[data-checkbox-all]").on("click change", function(e) {
|
||||
const checked = $(this).prop("checked");
|
||||
const idx = $(this).index() + 1;
|
||||
$(this)
|
||||
.closest("table")
|
||||
.find(`tr td:nth-child(${idx}) input[type=checkbox]`)
|
||||
.prop("checked", checked);
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
|
||||
$("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")) {
|
||||
|
@ -52,6 +74,29 @@ export default () => {
|
|||
}
|
||||
});
|
||||
|
||||
$(".page-select").change(function() {
|
||||
let url = new URL(window.location);
|
||||
url.searchParams.set("page", this.value);
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
|
||||
$('a[data-toggle="tab"]').on("shown.bs.tab", function(e) {
|
||||
sessionStorage.setItem("activeTab", $(e.target).attr("href"));
|
||||
});
|
||||
|
||||
let activeTab = sessionStorage.getItem("activeTab");
|
||||
if (activeTab) {
|
||||
let target = $(
|
||||
`.nav-tabs a[href="${activeTab}"], .nav-pills a[href="${activeTab}"]`
|
||||
);
|
||||
if (target.length) {
|
||||
target.tab("show");
|
||||
} else {
|
||||
sessionStorage.removeItem("activeTab");
|
||||
}
|
||||
}
|
||||
|
||||
makeSortableTables();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,4 +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}
|
||||
#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}.sort-col{cursor:pointer}input[type="checkbox"]{cursor:pointer}
|
||||
|
||||
|
|
|
@ -1 +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}
|
||||
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}.sort-col,input[type=checkbox],tr[data-href]{cursor:pointer}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,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/challenges": 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/challenges.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ "./CTFd/themes/admin/assets/js/pages/challenges.js":
|
||||
/*!*********************************************************!*\
|
||||
!*** ./CTFd/themes/admin/assets/js/pages/challenges.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 _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 deleteSelectedChallenges(event) {\n var challengeIDs = (0, _jquery.default)(\"input[data-challenge-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"challenge-id\");\n });\n var target = challengeIDs.length === 1 ? \"challenge\" : \"challenges\";\n (0, _ezq.ezQuery)({\n title: \"Delete Challenges\",\n body: \"Are you sure you want to delete \".concat(challengeIDs.length, \" \").concat(target, \"?\"),\n success: function success() {\n var reqs = [];\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = challengeIDs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var chalID = _step.value;\n reqs.push(_CTFd.default.fetch(\"/api/v1/challenges/\".concat(chalID), {\n method: \"DELETE\"\n }));\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return != null) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\nfunction bulkEditChallenges(event) {\n var challengeIDs = (0, _jquery.default)(\"input[data-challenge-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"challenge-id\");\n });\n (0, _ezq.ezAlert)({\n title: \"Edit Challenges\",\n body: (0, _jquery.default)(\"\\n <form id=\\\"challenges-bulk-edit\\\">\\n <div class=\\\"form-group\\\">\\n <label>Category</label>\\n <input type=\\\"text\\\" name=\\\"category\\\" data-initial=\\\"\\\" value=\\\"\\\">\\n </div>\\n <div class=\\\"form-group\\\">\\n <label>Value</label>\\n <input type=\\\"number\\\" name=\\\"value\\\" data-initial=\\\"\\\" value=\\\"\\\">\\n </div>\\n <div class=\\\"form-group\\\">\\n <label>State</label>\\n <select name=\\\"state\\\" data-initial=\\\"\\\">\\n <option value=\\\"\\\">--</option>\\n <option value=\\\"visible\\\">Visible</option>\\n <option value=\\\"hidden\\\">Hidden</option>\\n </select>\\n </div>\\n </form>\\n \"),\n button: \"Submit\",\n success: function success() {\n var data = (0, _jquery.default)(\"#challenges-bulk-edit\").serializeJSON(true);\n var reqs = [];\n var _iteratorNormalCompletion2 = true;\n var _didIteratorError2 = false;\n var _iteratorError2 = undefined;\n\n try {\n for (var _iterator2 = challengeIDs[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {\n var chalID = _step2.value;\n reqs.push(_CTFd.default.fetch(\"/api/v1/challenges/\".concat(chalID), {\n method: \"PATCH\",\n body: JSON.stringify(data)\n }));\n }\n } catch (err) {\n _didIteratorError2 = true;\n _iteratorError2 = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion2 && _iterator2.return != null) {\n _iterator2.return();\n }\n } finally {\n if (_didIteratorError2) {\n throw _iteratorError2;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#challenges-delete-button\").click(deleteSelectedChallenges);\n (0, _jquery.default)(\"#challenges-edit-button\").click(bulkEditChallenges);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/challenges.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@
|
|||
/******/
|
||||
/******/
|
||||
/******/ // 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"]);
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/main.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@
|
|||
/******/
|
||||
/******/
|
||||
/******/ // 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"]);
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/notifications.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@
|
|||
/******/
|
||||
/******/
|
||||
/******/ // 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"]);
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/pages.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
|
@ -162,7 +162,7 @@
|
|||
/***/ (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?");
|
||||
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 _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 deleteSelectedUsers(event) {\n var pageIDs = (0, _jquery.default)(\"input[data-page-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"page-id\");\n });\n var target = pageIDs.length === 1 ? \"page\" : \"pages\";\n (0, _ezq.ezQuery)({\n title: \"Delete Pages\",\n body: \"Are you sure you want to delete \".concat(pageIDs.length, \" \").concat(target, \"?\"),\n success: function success() {\n var reqs = [];\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = pageIDs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var pageID = _step.value;\n reqs.push(_CTFd.default.fetch(\"/api/v1/pages/\".concat(pageID), {\n method: \"DELETE\"\n }));\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return != null) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#pages-delete-button\").click(deleteSelectedUsers);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/pages.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@
|
|||
/******/
|
||||
/******/
|
||||
/******/ // 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"]);
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/reset.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@
|
|||
/******/
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/scoreboard.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/scoreboard.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
|
@ -162,7 +162,7 @@
|
|||
/***/ (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\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar api_func = {\n users: function users(x, y) {\n return _CTFd.default.api.patch_user_public({\n userId: x\n }, y);\n },\n teams: function teams(x, y) {\n return _CTFd.default.api.patch_team_public({\n teamId: x\n }, y);\n }\n};\n\nfunction toggleAccount() {\n var $btn = (0, _jquery.default)(this);\n var id = $btn.data(\"account-id\");\n var state = $btn.data(\"state\");\n var hidden = undefined;\n\n if (state === \"visible\") {\n hidden = true;\n } else if (state === \"hidden\") {\n hidden = false;\n }\n\n var params = {\n hidden: hidden\n };\n\n api_func[_CTFd.default.config.userMode](id, params).then(function (response) {\n if (response.success) {\n if (hidden) {\n $btn.data(\"state\", \"hidden\");\n $btn.addClass(\"btn-danger\").removeClass(\"btn-success\");\n $btn.text(\"Hidden\");\n } else {\n $btn.data(\"state\", \"visible\");\n $btn.addClass(\"btn-success\").removeClass(\"btn-danger\");\n $btn.text(\"Visible\");\n }\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".scoreboard-toggle\").click(toggleAccount);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/scoreboard.js?");
|
||||
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 _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar api_func = {\n users: function users(x, y) {\n return _CTFd.default.api.patch_user_public({\n userId: x\n }, y);\n },\n teams: function teams(x, y) {\n return _CTFd.default.api.patch_team_public({\n teamId: x\n }, y);\n }\n};\n\nfunction toggleAccount() {\n var $btn = (0, _jquery.default)(this);\n var id = $btn.data(\"account-id\");\n var state = $btn.data(\"state\");\n var hidden = undefined;\n\n if (state === \"visible\") {\n hidden = true;\n } else if (state === \"hidden\") {\n hidden = false;\n }\n\n var params = {\n hidden: hidden\n };\n\n api_func[_CTFd.default.config.userMode](id, params).then(function (response) {\n if (response.success) {\n if (hidden) {\n $btn.data(\"state\", \"hidden\");\n $btn.addClass(\"btn-danger\").removeClass(\"btn-success\");\n $btn.text(\"Hidden\");\n } else {\n $btn.data(\"state\", \"visible\");\n $btn.addClass(\"btn-success\").removeClass(\"btn-danger\");\n $btn.text(\"Visible\");\n }\n }\n });\n}\n\nfunction toggleSelectedAccounts(accountIDs, action) {\n var params = {\n hidden: action === \"hidden\" ? true : false\n };\n var reqs = [];\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = accountIDs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var accId = _step.value;\n reqs.push(api_func[_CTFd.default.config.userMode](accId, params));\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return != null) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n}\n\nfunction bulkToggleAccounts(event) {\n var accountIDs = (0, _jquery.default)(\"input[data-account-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"account-id\");\n });\n (0, _ezq.ezAlert)({\n title: \"Toggle Visibility\",\n body: (0, _jquery.default)(\"\\n <form id=\\\"scoreboard-bulk-edit\\\">\\n <div class=\\\"form-group\\\">\\n <label>Visibility</label>\\n <select name=\\\"visibility\\\" data-initial=\\\"\\\">\\n <option value=\\\"\\\">--</option>\\n <option value=\\\"visible\\\">Visible</option>\\n <option value=\\\"hidden\\\">Hidden</option>\\n </select>\\n </div>\\n </form>\\n \"),\n button: \"Submit\",\n success: function success() {\n var data = (0, _jquery.default)(\"#scoreboard-bulk-edit\").serializeJSON(true);\n var state = data.visibility;\n toggleSelectedAccounts(accountIDs, state);\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".scoreboard-toggle\").click(toggleAccount);\n (0, _jquery.default)(\"#scoreboard-edit-button\").click(bulkToggleAccounts);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/scoreboard.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@
|
|||
/******/
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/submissions.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/submissions.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
|
@ -162,7 +162,7 @@
|
|||
/***/ (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 deleteCorrectSubmission(event) {\n var key_id = (0, _jquery.default)(this).data(\"submission-id\");\n var $elem = (0, _jquery.default)(this).parent().parent();\n var chal_name = $elem.find(\".chal\").text().trim();\n var team_name = $elem.find(\".team\").text().trim();\n var row = (0, _jquery.default)(this).parent().parent();\n (0, _ezq.ezQuery)({\n title: \"Delete Submission\",\n body: \"Are you sure you want to delete correct submission from {0} for challenge {1}\".format(\"<strong>\" + (0, _utils.htmlEntities)(team_name) + \"</strong>\", \"<strong>\" + (0, _utils.htmlEntities)(chal_name) + \"</strong>\"),\n success: function success() {\n _CTFd.default.api.delete_submission({\n submissionId: key_id\n }).then(function (response) {\n if (response.success) {\n row.remove();\n }\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".delete-correct-submission\").click(deleteCorrectSubmission);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/submissions.js?");
|
||||
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 deleteCorrectSubmission(event) {\n var key_id = (0, _jquery.default)(this).data(\"submission-id\");\n var $elem = (0, _jquery.default)(this).parent().parent();\n var chal_name = $elem.find(\".chal\").text().trim();\n var team_name = $elem.find(\".team\").text().trim();\n var row = (0, _jquery.default)(this).parent().parent();\n (0, _ezq.ezQuery)({\n title: \"Delete Submission\",\n body: \"Are you sure you want to delete correct submission from {0} for challenge {1}\".format(\"<strong>\" + (0, _utils.htmlEntities)(team_name) + \"</strong>\", \"<strong>\" + (0, _utils.htmlEntities)(chal_name) + \"</strong>\"),\n success: function success() {\n _CTFd.default.api.delete_submission({\n submissionId: key_id\n }).then(function (response) {\n if (response.success) {\n row.remove();\n }\n });\n }\n });\n}\n\nfunction deleteSelectedSubmissions(event) {\n var submissionIDs = (0, _jquery.default)(\"input[data-submission-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"submission-id\");\n });\n var target = submissionIDs.length === 1 ? \"submission\" : \"submissions\";\n (0, _ezq.ezQuery)({\n title: \"Delete Submissions\",\n body: \"Are you sure you want to delete \".concat(submissionIDs.length, \" \").concat(target, \"?\"),\n success: function success() {\n var reqs = [];\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = submissionIDs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var subId = _step.value;\n reqs.push(_CTFd.default.api.delete_submission({\n submissionId: subId\n }));\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return != null) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".delete-correct-submission\").click(deleteCorrectSubmission);\n (0, _jquery.default)(\"#submission-delete-button\").click(deleteSelectedSubmissions);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/submissions.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,66 @@
|
|||
/******/ (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/teams": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ var deferredModules = [];
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
|
@ -79,9 +138,18 @@
|
|||
/******/ // __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;
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = "./CTFd/themes/admin/assets/js/pages/teams.js");
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/teams.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
@ -94,7 +162,7 @@
|
|||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/teams.js?");
|
||||
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 _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 deleteSelectedTeams(event) {\n var teamIDs = (0, _jquery.default)(\"input[data-team-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"team-id\");\n });\n var target = teamIDs.length === 1 ? \"team\" : \"teams\";\n (0, _ezq.ezQuery)({\n title: \"Delete Teams\",\n body: \"Are you sure you want to delete \".concat(teamIDs.length, \" \").concat(target, \"?\"),\n success: function success() {\n var reqs = [];\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = teamIDs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var teamID = _step.value;\n reqs.push(_CTFd.default.fetch(\"/api/v1/teams/\".concat(teamID), {\n method: \"DELETE\"\n }));\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return != null) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\nfunction bulkEditTeams(event) {\n var teamIDs = (0, _jquery.default)(\"input[data-team-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"team-id\");\n });\n (0, _ezq.ezAlert)({\n title: \"Edit Teams\",\n body: (0, _jquery.default)(\"\\n <form id=\\\"teams-bulk-edit\\\">\\n <div class=\\\"form-group\\\">\\n <label>Banned</label>\\n <select name=\\\"banned\\\" data-initial=\\\"\\\">\\n <option value=\\\"\\\">--</option>\\n <option value=\\\"true\\\">True</option>\\n <option value=\\\"false\\\">False</option>\\n </select>\\n </div>\\n <div class=\\\"form-group\\\">\\n <label>Hidden</label>\\n <select name=\\\"hidden\\\" data-initial=\\\"\\\">\\n <option value=\\\"\\\">--</option>\\n <option value=\\\"true\\\">True</option>\\n <option value=\\\"false\\\">False</option>\\n </select>\\n </div>\\n </form>\\n \"),\n button: \"Submit\",\n success: function success() {\n var data = (0, _jquery.default)(\"#teams-bulk-edit\").serializeJSON(true);\n var reqs = [];\n var _iteratorNormalCompletion2 = true;\n var _didIteratorError2 = false;\n var _iteratorError2 = undefined;\n\n try {\n for (var _iterator2 = teamIDs[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {\n var teamID = _step2.value;\n reqs.push(_CTFd.default.fetch(\"/api/v1/teams/\".concat(teamID), {\n method: \"PATCH\",\n body: JSON.stringify(data)\n }));\n }\n } catch (err) {\n _didIteratorError2 = true;\n _iteratorError2 = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion2 && _iterator2.return != null) {\n _iterator2.return();\n }\n } finally {\n if (_didIteratorError2) {\n throw _iteratorError2;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#teams-delete-button\").click(deleteSelectedTeams);\n (0, _jquery.default)(\"#teams-edit-button\").click(bulkEditTeams);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/teams.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@
|
|||
/******/
|
||||
/******/
|
||||
/******/ // add entry module to deferred list
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/users.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
|
||||
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/users.js","helpers","vendor","default~pages/challenge~pages/challenges~pages/configs~pages/editor~pages/main~pages/notifications~p~d5a3cc0a"]);
|
||||
/******/ // run deferred modules when ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ })
|
||||
|
@ -162,7 +162,7 @@
|
|||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
;
|
||||
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/users.js?");
|
||||
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 _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 deleteSelectedUsers(event) {\n var userIDs = (0, _jquery.default)(\"input[data-user-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"user-id\");\n });\n var target = userIDs.length === 1 ? \"user\" : \"users\";\n (0, _ezq.ezQuery)({\n title: \"Delete Users\",\n body: \"Are you sure you want to delete \".concat(userIDs.length, \" \").concat(target, \"?\"),\n success: function success() {\n var reqs = [];\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = userIDs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var userID = _step.value;\n reqs.push(_CTFd.default.fetch(\"/api/v1/users/\".concat(userID), {\n method: \"DELETE\"\n }));\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return != null) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\nfunction bulkEditUsers(event) {\n var userIDs = (0, _jquery.default)(\"input[data-user-id]:checked\").map(function () {\n return (0, _jquery.default)(this).data(\"user-id\");\n });\n (0, _ezq.ezAlert)({\n title: \"Edit Users\",\n body: (0, _jquery.default)(\"\\n <form id=\\\"users-bulk-edit\\\">\\n <div class=\\\"form-group\\\">\\n <label>Verified</label>\\n <select name=\\\"verified\\\" data-initial=\\\"\\\">\\n <option value=\\\"\\\">--</option>\\n <option value=\\\"true\\\">True</option>\\n <option value=\\\"false\\\">False</option>\\n </select>\\n </div>\\n <div class=\\\"form-group\\\">\\n <label>Banned</label>\\n <select name=\\\"banned\\\" data-initial=\\\"\\\">\\n <option value=\\\"\\\">--</option>\\n <option value=\\\"true\\\">True</option>\\n <option value=\\\"false\\\">False</option>\\n </select>\\n </div>\\n <div class=\\\"form-group\\\">\\n <label>Hidden</label>\\n <select name=\\\"hidden\\\" data-initial=\\\"\\\">\\n <option value=\\\"\\\">--</option>\\n <option value=\\\"true\\\">True</option>\\n <option value=\\\"false\\\">False</option>\\n </select>\\n </div>\\n </form>\\n \"),\n button: \"Submit\",\n success: function success() {\n var data = (0, _jquery.default)(\"#users-bulk-edit\").serializeJSON(true);\n var reqs = [];\n var _iteratorNormalCompletion2 = true;\n var _didIteratorError2 = false;\n var _iteratorError2 = undefined;\n\n try {\n for (var _iterator2 = userIDs[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {\n var userID = _step2.value;\n reqs.push(_CTFd.default.fetch(\"/api/v1/users/\".concat(userID), {\n method: \"PATCH\",\n body: JSON.stringify(data)\n }));\n }\n } catch (err) {\n _didIteratorError2 = true;\n _iteratorError2 = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion2 && _iterator2.return != null) {\n _iterator2.return();\n }\n } finally {\n if (_didIteratorError2) {\n throw _iteratorError2;\n }\n }\n }\n\n Promise.all(reqs).then(function (responses) {\n window.location.reload();\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#users-delete-button\").click(deleteSelectedUsers);\n (0, _jquery.default)(\"#users-edit-button\").click(bulkEditUsers);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/users.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -19,7 +19,7 @@
|
|||
<h2 class="text-center">{{ challenge.type }}</h2>
|
||||
{% set badge_state = 'badge-danger' if challenge.state == 'hidden' else 'badge-success' %}
|
||||
<h5>
|
||||
<span class="badge {{ badge_state }}">
|
||||
<span class="badge {{ badge_state }} challenge-state">
|
||||
{{ challenge.state }}
|
||||
</span>
|
||||
</h5>
|
||||
|
|
|
@ -18,28 +18,84 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% if q and field %}
|
||||
<h5 class="text-muted text-center">Searching for challenges with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong></h5>
|
||||
<h6 class="text-muted text-center pb-3">{{ total }} results</h6>
|
||||
{% endif %}
|
||||
|
||||
<form method="GET" class="form-inline">
|
||||
<div class="form-group col-md-2">
|
||||
<label for="sel1" class="sr-only" >Search Field</label>
|
||||
<select class="form-control custom-select w-100" id="sel1" name="field">
|
||||
<option value="name" {% if field == 'name' %}selected{% endif %}>Name</option>
|
||||
<option value="id" {% if field == 'id' %}selected{% endif %}>ID</option>
|
||||
<option value="category" {% if field == 'category' %}selected{% endif %}>Category</option>
|
||||
<option value="type" {% if field == 'type' %}selected{% endif %}>Type</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-8">
|
||||
<label for="challenges-search" class="sr-only">Parameter</label>
|
||||
<input type="text" class="form-control w-100" id="challenges-search" name="q" placeholder="Search for matching challenge" {% if q %}value="{{q}}"{% endif %}>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label class="sr-only">Search</label>
|
||||
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-search" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="float-right pb-3">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary" data-toggle="tooltip" title="Edit Challenges" id="challenges-edit-button">
|
||||
<i class="btn-fa fas fa-pencil-alt"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger" data-toggle="tooltip" title="Delete Challenges" id="challenges-delete-button">
|
||||
<i class="btn-fa fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div>
|
||||
<table id="challenges" class="table table-striped">
|
||||
<table id="challenges" class="table table-striped border">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>ID</b></td>
|
||||
<td><b>Name</b></td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell"><b>Category</b></td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell"><b>Value</b></td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell"><b>Type</b></td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>State</b></td>
|
||||
<td class="d-block border-right border-bottom text-center" data-checkbox>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" data-checkbox-all>
|
||||
</div>
|
||||
</td>
|
||||
<th class="sort-col text-center"><b>ID</b></th>
|
||||
<th class="sort-col"><b>Name</b></th>
|
||||
<th class="d-none d-md-table-cell d-lg-table-cell sort-col"><b>Category</b></th>
|
||||
<th class="d-none d-md-table-cell d-lg-table-cell sort-col text-center"><b>Value</b></th>
|
||||
<th class="d-none d-md-table-cell d-lg-table-cell sort-col text-center"><b>Type</b></th>
|
||||
<th class="d-none d-md-table-cell d-lg-table-cell sort-col text-center"><b>State</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for challenge in challenges %}
|
||||
<tr data-href="{{ url_for('admin.challenges_detail', challenge_id=challenge.id) }}">
|
||||
<td>{{ challenge.id }}</td>
|
||||
<td class="d-block border-right text-center" data-checkbox>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" value="{{ challenge.id }}" data-challenge-id="{{ challenge.id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">{{ challenge.id }}</td>
|
||||
<td><a href="{{ url_for('admin.challenges_detail', challenge_id=challenge.id) }}">{{ challenge.name }}</a></td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell">{{ challenge.category }}</td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell">{{ challenge.value }}</td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell">{{ challenge.type }}</td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center">{{ challenge.value }}</td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center">{{ challenge.type }}</td>
|
||||
<td class="d-none d-md-table-cell d-lg-table-cell text-center">
|
||||
{% set badge_state = 'badge-danger' if challenge.state == 'hidden' else 'badge-success' %}
|
||||
<span class="badge {{ badge_state }}">{{ challenge.state }}</span>
|
||||
|
@ -56,3 +112,7 @@
|
|||
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
||||
{% block entrypoint %}
|
||||
<script defer src="{{ url_for('views.themes', theme='admin', path='js/pages/challenges.js') }}"></script>
|
||||
{% endblock %}
|
|
@ -50,7 +50,7 @@
|
|||
<small class="form-text text-muted">Files distributed along with your challenge</small>
|
||||
</label>
|
||||
<input class="form-control-file" type="file" name="file" multiple="multiple">
|
||||
<sub>Attach multiple files using Control+Click or Cmd+Click</sub>
|
||||
<sub class="text-muted">Attach multiple files using Control+Click or Cmd+Click</sub>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('views.themes', theme='admin', path='css/codemirror.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
or choose to form teams.
|
||||
</small>
|
||||
</label>
|
||||
<div data-toggle="tooltip" data-placement="bottom" title="In order to change User Mode you must reset your CTF">
|
||||
<div data-toggle="tooltip" data-placement="bottom" title="In order to change User Mode you must reset your CTF and delete all accounts">
|
||||
<select class="form-control custom-select" id="user_mode" name="user_mode" disabled="true" style="z-index: -1;">
|
||||
<option value="teams" {% if user_mode == 'teams' %}selected{% endif %}>
|
||||
Teams
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<div class="col-md-12">
|
||||
<p>This is the time when the competition will begin. Challenges will automatically
|
||||
unlock and users will be able to submit answers.</p>
|
||||
<sub class="text-right mb-3">* All time fields required</sub>
|
||||
<sub class="text-muted text-right mb-3">* All time fields required</sub>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
|
@ -87,7 +87,7 @@
|
|||
<div class="col-md-12">
|
||||
<p>This is the time when the competition will end. Challenges will automatically
|
||||
close and users won't be able to submit answers.</p>
|
||||
<sub class="text-right mb-3">* All time fields required</sub>
|
||||
<sub class="text-muted text-right mb-3">* All time fields required</sub>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
|
@ -159,7 +159,7 @@
|
|||
<p>Freeze time specifies the timestamp that the competition will be frozen to.
|
||||
All solves before the freeze time will be shown, but new solves won't be shown to
|
||||
users. </p>
|
||||
<sub class="text-right mb-3">* All time fields required</sub>
|
||||
<sub class="text-muted text-right mb-3">* All time fields required</sub>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<form id="file-add-form" method="POST">
|
||||
<div class="form-group">
|
||||
<input class="form-control-file" type="file" name="file" multiple="multiple">
|
||||
<sub>Attach multiple files using Control+Click or Cmd+Click</sub>
|
||||
<sub class="text-muted">Attach multiple files using Control+Click or Cmd+Click</sub>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-success float-right" id="submit-files">Upload</button>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="form-group">
|
||||
<label>Tag
|
||||
<br>
|
||||
<small>Type tag and press Enter</small>
|
||||
<small class="text-muted">Type tag and press Enter</small>
|
||||
</label>
|
||||
<input id="tags-add-input" maxlength="80" type="text" class="form-control">
|
||||
</div>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue