add plugin system

This commit is contained in:
jr-k 2024-03-01 04:01:15 +01:00
parent f186f19d0a
commit 1d8238ee52
20 changed files with 390 additions and 89 deletions

2
.gitignore vendored
View File

@ -8,5 +8,7 @@ data/uploads/*
data/db/*
!data/db/slideshow.json.dist
config.json
/plugins/user/*
!/plugins/user/.gitkeep
*.lock
__pycache__/

View File

@ -4,5 +4,5 @@ import os
from src.Application import Application
if __name__ == '__main__':
app = Application(project_dir=os.path.dirname(__file__))
app = Application(project_dir=os.path.dirname(__file__)[:-2])
app.start()

View File

@ -0,0 +1,24 @@
from plugins.system.ObPlugin import ObPlugin
from typing import List, Dict
from src.model.Variable import Variable
from src.model.VariableType import VariableType
from src.model.HookType import HookType
from src.model.HookRegistration import HookRegistration
class FleetScreenRestart(ObPlugin):
def use_id(self):
return self.__class__.__name__.lower()
def use_title(self):
return 'Fleet Screen Restart'
def use_variables(self) -> List[Variable]:
return []
def use_hooks_registrations(self) -> List[HookRegistration]:
return [
super().set_hook_registration(hook=HookType.H_FLEET_SLIDESHOW_TOOLBAR_ACTIONS, priority=10)
]

View File

@ -0,0 +1 @@
<button class="normal sysinfo-restart"><i class="fa fa-refresh icon-left"></i>{{ l.sysinfo_panel_button_restart }}</button>

View File

@ -0,0 +1,50 @@
import abc
from typing import Optional, List, Dict, Union
from src.model.Variable import Variable
from src.model.VariableType import VariableType
from src.model.HookType import HookType
from src.model.HookRegistration import HookRegistration
from src.service.ModelStore import ModelStore
class ObPlugin(abc.ABC):
PLUGIN_PREFIX = "plugin_"
def __init__(self, directory: str, model_store: ModelStore):
self._directory = directory
self._model_store = model_store
@abc.abstractmethod
def use_id(self) -> str:
pass
@abc.abstractmethod
def use_title(self) -> str:
pass
@abc.abstractmethod
def use_variables(self) -> List[Variable]:
pass
@abc.abstractmethod
def use_hooks_registrations(self) -> List[HookRegistration]:
pass
def get_directory(self) -> Optional[str]:
return self._directory
def get_plugin_variable_prefix(self) -> str:
return "{}{}".format(self.PLUGIN_PREFIX, self.use_id())
def get_plugin_variable_name(self, name: str) -> str:
return "{}_{}".format(self.get_plugin_variable_prefix(), name)
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str) -> Variable:
return self._model_store.variable().set_variable(
name=self.get_plugin_variable_name(name), value=value, type=type, editable=editable, description=description
)
def set_hook_registration(self, hook: HookType, priority: int = 0) -> HookRegistration:
return HookRegistration(plugin=self, hook=hook, priority=priority)

0
plugins/user/.gitkeep Normal file
View File

View File

@ -3,7 +3,8 @@ import logging
import signal
import threading
from src.service.ModelManager import ModelManager
from src.service.ModelStore import ModelStore
from src.service.PluginStore import PluginStore
from src.service.WebServer import WebServer
@ -12,8 +13,9 @@ class Application:
def __init__(self, project_dir: str):
self._project_dir = project_dir
self._stop_event = threading.Event()
self._model_manager = ModelManager()
self._web_server = WebServer(project_dir=project_dir, model_manager=self._model_manager)
self._model_store = ModelStore()
self._plugin_store = PluginStore(project_dir=project_dir, model_store=self._model_store)
self._web_server = WebServer(project_dir=project_dir, model_store=self._model_store, plugin_store=self._plugin_store)
signal.signal(signal.SIGINT, self.signal_handler)

View File

@ -1,15 +1,15 @@
import json
from flask import Flask, render_template, redirect, request, url_for, jsonify
from src.service.ModelManager import ModelManager
from src.service.ModelStore import ModelStore
from src.model.Screen import Screen
class FleetController:
def __init__(self, app, model_manager: ModelManager):
def __init__(self, app, model_store: ModelStore):
self._app = app
self._model_manager = model_manager
self._model_store = model_store
self.register()
def register(self):
@ -24,19 +24,19 @@ class FleetController:
def fleet(self):
return render_template(
'fleet/fleet.jinja.html',
screens=self._model_manager.screen().get_enabled_screens(),
screens=self._model_store.screen().get_enabled_screens(),
)
def fleet_screen_list(self):
return render_template(
'fleet/list.jinja.html',
l=self._model_manager.lang().map(),
enabled_screens=self._model_manager.screen().get_enabled_screens(),
disabled_screens=self._model_manager.screen().get_disabled_screens(),
l=self._model_store.lang().map(),
enabled_screens=self._model_store.screen().get_enabled_screens(),
disabled_screens=self._model_store.screen().get_disabled_screens(),
)
def fleet_screen_add(self):
self._model_manager.screen().add_form(Screen(
self._model_store.screen().add_form(Screen(
name=request.form['name'],
host=request.form['host'],
port=request.form['port'],
@ -44,20 +44,20 @@ class FleetController:
return redirect(url_for('fleet_screen_list'))
def fleet_screen_edit(self):
self._model_manager.screen().update_form(request.form['id'], request.form['name'], request.form['host'], request.form['port'])
self._model_store.screen().update_form(request.form['id'], request.form['name'], request.form['host'], request.form['port'])
return redirect(url_for('fleet_screen_list'))
def fleet_screen_toggle(self):
data = request.get_json()
self._model_manager.screen().update_enabled(data.get('id'), data.get('enabled'))
self._model_store.screen().update_enabled(data.get('id'), data.get('enabled'))
return jsonify({'status': 'ok'})
def fleet_screen_delete(self):
data = request.get_json()
self._model_manager.screen().delete(data.get('id'))
self._model_store.screen().delete(data.get('id'))
return jsonify({'status': 'ok'})
def fleet_screen_position(self):
data = request.get_json()
self._model_manager.screen().update_positions(data)
self._model_store.screen().update_positions(data)
return jsonify({'status': 'ok'})

View File

@ -1,19 +1,19 @@
import json
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
from src.service.ModelManager import ModelManager
from src.service.ModelStore import ModelStore
from src.utils import get_ip_address
class PlayerController:
def __init__(self, app, model_manager: ModelManager):
def __init__(self, app, model_store: ModelStore):
self._app = app
self._model_manager = model_manager
self._model_store = model_store
self.register()
def _get_playlist(self) -> dict:
slides = self._model_manager.slide().to_dict(self._model_manager.slide().get_enabled_slides())
slides = self._model_store.slide().to_dict(self._model_store.slide().get_enabled_slides())
if len(slides) == 1:
return [slides[0], slides[0]]
@ -35,8 +35,8 @@ class PlayerController:
ipaddr = get_ip_address()
return render_template(
'player/default.jinja.html',
ipaddr=ipaddr if ipaddr else self._model_manager.lang().map().get('common_unknown_ipaddr'),
l=self._model_manager.lang().map()
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr'),
l=self._model_store.lang().map()
)
def player_playlist(self):

View File

@ -1,14 +1,14 @@
import json
from flask import Flask, render_template, redirect, request, url_for
from src.service.ModelManager import ModelManager
from src.service.ModelStore import ModelStore
class SettingsController:
def __init__(self, app, model_manager: ModelManager):
def __init__(self, app, model_store: ModelStore):
self._app = app
self._model_manager = model_manager
self._model_store = model_store
self.register()
def register(self):
@ -18,10 +18,10 @@ class SettingsController:
def settings_variable_list(self):
return render_template(
'settings/list.jinja.html',
l=self._model_manager.lang().map(),
variables=self._model_manager.variable().get_editable_variables(),
l=self._model_store.lang().map(),
variables=self._model_store.variable().get_editable_variables(),
)
def settings_variable_edit(self):
self._model_manager.variable().update_form(request.form['id'], request.form['value'])
self._model_store.variable().update_form(request.form['id'], request.form['value'])
return redirect(url_for('settings_variable_list'))

View File

@ -4,7 +4,7 @@ import time
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
from werkzeug.utils import secure_filename
from src.service.ModelManager import ModelManager
from src.service.ModelStore import ModelStore
from src.model.Slide import Slide
from src.model.SlideType import SlideType
from src.utils import str_to_enum
@ -12,9 +12,9 @@ from src.utils import str_to_enum
class SlideshowController:
def __init__(self, app, model_manager: ModelManager):
def __init__(self, app, model_store: ModelStore):
self._app = app
self._model_manager = model_manager
self._model_store = model_store
self.register()
def register(self):
@ -32,11 +32,11 @@ class SlideshowController:
def slideshow(self):
return render_template(
'slideshow/list.jinja.html',
l=self._model_manager.lang().map(),
enabled_slides=self._model_manager.slide().get_enabled_slides(),
disabled_slides=self._model_manager.slide().get_disabled_slides(),
var_last_restart=self._model_manager.variable().get_one_by_name('last_restart'),
var_external_url=self._model_manager.variable().get_one_by_name('external_url')
l=self._model_store.lang().map(),
enabled_slides=self._model_store.slide().get_enabled_slides(),
disabled_slides=self._model_store.slide().get_disabled_slides(),
var_last_restart=self._model_store.variable().get_one_by_name('last_restart'),
var_external_url=self._model_store.variable().get_one_by_name('external_url')
)
def slideshow_slide_add(self):
@ -63,34 +63,34 @@ class SlideshowController:
else:
slide.location = request.form['object']
self._model_manager.slide().add_form(slide)
self._model_store.slide().add_form(slide)
self._post_update()
return redirect(url_for('slideshow_slide_list'))
def slideshow_slide_edit(self):
self._model_manager.slide().update_form(request.form['id'], request.form['name'], request.form['duration'])
self._model_store.slide().update_form(request.form['id'], request.form['name'], request.form['duration'])
self._post_update()
return redirect(url_for('slideshow_slide_list'))
def slideshow_slide_toggle(self):
data = request.get_json()
self._model_manager.slide().update_enabled(data.get('id'), data.get('enabled'))
self._model_store.slide().update_enabled(data.get('id'), data.get('enabled'))
self._post_update()
return jsonify({'status': 'ok'})
def slideshow_slide_delete(self):
data = request.get_json()
self._model_manager.slide().delete(data.get('id'))
self._model_store.slide().delete(data.get('id'))
self._post_update()
return jsonify({'status': 'ok'})
def slideshow_slide_position(self):
data = request.get_json()
self._model_manager.slide().update_positions(data)
self._model_store.slide().update_positions(data)
self._post_update()
return jsonify({'status': 'ok'})
def _post_update(self):
self._model_manager.variable().update_by_name("last_slide_update", time.time())
self._model_store.variable().update_by_name("last_slide_update", time.time())

View File

@ -6,16 +6,16 @@ import subprocess
from flask import Flask, render_template, jsonify
from src.manager.VariableManager import VariableManager
from src.manager.ConfigManager import ConfigManager
from src.service.ModelManager import ModelManager
from src.service.ModelStore import ModelStore
from src.utils import get_ip_address
class SysinfoController:
def __init__(self, app, model_manager: ModelManager):
def __init__(self, app, model_store: ModelStore):
self._app = app
self._model_manager = model_manager
self._model_store = model_store
self.register()
def register(self):
@ -27,14 +27,14 @@ class SysinfoController:
ipaddr = get_ip_address()
return render_template(
'sysinfo/list.jinja.html',
ipaddr=ipaddr if ipaddr else self._model_manager.lang().map().get('common_unknown_ipaddr'),
l=self._model_manager.lang().map(),
ro_variables=self._model_manager.variable().get_readonly_variables(),
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr'),
l=self._model_store.lang().map(),
ro_variables=self._model_store.variable().get_readonly_variables(),
)
def sysinfo_restart(self):
if platform.system().lower() == 'darwin':
if self._model_manager.config().map().get('debug'):
if self._model_store.config().map().get('debug'):
python = sys.executable
os.execl(python, python, *sys.argv)
else:
@ -49,8 +49,8 @@ class SysinfoController:
return jsonify({'status': 'ok'})
def sysinfo_restart_needed(self):
var_last_slide_update = self._model_manager.variable().get_one_by_name('last_slide_update')
var_last_restart = self._model_manager.variable().get_one_by_name('last_restart')
var_last_slide_update = self._model_store.variable().get_one_by_name('last_slide_update')
var_last_restart = self._model_store.variable().get_one_by_name('last_restart')
if var_last_slide_update.value <= var_last_restart.value:
return jsonify({'status': False})

View File

@ -83,7 +83,6 @@ class SlideManager:
def delete(self, id: str) -> None:
slide = self.get(id)
print(id)
if slide:
if slide.has_file():

View File

@ -15,18 +15,19 @@ class VariableManager:
self._var_map = {}
self.reload()
def reload(self, lang_map: Optional[Dict] = None) -> None:
default_vars = [
{"name": "port", "value": 5000, "type": VariableType.INT.value, "editable": True, "description": lang_map['settings_variable_help_port'] if lang_map else ""},
{"name": "bind", "value": '0.0.0.0', "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_bind'] if lang_map else ""},
{"name": "lang", "value": "en", "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_lang'] if lang_map else ""},
{"name": "fleet_enabled", "value": "0", "type": VariableType.BOOL.value, "editable": True, "description": lang_map['settings_variable_help_fleet_enabled'] if lang_map else ""},
{"name": "external_url", "value": "", "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_external_url'] if lang_map else ""},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP.value, "editable": False, "description": lang_map['settings_variable_help_ro_editable'] if lang_map else ""},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP.value, "editable": False, "description": lang_map['settings_variable_help_ro_last_slide_update'] if lang_map else ""},
]
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str) -> Variable:
if isinstance(value, bool) and value:
value = '1'
elif isinstance(value, bool) and not value:
value = '0'
for default_var in default_vars:
default_var = {
"name": name,
"value": value,
"type": type.value,
"editable": editable,
"description": description
}
variable = self.get_one_by_name(default_var['name'])
if not variable:
@ -38,15 +39,35 @@ class VariableManager:
if variable.name == 'last_restart':
self._db.update_by_id(variable.id, {"value": time.time()})
return variable
def reload(self, lang_map: Optional[Dict] = None) -> None:
default_vars = [
{"name": "port", "value": 5000, "type": VariableType.INT, "editable": True, "description": lang_map['settings_variable_help_port'] if lang_map else ""},
{"name": "bind", "value": '0.0.0.0', "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_bind'] if lang_map else ""},
{"name": "lang", "value": "en", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_lang'] if lang_map else ""},
{"name": "fleet_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_help_fleet_enabled'] if lang_map else ""},
{"name": "external_url", "value": "", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_external_url'] if lang_map else ""},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_help_ro_editable'] if lang_map else ""},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_help_ro_last_slide_update'] if lang_map else ""},
]
for default_var in default_vars:
self.set_variable(**default_var)
self._var_map = self.prepare_variable_map()
def map(self) -> dict:
return self._var_map
def prepare_variable_map(self) -> Dict[str, Variable]:
return self.list_to_map(self.get_all())
@staticmethod
def list_to_map(list: List[Variable]) -> Dict[str, Variable]:
var_map = {}
for var in self.get_all():
for var in list:
var_map[var.name] = var
return var_map
@ -75,6 +96,9 @@ class VariableManager:
def get_by(self, query) -> List[Variable]:
return self.hydrate_dict(self._db.get_by_query(query=query))
def get_by_prefix(self, prefix: str) -> List[Variable]:
return self.hydrate_dict(self._db.get_by_query(query=lambda v: v['name'].startswith(prefix)))
def get_one_by_name(self, name: str) -> Optional[Variable]:
return self.get_one_by(query=lambda v: v['name'] == name)

View File

@ -0,0 +1,51 @@
from src.model.HookType import HookType
from typing import Optional
class HookRegistration:
def __init__(self, plugin, hook: HookType, priority: int = 0, template: Optional[str] = None):
self._plugin = plugin
self._hook = hook
self._priority = priority
self._template = template
@property
def plugin(self):
return self._plugin
@plugin.setter
def plugin(self, value):
self._plugin = value
@property
def hook(self) -> HookType:
return self._hook
@hook.setter
def hook(self, value: HookType):
self._hook = value
@property
def priority(self) -> int:
return self._priority
@priority.setter
def priority(self, value: int):
self._priority = value
@property
def template(self) -> Optional[str]:
return self._template
@template.setter
def template(self, value: Optional[str]):
self._template = value
def __str__(self) -> str:
return f"HookRegistration(" \
f"plugin='{self.plugin.get_id()}',\n" \
f"hook='{self.hook}',\n" \
f"priority='{self.priority}',\n" \
f"template='{self.template}',\n" \
f")"

6
src/model/HookType.py Normal file
View File

@ -0,0 +1,6 @@
from enum import Enum
class HookType(Enum):
H_FLEET_SLIDESHOW_TOOLBAR_ACTIONS = 'h_fleet_slideshow_toolbar_actions'

View File

@ -6,7 +6,7 @@ from src.manager.ConfigManager import ConfigManager
from src.manager.LoggingManager import LoggingManager
class ModelManager:
class ModelStore:
def __init__(self):
self._variable_manager = VariableManager()

112
src/service/PluginStore.py Normal file
View File

@ -0,0 +1,112 @@
import os
import logging
import inspect
import importlib
from plugins.system.ObPlugin import ObPlugin
from src.service.ModelStore import ModelStore
from src.manager.VariableManager import VariableManager
from src.model.VariableType import VariableType
from src.model.HookType import HookType
from src.model.HookRegistration import HookRegistration
from typing import List, Dict, Union
class PluginStore:
FOLDER_PLUGINS_SYSTEM = 'plugins/system'
FOLDER_PLUGINS_USER = 'plugins/user'
DEFAULT_PLUGIN_ENABLED_VARIABLE = "enabled"
def __init__(self, project_dir: str, model_store: ModelStore):
self._project_dir = project_dir
self._model_store = model_store
self._hooks = self.pre_load_hooks()
self._dead_variables_candidates = VariableManager.list_to_map(self._model_store.variable().get_by_prefix(ObPlugin.PLUGIN_PREFIX))
self._system_plugins = self.find_plugins_in_directory(self.FOLDER_PLUGINS_SYSTEM)
self._system_plugins = self.find_plugins_in_directory(self.FOLDER_PLUGINS_USER)
self.post_load_hooks()
self.clean_dead_variables()
def map_hooks(self) -> Dict[HookType, List[HookRegistration]]:
return self._hooks
def find_plugins_in_directory(self, directory: str) -> list:
plugins = []
for root, dirs, files in os.walk('{}/{}'.format(self._project_dir, directory)):
for file in files:
if file.endswith(".py"):
module_name = file[:-3]
module_path = os.path.join(root, file)
spec = importlib.util.spec_from_file_location(module_name, module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, ObPlugin) and obj is not ObPlugin:
plugin = obj(
directory=root,
model_store=self._model_store
)
plugins.append(plugin)
self.setup_plugin(plugin)
return plugins
def pre_load_hooks(self) -> Dict[HookType, List[HookRegistration]]:
hooks = {}
for hook in HookType:
hooks[hook] = []
return hooks
def post_load_hooks(self) -> None:
for hook_type in self._hooks:
self._hooks[hook_type] = sorted(self._hooks[hook_type], key=lambda hook_reg: hook_reg.priority, reverse=True)
def setup_plugin(self, plugin: ObPlugin) -> None:
variables = plugin.use_variables() + [
plugin.set_variable(
name=self.DEFAULT_PLUGIN_ENABLED_VARIABLE,
value=False,
type=VariableType.BOOL,
editable=True,
description="Enable {} plugin".format(plugin.use_title())
)
]
if not self.is_plugin_enabled(plugin):
return
for variable in variables:
if variable.name in self._dead_variables_candidates:
del self._dead_variables_candidates[variable.name]
hooks_registrations = plugin.use_hooks_registrations()
for hook_registration in hooks_registrations:
if hook_registration.hook not in self._hooks:
logging.error("Hook {} does not exist".format(hook.name))
continue
hook_registration.template = "{}/views/{}.jinja.html".format(plugin.get_directory(), hook_registration.hook.value)
self._hooks[hook_registration.hook].append(hook_registration)
logging.info("Plugin {} loaded ({} var{} and {} hook) from {}".format(
plugin.use_title(),
len(variables),
"s" if len(variables) > 1 else "",
len(hooks_registrations),
"s" if len(hooks_registrations) > 1 else "",
plugin.get_directory()
))
def clean_dead_variables(self) -> None:
for variable_name, variable in self._dead_variables_candidates.items():
logging.info("Removing dead plugin variable {}".format(variable_name))
self._model_store.variable().delete(variable.id)
def is_plugin_enabled(self, plugin: ObPlugin) -> bool:
var = self._model_store.variable().get_one_by_name(plugin.get_plugin_variable_name(self.DEFAULT_PLUGIN_ENABLED_VARIABLE))
return var.as_bool() if var else False

View File

@ -1,13 +1,16 @@
import os
import time
from flask import Flask, send_from_directory
from src.service.ModelManager import ModelManager
from flask import Flask, send_from_directory, Markup
from jinja2 import Environment, FileSystemLoader, select_autoescape
from src.service.ModelStore import ModelStore
from src.service.PluginStore import PluginStore
from src.controller.PlayerController import PlayerController
from src.controller.SlideshowController import SlideshowController
from src.controller.FleetController import FleetController
from src.controller.SysinfoController import SysinfoController
from src.controller.SettingsController import SettingsController
from src.model.HookType import HookType
class WebServer:
@ -18,16 +21,17 @@ class WebServer:
FOLDER_STATIC_WEB_ASSETS = "www"
MAX_UPLOADS = 16 * 1024 * 1024
def __init__(self, project_dir: str, model_manager: ModelManager):
def __init__(self, project_dir: str, model_store: ModelStore, plugin_store: PluginStore):
self._project_dir = project_dir
self._model_manager = model_manager
self._debug = self._model_manager.config().map().get('debug')
self._model_store = model_store
self._plugin_store = plugin_store
self._debug = self._model_store.config().map().get('debug')
self.setup()
def run(self) -> None:
self._app.run(
host=self._model_manager.variable().map().get('bind').as_string(),
port=self._model_manager.variable().map().get('port').as_int(),
host=self._model_store.variable().map().get('bind').as_string(),
port=self._model_store.variable().map().get('port').as_int(),
debug=self._debug
)
@ -58,23 +62,29 @@ class WebServer:
self._app.config['TEMPLATES_AUTO_RELOAD'] = True
def _setup_view_controllers(self) -> None:
PlayerController(self._app, self._model_manager)
SlideshowController(self._app, self._model_manager)
SettingsController(self._app, self._model_manager)
SysinfoController(self._app, self._model_manager)
PlayerController(self._app, self._model_store)
SlideshowController(self._app, self._model_store)
SettingsController(self._app, self._model_store)
SysinfoController(self._app, self._model_store)
if self._model_manager.variable().map().get('fleet_enabled').as_bool():
FleetController(self._app, self._model_manager)
if self._model_store.variable().map().get('fleet_enabled').as_bool():
FleetController(self._app, self._model_store)
def _setup_view_globals(self) -> None:
@self._app.context_processor
def inject_global_vars():
return dict(
FLEET_ENABLED=self._model_manager.variable().map().get('fleet_enabled').as_bool(),
LANG=self._model_manager.variable().map().get('lang').as_string(),
STATIC_PREFIX="/{}/{}/".format(self.FOLDER_STATIC, self.FOLDER_STATIC_WEB_ASSETS)
globals = dict(
FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(),
LANG=self._model_store.variable().map().get('lang').as_string(),
STATIC_PREFIX="/{}/{}/".format(self.FOLDER_STATIC, self.FOLDER_STATIC_WEB_ASSETS),
HOOK=self.render_hook,
)
for hook in HookType:
globals[hook.name] = hook
return globals
def _setup_view_extensions(self) -> None:
@self._app.template_filter('ctime')
def time_ctime(s):
@ -84,3 +94,22 @@ class WebServer:
@self._app.errorhandler(404)
def not_found(e):
return send_from_directory(self._get_template_folder(), 'core/error404.html'), 404
def render_hook(self, hook: HookType):
content = []
for hook_registration in self._plugin_store.map_hooks()[hook]:
env = Environment(
loader=FileSystemLoader("{}/{}".format(hook_registration.plugin.get_directory(), self.FOLDER_TEMPLATES)),
autoescape=select_autoescape(['html', 'xml'])
)
template = env.get_template(os.path.basename(hook_registration.template))
content.append(
template.render(
l=self._model_store.lang().map()
)
)
return Markup("".join(content))

View File

@ -16,8 +16,9 @@
<h2>{{ l.slideshow_page_title }}</h2>
<div class="toolbar-actions">
{% if fleet_mode %}
<button class="normal sysinfo-restart"><i class="fa fa-refresh icon-left"></i>{{ l.sysinfo_panel_button_restart }}</button>
{{ HOOK(H_FLEET_SLIDESHOW_TOOLBAR_ACTIONS) }}
{% endif %}
<button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button>