Merge pull request #26 from jr-k/develop

Release v1.8
This commit is contained in:
JRK 2024-05-07 13:42:04 +02:00 committed by GitHub
commit 04ed4daf65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 219 additions and 138 deletions

View File

@ -1,4 +1,3 @@
DEBUG=false DEBUG=false
PORT=5000 PORT=5000
AUTOCONFIGURE_REVERSE_PROXY_MODE=false
AUTOCONFIGURE_LX_FILE=/home/pi/.config/lxsession/LXDE-pi/autostart # Replace by "./var/run/dummy" if not needed AUTOCONFIGURE_LX_FILE=/home/pi/.config/lxsession/LXDE-pi/autostart # Replace by "./var/run/dummy" if not needed

View File

@ -31,7 +31,6 @@ mkdir -p obscreen/data/db obscreen/data/uploads && cd obscreen
docker run --rm --name obscreen --pull=always \ docker run --rm --name obscreen --pull=always \
-e DEBUG=false \ -e DEBUG=false \
-e PORT=5000 \ -e PORT=5000 \
-e AUTOCONFIGURE_REVERSE_PROXY_MODE=false \
-e AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile \ -e AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile \
-p 5000:5000 \ -p 5000:5000 \
-v ./data/db:/app/data/db \ -v ./data/db:/app/data/db \
@ -98,7 +97,6 @@ sudo journalctl -u obscreen -f
``` ```
## 👌 Usage ## 👌 Usage
- Hostname will be http://localhost:5000 or http://localhost with nginx or http://[SERVER_IP]:[PORT]
- Page which plays slideshow is reachable at `http://localhost:5000` - Page which plays slideshow is reachable at `http://localhost:5000`
- Slideshow manager is reachable at `http://localhost:5000/manage` - Slideshow manager is reachable at `http://localhost:5000/manage`
@ -108,7 +106,7 @@ sudo journalctl -u obscreen -f
## 📎 Additional ## 📎 Additional
### A. Hardware checks ### Hardware checks
- Basic Setup - Basic Setup
For basic RaspberryPi setup you can use most of the available guides, for example this one: For basic RaspberryPi setup you can use most of the available guides, for example this one:
https://gist.github.com/blackjid/dfde6bedef148253f987 https://gist.github.com/blackjid/dfde6bedef148253f987
@ -118,13 +116,3 @@ You may need to set the HDMI Mode on the raspi to ensure the hdmi resolution mat
https://www.raspberrypi.org/documentation/configuration/config-txt/video.md https://www.raspberrypi.org/documentation/configuration/config-txt/video.md
However, I used this one: `(2,82) = 1920x1080 60Hz 1080p` However, I used this one: `(2,82) = 1920x1080 60Hz 1080p`
### B. Nginx server to serve pages (useful for gzip compression for instance)
1. Install
```bash
sudo apt install -y nginx
sudo rm /etc/nginx/sites-enabled/default 2>/dev/null
sudo ln -s "$(pwd)/system/nginx-obscreen" /etc/nginx/sites-enabled
sudo systemctl reload nginx
```
2. Set `autoconfigure_reverse_proxy_mode` to `true` in `.env` file

View File

@ -27,6 +27,10 @@ body {
align-self: stretch; align-self: stretch;
} }
.invisible {
visibility: hidden !important;
}
.hidden { .hidden {
display: none !important; display: none !important;
} }
@ -280,7 +284,6 @@ button.purple:hover {
.panel td.infos { .panel td.infos {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 400px;
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
} }

View File

@ -35,6 +35,7 @@ jQuery(document).ready(function ($) {
showModal('modal-variable-edit'); showModal('modal-variable-edit');
$('.modal-variable-edit input:visible:eq(0)').focus().select(); $('.modal-variable-edit input:visible:eq(0)').focus().select();
$('#variable-edit-name').val(variable.name); $('#variable-edit-name').val(variable.name);
$('#variable-edit-description').html(variable.description);
$('#variable-edit-value').val(variable.value); $('#variable-edit-value').val(variable.value);
$('#variable-edit-id').val(variable.id); $('#variable-edit-id').val(variable.id);
}); });

View File

@ -9,7 +9,6 @@ services:
environment: environment:
- DEBUG=${DEBUG-false} - DEBUG=${DEBUG-false}
- PORT=${PORT-5000} - PORT=${PORT-5000}
- AUTOCONFIGURE_REVERSE_PROXY_MODE=${AUTOCONFIGURE_REVERSE_PROXY_MODE-false}
- AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile - AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile
volumes: volumes:
- .:/app - .:/app

