add user tracking on slides + add favicon/manifest root urls

This commit is contained in:
jr-k 2024-05-13 01:14:41 +02:00
parent 785c4c04ef
commit 173a4d2417
15 changed files with 374 additions and 89 deletions

View File

@ -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 {
@ -657,3 +686,20 @@ footer .version {
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +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="shortcut icon" href="/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">
@ -20,7 +20,7 @@
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_PREFIX }}favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="{{ STATIC_PREFIX }}favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_PREFIX }}favicon/favicon-16x16.png">
<link rel="manifest" href="{{ STATIC_PREFIX }}/manifest.json">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#692fbd">
<meta name="msapplication-TileImage" content="{{ STATIC_PREFIX }}favicon/ms-icon-144x144.png">
<meta name="theme-color" content="#692fbd">
@ -81,8 +81,12 @@
</a>
</li>
{% if AUTH_ENABLED %}
<li>
<a href="{{ url_for('logout') }}" title="{{ l.logout }}">
<li class="user-menu">
<div class="username">
<i class="fa fa-user"></i>
{{ current_user.username }}
</div>
<a href="{{ url_for('logout') }}" title="{{ l.logout }}" class="logout">
<i class="fa fa-right-from-bracket"></i>
</a>
</li>

View File

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

View File

@ -2,9 +2,14 @@
<thead>
<tr>
<th>{{ l.slideshow_slide_panel_th_name }}</th>
<th class="tac">{{ l.slideshow_slide_panel_th_duration }}</th>
{% if AUTH_ENABLED %}
<th class="tac">
<i class="fa fa-user"></i>
</th>
{% endif %}
<th class="tac">{{ l.slideshow_slide_panel_th_enabled }}</th>
<th class="">{{ l.slideshow_slide_panel_th_cron_scheduled }}</th>
<th class="tac">{{ l.slideshow_slide_panel_th_duration }}</th>
<th class="tac">{{ l.slideshow_slide_panel_th_activity }}</th>
</tr>
</thead>
@ -35,9 +40,13 @@
{{ slide.name }}
</div>
</td>
<td class="tac">
{{ slide.duration }} {{ l.slideshow_slide_panel_th_duration_unit }}
</td>
{% if AUTH_ENABLED %}
<td class="tac">
<div class="badge">
{{ track_updated(slide).username }}
</div>
</td>
{% endif %}
<td class="tac">
<label class="pure-material-switch">
<input type="checkbox" {% if slide.enabled %}checked="checked"{% endif %}><span></span>
@ -59,6 +68,9 @@
🔄 {{ l.slideshow_slide_panel_td_cron_scheduled_loop }}
{% endif %}
</td>
<td class="tac">
{{ slide.duration }} {{ l.slideshow_slide_panel_th_duration_unit }}
</td>
<td class="actions tac">
<a href="javascript:void(0);" class="item-edit slide-edit">
<i class="fa fa-pencil"></i>