From a47cdb7ce1af3735f82022f278a33719152138e4 Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Thu, 11 Jun 2020 02:32:20 -0400 Subject: [PATCH] WIP: Add form globals (#1469) * Work on #1467 * Creates the Form global which will contain all forms. --- CTFd/admin/__init__.py | 6 +- CTFd/auth.py | 18 ++- CTFd/constants/config.py | 4 + CTFd/forms/__init__.py | 49 ++++++ CTFd/forms/auth.py | 33 ++++ CTFd/forms/awards.py | 30 ++++ CTFd/forms/challenges.py | 30 ++++ CTFd/forms/config.py | 62 ++++++++ CTFd/forms/email.py | 10 ++ CTFd/forms/fields.py | 17 +++ CTFd/forms/notifications.py | 26 ++++ CTFd/forms/pages.py | 33 ++++ CTFd/forms/self.py | 22 +++ CTFd/forms/setup.py | 66 ++++++++ CTFd/forms/submissions.py | 16 ++ CTFd/forms/teams.py | 62 ++++++++ CTFd/forms/users.py | 42 +++++ CTFd/themes/admin/assets/js/pages/editor.js | 5 +- .../admin/static/js/pages/editor.dev.js | 2 +- .../admin/static/js/pages/editor.min.js | 2 +- .../templates/challenges/challenges.html | 26 ++-- .../admin/templates/configs/accounts.html | 62 +++----- .../admin/templates/configs/backup.html | 10 +- CTFd/themes/admin/templates/editor.html | 85 ++++------- .../admin/templates/modals/awards/create.html | 22 +-- .../templates/modals/challenges/files.html | 10 +- .../admin/templates/modals/mail/send.html | 16 +- .../admin/templates/modals/teams/create.html | 53 +++---- .../admin/templates/modals/teams/edit.html | 53 +++---- .../admin/templates/modals/users/create.html | 71 ++++----- .../admin/templates/modals/users/edit.html | 66 +++----- .../themes/admin/templates/notifications.html | 70 ++++----- CTFd/themes/admin/templates/reset.html | 33 ++-- CTFd/themes/admin/templates/submissions.html | 24 +-- CTFd/themes/admin/templates/teams/teams.html | 25 +-- CTFd/themes/admin/templates/users/users.html | 27 ++-- CTFd/themes/core/templates/base.html | 2 +- CTFd/themes/core/templates/confirm.html | 6 +- CTFd/themes/core/templates/login.html | 20 +-- CTFd/themes/core/templates/register.html | 24 ++- .../themes/core/templates/reset_password.html | 64 ++++---- CTFd/themes/core/templates/settings.html | 69 +++------ CTFd/themes/core/templates/setup.html | 143 ++++++++---------- .../core/templates/teams/join_team.html | 16 +- .../themes/core/templates/teams/new_team.html | 16 +- CTFd/themes/core/templates/teams/private.html | 67 +++----- CTFd/utils/countries/__init__.py | 3 + CTFd/utils/initialization/__init__.py | 2 + CTFd/views.py | 7 +- requirements.txt | 1 + tests/users/test_auth.py | 2 +- 51 files changed, 976 insertions(+), 654 deletions(-) create mode 100644 CTFd/forms/__init__.py create mode 100644 CTFd/forms/auth.py create mode 100644 CTFd/forms/awards.py create mode 100644 CTFd/forms/challenges.py create mode 100644 CTFd/forms/config.py create mode 100644 CTFd/forms/email.py create mode 100644 CTFd/forms/fields.py create mode 100644 CTFd/forms/notifications.py create mode 100644 CTFd/forms/pages.py create mode 100644 CTFd/forms/self.py create mode 100644 CTFd/forms/setup.py create mode 100644 CTFd/forms/submissions.py create mode 100644 CTFd/forms/teams.py create mode 100644 CTFd/forms/users.py diff --git a/CTFd/admin/__init__.py b/CTFd/admin/__init__.py index ee3abdad..5e38d895 100644 --- a/CTFd/admin/__init__.py +++ b/CTFd/admin/__init__.py @@ -163,17 +163,13 @@ def config(): # Clear the config cache so that we don't get stale values clear_config() - database_tables = sorted(db.metadata.tables.keys()) - configs = Configs.query.all() configs = dict([(c.key, get_config(c.key)) for c in configs]) themes = ctf_config.get_themes() themes.remove(get_config("ctf_theme")) - return render_template( - "admin/config.html", database_tables=database_tables, themes=themes, **configs - ) + return render_template("admin/config.html", themes=themes, **configs) @admin.route("/admin/reset", methods=["GET", "POST"]) diff --git a/CTFd/auth.py b/CTFd/auth.py index 8e7a9983..fbed3007 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -17,7 +17,7 @@ from CTFd.utils.config.visibility import registration_visible from CTFd.utils.crypto import verify_password from CTFd.utils.decorators import ratelimit from CTFd.utils.decorators.visibility import check_registration_visibility -from CTFd.utils.helpers import error_for, get_errors +from CTFd.utils.helpers import error_for, get_errors, markup from CTFd.utils.logging import log from CTFd.utils.modes import TEAMS_MODE from CTFd.utils.security.auth import login_user, logout_user @@ -93,6 +93,16 @@ def confirm(data=None): @auth.route("/reset_password/", methods=["POST", "GET"]) @ratelimit(method="POST", limit=10, interval=60) def reset_password(data=None): + if config.can_send_mail() is False: + return render_template( + "reset_password.html", + errors=[ + markup( + "This CTF is not configured to send email.
Please contact an organizer to have your password reset." + ) + ], + ) + if data is not None: try: email_address = unserialize(data, max_age=1800) @@ -142,12 +152,6 @@ def reset_password(data=None): get_errors() - if config.can_send_mail() is False: - return render_template( - "reset_password.html", - errors=["Email could not be sent due to server misconfiguration"], - ) - if not user: return render_template( "reset_password.html", diff --git a/CTFd/constants/config.py b/CTFd/constants/config.py index 9f3194a1..a56c2d77 100644 --- a/CTFd/constants/config.py +++ b/CTFd/constants/config.py @@ -6,6 +6,10 @@ class _ConfigsWrapper: def __getattr__(self, attr): return get_config(attr) + @property + def ctf_name(self): + return get_config("theme_header", default="CTFd") + @property def theme_header(self): return markup(get_config("theme_header", default="")) diff --git a/CTFd/forms/__init__.py b/CTFd/forms/__init__.py new file mode 100644 index 00000000..b6e29ce9 --- /dev/null +++ b/CTFd/forms/__init__.py @@ -0,0 +1,49 @@ +from wtforms import Form +from wtforms.csrf.core import CSRF + + +class CTFdCSRF(CSRF): + def generate_csrf_token(self, csrf_token_field): + from flask import session + + return session.get("nonce") + + +class BaseForm(Form): + class Meta: + csrf = True + csrf_class = CTFdCSRF + csrf_field_name = "nonce" + + +class _FormsWrapper: + pass + + +Forms = _FormsWrapper() + +from CTFd.forms import auth # noqa: I001 isort:skip +from CTFd.forms import self # noqa: I001 isort:skip +from CTFd.forms import teams # noqa: I001 isort:skip +from CTFd.forms import setup # noqa: I001 isort:skip +from CTFd.forms import submissions # noqa: I001 isort:skip +from CTFd.forms import users # noqa: I001 isort:skip +from CTFd.forms import challenges # noqa: I001 isort:skip +from CTFd.forms import notifications # noqa: I001 isort:skip +from CTFd.forms import config # noqa: I001 isort:skip +from CTFd.forms import pages # noqa: I001 isort:skip +from CTFd.forms import awards # noqa: I001 isort:skip +from CTFd.forms import email # noqa: I001 isort:skip + +Forms.auth = auth +Forms.self = self +Forms.teams = teams +Forms.setup = setup +Forms.submissions = submissions +Forms.users = users +Forms.challenges = challenges +Forms.notifications = notifications +Forms.config = config +Forms.pages = pages +Forms.awards = awards +Forms.email = email diff --git a/CTFd/forms/auth.py b/CTFd/forms/auth.py new file mode 100644 index 00000000..7708b561 --- /dev/null +++ b/CTFd/forms/auth.py @@ -0,0 +1,33 @@ +from wtforms import PasswordField, StringField +from wtforms.fields.html5 import EmailField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField + + +class RegistrationForm(BaseForm): + name = StringField("User Name", validators=[InputRequired()]) + email = EmailField("Email", validators=[InputRequired()]) + password = PasswordField("Password", validators=[InputRequired()]) + submit = SubmitField("Submit") + + +class LoginForm(BaseForm): + name = StringField("User Name or Email", validators=[InputRequired()]) + password = PasswordField("Password", validators=[InputRequired()]) + submit = SubmitField("Submit") + + +class ConfirmForm(BaseForm): + submit = SubmitField("Resend") + + +class ResetPasswordRequestForm(BaseForm): + email = EmailField("Email", validators=[InputRequired()]) + submit = SubmitField("Submit") + + +class ResetPasswordForm(BaseForm): + password = PasswordField("Password", validators=[InputRequired()]) + submit = SubmitField("Submit") diff --git a/CTFd/forms/awards.py b/CTFd/forms/awards.py new file mode 100644 index 00000000..32819fae --- /dev/null +++ b/CTFd/forms/awards.py @@ -0,0 +1,30 @@ +from wtforms import RadioField, StringField, TextAreaField +from wtforms.fields.html5 import IntegerField + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField + + +class AwardCreationForm(BaseForm): + name = StringField("Name") + value = IntegerField("Value") + category = StringField("Category") + description = TextAreaField("Description") + submit = SubmitField("Create") + icon = RadioField( + "Icon", + choices=[ + ("", "None"), + ("shield", "Shield"), + ("bug", "Bug"), + ("crown", "Crown"), + ("crosshairs", "Crosshairs"), + ("ban", "Ban"), + ("lightning", "Lightning"), + ("skull", "Skull"), + ("brain", "Brain"), + ("code", "Code"), + ("cowboy", "Cowboy"), + ("angry", "Angry"), + ], + ) diff --git a/CTFd/forms/challenges.py b/CTFd/forms/challenges.py new file mode 100644 index 00000000..e5b5a4ca --- /dev/null +++ b/CTFd/forms/challenges.py @@ -0,0 +1,30 @@ +from wtforms import MultipleFileField, SelectField, StringField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField + + +class ChallengeSearchForm(BaseForm): + field = SelectField( + "Search Field", + choices=[ + ("name", "Name"), + ("id", "ID"), + ("category", "Category"), + ("type", "Type"), + ], + default="name", + validators=[InputRequired()], + ) + q = StringField("Parameter", validators=[InputRequired()]) + submit = SubmitField("Search") + + +class ChallengeFilesUploadForm(BaseForm): + file = MultipleFileField( + "Upload Files", + description="Attach multiple files using Control+Click or Cmd+Click.", + validators=[InputRequired()], + ) + submit = SubmitField("Upload") diff --git a/CTFd/forms/config.py b/CTFd/forms/config.py new file mode 100644 index 00000000..301adea0 --- /dev/null +++ b/CTFd/forms/config.py @@ -0,0 +1,62 @@ +from wtforms import BooleanField, SelectField, StringField +from wtforms.fields.html5 import IntegerField +from wtforms.widgets.html5 import NumberInput + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField +from CTFd.models import db + + +class ResetInstanceForm(BaseForm): + accounts = BooleanField( + "Accounts", + description="Deletes all user and team accounts and their associated information", + ) + submissions = BooleanField( + "Submissions", + description="Deletes all records that accounts gained points or took an action", + ) + challenges = BooleanField( + "Challenges", description="Deletes all challenges and associated data" + ) + pages = BooleanField( + "Pages", description="Deletes all pages and their associated files" + ) + notifications = BooleanField( + "Notifications", description="Deletes all notifications" + ) + submit = SubmitField("Reset CTF") + + +class AccountSettingsForm(BaseForm): + domain_whitelist = StringField( + "Account Email Whitelist", + description="Comma-seperated email domains which users can register under (e.g. ctfd.io, gmail.com, yahoo.com)", + ) + team_size = IntegerField( + widget=NumberInput(min=0), description="Amount of users per team" + ) + verify_emails = SelectField( + "Verify Emails", + description="Control whether users must confirm their email addresses before playing", + choices=[("true", "Enabled"), ("false", "Disabled")], + default="false", + ) + name_changes = SelectField( + "Name Changes", + description="Control whether users can change their names", + choices=[("true", "Enabled"), ("false", "Disabled")], + default="true", + ) + + submit = SubmitField("Update") + + +class ExportCSVForm(BaseForm): + table = SelectField( + "Database Table", + choices=list( + zip(sorted(db.metadata.tables.keys()), sorted(db.metadata.tables.keys())) + ), + ) + submit = SubmitField("Download CSV") diff --git a/CTFd/forms/email.py b/CTFd/forms/email.py new file mode 100644 index 00000000..93cb2ba6 --- /dev/null +++ b/CTFd/forms/email.py @@ -0,0 +1,10 @@ +from wtforms import TextAreaField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField + + +class SendEmailForm(BaseForm): + message = TextAreaField("Message", validators=[InputRequired()]) + submit = SubmitField("Send") diff --git a/CTFd/forms/fields.py b/CTFd/forms/fields.py new file mode 100644 index 00000000..4c0a5bc4 --- /dev/null +++ b/CTFd/forms/fields.py @@ -0,0 +1,17 @@ +from wtforms import SubmitField as _SubmitField + + +class SubmitField(_SubmitField): + """ + This custom SubmitField exists because wtforms is dumb. + + See https://github.com/wtforms/wtforms/issues/205, https://github.com/wtforms/wtforms/issues/36 + The .submit() handler in JS will break if the form has an input with the name or id of "submit" so submit fields need to be changed. + """ + + def __init__(self, *args, **kwargs): + name = kwargs.pop("name", "_submit") + super().__init__(*args, **kwargs) + if self.name == "submit" or name: + self.id = name + self.name = name diff --git a/CTFd/forms/notifications.py b/CTFd/forms/notifications.py new file mode 100644 index 00000000..c590b6da --- /dev/null +++ b/CTFd/forms/notifications.py @@ -0,0 +1,26 @@ +from wtforms import BooleanField, RadioField, StringField, TextAreaField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField + + +class NotificationForm(BaseForm): + title = StringField("Title", description="Notification title") + content = TextAreaField( + "Content", + description="Notification contents. Can consist of HTML and/or Markdown.", + ) + type = RadioField( + "Notification Type", + choices=[("toast", "Toast"), ("alert", "Alert"), ("background", "Background")], + default="toast", + description="What type of notification users receive", + validators=[InputRequired()], + ) + sound = BooleanField( + "Play Sound", + default=True, + description="Play sound for users when they receive the notification", + ) + submit = SubmitField("Submit") diff --git a/CTFd/forms/pages.py b/CTFd/forms/pages.py new file mode 100644 index 00000000..c9ba76ba --- /dev/null +++ b/CTFd/forms/pages.py @@ -0,0 +1,33 @@ +from wtforms import ( + BooleanField, + HiddenField, + MultipleFileField, + StringField, + TextAreaField, +) +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm + + +class PageEditForm(BaseForm): + title = StringField( + "Title", description="This is the title shown on the navigation bar" + ) + route = StringField( + "Route", + description="This is the URL route that your page will be at (e.g. /page). You can also enter links to link to that page.", + ) + draft = BooleanField("Draft") + hidden = BooleanField("Hidden") + auth_required = BooleanField("Authentication Required") + content = TextAreaField("Content") + + +class PageFilesUploadForm(BaseForm): + file = MultipleFileField( + "Upload Files", + description="Attach multiple files using Control+Click or Cmd+Click.", + validators=[InputRequired()], + ) + type = HiddenField("Page Type", default="page", validators=[InputRequired()]) diff --git a/CTFd/forms/self.py b/CTFd/forms/self.py new file mode 100644 index 00000000..a57b2af2 --- /dev/null +++ b/CTFd/forms/self.py @@ -0,0 +1,22 @@ +from wtforms import PasswordField, SelectField, StringField +from wtforms.fields.html5 import DateField, URLField + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField +from CTFd.utils.countries import SELECT_COUNTRIES_LIST + + +class SettingsForm(BaseForm): + name = StringField("User Name") + email = StringField("Email") + password = PasswordField("Password") + confirm = PasswordField("Current Password") + affiliation = StringField("Affiliation") + website = URLField("Website") + country = SelectField("Country", choices=SELECT_COUNTRIES_LIST) + submit = SubmitField("Submit") + + +class TokensForm(BaseForm): + expiration = DateField("Expiration") + submit = SubmitField("Generate") diff --git a/CTFd/forms/setup.py b/CTFd/forms/setup.py new file mode 100644 index 00000000..f42168fc --- /dev/null +++ b/CTFd/forms/setup.py @@ -0,0 +1,66 @@ +from wtforms import ( + HiddenField, + PasswordField, + RadioField, + SelectField, + StringField, + TextAreaField, +) +from wtforms.fields.html5 import EmailField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField +from CTFd.utils.config import get_themes + + +class SetupForm(BaseForm): + ctf_name = StringField( + "Event Name", description="The name of your CTF event/workshop" + ) + ctf_description = TextAreaField( + "Event Description", description="Description for the CTF" + ) + user_mode = RadioField( + "User Mode", + choices=[("teams", "Team Mode"), ("users", "User Mode")], + default="teams", + description="Controls whether users join together in teams to play (Team Mode) or play as themselves (User Mode)", + validators=[InputRequired()], + ) + + name = StringField( + "Admin Username", + description="Your username for the administration account", + validators=[InputRequired()], + ) + email = EmailField( + "Admin Email", + description="Your email address for the administration account", + validators=[InputRequired()], + ) + password = PasswordField( + "Admin Password", + description="Your password for the administration account", + validators=[InputRequired()], + ) + + ctf_theme = SelectField( + "Theme", + description="CTFd Theme to use", + choices=list(zip(get_themes(), get_themes())), + default="core", + validators=[InputRequired()], + ) + theme_color = HiddenField( + "Theme Color", + description="Color used by theme to control aesthetics. Requires theme support. Optional.", + ) + + start = StringField( + "Start Time", description="Time when your CTF is scheduled to start. Optional." + ) + end = StringField( + "End Time", description="Time when your CTF is scheduled to end. Optional." + ) + submit = SubmitField("Finish") diff --git a/CTFd/forms/submissions.py b/CTFd/forms/submissions.py new file mode 100644 index 00000000..0457b5cc --- /dev/null +++ b/CTFd/forms/submissions.py @@ -0,0 +1,16 @@ +from wtforms import SelectField, StringField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField + + +class SubmissionSearchForm(BaseForm): + field = SelectField( + "Search Field", + choices=[("provided", "Provided"), ("id", "ID")], + default="provided", + validators=[InputRequired()], + ) + q = StringField("Parameter", validators=[InputRequired()]) + submit = SubmitField("Search") diff --git a/CTFd/forms/teams.py b/CTFd/forms/teams.py new file mode 100644 index 00000000..c5726190 --- /dev/null +++ b/CTFd/forms/teams.py @@ -0,0 +1,62 @@ +from wtforms import BooleanField, PasswordField, SelectField, StringField +from wtforms.fields.html5 import EmailField, URLField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField +from CTFd.utils.countries import SELECT_COUNTRIES_LIST + + +class TeamJoinForm(BaseForm): + name = StringField("Team Name", validators=[InputRequired()]) + password = PasswordField("Team Password", validators=[InputRequired()]) + submit = SubmitField("Join") + + +class TeamRegisterForm(BaseForm): + name = StringField("Team Name", validators=[InputRequired()]) + password = PasswordField("Team Password", validators=[InputRequired()]) + submit = SubmitField("Create") + + +class TeamSettingsForm(BaseForm): + name = StringField("Team Name") + confirm = PasswordField("Current Password") + password = PasswordField("Team Password") + affiliation = StringField("Affiliation") + website = URLField("Website") + country = SelectField("Country", choices=SELECT_COUNTRIES_LIST) + submit = SubmitField("Submit") + + +class TeamCaptainForm(BaseForm): + # Choices are populated dynamically at form creation time + captain_id = SelectField("Team Captain", choices=[], validators=[InputRequired()]) + submit = SubmitField("Submit") + + +class TeamSearchForm(BaseForm): + field = SelectField( + "Search Field", + choices=[("name", "Name"), ("id", "ID"), ("affiliation", "Affiliation")], + default="name", + validators=[InputRequired()], + ) + q = StringField("Parameter", validators=[InputRequired()]) + submit = SubmitField("Search") + + +class TeamCreateForm(BaseForm): + name = StringField("Team Name", validators=[InputRequired()]) + email = EmailField("Email") + password = PasswordField("Password") + website = URLField("Website") + affiliation = StringField("Affiliation") + country = SelectField("Country", choices=SELECT_COUNTRIES_LIST) + hidden = BooleanField("Hidden") + banned = BooleanField("Banned") + submit = SubmitField("Submit") + + +class TeamEditForm(TeamCreateForm): + pass diff --git a/CTFd/forms/users.py b/CTFd/forms/users.py new file mode 100644 index 00000000..63da61dd --- /dev/null +++ b/CTFd/forms/users.py @@ -0,0 +1,42 @@ +from wtforms import BooleanField, PasswordField, SelectField, StringField +from wtforms.fields.html5 import EmailField +from wtforms.validators import InputRequired + +from CTFd.forms import BaseForm +from CTFd.forms.fields import SubmitField +from CTFd.utils.countries import SELECT_COUNTRIES_LIST + + +class UserSearchForm(BaseForm): + field = SelectField( + "Search Field", + choices=[ + ("name", "Name"), + ("id", "ID"), + ("email", "Email"), + ("affiliation", "Affiliation"), + ("ip", "IP Address"), + ], + default="name", + validators=[InputRequired()], + ) + q = StringField("Parameter", validators=[InputRequired()]) + submit = SubmitField("Search") + + +class UserEditForm(BaseForm): + name = StringField("User Name", validators=[InputRequired()]) + email = EmailField("Email", validators=[InputRequired()]) + password = PasswordField("Password") + website = StringField("Website") + affiliation = StringField("Affiliation") + country = SelectField("Country", choices=SELECT_COUNTRIES_LIST) + type = SelectField("Type", choices=[("user", "User"), ("admin", "Admin")]) + verified = BooleanField("Verified") + hidden = BooleanField("Hidden") + banned = BooleanField("Banned") + submit = SubmitField("Submit") + + +class UserCreateForm(UserEditForm): + notify = BooleanField("Email account credentials to user", default=True) diff --git a/CTFd/themes/admin/assets/js/pages/editor.js b/CTFd/themes/admin/assets/js/pages/editor.js index 2aaac442..fb34b7a5 100644 --- a/CTFd/themes/admin/assets/js/pages/editor.js +++ b/CTFd/themes/admin/assets/js/pages/editor.js @@ -158,8 +158,9 @@ function submit_form() { var target = "/api/v1/pages"; var method = "POST"; - if (params.id) { - target += "/" + params.id; + let part = window.location.pathname.split("/").pop(); + if (part !== "new") { + target += "/" + part; method = "PATCH"; } diff --git a/CTFd/themes/admin/static/js/pages/editor.dev.js b/CTFd/themes/admin/static/js/pages/editor.dev.js index 135d14be..82c54012 100644 --- a/CTFd/themes/admin/static/js/pages/editor.dev.js +++ b/CTFd/themes/admin/static/js/pages/editor.dev.js @@ -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__webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _helpers = _interopRequireDefault(__webpack_require__(/*! core/helpers */ \"./CTFd/themes/core/assets/js/helpers.js\"));\n\nvar _codemirror = _interopRequireDefault(__webpack_require__(/*! codemirror */ \"./node_modules/codemirror/lib/codemirror.js\"));\n\n__webpack_require__(/*! codemirror/mode/htmlmixed/htmlmixed.js */ \"./node_modules/codemirror/mode/htmlmixed/htmlmixed.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 get_filetype_icon_class(filename) {\n var mapping = {\n // Image Files\n png: \"fa-file-image\",\n jpg: \"fa-file-image\",\n jpeg: \"fa-file-image\",\n gif: \"fa-file-image\",\n bmp: \"fa-file-image\",\n svg: \"fa-file-image\",\n // Text Files\n txt: \"fa-file-alt\",\n // Video Files\n mov: \"fa-file-video\",\n mp4: \"fa-file-video\",\n wmv: \"fa-file-video\",\n flv: \"fa-file-video\",\n mkv: \"fa-file-video\",\n avi: \"fa-file-video\",\n // PDF Files\n pdf: \"fa-file-pdf\",\n // Audio Files\n mp3: \"fa-file-sound\",\n wav: \"fa-file-sound\",\n aac: \"fa-file-sound\",\n // Archive Files\n zip: \"fa-file-archive\",\n gz: \"fa-file-archive\",\n tar: \"fa-file-archive\",\n \"7z\": \"fa-file-archive\",\n rar: \"fa-file-archive\",\n // Code Files\n py: \"fa-file-code\",\n c: \"fa-file-code\",\n cpp: \"fa-file-code\",\n html: \"fa-file-code\",\n js: \"fa-file-code\",\n rb: \"fa-file-code\",\n go: \"fa-file-code\"\n };\n var ext = filename.split(\".\").pop();\n return mapping[ext];\n}\n\nfunction get_page_files() {\n return _CTFd.default.fetch(\"/api/v1/files?type=page\", {\n credentials: \"same-origin\"\n }).then(function (response) {\n return response.json();\n });\n}\n\nfunction show_files(data) {\n var list = (0, _jquery.default)(\"#media-library-list\");\n list.empty();\n\n for (var i = 0; i < data.length; i++) {\n var f = data[i];\n var fname = f.location.split(\"/\").pop();\n var ext = get_filetype_icon_class(f.location);\n var wrapper = (0, _jquery.default)(\"
\").attr(\"class\", \"media-item-wrapper\");\n var link = (0, _jquery.default)(\"\");\n link.attr(\"href\", \"##\");\n\n if (ext === undefined) {\n link.append(' '.format(ext));\n } else {\n link.append(' '.format(ext));\n }\n\n link.append((0, _jquery.default)(\"\").attr(\"class\", \"media-item-title\").text(fname));\n link.click(function (e) {\n var media_div = (0, _jquery.default)(this).parent();\n var icon = (0, _jquery.default)(this).find(\"i\")[0];\n var f_loc = media_div.attr(\"data-location\");\n var fname = media_div.attr(\"data-filename\");\n var f_id = media_div.attr(\"data-id\");\n (0, _jquery.default)(\"#media-delete\").attr(\"data-id\", f_id);\n (0, _jquery.default)(\"#media-link\").val(f_loc);\n (0, _jquery.default)(\"#media-filename\").html((0, _jquery.default)(\"\").attr(\"href\", f_loc).attr(\"target\", \"_blank\").text(fname));\n (0, _jquery.default)(\"#media-icon\").empty();\n\n if ((0, _jquery.default)(icon).hasClass(\"fa-file-image\")) {\n (0, _jquery.default)(\"#media-icon\").append((0, _jquery.default)(\"\").attr(\"src\", f_loc).css({\n \"max-width\": \"100%\",\n \"max-height\": \"100%\",\n \"object-fit\": \"contain\"\n }));\n } else {\n // icon is empty so we need to pull outerHTML\n var copy_icon = (0, _jquery.default)(icon).clone();\n (0, _jquery.default)(copy_icon).addClass(\"fa-4x\");\n (0, _jquery.default)(\"#media-icon\").append(copy_icon);\n }\n\n (0, _jquery.default)(\"#media-item\").show();\n });\n wrapper.append(link);\n wrapper.attr(\"data-location\", _CTFd.default.config.urlRoot + \"/files/\" + f.location);\n wrapper.attr(\"data-id\", f.id);\n wrapper.attr(\"data-filename\", fname);\n list.append(wrapper);\n }\n}\n\nfunction refresh_files(cb) {\n get_page_files().then(function (response) {\n var data = response.data;\n show_files(data);\n\n if (cb) {\n cb();\n }\n });\n}\n\nfunction insert_at_cursor(editor, text) {\n var doc = editor.getDoc();\n var cursor = doc.getCursor();\n doc.replaceRange(text, cursor);\n}\n\nfunction submit_form() {\n // Save the CodeMirror data to the Textarea\n window.editor.save();\n var params = (0, _jquery.default)(\"#page-edit\").serializeJSON();\n var target = \"/api/v1/pages\";\n var method = \"POST\";\n\n if (params.id) {\n target += \"/\" + params.id;\n method = \"PATCH\";\n }\n\n _CTFd.default.fetch(target, {\n method: method,\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(params)\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n if (method === \"PATCH\" && response.success) {\n (0, _ezq.ezToast)({\n title: \"Saved\",\n body: \"Your changes have been saved\"\n });\n } else {\n window.location = _CTFd.default.config.urlRoot + \"/admin/pages/\" + response.data.id;\n }\n });\n}\n\nfunction preview_page() {\n editor.save(); // Save the CodeMirror data to the Textarea\n\n (0, _jquery.default)(\"#page-edit\").attr(\"action\", _CTFd.default.config.urlRoot + \"/admin/pages/preview\");\n (0, _jquery.default)(\"#page-edit\").attr(\"target\", \"_blank\");\n (0, _jquery.default)(\"#page-edit\").submit();\n}\n\nfunction upload_media() {\n _helpers.default.files.upload((0, _jquery.default)(\"#media-library-upload\"), {}, function (data) {\n refresh_files();\n });\n}\n\n(0, _jquery.default)(function () {\n window.editor = _codemirror.default.fromTextArea(document.getElementById(\"admin-pages-editor\"), {\n lineNumbers: true,\n lineWrapping: true,\n mode: \"htmlmixed\",\n htmlMode: true\n });\n (0, _jquery.default)(\"#media-insert\").click(function (e) {\n var tag = \"\";\n\n try {\n tag = (0, _jquery.default)(\"#media-icon\").children()[0].nodeName.toLowerCase();\n } catch (err) {\n tag = \"\";\n }\n\n var link = (0, _jquery.default)(\"#media-link\").val();\n var fname = (0, _jquery.default)(\"#media-filename\").text();\n var entry = null;\n\n if (tag === \"img\") {\n entry = \"![{0}]({1})\".format(fname, link);\n } else {\n entry = \"[{0}]({1})\".format(fname, link);\n }\n\n insert_at_cursor(editor, entry);\n });\n (0, _jquery.default)(\"#media-download\").click(function (e) {\n var link = (0, _jquery.default)(\"#media-link\").val();\n window.open(link, \"_blank\");\n });\n (0, _jquery.default)(\"#media-delete\").click(function (e) {\n var file_id = (0, _jquery.default)(this).attr(\"data-id\");\n (0, _ezq.ezQuery)({\n title: \"Delete File?\",\n body: \"Are you sure you want to delete this file?\",\n success: function success() {\n _CTFd.default.fetch(\"/api/v1/files/\" + file_id, {\n method: \"DELETE\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n }).then(function (response) {\n if (response.status === 200) {\n response.json().then(function (object) {\n if (object.success) {\n refresh_files();\n }\n });\n }\n });\n }\n });\n });\n (0, _jquery.default)(\"#save-page\").click(function (e) {\n e.preventDefault();\n submit_form();\n });\n (0, _jquery.default)(\"#media-button\").click(function () {\n (0, _jquery.default)(\"#media-library-list\").empty();\n refresh_files(function () {\n (0, _jquery.default)(\"#media-modal\").modal();\n });\n });\n (0, _jquery.default)(\".media-upload-button\").click(function () {\n upload_media();\n });\n (0, _jquery.default)(\".preview-page\").click(function () {\n preview_page();\n });\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/editor.js?"); +eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\n__webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _helpers = _interopRequireDefault(__webpack_require__(/*! core/helpers */ \"./CTFd/themes/core/assets/js/helpers.js\"));\n\nvar _codemirror = _interopRequireDefault(__webpack_require__(/*! codemirror */ \"./node_modules/codemirror/lib/codemirror.js\"));\n\n__webpack_require__(/*! codemirror/mode/htmlmixed/htmlmixed.js */ \"./node_modules/codemirror/mode/htmlmixed/htmlmixed.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 get_filetype_icon_class(filename) {\n var mapping = {\n // Image Files\n png: \"fa-file-image\",\n jpg: \"fa-file-image\",\n jpeg: \"fa-file-image\",\n gif: \"fa-file-image\",\n bmp: \"fa-file-image\",\n svg: \"fa-file-image\",\n // Text Files\n txt: \"fa-file-alt\",\n // Video Files\n mov: \"fa-file-video\",\n mp4: \"fa-file-video\",\n wmv: \"fa-file-video\",\n flv: \"fa-file-video\",\n mkv: \"fa-file-video\",\n avi: \"fa-file-video\",\n // PDF Files\n pdf: \"fa-file-pdf\",\n // Audio Files\n mp3: \"fa-file-sound\",\n wav: \"fa-file-sound\",\n aac: \"fa-file-sound\",\n // Archive Files\n zip: \"fa-file-archive\",\n gz: \"fa-file-archive\",\n tar: \"fa-file-archive\",\n \"7z\": \"fa-file-archive\",\n rar: \"fa-file-archive\",\n // Code Files\n py: \"fa-file-code\",\n c: \"fa-file-code\",\n cpp: \"fa-file-code\",\n html: \"fa-file-code\",\n js: \"fa-file-code\",\n rb: \"fa-file-code\",\n go: \"fa-file-code\"\n };\n var ext = filename.split(\".\").pop();\n return mapping[ext];\n}\n\nfunction get_page_files() {\n return _CTFd.default.fetch(\"/api/v1/files?type=page\", {\n credentials: \"same-origin\"\n }).then(function (response) {\n return response.json();\n });\n}\n\nfunction show_files(data) {\n var list = (0, _jquery.default)(\"#media-library-list\");\n list.empty();\n\n for (var i = 0; i < data.length; i++) {\n var f = data[i];\n var fname = f.location.split(\"/\").pop();\n var ext = get_filetype_icon_class(f.location);\n var wrapper = (0, _jquery.default)(\"
\").attr(\"class\", \"media-item-wrapper\");\n var link = (0, _jquery.default)(\"\");\n link.attr(\"href\", \"##\");\n\n if (ext === undefined) {\n link.append(' '.format(ext));\n } else {\n link.append(' '.format(ext));\n }\n\n link.append((0, _jquery.default)(\"\").attr(\"class\", \"media-item-title\").text(fname));\n link.click(function (e) {\n var media_div = (0, _jquery.default)(this).parent();\n var icon = (0, _jquery.default)(this).find(\"i\")[0];\n var f_loc = media_div.attr(\"data-location\");\n var fname = media_div.attr(\"data-filename\");\n var f_id = media_div.attr(\"data-id\");\n (0, _jquery.default)(\"#media-delete\").attr(\"data-id\", f_id);\n (0, _jquery.default)(\"#media-link\").val(f_loc);\n (0, _jquery.default)(\"#media-filename\").html((0, _jquery.default)(\"\").attr(\"href\", f_loc).attr(\"target\", \"_blank\").text(fname));\n (0, _jquery.default)(\"#media-icon\").empty();\n\n if ((0, _jquery.default)(icon).hasClass(\"fa-file-image\")) {\n (0, _jquery.default)(\"#media-icon\").append((0, _jquery.default)(\"\").attr(\"src\", f_loc).css({\n \"max-width\": \"100%\",\n \"max-height\": \"100%\",\n \"object-fit\": \"contain\"\n }));\n } else {\n // icon is empty so we need to pull outerHTML\n var copy_icon = (0, _jquery.default)(icon).clone();\n (0, _jquery.default)(copy_icon).addClass(\"fa-4x\");\n (0, _jquery.default)(\"#media-icon\").append(copy_icon);\n }\n\n (0, _jquery.default)(\"#media-item\").show();\n });\n wrapper.append(link);\n wrapper.attr(\"data-location\", _CTFd.default.config.urlRoot + \"/files/\" + f.location);\n wrapper.attr(\"data-id\", f.id);\n wrapper.attr(\"data-filename\", fname);\n list.append(wrapper);\n }\n}\n\nfunction refresh_files(cb) {\n get_page_files().then(function (response) {\n var data = response.data;\n show_files(data);\n\n if (cb) {\n cb();\n }\n });\n}\n\nfunction insert_at_cursor(editor, text) {\n var doc = editor.getDoc();\n var cursor = doc.getCursor();\n doc.replaceRange(text, cursor);\n}\n\nfunction submit_form() {\n // Save the CodeMirror data to the Textarea\n window.editor.save();\n var params = (0, _jquery.default)(\"#page-edit\").serializeJSON();\n var target = \"/api/v1/pages\";\n var method = \"POST\";\n var part = window.location.pathname.split(\"/\").pop();\n\n if (part !== \"new\") {\n target += \"/\" + part;\n method = \"PATCH\";\n }\n\n _CTFd.default.fetch(target, {\n method: method,\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(params)\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n if (method === \"PATCH\" && response.success) {\n (0, _ezq.ezToast)({\n title: \"Saved\",\n body: \"Your changes have been saved\"\n });\n } else {\n window.location = _CTFd.default.config.urlRoot + \"/admin/pages/\" + response.data.id;\n }\n });\n}\n\nfunction preview_page() {\n editor.save(); // Save the CodeMirror data to the Textarea\n\n (0, _jquery.default)(\"#page-edit\").attr(\"action\", _CTFd.default.config.urlRoot + \"/admin/pages/preview\");\n (0, _jquery.default)(\"#page-edit\").attr(\"target\", \"_blank\");\n (0, _jquery.default)(\"#page-edit\").submit();\n}\n\nfunction upload_media() {\n _helpers.default.files.upload((0, _jquery.default)(\"#media-library-upload\"), {}, function (data) {\n refresh_files();\n });\n}\n\n(0, _jquery.default)(function () {\n window.editor = _codemirror.default.fromTextArea(document.getElementById(\"admin-pages-editor\"), {\n lineNumbers: true,\n lineWrapping: true,\n mode: \"htmlmixed\",\n htmlMode: true\n });\n (0, _jquery.default)(\"#media-insert\").click(function (e) {\n var tag = \"\";\n\n try {\n tag = (0, _jquery.default)(\"#media-icon\").children()[0].nodeName.toLowerCase();\n } catch (err) {\n tag = \"\";\n }\n\n var link = (0, _jquery.default)(\"#media-link\").val();\n var fname = (0, _jquery.default)(\"#media-filename\").text();\n var entry = null;\n\n if (tag === \"img\") {\n entry = \"![{0}]({1})\".format(fname, link);\n } else {\n entry = \"[{0}]({1})\".format(fname, link);\n }\n\n insert_at_cursor(editor, entry);\n });\n (0, _jquery.default)(\"#media-download\").click(function (e) {\n var link = (0, _jquery.default)(\"#media-link\").val();\n window.open(link, \"_blank\");\n });\n (0, _jquery.default)(\"#media-delete\").click(function (e) {\n var file_id = (0, _jquery.default)(this).attr(\"data-id\");\n (0, _ezq.ezQuery)({\n title: \"Delete File?\",\n body: \"Are you sure you want to delete this file?\",\n success: function success() {\n _CTFd.default.fetch(\"/api/v1/files/\" + file_id, {\n method: \"DELETE\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n }).then(function (response) {\n if (response.status === 200) {\n response.json().then(function (object) {\n if (object.success) {\n refresh_files();\n }\n });\n }\n });\n }\n });\n });\n (0, _jquery.default)(\"#save-page\").click(function (e) {\n e.preventDefault();\n submit_form();\n });\n (0, _jquery.default)(\"#media-button\").click(function () {\n (0, _jquery.default)(\"#media-library-list\").empty();\n refresh_files(function () {\n (0, _jquery.default)(\"#media-modal\").modal();\n });\n });\n (0, _jquery.default)(\".media-upload-button\").click(function () {\n upload_media();\n });\n (0, _jquery.default)(\".preview-page\").click(function () {\n preview_page();\n });\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/editor.js?"); /***/ }) diff --git a/CTFd/themes/admin/static/js/pages/editor.min.js b/CTFd/themes/admin/static/js/pages/editor.min.js index eb01cb22..946ae58c 100644 --- a/CTFd/themes/admin/static/js/pages/editor.min.js +++ b/CTFd/themes/admin/static/js/pages/editor.min.js @@ -1 +1 @@ -!function(r){function e(e){for(var t,o,n=e[0],s=e[1],a=e[2],i=0,l=[];i").attr("class","media-item-wrapper"),l=(0,r.default)("");l.attr("href","##"),void 0===a?l.append(' '.format(a)):l.append(' '.format(a)),l.append((0,r.default)("").attr("class","media-item-title").text(s)),l.click(function(e){var t=(0,r.default)(this).parent(),o=(0,r.default)(this).find("i")[0],n=t.attr("data-location"),s=t.attr("data-filename"),a=t.attr("data-id");if((0,r.default)("#media-delete").attr("data-id",a),(0,r.default)("#media-link").val(n),(0,r.default)("#media-filename").html((0,r.default)("").attr("href",n).attr("target","_blank").text(s)),(0,r.default)("#media-icon").empty(),(0,r.default)(o).hasClass("fa-file-image"))(0,r.default)("#media-icon").append((0,r.default)("").attr("src",n).css({"max-width":"100%","max-height":"100%","object-fit":"contain"}));else{var i=(0,r.default)(o).clone();(0,r.default)(i).addClass("fa-4x"),(0,r.default)("#media-icon").append(i)}(0,r.default)("#media-item").show()}),i.append(l),i.attr("data-location",d.default.config.urlRoot+"/files/"+n.location),i.attr("data-id",n.id),i.attr("data-filename",s),t.append(i)}}(e.data),t&&t()})}(0,r.default)(function(){window.editor=s.default.fromTextArea(document.getElementById("admin-pages-editor"),{lineNumbers:!0,lineWrapping:!0,mode:"htmlmixed",htmlMode:!0}),(0,r.default)("#media-insert").click(function(e){var t="";try{t=(0,r.default)("#media-icon").children()[0].nodeName.toLowerCase()}catch(e){t=""}var o=(0,r.default)("#media-link").val(),n=(0,r.default)("#media-filename").text(),s=null;s="img"===t?"![{0}]({1})".format(n,o):"[{0}]({1})".format(n,o),function(e,t){var o=e.getDoc(),n=o.getCursor();o.replaceRange(t,n)}(editor,s)}),(0,r.default)("#media-download").click(function(e){var t=(0,r.default)("#media-link").val();window.open(t,"_blank")}),(0,r.default)("#media-delete").click(function(e){var t=(0,r.default)(this).attr("data-id");(0,a.ezQuery)({title:"Delete File?",body:"Are you sure you want to delete this file?",success:function(){d.default.fetch("/api/v1/files/"+t,{method:"DELETE",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"}}).then(function(e){200===e.status&&e.json().then(function(e){e.success&&l()})})}})}),(0,r.default)("#save-page").click(function(e){e.preventDefault(),function(){window.editor.save();var e=(0,r.default)("#page-edit").serializeJSON(),t="/api/v1/pages",o="POST";e.id&&(t+="/"+e.id,o="PATCH"),d.default.fetch(t,{method:o,credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)}).then(function(e){return e.json()}).then(function(e){"PATCH"===o&&e.success?(0,a.ezToast)({title:"Saved",body:"Your changes have been saved"}):window.location=d.default.config.urlRoot+"/admin/pages/"+e.data.id})}()}),(0,r.default)("#media-button").click(function(){(0,r.default)("#media-library-list").empty(),l(function(){(0,r.default)("#media-modal").modal()})}),(0,r.default)(".media-upload-button").click(function(){n.default.files.upload((0,r.default)("#media-library-upload"),{},function(e){l()})}),(0,r.default)(".preview-page").click(function(){editor.save(),(0,r.default)("#page-edit").attr("action",d.default.config.urlRoot+"/admin/pages/preview"),(0,r.default)("#page-edit").attr("target","_blank"),(0,r.default)("#page-edit").submit()})})},"./CTFd/themes/admin/assets/js/pages/main.js":function(e,t,o){var n=u(o("./CTFd/themes/core/assets/js/CTFd.js")),s=u(o("./node_modules/jquery/dist/jquery.js")),a=u(o("./node_modules/moment/moment.js")),i=u(o("./node_modules/nunjucks/browser/nunjucks.js")),l=o("./node_modules/howler/dist/howler.js"),r=u(o("./CTFd/themes/core/assets/js/events.js")),d=u(o("./CTFd/themes/core/assets/js/times.js")),c=u(o("./CTFd/themes/admin/assets/js/styles.js")),m=u(o("./CTFd/themes/core/assets/js/helpers.js"));function u(e){return e&&e.__esModule?e:{default:e}}n.default.init(window.init),window.CTFd=n.default,window.helpers=m.default,window.$=s.default,window.Moment=a.default,window.nunjucks=i.default,window.Howl=l.Howl,(0,s.default)(function(){(0,c.default)(),(0,d.default)(),(0,r.default)(n.default.config.urlRoot)})},"./CTFd/themes/admin/assets/js/styles.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,o("./node_modules/bootstrap/dist/js/bootstrap.bundle.js");var n,s=o("./CTFd/themes/core/assets/js/utils.js"),a=(n=o("./node_modules/jquery/dist/jquery.js"))&&n.__esModule?n:{default:n};t.default=function(){(0,a.default)(":input").each(function(){(0,a.default)(this).data("initial",(0,a.default)(this).val())}),(0,a.default)(".form-control").bind({focus:function(){(0,a.default)(this).addClass("input-filled-valid")},blur:function(){""===(0,a.default)(this).val()&&(0,a.default)(this).removeClass("input-filled-valid")}}),(0,a.default)(".modal").on("show.bs.modal",function(e){(0,a.default)(".form-control").each(function(){(0,a.default)(this).val()&&(0,a.default)(this).addClass("input-filled-valid")})}),(0,a.default)(function(){(0,a.default)(".form-control").each(function(){(0,a.default)(this).val()&&(0,a.default)(this).addClass("input-filled-valid")}),(0,a.default)("tr[data-href], td[data-href]").click(function(){if(!getSelection().toString()){var e=(0,a.default)(this).attr("data-href");e&&(window.location=e)}return!1}),(0,a.default)("[data-checkbox]").click(function(e){(0,a.default)(e.target).is("input[type=checkbox]")?e.stopImmediatePropagation():((0,a.default)(this).find("input[type=checkbox]").click(),e.stopImmediatePropagation())}),(0,a.default)("[data-checkbox-all]").on("click change",function(e){var t=(0,a.default)(this).prop("checked"),o=(0,a.default)(this).index()+1;(0,a.default)(this).closest("table").find("tr td:nth-child(".concat(o,") input[type=checkbox]")).prop("checked",t),e.stopImmediatePropagation()}),(0,a.default)("tr[data-href] a, tr[data-href] button").click(function(e){(0,a.default)(this).attr("data-dismiss")||e.stopPropagation()}),(0,a.default)(".page-select").change(function(){var e=new URL(window.location);e.searchParams.set("page",this.value),window.location.href=e.toString()}),(0,a.default)('a[data-toggle="tab"]').on("shown.bs.tab",function(e){sessionStorage.setItem("activeTab",(0,a.default)(e.target).attr("href"))});var e=sessionStorage.getItem("activeTab");if(e){var t=(0,a.default)('.nav-tabs a[href="'.concat(e,'"], .nav-pills a[href="').concat(e,'"]'));t.length?t.tab("show"):sessionStorage.removeItem("activeTab")}(0,s.makeSortableTables)(),(0,a.default)('[data-toggle="tooltip"]').tooltip()})}},"./CTFd/themes/core/assets/js/CTFd.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=d(o("./CTFd/themes/core/assets/js/fetch.js")),s=d(o("./CTFd/themes/core/assets/js/config.js")),a=o("./CTFd/themes/core/assets/js/api.js");o("./CTFd/themes/core/assets/js/patch.js");var i=d(o("./node_modules/markdown-it/index.js")),l=d(o("./node_modules/jquery/dist/jquery.js")),r=d(o("./CTFd/themes/core/assets/js/ezq.js"));function d(e){return e&&e.__esModule?e:{default:e}}function c(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}var m=new a.API("/"),u={},p={ezq:r.default},f={$:l.default,markdown:function(e){var t=function(t){for(var e=1;e".concat(e.body,"

")):o.find(".modal-body").append((0,l.default)(e.body));var n=(0,l.default)(c.format(e.button));return e.success&&(0,l.default)(n).click(function(){e.success()}),e.large&&o.find(".modal-dialog").addClass("modal-lg"),o.find(".modal-footer").append(n),(0,l.default)("main").append(o),o.modal("show"),(0,l.default)(o).on("hidden.bs.modal",function(){(0,l.default)(this).modal("dispose")}),o}function f(e){(0,l.default)("#ezq--notifications-toast-container").length||(0,l.default)("body").append((0,l.default)("
").attr({id:"ezq--notifications-toast-container"}).css({position:"fixed",bottom:"0",right:"0","min-width":"20%"}));var t=r.format(e.title,e.body),o=(0,l.default)(t);if(e.onclose&&(0,l.default)(o).find("button[data-dismiss=toast]").click(function(){e.onclose()}),e.onclick){var n=(0,l.default)(o).find(".toast-body");n.addClass("cursor-pointer"),n.click(function(){e.onclick()})}var s=!1!==e.autohide,a=!1!==e.animation,i=e.delay||1e4;return(0,l.default)("#ezq--notifications-toast-container").prepend(o),o.toast({autohide:s,delay:i,animation:a}),o.toast("show"),o}function j(e){var t=a.format(e.title),o=(0,l.default)(t);"string"==typeof e.body?o.find(".modal-body").append("

".concat(e.body,"

")):o.find(".modal-body").append((0,l.default)(e.body));var n=(0,l.default)(u),s=(0,l.default)(m);return o.find(".modal-footer").append(s),o.find(".modal-footer").append(n),(0,l.default)("main").append(o),(0,l.default)(o).on("hidden.bs.modal",function(){(0,l.default)(this).modal("dispose")}),(0,l.default)(n).click(function(){e.success()}),o.modal("show"),o}function h(e){if(e.target){var t=(0,l.default)(e.target);return t.find(".progress-bar").css("width",e.width+"%"),t}var o=i.format(e.width),n=a.format(e.title),s=(0,l.default)(n);return s.find(".modal-body").append((0,l.default)(o)),(0,l.default)("main").append(s),s.modal("show")}function _(e){var t={success:d,error:s}[e.type].format(e.body);return(0,l.default)(t)}var v={ezAlert:p,ezToast:f,ezQuery:j,ezProgressBar:h,ezBadge:_};t.default=v},"./CTFd/themes/core/assets/js/fetch.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,o("./node_modules/whatwg-fetch/fetch.js");var n,s=(n=o("./CTFd/themes/core/assets/js/config.js"))&&n.__esModule?n:{default:n};var a=window.fetch;t.default=function(e,t){return void 0===t&&(t={method:"GET",credentials:"same-origin",headers:{}}),e=s.default.urlRoot+e,void 0===t.headers&&(t.headers={}),t.credentials="same-origin",t.headers.Accept="application/json",t.headers["Content-Type"]="application/json",t.headers["CSRF-Token"]=s.default.csrfNonce,a(e,t)}},"./CTFd/themes/core/assets/js/patch.js":function(e,t,o){var n,l=(n=o("./node_modules/q/q.js"))&&n.__esModule?n:{default:n},s=o("./CTFd/themes/core/assets/js/api.js");function a(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function r(e,t){return function(t){for(var e=1;e>8*s&255).toString(16)).substr(-2)}return n},t.htmlEntities=function(e){return(0,i.default)("
").text(e).html()},t.cumulativeSum=function(e){for(var t=e.concat(),o=0;o'),(0,i.default)("th.sort-col").click(function(){var e=(0,i.default)(this).parents("table").eq(0),t=e.find("tr:gt(0)").toArray().sort(function(s){return function(e,t){var o=a(e,s),n=a(t,s);return i.default.isNumeric(o)&&i.default.isNumeric(n)?o-n:o.toString().localeCompare(n)}}((0,i.default)(this).index()));this.asc=!this.asc,this.asc||(t=t.reverse());for(var o=0;o").attr("class","media-item-wrapper"),l=(0,r.default)("");l.attr("href","##"),void 0===a?l.append(' '.format(a)):l.append(' '.format(a)),l.append((0,r.default)("").attr("class","media-item-title").text(s)),l.click(function(e){var t=(0,r.default)(this).parent(),o=(0,r.default)(this).find("i")[0],n=t.attr("data-location"),s=t.attr("data-filename"),a=t.attr("data-id");if((0,r.default)("#media-delete").attr("data-id",a),(0,r.default)("#media-link").val(n),(0,r.default)("#media-filename").html((0,r.default)("").attr("href",n).attr("target","_blank").text(s)),(0,r.default)("#media-icon").empty(),(0,r.default)(o).hasClass("fa-file-image"))(0,r.default)("#media-icon").append((0,r.default)("").attr("src",n).css({"max-width":"100%","max-height":"100%","object-fit":"contain"}));else{var i=(0,r.default)(o).clone();(0,r.default)(i).addClass("fa-4x"),(0,r.default)("#media-icon").append(i)}(0,r.default)("#media-item").show()}),i.append(l),i.attr("data-location",d.default.config.urlRoot+"/files/"+n.location),i.attr("data-id",n.id),i.attr("data-filename",s),t.append(i)}}(e.data),t&&t()})}(0,r.default)(function(){window.editor=s.default.fromTextArea(document.getElementById("admin-pages-editor"),{lineNumbers:!0,lineWrapping:!0,mode:"htmlmixed",htmlMode:!0}),(0,r.default)("#media-insert").click(function(e){var t="";try{t=(0,r.default)("#media-icon").children()[0].nodeName.toLowerCase()}catch(e){t=""}var o=(0,r.default)("#media-link").val(),n=(0,r.default)("#media-filename").text(),s=null;s="img"===t?"![{0}]({1})".format(n,o):"[{0}]({1})".format(n,o),function(e,t){var o=e.getDoc(),n=o.getCursor();o.replaceRange(t,n)}(editor,s)}),(0,r.default)("#media-download").click(function(e){var t=(0,r.default)("#media-link").val();window.open(t,"_blank")}),(0,r.default)("#media-delete").click(function(e){var t=(0,r.default)(this).attr("data-id");(0,a.ezQuery)({title:"Delete File?",body:"Are you sure you want to delete this file?",success:function(){d.default.fetch("/api/v1/files/"+t,{method:"DELETE",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"}}).then(function(e){200===e.status&&e.json().then(function(e){e.success&&l()})})}})}),(0,r.default)("#save-page").click(function(e){e.preventDefault(),function(){window.editor.save();var e=(0,r.default)("#page-edit").serializeJSON(),t="/api/v1/pages",o="POST",n=window.location.pathname.split("/").pop();"new"!==n&&(t+="/"+n,o="PATCH"),d.default.fetch(t,{method:o,credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)}).then(function(e){return e.json()}).then(function(e){"PATCH"===o&&e.success?(0,a.ezToast)({title:"Saved",body:"Your changes have been saved"}):window.location=d.default.config.urlRoot+"/admin/pages/"+e.data.id})}()}),(0,r.default)("#media-button").click(function(){(0,r.default)("#media-library-list").empty(),l(function(){(0,r.default)("#media-modal").modal()})}),(0,r.default)(".media-upload-button").click(function(){n.default.files.upload((0,r.default)("#media-library-upload"),{},function(e){l()})}),(0,r.default)(".preview-page").click(function(){editor.save(),(0,r.default)("#page-edit").attr("action",d.default.config.urlRoot+"/admin/pages/preview"),(0,r.default)("#page-edit").attr("target","_blank"),(0,r.default)("#page-edit").submit()})})},"./CTFd/themes/admin/assets/js/pages/main.js":function(e,t,o){var n=u(o("./CTFd/themes/core/assets/js/CTFd.js")),s=u(o("./node_modules/jquery/dist/jquery.js")),a=u(o("./node_modules/moment/moment.js")),i=u(o("./node_modules/nunjucks/browser/nunjucks.js")),l=o("./node_modules/howler/dist/howler.js"),r=u(o("./CTFd/themes/core/assets/js/events.js")),d=u(o("./CTFd/themes/core/assets/js/times.js")),c=u(o("./CTFd/themes/admin/assets/js/styles.js")),m=u(o("./CTFd/themes/core/assets/js/helpers.js"));function u(e){return e&&e.__esModule?e:{default:e}}n.default.init(window.init),window.CTFd=n.default,window.helpers=m.default,window.$=s.default,window.Moment=a.default,window.nunjucks=i.default,window.Howl=l.Howl,(0,s.default)(function(){(0,c.default)(),(0,d.default)(),(0,r.default)(n.default.config.urlRoot)})},"./CTFd/themes/admin/assets/js/styles.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,o("./node_modules/bootstrap/dist/js/bootstrap.bundle.js");var n,s=o("./CTFd/themes/core/assets/js/utils.js"),a=(n=o("./node_modules/jquery/dist/jquery.js"))&&n.__esModule?n:{default:n};t.default=function(){(0,a.default)(":input").each(function(){(0,a.default)(this).data("initial",(0,a.default)(this).val())}),(0,a.default)(".form-control").bind({focus:function(){(0,a.default)(this).addClass("input-filled-valid")},blur:function(){""===(0,a.default)(this).val()&&(0,a.default)(this).removeClass("input-filled-valid")}}),(0,a.default)(".modal").on("show.bs.modal",function(e){(0,a.default)(".form-control").each(function(){(0,a.default)(this).val()&&(0,a.default)(this).addClass("input-filled-valid")})}),(0,a.default)(function(){(0,a.default)(".form-control").each(function(){(0,a.default)(this).val()&&(0,a.default)(this).addClass("input-filled-valid")}),(0,a.default)("tr[data-href], td[data-href]").click(function(){if(!getSelection().toString()){var e=(0,a.default)(this).attr("data-href");e&&(window.location=e)}return!1}),(0,a.default)("[data-checkbox]").click(function(e){(0,a.default)(e.target).is("input[type=checkbox]")?e.stopImmediatePropagation():((0,a.default)(this).find("input[type=checkbox]").click(),e.stopImmediatePropagation())}),(0,a.default)("[data-checkbox-all]").on("click change",function(e){var t=(0,a.default)(this).prop("checked"),o=(0,a.default)(this).index()+1;(0,a.default)(this).closest("table").find("tr td:nth-child(".concat(o,") input[type=checkbox]")).prop("checked",t),e.stopImmediatePropagation()}),(0,a.default)("tr[data-href] a, tr[data-href] button").click(function(e){(0,a.default)(this).attr("data-dismiss")||e.stopPropagation()}),(0,a.default)(".page-select").change(function(){var e=new URL(window.location);e.searchParams.set("page",this.value),window.location.href=e.toString()}),(0,a.default)('a[data-toggle="tab"]').on("shown.bs.tab",function(e){sessionStorage.setItem("activeTab",(0,a.default)(e.target).attr("href"))});var e=sessionStorage.getItem("activeTab");if(e){var t=(0,a.default)('.nav-tabs a[href="'.concat(e,'"], .nav-pills a[href="').concat(e,'"]'));t.length?t.tab("show"):sessionStorage.removeItem("activeTab")}(0,s.makeSortableTables)(),(0,a.default)('[data-toggle="tooltip"]').tooltip()})}},"./CTFd/themes/core/assets/js/CTFd.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=d(o("./CTFd/themes/core/assets/js/fetch.js")),s=d(o("./CTFd/themes/core/assets/js/config.js")),a=o("./CTFd/themes/core/assets/js/api.js");o("./CTFd/themes/core/assets/js/patch.js");var i=d(o("./node_modules/markdown-it/index.js")),l=d(o("./node_modules/jquery/dist/jquery.js")),r=d(o("./CTFd/themes/core/assets/js/ezq.js"));function d(e){return e&&e.__esModule?e:{default:e}}function c(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}var m=new a.API("/"),u={},p={ezq:r.default},f={$:l.default,markdown:function(e){var t=function(t){for(var e=1;e".concat(e.body,"

")):o.find(".modal-body").append((0,l.default)(e.body));var n=(0,l.default)(c.format(e.button));return e.success&&(0,l.default)(n).click(function(){e.success()}),e.large&&o.find(".modal-dialog").addClass("modal-lg"),o.find(".modal-footer").append(n),(0,l.default)("main").append(o),o.modal("show"),(0,l.default)(o).on("hidden.bs.modal",function(){(0,l.default)(this).modal("dispose")}),o}function f(e){(0,l.default)("#ezq--notifications-toast-container").length||(0,l.default)("body").append((0,l.default)("
").attr({id:"ezq--notifications-toast-container"}).css({position:"fixed",bottom:"0",right:"0","min-width":"20%"}));var t=r.format(e.title,e.body),o=(0,l.default)(t);if(e.onclose&&(0,l.default)(o).find("button[data-dismiss=toast]").click(function(){e.onclose()}),e.onclick){var n=(0,l.default)(o).find(".toast-body");n.addClass("cursor-pointer"),n.click(function(){e.onclick()})}var s=!1!==e.autohide,a=!1!==e.animation,i=e.delay||1e4;return(0,l.default)("#ezq--notifications-toast-container").prepend(o),o.toast({autohide:s,delay:i,animation:a}),o.toast("show"),o}function j(e){var t=a.format(e.title),o=(0,l.default)(t);"string"==typeof e.body?o.find(".modal-body").append("

".concat(e.body,"

")):o.find(".modal-body").append((0,l.default)(e.body));var n=(0,l.default)(u),s=(0,l.default)(m);return o.find(".modal-footer").append(s),o.find(".modal-footer").append(n),(0,l.default)("main").append(o),(0,l.default)(o).on("hidden.bs.modal",function(){(0,l.default)(this).modal("dispose")}),(0,l.default)(n).click(function(){e.success()}),o.modal("show"),o}function h(e){if(e.target){var t=(0,l.default)(e.target);return t.find(".progress-bar").css("width",e.width+"%"),t}var o=i.format(e.width),n=a.format(e.title),s=(0,l.default)(n);return s.find(".modal-body").append((0,l.default)(o)),(0,l.default)("main").append(s),s.modal("show")}function _(e){var t={success:d,error:s}[e.type].format(e.body);return(0,l.default)(t)}var v={ezAlert:p,ezToast:f,ezQuery:j,ezProgressBar:h,ezBadge:_};t.default=v},"./CTFd/themes/core/assets/js/fetch.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,o("./node_modules/whatwg-fetch/fetch.js");var n,s=(n=o("./CTFd/themes/core/assets/js/config.js"))&&n.__esModule?n:{default:n};var a=window.fetch;t.default=function(e,t){return void 0===t&&(t={method:"GET",credentials:"same-origin",headers:{}}),e=s.default.urlRoot+e,void 0===t.headers&&(t.headers={}),t.credentials="same-origin",t.headers.Accept="application/json",t.headers["Content-Type"]="application/json",t.headers["CSRF-Token"]=s.default.csrfNonce,a(e,t)}},"./CTFd/themes/core/assets/js/patch.js":function(e,t,o){var n,l=(n=o("./node_modules/q/q.js"))&&n.__esModule?n:{default:n},s=o("./CTFd/themes/core/assets/js/api.js");function a(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function r(e,t){return function(t){for(var e=1;e>8*s&255).toString(16)).substr(-2)}return n},t.htmlEntities=function(e){return(0,i.default)("
").text(e).html()},t.cumulativeSum=function(e){for(var t=e.concat(),o=0;o'),(0,i.default)("th.sort-col").click(function(){var e=(0,i.default)(this).parents("table").eq(0),t=e.find("tr:gt(0)").toArray().sort(function(s){return function(e,t){var o=a(e,s),n=a(t,s);return i.default.isNumeric(o)&&i.default.isNumeric(n)?o-n:o.toString().localeCompare(n)}}((0,i.default)(this).index()));this.asc=!this.asc,this.asc||(t=t.reverse());for(var o=0;o
{% if q and field %} -
Searching for challenges with {{ field }} matching {{ q }}
-
{{ total }} results
+
+ Searching for challenges with {{ field }} matching {{ q }} +
+
+ {{ total }} results +
{% endif %} + {% with form = Forms.challenges.ChallengeSearchForm(field=field, q=q) %}
- - + {{ form.field(class="form-control custom-select w-100") }}
- - + {{ form.q(class="form-control w-100", placeholder="Search for matching challenge") }}
- - +
+ {% endwith %}
diff --git a/CTFd/themes/admin/templates/configs/accounts.html b/CTFd/themes/admin/templates/configs/accounts.html index 034d4372..6803f81a 100644 --- a/CTFd/themes/admin/templates/configs/accounts.html +++ b/CTFd/themes/admin/templates/configs/accounts.html @@ -1,57 +1,43 @@
+ + {% set verify_emails = "true" if verify_emails == True else "false" %} + {% set name_changes = "true" if name_changes == True else "false" %} + {% with form = Forms.config.AccountSettingsForm(verify_emails=verify_emails, name_changes=name_changes) %}
- - + {{ form.domain_whitelist.label }} + {{ form.domain_whitelist(class="form-control", value=domain_whitelist or "") }} - e.g. ctfd.io,gmail.com,yahoo.com + {{ form.domain_whitelist.description }}
- - + {{ form.verify_emails.label }} + {{ form.verify_emails(class="form-control custom-select") }} + + {{ form.verify_emails.description }} +
- - + {{ form.team_size.label }} + {{ form.team_size(class="form-control", value=team_size) }} + + {{ form.team_size.description }} +
- - + {{ form.name_changes.label }} + {{ form.name_changes(class="form-control custom-select") }} + + {{ form.name_changes.description }} +
- + {{ form.submit(class="btn btn-md btn-primary float-right") }}
+ {% endwith %}
\ No newline at end of file diff --git a/CTFd/themes/admin/templates/configs/backup.html b/CTFd/themes/admin/templates/configs/backup.html index bb3d52cd..743d9a85 100644 --- a/CTFd/themes/admin/templates/configs/backup.html +++ b/CTFd/themes/admin/templates/configs/backup.html @@ -39,17 +39,15 @@
+ {% with form = Forms.config.ExportCSVForm() %}
- - + {{ form.table.label }} + {{ form.table(class="form-control custom-select") }}
+ {% endwith %}
\ No newline at end of file diff --git a/CTFd/themes/admin/templates/editor.html b/CTFd/themes/admin/templates/editor.html index c7becb21..9f4c5807 100644 --- a/CTFd/themes/admin/templates/editor.html +++ b/CTFd/themes/admin/templates/editor.html @@ -69,18 +69,19 @@
+ + {% with form = Forms.pages.PageFilesUploadForm() %}
- - + {{ form.file.label }} + {{ form.file(id="media-files", class="form-control-file") }} - Attach multiple files using Control+Click or Cmd+Click. + {{ form.file.description }}
+ {% endwith %}
- - + {{ form.content(id="admin-pages-editor", class="d-none") }}
@@ -218,17 +196,14 @@
- + {{ form.nonce() }}
+ {% endwith %} diff --git a/CTFd/themes/admin/templates/modals/awards/create.html b/CTFd/themes/admin/templates/modals/awards/create.html index 433b66a8..1cc6d0ca 100644 --- a/CTFd/themes/admin/templates/modals/awards/create.html +++ b/CTFd/themes/admin/templates/modals/awards/create.html @@ -1,22 +1,23 @@ +{% with form = Forms.awards.AwardCreationForm() %}
- - + {{ form.name.label }} + {{ form.name(class="form-control", id="award-name-input") }}
- - + {{ form.value.label }} + {{ form.value(class="form-control", id="award-value-input") }}
- - + {{ form.category.label }} + {{ form.category(class="form-control", id="award-category-input") }}
- - + {{ form.description.label }} + {{ form.description(id="award-description-input", class="form-control", rows="5") }}
- + {{ form.icon.label }}
@@ -129,6 +130,7 @@
+{% endwith %} \ No newline at end of file diff --git a/CTFd/themes/admin/templates/modals/challenges/files.html b/CTFd/themes/admin/templates/modals/challenges/files.html index c3f78c1a..86074aec 100644 --- a/CTFd/themes/admin/templates/modals/challenges/files.html +++ b/CTFd/themes/admin/templates/modals/challenges/files.html @@ -21,13 +21,17 @@
+ {% with form = Forms.challenges.ChallengeFilesUploadForm() %}
- - Attach multiple files using Control+Click or Cmd+Click + {{ form.file(class="form-control-file") }} + + {{ form.file.description }} +
- + {{ form.submit(class="btn btn-success float-right") }}
+ {% endwith %}
\ No newline at end of file diff --git a/CTFd/themes/admin/templates/modals/mail/send.html b/CTFd/themes/admin/templates/modals/mail/send.html index cf482937..8971a0bd 100644 --- a/CTFd/themes/admin/templates/modals/mail/send.html +++ b/CTFd/themes/admin/templates/modals/mail/send.html @@ -1,16 +1,12 @@ +{% with form = Forms.email.SendEmailForm() %}
- - + {{ form.message.label }} + {{ form.message(class="form-control", rows="15") }}
- -
\ No newline at end of file + {{ form.submit(class="btn btn-primary float-right") }} + +{% endwith %} \ No newline at end of file diff --git a/CTFd/themes/admin/templates/modals/teams/create.html b/CTFd/themes/admin/templates/modals/teams/create.html index b115ee23..84fd5aae 100644 --- a/CTFd/themes/admin/templates/modals/teams/create.html +++ b/CTFd/themes/admin/templates/modals/teams/create.html @@ -1,57 +1,42 @@ +{% with form = Forms.teams.TeamEditForm() %}
-
- - + {{ form.name.label }} + {{ form.name(class="form-control") }}
- - + {{ form.email.label }} + {{ form.email(class="form-control") }}
- - + {{ form.password.label }} + {{ form.password(class="form-control") }}
- - + {{ form.website.label }} + {{ form.website(class="form-control") }}
- - + {{ form.affiliation.label }} + {{ form.affiliation(class="form-control") }}
- - + {{ form.country.label }} + {{ form.country(class="form-control custom-select") }}
- - + {{ form.hidden(class="form-check-input") }} + {{ form.hidden.label(class="form-check-label") }}
- - + {{ form.banned(class="form-check-input") }} + {{ form.banned.label(class="form-check-label") }}
- + {{ form.submit(id="update-team", class="btn btn-primary btn-outlined float-right modal-action") }}
+{% endwith %} \ No newline at end of file diff --git a/CTFd/themes/admin/templates/modals/teams/edit.html b/CTFd/themes/admin/templates/modals/teams/edit.html index 0aa907d6..bca653e4 100644 --- a/CTFd/themes/admin/templates/modals/teams/edit.html +++ b/CTFd/themes/admin/templates/modals/teams/edit.html @@ -1,57 +1,42 @@ +{% with form = Forms.teams.TeamCreateForm(obj=team) %}
-
- - + {{ form.name.label }} + {{ form.name(class="form-control") }}
- - + {{ form.email.label }} + {{ form.email(class="form-control") }}
- - + {{ form.password.label }} + {{ form.password(class="form-control") }}
- - + {{ form.website.label }} + {{ form.website(class="form-control") }}
- - + {{ form.affiliation.label }} + {{ form.affiliation(class="form-control") }}
- - + {{ form.country.label }} + {{ form.country(class="form-control custom-select") }}
- - + {{ form.hidden(class="form-check-input") }} + {{ form.hidden.label(class="form-check-label") }}
- - + {{ form.banned(class="form-check-input") }} + {{ form.banned.label(class="form-check-label") }}
- + {{ form.submit(id="update-team", class="btn btn-primary btn-outlined float-right modal-action") }}
+{% endwith %} \ No newline at end of file diff --git a/CTFd/themes/admin/templates/modals/users/create.html b/CTFd/themes/admin/templates/modals/users/create.html index 1fd548c5..c63dfca7 100644 --- a/CTFd/themes/admin/templates/modals/users/create.html +++ b/CTFd/themes/admin/templates/modals/users/create.html @@ -1,78 +1,59 @@ +{% with form = Forms.users.UserCreateForm() %}
-
- - + {{ form.name.label }} + {{ form.name(class="form-control") }}
- - + {{ form.email.label }} + {{ form.email(class="form-control") }}
- - + {{ form.password.label }} + {{ form.password(class="form-control") }}
- - + {{ form.website.label }} + {{ form.website(class="form-control") }}
- - + {{ form.affiliation.label }} + {{ form.affiliation(class="form-control") }}
- - + {{ form.country.label }} + {{ form.country(class="form-control custom-select") }}
- + {{ form.type(class="form-control form-inline custom-select", id="type-select") }}
- - + {{ form.verified(class="form-check-input") }} + {{ form.verified.label(class="form-check-label") }}
- - + {{ form.hidden(class="form-check-input") }} + {{ form.hidden.label(class="form-check-label") }}
- - + {{ form.banned(class="form-check-input") }} + {{ form.banned.label(class="form-check-label") }}
{% if can_send_mail() %}
- - + {{ form.notify(class="form-check-input", id="notify-checkbox") }} + {{ form.notify.label(class="form-check-label") }}
{% endif %}
- -
\ No newline at end of file + + {{ form.submit(id="update-user", class="btn btn-primary btn-outlined float-right modal-action") }} + +{% endwith %} \ No newline at end of file diff --git a/CTFd/themes/admin/templates/modals/users/edit.html b/CTFd/themes/admin/templates/modals/users/edit.html index 47bcda5c..e3df0c50 100644 --- a/CTFd/themes/admin/templates/modals/users/edit.html +++ b/CTFd/themes/admin/templates/modals/users/edit.html @@ -1,68 +1,48 @@ +{% with form = Forms.users.UserEditForm(obj=user) %}
-
- - + {{ form.name.label }} + {{ form.name(class="form-control") }}
- - + {{ form.email.label }} + {{ form.email(class="form-control") }}
- - + {{ form.password.label }} + {{ form.password(class="form-control") }}
- - + {{ form.website.label }} + {{ form.website(class="form-control") }}
- - + {{ form.affiliation.label }} + {{ form.affiliation(class="form-control") }}
- - + {{ form.country.label }} + {{ form.country(class="form-control custom-select") }}
- + {{ form.type(class="form-control form-inline custom-select", id="type-select") }}
- - + {{ form.verified(class="form-check-input") }} + {{ form.verified.label(class="form-check-label") }}
- - + {{ form.hidden(class="form-check-input") }} + {{ form.hidden.label(class="form-check-label") }}
- - + {{ form.banned(class="form-check-input") }} + {{ form.banned.label(class="form-check-label") }}
- -
\ No newline at end of file + {{ form.submit(class="btn btn-primary btn-outlined float-right modal-action") }} + +{% endwith %} \ No newline at end of file diff --git a/CTFd/themes/admin/templates/notifications.html b/CTFd/themes/admin/templates/notifications.html index 6852f210..c73ad622 100644 --- a/CTFd/themes/admin/templates/notifications.html +++ b/CTFd/themes/admin/templates/notifications.html @@ -9,67 +9,55 @@
+ {% with form = Forms.notifications.NotificationForm() %}
- - + {{ form.title.label }} + {{ form.title(class="form-control") }} + + {{ form.title.description }} +
- - + {{ form.content.label }} + {{ form.content(class="form-control", rows="3") }} + + {{ form.content.description }} +
- -
-
- - -
-
- - -
-
- - + {{ form.type.label }}
+ {% for radio in form.type %} +
+ {{ radio(class="form-check-input") }} + {{ radio.label(class="form-check-label") }} +
+ {% endfor %} + + {{ form.type.description }} +
- + {{ form.sound.label }}
- - + {{ form.sound(class="form-check-input") }} + {{ form.sound.label(class="form-check-label") }}
+ + {{ form.sound.description }} +
- + {{ form.submit(class="btn btn-success text-center") }}
+ {% endwith %}
diff --git a/CTFd/themes/admin/templates/reset.html b/CTFd/themes/admin/templates/reset.html index 29d36d9e..27ffb4fe 100644 --- a/CTFd/themes/admin/templates/reset.html +++ b/CTFd/themes/admin/templates/reset.html @@ -12,6 +12,7 @@
+ {% with form = Forms.config.ResetInstanceForm() %}