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

View File

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

View 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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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