View File

@ -6,7 +6,6 @@ services:
environment: environment:
- DEBUG=false - DEBUG=false
- PORT=5000 - PORT=5000
- AUTOCONFIGURE_REVERSE_PROXY_MODE=false
- AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile - AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile
volumes: volumes:
# If you aren't on a RaspberryPi comment the line below # If you aren't on a RaspberryPi comment the line below

View File

@ -1,5 +1,6 @@
{ {
"slideshow_page_title": "Schedule Overview", "slideshow_page_title": "Schedule Overview",
"slideshow_goto_player": "Go to player",
"slideshow_slide_button_add": "Add a slide", "slideshow_slide_button_add": "Add a slide",
"slideshow_slide_panel_active": "Active slides", "slideshow_slide_panel_active": "Active slides",
"slideshow_slide_panel_inactive": "Inactive slides", "slideshow_slide_panel_inactive": "Inactive slides",
@ -56,7 +57,7 @@
"js_fleet_screen_delete_confirmation": "Are you sure?", "js_fleet_screen_delete_confirmation": "Are you sure?",
"settings_page_title": "Settings", "settings_page_title": "Settings",
"settings_variable_panel_system_variables": "System settings", "settings_variable_panel_system_variables": "General settings",
"settings_variable_panel_plugin_variables": "Plugins settings", "settings_variable_panel_plugin_variables": "Plugins settings",
"settings_variable_panel_th_description": "Description", "settings_variable_panel_th_description": "Description",
"settings_variable_panel_th_value": "Value", "settings_variable_panel_th_value": "Value",
@ -81,6 +82,8 @@
"sysinfo_page_title": "System infos", "sysinfo_page_title": "System infos",
"sysinfo_panel_button_restart": "Restart", "sysinfo_panel_button_restart": "Restart",
"sysinfo_panel_table_section_system": "System",
"sysinfo_panel_table_section_application": "Application",
"sysinfo_panel_title": "Infos", "sysinfo_panel_title": "Infos",
"sysinfo_panel_th_attribute": "Attribute", "sysinfo_panel_th_attribute": "Attribute",
"sysinfo_panel_th_value": "Value", "sysinfo_panel_th_value": "Value",
@ -115,5 +118,7 @@
"enum_animation_speed_slow": "Slow", "enum_animation_speed_slow": "Slow",
"enum_animation_speed_normal": "Normal", "enum_animation_speed_normal": "Normal",
"enum_animation_speed_fast": "Fast", "enum_animation_speed_fast": "Fast",
"enum_animation_speed_faster": "Faster" "enum_animation_speed_faster": "Faster",
"enum_variable_section_general": "General",
"enum_variable_section_animation": "Animation"
} }

View File

@ -1,5 +1,6 @@
{ {
"slideshow_page_title": "Vue Planning", "slideshow_page_title": "Vue Planning",
"slideshow_goto_player": "Voir le lecteur",
"slideshow_slide_button_add": "Ajouter une slide", "slideshow_slide_button_add": "Ajouter une slide",
"slideshow_slide_panel_active": "Slides actives", "slideshow_slide_panel_active": "Slides actives",
"slideshow_slide_panel_inactive": "Slides inactives", "slideshow_slide_panel_inactive": "Slides inactives",
@ -56,7 +57,7 @@
"js_fleet_screen_delete_confirmation": "Êtes-vous sûr ?", "js_fleet_screen_delete_confirmation": "Êtes-vous sûr ?",
"settings_page_title": "Paramètres", "settings_page_title": "Paramètres",
"settings_variable_panel_system_variables": "Paramètres système", "settings_variable_panel_system_variables": "Paramètres généraux",
"settings_variable_panel_plugin_variables": "Paramètres des plugins", "settings_variable_panel_plugin_variables": "Paramètres des plugins",
"settings_variable_panel_th_description": "Description", "settings_variable_panel_th_description": "Description",
"settings_variable_panel_th_value": "Valeur", "settings_variable_panel_th_value": "Valeur",
@ -81,6 +82,8 @@
"sysinfo_page_title": "Système", "sysinfo_page_title": "Système",
"sysinfo_panel_button_restart": "Redémarrer", "sysinfo_panel_button_restart": "Redémarrer",
"sysinfo_panel_table_section_system": "Système",
"sysinfo_panel_table_section_application": "Application",
"sysinfo_panel_title": "Informations", "sysinfo_panel_title": "Informations",
"sysinfo_panel_th_attribute": "Attribut", "sysinfo_panel_th_attribute": "Attribut",
"sysinfo_panel_th_value": "Valeur", "sysinfo_panel_th_value": "Valeur",
@ -115,5 +118,7 @@
"enum_animation_speed_slow": "Lent", "enum_animation_speed_slow": "Lent",
"enum_animation_speed_normal": "Normal", "enum_animation_speed_normal": "Normal",
"enum_animation_speed_fast": "Rapide", "enum_animation_speed_fast": "Rapide",
"enum_animation_speed_faster": "Très rapide" "enum_animation_speed_faster": "Très rapide",
"enum_variable_section_general": "Général",
"enum_variable_section_animation": "Animation"
} }

