commit
04ed4daf65
@ -1,4 +1,3 @@
|
||||
DEBUG=false
|
||||
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
|
||||
|
||||
14
README.md
14
README.md
@ -31,7 +31,6 @@ mkdir -p obscreen/data/db obscreen/data/uploads && cd obscreen
|
||||
docker run --rm --name obscreen --pull=always \
|
||||
-e DEBUG=false \
|
||||
-e PORT=5000 \
|
||||
-e AUTOCONFIGURE_REVERSE_PROXY_MODE=false \
|
||||
-e AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile \
|
||||
-p 5000:5000 \
|
||||
-v ./data/db:/app/data/db \
|
||||
@ -98,7 +97,6 @@ sudo journalctl -u obscreen -f
|
||||
```
|
||||
|
||||
## 👌 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`
|
||||
- Slideshow manager is reachable at `http://localhost:5000/manage`
|
||||
|
||||
@ -108,7 +106,7 @@ sudo journalctl -u obscreen -f
|
||||
|
||||
## 📎 Additional
|
||||
|
||||
### A. Hardware checks
|
||||
### Hardware checks
|
||||
- Basic Setup
|
||||
For basic RaspberryPi setup you can use most of the available guides, for example this one:
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@ -27,6 +27,10 @@ body {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
@ -280,7 +284,6 @@ button.purple:hover {
|
||||
.panel td.infos {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 400px;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ jQuery(document).ready(function ($) {
|
||||
showModal('modal-variable-edit');
|
||||
$('.modal-variable-edit input:visible:eq(0)').focus().select();
|
||||
$('#variable-edit-name').val(variable.name);
|
||||
$('#variable-edit-description').html(variable.description);
|
||||
$('#variable-edit-value').val(variable.value);
|
||||
$('#variable-edit-id').val(variable.id);
|
||||
});
|
||||
|
||||
@ -9,7 +9,6 @@ services:
|
||||
environment:
|
||||
- DEBUG=${DEBUG-false}
|
||||
- PORT=${PORT-5000}
|
||||
- AUTOCONFIGURE_REVERSE_PROXY_MODE=${AUTOCONFIGURE_REVERSE_PROXY_MODE-false}
|
||||
- AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
@ -6,7 +6,6 @@ services:
|
||||
environment:
|
||||
- DEBUG=false
|
||||
- PORT=5000
|
||||
- AUTOCONFIGURE_REVERSE_PROXY_MODE=false
|
||||
- AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile
|
||||
volumes:
|
||||
# If you aren't on a RaspberryPi comment the line below
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"slideshow_page_title": "Schedule Overview",
|
||||
"slideshow_goto_player": "Go to player",
|
||||
"slideshow_slide_button_add": "Add a slide",
|
||||
"slideshow_slide_panel_active": "Active slides",
|
||||
"slideshow_slide_panel_inactive": "Inactive slides",
|
||||
@ -56,7 +57,7 @@
|
||||
"js_fleet_screen_delete_confirmation": "Are you sure?",
|
||||
|
||||
"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_th_description": "Description",
|
||||
"settings_variable_panel_th_value": "Value",
|
||||
@ -81,6 +82,8 @@
|
||||
|
||||
"sysinfo_page_title": "System infos",
|
||||
"sysinfo_panel_button_restart": "Restart",
|
||||
"sysinfo_panel_table_section_system": "System",
|
||||
"sysinfo_panel_table_section_application": "Application",
|
||||
"sysinfo_panel_title": "Infos",
|
||||
"sysinfo_panel_th_attribute": "Attribute",
|
||||
"sysinfo_panel_th_value": "Value",
|
||||
@ -115,5 +118,7 @@
|
||||
"enum_animation_speed_slow": "Slow",
|
||||
"enum_animation_speed_normal": "Normal",
|
||||
"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"
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"slideshow_page_title": "Vue Planning",
|
||||
"slideshow_goto_player": "Voir le lecteur",
|
||||
"slideshow_slide_button_add": "Ajouter une slide",
|
||||
"slideshow_slide_panel_active": "Slides actives",
|
||||
"slideshow_slide_panel_inactive": "Slides inactives",
|
||||
@ -56,7 +57,7 @@
|
||||
"js_fleet_screen_delete_confirmation": "Êtes-vous sûr ?",
|
||||
|
||||
"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_th_description": "Description",
|
||||
"settings_variable_panel_th_value": "Valeur",
|
||||
@ -81,6 +82,8 @@
|
||||
|
||||
"sysinfo_page_title": "Système",
|
||||
"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_th_attribute": "Attribut",
|
||||
"sysinfo_panel_th_value": "Valeur",
|
||||
@ -115,5 +118,7 @@
|
||||
"enum_animation_speed_slow": "Lent",
|
||||
"enum_animation_speed_normal": "Normal",
|
||||
"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"
|
||||
}
|
||||
|
||||
@ -56,9 +56,10 @@ class ObPlugin(abc.ABC):
|
||||
def get_plugin_variable_name(self, name: str) -> str:
|
||||
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(
|
||||
name=self.get_plugin_variable_name(name),
|
||||
section=section,
|
||||
value=value,
|
||||
type=type,
|
||||
editable=editable,
|
||||
|
||||
@ -20,7 +20,6 @@ class ConfigManager:
|
||||
'port': self.DEFAULT_PORT,
|
||||
'bind': '0.0.0.0',
|
||||
'debug': False,
|
||||
'autoconfigure_reverse_proxy_mode': False,
|
||||
'autoconfigure_lx_file': '/home/pi/.config/lxsession/LXDE-pi/autostart',
|
||||
'log_file': None,
|
||||
'log_level': 'INFO',
|
||||
@ -47,7 +46,6 @@ class ConfigManager:
|
||||
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('--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('--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')
|
||||
@ -65,8 +63,6 @@ class ConfigManager:
|
||||
|
||||
if 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:
|
||||
self._CONFIG['autoconfigure_lx_file'] = args.autoconfigure_lx_file
|
||||
if args.log_file:
|
||||
@ -91,21 +87,9 @@ class ConfigManager:
|
||||
logging.info(f"Env var {key} has been found")
|
||||
|
||||
def autoconfigure(self) -> None:
|
||||
if self.map().get('autoconfigure_reverse_proxy_mode'):
|
||||
self.autoconfigure_nginx()
|
||||
|
||||
if self.map().get('autoconfigure_lx_file'):
|
||||
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:
|
||||
destination_path = self.map().get('autoconfigure_lx_file')
|
||||
player_url = self.map().get('player_url')
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from typing import Union, Dict
|
||||
from enum import Enum
|
||||
|
||||
from src.utils import camel_to_snake
|
||||
|
||||
|
||||
class LangManager:
|
||||
|
||||
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._lang = lang.lower()
|
||||
self.load()
|
||||
@ -24,5 +34,31 @@ class LangManager:
|
||||
def map(self) -> dict:
|
||||
return self._map
|
||||
|
||||
def get_locale(self, local_with_country: bool = False) -> str:
|
||||
return "{}_{}".format(self._lang, self._lang.upper()) if local_with_country else self._lang
|
||||
def get_lang(self, local_with_country: bool = False) -> str:
|
||||
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
|
||||
@ -3,9 +3,11 @@ from typing import Dict, Optional, List, Tuple, Union
|
||||
|
||||
from src.model.entity.Screen import Screen
|
||||
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_MODEL = [
|
||||
@ -16,8 +18,8 @@ class ScreenManager:
|
||||
"port"
|
||||
]
|
||||
|
||||
def __init__(self, database_manager: DatabaseManager):
|
||||
self._database_manager = database_manager
|
||||
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
|
||||
super().__init__(lang_manager, database_manager)
|
||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -6,9 +6,11 @@ from pysondb.errors import IdDoesNotExistError
|
||||
from src.model.entity.Slide import Slide
|
||||
from src.utils import str_to_enum, get_optional_string
|
||||
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_MODEL = [
|
||||
@ -21,8 +23,8 @@ class SlideManager:
|
||||
"cron_schedule"
|
||||
]
|
||||
|
||||
def __init__(self, database_manager: DatabaseManager):
|
||||
self._database_manager = database_manager
|
||||
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
|
||||
super().__init__(lang_manager, database_manager)
|
||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -3,25 +3,29 @@ from typing import Dict, Optional, List, Tuple, Union
|
||||
from pysondb.errors import IdDoesNotExistError
|
||||
|
||||
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.Selectable import Selectable
|
||||
from src.model.enum.VariableType import VariableType
|
||||
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.AnimationExitEffect import AnimationExitEffect
|
||||
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": "❌"}
|
||||
|
||||
|
||||
class VariableManager:
|
||||
class VariableManager(ModelManager):
|
||||
|
||||
TABLE_NAME = "settings"
|
||||
TABLE_MODEL = [
|
||||
"description",
|
||||
"editable",
|
||||
"name",
|
||||
"section",
|
||||
"plugin",
|
||||
"selectables",
|
||||
"type",
|
||||
@ -29,13 +33,13 @@ class VariableManager:
|
||||
"value"
|
||||
]
|
||||
|
||||
def __init__(self, database_manager: DatabaseManager):
|
||||
self._database_manager = database_manager
|
||||
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
|
||||
super().__init__(lang_manager, database_manager)
|
||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||
self._var_map = {}
|
||||
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:
|
||||
value = '1'
|
||||
elif isinstance(value, bool) and not value:
|
||||
@ -46,6 +50,7 @@ class VariableManager:
|
||||
|
||||
default_var = {
|
||||
"name": name,
|
||||
"section": section,
|
||||
"value": value,
|
||||
"type": type.value,
|
||||
"editable": editable,
|
||||
@ -68,6 +73,9 @@ class VariableManager:
|
||||
if variable.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:
|
||||
self._db.update_by_id(variable.id, {"selectables": default_var['selectables']})
|
||||
|
||||
@ -76,18 +84,21 @@ class VariableManager:
|
||||
|
||||
return variable
|
||||
|
||||
def reload(self, lang_map: Optional[Dict] = None) -> None:
|
||||
def reload(self) -> None:
|
||||
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"}},
|
||||
{"name": "fleet_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_desc_fleet_enabled'] if lang_map else ""},
|
||||
{"name": "external_url", "value": "", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_desc_external_url'] if lang_map else ""},
|
||||
{"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": "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_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_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_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": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_desc_ro_editable'] if lang_map else ""},
|
||||
{"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 ""},
|
||||
# Editable (Customizable settings)
|
||||
{"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": "fleet_enabled", "section": self.t(VariableSection.GENERAL), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_fleet_enabled')},
|
||||
{"name": "external_url", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": True, "description": self.t('settings_variable_desc_external_url')},
|
||||
{"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_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_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_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": "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)},
|
||||
|
||||
# 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:
|
||||
|
||||
@ -10,11 +10,12 @@ from src.utils import str_to_enum
|
||||
|
||||
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,
|
||||
plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None):
|
||||
self._id = id if id else None
|
||||
self._name = name
|
||||
self._section = section
|
||||
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._description = description
|
||||
@ -46,6 +47,14 @@ class Variable:
|
||||
def name(self, value: str):
|
||||
self._name = value
|
||||
|
||||
@property
|
||||
def section(self) -> str:
|
||||
return self._section
|
||||
|
||||
@section.setter
|
||||
def section(self, value: str):
|
||||
self._section = value
|
||||
|
||||
@property
|
||||
def type(self) -> VariableType:
|
||||
return self._type
|
||||
@ -98,6 +107,7 @@ class Variable:
|
||||
return f"Variable(" \
|
||||
f"id='{self.id}',\n" \
|
||||
f"name='{self.name}',\n" \
|
||||
f"section='{self.section}',\n" \
|
||||
f"value='{self.value}',\n" \
|
||||
f"type='{self.type}',\n" \
|
||||
f"unit='{self.unit}',\n" \
|
||||
@ -114,6 +124,7 @@ class Variable:
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"section": self.section,
|
||||
"value": self.value,
|
||||
"type": self.type.value,
|
||||
"unit": self.unit.value if self.unit else None,
|
||||
|
||||
@ -54,12 +54,4 @@ class AnimationEntranceEffect(Enum):
|
||||
SLIDE_IN_LEFT = 'slideInLeft'
|
||||
SLIDE_IN_RIGHT = 'slideInRight'
|
||||
SLIDE_IN_UP = 'slideInUp'
|
||||
|
||||
@staticmethod
|
||||
def get_values() -> dict:
|
||||
values = {}
|
||||
|
||||
for enum_item in AnimationEntranceEffect:
|
||||
values[enum_item.value] = enum_item.value
|
||||
|
||||
return values
|
||||
|
||||
@ -56,12 +56,3 @@ class AnimationExitEffect(Enum):
|
||||
SLIDE_OUT_LEFT = 'slideOutLeft'
|
||||
SLIDE_OUT_RIGHT = 'slideOutRight'
|
||||
SLIDE_OUT_UP = 'slideOutUp'
|
||||
|
||||
@staticmethod
|
||||
def get_values() -> dict:
|
||||
values = {}
|
||||
|
||||
for enum_item in AnimationExitEffect:
|
||||
values[enum_item.value] = enum_item.value
|
||||
|
||||
return values
|
||||
@ -3,21 +3,8 @@ from enum import Enum
|
||||
|
||||
class AnimationSpeed(Enum):
|
||||
|
||||
SLOWER = 'slower'
|
||||
SLOW = 'slow'
|
||||
NORMAL = 'normal'
|
||||
FAST = 'fast'
|
||||
FASTER = 'faster'
|
||||
|
||||
@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
|
||||
}
|
||||
SLOWER = 'slower' # 3s
|
||||
SLOW = 'slow' # 2s
|
||||
NORMAL = 'normal' # 1s
|
||||
FAST = 'fast' # 800ms
|
||||
FASTER = 'faster' # 500ms
|
||||
|
||||
7
src/model/enum/VariableSection.py
Normal file
7
src/model/enum/VariableSection.py
Normal file
@ -0,0 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class VariableSection(Enum):
|
||||
|
||||
GENERAL = 'general'
|
||||
ANIMATION = 'animation'
|
||||
23
src/service/ModelManager.py
Normal file
23
src/service/ModelManager.py
Normal 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
|
||||
@ -10,14 +10,22 @@ from src.manager.LoggingManager import LoggingManager
|
||||
class ModelStore:
|
||||
|
||||
def __init__(self):
|
||||
# Pure
|
||||
self._lang_manager = LangManager()
|
||||
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._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)
|
||||
self._lang_manager = LangManager(lang=self.variable().map().get('lang').as_string())
|
||||
self._variable_manager.reload(lang_map=self._lang_manager.map())
|
||||
|
||||
# Model
|
||||
self._screen_manager = ScreenManager(lang_manager=self._lang_manager, database_manager=self._database_manager)
|
||||
self._slide_manager = SlideManager(lang_manager=self._lang_manager, database_manager=self._database_manager)
|
||||
self._variable_manager.reload()
|
||||
|
||||
def logging(self) -> LoggingManager:
|
||||
return self._logging_manager
|
||||
|
||||
@ -20,7 +20,7 @@ class TemplateRenderer:
|
||||
self._render_hook = render_hook
|
||||
|
||||
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:
|
||||
globals = dict(
|
||||
|
||||
17
src/utils.py
17
src/utils.py
@ -2,11 +2,26 @@ import re
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, Dict
|
||||
from enum import Enum
|
||||
from cron_descriptor import ExpressionDescriptor
|
||||
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:
|
||||
pattern = re.compile(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$')
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -1 +1 @@
|
||||
1.7
|
||||
1.8
|
||||
|
||||
@ -7,13 +7,20 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set ns = namespace(last_plugin='') %}
|
||||
{% set ns = namespace(last_section='') %}
|
||||
{% 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>
|
||||
<td colspan="2">
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
@ -21,7 +28,6 @@
|
||||
<tr class="variable-item" data-level="{{ variable.id }}" data-entity="{{ variable.to_json() }}">
|
||||
<td class="infos">
|
||||
<div class="inner">
|
||||
<i class="fa fa-cog icon-left"></i>
|
||||
{{ variable.description }}
|
||||
</div>
|
||||
</td>
|
||||
@ -46,7 +52,11 @@
|
||||
</a>
|
||||
</td>
|
||||
</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 %}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -6,10 +6,14 @@
|
||||
<form action="/settings/variable/edit" method="POST">
|
||||
<input type="hidden" name="id" id="variable-edit-id" />
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
<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>
|
||||
|
||||
|
||||
@ -28,6 +28,11 @@
|
||||
{{ HOOK(H_FLEETMODE_SLIDESHOW_TOOLBAR_ACTIONS) }}
|
||||
{% 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>
|
||||
{{ HOOK(H_SLIDESHOW_TOOLBAR_ACTIONS_END) }}
|
||||
</div>
|
||||
|
||||
@ -27,6 +27,13 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h3>
|
||||
<i class="fa fa-server icon-left"></i> {{ l.sysinfo_panel_table_section_system }}
|
||||
</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ l.sysinfo_panel_td_ipaddr }}</td>
|
||||
<td>{{ ipaddr }}</td>
|
||||
@ -51,9 +58,17 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% 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() %}
|
||||
<tr>
|
||||
<td>{{ env_key }}</td>
|
||||
<td>{{ env_key.replace('_',' ')|capitalize }}</td>
|
||||
<td>
|
||||
{% if env_value == true %}
|
||||
✅
|
||||
|
||||
Loading…
Reference in New Issue
Block a user