WIP: Add form globals (#1469)

* Work on #1467 
* Creates the Form global which will contain all forms.
This commit is contained in:
Kevin Chung
2020-06-11 02:32:20 -04:00
committed by GitHub
parent 62b5d4fc4e
commit a47cdb7ce1
51 changed files with 976 additions and 654 deletions

View File

@@ -163,17 +163,13 @@ def config():
# Clear the config cache so that we don't get stale values # Clear the config cache so that we don't get stale values
clear_config() clear_config()
database_tables = sorted(db.metadata.tables.keys())
configs = Configs.query.all() configs = Configs.query.all()
configs = dict([(c.key, get_config(c.key)) for c in configs]) configs = dict([(c.key, get_config(c.key)) for c in configs])
themes = ctf_config.get_themes() themes = ctf_config.get_themes()
themes.remove(get_config("ctf_theme")) themes.remove(get_config("ctf_theme"))
return render_template( return render_template("admin/config.html", themes=themes, **configs)
"admin/config.html", database_tables=database_tables, themes=themes, **configs
)
@admin.route("/admin/reset", methods=["GET", "POST"]) @admin.route("/admin/reset", methods=["GET", "POST"])

View File

@@ -17,7 +17,7 @@ from CTFd.utils.config.visibility import registration_visible
from CTFd.utils.crypto import verify_password from CTFd.utils.crypto import verify_password
from CTFd.utils.decorators import ratelimit from CTFd.utils.decorators import ratelimit
from CTFd.utils.decorators.visibility import check_registration_visibility 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.logging import log
from CTFd.utils.modes import TEAMS_MODE from CTFd.utils.modes import TEAMS_MODE
from CTFd.utils.security.auth import login_user, logout_user from CTFd.utils.security.auth import login_user, logout_user
@@ -93,6 +93,16 @@ def confirm(data=None):
@auth.route("/reset_password/<data>", methods=["POST", "GET"]) @auth.route("/reset_password/<data>", methods=["POST", "GET"])
@ratelimit(method="POST", limit=10, interval=60) @ratelimit(method="POST", limit=10, interval=60)
def reset_password(data=None): 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.<br> Please contact an organizer to have your password reset."
)
],
)
if data is not None: if data is not None:
try: try:
email_address = unserialize(data, max_age=1800) email_address = unserialize(data, max_age=1800)
@@ -142,12 +152,6 @@ def reset_password(data=None):
get_errors() 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: if not user:
return render_template( return render_template(
"reset_password.html", "reset_password.html",

View File

@@ -6,6 +6,10 @@ class _ConfigsWrapper:
def __getattr__(self, attr): def __getattr__(self, attr):
return get_config(attr) return get_config(attr)
@property
def ctf_name(self):
return get_config("theme_header", default="CTFd")
@property @property
def theme_header(self): def theme_header(self):
return markup(get_config("theme_header", default="")) return markup(get_config("theme_header", default=""))

49
CTFd/forms/__init__.py Normal file
View File

@@ -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

33
CTFd/forms/auth.py Normal file
View File

@@ -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")

30
CTFd/forms/awards.py Normal file
View File

@@ -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"),
],
)

30
CTFd/forms/challenges.py Normal file
View File

@@ -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")

62
CTFd/forms/config.py Normal file
View File

@@ -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")

10
CTFd/forms/email.py Normal file
View File

@@ -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")

17
CTFd/forms/fields.py Normal file
View File

@@ -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

View File

@@ -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")

33
CTFd/forms/pages.py Normal file
View File

@@ -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()])

22
CTFd/forms/self.py Normal file
View File

@@ -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")

66
CTFd/forms/setup.py Normal file
View File

@@ -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")

16
CTFd/forms/submissions.py Normal file
View File

@@ -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")

62
CTFd/forms/teams.py Normal file
View File

@@ -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

42
CTFd/forms/users.py Normal file
View File

@@ -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)

View File

