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 #423
selenium-screenshot-testing
Kevin Chung 2017-10-25 00:05:27 -04:00 committed by GitHub
parent 710ce6d500
commit b5a383a2e1
6 changed files with 162 additions and 13 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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