git updater plugin ok

This commit is contained in:
jr-k 2024-05-27 15:19:00 +02:00
parent 156d1e10a1
commit 480571512b
22 changed files with 151 additions and 37 deletions

19
data/www/js/global.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
{
"button_update": "Update"
}

View File

@ -0,0 +1,3 @@
{
"button_update": "Mettre à jour"
}

View File

@ -0,0 +1,2 @@
<a href="{{ url_for('git_updater_update_now') }}" class="btn sysinfo-update protected"><i class="fa fa-cloud-arrow-down icon-left"></i>{{ l.git_updater_button_update }}</a>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -122,6 +122,7 @@
<script>
var secret_key = '{{ SECRET_KEY }}';
var l = {
'js_common_are_you_sure': '{{ l.common_are_you_sure }}',
'js_playlist_delete_confirmation': '{{ l.js_playlist_delete_confirmation }}',
'js_slideshow_slide_delete_confirmation': '{{ l.js_slideshow_slide_delete_confirmation }}',
'js_fleet_studio_delete_confirmation': '{{ l.js_fleet_studio_delete_confirmation }}',
@ -131,6 +132,7 @@
};
</script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="{{ STATIC_PREFIX }}js/global.js"></script>
{{ HOOK(H_ROOT_JAVASCRIPT) }}
{% block add_js %}{% endblock %}
</body>

View File

@ -13,7 +13,9 @@
<div class="toolbar">
<h2>{{ l.sysinfo_page_title }}</h2>
<div class="toolbar-actions">
{{ HOOK(H_SYSINFO_TOOLBAR_ACTIONS_START) }}
<button class="purple sysinfo-restart"><i class="fa fa-refresh icon-left"></i>{{ l.sysinfo_panel_button_restart }}</button>
{{ HOOK(H_SYSINFO_TOOLBAR_ACTIONS_END) }}
</div>
</div>
<div class="panel">
@ -35,10 +37,10 @@
</td>
</tr>
{% for sysinfo_label, sysinfo in sysinfos.items() %}
{% for sysinfo_label, sysinfo_value in sysinfo.items() %}
<tr>
<td>{{ l[sysinfo_label] }}</td>
<td>{{ t(sysinfo) }}</td>
<td>{{ t(sysinfo_value) }}</td>
</tr>
{% endfor %}