diff --git a/data/www/js/global.js b/data/www/js/global.js
new file mode 100644
index 0000000..2f7570d
--- /dev/null
+++ b/data/www/js/global.js
@@ -0,0 +1,19 @@
+jQuery(document).ready(function ($) {
+
+ $(document).on('click', '.protected', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (confirm(l.js_common_are_you_sure)) {
+ if ($(this).is('a')) {
+ if ($(this).attr('target') == '_blank') {
+ window.open($(this).attr('href'));
+ } else {
+ document.location.href = $(this).attr('href');
+ }
+ }
+ }
+
+ return false;
+ })
+});
\ No newline at end of file
diff --git a/docs/setup-run-headless.md b/docs/setup-run-headless.md
index a0d9a2f..5d49715 100644
--- a/docs/setup-run-headless.md
+++ b/docs/setup-run-headless.md
@@ -114,6 +114,7 @@ chromium --disable-features=Translate --ignore-certificate-errors --disable-web-
- Execute following script
```bash
cd ~/obscreen
+git pull
source ./venv/bin/activate
pip install -r requirements.txt
sudo systemctl restart obscreen-studio.service
diff --git a/docs/setup-run-on-rpi.md b/docs/setup-run-on-rpi.md
index 4b842f0..be6a5aa 100644
--- a/docs/setup-run-on-rpi.md
+++ b/docs/setup-run-on-rpi.md
@@ -137,6 +137,7 @@ However, I used this one: `(2,82) = 1920x1080 60Hz 1080p`
- Execute following script
```bash
cd ~/obscreen
+git pull
source ./venv/bin/activate
pip install -r requirements.txt
sudo systemctl restart obscreen-studio.service
diff --git a/lang/en.json b/lang/en.json
index 7e64b06..be17d43 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -165,6 +165,7 @@
"common_unknown_ipaddr": "Unknown IP address",
"common_empty": "[Empty]",
+ "common_are_you_sure": "Are you sure?",
"logout": "Logout",
"login_error_not_found": "Bad credentials",
"login_error_bad_credentials": "Bad credentials",
diff --git a/lang/fr.json b/lang/fr.json
index 52b3dc6..59f5680 100644
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -165,6 +165,7 @@
"common_unknown_ipaddr": "Adresse IP inconnue",
"common_empty": "[Vide]",
+ "common_are_you_sure": "Êtes-vous sûr ?",
"logout": "Déconnexion",
"login_error_not_found": "Identifiants invalides",
"login_error_bad_credentials": "Identifiants invalides",
diff --git a/plugins/system/GitUpdater/GitUpdater.py b/plugins/system/GitUpdater/GitUpdater.py
new file mode 100644
index 0000000..5c85a42
--- /dev/null
+++ b/plugins/system/GitUpdater/GitUpdater.py
@@ -0,0 +1,24 @@
+from src.interface.ObPlugin import ObPlugin
+
+from typing import List, Dict
+from src.model.entity.Variable import Variable
+from src.model.enum.VariableType import VariableType
+from src.model.enum.HookType import HookType
+from src.model.hook.HookRegistration import HookRegistration
+
+
+class GitUpdater(ObPlugin):
+
+ def use_id(self):
+ return 'git_updater'
+
+ def use_title(self):
+ return 'Git Updater'
+
+ def use_variables(self) -> List[Variable]:
+ return []
+
+ def use_hooks_registrations(self) -> List[HookRegistration]:
+ return [
+ super().add_static_hook_registration(hook=HookType.H_SYSINFO_TOOLBAR_ACTIONS_START, priority=10),
+ ]
diff --git a/plugins/system/GitUpdater/controller/GitUpdaterController.py b/plugins/system/GitUpdater/controller/GitUpdaterController.py
new file mode 100644
index 0000000..5f32f72
--- /dev/null
+++ b/plugins/system/GitUpdater/controller/GitUpdaterController.py
@@ -0,0 +1,20 @@
+from flask import Flask, redirect, url_for
+from src.interface.ObController import ObController
+from src.utils import run_system_command, sudo_run_system_command, get_working_directory
+from src.Application import Application
+
+
+class GitUpdaterController(ObController):
+
+ def register(self):
+ self._app.add_url_rule('/git-updater/update/now', 'git_updater_update_now', self._auth(self.update_now), methods=['GET'])
+
+ def update_now(self):
+ sudo_run_system_command(['apt', 'install'] + ''.join('git python3-pip python3-venv libsqlite3-dev'))
+ run_system_command(['git', '-C', get_working_directory(), 'stash'])
+ run_system_command(['git', '-C', get_working_directory(), 'checkout', 'tags/v{}'.format(Application.get_version)])
+ run_system_command(['git', '-C', get_working_directory(), 'pull'])
+ run_system_command(['pip', 'install', '-r', 'requirements.txt'])
+ sudo_run_system_command(['systemctl', 'restart', Application.get_name()])
+
+ return redirect(url_for('sysinfo_attribute_list'))
diff --git a/plugins/system/GitUpdater/lang/en.json b/plugins/system/GitUpdater/lang/en.json
new file mode 100644
index 0000000..a0c2cf9
--- /dev/null
+++ b/plugins/system/GitUpdater/lang/en.json
@@ -0,0 +1,3 @@
+{
+ "button_update": "Update"
+}
diff --git a/plugins/system/GitUpdater/lang/fr.json b/plugins/system/GitUpdater/lang/fr.json
new file mode 100644
index 0000000..5da8f45
--- /dev/null
+++ b/plugins/system/GitUpdater/lang/fr.json
@@ -0,0 +1,3 @@
+{
+ "button_update": "Mettre à jour"
+}
diff --git a/plugins/system/GitUpdater/views/hook/h_sysinfo_toolbar_actions_start.jinja.html b/plugins/system/GitUpdater/views/hook/h_sysinfo_toolbar_actions_start.jinja.html
new file mode 100644
index 0000000..5ac7986
--- /dev/null
+++ b/plugins/system/GitUpdater/views/hook/h_sysinfo_toolbar_actions_start.jinja.html
@@ -0,0 +1,2 @@
+{{ l.git_updater_button_update }}
+
diff --git a/src/Application.py b/src/Application.py
index 50dfd38..0988bd7 100644
--- a/src/Application.py
+++ b/src/Application.py
@@ -12,6 +12,7 @@ from src.model.enum.HookType import HookType
class Application:
+
def __init__(self, project_dir: str):
self._project_dir = project_dir
self._stop_event = threading.Event()
@@ -19,7 +20,7 @@ class Application:
self._template_renderer = TemplateRenderer(project_dir=project_dir, model_store=self._model_store, render_hook=self.render_hook)
self._web_server = WebServer(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer)
- logging.info("[Obscreen] Starting...")
+ logging.info("[obscreen] Starting...")
self._plugin_store = PluginStore(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server)
signal.signal(signal.SIGINT, self.signal_handler)
@@ -35,3 +36,11 @@ class Application:
def render_hook(self, hook: HookType) -> str:
return self._template_renderer.render_hooks(self._plugin_store.map_hooks()[hook])
+ @staticmethod
+ def get_name() -> str:
+ return 'obscreen-studio'
+
+ @staticmethod
+ def get_version() -> str:
+ with open("version.txt", 'r') as file:
+ return file.read()
diff --git a/src/controller/SettingsController.py b/src/controller/SettingsController.py
index 386653c..0fc01ed 100644
--- a/src/controller/SettingsController.py
+++ b/src/controller/SettingsController.py
@@ -38,11 +38,6 @@ class SettingsController(ObController):
if variable.name == 'auth_enabled':
self.reload_web_server()
- if variable.as_bool():
- return redirect(url_for(
- 'logout',
- restart=1
- ))
if variable.name == 'lang':
self._model_store.lang().set_lang(variable.value)
diff --git a/src/controller/SysinfoController.py b/src/controller/SysinfoController.py
index dd10fd1..8c87753 100644
--- a/src/controller/SysinfoController.py
+++ b/src/controller/SysinfoController.py
@@ -12,7 +12,7 @@ from src.service.ModelStore import ModelStore
from src.interface.ObController import ObController
from src.utils import get_ip_address, am_i_in_docker
-from src.service.Sysinfos import get_all_sysinfos
+from src.service.Sysinfo import get_all_sysinfo
class SysinfoController(ObController):
@@ -25,7 +25,7 @@ class SysinfoController(ObController):
def sysinfo(self):
return render_template(
'sysinfo/list.jinja.html',
- sysinfos=get_all_sysinfos(),
+ sysinfo=get_all_sysinfo(),
last_logs=self._model_store.logging().get_last_lines_of_stdout(100),
ro_variables=self._model_store.variable().get_readonly_variables(),
env_variables=self._model_store.config().map()
diff --git a/src/interface/ObController.py b/src/interface/ObController.py
index cee6660..4e5727f 100644
--- a/src/interface/ObController.py
+++ b/src/interface/ObController.py
@@ -9,8 +9,8 @@ from src.interface.ObPlugin import ObPlugin
class ObController(abc.ABC):
def __init__(self, web_server, app, auth_required, model_store: ModelStore, template_renderer: TemplateRenderer, plugin: Optional[ObPlugin] = None):
- self._app = app
self._web_server = web_server
+ self._app = app
self._auth = auth_required
self._model_store = model_store
self._template_renderer = template_renderer
diff --git a/src/model/enum/HookType.py b/src/model/enum/HookType.py
index 5c0762a..125091b 100644
--- a/src/model/enum/HookType.py
+++ b/src/model/enum/HookType.py
@@ -4,12 +4,14 @@ from enum import Enum
class HookType(Enum):
H_FLEETMODE_SLIDESHOW_TOOLBAR_ACTIONS = 'h_fleetmode_slideshow_toolbar_actions'
-
H_SLIDESHOW_TOOLBAR_ACTIONS_START = 'h_slideshow_toolbar_actions_start'
H_SLIDESHOW_TOOLBAR_ACTIONS_END = 'h_slideshow_toolbar_actions_end'
H_SLIDESHOW_CSS = 'h_slideshow_css'
H_SLIDESHOW_JAVASCRIPT = 'h_slideshow_javascript'
+ H_SYSINFO_TOOLBAR_ACTIONS_START = 'h_sysinfo_toolbar_actions_start'
+ H_SYSINFO_TOOLBAR_ACTIONS_END = 'h_sysinfo_toolbar_actions_end'
+
H_FLEET_TOOLBAR_ACTIONS_START = 'h_fleet_toolbar_actions_start'
H_FLEET_TOOLBAR_ACTIONS_END = 'h_fleet_toolbar_actions_end'
H_FLEET_CSS = 'h_fleet_css'
diff --git a/src/service/PluginStore.py b/src/service/PluginStore.py
index 4ff4c53..9c43716 100644
--- a/src/service/PluginStore.py
+++ b/src/service/PluginStore.py
@@ -74,7 +74,9 @@ class PluginStore:
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, ObController) and obj is not ObController:
obj(
+ web_server=self._web_server,
app=self._web_server.get_app(),
+ auth_required=self._web_server.auth_required,
model_store=self._model_store,
template_renderer=self._template_renderer,
plugin=plugin
@@ -156,8 +158,7 @@ class PluginStore:
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))
- if var.as_bool:
- logging.info("[Plugin] {} enabled".format(plugin.use_title()))
+ logging.info("[plugin] {} {}".format("🟢" if var.as_bool() else "⚫️", plugin.use_title()))
return var.as_bool() if var else False
diff --git a/src/service/Sysinfos.py b/src/service/Sysinfo.py
similarity index 95%
rename from src/service/Sysinfos.py
rename to src/service/Sysinfo.py
index d45059e..14912c8 100644
--- a/src/service/Sysinfos.py
+++ b/src/service/Sysinfo.py
@@ -3,7 +3,7 @@ import platform
import psutil
import socket
-from src.utils import convert_size
+from src.utils import convert_size, get_working_directory
def get_rpi_model():
@@ -94,14 +94,14 @@ def get_default_log_file():
return None
-def get_all_sysinfos():
+def get_all_sysinfo():
rpi_model = get_rpi_model()
infos = {
"sysinfo_rpi_model": rpi_model if rpi_model else 'sysinfo_rpi_model_unknown',
"sysinfo_storage_free_space": get_free_space(),
"sysinfo_memory_usage": "{}{}".format(get_memory_usage()['percent'], "%"),
"sysinfo_os_version": get_os_version(),
- "sysinfo_install_directory": os.getcwd()
+ "sysinfo_install_directory": get_working_directory()
}
network_info = get_network_info()
diff --git a/src/service/TemplateRenderer.py b/src/service/TemplateRenderer.py
index 80d3eaa..7c6a00f 100644
--- a/src/service/TemplateRenderer.py
+++ b/src/service/TemplateRenderer.py
@@ -1,6 +1,6 @@
import os
-from flask import Flask, send_from_directory, Markup
+from flask import Flask, send_from_directory, Markup, url_for
from typing import List
from jinja2 import Environment, FileSystemLoader, select_autoescape
from src.service.ModelStore import ModelStore
@@ -57,7 +57,8 @@ class TemplateRenderer:
os.path.basename(hook_registration.template)
))
content.append(template.render(
- **self.get_view_globals()
+ **self.get_view_globals(),
+ url_for=url_for
))
elif isinstance(hook_registration, FunctionalHookRegistration):
content.append(hook_registration.function())
diff --git a/src/service/WebServer.py b/src/service/WebServer.py
index 9e794bc..5997978 100644
--- a/src/service/WebServer.py
+++ b/src/service/WebServer.py
@@ -43,7 +43,6 @@ class WebServer:
self.setup()
def setup(self) -> None:
- self._auth_enabled = self._model_store.variable().map().get('auth_enabled').as_bool()
self._setup_flask_app()
self._setup_web_globals()
self._setup_web_errors()
@@ -82,33 +81,37 @@ class WebServer:
self._login_manager.init_app(self._app)
self._login_manager.login_view = 'login'
- if self._auth_enabled and self._model_store.user().count_all_enabled() == 0:
+ if self._model_store.variable().map().get('auth_enabled').as_bool() and self._model_store.user().count_all_enabled() == 0:
self._model_store.user().add_form(User(username="admin", password="admin", enabled=True))
@self._login_manager.user_loader
def load_user(user_id):
return self._model_store.user().get(user_id)
- def _setup_web_controllers(self) -> None:
- def auth_required(f):
- if not self._auth_enabled:
- return f
+ def auth_required(self, f):
+ if not self._model_store.variable().map().get('auth_enabled').as_bool():
+ return f
- def decorated_function(*args, **kwargs):
- if not current_user.is_authenticated:
- return redirect(url_for('login'))
+ def decorated_function(*args, **kwargs):
+ if not self._model_store.variable().map().get('auth_enabled').as_bool():
return f(*args, **kwargs)
- return decorated_function
+ if not current_user.is_authenticated:
+ return redirect(url_for('login'))
+ return f(*args, **kwargs)
- CoreController(self, self._app, auth_required, self._model_store, self._template_renderer)
- PlayerController(self, self._app, auth_required, self._model_store, self._template_renderer)
- SlideshowController(self, self._app, auth_required, self._model_store, self._template_renderer)
- SettingsController(self, self._app, auth_required, self._model_store, self._template_renderer)
- SysinfoController(self, self._app, auth_required, self._model_store, self._template_renderer)
- FleetController(self, self._app, auth_required, self._model_store, self._template_renderer)
- PlaylistController(self, self._app, auth_required, self._model_store, self._template_renderer)
- AuthController(self, self._app, auth_required, self._model_store, self._template_renderer)
+ return decorated_function
+
+ def _setup_web_controllers(self) -> None:
+
+ CoreController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
+ PlayerController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
+ SlideshowController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
+ SettingsController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
+ SysinfoController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
+ FleetController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
+ PlaylistController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
+ AuthController(self, self._app, self.auth_required, self._model_store, self._template_renderer)
def _setup_web_globals(self) -> None:
@self._app.context_processor
diff --git a/src/utils.py b/src/utils.py
index a56c191..9c6c9d9 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -1,6 +1,7 @@
import os
import re
import uuid
+import inspect
import logging
import subprocess
import unicodedata
@@ -234,3 +235,26 @@ def convert_size(size_bytes):
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return f"{s} {size_name[i]}"
+
+
+def get_working_directory():
+ return os.getcwd()
+
+
+def run_system_command(commands: list, shell: bool = False, stdout=subprocess.PIPE, stderr=subprocess.PIPE):
+ return subprocess.run(commands, shell=shell, stdout=stdout, stderr=stderr)
+
+
+def sudo_run_system_command(commands: list, shell: bool = False, stdout=subprocess.PIPE, stderr=subprocess.PIPE):
+ if commands[0] != 'sudo':
+ commands.insert(0, 'sudo')
+ return run_system_command(commands, shell=shell, stdout=stdout, stderr=stderr)
+
+
+def get_function_caller(depth: int = 3) -> str:
+ return inspect.getmodulename(inspect.stack()[depth][1])
+
+
+def clamp(x: float, minimum: float, maximum: float) -> float:
+ return max(minimum, min(x, maximum))
+
diff --git a/views/base.jinja.html b/views/base.jinja.html
index 7d5cbad..82de490 100755
--- a/views/base.jinja.html
+++ b/views/base.jinja.html
@@ -122,6 +122,7 @@
+
{{ HOOK(H_ROOT_JAVASCRIPT) }}
{% block add_js %}{% endblock %}