commit
74a8283211
13
data/www/css/flatpickr.min.css
vendored
Executable file
13
data/www/css/flatpickr.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -516,10 +516,14 @@ form .form-group input[type=checkbox] {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
form .form-group input[type=checkbox].trigger {
|
||||
form .form-group .trigger {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
form .form-group select.trigger {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
form .form-group span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
2
data/www/js/flatpickr.min.js
vendored
Normal file
2
data/www/js/flatpickr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -3,6 +3,36 @@ jQuery(document).ready(function ($) {
|
||||
const $tableInactive = $('table.inactive-slides');
|
||||
const $modalsRoot = $('.modals');
|
||||
|
||||
const validateCronDateTime = function(cronExpression) {
|
||||
const pattern = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$/;
|
||||
return pattern.test(cronExpression);
|
||||
};
|
||||
|
||||
const getCronDateTime = function(cronExpression) {
|
||||
const [minutes, hours, day, month, _, year] = cronExpression.split(' ');
|
||||
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')} ${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
|
||||
const loadDateTimePicker = function($el) {
|
||||
$el.val('');
|
||||
const pickr = $el.flatpickr({
|
||||
enableTime: true,
|
||||
time_24hr: true,
|
||||
allowInput: false,
|
||||
allowInvalidPreload: false,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
onChange: function(selectedDates, dateStr, instance) {
|
||||
const d = selectedDates[0];
|
||||
const $target = $el.parents('.widget:eq(0)').find('.target');
|
||||
$target.val(
|
||||
d ? `${d.getMinutes()} ${d.getHours()} ${d.getDate()} ${(d.getMonth() + 1)} * ${d.getFullYear()}` : ''
|
||||
);
|
||||
}
|
||||
});
|
||||
$el.addClass('hidden');
|
||||
};
|
||||
|
||||
const getId = function ($el) {
|
||||
return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level');
|
||||
};
|
||||
@ -68,12 +98,21 @@ jQuery(document).ready(function ($) {
|
||||
updateTable();
|
||||
});
|
||||
|
||||
$(document).on('change', '.modal-slide input[type=checkbox]', function () {
|
||||
const $target = $('#'+ $(this).attr('id').replace('-trigger', ''));
|
||||
const hide = !$(this).is(':checked');
|
||||
$target.toggleClass('hidden', hide);
|
||||
$(document).on('change', '.modal-slide select.trigger', function () {
|
||||
const $target = $(this).parents('.widget:eq(0)').find('.target');
|
||||
const $datetimepicker = $(this).parents('.widget:eq(0)').find('.datetimepicker');
|
||||
|
||||
if (hide) {
|
||||
const isDateTime = $(this).val() === 'datetime';
|
||||
const isLoop = $(this).val() === 'loop';
|
||||
const flushValue = isLoop;
|
||||
|
||||
const hideCronField = isLoop || isDateTime;
|
||||
const hideDateTimeField = !isDateTime;
|
||||
|
||||
$target.toggleClass('hidden', hideCronField);
|
||||
$datetimepicker.toggleClass('hidden', hideDateTimeField)
|
||||
|
||||
if (flushValue) {
|
||||
$target.val('');
|
||||
}
|
||||
});
|
||||
@ -99,20 +138,28 @@ jQuery(document).ready(function ($) {
|
||||
|
||||
$(document).on('click', '.slide-add', function () {
|
||||
showModal('modal-slide-add');
|
||||
loadDateTimePicker($('.modal-slide-add .datetimepicker'))
|
||||
$('.modal-slide-add input:eq(0)').focus().select();
|
||||
});
|
||||
|
||||
$(document).on('click', '.slide-edit', function () {
|
||||
const slide = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
|
||||
showModal('modal-slide-edit');
|
||||
loadDateTimePicker($('.modal-slide-edit .datetimepicker'))
|
||||
|
||||
const hasCron = slide.cron_schedule && slide.cron_schedule.length > 0;
|
||||
const hasDateTime = hasCron && validateCronDateTime(slide.cron_schedule);
|
||||
|
||||
$('.modal-slide-edit input:visible:eq(0)').focus().select();
|
||||
$('#slide-edit-name').val(slide.name);
|
||||
$('#slide-edit-type').val(slide.type);
|
||||
$('#slide-edit-location').val(slide.location);
|
||||
$('#slide-edit-duration').val(slide.duration);
|
||||
$('#slide-edit-cron-schedule').val(slide.cron_schedule).toggleClass('hidden', !hasCron);
|
||||
$('#slide-edit-cron-schedule-trigger').prop('checked', hasCron);
|
||||
$('#slide-edit-cron-schedule').val(slide.cron_schedule).toggleClass('hidden', !hasCron || hasDateTime);
|
||||
$('#slide-edit-cron-schedule-trigger').val(hasDateTime ? 'datetime' : (hasCron ? 'cron' : 'loop'));
|
||||
$('#slide-edit-cron-schedule-datetimepicker').toggleClass('hidden', !hasDateTime).val(
|
||||
hasDateTime ? getCronDateTime(slide.cron_schedule) : ''
|
||||
);
|
||||
$('#slide-edit-id').val(slide.id);
|
||||
});
|
||||
|
||||
@ -137,4 +184,4 @@ jQuery(document).ready(function ($) {
|
||||
});
|
||||
|
||||
main();
|
||||
});
|
||||
});
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
"slideshow_slide_form_label_duration": "Duration",
|
||||
"slideshow_slide_form_label_duration_unit": "seconds",
|
||||
"slideshow_slide_form_label_cron_scheduled": "Scheduled",
|
||||
"slideshow_slide_form_label_cron_scheduled_loop": "In the loop",
|
||||
"slideshow_slide_form_label_cron_scheduled_datetime": "Date & Time",
|
||||
"slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Set a date and time",
|
||||
"slideshow_slide_form_label_cron_scheduled_cron": "Cron",
|
||||
"slideshow_slide_form_widget_cron_scheduled_placeholder": "Use crontab format: * * * * *",
|
||||
"slideshow_slide_form_button_cancel": "Cancel",
|
||||
"js_slideshow_slide_delete_confirmation": "Are you sure?",
|
||||
@ -65,6 +69,7 @@
|
||||
"settings_variable_desc_lang": "Server language",
|
||||
"settings_variable_desc_fleet_enabled": "Enable fleet screen management view",
|
||||
"settings_variable_desc_external_url": "External url (i.e: https://screen-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Slide upload limit (in bytes, 32*1024*1024 for 32MB)",
|
||||
|
||||
"settings_variable_desc_slide_animation_enabled": "Enable animation effect between slides",
|
||||
"settings_variable_desc_slide_animation_entrance_effect": "Slide animation entrance effect",
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
"slideshow_slide_form_label_duration": "Durée",
|
||||
"slideshow_slide_form_label_duration_unit": "secondes",
|
||||
"slideshow_slide_form_label_cron_scheduled": "Programmer",
|
||||
"slideshow_slide_form_label_cron_scheduled_loop": "Dans la boucle",
|
||||
"slideshow_slide_form_label_cron_scheduled_datetime": "Date & Heure",
|
||||
"slideshow_slide_form_label_cron_scheduled_datetime_placeholder": "Choisir une date et un heure",
|
||||
"slideshow_slide_form_label_cron_scheduled_cron": "Cron",
|
||||
"slideshow_slide_form_widget_cron_scheduled_placeholder": "Utiliser le format crontab: * * * * *",
|
||||
"slideshow_slide_form_button_cancel": "Annuler",
|
||||
"js_slideshow_slide_delete_confirmation": "Êtes-vous sûr ?",
|
||||
@ -65,6 +69,7 @@
|
||||
"settings_variable_desc_lang": "Langage de l'application",
|
||||
"settings_variable_desc_fleet_enabled": "Activer la gestion de flotte des écrans",
|
||||
"settings_variable_desc_external_url": "URL externe (i.e: https://screen-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en octets, 32*1024*1024 pour 32Mo)",
|
||||
|
||||
"settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
|
||||
"settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide",
|
||||
|
||||
@ -2,4 +2,4 @@ flask==2.3.3
|
||||
pysondb-v2==2.1.0
|
||||
python-dotenv
|
||||
cron-descriptor
|
||||
|
||||
waitress
|
||||
@ -5,6 +5,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from typing import Optional, List, Dict, Union
|
||||
from src.model.entity.Variable import Variable
|
||||
from src.model.enum.VariableType import VariableType
|
||||
from src.model.enum.VariableUnit import VariableUnit
|
||||
from src.model.enum.HookType import HookType
|
||||
from src.model.hook.HookRegistration import HookRegistration
|
||||
from src.model.hook.StaticHookRegistration import StaticHookRegistration
|
||||
@ -55,13 +56,14 @@ 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) -> Variable:
|
||||
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:
|
||||
return self._model_store.variable().set_variable(
|
||||
name=self.get_plugin_variable_name(name),
|
||||
value=value,
|
||||
type=type,
|
||||
editable=editable,
|
||||
description=description,
|
||||
unit=unit,
|
||||
selectables=selectables if isinstance(selectables, dict) else None,
|
||||
plugin=self.use_id(),
|
||||
)
|
||||
|
||||
33
src/manager/DatabaseManager.py
Normal file
33
src/manager/DatabaseManager.py
Normal file
@ -0,0 +1,33 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
from pysondb import PysonDB
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class DatabaseManager:
|
||||
|
||||
DB_DIR = 'data/db'
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def open(self, table_name: str, table_model: list) -> PysonDB:
|
||||
db_file = "{}/{}.json".format(self.DB_DIR, table_name)
|
||||
db = PysonDB(db_file)
|
||||
db = self._update_model(db_file, table_model)
|
||||
return db
|
||||
|
||||
@staticmethod
|
||||
def _update_model(db_file: str, table_model: list) -> Optional[PysonDB]:
|
||||
try:
|
||||
with open(db_file, 'r') as file:
|
||||
db_model = file.read()
|
||||
db_model = json.loads(db_model)
|
||||
db_model['keys'] = table_model
|
||||
with open(db_file, 'w') as file:
|
||||
file.write(json.dumps(db_model, indent=4))
|
||||
return PysonDB(db_file)
|
||||
except FileNotFoundError:
|
||||
logging.error("Database file {} not found".format(db_file))
|
||||
return None
|
||||
@ -1,15 +1,24 @@
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
from src.model.entity.Screen import Screen
|
||||
from pysondb import PysonDB
|
||||
from pysondb.errors import IdDoesNotExistError
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
|
||||
from src.model.entity.Screen import Screen
|
||||
from src.manager.DatabaseManager import DatabaseManager
|
||||
|
||||
|
||||
class ScreenManager:
|
||||
|
||||
DB_FILE = "data/db/fleet.json"
|
||||
TABLE_NAME = "fleet"
|
||||
TABLE_MODEL = [
|
||||
"name",
|
||||
"enabled",
|
||||
"position",
|
||||
"host",
|
||||
"port"
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._db = PysonDB(self.DB_FILE)
|
||||
def __init__(self, database_manager: DatabaseManager):
|
||||
self._database_manager = database_manager
|
||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||
|
||||
@staticmethod
|
||||
def hydrate_object(raw_screen: dict, id: Optional[str] = None) -> Screen:
|
||||
|
||||
@ -1,18 +1,29 @@
|
||||
import os
|
||||
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
from pysondb.errors import IdDoesNotExistError
|
||||
|
||||
from src.model.entity.Slide import Slide
|
||||
from src.utils import str_to_enum, get_optional_string
|
||||
from pysondb import PysonDB
|
||||
from pysondb.errors import IdDoesNotExistError
|
||||
from src.manager.DatabaseManager import DatabaseManager
|
||||
|
||||
|
||||
class SlideManager:
|
||||
|
||||
DB_FILE = "data/db/slideshow.json"
|
||||
TABLE_NAME = "slideshow"
|
||||
TABLE_MODEL = [
|
||||
"name",
|
||||
"type",
|
||||
"enabled",
|
||||
"duration",
|
||||
"position",
|
||||
"location",
|
||||
"cron_schedule"
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._db = PysonDB(self.DB_FILE)
|
||||
def __init__(self, database_manager: DatabaseManager):
|
||||
self._database_manager = database_manager
|
||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||
|
||||
@staticmethod
|
||||
def hydrate_object(raw_slide: dict, id: str = None) -> Slide:
|
||||
|
||||
@ -1,27 +1,41 @@
|
||||
import time
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
from pysondb.errors import IdDoesNotExistError
|
||||
|
||||
from src.manager.DatabaseManager import DatabaseManager
|
||||
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.AnimationEntranceEffect import AnimationEntranceEffect
|
||||
from src.model.enum.AnimationExitEffect import AnimationExitEffect
|
||||
from src.model.enum.AnimationSpeed import AnimationSpeed
|
||||
from pysondb import PysonDB
|
||||
from pysondb.errors import IdDoesNotExistError
|
||||
from src.utils import get_keys
|
||||
import time
|
||||
from src.utils import get_keys, enum_to_str
|
||||
|
||||
SELECTABLE_BOOLEAN = {"1": "✅", "0": "❌"}
|
||||
|
||||
|
||||
class VariableManager:
|
||||
|
||||
DB_FILE = "data/db/settings.json"
|
||||
TABLE_NAME = "settings"
|
||||
TABLE_MODEL = [
|
||||
"description",
|
||||
"editable",
|
||||
"name",
|
||||
"plugin",
|
||||
"selectables",
|
||||
"type",
|
||||
"unit",
|
||||
"value"
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._db = PysonDB(self.DB_FILE)
|
||||
def __init__(self, database_manager: DatabaseManager):
|
||||
self._database_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) -> 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) -> Variable:
|
||||
if isinstance(value, bool) and value:
|
||||
value = '1'
|
||||
elif isinstance(value, bool) and not value:
|
||||
@ -37,6 +51,7 @@ class VariableManager:
|
||||
"editable": editable,
|
||||
"description": description,
|
||||
"plugin": plugin,
|
||||
"unit": unit.value if unit else None,
|
||||
"selectables": ([{"key": key, "label": label} for key, label in selectables.items()]) if isinstance(selectables, dict) else None
|
||||
}
|
||||
variable = self.get_one_by_name(default_var['name'])
|
||||
@ -50,6 +65,9 @@ class VariableManager:
|
||||
if variable.description != default_var['description']:
|
||||
self._db.update_by_id(variable.id, {"description": default_var['description']})
|
||||
|
||||
if variable.unit != default_var['unit']:
|
||||
self._db.update_by_id(variable.id, {"unit": default_var['unit']})
|
||||
|
||||
if not same_selectables:
|
||||
self._db.update_by_id(variable.id, {"selectables": default_var['selectables']})
|
||||
|
||||
@ -63,6 +81,7 @@ class VariableManager:
|
||||
{"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()},
|
||||
|
||||
@ -3,6 +3,7 @@ import time
|
||||
|
||||
from typing import Optional, Union, Dict, List
|
||||
from src.model.enum.VariableType import VariableType
|
||||
from src.model.enum.VariableUnit import VariableUnit
|
||||
from src.model.entity.Selectable import Selectable
|
||||
from src.utils import str_to_enum
|
||||
|
||||
@ -11,10 +12,11 @@ class Variable:
|
||||
|
||||
def __init__(self, name: 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):
|
||||
plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None):
|
||||
self._id = id if id else None
|
||||
self._name = name
|
||||
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
|
||||
self._value = value
|
||||
self._editable = editable
|
||||
@ -52,6 +54,14 @@ class Variable:
|
||||
def type(self, value: VariableType):
|
||||
self._type = value
|
||||
|
||||
@property
|
||||
def unit(self) -> VariableUnit:
|
||||
return self._unit
|
||||
|
||||
@unit.setter
|
||||
def unit(self, value: VariableUnit):
|
||||
self._unit = value
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self._description
|
||||
@ -90,6 +100,7 @@ class Variable:
|
||||
f"name='{self.name}',\n" \
|
||||
f"value='{self.value}',\n" \
|
||||
f"type='{self.type}',\n" \
|
||||
f"unit='{self.unit}',\n" \
|
||||
f"description='{self.description}',\n" \
|
||||
f"editable='{self.editable}',\n" \
|
||||
f"plugin='{self.plugin}',\n" \
|
||||
@ -105,6 +116,7 @@ class Variable:
|
||||
"name": self.name,
|
||||
"value": self.value,
|
||||
"type": self.type.value,
|
||||
"unit": self.unit.value if self.unit else None,
|
||||
"description": self.description,
|
||||
"editable": self.editable,
|
||||
"plugin": self.plugin,
|
||||
@ -127,9 +139,17 @@ class Variable:
|
||||
value = self.eval()
|
||||
|
||||
if self.type == VariableType.SELECT_SINGLE:
|
||||
for selectable in self.selectables:
|
||||
if selectable.key == value:
|
||||
return str(selectable.label)
|
||||
if isinstance(self._selectables, list):
|
||||
for selectable in self.selectables:
|
||||
if selectable.key == value:
|
||||
value = str(selectable.label)
|
||||
break
|
||||
|
||||
if self.unit == VariableUnit.BYTE:
|
||||
value = "{} {}".format(
|
||||
value / 1024 / 1024,
|
||||
"MB"
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
6
src/model/enum/VariableUnit.py
Normal file
6
src/model/enum/VariableUnit.py
Normal file
@ -0,0 +1,6 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class VariableUnit(Enum):
|
||||
|
||||
BYTE = 'byte'
|
||||
@ -2,6 +2,7 @@ from src.manager.SlideManager import SlideManager
|
||||
from src.manager.ScreenManager import ScreenManager
|
||||
from src.manager.VariableManager import VariableManager
|
||||
from src.manager.LangManager import LangManager
|
||||
from src.manager.DatabaseManager import DatabaseManager
|
||||
from src.manager.ConfigManager import ConfigManager
|
||||
from src.manager.LoggingManager import LoggingManager
|
||||
|
||||
@ -9,11 +10,12 @@ from src.manager.LoggingManager import LoggingManager
|
||||
class ModelStore:
|
||||
|
||||
def __init__(self):
|
||||
self._variable_manager = VariableManager()
|
||||
self._database_manager = DatabaseManager()
|
||||
self._variable_manager = VariableManager(database_manager=self._database_manager)
|
||||
self._config_manager = ConfigManager(variable_manager=self._variable_manager)
|
||||
self._logging_manager = LoggingManager(config_manager=self._config_manager)
|
||||
self._screen_manager = ScreenManager()
|
||||
self._slide_manager = SlideManager()
|
||||
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())
|
||||
|
||||
@ -26,6 +28,9 @@ class ModelStore:
|
||||
def variable(self) -> VariableManager:
|
||||
return self._variable_manager
|
||||
|
||||
def database(self) -> DatabaseManager:
|
||||
return self._database_manager
|
||||
|
||||
def slide(self) -> SlideManager:
|
||||
return self._slide_manager
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ from src.model.hook.HookRegistration import HookRegistration
|
||||
from src.model.hook.StaticHookRegistration import StaticHookRegistration
|
||||
from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
|
||||
from src.constant.WebDirConstant import WebDirConstant
|
||||
from src.utils import get_safe_cron_descriptor
|
||||
from src.utils import get_safe_cron_descriptor, is_validate_cron_date_time
|
||||
|
||||
|
||||
class TemplateRenderer:
|
||||
@ -29,7 +29,8 @@ class TemplateRenderer:
|
||||
VERSION=self._model_store.config().map().get('version'),
|
||||
LANG=self._model_store.variable().map().get('lang').as_string(),
|
||||
HOOK=self._render_hook,
|
||||
cron_descriptor=self.cron_descriptor
|
||||
cron_descriptor=self.cron_descriptor,
|
||||
is_validate_cron_date_time=is_validate_cron_date_time
|
||||
)
|
||||
|
||||
for hook in HookType:
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import os
|
||||
import time
|
||||
from waitress import serve
|
||||
|
||||
from flask import Flask, send_from_directory
|
||||
from src.service.ModelStore import ModelStore
|
||||
@ -14,8 +15,6 @@ from src.constant.WebDirConstant import WebDirConstant
|
||||
|
||||
class WebServer:
|
||||
|
||||
MAX_UPLOADS = 16 * 1024 * 1024
|
||||
|
||||
def __init__(self, project_dir: str, model_store: ModelStore, template_renderer: TemplateRenderer):
|
||||
self._project_dir = project_dir
|
||||
self._model_store = model_store
|
||||
@ -24,10 +23,10 @@ class WebServer:
|
||||
self.setup()
|
||||
|
||||
def run(self) -> None:
|
||||
self._app.run(
|
||||
serve(
|
||||
self._app,
|
||||
host=self._model_store.config().map().get('bind'),
|
||||
port=self._model_store.config().map().get('port'),
|
||||
debug=self._debug
|
||||
port=self._model_store.config().map().get('port')
|
||||
)
|
||||
|
||||
def setup(self) -> None:
|
||||
@ -53,7 +52,7 @@ class WebServer:
|
||||
)
|
||||
|
||||
self._app.config['UPLOAD_FOLDER'] = "{}/{}".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_UPLOADS)
|
||||
self._app.config['MAX_CONTENT_LENGTH'] = self.MAX_UPLOADS
|
||||
self._app.config['MAX_CONTENT_LENGTH'] = self._model_store.variable().map().get('slide_upload_limit').as_int()
|
||||
|
||||
if self._debug:
|
||||
self._app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||||
|
||||
23
src/utils.py
23
src/utils.py
@ -8,7 +8,22 @@ from cron_descriptor import ExpressionDescriptor
|
||||
from cron_descriptor.Exception import FormatException, WrongArgumentException, MissingFieldException
|
||||
|
||||
|
||||
def is_validate_cron_date_time(expression) -> bool:
|
||||
pattern = re.compile(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\*\s+(\d+)$')
|
||||
return bool(pattern.match(expression))
|
||||
|
||||
|
||||
def get_safe_cron_descriptor(expression: str, use_24hour_time_format=True, locale_code: Optional[str] = None) -> str:
|
||||
if is_validate_cron_date_time(expression):
|
||||
[minutes, hours, day, month, _, year] = expression.split(' ')
|
||||
return "{}-{}-{} at {}:{}".format(
|
||||
year,
|
||||
month.zfill(2),
|
||||
day.zfill(2),
|
||||
hours.zfill(2),
|
||||
minutes.zfill(2)
|
||||
)
|
||||
|
||||
options = {
|
||||
"expression": expression,
|
||||
"use_24hour_time_format": use_24hour_time_format
|
||||
@ -60,6 +75,14 @@ def get_keys(dict_or_object, key_list_name: str, key_attr_name: str = 'key') ->
|
||||
return None
|
||||
|
||||
|
||||
def enum_to_str(enum: Optional[Enum]) -> Optional[str]:
|
||||
if enum:
|
||||
print(enum)
|
||||
return str(enum.value)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def str_to_enum(str_val: str, enum_class) -> Enum:
|
||||
for enum_item in enum_class:
|
||||
if enum_item.value == str_val:
|
||||
|
||||
@ -1 +1 @@
|
||||
1.6
|
||||
1.7
|
||||
@ -6,6 +6,7 @@
|
||||
</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="shortcut icon" href="{{ STATIC_PREFIX }}/favicon.ico">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{{ STATIC_PREFIX }}favicon/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="{{ STATIC_PREFIX }}favicon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ STATIC_PREFIX }}favicon/apple-icon-72x72.png">
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
<title>Obscreen</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/animate.min.css" />
|
||||
<link rel="shortcut icon" href="{{ STATIC_PREFIX }}/favicon.ico">
|
||||
{% if slide_animation_enabled.eval() %}
|
||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/animate.min.css" />
|
||||
{% endif %}
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background-color: white; display: flex; flex-direction: row; justify-content: center; align-items: center; }
|
||||
.slide { display: flex; flex-direction: row; justify-content: center; align-items: center; background: black; }
|
||||
|
||||
@ -47,7 +47,11 @@
|
||||
{% if slide.cron_schedule %}
|
||||
{% set cron_desc = cron_descriptor(slide.cron_schedule) %}
|
||||
{% if cron_desc %}
|
||||
{% if is_validate_cron_date_time(slide.cron_schedule) %}
|
||||
📆 {{ cron_desc }}
|
||||
{% else %}
|
||||
⏳ {{ cron_desc }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="error">⚠️ {{ l.slideshow_slide_panel_td_cron_scheduled_bad_cron }}</span>
|
||||
{% endif %}
|
||||
|
||||
@ -5,10 +5,12 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block add_css %}
|
||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/flatpickr.min.css" />
|
||||
{{ HOOK(H_SLIDESHOW_CSS) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block add_js %}
|
||||
<script src="{{ STATIC_PREFIX }}js/flatpickr.min.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/tablednd-fixed.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/slideshow.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/restart.js"></script>
|
||||
|
||||
@ -25,8 +25,7 @@
|
||||
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_object }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="object" id="slide-add-object-input-text" class="slide-add-object-input" />
|
||||
<input type="file" name="object" id="slide-add-object-input-upload"
|
||||
class="slide-add-object-input hidden" disabled="disabled" />
|
||||
<input type="file" name="object" id="slide-add-object-input-upload" class="slide-add-object-input hidden" disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -41,8 +40,13 @@
|
||||
<div class="form-group">
|
||||
<label for="slide-add-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
|
||||
<div class="widget">
|
||||
<input type="checkbox" id="slide-add-cron-schedule-trigger" class="trigger" />
|
||||
<input type="text" name="cron_schedule" id="slide-add-cron-schedule" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" class="hidden" />
|
||||
<select id="slide-add-cron-schedule-trigger" class="trigger">
|
||||
<option value="loop">{{ l.slideshow_slide_form_label_cron_scheduled_loop }}</option>
|
||||
<option value="datetime">{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}</option>
|
||||
<option value="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
|
||||
</select>
|
||||
<input type="text" id="slide-add-cron-schedule-datetimepicker" class="datetimepicker" value="" placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}" />
|
||||
<input type="text" name="cron_schedule" id="slide-add-cron-schedule" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -41,8 +41,13 @@
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
|
||||
<div class="widget">
|
||||
<input type="checkbox" id="slide-edit-cron-schedule-trigger" class="trigger" />
|
||||
<input type="text" name="cron_schedule" id="slide-edit-cron-schedule" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" class="hidden" />
|
||||
<select id="slide-edit-cron-schedule-trigger" class="trigger">
|
||||
<option value="loop">{{ l.slideshow_slide_form_label_cron_scheduled_loop }}</option>
|
||||
<option value="datetime">{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}</option>
|
||||
<option value="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
|
||||
</select>
|
||||
<input type="text" id="slide-edit-cron-schedule-datetimepicker" class="datetimepicker" value="" placeholder="{{ l.slideshow_slide_form_label_cron_scheduled_datetime_placeholder }}" />
|
||||
<input type="text" name="cron_schedule" id="slide-edit-cron-schedule" class="target hidden" placeholder="{{ l.slideshow_slide_form_widget_cron_scheduled_placeholder }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user