diff --git a/data/www/css/main.css b/data/www/css/main.css index e85be4a..8fe7178 100644 --- a/data/www/css/main.css +++ b/data/www/css/main.css @@ -106,7 +106,36 @@ header nav ul { header nav ul li { margin: 0 15px; +} +header nav ul li.user-menu { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + border: 1px solid #0bc44e; + padding: 5px 15px; + border-radius: 4px; + background: rgba(14, 239, 95, .2); +} + +header nav ul li.user-menu .logout { + color: rgb(255, 255, 255); +} + +header nav ul li.user-menu .username { + margin-right: 20px; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + font-weight: bold; + color: #0bc44e; +} + +header nav ul li.user-menu .username i { + margin-right: 5px; + font-size: 12px; } header nav ul li a { @@ -656,4 +685,21 @@ footer .version { text-align: center; font-size: 36px; margin: 0 0 40px 0; +} + +.badge { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 5px 5px; + border-radius: 4px; + font-size: 12px; + background: rgba(255, 255, 255, .1); + color: #ffffff; +} + +.panel-inactive .badge { + background: rgba(200, 200, 200, .1); + color: #999999; } \ No newline at end of file diff --git a/src/controller/CoreController.py b/src/controller/CoreController.py new file mode 100644 index 0000000..2b3cf11 --- /dev/null +++ b/src/controller/CoreController.py @@ -0,0 +1,22 @@ + +from flask import Flask, send_file, render_template_string, jsonify + +from src.interface.ObController import ObController + + +class CoreController(ObController): + + def register(self): + self._app.add_url_rule('/manifest.json', 'manifest', self.manifest, methods=['GET']) + self._app.add_url_rule('/favicon.ico', 'favicon', self.favicon, methods=['GET']) + + def manifest(self): + with open("{}/manifest.jinja.json".format(self.get_template_folder()), 'r') as file: + template_content = file.read() + + rendered_content = render_template_string(template_content) + + return self._app.response_class(rendered_content, mimetype='application/json') + + def favicon(self): + return send_file("{}/favicon.ico".format(self.get_web_folder()), mimetype='image/x-icon') \ No newline at end of file diff --git a/src/interface/ObController.py b/src/interface/ObController.py index 9362806..cee6660 100644 --- a/src/interface/ObController.py +++ b/src/interface/ObController.py @@ -27,6 +27,12 @@ class ObController(abc.ABC): return self._plugin + def get_template_folder(self): + return self._web_server.get_template_folder() + + def get_web_folder(self): + return self._web_server.get_web_folder() + def reload_web_server(self): self._web_server.reload() diff --git a/src/manager/ScreenManager.py b/src/manager/ScreenManager.py index fc36143..811ff65 100644 --- a/src/manager/ScreenManager.py +++ b/src/manager/ScreenManager.py @@ -4,6 +4,7 @@ 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.manager.UserManager import UserManager from src.service.ModelManager import ModelManager @@ -18,24 +19,21 @@ class ScreenManager(ModelManager): "port" ] - def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager): - super().__init__(lang_manager, database_manager) + def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): + super().__init__(lang_manager, database_manager, user_manager) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) - @staticmethod - def hydrate_object(raw_screen: dict, id: Optional[str] = None) -> Screen: + def hydrate_object(self, raw_screen: dict, id: Optional[str] = None) -> Screen: if id: raw_screen['id'] = id return Screen(**raw_screen) - @staticmethod - def hydrate_dict(raw_screens: dict) -> List[Screen]: - return [ScreenManager.hydrate_object(raw_screen, raw_id) for raw_id, raw_screen in raw_screens.items()] + def hydrate_dict(self, raw_screens: dict) -> List[Screen]: + return [self.hydrate_object(raw_screen, raw_id) for raw_id, raw_screen in raw_screens.items()] - @staticmethod - def hydrate_list(raw_screens: list) -> List[Screen]: - return [ScreenManager.hydrate_object(raw_screen) for raw_screen in raw_screens] + def hydrate_list(self, raw_screens: list) -> List[Screen]: + return [self.hydrate_object(raw_screen) for raw_screen in raw_screens] def get(self, id: str) -> Optional[Screen]: try: @@ -59,10 +57,10 @@ class ScreenManager(ModelManager): if isinstance(raw_screens, dict): if sort: - return sorted(ScreenManager.hydrate_dict(raw_screens), key=lambda x: x.position) - return ScreenManager.hydrate_dict(raw_screens) + return sorted(self.hydrate_dict(raw_screens), key=lambda x: x.position) + return self.hydrate_dict(raw_screens) - return ScreenManager.hydrate_list(sorted(raw_screens, key=lambda x: x['position']) if sort else raw_screens) + return self.hydrate_list(sorted(raw_screens, key=lambda x: x['position']) if sort else raw_screens) def get_enabled_screens(self) -> List[Screen]: return [screen for screen in self.get_all(sort=True) if screen.enabled] diff --git a/src/manager/SlideManager.py b/src/manager/SlideManager.py index 364fbac..86a0875 100644 --- a/src/manager/SlideManager.py +++ b/src/manager/SlideManager.py @@ -8,6 +8,7 @@ from src.model.enum.SlideType import SlideType from src.utils import get_optional_string, get_yt_video_id from src.manager.DatabaseManager import DatabaseManager from src.manager.LangManager import LangManager +from src.manager.UserManager import UserManager from src.service.ModelManager import ModelManager @@ -22,27 +23,33 @@ class SlideManager(ModelManager): "position", "location", "cron_schedule", - "cron_schedule_end" + "cron_schedule_end", + "created_by", + "updated_by", + "created_at", + "updated_at" ] - def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager): - super().__init__(lang_manager, database_manager) + def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): + super().__init__(lang_manager, database_manager, user_manager) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) - @staticmethod - def hydrate_object(raw_slide: dict, id: str = None) -> Slide: + def hydrate_object(self, raw_slide: dict, id: str = None) -> Slide: if id: raw_slide['id'] = id + [raw_slide, user_tracker_edits] = self.user_manager.initialize_user_trackers(raw_slide) + + if len(user_tracker_edits) > 0: + self._db.update_by_id(raw_slide['id'], user_tracker_edits) + return Slide(**raw_slide) - @staticmethod - def hydrate_dict(raw_slides: dict) -> List[Slide]: - return [SlideManager.hydrate_object(raw_slide, raw_id) for raw_id, raw_slide in raw_slides.items()] + def hydrate_dict(self, raw_slides: dict) -> List[Slide]: + return [self.hydrate_object(raw_slide, raw_id) for raw_id, raw_slide in raw_slides.items()] - @staticmethod - def hydrate_list(raw_slides: list) -> List[Slide]: - return [SlideManager.hydrate_object(raw_slide) for raw_slide in raw_slides] + def hydrate_list(self, raw_slides: list) -> List[Slide]: + return [self.hydrate_object(raw_slide) for raw_slide in raw_slides] def get(self, id: str) -> Optional[Slide]: try: @@ -66,10 +73,10 @@ class SlideManager(ModelManager): if isinstance(raw_slides, dict): if sort: - return sorted(SlideManager.hydrate_dict(raw_slides), key=lambda x: x.position) - return SlideManager.hydrate_dict(raw_slides) + return sorted(self.hydrate_dict(raw_slides), key=lambda x: x.position) + return self.hydrate_dict(raw_slides) - return SlideManager.hydrate_list(sorted(raw_slides, key=lambda x: x['position']) if sort else raw_slides) + return self.hydrate_list(sorted(raw_slides, key=lambda x: x['position']) if sort else raw_slides) def get_enabled_slides(self) -> List[Slide]: return [slide for slide in self.get_all(sort=True) if slide.enabled] @@ -77,8 +84,33 @@ class SlideManager(ModelManager): def get_disabled_slides(self) -> List[Slide]: return [slide for slide in self.get_all(sort=True) if not slide.enabled] + def pre_add(self, slide: Dict) -> Dict: + self.user_manager.track_user_on_create(slide) + self.user_manager.track_user_on_update(slide) + return slide + + def pre_update(self, slide: Dict) -> Dict: + self.user_manager.track_user_on_update(slide) + return slide + + def pre_delete(self, slide_id: str) -> str: + return slide_id + + def post_add(self, slide_id: str) -> str: + return slide_id + + def post_update(self, slide_id: str) -> str: + return slide_id + + def post_updates(self): + pass + + def post_delete(self, slide_id: str) -> str: + return slide_id + def update_enabled(self, id: str, enabled: bool) -> None: - self._db.update_by_id(id, {"enabled": enabled, "position": 999}) + self._db.update_by_id(id, self.pre_update({"enabled": enabled, "position": 999})) + self.post_update(id) def update_positions(self, positions: list) -> None: for slide_id, slide_position in positions.items(): @@ -103,7 +135,8 @@ class SlideManager(ModelManager): if slide.type == SlideType.YOUTUBE: form['location'] = get_yt_video_id(form['location']) - self._db.update_by_id(id, form) + self._db.update_by_id(id, self.pre_update(form)) + self.post_update(id) def add_form(self, slide: Union[Slide, Dict]) -> None: form = slide @@ -115,7 +148,8 @@ class SlideManager(ModelManager): if form['type'] == SlideType.YOUTUBE: form['location'] = get_yt_video_id(form['location']) - self._db.add(form) + self._db.add(self.pre_add(form)) + self.post_add(slide.id) def delete(self, id: str) -> None: slide = self.get(id) @@ -127,7 +161,9 @@ class SlideManager(ModelManager): except FileNotFoundError: pass + self.pre_delete(id) self._db.delete_by_id(id) + self.post_delete(id) def to_dict(self, slides: List[Slide]) -> List[Dict]: return [slide.to_dict() for slide in slides] diff --git a/src/manager/UserManager.py b/src/manager/UserManager.py index 82de2be..80ea971 100644 --- a/src/manager/UserManager.py +++ b/src/manager/UserManager.py @@ -1,14 +1,15 @@ import hashlib +import time from pysondb.errors import IdDoesNotExistError from typing import Dict, Optional, List, Tuple, Union +from flask_login import current_user from src.model.entity.User import User from src.manager.DatabaseManager import DatabaseManager from src.manager.LangManager import LangManager -from src.service.ModelManager import ModelManager -class UserManager(ModelManager): +class UserManager: TABLE_NAME = "user" TABLE_MODEL = [ @@ -17,24 +18,40 @@ class UserManager(ModelManager): "enabled" ] - def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager): - super().__init__(lang_manager, database_manager) + def __init__(self, database_manager: DatabaseManager): self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) + self._user_map = {} + self.reload() + + def reload(self) -> None: + self._user_map = self.prepare_map() + + def map(self) -> dict: + return self._user_map + + def prepare_map(self) -> Dict[str, User]: + return self.list_to_map(self.get_all()) @staticmethod - def hydrate_object(raw_user: dict, id: Optional[str] = None) -> User: + def list_to_map(list: List[User]) -> Dict[str, User]: + user_map = {} + + for user in list: + user_map[user.id] = user + + return user_map + + def hydrate_object(self, raw_user: dict, id: Optional[str] = None) -> User: if id: raw_user['id'] = id return User(**raw_user) - @staticmethod - def hydrate_dict(raw_users: dict) -> List[User]: - return [UserManager.hydrate_object(raw_user, raw_id) for raw_id, raw_user in raw_users.items()] + def hydrate_dict(self, raw_users: dict) -> List[User]: + return [self.hydrate_object(raw_user, raw_id) for raw_id, raw_user in raw_users.items()] - @staticmethod - def hydrate_list(raw_users: list) -> List[User]: - return [UserManager.hydrate_object(raw_user) for raw_user in raw_users] + def hydrate_list(self, raw_users: list) -> List[User]: + return [self.hydrate_object(raw_user) for raw_user in raw_users] def get(self, id: str) -> Optional[User]: try: @@ -59,15 +76,33 @@ class UserManager(ModelManager): def count_all_enabled(self): return len(self.get_enabled_users()) + def track_user_created(self, id_or_entity: Optional[str]) -> User: + return self.track_user_action(id_or_entity, 'created_by') + + def track_user_updated(self, id_or_entity: Optional[str]) -> User: + return self.track_user_action(id_or_entity, 'updated_by') + + def track_user_action(self, id_or_entity: Optional[str], attribute: Optional[str] = 'created_by') -> User: + if not isinstance(id_or_entity, str): + id_or_entity = getattr(id_or_entity, attribute) + id_or_entity = str(id_or_entity) + + user_map = self.map() + + if id_or_entity in user_map: + return user_map[id_or_entity] + + return User(username="") + def get_all(self, sort: bool = False) -> List[User]: raw_users = self._db.get_all() if isinstance(raw_users, dict): if sort: - return sorted(UserManager.hydrate_dict(raw_users), key=lambda x: x.username) - return UserManager.hydrate_dict(raw_users) + return sorted(self.hydrate_dict(raw_users), key=lambda x: x.username) + return self.hydrate_dict(raw_users) - return UserManager.hydrate_list(sorted(raw_users, key=lambda x: x['username']) if sort else raw_users) + return self.hydrate_list(sorted(raw_users, key=lambda x: x['username']) if sort else raw_users) def get_enabled_users(self) -> List[User]: return [user for user in self.get_all(sort=True) if user.enabled] @@ -75,8 +110,30 @@ class UserManager(ModelManager): def get_disabled_users(self) -> List[User]: return [user for user in self.get_all(sort=True) if not user.enabled] + def pre_add(self, user: Dict) -> Dict: + return user + + def pre_update(self, user: Dict) -> Dict: + return user + + def pre_delete(self, user_id: str) -> str: + return user_id + + def post_add(self, user_id: str) -> str: + self.reload() + return user_id + + def post_update(self, user_id: str) -> str: + self.reload() + return user_id + + def post_delete(self, user_id: str) -> str: + self.reload() + return user_id + def update_enabled(self, id: str, enabled: bool) -> None: - self._db.update_by_id(id, {"enabled": enabled}) + self._db.update_by_id(id, self.pre_update({"enabled": enabled})) + self.post_update(id) def update_form(self, id: str, username: str, password: Optional[str]) -> None: form = {"username": username} @@ -84,7 +141,8 @@ class UserManager(ModelManager): if password is not None and password: form['password'] = self.encode_password(password) - self._db.update_by_id(id, form) + self._db.update_by_id(id, self.pre_update(form)) + self.post_update(id) def add_form(self, user: Union[User, Dict]) -> None: form = user @@ -95,13 +153,57 @@ class UserManager(ModelManager): form['password'] = self.encode_password(form['password']) - self._db.add(form) + self._db.add(self.pre_add(form)) + self.post_add(user.id) def delete(self, id: str) -> None: + self.pre_delete(id) self._db.delete_by_id(id) + self.post_delete(id) def to_dict(self, users: List[User]) -> List[Dict]: return [user.to_dict() for user in users] def encode_password(self, password: str) -> str: - return hashlib.sha256(password.encode()).hexdigest() \ No newline at end of file + return hashlib.sha256(password.encode()).hexdigest() + + def initialize_user_trackers(self, object: dict) -> dict: + edits = {} + user_id = self.get_logged_user("id") + now = time.time() + + if 'created_by' not in object or not object['created_by']: + object["created_by"] = user_id + edits['created_by'] = object['created_by'] + + if 'updated_by' not in object or not object['updated_by']: + object["updated_by"] = user_id + edits['updated_by'] = object['updated_by'] + + if 'created_at' not in object or not object['created_at']: + object["created_at"] = now + edits['created_at'] = object['created_at'] + + if 'updated_at' not in object or not object['updated_at']: + object["updated_at"] = now + edits['updated_at'] = object['updated_at'] + + return [object, edits] + + def track_user_on_create(self, object: dict) -> dict: + object["created_at"] = time.time() + object["created_by"] = self.get_logged_user("id") + return object + + def track_user_on_update(self, object: dict) -> dict: + object["updated_at"] = time.time() + object["updated_by"] = self.get_logged_user("id") + return object + + def get_logged_user(self, attribute: Optional[str]) -> Union[User, None, str]: + if current_user and current_user.is_authenticated: + if attribute: + return getattr(current_user, attribute) + return current_user + + return None diff --git a/src/manager/VariableManager.py b/src/manager/VariableManager.py index 58ddd2d..9404cfc 100644 --- a/src/manager/VariableManager.py +++ b/src/manager/VariableManager.py @@ -4,6 +4,7 @@ from pysondb.errors import IdDoesNotExistError from src.manager.DatabaseManager import DatabaseManager from src.manager.LangManager import LangManager +from src.manager.UserManager import UserManager from src.service.ModelManager import ModelManager from src.model.entity.Variable import Variable from src.model.entity.Selectable import Selectable @@ -36,8 +37,8 @@ class VariableManager(ModelManager): "value" ] - def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager): - super().__init__(lang_manager, database_manager) + def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): + super().__init__(lang_manager, database_manager, user_manager) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) self._var_map = {} self.reload() @@ -126,12 +127,12 @@ class VariableManager(ModelManager): for default_var in default_vars: self.set_variable(**default_var) - self._var_map = self.prepare_variable_map() + self._var_map = self.prepare_map() def map(self) -> dict: return self._var_map - def prepare_variable_map(self) -> Dict[str, Variable]: + def prepare_map(self) -> Dict[str, Variable]: return self.list_to_map(self.get_all()) @staticmethod @@ -143,8 +144,7 @@ class VariableManager(ModelManager): return var_map - @staticmethod - def hydrate_object(raw_variable: dict, id: Optional[str] = None) -> Variable: + def hydrate_object(self, raw_variable: dict, id: Optional[str] = None) -> Variable: if id: raw_variable['id'] = id @@ -153,13 +153,11 @@ class VariableManager(ModelManager): return Variable(**raw_variable) - @staticmethod - def hydrate_dict(raw_variables: dict) -> List[Variable]: - return [VariableManager.hydrate_object(raw_variable, raw_id) for raw_id, raw_variable in raw_variables.items()] + def hydrate_dict(self, raw_variables: dict) -> List[Variable]: + return [self.hydrate_object(raw_variable, raw_id) for raw_id, raw_variable in raw_variables.items()] - @staticmethod - def hydrate_list(raw_variables: list) -> List[Variable]: - return [VariableManager.hydrate_object(raw_variable) for raw_variable in raw_variables] + def hydrate_list(self, raw_variables: list) -> List[Variable]: + return [self.hydrate_object(raw_variable) for raw_variable in raw_variables] def get(self, id: str) -> Optional[Variable]: try: @@ -192,9 +190,9 @@ class VariableManager(ModelManager): raw_variables = self._db.get_all() if isinstance(raw_variables, dict): - return VariableManager.hydrate_dict(raw_variables) + return self.hydrate_dict(raw_variables) - return VariableManager.hydrate_list(raw_variables) + return self.hydrate_list(raw_variables) def get_editable_variables(self, plugin: bool = True, sort: Optional[str] = None) -> List[Variable]: query = lambda v: (not plugin and not isinstance(v['plugin'], str)) or (plugin and isinstance(v['plugin'], str)) diff --git a/src/model/entity/Slide.py b/src/model/entity/Slide.py index a735be8..43607c4 100644 --- a/src/model/entity/Slide.py +++ b/src/model/entity/Slide.py @@ -1,4 +1,5 @@ import json +import time from typing import Optional, Union from src.model.enum.SlideType import SlideType, SlideInputType @@ -7,7 +8,7 @@ from src.utils import str_to_enum class Slide: - def __init__(self, location: str = '', duration: int = 3, type: Union[SlideType, str] = SlideType.URL, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[str] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None): + def __init__(self, location: str = '', duration: int = 3, type: Union[SlideType, str] = SlideType.URL, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[str] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): self._id = id if id else None self._location = location self._duration = duration @@ -17,6 +18,10 @@ class Slide: self._position = position self._cron_schedule = cron_schedule self._cron_schedule_end = cron_schedule_end + self._created_by = int(created_by if created_by else time.time()) + self._updated_by = updated_by + self._created_at = int(created_at if created_at else time.time()) + self._updated_at = int(updated_at if updated_at else time.time()) @property def id(self) -> Optional[str]: @@ -30,6 +35,38 @@ class Slide: def location(self, value: str): self._location = value + @property + def created_by(self) -> str: + return self._created_by + + @created_by.setter + def created_by(self, value: str): + self._created_by = value + + @property + def updated_by(self) -> str: + return self._updated_by + + @updated_by.setter + def updated_by(self, value: str): + self._updated_by = value + + @property + def created_at(self) -> int: + return self._created_at + + @created_at.setter + def created_at(self, value: int): + self._created_at = value + + @property + def updated_at(self) -> int: + return self._updated_at + + @updated_at.setter + def updated_at(self, value: int): + self._updated_at = value + @property def type(self) -> SlideType: return self._type @@ -95,6 +132,10 @@ class Slide: f"duration='{self.duration}',\n" \ f"position='{self.position}',\n" \ f"location='{self.location}',\n" \ + f"created_by='{self.created_by}',\n" \ + f"updated_by='{self.updated_by}',\n" \ + f"created_at='{self.created_at}',\n" \ + f"updated_at='{self.updated_at}',\n" \ f"cron_schedule='{self.cron_schedule}',\n" \ f"cron_schedule_end='{self.cron_schedule_end}',\n" \ f")" @@ -111,6 +152,10 @@ class Slide: "type": self.type.value, "duration": self.duration, "location": self.location, + "created_by": self.created_by, + "updated_by": self.updated_by, + "created_at": self.created_at, + "updated_at": self.updated_at, "cron_schedule": self.cron_schedule, "cron_schedule_end": self.cron_schedule_end, } diff --git a/src/service/ModelManager.py b/src/service/ModelManager.py index 5ac041b..1ae541c 100644 --- a/src/service/ModelManager.py +++ b/src/service/ModelManager.py @@ -1,15 +1,18 @@ from enum import Enum -from typing import Union, Dict +from typing import Union, Dict, Optional +from src.model.entity.User import User from src.manager.LangManager import LangManager +from src.manager.UserManager import UserManager from src.manager.DatabaseManager import DatabaseManager class ModelManager: - def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager): + def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): self._lang_manager = lang_manager self._database_manager = database_manager + self._user_manager = user_manager def t(self, token) -> Union[Dict, str]: return self.lang_manager.translate(token) @@ -21,3 +24,9 @@ class ModelManager: @property def database_manager(self) -> DatabaseManager: return self._database_manager + + @property + def user_manager(self) -> UserManager: + return self._user_manager + + diff --git a/src/service/ModelStore.py b/src/service/ModelStore.py index 57b5c7c..38bb80c 100644 --- a/src/service/ModelStore.py +++ b/src/service/ModelStore.py @@ -16,7 +16,8 @@ class ModelStore: self._database_manager = DatabaseManager() # Dynamics - self._variable_manager = VariableManager(lang_manager=self._lang_manager, database_manager=self._database_manager) + self._user_manager = UserManager(database_manager=self._database_manager) + self._variable_manager = VariableManager(lang_manager=self._lang_manager, database_manager=self._database_manager, user_manager=self._user_manager) self._lang_manager.set_lang(self.variable().map().get('lang').as_string()) # Core @@ -24,9 +25,8 @@ class ModelStore: self._logging_manager = LoggingManager(config_manager=self._config_manager) # Model - self._user_manager = UserManager(lang_manager=self._lang_manager, database_manager=self._database_manager) - 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._screen_manager = ScreenManager(lang_manager=self._lang_manager, database_manager=self._database_manager, user_manager=self._user_manager) + self._slide_manager = SlideManager(lang_manager=self._lang_manager, database_manager=self._database_manager, user_manager=self._user_manager) self._variable_manager.reload() def logging(self) -> LoggingManager: diff --git a/src/service/TemplateRenderer.py b/src/service/TemplateRenderer.py index 3975e3e..c680099 100644 --- a/src/service/TemplateRenderer.py +++ b/src/service/TemplateRenderer.py @@ -27,6 +27,8 @@ class TemplateRenderer: STATIC_PREFIX="/{}/{}/".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS), FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(), AUTH_ENABLED=self._model_store.variable().map().get('auth_enabled').as_bool(), + track_created=self._model_store.user().track_user_created, + track_updated=self._model_store.user().track_user_updated, VERSION=self._model_store.config().map().get('version'), LANG=self._model_store.variable().map().get('lang').as_string(), HOOK=self._render_hook, diff --git a/src/service/WebServer.py b/src/service/WebServer.py index 951a15c..e1ab350 100644 --- a/src/service/WebServer.py +++ b/src/service/WebServer.py @@ -15,6 +15,7 @@ from src.controller.FleetController import FleetController from src.controller.AuthController import AuthController from src.controller.SysinfoController import SysinfoController from src.controller.SettingsController import SettingsController +from src.controller.CoreController import CoreController from src.constant.WebDirConstant import WebDirConstant @@ -50,17 +51,20 @@ class WebServer: def get_app(self): return self._app - def _get_template_folder(self) -> str: + def get_template_folder(self) -> str: return "{}/{}".format(self._project_dir, WebDirConstant.FOLDER_TEMPLATES) - def _get_static_folder(self) -> str: + def get_static_folder(self) -> str: return "{}/{}".format(self._project_dir, WebDirConstant.FOLDER_STATIC) + def get_web_folder(self) -> str: + return "{}/{}/{}".format(self._project_dir, WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS) + def _setup_flask_app(self) -> None: self._app = Flask( __name__, - template_folder=self._get_template_folder(), - static_folder=self._get_static_folder(), + template_folder=self.get_template_folder(), + static_folder=self.get_static_folder(), ) self._app.config['UPLOAD_FOLDER'] = "{}/{}".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_UPLOADS) @@ -96,6 +100,7 @@ class WebServer: return decorated_function + CoreController(self, self._app, auth_required, self._model_store, self._template_renderer) PlayerController(self, self._app, auth_required, self._model_store, self._template_renderer) SlideshowController(self, self._app, auth_required, self._model_store, self._template_renderer) SettingsController(self, self._app, auth_required, self._model_store, self._template_renderer) @@ -111,5 +116,5 @@ class WebServer: def _setup_web_errors(self) -> None: @self._app.errorhandler(404) def not_found(e): - return send_from_directory(self._get_template_folder(), 'core/error404.html'), 404 + return send_from_directory(self.get_template_folder(), 'core/error404.html'), 404 diff --git a/views/base.jinja.html b/views/base.jinja.html index 0bba1ab..d97b3bb 100755 --- a/views/base.jinja.html +++ b/views/base.jinja.html @@ -6,7 +6,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -81,8 +81,12 @@ {% if AUTH_ENABLED %} -
  • - +
  • +
    + + {{ current_user.username }} +
    +
  • diff --git a/data/www/manifest.json b/views/manifest.jinja.json similarity index 53% rename from data/www/manifest.json rename to views/manifest.jinja.json index 6c3aeda..a18c267 100755 --- a/data/www/manifest.json +++ b/views/manifest.jinja.json @@ -1,38 +1,38 @@ { - "name": "App", + "name": "Obscreen", "icons": [ { - "src": "\/favicon/android-icon-36x36.png", + "src": "{{ STATIC_PREFIX }}favicon/android-icon-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { - "src": "\/favicon/android-icon-48x48.png", + "src": "{{ STATIC_PREFIX }}favicon/android-icon-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { - "src": "\/favicon/android-icon-72x72.png", + "src": "{{ STATIC_PREFIX }}favicon/android-icon-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { - "src": "\/favicon/android-icon-96x96.png", + "src": "{{ STATIC_PREFIX }}favicon/android-icon-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { - "src": "\/favicon/android-icon-144x144.png", + "src": "{{ STATIC_PREFIX }}favicon/android-icon-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { - "src": "\/favicon/android-icon-192x192.png", + "src": "{{ STATIC_PREFIX }}favicon/android-icon-192x192.png", "sizes": "192x192", "type": "image\/png", "density": "4.0" diff --git a/views/slideshow/component/table.jinja.html b/views/slideshow/component/table.jinja.html index 64c353b..08c1d7e 100644 --- a/views/slideshow/component/table.jinja.html +++ b/views/slideshow/component/table.jinja.html @@ -2,9 +2,14 @@ {{ l.slideshow_slide_panel_th_name }} - {{ l.slideshow_slide_panel_th_duration }} + {% if AUTH_ENABLED %} + + + + {% endif %} {{ l.slideshow_slide_panel_th_enabled }} {{ l.slideshow_slide_panel_th_cron_scheduled }} + {{ l.slideshow_slide_panel_th_duration }} {{ l.slideshow_slide_panel_th_activity }} @@ -35,9 +40,13 @@ {{ slide.name }} - - {{ slide.duration }} {{ l.slideshow_slide_panel_th_duration_unit }} - + {% if AUTH_ENABLED %} + +
    + {{ track_updated(slide).username }} +
    + + {% endif %}