@@ -158,8 +158,9 @@ function submit_form() {
var target = "/api/v1/pages"; var target = "/api/v1/pages";
var method = "POST"; var method = "POST";
if (params.id) { let part = window.location.pathname.split("/").pop();
target += "/" + params.id; if (part !== "new") {
target += "/" + part;
method = "PATCH"; method = "PATCH";
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -21,29 +21,29 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if q and field %} {% if q and field %}
<h5 class="text-muted text-center">Searching for challenges with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong></h5> <h5 class="text-muted text-center">
<h6 class="text-muted text-center pb-3">{{ total }} results</h6> Searching for challenges with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong>
</h5>
<h6 class="text-muted text-center pb-3">
{{ total }} results
</h6>
{% endif %} {% endif %}
{% with form = Forms.challenges.ChallengeSearchForm(field=field, q=q) %}
<form method="GET" class="form-inline"> <form method="GET" class="form-inline">
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label for="sel1" class="sr-only" >Search Field</label> {{ form.field(class="form-control custom-select w-100") }}
<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>
<div class="form-group col-md-8"> <div class="form-group col-md-8">
<label for="challenges-search" class="sr-only">Parameter</label> {{ form.q(class="form-control w-100", placeholder="Search for matching challenge") }}
<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>
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label class="sr-only">Search</label> <button type="submit" class="btn btn-primary w-100">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-search" aria-hidden="true"></i></button> <i class="fas fa-search" aria-hidden="true"></i>
</button>
</div> </div>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>

View File

@@ -1,57 +1,43 @@
<div role="tabpanel" class="tab-pane config-section" id="accounts"> <div role="tabpanel" class="tab-pane config-section" id="accounts">
{% 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 method="POST" autocomplete="off" class="w-100"> <form method="POST" autocomplete="off" class="w-100">
<div class="form-group"> <div class="form-group">
<label> {{ form.domain_whitelist.label }}
Account Email Whitelist {{ form.domain_whitelist(class="form-control", value=domain_whitelist or "") }}
<small class="form-text text-muted">
Comma-seperated email domains which users can register under.
</small>
</label>
<input class="form-control" id='domain-whitelist' name='domain_whitelist' type='text'
placeholder=""
{% if domain_whitelist is defined and domain_whitelist != None %}value="{{ domain_whitelist }}"{% endif %}>
<small class="form-text text-muted"> <small class="form-text text-muted">
e.g. ctfd.io,gmail.com,yahoo.com {{ form.domain_whitelist.description }}
</small> </small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> {{ form.verify_emails.label }}
Verify Emails<br> {{ form.verify_emails(class="form-control custom-select") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Control whether users must confirm their email addresses before playing {{ form.verify_emails.description }}
</small> </small>
</label>
<select class="form-control custom-select" name="verify_emails">
<option value="true" {% if verify_emails == True %}selected{% endif %}>Enabled</option>
<option value="false" {% if verify_emails == False or verify_emails == None %}selected{% endif %}>Disabled</option>
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> {{ form.team_size.label }}
Team Size<br> {{ form.team_size(class="form-control", value=team_size) }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Amount of users per team {{ form.team_size.description }}
</small> </small>
</label>
<input class="form-control" id="team_size" name="team_size" type="number" min="0" {% if team_size %}value="{{ team_size }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> {{ form.name_changes.label }}
Name Changes<br> {{ form.name_changes(class="form-control custom-select") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Control whether users can change their names {{ form.name_changes.description }}
</small> </small>
</label>
<select class="form-control custom-select" name="name_changes">
<option value="true" {% if name_changes == True or name_changes == None %}selected{% endif %}>Enabled</option>
<option value="false" {% if name_changes == False %}selected{% endif %}>Disabled</option>
</select>
</div> </div>
<button type="submit" class="btn btn-md btn-primary float-right">Update</button> {{ form.submit(class="btn btn-md btn-primary float-right") }}
</form> </form>
{% endwith %}
</div> </div>

View File

@@ -39,17 +39,15 @@
<input id="import-button" type="submit" class="btn btn-warning" value="Import"> <input id="import-button" type="submit" class="btn btn-warning" value="Import">
</div> </div>
<div role="tabpanel" class="tab-pane" id="save-csv"> <div role="tabpanel" class="tab-pane" id="save-csv">
{% with form = Forms.config.ExportCSVForm() %}
<form method="GET" action="{{ url_for('admin.export_csv') }}"> <form method="GET" action="{{ url_for('admin.export_csv') }}">
<div class="form-group"> <div class="form-group">
<label for="container-files">Database Table</label> {{ form.table.label }}
<select class="form-control custom-select" name="table"> {{ form.table(class="form-control custom-select") }}
{% for table in database_tables %}
<option value="{{ table }}">{{ table }}</option>
{% endfor %}
</select>
</div> </div>
<input type="submit" class="btn btn-warning" value="Download CSV"> <input type="submit" class="btn btn-warning" value="Download CSV">
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -69,18 +69,19 @@
</div> </div>
</div> </div>
</div> </div>
{% with form = Forms.pages.PageFilesUploadForm() %}
<form id="media-library-upload" enctype="multipart/form-data"> <form id="media-library-upload" enctype="multipart/form-data">
<div class="form-group"> <div class="form-group">
<label for="media-files"> <b>{{ form.file.label }}</b>
Upload Files {{ form.file(id="media-files", class="form-control-file") }}
</label>
<input type="file" name="file" id="media-files" class="form-control-file" multiple>
<sub class="help-block"> <sub class="help-block">
Attach multiple files using Control+Click or Cmd+Click. {{ form.file.description }}
</sub> </sub>
</div> </div>
<input type="hidden" value="page" name="type"> <input type="hidden" value="page" name="type">
</form> </form>
{% endwith %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<div class="float-right"> <div class="float-right">
@@ -106,29 +107,28 @@
{% endfor %} {% endfor %}
</div> </div>
{% set content = page.content if page is defined else "" %}
{% with form = Forms.pages.PageEditForm(content=content) %}
<form id="page-edit" method="POST"> <form id="page-edit" method="POST">
<div class="form-group"> <div class="form-group">
<div class="col-md-12"> <div class="col-md-12">
<label> {% set title = page.title if page is defined %}
Title<br> <b>{{ form.title.label }}</b>
<small class="text-muted">This is the title shown on the navigation bar</small> {{ form.title(class="form-control radius", id="route", placeholder="Title", value=title) }}
</label> <small class="text-muted">
<input class="form-control radius" id="route" type="text" name="title" {{ form.title.description }}
value="{% if page is defined %}{{ page.title }}{% endif %}" placeholder="Title"> </small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-md-12"> <div class="col-md-12">
<label> {% set route = page.route if page is defined %}
Route<br> <b>{{ form.route.label }}</b>
<small class="text-muted"> {{ form.route(class="form-control radius", placeholder="Route", value=route) }}
This is the URL route that your page will be at (e.g. /page). You can <small class="text-muted">
also enter links to link to that page. {{ form.route.description }}
</small> </small>
</label>
<input class="form-control radius" id="route" type="text" name="route"
value="{% if page is defined %}{{ page.route }}{% endif %}" placeholder="Route">
</div> </div>
</div> </div>
@@ -165,50 +165,28 @@
<span class="nav-link d-none d-md-block d-lg-block">|</span> <span class="nav-link d-none d-md-block d-lg-block">|</span>
<div class="form-group pr-3"> <div class="form-group pr-3">
Draft:
{% set draft = page is defined and page.draft == True %} {% set draft = page is defined and page.draft == True %}
<select class="form-control custom-select" name="draft"> {{ form.draft(class="form-check-input", checked=draft) }}
<option value="true" {% if draft == True %}selected{% endif %}> {{ form.draft.label(class="form-check-label") }}
True
</option>
<option value="false" {% if draft == False %}selected{% endif %}>
False
</option>
</select>
</div> </div>
<div class="form-group pr-3"> <div class="form-group pr-3">
Hidden:
{% set hidden = page is defined and page.hidden == True %} {% set hidden = page is defined and page.hidden == True %}
<select class="form-control custom-select" name="hidden"> {{ form.hidden(class="form-check-input", checked=hidden) }}
<option value="true" {% if hidden == True %}selected{% endif %}>True {{ form.hidden.label(class="form-check-label") }}
</option>
<option value="false" {% if hidden == False %}selected{% endif %}>False
</option>
</select>
</div> </div>
<div class="form-group pr-3"> <div class="form-group pr-3">
Authentication Required:
{% set auth_required = page is defined and page.auth_required == True %} {% set auth_required = page is defined and page.auth_required == True %}
<select class="form-control custom-select" name="auth_required"> {{ form.auth_required(class="form-check-input", checked=auth_required) }}
<option value="true" {% if auth_required == True %}selected{% endif %}> {{ form.auth_required.label(class="form-check-label") }}
True
</option>
<option value="false" {% if auth_required == False %}selected{% endif %}>
False
</option>
</select>
</div> </div>
</div> </div>
<br> <br>
<div class="form-group"> <div class="form-group">
<textarea id="admin-pages-editor" class="d-none" {{ form.content(id="admin-pages-editor", class="d-none") }}
name="content">{% if page is defined %}{{ page.content }}{% endif %}</textarea>
<input name='id' type='hidden'
{% if page is defined %}value="{{ page.id }}"{% endif %}>
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane content" id="content-preview" style="height:400px"> <div role="tabpanel" class="tab-pane content" id="content-preview" style="height:400px">
@@ -218,17 +196,14 @@
</div> </div>
<div class="form-group float-right"> <div class="form-group float-right">
<input name='nonce' type='hidden' value="{{ nonce }}"> {{ form.nonce() }}
<button class="btn btn-primary" id="save-page"> <button class="btn btn-primary" id="save-page">
{% if page is defined %} Save
Update
{% else %}
Save
{% endif %}
</button> </button>
</div> </div>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,22 +1,23 @@
{% with form = Forms.awards.AwardCreationForm() %}
<form id="user-award-form" method="POST"> <form id="user-award-form" method="POST">
<div class="form-group"> <div class="form-group">
<label for="award-name-input">Name</label> <b>{{ form.name.label }}</b>
<input type="text" class="form-control" id="award-name-input" name="name"> {{ form.name(class="form-control", id="award-name-input") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="award-value-input">Value</label> <b>{{ form.value.label }}</b>
<input type="number" class="form-control" id="award-value-input" name="value"> {{ form.value(class="form-control", id="award-value-input") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="award-category-input">Category</label> <b>{{ form.category.label }}</b>
<input type="text" class="form-control" id="award-category-input" name="category"> {{ form.category(class="form-control", id="award-category-input") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="award-description-input" class="control-label">Description</label> <b>{{ form.description.label }}</b>
<textarea id="award-description-input" class="form-control" name="description" rows="10"></textarea> {{ form.description(id="award-description-input", class="form-control", rows="5") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="award-icon-input">Icon</label> <b>{{ form.icon.label }}</b>
<div class="pt-1"> <div class="pt-1">
<table class="table table-sm w-100"> <table class="table table-sm w-100">
<tr> <tr>
@@ -129,6 +130,7 @@
<div id="results"> <div id="results">
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" id="award-create-button" class="btn btn-primary">Create</button> {{ form.submit(id="award-create-button", class="btn btn-primary") }}
</div> </div>
</form> </form>
{% endwith %}

View File

@@ -21,13 +21,17 @@
</table> </table>
<div class="col-md-12 mt-3"> <div class="col-md-12 mt-3">
{% with form = Forms.challenges.ChallengeFilesUploadForm() %}
<form id="file-add-form" method="POST"> <form id="file-add-form" method="POST">
<div class="form-group"> <div class="form-group">
<input class="form-control-file" type="file" name="file" multiple="multiple"> {{ form.file(class="form-control-file") }}
<sub class="text-muted">Attach multiple files using Control+Click or Cmd+Click</sub> <sub class="text-muted">
{{ form.file.description }}
</sub>
</div> </div>
<div class="form-group"> <div class="form-group">
<button class="btn btn-success float-right" id="submit-files">Upload</button> {{ form.submit(class="btn btn-success float-right") }}
</div> </div>
</form> </form>
{% endwith %}
</div> </div>

View File

@@ -1,16 +1,12 @@
{% with form = Forms.email.SendEmailForm() %}
<form id="user-mail-form" method="POST"> <form id="user-mail-form" method="POST">
<div class="form-group"> <div class="form-group">
<label> {{ form.message.label }}
Message {{ form.message(class="form-control", rows="15") }}
<br>
<small></small>
</label>
<textarea class="form-control" name="text" placeholder="" rows="15"></textarea>
</div> </div>
<div id="results"> <div id="results">
</div> </div>
<button type="submit" class="btn btn-primary float-right"> {{ form.submit(class="btn btn-primary float-right") }}
Send </form>
</button> {% endwith %}
</form>

View File

@@ -1,57 +1,42 @@
{% with form = Forms.teams.TeamEditForm() %}
<form id="team-info-create-form" method="POST"> <form id="team-info-create-form" method="POST">
<input type="hidden" name="id">
<div class="form-group"> <div class="form-group">
<label for="name">Team Name</label> <b>{{ form.name.label }}</b>
<input type="text" class="form-control" name="name" id="name" {{ form.name(class="form-control") }}
{% if team is defined and team.name %}value="{{ team.name }}"{% endif %} required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Email</label> <b>{{ form.email.label }}</b>
<input type="email" class="form-control" name="email" id="email" {{ form.email(class="form-control") }}
{% if team is defined and team.email %}value="{{ team.email }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> <b>{{ form.password.label }}</b>
<input type="password" class="form-control" name="password" id="password"/> {{ form.password(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="website">Website</label> <b>{{ form.website.label }}</b>
<input type="url" class="form-control" name="website" id="website" {{ form.website(class="form-control") }}
{% if team is defined and team.website %}value="{{ team.website }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Affiliation</label> <b>{{ form.affiliation.label }}</b>
<input type="text" class="form-control" name="affiliation" id="affiliation" {{ form.affiliation(class="form-control") }}
{% if team is defined and team.affiliation %}value="{{ team.affiliation }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Country</label> <b>{{ form.country.label }}</b>
<select class="form-control custom-select" id="country-input" name="country"> {{ form.country(class="form-control custom-select") }}
<option></option>
{% set countries = get_countries() %}
{% for country_code in countries.keys() %}
<option value="{{ country_code }}" {% if team is defined and team.country == country_code %}selected{% endif %}>
{{ countries[country_code] }}
</option>
{% endfor %}
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="hidden" id="hidden-checkbox" {{ form.hidden(class="form-check-input") }}
{% if team is defined and team.hidden %}checked{% endif %}> {{ form.hidden.label(class="form-check-label") }}
<label class="form-check-label" for="hidden-checkbox">Hidden</label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="banned" id="banned-checkbox" {{ form.banned(class="form-check-input") }}
{% if team is defined and team.banned %}checked{% endif %}> {{ form.banned.label(class="form-check-label") }}
<label class="form-check-label" for="banned-checkbox">Banned</label>
</div> </div>
</div> </div>
<div id="results"> <div id="results">
</div> </div>
<button id="update-team" type="submit" class="btn btn-primary btn-outlined float-right modal-action"> {{ form.submit(id="update-team", class="btn btn-primary btn-outlined float-right modal-action") }}
Submit
</button>
</form> </form>
{% endwith %}

View File

@@ -1,57 +1,42 @@
{% with form = Forms.teams.TeamCreateForm(obj=team) %}
<form id="team-info-edit-form" method="POST"> <form id="team-info-edit-form" method="POST">
<input type="hidden" name="id">
<div class="form-group"> <div class="form-group">
<label for="name">Team Name</label> {{ form.name.label }}
<input type="text" class="form-control" name="name" id="name" {{ form.name(class="form-control") }}
{% if team is defined and team.name %}value="{{ team.name }}"{% endif %} required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Email</label> {{ form.email.label }}
<input type="email" class="form-control" name="email" id="email" {{ form.email(class="form-control") }}
{% if team is defined and team.email %}value="{{ team.email }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> {{ form.password.label }}
<input type="password" class="form-control" name="password" id="password"/> {{ form.password(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="website">Website</label> {{ form.website.label }}
<input type="url" class="form-control" name="website" id="website" {{ form.website(class="form-control") }}
{% if team is defined and team.website %}value="{{ team.website }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Affiliation</label> {{ form.affiliation.label }}
<input type="text" class="form-control" name="affiliation" id="affiliation" {{ form.affiliation(class="form-control") }}
{% if team is defined and team.affiliation %}value="{{ team.affiliation }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Country</label> {{ form.country.label }}
<select class="form-control custom-select" id="country-input" name="country"> {{ form.country(class="form-control custom-select") }}
<option></option>
{% set countries = get_countries() %}
{% for country_code in countries.keys() %}
<option value="{{ country_code }}" {% if team is defined and team.country == country_code %}selected{% endif %}>
{{ countries[country_code] }}
</option>
{% endfor %}
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="hidden" id="hidden-checkbox" {{ form.hidden(class="form-check-input") }}
{% if team is defined and team.hidden %}checked{% endif %}> {{ form.hidden.label(class="form-check-label") }}
<label class="form-check-label" for="hidden-checkbox">Hidden</label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="banned" id="banned-checkbox" {{ form.banned(class="form-check-input") }}
{% if team is defined and team.banned %}checked{% endif %}> {{ form.banned.label(class="form-check-label") }}
<label class="form-check-label" for="banned-checkbox">Banned</label>
</div> </div>
</div> </div>
<div id="results"> <div id="results">
</div> </div>
<button id="update-team" type="submit" class="btn btn-primary btn-outlined float-right modal-action"> {{ form.submit(id="update-team", class="btn btn-primary btn-outlined float-right modal-action") }}
Submit
</button>
</form> </form>
{% endwith %}

View File

@@ -1,78 +1,59 @@
{% with form = Forms.users.UserCreateForm() %}
<form id="user-info-create-form" method="POST"> <form id="user-info-create-form" method="POST">
<input type="hidden" name="id">
<div class="form-group"> <div class="form-group">
<label for="name">User Name</label> {{ form.name.label }}
<input type="text" class="form-control" name="name" id="name" {{ form.name(class="form-control") }}
{% if user is defined and user.name %}value="{{ user.name }}"{% endif %} required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Email</label> {{ form.email.label }}
<input type="email" class="form-control" name="email" id="email" {{ form.email(class="form-control") }}
{% if user is defined and user.email %}value="{{ user.email }}"{% endif %} required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> {{ form.password.label }}
<input type="password" class="form-control" name="password" id="password"/> {{ form.password(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="website">Website</label> {{ form.website.label }}
<input type="text" class="form-control" name="website" id="website" {{ form.website(class="form-control") }}
{% if user is defined and user.website %}value="{{ user.website }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Affiliation</label> {{ form.affiliation.label }}
<input type="text" class="form-control" name="affiliation" id="affiliation" {{ form.affiliation(class="form-control") }}
{% if user is defined and user.affiliation %}value="{{ user.affiliation }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Country</label> {{ form.country.label }}
<select class="form-control custom-select" id="country-input" name="country"> {{ form.country(class="form-control custom-select") }}
<option></option>
{% set countries = get_countries() %}
{% for country_code in countries.keys() %}
<option value="{{ country_code }}" {% if user is defined and user.country == country_code %}selected{% endif %}>
{{ countries[country_code] }}
</option>
{% endfor %}
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<select class="form-control form-inline custom-select" id="type-select" name="type"> {{ form.type(class="form-control form-inline custom-select", id="type-select") }}
<option value="user"{% if user is defined and user.type == 'user' %} selected{% endif %}>
User
</option>
<option value="admin"{% if user is defined and user.type == 'admin' %} selected{% endif %}>
Admin
</option>
</select>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="verified" id="verified-checkbox"> {{ form.verified(class="form-check-input") }}
<label class="form-check-label" for="verified-checkbox">Verified</label> {{ form.verified.label(class="form-check-label") }}
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="hidden" id="hidden-checkbox"> {{ form.hidden(class="form-check-input") }}
<label class="form-check-label" for="hidden-checkbox">Hidden</label> {{ form.hidden.label(class="form-check-label") }}
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="banned" id="banned-checkbox"> {{ form.banned(class="form-check-input") }}
<label class="form-check-label" for="banned-checkbox">Banned</label> {{ form.banned.label(class="form-check-label") }}
</div> </div>
</div> </div>
{% if can_send_mail() %} {% if can_send_mail() %}
<div class="form-group"> <div class="form-group">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="notify" id="notify-checkbox" checked> {{ form.notify(class="form-check-input", id="notify-checkbox") }}
<label class="form-check-label" for="notify-checkbox">Email account credentials to user</label> {{ form.notify.label(class="form-check-label") }}
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div id="results"> <div id="results">
</div> </div>
<button id="update-user" type="submit" class="btn btn-primary btn-outlined float-right modal-action">
Submit {{ form.submit(id="update-user", class="btn btn-primary btn-outlined float-right modal-action") }}
</button> </form>
</form> {% endwith %}

View File

@@ -1,68 +1,48 @@
{% with form = Forms.users.UserEditForm(obj=user) %}
<form id="user-info-edit-form"> <form id="user-info-edit-form">
<input type="hidden" name="id">
<div class="form-group"> <div class="form-group">
<label for="name">User Name</label> {{ form.name.label }}
<input type="text" class="form-control" name="name" id="name" {{ form.name(class="form-control") }}
{% if user is defined and user.name %}value="{{ user.name }}"{% endif %} required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Email</label> {{ form.email.label }}
<input type="email" class="form-control" name="email" id="email" {{ form.email(class="form-control") }}
{% if user is defined and user.email %}value="{{ user.email }}"{% endif %} required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> {{ form.password.label }}
<input type="password" class="form-control" name="password" id="password"/> {{ form.password(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="website">Website</label> {{ form.website.label }}
<input type="text" class="form-control" name="website" id="website" {{ form.website(class="form-control") }}
{% if user is defined and user.website %}value="{{ user.website }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Affiliation</label> {{ form.affiliation.label }}
<input type="text" class="form-control" name="affiliation" id="affiliation" {{ form.affiliation(class="form-control") }}
{% if user is defined and user.affiliation %}value="{{ user.affiliation }}"{% endif %}>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Country</label> {{ form.country.label }}
<select class="form-control custom-select" id="country-input" name="country"> {{ form.country(class="form-control custom-select") }}
<option></option>
{% set countries = get_countries() %}
{% for country_code in countries.keys() %}
<option value="{{ country_code }}" {% if user is defined and user.country == country_code %}selected{% endif %}>
{{ countries[country_code] }}
</option>
{% endfor %}
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<select class="form-control form-inline custom-select" id="type-select" name="type"> {{ form.type(class="form-control form-inline custom-select", id="type-select") }}
<option value="user"{% if user is defined and user.type == 'user' %} selected{% endif %}>
User
</option>
<option value="admin"{% if user is defined and user.type == 'admin' %} selected{% endif %}>
Admin
</option>
</select>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="verified" id="verified-checkbox" {% if user is defined and user.verified %}checked{% endif %}> {{ form.verified(class="form-check-input") }}
<label class="form-check-label" for="verified-checkbox">Verified</label> {{ form.verified.label(class="form-check-label") }}
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="hidden" id="hidden-checkbox" {% if user is defined and user.hidden %}checked{% endif %}> {{ form.hidden(class="form-check-input") }}
<label class="form-check-label" for="hidden-checkbox">Hidden</label> {{ form.hidden.label(class="form-check-label") }}
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="banned" id="banned-checkbox" {% if user is defined and user.banned %}checked{% endif %}> {{ form.banned(class="form-check-input") }}
<label class="form-check-label" for="banned-checkbox">Banned</label> {{ form.banned.label(class="form-check-label") }}
</div> </div>
</div> </div>
<div id="results"> <div id="results">
</div> </div>
<button id="update-user" type="submit" class="btn btn-primary btn-outlined float-right modal-action"> {{ form.submit(class="btn btn-primary btn-outlined float-right modal-action") }}
Submit </form>
</button> {% endwith %}
</form>

View File

@@ -9,67 +9,55 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% with form = Forms.notifications.NotificationForm() %}
<form method="POST" id="notifications_form"> <form method="POST" id="notifications_form">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.title.label }}</b>
Title {{ form.title(class="form-control") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Notification title {{ form.title.description }}
</small> </small>
</label>
<input class="form-control" type="text" name="title">
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.content.label }}</b>
Content {{ form.content(class="form-control", rows="3") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Notification contents can be made up of HTML {{ form.content.description }}
</small> </small>
</label>
<textarea class="form-control" type="text" name="content" rows="3"></textarea>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="col"> <div class="col">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.type.label }}</b>
Type
<small class="form-text text-muted">
What type of notification users receive
</small>
</label>
</div>
<div class="form-check form-check-inline pr-1">
<input class="form-check-input" type="radio" name="type" value="toast" id="type-radio-toast" checked>
<label class="form-check-label" for="type-radio-toast">Toast</label>
</div>
<div class="form-check form-check-inline pr-1">
<input class="form-check-input" type="radio" name="type" value="alert" id="type-radio-alert">
<label class="form-check-label" for="type-radio-alert">Alert</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="type" value="background" id="type-radio-bg">
<label class="form-check-label" for="type-radio-bg">Background</label>
</div> </div>
{% for radio in form.type %}
<div class="form-check form-check-inline pr-1">
{{ radio(class="form-check-input") }}
{{ radio.label(class="form-check-label") }}
</div>
{% endfor %}
<small class="form-text text-muted">
{{ form.type.description }}
</small>
</div> </div>
<div class="col"> <div class="col">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.sound.label }}</b>
Sound
<small class="form-text text-muted">
Play sound for users
</small>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="sound" value="true" id="sound-enable" checked> {{ form.sound(class="form-check-input") }}
<label class="form-check-label" for="sound-enable">Play Sound</label> {{ form.sound.label(class="form-check-label") }}
</div> </div>
<small class="form-text text-muted">
{{ form.sound.description }}
</small>
</div> </div>
</div> </div>
<div class="float-right"> <div class="float-right">
<button type="submit" class="btn btn-success text-center">Submit</button> {{ form.submit(class="btn btn-success text-center") }}
</div> </div>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,6 +12,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% with form = Forms.config.ResetInstanceForm() %}
<form method="POST" id="reset-ctf-form"> <form method="POST" id="reset-ctf-form">
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
<p> <p>
@@ -38,9 +39,8 @@
<div class="form-group pb-2"> <div class="form-group pb-2">
<div class="form-check"> <div class="form-check">
<label class="form-check-label"> {{ form.accounts(class="form-check-input") }}
<input class="form-check-input" type="checkbox" name="accounts"> Accounts {{ form.accounts.label(class="form-check-label") }}
</label>
</div> </div>
<span class="text-muted"> <span class="text-muted">
Deletes all user and team accounts and their associated information<br> Deletes all user and team accounts and their associated information<br>
@@ -50,9 +50,8 @@
<div class="form-group pb-2"> <div class="form-group pb-2">
<div class="form-check"> <div class="form-check">
<label class="form-check-label"> {{ form.submissions(class="form-check-input") }}
<input class="form-check-input" type="checkbox" name="submissions"> Submissions {{ form.submissions.label(class="form-check-label") }}
</label>
</div> </div>
<span class="text-muted"> <span class="text-muted">
Deletes all records that accounts gained points or took an action<br> Deletes all records that accounts gained points or took an action<br>
@@ -62,9 +61,8 @@
<div class="form-group pb-2"> <div class="form-group pb-2">
<div class="form-check"> <div class="form-check">
<label class="form-check-label"> {{ form.challenges(class="form-check-input") }}
<input class="form-check-input" type="checkbox" name="challenges"> Challenges {{ form.challenges.label(class="form-check-label") }}
</label>
</div> </div>
<span class="text-muted"> <span class="text-muted">
Deletes all challenges and associated data<br> Deletes all challenges and associated data<br>
@@ -74,9 +72,8 @@
<div class="form-group pb-2"> <div class="form-group pb-2">
<div class="form-check"> <div class="form-check">
<label class="form-check-label"> {{ form.pages(class="form-check-input") }}
<input class="form-check-input" type="checkbox" name="pages"> Pages {{ form.pages.label(class="form-check-label") }}
</label>
</div> </div>
<span class="text-muted"> <span class="text-muted">
Deletes all pages and their associated files<br> Deletes all pages and their associated files<br>
@@ -86,9 +83,8 @@
<div class="form-group pb-2"> <div class="form-group pb-2">
<div class="form-check"> <div class="form-check">
<label class="form-check-label"> {{ form.notifications(class="form-check-input") }}
<input class="form-check-input" type="checkbox" name="notifications"> Notifications {{ form.notifications.label(class="form-check-label") }}
</label>
</div> </div>
<span class="text-muted"> <span class="text-muted">
Deletes all notifications<br> Deletes all notifications<br>
@@ -98,12 +94,11 @@
<br> <br>
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}"> {{ form.nonce() }}
<button class="btn btn-warning btn-lg btn-block"> {{ form.submit(class="btn btn-warning btn-lg btn-block") }}
Reset CTF
</button>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -16,27 +16,29 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if q and field %} {% if q and field %}
<h5 class="text-muted text-center">Searching for submissions with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong></h5> <h5 class="text-muted text-center">
<h6 class="text-muted text-center pb-3">Page {{ submissions.page }} of {{ submissions.total }} results</h6> Searching for submissions with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong>
</h5>
<h6 class="text-muted text-center pb-3">
Page {{ submissions.page }} of {{ submissions.total }} results
</h6>
{% endif %} {% endif %}
{% with form = Forms.submissions.SubmissionSearchForm(field=field, q=q) %}
<form method="GET" class="form-inline"> <form method="GET" class="form-inline">
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label for="sel1" class="sr-only" >Search Field</label> {{ form.field(class="form-control custom-select w-100") }}
<select class="form-control custom-select w-100" id="sel1" name="field">
<option value="provided" {% if field == 'provided' %}selected{% endif %}>Provided</option>
<option value="id" {% if field == 'id' %}selected{% endif %}>ID</option>
</select>
</div> </div>
<div class="form-group col-md-8"> <div class="form-group col-md-8">
<label for="team-name-search" class="sr-only">Parameter</label> {{ form.q(class="form-control w-100", placeholder="Search for matching submission") }}
<input type="text" class="form-control w-100" id="team-name-search" name="q" placeholder="Search for matching submission" {% if q %}value="{{q}}"{% endif %}>
</div> </div>
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label for="team-name-search" class="sr-only">Search</label> <button type="submit" class="btn btn-primary w-100">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-search" aria-hidden="true"></i></button> <i class="fas fa-search" aria-hidden="true"></i>
</button>
</div> </div>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>

View File

@@ -20,28 +20,29 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if q and field %} {% if q and field %}
<h5 class="text-muted text-center">Searching for teams with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong></h5> <h5 class="text-muted text-center">
<h6 class="text-muted text-center pb-3">Page {{ teams.page }} of {{ teams.total }} results</h6> Searching for teams with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong>
</h5>
<h6 class="text-muted text-center pb-3">
Page {{ teams.page }} of {{ teams.total }} results
</h6>
{% endif %} {% endif %}
{% with form = Forms.teams.TeamSearchForm() %}
<form method="GET" class="form-inline"> <form method="GET" class="form-inline">
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label for="sel1" class="sr-only" >Search Field</label> {{ form.field(class="form-control custom-select w-100") }}
<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="affiliation" {% if field == 'affiliation' %}selected{% endif %}>Affiliation</option>
</select>
</div> </div>
<div class="form-group col-md-8"> <div class="form-group col-md-8">
<label for="team-search" class="sr-only">Parameter</label> {{ form.q(class="form-control w-100", placeholder="Search for matching teams") }}
<input type="text" class="form-control w-100" id="team-search" name="q" placeholder="Search for matching teams" {% if q %}value="{{q}}"{% endif %}>
</div> </div>
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label class="sr-only">Search</label> <button type="submit" class="btn btn-primary w-100">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-search" aria-hidden="true"></i></button> <i class="fas fa-search" aria-hidden="true"></i>
</button>
</div> </div>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>

View File

@@ -21,30 +21,29 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if q and field %} {% if q and field %}
<h5 class="text-muted text-center">Searching for users with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong></h5> <h5 class="text-muted text-center">
<h6 class="text-muted text-center pb-3">Page {{ users.page }} of {{ users.total }} results</h6> Searching for users with <strong>{{ field }}</strong> matching <strong>{{ q }}</strong>
</h5>
<h6 class="text-muted text-center pb-3">
Page {{ users.page }} of {{ users.total }} results
</h6>
{% endif %} {% endif %}
{% with form = Forms.users.UserSearchForm(field=field, q=q) %}
<form method="GET" class="form-inline"> <form method="GET" class="form-inline">
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label for="sel1" class="sr-only" >Search Field</label> {{ form.field(class="form-control custom-select w-100") }}
<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="email" {% if field == 'email' %}selected{% endif %}>Email</option>
<option value="affiliation" {% if field == 'affiliation' %}selected{% endif %}>Affiliation</option>
<option value="ip" {% if field == 'ip' %}selected{% endif %}>IP Address</option>
</select>
</div> </div>
<div class="form-group col-md-8"> <div class="form-group col-md-8">
<label for="users-search" class="sr-only">Parameter</label> {{ form.q(class="form-control w-100", placeholder="Search for matching users") }}
<input type="text" class="form-control w-100" id="users-search" name="q" placeholder="Search for matching user" {% if q %}value="{{q}}"{% endif %}>
</div> </div>
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<label for="users-search" class="sr-only">Search</label> <button type="submit" class="btn btn-primary w-100">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-search" aria-hidden="true"></i></button> <i class="fas fa-search" aria-hidden="true"></i>
</button>
</div> </div>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>

View File

@@ -15,7 +15,7 @@
<script type="text/javascript"> <script type="text/javascript">
var init = { var init = {
'urlRoot': "{{ request.script_root }}", 'urlRoot': "{{ request.script_root }}",
'csrfNonce': "{{ nonce }}", 'csrfNonce': "{{ Session.nonce }}",
'userMode': "{{ Configs.user_mode }}", 'userMode': "{{ Configs.user_mode }}",
'userId': {{ Session.id }}, 'userId': {{ Session.id }},
'start': {{ Configs.start | tojson }}, 'start': {{ Configs.start | tojson }},

View File

@@ -26,15 +26,17 @@
<hr> <hr>
{% with form = Forms.auth.ConfirmForm() %}
<form method="POST" action="{{ url_for('auth.confirm') }}"> <form method="POST" action="{{ url_for('auth.confirm') }}">
<h4 class="text-center"> <h4 class="text-center">
Need to resend the confirmation email? Need to resend the confirmation email?
</h4> </h4>
<div class="col-md-12 text-center"> <div class="col-md-12 text-center">
<button type="submit" tabindex="0" class="btn btn-md btn-primary btn-outlined">Resend</button> {{ form.submit(class="btn btn-md btn-primary btn-outlined") }}
</div> </div>
<input type="hidden" name="nonce" value="{{ nonce }}"> {{ form.nonce() }}
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -22,18 +22,15 @@
<hr> <hr>
{% endif %} {% endif %}
{% with form = Forms.auth.LoginForm() %}
<form method="post" accept-charset="utf-8" autocomplete="off"> <form method="post" accept-charset="utf-8" autocomplete="off">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.name.label }}</b>
User Name or Email {{ form.name(class="form-control", value=name) }}
</label>
<input class="form-control" type="text" name="name" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.password.label }}</b>
Password {{ form.password(class="form-control", value=password) }}
</label>
<input class="form-control" type="password" name="password" />
</div> </div>
<div class="row pt-3"> <div class="row pt-3">
<div class="col-md-6"> <div class="col-md-6">
@@ -42,13 +39,12 @@
</a> </a>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<button type="submit" class="btn btn-md btn-primary btn-outlined float-right"> {{ form.submit(class="btn btn-md btn-primary btn-outlined float-right") }}
Submit
</button>
</div> </div>
</div> </div>
<input type="hidden" name="nonce" value="{{ nonce }}"> {{ form.nonce() }}
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -22,32 +22,28 @@
<hr> <hr>
{% endif %} {% endif %}
{% with form = Forms.auth.RegistrationForm() %}
<form method="post" accept-charset="utf-8" autocomplete="off" role="form"> <form method="post" accept-charset="utf-8" autocomplete="off" role="form">
<div class="form-group"> <div class="form-group">
<label> {{ form.name.label }}
User Name {{ form.name(class="form-control", value=name) }}
</label>
<input class="form-control" type="text" name="name" {% if name %}value="{{ name }}"{% endif %} />
</div> </div>
<div class="form-group"> <div class="form-group">
<label> {{ form.email.label }}
Email {{ form.email(class="form-control", value=email) }}
</label>
<input class="form-control" type="text" name="email" {% if email %}value="{{ email }}"{% endif %} />
</div> </div>
<div class="form-group"> <div class="form-group">
<label> {{ form.password.label }}
Password {{ form.password(class="form-control", value=password) }}
</label>
<input class="form-control" type="password" name="password" {% if password %}value="{{ password }}"{% endif %}/>
</div> </div>
<div class="row pt-3"> <div class="row pt-3">
<div class="col-md-12"> <div class="col-md-12">
<button type="submit" class="btn btn-md btn-primary btn-outlined float-right">Submit</button> {{ form.submit(class="btn btn-md btn-primary btn-outlined float-right") }}
</div> </div>
</div> </div>
<input type="hidden" name="nonce" value="{{ nonce }}"> {{ form.nonce() }}
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -14,34 +14,46 @@
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% include "components/errors.html" %} {% include "components/errors.html" %}
{% if can_send_mail() %} {% if mode == "set" %}
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal"> {% with form = Forms.auth.ResetPasswordForm() %}
<input name='nonce' type='hidden' value="{{ nonce }}"> <form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
{% if mode %} <div class="row">
<div class="form-group"> <div class="col-md-12">
<label for="password-input"> <p>You can now reset the password for your account and log in. Please enter in a new password below.</p>
Password </div>
</label>
<input class="form-control" type="password" name="password" id="password-input" />
</div>
{% else %}
<div class="form-group">
<label for="email-input">
Email
</label>
<input class="form-control" type="text" name="email" id="email-input" />
</div>
{% endif %}
<div class="row">
<div class="col-md-6 offset-md-6">
<button type="submit" tabindex="0" class="btn btn-md btn-primary float-right">Submit</button>
</div> </div>
</div> <div class="form-group">
</form> {{ form.password.label }}
{{ form.password(class="form-control") }}
</div>
<div class="row">
<div class="col-md-6 offset-md-6">
{{ form.submit(class="btn btn-md btn-primary float-right") }}
</div>
</div>
{{ form.nonce() }}
</form>
{% endwith %}
{% else %} {% else %}
<h3 class="text-center">Contact a CTF organizer</h3> {% with form = Forms.auth.ResetPasswordRequestForm() %}
<p>This CTF is not configured to send email.</p> <form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
<p>Please contact an organizer to have your password reset</p> <div class="row">
<div class="col-md-12">
<p>Please provide the email address associated with your account below.</p>
</div>
</div>
<div class="form-group">
{{ form.email.label }}
{{ form.email(class="form-control") }}
</div>
<div class="row">
<div class="col-md-6 offset-md-6">
{{ form.submit(class="btn btn-md btn-primary float-right") }}
</div>
</div>
{{ form.nonce() }}
</form>
{% endwith %}
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@@ -22,91 +22,70 @@
<div class="tab-pane fade show active" id="profile" role="tabpanel"> <div class="tab-pane fade show active" id="profile" role="tabpanel">
{% include "components/errors.html" %} {% include "components/errors.html" %}
{% with form = Forms.self.SettingsForm(country=country) %}
<form id="user-profile-form" method="post" accept-charset="utf-8" autocomplete="off" role="form" <form id="user-profile-form" method="post" accept-charset="utf-8" autocomplete="off" role="form"
class="form-horizontal"> class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label for="name-input"> {{ form.name.label }}
User Name {{ form.name(class="form-control", value=name) }}
</label>
<input class="form-control" type="text" name="name" id="name-input" value="{{name}}" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email-input"> {{ form.email.label }}
Email {{ form.email(class="form-control", value=email) }}
</label>
<input class="form-control" type="email" name="email" id="email-input" value="{{email}}" />
</div> </div>
<hr> <hr>
<div class="form-group"> <div class="form-group">
<label for="confirm-input"> {{ form.confirm.label }}
Current Password {{ form.confirm(class="form-control") }}
</label>
<input class="form-control" type="password" name="confirm" id="confirm-input" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password-input"> {{ form.password.label }}
New Password {{ form.password(class="form-control") }}
</label>
<input class="form-control" type="password" name="password" id="password-input" />
</div> </div>
<hr> <hr>
<div class="form-group"> <div class="form-group">
<label for="affiliation-input"> {{ form.affiliation.label }}
Affiliation {{ form.affiliation(class="form-control", value=affiliation or "") }}
</label>
<input class="form-control" type="text" name="affiliation" id="affiliation-input"
value="{% if affiliation %}{{affiliation}}{% endif %}" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="website-input"> {{ form.website.label }}
Website {{ form.website(class="form-control", value=website or "") }}
</label>
<input class="form-control" type="url" name="website" id="website-input"
value="{% if website %}{{website}}{% endif %}" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="country-input"> {{ form.country.label }}
Country {{ form.country(class="form-control custom-select", value=country) }}
</label>
<select class="form-control custom-select" id="country-input" name="country">
<option value=""></option>
{% set countries = get_countries() %}
{% for country_code in countries.keys() %}
<option value="{{ country_code }}" {% if country == country_code %}selected{% endif %}>
{{ countries[country_code] }}</option>
{% endfor %}
</select>
</div> </div>
<div id="results" class="form-group"> <div id="results" class="form-group">
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" tabindex="0" class="btn btn-md btn-primary btn-outlined float-right">Submit</button> {{ form.submit(class="btn btn-md btn-primary btn-outlined float-right") }}
</div> </div>
</form> </form>
{% endwith %}
</div> </div>
<div class="tab-pane fade" id="tokens" role="tabpanel"> <div class="tab-pane fade" id="tokens" role="tabpanel">
<form method="POST"id="user-token-form"> {% with form = Forms.self.TokensForm() %}
<form method="POST" id="user-token-form">
<div class="form-group"> <div class="form-group">
<label for="name-input"> {{ form.expiration.label }}
Expiration {{ form.expiration(class="form-control") }}
</label>
<input class="form-control" type="date" name="expiration" id="expiration-input" />
</div> </div>
<div class="form-group text-right"> <div class="form-group text-right">
<button type="submit" class="btn btn-md btn-primary btn-outlined">Generate</button> {{ form.submit(class="btn btn-md btn-primary btn-outlined") }}
</div> </div>
</form> </form>
{% endwith %}
{% if tokens %} {% if tokens %}
<hr> <hr>
<h4>Active Tokens</h4> <h4 class="text-center">Active Tokens</h4>
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>

View File

@@ -13,6 +13,7 @@
<div class="col-md-8 offset-md-2"> <div class="col-md-8 offset-md-2">
{% include "components/errors.html" %} {% include "components/errors.html" %}
{% with form = Forms.setup.SetupForm() %}
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal" id="setup-form"> <form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal" id="setup-form">
<ul class="nav nav-pills nav-fill mb-4"> <ul class="nav nav-pills nav-fill mb-4">
<li class="nav-item"> <li class="nav-item">
@@ -35,38 +36,32 @@
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane fade show active" id="general" role="tabpanel"> <div class="tab-pane fade show active" id="general" role="tabpanel">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.ctf_name.label }}</b>
CTF Name<br> {{ form.ctf_name(class="form-control") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Name for the CTF {{ form.ctf_name.description }}
</small> </small>
</label>
<input class="form-control" type="text" id="ctf_name" name="ctf_name" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.ctf_description.label }}</b>
CTF Description<br> {{ form.ctf_description(class="form-control", rows="5") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Description for the CTF {{ form.ctf_description.description }}
</small> </small>
</label>
<textarea class="form-control" type="text" id="ctf_description" name="ctf_description" rows="5"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.user_mode.label }}</b>
CTF User Mode {% for radio in form.user_mode %}
<small class="form-text text-muted"> <div class="form-check ml-3">
User Mode for the CTF. <br> {{ radio(class="form-check-input") }}
Dictates whether users join teams to play (Team Mode) or {{ radio.label }}
play as themselves (User Mode) </div>
</small> {% endfor %}
</label> <small class="form-text text-muted">
<select class="form-control custom-select" id="user_mode" name="user_mode"> {{ form.user_mode.description }}
<option value="teams">Team Mode</option> </small>
<option value="users">User Mode</option>
</select>
</div> </div>
<div class="float-right"> <div class="float-right">
@@ -77,31 +72,25 @@
</div> </div>
<div class="tab-pane fade" id="administration" role="tabpanel"> <div class="tab-pane fade" id="administration" role="tabpanel">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.name.label }}</b>
Admin Username<br> {{ form.name(class="form-control") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Your username for the administration account {{ form.name.description }}
</small> </small>
</label>
<input class="form-control" type="text" name="name" required/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.email.label }}</b>
Admin Email<br> {{ form.email(class="form-control") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Your email address for the administration account {{ form.email.description }}
</small> </small>
</label>
<input class="form-control" type="email" name="email" required/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.password.label }}</b>
Admin Password<br> {{ form.password(class="form-control") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
Your password for the administration account {{ form.password.description }}
</small> </small>
</label>
<input class="form-control" type="password" name="password" required/>
</div> </div>
<div class="form-check"> <div class="form-check">
@@ -119,28 +108,17 @@
</div> </div>
<div class="tab-pane fade" id="style" role="tabpanel"> <div class="tab-pane fade" id="style" role="tabpanel">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.ctf_theme.label }}</b>
Theme<br> {{ form.ctf_theme(class="form-control custom-select") }}
<small class="form-text text-muted"> <small class="form-text text-muted">
CTFd Theme to use {{ form.ctf_theme.description }}
</small> </small>
</label>
<select class="form-control custom-select" name="ctf_theme">
{% for theme in themes %}
<option>{{ theme }}</option>
{% endfor %}
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.theme_color.label }}</b>
Theme Color
<small class="form-text text-muted">
Color used by theme to control aesthetics. Requires theme support.
</small>
</label>
<br> <br>
<div class="d-inline-block"> <div class="d-inline-block">
<input type="hidden" name="theme_color" id="config-color-input"> {{ form.theme_color(id="config-color-input") }}
<div class="btn-group"> <div class="btn-group">
<input type="color" id="config-color-picker" class="pr-1" style="width: 100px; height: 30px;" value=""> <input type="color" id="config-color-picker" class="pr-1" style="width: 100px; height: 30px;" value="">
</div> </div>
@@ -148,6 +126,9 @@
<button type="button" id="config-color-reset">Reset</button> <button type="button" id="config-color-reset">Reset</button>
</div> </div>
</div> </div>
<small class="form-text text-muted">
{{ form.theme_color.description }}
</small>
</div> </div>
<div class="float-right"> <div class="float-right">
<button type="button" class="btn btn-primary btn-outlined tab-next" data-href="#datetime"> <button type="button" class="btn btn-primary btn-outlined tab-next" data-href="#datetime">
@@ -157,12 +138,7 @@
</div> </div>
<div class="tab-pane fade" id="datetime" role="tabpanel"> <div class="tab-pane fade" id="datetime" role="tabpanel">
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.start.label }}</b>
Start Time<br>
<small class="form-text text-muted">
Time when your CTF is scheduled to start. Optional.
</small>
</label>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<label>Date</label> <label>Date</label>
@@ -174,18 +150,16 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label>UTC Preview</label> <label>UTC Preview</label>
<input class="form-control" type="text" id="start-preview" name="start" readonly/> {{ form.start(class="form-control", id="start-preview", readonly=True) }}
</div> </div>
</div> </div>
<small class="form-text text-muted">
{{ form.start.description }}
</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <b>{{ form.end.label }}</b>
End Time<br>
<small class="form-text text-muted">
Time when your CTF is scheduled to end. Optional.
</small>
</label>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<label>Date</label> <label>Date</label>
@@ -197,9 +171,12 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label>UTC Preview</label> <label>UTC Preview</label>
<input class="form-control" type="text" id="end-preview" name="end" readonly /> {{ form.start(class="form-control", id="end-preview", readonly=True) }}
</div> </div>
</div> </div>
<small class="form-text text-muted">
{{ form.end.description }}
</small>
</div> </div>
<div class="float-right"> <div class="float-right">
@@ -231,16 +208,14 @@
<br> <br>
<div class="submit-row float-right"> <div class="submit-row float-right">
<button type="submit" tabindex="0" class="btn btn-md btn-primary btn-outlined"> {{ form.submit(class="btn btn-md btn-primary btn-outlined") }}
Finish
</button>
</div> </div>
</div> </div>
</div> </div>
<input type="hidden" name="nonce" value="{{ nonce }}"> {{ form.nonce() }}
{# This nonce is implemented specially in the route itself #}
</form> </form>
{% endwith %}
</div> </div>
</div> </div>

View File

@@ -14,24 +14,24 @@
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% include "components/errors.html" %} {% include "components/errors.html" %}
{% with form = Forms.teams.TeamJoinForm() %}
<form method="POST"> <form method="POST">
<div class="form-group"> <div class="form-group">
<label>Team Name:</label> {{ form.name.label }}
<input class="form-control" type="text" name="name"> {{ form.name(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Team Password:</label> {{ form.password.label }}
<input class="form-control" type="password" name="password"> {{ form.password(class="form-control") }}
</div> </div>
<input type="hidden" name="nonce" value="{{ nonce }}">
<div class="row pt-3"> <div class="row pt-3">
<div class="col-md-12"> <div class="col-md-12">
<button type="submit" class="btn btn-success float-right"> {{ form.submit(class="btn btn-success float-right") }}
Join
</button>
</div> </div>
</div> </div>
{{ form.nonce() }}
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -14,25 +14,25 @@
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% include "components/errors.html" %} {% include "components/errors.html" %}
{% with form = Forms.teams.TeamRegisterForm() %}
<form method="POST"> <form method="POST">
<div class="form-group"> <div class="form-group">
<label>Team Name:</label> {{ form.name.label }}
<input class="form-control" type="text" name="name"> {{ form.name(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Team Password:</label> {{ form.password.label }}
<input class="form-control" type="password" name="password"> {{ form.password(class="form-control") }}
</div> </div>
<input type="hidden" name="nonce" value="{{ nonce }}">
<div class="row pt-3"> <div class="row pt-3">
<div class="col-md-12"> <div class="col-md-12">
<p>After creating your team, share the team name and password with your teammates so they can join your team.</p> <p>After creating your team, share the team name and password with your teammates so they can join your team.</p>
<button type="submit" class="btn btn-success float-right"> {{ form.submit(class="btn btn-success float-right") }}
Create
</button>
</div> </div>
</div> </div>
{{ form.nonce() }}
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -14,50 +14,38 @@
</button> </button>
</div> </div>
<div class="modal-body clearfix"> <div class="modal-body clearfix">
{% with form = Forms.teams.TeamSettingsForm(obj=team) %}
<form id="team-info-form" method="POST"> <form id="team-info-form" method="POST">
<div class="form-group"> <div class="form-group">
<label for="name">Team Name</label> {{ form.name.label }}
<input type="text" class="form-control" name="name" id="name" {{ form.name(class="form-control") }}
{% if team is defined and team.name %}value="{{ team.name }}"{% endif %} required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Current Password</label> {{ form.confirm.label }}
<input type="password" class="form-control" name="confirm" id="confirm"/> {{ form.confirm(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> {{ form.password.label }}
<input type="password" class="form-control" name="password" id="password"/> {{ form.password(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="website">Website</label> {{ form.website.label }}
<input type="url" class="form-control" name="website" id="website" {{ form.website(class="form-control") }}
{% if team is defined and team.website %}value="{{ team.website }}"{% endif %} />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Affiliation</label> {{ form.affiliation.label }}
<input type="text" class="form-control" name="affiliation" id="affiliation" {{ form.affiliation(class="form-control") }}
{% if team is defined and team.affiliation %}value="{{ team.affiliation }}"{% endif %} />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="affiliation">Country</label> {{ form.country.label }}
<select class="form-control custom-select" id="country-input" name="country"> {{ form.country(class="form-control custom-select") }}
<option></option>
{% set countries = get_countries() %}
{% for country_code in countries.keys() %}
<option value="{{ country_code }}"{% if team is defined and team.country == country_code %} selected{% endif %}>
{{ countries[country_code] }}
</option>
{% endfor %}
</select>
</div> </div>
<div id="results"> <div id="results">
</div> </div>
<button id="update-team" type="submit" {{ form.submit(class="btn btn-primary btn-outlined float-right modal-action") }}
class="btn btn-primary btn-outlined float-right modal-action">
Submit
</button>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>
@@ -73,28 +61,21 @@
</button> </button>
</div> </div>
<div class="modal-body clearfix"> <div class="modal-body clearfix">
{% with form = Forms.teams.TeamCaptainForm(captain_id=team.captain_id) %}
<form id="team-captain-form" method="POST"> <form id="team-captain-form" method="POST">
<input type="hidden" name="id">
<div class="form-group"> <div class="form-group">
<label for="captain">Team Captain</label> {{ form.captain_id.label }}
<select class="form-control custom-select" id="captain" name="captain_id"> {% for member in team.members %}
{% if team is defined %} {# Append members to the select choices #}
<option value="{{ team.captain.id }}">{{ team.captain.name }}</option> {% set _ = form.captain_id.choices.append((member.id, member.name)) %}
{% for member in team.members %} {% endfor %}
{% if member.id != team.captain.id %} {{ form.captain_id(class="form-control custom-select") }}
<option value="{{ member.id }}">{{ member.name }}</option>
{% endif %}
{% endfor %}
{% endif %}
</select>
</div> </div>
<div id="results"> <div id="results">
</div> </div>
<button type="submit" class="btn btn-primary btn-outlined float-right modal-action"> {{ form.submit(class="btn btn-primary btn-outlined float-right modal-action") }}
Submit
</button>
</form> </form>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -262,6 +262,9 @@ COUNTRIES_LIST = [
# Nicely titled (and translatable) country names. # Nicely titled (and translatable) country names.
COUNTRIES_DICT = OrderedDict(COUNTRIES_LIST) COUNTRIES_DICT = OrderedDict(COUNTRIES_LIST)
# List of countries suitable for use in forms
SELECT_COUNTRIES_LIST = [("", "")] + COUNTRIES_LIST
def get_countries(): def get_countries():
return COUNTRIES_DICT return COUNTRIES_DICT

View File

@@ -60,6 +60,7 @@ def init_template_globals(app):
from CTFd.constants.config import Configs from CTFd.constants.config import Configs
from CTFd.constants.plugins import Plugins from CTFd.constants.plugins import Plugins
from CTFd.constants.sessions import Session from CTFd.constants.sessions import Session
from CTFd.forms import Forms
app.jinja_env.globals.update(config=config) app.jinja_env.globals.update(config=config)
app.jinja_env.globals.update(get_pages=get_pages) app.jinja_env.globals.update(get_pages=get_pages)
@@ -94,6 +95,7 @@ def init_template_globals(app):
app.jinja_env.globals.update(Configs=Configs) app.jinja_env.globals.update(Configs=Configs)
app.jinja_env.globals.update(Plugins=Plugins) app.jinja_env.globals.update(Plugins=Plugins)
app.jinja_env.globals.update(Session=Session) app.jinja_env.globals.update(Session=Session)
app.jinja_env.globals.update(Forms=Forms)
def init_logs(app): def init_logs(app):

View File

@@ -226,12 +226,7 @@ def setup():
cache.clear() cache.clear()
return redirect(url_for("views.static_html")) return redirect(url_for("views.static_html"))
return render_template( return render_template("setup.html", state=serialize(generate_nonce()))
"setup.html",
nonce=session.get("nonce"),
state=serialize(generate_nonce()),
themes=config.get_themes(),
)
return redirect(url_for("views.static_html")) return redirect(url_for("views.static_html"))

View File

@@ -22,3 +22,4 @@ flask-marshmallow==0.10.1
marshmallow-sqlalchemy==0.17.0 marshmallow-sqlalchemy==0.17.0
boto3==1.13.9 boto3==1.13.9
marshmallow==2.20.2 marshmallow==2.20.2
WTForms==2.3.1

View File

@@ -277,7 +277,7 @@ def test_contact_for_password_reset():
forgot_link = "http://localhost/reset_password" forgot_link = "http://localhost/reset_password"
r = client.get(forgot_link) r = client.get(forgot_link)
assert "Contact a CTF organizer" in r.get_data(as_text=True) assert "contact an organizer" in r.get_data(as_text=True)
destroy_ctfd(app) destroy_ctfd(app)