View File

@ -56,9 +56,10 @@ class ObPlugin(abc.ABC):
def get_plugin_variable_name(self, name: str) -> str: def get_plugin_variable_name(self, name: str) -> str:
return "{}_{}".format(self.get_plugin_variable_prefix(), name) return "{}_{}".format(self.get_plugin_variable_prefix(), name)
def add_variable(self, name: str, value='', type: VariableType = VariableType.STRING, editable: bool = True, description: str = '', selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None) -> Variable: def add_variable(self, name: str, value='', section: str = '', type: VariableType = VariableType.STRING, editable: bool = True, description: str = '', selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None) -> Variable:
return self._model_store.variable().set_variable( return self._model_store.variable().set_variable(
name=self.get_plugin_variable_name(name), name=self.get_plugin_variable_name(name),
section=section,
value=value, value=value,
type=type, type=type,
editable=editable, editable=editable,

View File

@ -20,7 +20,6 @@ class ConfigManager:
'port': self.DEFAULT_PORT, 'port': self.DEFAULT_PORT,
'bind': '0.0.0.0', 'bind': '0.0.0.0',
'debug': False, 'debug': False,
'autoconfigure_reverse_proxy_mode': False,
'autoconfigure_lx_file': '/home/pi/.config/lxsession/LXDE-pi/autostart', 'autoconfigure_lx_file': '/home/pi/.config/lxsession/LXDE-pi/autostart',
'log_file': None, 'log_file': None,
'log_level': 'INFO', 'log_level': 'INFO',
@ -47,7 +46,6 @@ class ConfigManager:
parser.add_argument('--debug', '-d', default=self._CONFIG['debug'], help='Debug mode') parser.add_argument('--debug', '-d', default=self._CONFIG['debug'], help='Debug mode')
parser.add_argument('--port', '-p', default=self._CONFIG['port'], help='Application port') parser.add_argument('--port', '-p', default=self._CONFIG['port'], help='Application port')
parser.add_argument('--bind', '-b', default=self._CONFIG['bind'], help='Application bind address') parser.add_argument('--bind', '-b', default=self._CONFIG['bind'], help='Application bind address')
parser.add_argument('--autoconfigure-reverse-proxy-mode', '-r', default=self._CONFIG['autoconfigure_reverse_proxy_mode'], action='store_true', help='true if you want to use nginx on port 80')
parser.add_argument('--autoconfigure-lx-file', '-x', default=self._CONFIG['autoconfigure_lx_file'], help='Path to lx autostart file') parser.add_argument('--autoconfigure-lx-file', '-x', default=self._CONFIG['autoconfigure_lx_file'], help='Path to lx autostart file')
parser.add_argument('--log-file', '-lf', default=self._CONFIG['log_file'], help='Log File path') parser.add_argument('--log-file', '-lf', default=self._CONFIG['log_file'], help='Log File path')
parser.add_argument('--log-level', '-ll', default=self._CONFIG['log_level'], help='Log Level') parser.add_argument('--log-level', '-ll', default=self._CONFIG['log_level'], help='Log Level')
@ -65,8 +63,6 @@ class ConfigManager:
if args.debug: if args.debug:
self._CONFIG['debug'] = args.debug self._CONFIG['debug'] = args.debug
if args.autoconfigure_reverse_proxy_mode:
self._CONFIG['autoconfigure_reverse_proxy_mode'] = args.autoconfigure_reverse_proxy_mode
if args.autoconfigure_lx_file: if args.autoconfigure_lx_file:
self._CONFIG['autoconfigure_lx_file'] = args.autoconfigure_lx_file self._CONFIG['autoconfigure_lx_file'] = args.autoconfigure_lx_file
if args.log_file: if args.log_file:
@ -91,21 +87,9 @@ class ConfigManager:
logging.info(f"Env var {key} has been found") logging.info(f"Env var {key} has been found")
def autoconfigure(self) -> None: def autoconfigure(self) -> None:
if self.map().get('autoconfigure_reverse_proxy_mode'):
self.autoconfigure_nginx()
if self.map().get('autoconfigure_lx_file'): if self.map().get('autoconfigure_lx_file'):
self.autoconfigure_lxconf() self.autoconfigure_lxconf()
def autoconfigure_nginx(self) -> None:
reverse_proxy_config_file = 'system/nginx-obscreen'
with open(reverse_proxy_config_file, 'r') as file:
content = file.read()
with open(reverse_proxy_config_file, 'w') as file:
file.write(re.sub(r'proxy_pass .*?;', 'proxy_pass {};'.format(self.map().get('player_url')), content))
self._CONFIG['player_url'] = 'http://localhost'
def autoconfigure_lxconf(self) -> None: def autoconfigure_lxconf(self) -> None:
destination_path = self.map().get('autoconfigure_lx_file') destination_path = self.map().get('autoconfigure_lx_file')
player_url = self.map().get('player_url') player_url = self.map().get('player_url')

View File

@ -1,12 +1,22 @@
import json import json
import logging import logging
from typing import Union, Dict
from enum import Enum
from src.utils import camel_to_snake
class LangManager: class LangManager:
LANG_FILE = "lang/{}.json" LANG_FILE = "lang/{}.json"
def __init__(self, lang: str): def __init__(self, lang: str = "en"):
self._map = {}
self._lang = lang.lower()
self.load()
def set_lang(self, lang):
self._map = {} self._map = {}
self._lang = lang.lower() self._lang = lang.lower()
self.load() self.load()
@ -24,5 +34,31 @@ class LangManager:
def map(self) -> dict: def map(self) -> dict:
return self._map return self._map
def get_locale(self, local_with_country: bool = False) -> str: def get_lang(self, local_with_country: bool = False) -> str:
return "{}_{}".format(self._lang, self._lang.upper()) if local_with_country else self._lang return "{}_{}".format(self._lang, self._lang.upper()) if local_with_country else self._lang
@staticmethod
def enum_to_translation_key(enum: Enum) -> str:
translation_key = str(enum)
[classname, case] = translation_key.split('.')
return "enum_{}_{}".format(
camel_to_snake(classname),
case.lower()
)
def translate(self, token: Union[Enum, str]) -> Union[Dict, str]:
translation_key = str(token)
if isinstance(token, type) and type(token).__name__ == 'EnumType':
values = {}
for enum_item in token:
tkey = self.enum_to_translation_key(enum_item)
values[enum_item.value] = self.translate(tkey)
return values
elif isinstance(token, Enum):
translation_key = self.enum_to_translation_key(token)
map = self.map()
return map[translation_key] if translation_key in map else translation_key

View File

@ -3,9 +3,11 @@ from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Screen import Screen from src.model.entity.Screen import Screen
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
from src.manager.LangManager import LangManager
from src.service.ModelManager import ModelManager
class ScreenManager: class ScreenManager(ModelManager):
TABLE_NAME = "fleet" TABLE_NAME = "fleet"
TABLE_MODEL = [ TABLE_MODEL = [
@ -16,8 +18,8 @@ class ScreenManager:
"port" "port"
] ]
def __init__(self, database_manager: DatabaseManager): def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
self._database_manager = database_manager super().__init__(lang_manager, database_manager)
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
@staticmethod @staticmethod

View File

@ -6,9 +6,11 @@ from pysondb.errors import IdDoesNotExistError
from src.model.entity.Slide import Slide from src.model.entity.Slide import Slide
from src.utils import str_to_enum, get_optional_string from src.utils import str_to_enum, get_optional_string
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
from src.manager.LangManager import LangManager
from src.service.ModelManager import ModelManager
class SlideManager: class SlideManager(ModelManager):
TABLE_NAME = "slideshow" TABLE_NAME = "slideshow"
TABLE_MODEL = [ TABLE_MODEL = [
@ -21,8 +23,8 @@ class SlideManager:
"cron_schedule" "cron_schedule"
] ]
def __init__(self, database_manager: DatabaseManager): def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
self._database_manager = database_manager super().__init__(lang_manager, database_manager)
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
@staticmethod @staticmethod

View File

@ -3,25 +3,29 @@ from typing import Dict, Optional, List, Tuple, Union
from pysondb.errors import IdDoesNotExistError from pysondb.errors import IdDoesNotExistError
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
from src.manager.LangManager import LangManager
from src.service.ModelManager import ModelManager
from src.model.entity.Variable import Variable from src.model.entity.Variable import Variable
from src.model.entity.Selectable import Selectable from src.model.entity.Selectable import Selectable
from src.model.enum.VariableType import VariableType from src.model.enum.VariableType import VariableType
from src.model.enum.VariableUnit import VariableUnit from src.model.enum.VariableUnit import VariableUnit
from src.model.enum.VariableSection import VariableSection
from src.model.enum.AnimationEntranceEffect import AnimationEntranceEffect from src.model.enum.AnimationEntranceEffect import AnimationEntranceEffect
from src.model.enum.AnimationExitEffect import AnimationExitEffect from src.model.enum.AnimationExitEffect import AnimationExitEffect
from src.model.enum.AnimationSpeed import AnimationSpeed from src.model.enum.AnimationSpeed import AnimationSpeed
from src.utils import get_keys, enum_to_str from src.utils import get_keys, enum_to_str, enum_to_dict
SELECTABLE_BOOLEAN = {"1": "", "0": ""} SELECTABLE_BOOLEAN = {"1": "", "0": ""}
class VariableManager: class VariableManager(ModelManager):
TABLE_NAME = "settings" TABLE_NAME = "settings"
TABLE_MODEL = [ TABLE_MODEL = [
"description", "description",
"editable", "editable",
"name", "name",
"section",
"plugin", "plugin",
"selectables", "selectables",
"type", "type",
@ -29,13 +33,13 @@ class VariableManager:
"value" "value"
] ]
def __init__(self, database_manager: DatabaseManager): def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
self._database_manager = database_manager super().__init__(lang_manager, database_manager)
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
self._var_map = {} self._var_map = {}
self.reload() self.reload()
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str, plugin: Optional[None] = None, selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None) -> Variable: def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str, plugin: Optional[None] = None, selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None, section: str = '') -> Variable:
if isinstance(value, bool) and value: if isinstance(value, bool) and value:
value = '1' value = '1'
elif isinstance(value, bool) and not value: elif isinstance(value, bool) and not value:
@ -46,6 +50,7 @@ class VariableManager:
default_var = { default_var = {
"name": name, "name": name,
"section": section,
"value": value, "value": value,
"type": type.value, "type": type.value,
"editable": editable, "editable": editable,
@ -68,6 +73,9 @@ class VariableManager:
if variable.unit != default_var['unit']: if variable.unit != default_var['unit']:
self._db.update_by_id(variable.id, {"unit": default_var['unit']}) self._db.update_by_id(variable.id, {"unit": default_var['unit']})
if variable.section != default_var['section']:
self._db.update_by_id(variable.id, {"section": default_var['section']})
if not same_selectables: if not same_selectables:
self._db.update_by_id(variable.id, {"selectables": default_var['selectables']}) self._db.update_by_id(variable.id, {"selectables": default_var['selectables']})
@ -76,18 +84,21 @@ class VariableManager:
return variable return variable
def reload(self, lang_map: Optional[Dict] = None) -> None: def reload(self) -> None:
default_vars = [ default_vars = [
{"name": "lang", "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_lang'] if lang_map else "", "selectables": {"en": "English", "fr": "French"}}, # Editable (Customizable settings)
{"name": "fleet_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_desc_fleet_enabled'] if lang_map else ""}, {"name": "lang", "section": self.t(VariableSection.GENERAL), "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_lang'), "selectables": {"en": "English", "fr": "French"}},
{"name": "external_url", "value": "", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_desc_external_url'] if lang_map else ""}, {"name": "fleet_enabled", "section": self.t(VariableSection.GENERAL), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_fleet_enabled')},
{"name": "slide_upload_limit", "value": 32 * 1024 * 1024, "unit": VariableUnit.BYTE, "type": VariableType.INT, "editable": True, "description": lang_map['settings_variable_desc_slide_upload_limit'] if lang_map else ""}, {"name": "external_url", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": True, "description": self.t('settings_variable_desc_external_url')},
{"name": "slide_animation_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_enabled'] if lang_map else ""}, {"name": "slide_upload_limit", "section": self.t(VariableSection.ANIMATION), "value": 32 * 1024 * 1024, "unit": VariableUnit.BYTE, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_slide_upload_limit')},
{"name": "slide_animation_entrance_effect", "value": AnimationEntranceEffect.FADE_IN.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_entrance_effect'] if lang_map else "", "selectables": AnimationEntranceEffect.get_values()}, {"name": "slide_animation_enabled", "section": self.t(VariableSection.ANIMATION), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_slide_animation_enabled')},
{"name": "slide_animation_exit_effect", "value": AnimationExitEffect.NONE.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_exit_effect'] if lang_map else "", "selectables": AnimationExitEffect.get_values()}, {"name": "slide_animation_entrance_effect", "section": self.t(VariableSection.ANIMATION), "value": AnimationEntranceEffect.FADE_IN.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_entrance_effect'), "selectables": enum_to_dict(AnimationEntranceEffect)},
{"name": "slide_animation_speed", "value": AnimationSpeed.NORMAL.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_speed'] if lang_map else "", "selectables": AnimationSpeed.get_values(lang_map)}, {"name": "slide_animation_exit_effect", "section": self.t(VariableSection.ANIMATION), "value": AnimationExitEffect.NONE.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_exit_effect'), "selectables": enum_to_dict(AnimationExitEffect)},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_desc_ro_editable'] if lang_map else ""}, {"name": "slide_animation_speed", "section": self.t(VariableSection.ANIMATION), "value": AnimationSpeed.NORMAL.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_speed'), "selectables": self.t(AnimationSpeed)},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_desc_ro_last_slide_update'] if lang_map else ""},
# Not editable (System information)
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_last_slide_update')},
] ]
for default_var in default_vars: for default_var in default_vars:

