mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 22:14:25 +01:00
1234 error components (#1465)
Start cleaning up a lot of the core theme. Extract pieces into components that can be included into overarching templates. Work on #1234
This commit is contained in:
16
CTFd/auth.py
16
CTFd/auth.py
@@ -66,7 +66,7 @@ def confirm(data=None):
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
# User is trying to start or restart the confirmation flow
|
||||
if not current_user.authed():
|
||||
if current_user.authed() is False:
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
user = Users.query.filter_by(id=session["id"]).first_or_404()
|
||||
@@ -82,13 +82,11 @@ def confirm(data=None):
|
||||
format="[{date}] {ip} - {name} initiated a confirmation email resend",
|
||||
)
|
||||
return render_template(
|
||||
"confirm.html",
|
||||
user=user,
|
||||
infos=["Your confirmation email has been resent!"],
|
||||
"confirm.html", infos=[f"Confirmation email sent to {user.email}!"]
|
||||
)
|
||||
elif request.method == "GET":
|
||||
# User has been directed to the confirm page
|
||||
return render_template("confirm.html", user=user)
|
||||
return render_template("confirm.html")
|
||||
|
||||
|
||||
@auth.route("/reset_password", methods=["POST", "GET"])
|
||||
@@ -115,7 +113,7 @@ def reset_password(data=None):
|
||||
if user.oauth_id:
|
||||
return render_template(
|
||||
"reset_password.html",
|
||||
errors=[
|
||||
infos=[
|
||||
"Your account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
|
||||
],
|
||||
)
|
||||
@@ -153,7 +151,7 @@ def reset_password(data=None):
|
||||
if not user:
|
||||
return render_template(
|
||||
"reset_password.html",
|
||||
errors=[
|
||||
infos=[
|
||||
"If that account exists you will receive an email, please check your inbox"
|
||||
],
|
||||
)
|
||||
@@ -161,7 +159,7 @@ def reset_password(data=None):
|
||||
if user.oauth_id:
|
||||
return render_template(
|
||||
"reset_password.html",
|
||||
errors=[
|
||||
infos=[
|
||||
"The email address associated with this account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
|
||||
],
|
||||
)
|
||||
@@ -170,7 +168,7 @@ def reset_password(data=None):
|
||||
|
||||
return render_template(
|
||||
"reset_password.html",
|
||||
errors=[
|
||||
infos=[
|
||||
"If that account exists you will receive an email, please check your inbox"
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from flask import Blueprint, render_template
|
||||
|
||||
from CTFd.utils import config, get_config
|
||||
from CTFd.utils.dates import ctf_ended, ctf_paused, view_after_ctf
|
||||
from CTFd.utils import config
|
||||
from CTFd.utils.dates import ctf_ended, ctf_paused, ctf_started
|
||||
from CTFd.utils.decorators import (
|
||||
during_ctf_time_only,
|
||||
require_team,
|
||||
@@ -21,16 +21,14 @@ challenges = Blueprint("challenges", __name__)
|
||||
def listing():
|
||||
infos = get_infos()
|
||||
errors = get_errors()
|
||||
start = get_config("start") or 0
|
||||
end = get_config("end") or 0
|
||||
|
||||
if ctf_paused():
|
||||
infos.append("{} is paused".format(config.ctf_name()))
|
||||
if ctf_started() is False:
|
||||
errors.append(f"{config.ctf_name()} has not started yet")
|
||||
|
||||
# CTF has ended but we want to allow view_after_ctf. Show error but let JS load challenges.
|
||||
if ctf_ended() and view_after_ctf():
|
||||
infos.append("{} has ended".format(config.ctf_name()))
|
||||
if ctf_paused() is True:
|
||||
infos.append(f"{config.ctf_name()} is paused")
|
||||
|
||||
return render_template(
|
||||
"challenges.html", infos=infos, errors=errors, start=int(start), end=int(end)
|
||||
)
|
||||
if ctf_ended() is True:
|
||||
infos.append(f"{config.ctf_name()} has ended")
|
||||
|
||||
return render_template("challenges.html", infos=infos, errors=errors)
|
||||
|
||||
19
CTFd/constants/config.py
Normal file
19
CTFd/constants/config.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from markupsafe import Markup
|
||||
|
||||
from CTFd.utils import get_config
|
||||
|
||||
|
||||
class _ConfigsWrapper:
|
||||
def __getattr__(self, attr):
|
||||
return get_config(attr)
|
||||
|
||||
@property
|
||||
def theme_header(self):
|
||||
return Markup(get_config("theme_header", default=""))
|
||||
|
||||
@property
|
||||
def theme_footer(self):
|
||||
return Markup(get_config("theme_footer", default=""))
|
||||
|
||||
|
||||
Configs = _ConfigsWrapper()
|
||||
54
CTFd/constants/plugins.py
Normal file
54
CTFd/constants/plugins.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from flask import current_app
|
||||
from markupsafe import Markup
|
||||
|
||||
from CTFd.plugins import get_admin_plugin_menu_bar, get_user_page_menu_bar
|
||||
from CTFd.utils.plugins import get_registered_scripts, get_registered_stylesheets
|
||||
|
||||
|
||||
class _PluginWrapper:
|
||||
@property
|
||||
def scripts(self):
|
||||
application_root = current_app.config.get("APPLICATION_ROOT")
|
||||
subdir = application_root != "/"
|
||||
scripts = []
|
||||
for script in get_registered_scripts():
|
||||
if script.startswith("http"):
|
||||
scripts.append(f'<script defer src="{script}"></script>')
|
||||
elif subdir:
|
||||
scripts.append(
|
||||
f'<script defer src="{application_root}/{script}"></script>'
|
||||
)
|
||||
else:
|
||||
scripts.append(f'<script defer src="{script}"></script>')
|
||||
return Markup("\n".join(scripts))
|
||||
|
||||
@property
|
||||
def styles(self):
|
||||
application_root = current_app.config.get("APPLICATION_ROOT")
|
||||
subdir = application_root != "/"
|
||||
_styles = []
|
||||
for stylesheet in get_registered_stylesheets():
|
||||
if stylesheet.startswith("http"):
|
||||
_styles.append(
|
||||
f'<link rel="stylesheet" type="text/css" href="{stylesheet}">'
|
||||
)
|
||||
elif subdir:
|
||||
_styles.append(
|
||||
f'<link rel="stylesheet" type="text/css" href="{application_root}/{stylesheet}">'
|
||||
)
|
||||
else:
|
||||
_styles.append(
|
||||
f'<link rel="stylesheet" type="text/css" href="{stylesheet}">'
|
||||
)
|
||||
return Markup("\n".join(_styles))
|
||||
|
||||
@property
|
||||
def user_menu_pages(self):
|
||||
return get_user_page_menu_bar()
|
||||
|
||||
@property
|
||||
def admin_menu_pages(self):
|
||||
return get_admin_plugin_menu_bar()
|
||||
|
||||
|
||||
Plugins = _PluginWrapper()
|
||||
@@ -266,6 +266,16 @@ class Users(db.Model):
|
||||
elif user_mode == "users":
|
||||
return self.id
|
||||
|
||||
@hybrid_property
|
||||
def account(self):
|
||||
from CTFd.utils import get_config
|
||||
|
||||
user_mode = get_config("user_mode")
|
||||
if user_mode == "teams":
|
||||
return self.team
|
||||
elif user_mode == "users":
|
||||
return self
|
||||
|
||||
@property
|
||||
def solves(self):
|
||||
return self.get_solves(admin=False)
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
from collections import namedtuple
|
||||
|
||||
from flask import current_app as app
|
||||
from flask import send_file, send_from_directory
|
||||
from flask import send_file, send_from_directory, url_for
|
||||
|
||||
from CTFd.utils.config.pages import get_pages
|
||||
from CTFd.utils.decorators import admins_only as admins_only_wrapper
|
||||
@@ -114,6 +114,9 @@ def register_admin_plugin_menu_bar(title, route):
|
||||
:param route: A string that is the href used by the link
|
||||
:return:
|
||||
"""
|
||||
if (route.startswith("http://") or route.startswith("https://")) is False:
|
||||
route = url_for("views.static_html", route=route)
|
||||
|
||||
am = Menu(title=title, route=route)
|
||||
app.admin_plugin_menu_bar.append(am)
|
||||
|
||||
@@ -135,6 +138,9 @@ def register_user_page_menu_bar(title, route):
|
||||
:param route: A string that is the href used by the link
|
||||
:return:
|
||||
"""
|
||||
if (route.startswith("http://") or route.startswith("https://")) is False:
|
||||
route = url_for("views.static_html", route=route)
|
||||
|
||||
p = Menu(title=title, route=route)
|
||||
app.plugin_menu_bar.append(p)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from flask import Blueprint, render_template
|
||||
from CTFd.cache import cache, make_cache_key
|
||||
from CTFd.utils import config
|
||||
from CTFd.utils.decorators.visibility import check_score_visibility
|
||||
from CTFd.utils.helpers import get_infos
|
||||
from CTFd.utils.scores import get_standings
|
||||
|
||||
scoreboard = Blueprint("scoreboard", __name__)
|
||||
@@ -12,9 +13,10 @@ scoreboard = Blueprint("scoreboard", __name__)
|
||||
@check_score_visibility
|
||||
@cache.cached(timeout=60, key_prefix=make_cache_key)
|
||||
def listing():
|
||||
infos = get_infos()
|
||||
|
||||
if config.is_scoreboard_frozen():
|
||||
infos.append("Scoreboard has been frozen")
|
||||
|
||||
standings = get_standings()
|
||||
return render_template(
|
||||
"scoreboard.html",
|
||||
standings=standings,
|
||||
score_frozen=config.is_scoreboard_frozen(),
|
||||
)
|
||||
return render_template("scoreboard.html", standings=standings, infos=infos)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Move down content because we have a fixed navbar that is 3.5rem tall */
|
||||
body {
|
||||
main {
|
||||
padding-top: 3.5rem;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ html {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
main {
|
||||
/* Margin bottom by footer height */
|
||||
margin-bottom: 60px;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -8,16 +8,10 @@
|
||||
<link rel="stylesheet" href="{{ url_for('views.themes', path='css/fonts.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('views.themes', path='css/main.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('views.themes', path='css/core.css') }}">
|
||||
{% block stylesheets %}{% endblock %}
|
||||
{% for stylesheet in get_registered_stylesheets() %}
|
||||
{% if stylesheet.startswith('http') %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
|
||||
{% elif request.script_root %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/{{ stylesheet }}">
|
||||
{% else %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% block stylesheets %}
|
||||
{% endblock %}
|
||||
|
||||
{{ Plugins.styles }}
|
||||
<script type="text/javascript">
|
||||
var init = {
|
||||
'urlRoot': "{{ request.script_root }}",
|
||||
@@ -28,154 +22,10 @@
|
||||
'end': {{ get_config("end") | tojson }},
|
||||
}
|
||||
</script>
|
||||
{{ get_config('theme_header', '') | safe }}
|
||||
{{ Configs.theme_header }}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
||||
<div class="container">
|
||||
<a href="{{ url_for('views.static_html', route='/') }}" class="navbar-brand">
|
||||
{% if get_ctf_logo() %}
|
||||
<img class="img-responsive ctf_logo" src="{{ url_for('views.files', path=get_ctf_logo()) }}" height="25" alt="{{ get_ctf_name() }}">
|
||||
{% else %}
|
||||
{{ get_ctf_name() }}
|
||||
{% endif %}
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#base-navbars"
|
||||
aria-controls="base-navbars" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="base-navbars">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{% for page in get_user_page_menu_bar() %}
|
||||
{% if page.route.startswith('http://') or page.route.startswith('https://') %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ page.route }}">{{ page.title }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('views.static_html', route=page.route) }}">{{ page.title }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if get_config('account_visibility') != 'admins' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('users.listing') }}">Users</a>
|
||||
</li>
|
||||
{% if get_config('user_mode') == 'teams' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('teams.listing') }}">Teams</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if get_config('score_visibility') != 'admins' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('scoreboard.listing') }}">Scoreboard</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('challenges.listing') }}">Challenges</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr class="d-sm-flex d-md-flex d-lg-none">
|
||||
|
||||
<ul class="navbar-nav ml-md-auto d-block d-sm-flex d-md-flex">
|
||||
{% if authed() %}
|
||||
{% if is_admin() %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.view') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Admin Panel">
|
||||
<i class="fas fa-wrench d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-wrench pr-1"></i>Admin Panel
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('views.notifications') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Notifications">
|
||||
<i class="fas fa-bell d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-bell pr-1"></i>
|
||||
<span class="badge badge-pill badge-danger badge-notification"></span>
|
||||
Notifications
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% if config.user_mode() == "teams" %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('teams.private') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Team">
|
||||
<i class="fas fa-users d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-users pr-1"></i>Team
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('users.private') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Profile">
|
||||
<i class="fas fa-user-circle d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-user-circle pr-1"></i>Profile
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('views.settings') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Settings">
|
||||
<i class="fas fa-cogs d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-cogs pr-1"></i>Settings
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.logout') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Logout">
|
||||
<i class="fas fa-sign-out-alt d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-sign-out-alt pr-1"></i><span class="d-lg-none">Logout</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% if registration_visible() %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.register') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Register">
|
||||
<i class="fas fa-user-plus d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-user-plus pr-1"></i>Register
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.login') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Login">
|
||||
<i class="fas fa-sign-in-alt d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-sign-in-alt pr-1"></i>Login
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% include "components/navbar.html" %}
|
||||
|
||||
<main role="main">
|
||||
{% block content %}
|
||||
@@ -201,16 +51,8 @@
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
||||
{% for script in get_registered_scripts() %}
|
||||
{% if script.startswith('http') %}
|
||||
<script defer src="{{ script }}"></script>
|
||||
{% elif request.script_root %}
|
||||
<script defer src="{{ request.script_root }}/{{ script }}"></script>
|
||||
{% else %}
|
||||
<script defer src="{{ script }}"></script>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ Plugins.scripts }}
|
||||
|
||||
{{ get_config('theme_footer', '') | safe }}
|
||||
{{ Configs.theme_footer }}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,58 +5,29 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<h1>Challenges</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if infos %}
|
||||
<div class="container">
|
||||
<div id='errors' class="row">
|
||||
<div class="col-md-12">
|
||||
{% for info in infos %}
|
||||
<h1 class="text-center">{{ info }}</h1>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if errors %}
|
||||
<div class="container">
|
||||
<div id='errors' class="row">
|
||||
<div class="col-md-12">
|
||||
{% for error in errors %}
|
||||
<h1 class="text-center">{{ error }}</h1>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="challenge-window" tabindex="-1" role="dialog">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if admin or not errors %}
|
||||
<div class="container">
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
<div id='challenges-board'>
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
|
||||
|
||||
<div class="modal fade" id="challenge-window" tabindex="-1" role="dialog">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
||||
{% block entrypoint %}
|
||||
{% if admin or not errors %}
|
||||
<script defer src="{{ url_for('views.themes', path='js/pages/challenges.js') }}"></script>
|
||||
{% endif %}
|
||||
<script defer src="{{ url_for('views.themes', path='js/pages/challenges.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
18
CTFd/themes/core/templates/components/errors.html
Normal file
18
CTFd/themes/core/templates/components/errors.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<div>
|
||||
{% for info in infos %}
|
||||
<div class="alert alert-info alert-dismissable text-center" role="alert">
|
||||
<span>{{ info }}</span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for error in errors %}
|
||||
<div class="alert alert-danger alert-dismissable text-center" role="alert">
|
||||
<span>{{ error }}</span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
141
CTFd/themes/core/templates/components/navbar.html
Normal file
141
CTFd/themes/core/templates/components/navbar.html
Normal file
@@ -0,0 +1,141 @@
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
||||
<div class="container">
|
||||
<a href="{{ url_for('views.static_html', route='/') }}" class="navbar-brand">
|
||||
{% if get_ctf_logo() %}
|
||||
<img class="img-responsive ctf_logo" src="{{ url_for('views.files', path=get_ctf_logo()) }}" height="25" alt="{{ get_ctf_name() }}">
|
||||
{% else %}
|
||||
{{ get_ctf_name() }}
|
||||
{% endif %}
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#base-navbars"
|
||||
aria-controls="base-navbars" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="base-navbars">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{% for page in Plugins.user_menu_pages %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ page.route }}">{{ page.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if Configs.account_visibility != 'admins' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('users.listing') }}">Users</a>
|
||||
</li>
|
||||
{% if Configs.user_mode == 'teams' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('teams.listing') }}">Teams</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if Configs.score_visibility != 'admins' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('scoreboard.listing') }}">Scoreboard</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('challenges.listing') }}">Challenges</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr class="d-sm-flex d-md-flex d-lg-none">
|
||||
|
||||
<ul class="navbar-nav ml-md-auto d-block d-sm-flex d-md-flex">
|
||||
{% if authed() %}
|
||||
{% if is_admin() %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.view') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Admin Panel">
|
||||
<i class="fas fa-wrench d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-wrench pr-1"></i>Admin Panel
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('views.notifications') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Notifications">
|
||||
<i class="fas fa-bell d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-bell pr-1"></i>
|
||||
<span class="badge badge-pill badge-danger badge-notification"></span>
|
||||
Notifications
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% if Configs.user_mode == "teams" %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('teams.private') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Team">
|
||||
<i class="fas fa-users d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-users pr-1"></i>Team
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('users.private') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Profile">
|
||||
<i class="fas fa-user-circle d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-user-circle pr-1"></i>Profile
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('views.settings') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Settings">
|
||||
<i class="fas fa-cogs d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-cogs pr-1"></i>Settings
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.logout') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Logout">
|
||||
<i class="fas fa-sign-out-alt d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-sign-out-alt pr-1"></i><span class="d-lg-none">Logout</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% if registration_visible() %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.register') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Register">
|
||||
<i class="fas fa-user-plus d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-user-plus pr-1"></i>Register
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.login') }}">
|
||||
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Login">
|
||||
<i class="fas fa-sign-in-alt d-none d-md-block d-lg-none"></i>
|
||||
</span>
|
||||
<span class="d-sm-block d-md-none d-lg-block">
|
||||
<i class="fas fa-sign-in-alt pr-1"></i>Login
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -12,25 +12,10 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
{% for info in infos %}
|
||||
<div class="alert alert-info alert-dismissable" role="alert">
|
||||
<span class="sr-only"></span>
|
||||
{{ info }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for error in errors %}
|
||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ error }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if user %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
<h3 class="text-center">
|
||||
We've sent a confirmation email to {{ user.email }}
|
||||
We've sent a confirmation email to your email address.
|
||||
</h3>
|
||||
|
||||
<br>
|
||||
@@ -38,11 +23,9 @@
|
||||
<h4 class="text-center">
|
||||
Please click the link in that email to confirm your account.
|
||||
</h4>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
|
||||
{% if name %}
|
||||
<form method="POST" action="{{ url_for('auth.confirm') }}">
|
||||
<h4 class="text-center">
|
||||
Need to resend the confirmation email?
|
||||
@@ -52,7 +35,6 @@
|
||||
</div>
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center">Forbidden</h1>
|
||||
<h2 class="text-center">{{ error }}</h2>
|
||||
<div class="pt-5 mt-5 text-center">
|
||||
<h1>Forbidden</h1>
|
||||
<hr class="w-50">
|
||||
<h2>{{ error }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center">404</h1>
|
||||
<h2 class="text-center">Whoops, looks like we can't find that.</h2>
|
||||
<h2 class="text-center">Sorry about that</h2>
|
||||
<div class="pt-5 mt-5 text-center">
|
||||
<h1>404</h1>
|
||||
<hr class="w-50">
|
||||
<h2>File not found</h2>
|
||||
<h2>Sorry about that</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center">429</h1>
|
||||
<h2 class="text-center">Too many requests</h2>
|
||||
<h2 class="text-center">Please slow down!</h2>
|
||||
<div class="pt-5 mt-5 text-center">
|
||||
<h1>429</h1>
|
||||
<hr class="w-50">
|
||||
<h2>Too many requests</h2>
|
||||
<h2>Please slow down!</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center">500</h1>
|
||||
<h2 class="text-center">An Internal Server Error has occurred</h2>
|
||||
<div class="pt-5 mt-5 text-center">
|
||||
<h1>500</h1>
|
||||
<hr class="w-50">
|
||||
<h2>An Internal Server Error has occurred</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center">502</h1>
|
||||
<h2 class="text-center">Bad Gateway</h2>
|
||||
<div class="pt-5 mt-5 text-center">
|
||||
<h1>502</h1>
|
||||
<hr class="w-50">
|
||||
<h2>Bad Gateway</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,13 +12,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
{% for error in errors %}
|
||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ error }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
{% if integrations.mlc() %}
|
||||
<a class="btn btn-secondary btn-lg btn-block" href="{{ url_for('auth.oauth_login') }}">
|
||||
|
||||
@@ -12,13 +12,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
{% for error in errors %}
|
||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ error }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
{% if integrations.mlc() %}
|
||||
<a class="btn btn-secondary btn-lg btn-block" href="{{ url_for('auth.oauth_login') }}">
|
||||
|
||||
@@ -12,13 +12,8 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
{% for error in errors %}
|
||||
<div class="alert alert-info alert-dismissable" role="alert">
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ error }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
{% if can_send_mail() %}
|
||||
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
|
||||
@@ -7,70 +7,52 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
{% if errors %}
|
||||
<div id='errors' class="row">
|
||||
<div class="col-md-12">
|
||||
{% for error in errors %}
|
||||
<h1 class="text-center">{{ error }}</h1>
|
||||
{% endfor %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
<div id="score-graph" class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if score_frozen %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center">Scoreboard has been frozen.</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="score-graph" class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scoreboard" class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td scope="col" width="10px"><b>Place</b></td>
|
||||
<td scope="col"><b>Team</b></td>
|
||||
<td scope="col"><b>Score</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for standing in standings %}
|
||||
<tr>
|
||||
<th scope="row" class="text-center">{{ loop.index }}</th>
|
||||
<td>
|
||||
<a href="{{ generate_account_url(standing.account_id) }}">
|
||||
{{ standing.name | truncate(50) }}
|
||||
|
||||
{% if standings %}
|
||||
<div id="scoreboard" class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td scope="col" width="10px"><b>Place</b></td>
|
||||
<td scope="col"><b>Team</b></td>
|
||||
<td scope="col"><b>Score</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for standing in standings %}
|
||||
<tr>
|
||||
<th scope="row" class="text-center">{{ loop.index }}</th>
|
||||
<td>
|
||||
<a href="{{ generate_account_url(standing.account_id) }}">
|
||||
{{ standing.name | truncate(50) }}
|
||||
|
||||
{% if standing.oauth_id %}
|
||||
{% if get_config('user_mode') == 'teams' %}
|
||||
<a href="https://majorleaguecyber.org/t/{{ standing.name }}">
|
||||
<span class="badge badge-primary">Official</span>
|
||||
</a>
|
||||
{% elif get_config('user_mode') == 'users' %}
|
||||
<a href="https://majorleaguecyber.org/u/{{ standing.name }}">
|
||||
<span class="badge badge-primary">Official</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if standing.oauth_id %}
|
||||
{% if get_config('user_mode') == 'teams' %}
|
||||
<a href="https://majorleaguecyber.org/t/{{ standing.name }}">
|
||||
<span class="badge badge-primary">Official</span>
|
||||
</a>
|
||||
{% elif get_config('user_mode') == 'users' %}
|
||||
<a href="https://majorleaguecyber.org/u/{{ standing.name }}">
|
||||
<span class="badge badge-primary">Official</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ standing.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ standing.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -20,18 +20,8 @@
|
||||
<div class="col-md-8">
|
||||
<div class="tab-content" id="v-pills-tabContent">
|
||||
<div class="tab-pane fade show active" id="profile" role="tabpanel">
|
||||
{% if confirm_email %}
|
||||
<div class="alert alert-info alert-dismissable submit-row" role="alert">
|
||||
Your email address isn't confirmed!
|
||||
Please check your email to confirm your email address.
|
||||
<br>
|
||||
<br>
|
||||
To have the confirmation email resent please <a href="{{ url_for('auth.confirm') }}">click
|
||||
here.</a>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
<form id="user-profile-form" method="post" accept-charset="utf-8" autocomplete="off" role="form"
|
||||
class="form-horizontal">
|
||||
<div class="form-group">
|
||||
|
||||
@@ -11,16 +11,8 @@
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
{% for error in errors %}
|
||||
<div class="submit-row">
|
||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ error }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
<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">
|
||||
<li class="nav-item">
|
||||
|
||||
@@ -12,22 +12,8 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
{% for info in infos %}
|
||||
<div class="alert alert-info alert-dismissable" role="alert">
|
||||
<span class="sr-only"></span>
|
||||
{{ info }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for error in errors %}
|
||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ error }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Team Name:</label>
|
||||
|
||||
@@ -12,22 +12,8 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
{% for info in infos %}
|
||||
<div class="alert alert-info alert-dismissable" role="alert">
|
||||
<span class="sr-only"></span>
|
||||
{{ info }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for error in errors %}
|
||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ error }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Team Name:</label>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<h1 id="user-id" user-id="{{ user.id }}">{{ user.name }}</h1>
|
||||
<h1>{{ user.name }}</h1>
|
||||
|
||||
{% if user.team_id %}
|
||||
<h2 id="user-team-id" user-team-id="{{ user.team_id }}">
|
||||
<h2>
|
||||
<a href="{{ url_for('teams.private') }}">
|
||||
<span class="badge badge-secondary">
|
||||
{{ user.team.name }}
|
||||
@@ -39,39 +39,21 @@
|
||||
</h3>
|
||||
{% endif %}
|
||||
|
||||
{% if user.team_id %}
|
||||
<div class="user-team-info">
|
||||
<h2 id="user-team-place" class="text-center">
|
||||
{# This intentionally hides the user's place because this can be their internal profile. #}
|
||||
{# Public page hiding is done at the route level #}
|
||||
{% if scores_visible() and user.team.place %}
|
||||
{{ user.team.place }} <small>place</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h2 id="user-team-score" class="text-center">
|
||||
{{ user.team.score }} <small>points</small>
|
||||
</h2>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="user-info">
|
||||
<h2 id="user-place" class="text-center">
|
||||
{# This intentionally hides the user's place because this can be their internal profile. #}
|
||||
{# Public page hiding is done at the route level #}
|
||||
{% if scores_visible() and user.place %}
|
||||
{{ user.place }} <small>place</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h2 id="user-score" class="text-center">
|
||||
{% if score %}
|
||||
{{ user.score }}
|
||||
<small>points</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h2 class="text-center">
|
||||
{% if account.place %}
|
||||
{{ account.place }} <small>place</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h2 class="text-center">
|
||||
{% if account.place %}
|
||||
{{ account.score }} <small>points</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="pt-3">
|
||||
{% if user.website and (user.website.startswith('http://') or user.website.startswith('https://')) %}
|
||||
{% if user.website %}
|
||||
<a href="{{ user.website }}" target="_blank" style="color: inherit;">
|
||||
<i class="fas fa-external-link-alt fa-2x px-2" data-toggle="tooltip" data-placement="top"
|
||||
title="{{ user.website }}"></i>
|
||||
@@ -81,103 +63,83 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
{% if errors %}
|
||||
<div id='errors' class="row">
|
||||
{% for error in errors %}
|
||||
<h1>{{ error }}</h1>
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
{% if user.solves %}
|
||||
<div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="categories-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<br class="clearfix">
|
||||
<div id="score-graph" class="w-100 float-right d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Awards</h3>
|
||||
</div>
|
||||
{% for award in user.awards %}
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<p class="text-center">
|
||||
<i class="award-icon award-{{ award.icon }} fa-2x"></i>
|
||||
<br>
|
||||
<strong>{{ award.name }}</strong>
|
||||
</p>
|
||||
<p class="text-center">{{ award.category or ""}}</p>
|
||||
<p class="text-center">{{ award.description or ""}}</p>
|
||||
<p class="text-center">{{ award.value }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
{% if score_frozen %}
|
||||
<div class="row">
|
||||
<h1 class="text-center">Scoreboard has been frozen.</h1>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<br>
|
||||
|
||||
{% set solves = user.solves %}
|
||||
{% set awards = user.awards %}
|
||||
|
||||
{% if solves %}
|
||||
<div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="categories-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<br class="clearfix">
|
||||
<div id="score-graph" class="w-100 float-right d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
{% if awards %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Awards</h3>
|
||||
</div>
|
||||
{% for award in awards %}
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<p class="text-center">
|
||||
<i class="award-icon award-{{ award.icon }} fa-2x"></i>
|
||||
<br>
|
||||
<strong>{{ award.name }}</strong>
|
||||
</p>
|
||||
{% if award.category %}<p class="text-center">{{ award.category }}</p>{% endif %}
|
||||
{% if award.description %}<p class="text-center">{{ award.description }}</p>{% endif %}
|
||||
<p class="text-center">{{ award.value }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Solves</h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Solves</h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Challenge</b></td>
|
||||
<td class="d-none d-md-block d-lg-block"><b>Category</b></td>
|
||||
<td><b>Value</b></td>
|
||||
<td><b>Time</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for solve in user.solves %}
|
||||
<tr>
|
||||
<td><b>Challenge</b></td>
|
||||
<td class="d-none d-md-block d-lg-block"><b>Category</b></td>
|
||||
<td><b>Value</b></td>
|
||||
<td><b>Time</b></td>
|
||||
<td>
|
||||
<a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a>
|
||||
</td>
|
||||
<td class="d-none d-md-block d-lg-block">{{ solve.challenge.category }}</td>
|
||||
<td>{{ solve.challenge.value }}</td>
|
||||
<td class="solve-time">
|
||||
<span data-time="{{ solve.date | isoformat }}"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for solve in solves %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a>
|
||||
</td>
|
||||
<td class="d-none d-md-block d-lg-block">{{ solve.challenge.category }}</td>
|
||||
<td>{{ solve.challenge.value }}</td>
|
||||
<td class="solve-time">
|
||||
<span data-time="{{ solve.date | isoformat }}"></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row min-vh-25">
|
||||
<h3 class="opacity-50 text-center w-100 justify-content-center align-self-center">
|
||||
No solves yet
|
||||
</h3>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row min-vh-25">
|
||||
<h3 class="opacity-50 text-center w-100 justify-content-center align-self-center">
|
||||
No solves yet
|
||||
</h3>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<h1 id="user-id" user-id="{{ user.id }}">{{ user.name }}</h1>
|
||||
<h1>{{ user.name }}</h1>
|
||||
|
||||
{% if user.team_id %}
|
||||
<h2 id="user-team-id" user-team-id="{{ user.team_id }}">
|
||||
<h2>
|
||||
<a href="{{ url_for('teams.public', team_id=user.team_id) }}">
|
||||
<span class="badge badge-secondary">
|
||||
{{ user.team.name }}
|
||||
@@ -39,42 +39,21 @@
|
||||
</h3>
|
||||
{% endif %}
|
||||
|
||||
{% if user.team_id %}
|
||||
<div class="user-team-info">
|
||||
<h2 id="user-team-place" class="text-center">
|
||||
{# This intentionally hides the user's place because this can be their internal profile. #}
|
||||
{# Public page hiding is done at the route level #}
|
||||
{% if scores_visible() and user.team.place %}
|
||||
{{ user.team.place }}
|
||||
<small>place</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h2 id="user-team-score" class="text-center">
|
||||
{{ user.team.score }}
|
||||
<small>points</small>
|
||||
</h2>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="user-info">
|
||||
<h2 id="user-place" class="text-center">
|
||||
{# This intentionally hides the user's place because this can be their internal profile. #}
|
||||
{# Public page hiding is done at the route level #}
|
||||
{% if scores_visible() and user.place %}
|
||||
{{ user.place }}
|
||||
<small>place</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h2 id="user-score" class="text-center">
|
||||
{% if user.score %}
|
||||
{{ user.score }}
|
||||
<small>points</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h2 class="text-center">
|
||||
{% if account.place %}
|
||||
{{ account.place }} <small>place</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h2 class="text-center">
|
||||
{% if account.place %}
|
||||
{{ account.score }} <small>points</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="pt-3">
|
||||
{% if user.website and (user.website.startswith('http://') or user.website.startswith('https://')) %}
|
||||
{% if user.website %}
|
||||
<a href="{{ user.website }}" target="_blank" style="color: inherit;">
|
||||
<i class="fas fa-external-link-alt fa-2x px-2" data-toggle="tooltip" data-placement="top"
|
||||
title="{{ user.website }}"></i>
|
||||
@@ -84,103 +63,83 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
{% if errors %}
|
||||
<div id='errors' class="row">
|
||||
{% for error in errors %}
|
||||
<h1>{{ error }}</h1>
|
||||
{% include "components/errors.html" %}
|
||||
|
||||
{% if user.solves %}
|
||||
<div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="categories-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<br class="clearfix">
|
||||
<div id="score-graph" class="w-100 float-right d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Awards</h3>
|
||||
</div>
|
||||
{% for award in user.awards %}
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<p class="text-center">
|
||||
<i class="award-icon award-{{ award.icon }} fa-2x"></i>
|
||||
<br>
|
||||
<strong>{{ award.name }}</strong>
|
||||
</p>
|
||||
<p class="text-center">{{ award.category or "" }}</p>
|
||||
<p class="text-center">{{ award.description or "" }}</p>
|
||||
<p class="text-center">{{ award.value }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
{% if score_frozen %}
|
||||
<div class="row">
|
||||
<h1 class="text-center">Scoreboard has been frozen.</h1>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<br>
|
||||
|
||||
{% set solves = user.solves %}
|
||||
{% set awards = user.awards %}
|
||||
|
||||
{% if solves %}
|
||||
<div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="categories-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
<br class="clearfix">
|
||||
<div id="score-graph" class="w-100 float-right d-none d-md-block d-lg-block">
|
||||
<div class="text-center">
|
||||
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
{% if awards %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Awards</h3>
|
||||
</div>
|
||||
{% for award in awards %}
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<p class="text-center">
|
||||
<i class="award-icon award-{{ award.icon }} fa-2x"></i>
|
||||
<br>
|
||||
<strong>{{ award.name }}</strong>
|
||||
</p>
|
||||
{% if award.category %}<p class="text-center">{{ award.category }}</p>{% endif %}
|
||||
{% if award.description %}<p class="text-center">{{ award.description }}</p>{% endif %}
|
||||
<p class="text-center">{{ award.value }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Solves</h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Solves</h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>Challenge</b></td>
|
||||
<td class="d-none d-md-block d-lg-block"><b>Category</b></td>
|
||||
<td><b>Value</b></td>
|
||||
<td><b>Time</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for solve in user.solves %}
|
||||
<tr>
|
||||
<td><b>Challenge</b></td>
|
||||
<td class="d-none d-md-block d-lg-block"><b>Category</b></td>
|
||||
<td><b>Value</b></td>
|
||||
<td><b>Time</b></td>
|
||||
<td>
|
||||
<a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a>
|
||||
</td>
|
||||
<td class="d-none d-md-block d-lg-block">{{ solve.challenge.category }}</td>
|
||||
<td>{{ solve.challenge.value }}</td>
|
||||
<td class="solve-time">
|
||||
<span data-time="{{ solve.date | isoformat }}"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for solve in solves %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a>
|
||||
</td>
|
||||
<td class="d-none d-md-block d-lg-block">{{ solve.challenge.category }}</td>
|
||||
<td>{{ solve.challenge.value }}</td>
|
||||
<td class="solve-time">
|
||||
<span data-time="{{ solve.date | isoformat }}"></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row min-vh-25">
|
||||
<h3 class="opacity-50 text-center w-100 justify-content-center align-self-center">
|
||||
No solves yet
|
||||
</h3>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row min-vh-25">
|
||||
<h3 class="opacity-50 text-center w-100 justify-content-center align-self-center">
|
||||
No solves yet
|
||||
</h3>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,6 +7,7 @@ from CTFd.utils.decorators.visibility import (
|
||||
check_account_visibility,
|
||||
check_score_visibility,
|
||||
)
|
||||
from CTFd.utils.helpers import get_errors, get_infos
|
||||
from CTFd.utils.user import get_current_user
|
||||
|
||||
users = Blueprint("users", __name__)
|
||||
@@ -20,7 +21,7 @@ def listing():
|
||||
users = (
|
||||
Users.query.filter_by(banned=False, hidden=False)
|
||||
.order_by(Users.id.asc())
|
||||
.paginate(page=page, per_page=10)
|
||||
.paginate(page=page, per_page=50)
|
||||
)
|
||||
|
||||
return render_template("users/users.html", users=users)
|
||||
@@ -30,22 +31,20 @@ def listing():
|
||||
@users.route("/user")
|
||||
@authed_only
|
||||
def private():
|
||||
infos = get_infos()
|
||||
errors = get_errors()
|
||||
|
||||
user = get_current_user()
|
||||
|
||||
solves = user.get_solves()
|
||||
awards = user.get_awards()
|
||||
|
||||
place = user.place
|
||||
score = user.score
|
||||
if config.is_scoreboard_frozen():
|
||||
infos.append("Scoreboard has been frozen")
|
||||
|
||||
return render_template(
|
||||
"users/private.html",
|
||||
solves=solves,
|
||||
awards=awards,
|
||||
user=user,
|
||||
score=score,
|
||||
place=place,
|
||||
score_frozen=config.is_scoreboard_frozen(),
|
||||
account=user.account,
|
||||
infos=infos,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
@@ -53,5 +52,13 @@ def private():
|
||||
@check_account_visibility
|
||||
@check_score_visibility
|
||||
def public(user_id):
|
||||
infos = get_infos()
|
||||
errors = get_errors()
|
||||
user = Users.query.filter_by(id=user_id, banned=False, hidden=False).first_or_404()
|
||||
return render_template("users/public.html", user=user)
|
||||
|
||||
if config.is_scoreboard_frozen():
|
||||
infos.append("Scoreboard has been frozen")
|
||||
|
||||
return render_template(
|
||||
"users/public.html", user=user, account=user.account, infos=infos, errors=errors
|
||||
)
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import os
|
||||
|
||||
from flask import current_app, flash, get_flashed_messages, request
|
||||
from markupsafe import Markup
|
||||
|
||||
|
||||
def markup(text):
|
||||
"""
|
||||
Mark text as safe to inject as HTML into templates
|
||||
"""
|
||||
return Markup(text)
|
||||
|
||||
|
||||
def info_for(endpoint, message):
|
||||
|
||||
@@ -8,6 +8,8 @@ from sqlalchemy.exc import IntegrityError, InvalidRequestError
|
||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||
|
||||
from CTFd.cache import clear_user_recent_ips
|
||||
from CTFd.constants.config import Configs
|
||||
from CTFd.constants.plugins import Plugins
|
||||
from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException
|
||||
from CTFd.models import Tracking, db
|
||||
from CTFd.utils import config, get_config, markdown
|
||||
@@ -87,6 +89,8 @@ def init_template_globals(app):
|
||||
app.jinja_env.globals.update(get_current_user_attrs=get_current_user_attrs)
|
||||
app.jinja_env.globals.update(get_current_team_attrs=get_current_team_attrs)
|
||||
app.jinja_env.globals.update(get_ip=get_ip)
|
||||
app.jinja_env.globals.update(Configs=Configs)
|
||||
app.jinja_env.globals.update(Plugins=Plugins)
|
||||
|
||||
|
||||
def init_logs(app):
|
||||
|
||||
@@ -35,7 +35,7 @@ from CTFd.utils.email import (
|
||||
DEFAULT_VERIFICATION_EMAIL_BODY,
|
||||
DEFAULT_VERIFICATION_EMAIL_SUBJECT,
|
||||
)
|
||||
from CTFd.utils.helpers import get_errors
|
||||
from CTFd.utils.helpers import get_errors, get_infos, markup
|
||||
from CTFd.utils.modes import USERS_MODE
|
||||
from CTFd.utils.security.auth import login_user
|
||||
from CTFd.utils.security.csrf import generate_nonce
|
||||
@@ -272,6 +272,8 @@ def notifications():
|
||||
@views.route("/settings", methods=["GET"])
|
||||
@authed_only
|
||||
def settings():
|
||||
infos = get_infos()
|
||||
|
||||
user = get_current_user()
|
||||
name = user.name
|
||||
email = user.email
|
||||
@@ -282,7 +284,17 @@ def settings():
|
||||
tokens = UserTokens.query.filter_by(user_id=user.id).all()
|
||||
|
||||
prevent_name_change = get_config("prevent_name_change")
|
||||
confirm_email = get_config("verify_emails") and not user.verified
|
||||
|
||||
if get_config("verify_emails") and not user.verified:
|
||||
confirm_url = markup(url_for("auth.confirm"))
|
||||
infos.append(
|
||||
markup(
|
||||
"Your email address isn't confirmed!<br>"
|
||||
"Please check your email to confirm your email address.<br><br>"
|
||||
f'To have the confirmation email resent please <a href="{confirm_url}">click here</a>.'
|
||||
)
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"settings.html",
|
||||
name=name,
|
||||
@@ -292,7 +304,7 @@ def settings():
|
||||
country=country,
|
||||
tokens=tokens,
|
||||
prevent_name_change=prevent_name_change,
|
||||
confirm_email=confirm_email,
|
||||
infos=infos,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user