mirror of https://github.com/JohnHammond/CTFd.git
Navbar links (#427)
* Adding config.json concept in lieu of config.html * Add links to the admin menubar from a plugin * Add links to the user navigation menubar from plugin * Add tests for navbar links * Closes #423selenium-screenshot-testing
parent
710ce6d500
commit
b5a383a2e1
|
@ -38,7 +38,12 @@ def admin_view():
|
|||
@admins_only
|
||||
def admin_plugin_config(plugin):
|
||||
if request.method == 'GET':
|
||||
if plugin in utils.get_configurable_plugins():
|
||||
plugins_path = os.path.join(app.root_path, 'plugins')
|
||||
|
||||
config_html_plugins = [name for name in os.listdir(plugins_path)
|
||||
if os.path.isfile(os.path.join(plugins_path, name, 'config.html'))]
|
||||
|
||||
if plugin in config_html_plugins:
|
||||
config = open(os.path.join(app.root_path, 'plugins', plugin, 'config.html')).read()
|
||||
return render_template_string(config)
|
||||
abort(404)
|
||||
|
|
|
@ -2,6 +2,7 @@ import glob
|
|||
import importlib
|
||||
import os
|
||||
|
||||
from collections import namedtuple
|
||||
from flask.helpers import safe_join
|
||||
from flask import current_app as app, send_file, send_from_directory, abort
|
||||
from CTFd.utils import (
|
||||
|
@ -12,6 +13,11 @@ from CTFd.utils import (
|
|||
)
|
||||
|
||||
|
||||
Menu = namedtuple('Menu', ['name', 'route'])
|
||||
ADMIN_PLUGIN_MENU_BAR = []
|
||||
USER_PAGE_MENU_BAR = []
|
||||
|
||||
|
||||
def register_plugin_assets_directory(app, base_path, admins_only=False):
|
||||
"""
|
||||
Registers a directory to serve assets
|
||||
|
@ -76,6 +82,48 @@ def register_plugin_stylesheet(*args, **kwargs):
|
|||
utils_register_plugin_stylesheet(*args, **kwargs)
|
||||
|
||||
|
||||
def register_admin_plugin_menu_bar(name, route):
|
||||
"""
|
||||
Registers links on the Admin Panel menubar/navbar
|
||||
|
||||
:param name: A string that is shown on the navbar HTML
|
||||
:param route: A string that is the href used by the link
|
||||
:return:
|
||||
"""
|
||||
am = Menu(name=name, route=route)
|
||||
ADMIN_PLUGIN_MENU_BAR.append(am)
|
||||
|
||||
|
||||
def get_admin_plugin_menu_bar():
|
||||
"""
|
||||
Access the list used to store the plugin menu bar
|
||||
|
||||
:return: Returns a list of Menu namedtuples. They have name, and route attributes.
|
||||
"""
|
||||
return ADMIN_PLUGIN_MENU_BAR
|
||||
|
||||
|
||||
def register_user_page_menu_bar(name, route):
|
||||
"""
|
||||
Registers links on the User side menubar/navbar
|
||||
|
||||
:param name: A string that is shown on the navbar HTML
|
||||
:param route: A string that is the href used by the link
|
||||
:return:
|
||||
"""
|
||||
p = Menu(name=name, route=route)
|
||||
USER_PAGE_MENU_BAR.append(p)
|
||||
|
||||
|
||||
def get_user_page_menu_bar():
|
||||
"""
|
||||
Access the list used to store the user page menu bar
|
||||
|
||||
:return: Returns a list of Menu namedtuples. They have name, and route attributes.
|
||||
"""
|
||||
return USER_PAGE_MENU_BAR
|
||||
|
||||
|
||||
def init_plugins(app):
|
||||
"""
|
||||
Searches for the load function in modules in the CTFd/plugins folder. This function is called with the current CTFd
|
||||
|
@ -93,3 +141,6 @@ def init_plugins(app):
|
|||
module = importlib.import_module(module, package='CTFd.plugins')
|
||||
module.load(app)
|
||||
print(" * Loaded module, %s" % module)
|
||||
|
||||
app.jinja_env.globals.update(get_admin_plugin_menu_bar=get_admin_plugin_menu_bar)
|
||||
app.jinja_env.globals.update(get_user_page_menu_bar=get_user_page_menu_bar)
|
||||
|
|
|
@ -50,14 +50,27 @@
|
|||
<li><a href="{{ request.script_root }}/admin/chals">Challenges</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
|
||||
|
||||
{% set plugin_menu = get_admin_plugin_menu_bar() %}
|
||||
{% set plugins = get_configurable_plugins() %}
|
||||
{% if plugin_menu or plugins %}
|
||||
<li><a style="padding-left:0px;padding-right:0px;">|</a></li>
|
||||
|
||||
{% for menu in plugin_menu %}
|
||||
<li><a href="{{ request.script_root }}{{ menu.route }}">{{ menu.name }}</a></li>
|
||||
{% endfor %}
|
||||
|
||||
{% if plugins %}
|
||||
<li>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Plugins <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{% for plugin in get_configurable_plugins() %}
|
||||
<li><a href="{{ request.script_root }}/admin/plugins/{{ plugin }}">{{ plugin }}</a></li>
|
||||
{% for plugin in plugins %}
|
||||
<li><a href="{{ request.script_root }}{{ plugin.route }}">{{ plugin.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -45,6 +45,16 @@
|
|||
{% for page in pages() %}
|
||||
<li><a href="{{ request.script_root }}/{{ page.route }}">{{ page.route|title }}</a></li>
|
||||
{% endfor %}
|
||||
|
||||
{% set page_menu = get_user_page_menu_bar() %}
|
||||
{% for menu in page_menu %}
|
||||
{% if menu.route.startswith('http://') or menu.route.startswith('https://') %}
|
||||
<li><a href="{{ menu.route }}">{{ menu.name }}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ request.script_root }}{{ menu.route }}">{{ menu.name }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<li><a href="{{ request.script_root }}/teams">Teams</a></li>
|
||||
{% if not hide_scores() %}
|
||||
<li><a href="{{ request.script_root }}/scoreboard">Scoreboard</a></li>
|
||||
|
|
|
@ -21,6 +21,7 @@ import datafreeze
|
|||
import zipfile
|
||||
import io
|
||||
|
||||
from collections import namedtuple
|
||||
from email.mime.text import MIMEText
|
||||
from flask import current_app as app, request, redirect, url_for, session, render_template, abort
|
||||
from flask_caching import Cache
|
||||
|
@ -407,9 +408,31 @@ def get_themes():
|
|||
|
||||
|
||||
def get_configurable_plugins():
|
||||
dir = os.path.join(app.root_path, 'plugins')
|
||||
return [name for name in os.listdir(dir)
|
||||
if os.path.isfile(os.path.join(dir, name, 'config.html'))]
|
||||
Plugin = namedtuple('Plugin', ['name', 'route'])
|
||||
|
||||
plugins_path = os.path.join(app.root_path, 'plugins')
|
||||
plugin_directories = os.listdir(plugins_path)
|
||||
|
||||
plugins = []
|
||||
|
||||
for dir in plugin_directories:
|
||||
if os.path.isfile(os.path.join(plugins_path, dir, 'config.json')):
|
||||
path = os.path.join(plugins_path, dir, 'config.json')
|
||||
with open(path) as f:
|
||||
plugin_json_data = json.loads(f.read())
|
||||
p = Plugin(
|
||||
name=plugin_json_data.get('name'),
|
||||
route=plugin_json_data.get('route')
|
||||
)
|
||||
plugins.append(p)
|
||||
elif os.path.isfile(os.path.join(plugins_path, dir, 'config.html')):
|
||||
p = Plugin(
|
||||
name=dir,
|
||||
route='/admin/plugins/{}'.format(dir)
|
||||
)
|
||||
plugins.append(p)
|
||||
|
||||
return plugins
|
||||
|
||||
|
||||
def get_registered_scripts():
|
||||
|
|
|
@ -8,7 +8,11 @@ from CTFd.plugins import (
|
|||
register_plugin_asset,
|
||||
register_plugin_script,
|
||||
register_plugin_stylesheet,
|
||||
override_template
|
||||
override_template,
|
||||
register_admin_plugin_menu_bar,
|
||||
get_admin_plugin_menu_bar,
|
||||
register_user_page_menu_bar,
|
||||
get_user_page_menu_bar
|
||||
)
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
|
@ -98,3 +102,46 @@ def test_register_plugin_stylesheet():
|
|||
assert '/fake/stylesheet/path.css' in output
|
||||
assert 'http://ctfd.io/fake/stylesheet/path.css' in output
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
def test_register_admin_plugin_menu_bar():
|
||||
"""
|
||||
Test that register_admin_plugin_menu_bar() properly inserts into HTML and get_admin_plugin_menu_bar()
|
||||
returns the proper list.
|
||||
"""
|
||||
app = create_ctfd()
|
||||
with app.app_context():
|
||||
register_admin_plugin_menu_bar(name='test_admin_plugin_name', route='/test_plugin')
|
||||
|
||||
client = login_as_user(app, name="admin", password="password")
|
||||
r = client.get('/admin/graphs')
|
||||
output = r.get_data(as_text=True)
|
||||
assert '/test_plugin' in output
|
||||
assert 'test_admin_plugin_name' in output
|
||||
|
||||
menu_item = get_admin_plugin_menu_bar()[0]
|
||||
assert menu_item.name == 'test_admin_plugin_name'
|
||||
assert menu_item.route == '/test_plugin'
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
def test_register_user_page_menu_bar():
|
||||
"""
|
||||
Test that the register_user_page_menu_bar() properly inserts into HTML and get_user_page_menu_bar() returns the
|
||||
proper list.
|
||||
"""
|
||||
app = create_ctfd()
|
||||
with app.app_context():
|
||||
register_user_page_menu_bar(name='test_user_menu_link', route='/test_user_href')
|
||||
|
||||
client = login_as_user(app)
|
||||
r = client.get('/')
|
||||
|
||||
output = r.get_data(as_text=True)
|
||||
assert '/test_user_href' in output
|
||||
assert 'test_user_menu_link' in output
|
||||
|
||||
menu_item = get_user_page_menu_bar()[0]
|
||||
assert menu_item.name == 'test_user_menu_link'
|
||||
assert menu_item.route == '/test_user_href'
|
||||
destroy_ctfd(app)
|
||||
|
|
Loading…
Reference in New Issue