View File

@ -10,11 +10,12 @@ from src.utils import str_to_enum
class Variable: class Variable:
def __init__(self, name: str = '', description: str = '', type: Union[VariableType, str] = VariableType.STRING, def __init__(self, name: str = '', section: str = '', description: str = '', type: Union[VariableType, str] = VariableType.STRING,
value: Union[int, bool, str] = '', editable: bool = True, id: Optional[str] = None, value: Union[int, bool, str] = '', editable: bool = True, id: Optional[str] = None,
plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None): plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None):
self._id = id if id else None self._id = id if id else None
self._name = name self._name = name
self._section = section
self._type = str_to_enum(type, VariableType) if isinstance(type, str) else type self._type = str_to_enum(type, VariableType) if isinstance(type, str) else type
self._unit = str_to_enum(unit, VariableUnit) if isinstance(unit, str) else unit self._unit = str_to_enum(unit, VariableUnit) if isinstance(unit, str) else unit
self._description = description self._description = description
@ -46,6 +47,14 @@ class Variable:
def name(self, value: str): def name(self, value: str):
self._name = value self._name = value
@property
def section(self) -> str:
return self._section
@section.setter
def section(self, value: str):
self._section = value
@property @property
def type(self) -> VariableType: def type(self) -> VariableType:
return self._type return self._type
@ -98,6 +107,7 @@ class Variable:
return f"Variable(" \ return f"Variable(" \
f"id='{self.id}',\n" \ f"id='{self.id}',\n" \
f"name='{self.name}',\n" \ f"name='{self.name}',\n" \
f"section='{self.section}',\n" \
f"value='{self.value}',\n" \ f"value='{self.value}',\n" \
f"type='{self.type}',\n" \ f"type='{self.type}',\n" \
f"unit='{self.unit}',\n" \ f"unit='{self.unit}',\n" \
@ -114,6 +124,7 @@ class Variable:
return { return {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"section": self.section,
"value": self.value, "value": self.value,
"type": self.type.value, "type": self.type.value,
"unit": self.unit.value if self.unit else None, "unit": self.unit.value if self.unit else None,

View File

@ -55,11 +55,3 @@ class AnimationEntranceEffect(Enum):
SLIDE_IN_RIGHT = 'slideInRight' SLIDE_IN_RIGHT = 'slideInRight'
SLIDE_IN_UP = 'slideInUp' SLIDE_IN_UP = 'slideInUp'
@staticmethod
def get_values() -> dict:
values = {}
for enum_item in AnimationEntranceEffect:
values[enum_item.value] = enum_item.value
return values

View File

@ -56,12 +56,3 @@ class AnimationExitEffect(Enum):
SLIDE_OUT_LEFT = 'slideOutLeft' SLIDE_OUT_LEFT = 'slideOutLeft'
SLIDE_OUT_RIGHT = 'slideOutRight' SLIDE_OUT_RIGHT = 'slideOutRight'
SLIDE_OUT_UP = 'slideOutUp' SLIDE_OUT_UP = 'slideOutUp'
@staticmethod
def get_values() -> dict:
values = {}
for enum_item in AnimationExitEffect:
values[enum_item.value] = enum_item.value
return values

View File

@ -3,21 +3,8 @@ from enum import Enum
class AnimationSpeed(Enum): class AnimationSpeed(Enum):
SLOWER = 'slower' SLOWER = 'slower' # 3s
SLOW = 'slow' SLOW = 'slow' # 2s
NORMAL = 'normal' NORMAL = 'normal' # 1s
FAST = 'fast' FAST = 'fast' # 800ms
FASTER = 'faster' FASTER = 'faster' # 500ms
@staticmethod
def get_values(lang_map: dict) -> dict:
if lang_map is None:
return {}
return {
AnimationSpeed.SLOWER.value: lang_map['enum_animation_speed_slower'], # 3s
AnimationSpeed.SLOW.value: lang_map['enum_animation_speed_slow'], # 2s
AnimationSpeed.NORMAL.value: lang_map['enum_animation_speed_normal'], # 1s
AnimationSpeed.FAST.value: lang_map['enum_animation_speed_fast'], # 800ms
AnimationSpeed.FASTER.value: lang_map['enum_animation_speed_faster'] # 500ms
}

View File

@ -0,0 +1,7 @@
from enum import Enum
class VariableSection(Enum):
GENERAL = 'general'
ANIMATION = 'animation'

View File

@ -0,0 +1,23 @@
from enum import Enum
from typing import Union, Dict
from src.manager.LangManager import LangManager
from src.manager.DatabaseManager import DatabaseManager
class ModelManager:
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
self._lang_manager = lang_manager
self._database_manager = database_manager
def t(self, token: Union[Enum, str]) -> Union[Dict, str]:
return self.lang_manager.translate(token)
@property
def lang_manager(self) -> LangManager:
return self._lang_manager
@property
def database_manager(self) -> DatabaseManager:
return self._database_manager

View File

@ -10,14 +10,22 @@ from src.manager.LoggingManager import LoggingManager
class ModelStore: class ModelStore:
def __init__(self): def __init__(self):
# Pure
self._lang_manager = LangManager()
self._database_manager = DatabaseManager() self._database_manager = DatabaseManager()
self._variable_manager = VariableManager(database_manager=self._database_manager)
# Dynamics
self._variable_manager = VariableManager(lang_manager=self._lang_manager, database_manager=self._database_manager)
self._lang_manager.set_lang(self.variable().map().get('lang').as_string())
# Core
self._config_manager = ConfigManager(variable_manager=self._variable_manager) self._config_manager = ConfigManager(variable_manager=self._variable_manager)
self._logging_manager = LoggingManager(config_manager=self._config_manager) self._logging_manager = LoggingManager(config_manager=self._config_manager)
self._screen_manager = ScreenManager(database_manager=self._database_manager)
self._slide_manager = SlideManager(database_manager=self._database_manager) # Model
self._lang_manager = LangManager(lang=self.variable().map().get('lang').as_string()) self._screen_manager = ScreenManager(lang_manager=self._lang_manager, database_manager=self._database_manager)
self._variable_manager.reload(lang_map=self._lang_manager.map()) self._slide_manager = SlideManager(lang_manager=self._lang_manager, database_manager=self._database_manager)
self._variable_manager.reload()
def logging(self) -> LoggingManager: def logging(self) -> LoggingManager:
return self._logging_manager return self._logging_manager

View File

@ -20,7 +20,7 @@ class TemplateRenderer:
self._render_hook = render_hook self._render_hook = render_hook
def cron_descriptor(self, expression: str, use_24hour_time_format=True) -> str: def cron_descriptor(self, expression: str, use_24hour_time_format=True) -> str:
return get_safe_cron_descriptor(expression, use_24hour_time_format, self._model_store.lang().get_locale(local_with_country=True)) return get_safe_cron_descriptor(expression, use_24hour_time_format, self._model_store.lang().get_lang(local_with_country=True))
def get_view_globals(self) -> dict: def get_view_globals(self) -> dict:
globals = dict( globals = dict(

View File

@ -2,11 +2,26 @@ import re
import subprocess import subprocess
import platform import platform
from typing import Optional, List from typing import Optional, List, Dict
from enum import Enum from enum import Enum
from cron_descriptor import ExpressionDescriptor from cron_descriptor import ExpressionDescriptor
from cron_descriptor.Exception import FormatException, WrongArgumentException, MissingFieldException from cron_descriptor.Exception import FormatException, WrongArgumentException, MissingFieldException
CAMEL_CASE_TO_SNAKE_CASE_PATTERN = re.compile(r'(?<!^)(?=[A-Z])')
def enum_to_dict(enum_class) -> Dict:
values = {}
for enum_item in enum_class:
values[enum_item.value] = enum_item.value
return values
def camel_to_snake(camel: str) -> str:
return CAMEL_CASE_TO_SNAKE_CASE_PATTERN.sub('_', camel).lower()
def is_validate_cron_date_time(expression) -> bool: def is_validate_cron_date_time(expression) -> bool:
pattern = re.compile(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$') pattern = re.compile(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$')

View File

@ -1,22 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location / {
proxy_connect_timeout 60;
proxy_read_timeout 60;
proxy_send_timeout 60;
proxy_intercept_errors on;
proxy_http_version 1.1;
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
error_log /var/log/nginx/obscreen.error.log;
access_log /var/log/nginx/obscreen.access.log;
}

View File

@ -1 +1 @@
1.7 1.8

View File

@ -7,13 +7,20 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% set ns = namespace(last_plugin='') %} {% set ns = namespace(last_section='') %}
{% for variable in variables %} {% for variable in variables %}
{% if variable.plugin and ns.last_plugin != variable.plugin %} {% if
variable.plugin and ns.last_section != variable.plugin
or variable.section and ns.last_section != variable.section
%}
<tr> <tr>
<td colspan="2"> <td colspan="2">
<h3> <h3>
<i class="fa fa-puzzle-piece"></i> {{ variable.plugin.replace('_',' ')|capitalize }} {% if variable.is_from_plugin() %}
<i class="fa fa-puzzle-piece icon-left"></i> {{ variable.plugin.replace('_',' ')|capitalize }}
{% else %}
<i class="fa fa-cog icon-left"></i> {{ variable.section }}
{% endif %}
</h3> </h3>
</td> </td>
</tr> </tr>
@ -21,7 +28,6 @@
<tr class="variable-item" data-level="{{ variable.id }}" data-entity="{{ variable.to_json() }}"> <tr class="variable-item" data-level="{{ variable.id }}" data-entity="{{ variable.to_json() }}">
<td class="infos"> <td class="infos">
<div class="inner"> <div class="inner">
<i class="fa fa-cog icon-left"></i>
{{ variable.description }} {{ variable.description }}
</div> </div>
</td> </td>
@ -46,7 +52,11 @@
</a> </a>
</td> </td>
</tr> </tr>
{% set ns.last_plugin = variable.plugin %} {% if variable.is_from_plugin() %}
{% set ns.last_section = variable.plugin %}
{% else %}
{% set ns.last_section = variable.section %}
{% endif %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@ -6,10 +6,14 @@
<form action="/settings/variable/edit" method="POST"> <form action="/settings/variable/edit" method="POST">
<input type="hidden" name="id" id="variable-edit-id" /> <input type="hidden" name="id" id="variable-edit-id" />
<div class="form-group"> <div class="form-group">
<label for="variable-edit-name">{{ l.settings_variable_form_label_name }}</label> <label for="variable-edit-name" class="hidden">{{ l.settings_variable_form_label_name }}</label>
<div class="widget"> <div class="widget">
<input type="text" name="name" id="variable-edit-name" required="required" disabled="disabled" /> <div id="variable-edit-description"></div>
<input type="text" name="name" id="variable-edit-name" required="required" disabled="disabled" class="hidden" />
</div> </div>
</div> </div>

View File

@ -28,6 +28,11 @@
{{ HOOK(H_FLEETMODE_SLIDESHOW_TOOLBAR_ACTIONS) }} {{ HOOK(H_FLEETMODE_SLIDESHOW_TOOLBAR_ACTIONS) }}
{% endif %} {% endif %}
<a href="/" target="_blank" class="btn">
<i class="fa fa-play icon-left"></i>
{{ l.slideshow_goto_player }}
</a>
<button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button> <button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button>
{{ HOOK(H_SLIDESHOW_TOOLBAR_ACTIONS_END) }} {{ HOOK(H_SLIDESHOW_TOOLBAR_ACTIONS_END) }}
</div> </div>

View File

@ -27,6 +27,13 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr>
<td colspan="2">
<h3>
<i class="fa fa-server icon-left"></i> {{ l.sysinfo_panel_table_section_system }}
</h3>
</td>
</tr>
<tr> <tr>
<td>{{ l.sysinfo_panel_td_ipaddr }}</td> <td>{{ l.sysinfo_panel_td_ipaddr }}</td>
<td>{{ ipaddr }}</td> <td>{{ ipaddr }}</td>
@ -51,9 +58,17 @@
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
<tr>
<td colspan="2">
<h3>
<i class="fa fa-box-open icon-left"></i> {{ l.sysinfo_panel_table_section_application }}
</h3>
</td>
</tr>
{% for env_key, env_value in env_variables.items() %} {% for env_key, env_value in env_variables.items() %}
<tr> <tr>
<td>{{ env_key }}</td> <td>{{ env_key.replace('_',' ')|capitalize }}</td>
<td> <td>
{% if env_value == true %} {% if env_value == true %}