add user tracking on slides + add favicon/manifest root urls
This commit is contained in:
parent
785c4c04ef
commit
173a4d2417
@ -106,7 +106,36 @@ header nav ul {
|
|||||||
|
|
||||||
header nav ul li {
|
header nav ul li {
|
||||||
margin: 0 15px;
|
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 {
|
header nav ul li a {
|
||||||
@ -657,3 +686,20 @@ footer .version {
|
|||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
margin: 0 0 40px 0;
|
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;
|
||||||
|
}
|
||||||
22
src/controller/CoreController.py
Normal file
22
src/controller/CoreController.py
Normal 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')
|
||||||
@ -27,6 +27,12 @@ class ObController(abc.ABC):
|
|||||||
|
|
||||||
return self._plugin
|
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):
|
def reload_web_server(self):
|
||||||
self._web_server.reload()
|
self._web_server.reload()
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from typing import Dict, Optional, List, Tuple, Union
|
|||||||
from src.model.entity.Screen import Screen
|
from src.model.entity.Screen import Screen
|
||||||
from src.manager.DatabaseManager import DatabaseManager
|
from src.manager.DatabaseManager import DatabaseManager
|
||||||
from src.manager.LangManager import LangManager
|
from src.manager.LangManager import LangManager
|
||||||
|
from src.manager.UserManager import UserManager
|
||||||
from src.service.ModelManager import ModelManager
|
from src.service.ModelManager import ModelManager
|
||||||
|
|
||||||
|
|
||||||
@ -18,24 +19,21 @@ class ScreenManager(ModelManager):
|
|||||||
"port"
|
"port"
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
|
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager):
|
||||||
super().__init__(lang_manager, database_manager)
|
super().__init__(lang_manager, database_manager, user_manager)
|
||||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_object(self, raw_screen: dict, id: Optional[str] = None) -> Screen:
|
||||||
def hydrate_object(raw_screen: dict, id: Optional[str] = None) -> Screen:
|
|
||||||
if id:
|
if id:
|
||||||
raw_screen['id'] = id
|
raw_screen['id'] = id
|
||||||
|
|
||||||
return Screen(**raw_screen)
|
return Screen(**raw_screen)
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_dict(self, raw_screens: dict) -> List[Screen]:
|
||||||
def hydrate_dict(raw_screens: dict) -> List[Screen]:
|
return [self.hydrate_object(raw_screen, raw_id) for raw_id, raw_screen in raw_screens.items()]
|
||||||
return [ScreenManager.hydrate_object(raw_screen, raw_id) for raw_id, raw_screen in raw_screens.items()]
|
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_list(self, raw_screens: list) -> List[Screen]:
|
||||||
def hydrate_list(raw_screens: list) -> List[Screen]:
|
return [self.hydrate_object(raw_screen) for raw_screen in raw_screens]
|
||||||
return [ScreenManager.hydrate_object(raw_screen) for raw_screen in raw_screens]
|
|
||||||
|
|
||||||
def get(self, id: str) -> Optional[Screen]:
|
def get(self, id: str) -> Optional[Screen]:
|
||||||
try:
|
try:
|
||||||
@ -59,10 +57,10 @@ class ScreenManager(ModelManager):
|
|||||||
|
|
||||||
if isinstance(raw_screens, dict):
|
if isinstance(raw_screens, dict):
|
||||||
if sort:
|
if sort:
|
||||||
return sorted(ScreenManager.hydrate_dict(raw_screens), key=lambda x: x.position)
|
return sorted(self.hydrate_dict(raw_screens), key=lambda x: x.position)
|
||||||
return ScreenManager.hydrate_dict(raw_screens)
|
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]:
|
def get_enabled_screens(self) -> List[Screen]:
|
||||||
return [screen for screen in self.get_all(sort=True) if screen.enabled]
|
return [screen for screen in self.get_all(sort=True) if screen.enabled]
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from src.model.enum.SlideType import SlideType
|
|||||||
from src.utils import get_optional_string, get_yt_video_id
|
from src.utils import get_optional_string, get_yt_video_id
|
||||||
from src.manager.DatabaseManager import DatabaseManager
|
from src.manager.DatabaseManager import DatabaseManager
|
||||||
from src.manager.LangManager import LangManager
|
from src.manager.LangManager import LangManager
|
||||||
|
from src.manager.UserManager import UserManager
|
||||||
from src.service.ModelManager import ModelManager
|
from src.service.ModelManager import ModelManager
|
||||||
|
|
||||||
|
|
||||||
@ -22,27 +23,33 @@ class SlideManager(ModelManager):
|
|||||||
"position",
|
"position",
|
||||||
"location",
|
"location",
|
||||||
"cron_schedule",
|
"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):
|
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager):
|
||||||
super().__init__(lang_manager, database_manager)
|
super().__init__(lang_manager, database_manager, user_manager)
|
||||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_object(self, raw_slide: dict, id: str = None) -> Slide:
|
||||||
def hydrate_object(raw_slide: dict, id: str = None) -> Slide:
|
|
||||||
if id:
|
if id:
|
||||||
raw_slide['id'] = 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)
|
return Slide(**raw_slide)
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_dict(self, raw_slides: dict) -> List[Slide]:
|
||||||
def hydrate_dict(raw_slides: dict) -> List[Slide]:
|
return [self.hydrate_object(raw_slide, raw_id) for raw_id, raw_slide in raw_slides.items()]
|
||||||
return [SlideManager.hydrate_object(raw_slide, raw_id) for raw_id, raw_slide in raw_slides.items()]
|
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_list(self, raw_slides: list) -> List[Slide]:
|
||||||
def hydrate_list(raw_slides: list) -> List[Slide]:
|
return [self.hydrate_object(raw_slide) for raw_slide in raw_slides]
|
||||||
return [SlideManager.hydrate_object(raw_slide) for raw_slide in raw_slides]
|
|
||||||
|
|
||||||
def get(self, id: str) -> Optional[Slide]:
|
def get(self, id: str) -> Optional[Slide]:
|
||||||
try:
|
try:
|
||||||
@ -66,10 +73,10 @@ class SlideManager(ModelManager):
|
|||||||
|
|
||||||
if isinstance(raw_slides, dict):
|
if isinstance(raw_slides, dict):
|
||||||
if sort:
|
if sort:
|
||||||
return sorted(SlideManager.hydrate_dict(raw_slides), key=lambda x: x.position)
|
return sorted(self.hydrate_dict(raw_slides), key=lambda x: x.position)
|
||||||
return SlideManager.hydrate_dict(raw_slides)
|
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]:
|
def get_enabled_slides(self) -> List[Slide]:
|
||||||
return [slide for slide in self.get_all(sort=True) if slide.enabled]
|
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]:
|
def get_disabled_slides(self) -> List[Slide]:
|
||||||
return [slide for slide in self.get_all(sort=True) if not slide.enabled]
|
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:
|
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:
|
def update_positions(self, positions: list) -> None:
|
||||||
for slide_id, slide_position in positions.items():
|
for slide_id, slide_position in positions.items():
|
||||||
@ -103,7 +135,8 @@ class SlideManager(ModelManager):
|
|||||||
if slide.type == SlideType.YOUTUBE:
|
if slide.type == SlideType.YOUTUBE:
|
||||||
form['location'] = get_yt_video_id(form['location'])
|
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:
|
def add_form(self, slide: Union[Slide, Dict]) -> None:
|
||||||
form = slide
|
form = slide
|
||||||
@ -115,7 +148,8 @@ class SlideManager(ModelManager):
|
|||||||
if form['type'] == SlideType.YOUTUBE:
|
if form['type'] == SlideType.YOUTUBE:
|
||||||
form['location'] = get_yt_video_id(form['location'])
|
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:
|
def delete(self, id: str) -> None:
|
||||||
slide = self.get(id)
|
slide = self.get(id)
|
||||||
@ -127,7 +161,9 @@ class SlideManager(ModelManager):
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
self.pre_delete(id)
|
||||||
self._db.delete_by_id(id)
|
self._db.delete_by_id(id)
|
||||||
|
self.post_delete(id)
|
||||||
|
|
||||||
def to_dict(self, slides: List[Slide]) -> List[Dict]:
|
def to_dict(self, slides: List[Slide]) -> List[Dict]:
|
||||||
return [slide.to_dict() for slide in slides]
|
return [slide.to_dict() for slide in slides]
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
import time
|
||||||
from pysondb.errors import IdDoesNotExistError
|
from pysondb.errors import IdDoesNotExistError
|
||||||
from typing import Dict, Optional, List, Tuple, Union
|
from typing import Dict, Optional, List, Tuple, Union
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
from src.model.entity.User import User
|
from src.model.entity.User import User
|
||||||
from src.manager.DatabaseManager import DatabaseManager
|
from src.manager.DatabaseManager import DatabaseManager
|
||||||
from src.manager.LangManager import LangManager
|
from src.manager.LangManager import LangManager
|
||||||
from src.service.ModelManager import ModelManager
|
|
||||||
|
|
||||||
|
|
||||||
class UserManager(ModelManager):
|
class UserManager:
|
||||||
|
|
||||||
TABLE_NAME = "user"
|
TABLE_NAME = "user"
|
||||||
TABLE_MODEL = [
|
TABLE_MODEL = [
|
||||||
@ -17,24 +18,40 @@ class UserManager(ModelManager):
|
|||||||
"enabled"
|
"enabled"
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
|
def __init__(self, database_manager: DatabaseManager):
|
||||||
super().__init__(lang_manager, database_manager)
|
|
||||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
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
|
@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:
|
if id:
|
||||||
raw_user['id'] = id
|
raw_user['id'] = id
|
||||||
|
|
||||||
return User(**raw_user)
|
return User(**raw_user)
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_dict(self, raw_users: dict) -> List[User]:
|
||||||
def hydrate_dict(raw_users: dict) -> List[User]:
|
return [self.hydrate_object(raw_user, raw_id) for raw_id, raw_user in raw_users.items()]
|
||||||
return [UserManager.hydrate_object(raw_user, raw_id) for raw_id, raw_user in raw_users.items()]
|
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_list(self, raw_users: list) -> List[User]:
|
||||||
def hydrate_list(raw_users: list) -> List[User]:
|
return [self.hydrate_object(raw_user) for raw_user in raw_users]
|
||||||
return [UserManager.hydrate_object(raw_user) for raw_user in raw_users]
|
|
||||||
|
|
||||||
def get(self, id: str) -> Optional[User]:
|
def get(self, id: str) -> Optional[User]:
|
||||||
try:
|
try:
|
||||||
@ -59,15 +76,33 @@ class UserManager(ModelManager):
|
|||||||
def count_all_enabled(self):
|
def count_all_enabled(self):
|
||||||
return len(self.get_enabled_users())
|
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]:
|
def get_all(self, sort: bool = False) -> List[User]:
|
||||||
raw_users = self._db.get_all()
|
raw_users = self._db.get_all()
|
||||||
|
|
||||||
if isinstance(raw_users, dict):
|
if isinstance(raw_users, dict):
|
||||||
if sort:
|
if sort:
|
||||||
return sorted(UserManager.hydrate_dict(raw_users), key=lambda x: x.username)
|
return sorted(self.hydrate_dict(raw_users), key=lambda x: x.username)
|
||||||
return UserManager.hydrate_dict(raw_users)
|
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]:
|
def get_enabled_users(self) -> List[User]:
|
||||||
return [user for user in self.get_all(sort=True) if user.enabled]
|
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]:
|
def get_disabled_users(self) -> List[User]:
|
||||||
return [user for user in self.get_all(sort=True) if not user.enabled]
|
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:
|
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:
|
def update_form(self, id: str, username: str, password: Optional[str]) -> None:
|
||||||
form = {"username": username}
|
form = {"username": username}
|
||||||
@ -84,7 +141,8 @@ class UserManager(ModelManager):
|
|||||||
if password is not None and password:
|
if password is not None and password:
|
||||||
form['password'] = self.encode_password(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:
|
def add_form(self, user: Union[User, Dict]) -> None:
|
||||||
form = user
|
form = user
|
||||||
@ -95,13 +153,57 @@ class UserManager(ModelManager):
|
|||||||
|
|
||||||
form['password'] = self.encode_password(form['password'])
|
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:
|
def delete(self, id: str) -> None:
|
||||||
|
self.pre_delete(id)
|
||||||
self._db.delete_by_id(id)
|
self._db.delete_by_id(id)
|
||||||
|
self.post_delete(id)
|
||||||
|
|
||||||
def to_dict(self, users: List[User]) -> List[Dict]:
|
def to_dict(self, users: List[User]) -> List[Dict]:
|
||||||
return [user.to_dict() for user in users]
|
return [user.to_dict() for user in users]
|
||||||
|
|
||||||
def encode_password(self, password: str) -> str:
|
def encode_password(self, password: str) -> str:
|
||||||
return hashlib.sha256(password.encode()).hexdigest()
|
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
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from pysondb.errors import IdDoesNotExistError
|
|||||||
|
|
||||||
from src.manager.DatabaseManager import DatabaseManager
|
from src.manager.DatabaseManager import DatabaseManager
|
||||||
from src.manager.LangManager import LangManager
|
from src.manager.LangManager import LangManager
|
||||||
|
from src.manager.UserManager import UserManager
|
||||||
from src.service.ModelManager import ModelManager
|
from src.service.ModelManager import ModelManager
|
||||||
from src.model.entity.Variable import Variable
|
from src.model.entity.Variable import Variable
|
||||||
from src.model.entity.Selectable import Selectable
|
from src.model.entity.Selectable import Selectable
|
||||||
@ -36,8 +37,8 @@ class VariableManager(ModelManager):
|
|||||||
"value"
|
"value"
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager):
|
def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager):
|
||||||
super().__init__(lang_manager, database_manager)
|
super().__init__(lang_manager, database_manager, user_manager)
|
||||||
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
|
||||||
self._var_map = {}
|
self._var_map = {}
|
||||||
self.reload()
|
self.reload()
|
||||||
@ -126,12 +127,12 @@ class VariableManager(ModelManager):
|
|||||||
for default_var in default_vars:
|
for default_var in default_vars:
|
||||||
self.set_variable(**default_var)
|
self.set_variable(**default_var)
|
||||||
|
|
||||||
self._var_map = self.prepare_variable_map()
|
self._var_map = self.prepare_map()
|
||||||
|
|
||||||
def map(self) -> dict:
|
def map(self) -> dict:
|
||||||
return self._var_map
|
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())
|
return self.list_to_map(self.get_all())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -143,8 +144,7 @@ class VariableManager(ModelManager):
|
|||||||
|
|
||||||
return var_map
|
return var_map
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_object(self, raw_variable: dict, id: Optional[str] = None) -> Variable:
|
||||||
def hydrate_object(raw_variable: dict, id: Optional[str] = None) -> Variable:
|
|
||||||
if id:
|
if id:
|
||||||
raw_variable['id'] = id
|
raw_variable['id'] = id
|
||||||
|
|
||||||
@ -153,13 +153,11 @@ class VariableManager(ModelManager):
|
|||||||
|
|
||||||
return Variable(**raw_variable)
|
return Variable(**raw_variable)
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_dict(self, raw_variables: dict) -> List[Variable]:
|
||||||
def hydrate_dict(raw_variables: dict) -> List[Variable]:
|
return [self.hydrate_object(raw_variable, raw_id) for raw_id, raw_variable in raw_variables.items()]
|
||||||
return [VariableManager.hydrate_object(raw_variable, raw_id) for raw_id, raw_variable in raw_variables.items()]
|
|
||||||
|
|
||||||
@staticmethod
|
def hydrate_list(self, raw_variables: list) -> List[Variable]:
|
||||||
def hydrate_list(raw_variables: list) -> List[Variable]:
|
return [self.hydrate_object(raw_variable) for raw_variable in raw_variables]
|
||||||
return [VariableManager.hydrate_object(raw_variable) for raw_variable in raw_variables]
|
|
||||||
|
|
||||||
def get(self, id: str) -> Optional[Variable]:
|
def get(self, id: str) -> Optional[Variable]:
|
||||||
try:
|
try:
|
||||||
@ -192,9 +190,9 @@ class VariableManager(ModelManager):
|
|||||||
raw_variables = self._db.get_all()
|
raw_variables = self._db.get_all()
|
||||||
|
|
||||||
if isinstance(raw_variables, dict):
|
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]:
|
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))
|
query = lambda v: (not plugin and not isinstance(v['plugin'], str)) or (plugin and isinstance(v['plugin'], str))
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
from src.model.enum.SlideType import SlideType, SlideInputType
|
from src.model.enum.SlideType import SlideType, SlideInputType
|
||||||
@ -7,7 +8,7 @@ from src.utils import str_to_enum
|
|||||||
|
|
||||||
class Slide:
|
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._id = id if id else None
|
||||||
self._location = location
|
self._location = location
|
||||||
self._duration = duration
|
self._duration = duration
|
||||||
@ -17,6 +18,10 @@ class Slide:
|
|||||||
self._position = position
|
self._position = position
|
||||||
self._cron_schedule = cron_schedule
|
self._cron_schedule = cron_schedule
|
||||||
self._cron_schedule_end = cron_schedule_end
|
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
|
@property
|
||||||
def id(self) -> Optional[str]:
|
def id(self) -> Optional[str]:
|
||||||
@ -30,6 +35,38 @@ class Slide:
|
|||||||
def location(self, value: str):
|
def location(self, value: str):
|
||||||
self._location = value
|
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
|
@property
|
||||||
def type(self) -> SlideType:
|
def type(self) -> SlideType:
|
||||||
return self._type
|
return self._type
|
||||||
@ -95,6 +132,10 @@ class Slide:
|
|||||||
f"duration='{self.duration}',\n" \
|
f"duration='{self.duration}',\n" \
|
||||||
f"position='{self.position}',\n" \
|
f"position='{self.position}',\n" \
|
||||||
f"location='{self.location}',\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='{self.cron_schedule}',\n" \
|
||||||
f"cron_schedule_end='{self.cron_schedule_end}',\n" \
|
f"cron_schedule_end='{self.cron_schedule_end}',\n" \
|
||||||
f")"
|
f")"
|
||||||
@ -111,6 +152,10 @@ class Slide:
|
|||||||
"type": self.type.value,
|
"type": self.type.value,
|
||||||
"duration": self.duration,
|
"duration": self.duration,
|
||||||
"location": self.location,
|
"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": self.cron_schedule,
|
||||||
"cron_schedule_end": self.cron_schedule_end,
|
"cron_schedule_end": self.cron_schedule_end,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,18 @@
|
|||||||
from enum import Enum
|
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.LangManager import LangManager
|
||||||
|
from src.manager.UserManager import UserManager
|
||||||
from src.manager.DatabaseManager import DatabaseManager
|
from src.manager.DatabaseManager import DatabaseManager
|
||||||
|
|
||||||
|
|
||||||
class ModelManager:
|
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._lang_manager = lang_manager
|
||||||
self._database_manager = database_manager
|
self._database_manager = database_manager
|
||||||
|
self._user_manager = user_manager
|
||||||
|
|
||||||
def t(self, token) -> Union[Dict, str]:
|
def t(self, token) -> Union[Dict, str]:
|
||||||
return self.lang_manager.translate(token)
|
return self.lang_manager.translate(token)
|
||||||
@ -21,3 +24,9 @@ class ModelManager:
|
|||||||
@property
|
@property
|
||||||
def database_manager(self) -> DatabaseManager:
|
def database_manager(self) -> DatabaseManager:
|
||||||
return self._database_manager
|
return self._database_manager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_manager(self) -> UserManager:
|
||||||
|
return self._user_manager
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,8 @@ class ModelStore:
|
|||||||
self._database_manager = DatabaseManager()
|
self._database_manager = DatabaseManager()
|
||||||
|
|
||||||
# Dynamics
|
# 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())
|
self._lang_manager.set_lang(self.variable().map().get('lang').as_string())
|
||||||
|
|
||||||
# Core
|
# Core
|
||||||
@ -24,9 +25,8 @@ class ModelStore:
|
|||||||
self._logging_manager = LoggingManager(config_manager=self._config_manager)
|
self._logging_manager = LoggingManager(config_manager=self._config_manager)
|
||||||
|
|
||||||
# Model
|
# 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, user_manager=self._user_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, user_manager=self._user_manager)
|
||||||
self._slide_manager = SlideManager(lang_manager=self._lang_manager, database_manager=self._database_manager)
|
|
||||||
self._variable_manager.reload()
|
self._variable_manager.reload()
|
||||||
|
|
||||||
def logging(self) -> LoggingManager:
|
def logging(self) -> LoggingManager:
|
||||||
|
|||||||
@ -27,6 +27,8 @@ class TemplateRenderer:
|
|||||||
STATIC_PREFIX="/{}/{}/".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS),
|
STATIC_PREFIX="/{}/{}/".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS),
|
||||||
FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(),
|
FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(),
|
||||||
AUTH_ENABLED=self._model_store.variable().map().get('auth_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'),
|
VERSION=self._model_store.config().map().get('version'),
|
||||||
LANG=self._model_store.variable().map().get('lang').as_string(),
|
LANG=self._model_store.variable().map().get('lang').as_string(),
|
||||||
HOOK=self._render_hook,
|
HOOK=self._render_hook,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from src.controller.FleetController import FleetController
|
|||||||
from src.controller.AuthController import AuthController
|
from src.controller.AuthController import AuthController
|
||||||
from src.controller.SysinfoController import SysinfoController
|
from src.controller.SysinfoController import SysinfoController
|
||||||
from src.controller.SettingsController import SettingsController
|
from src.controller.SettingsController import SettingsController
|
||||||
|
from src.controller.CoreController import CoreController
|
||||||
from src.constant.WebDirConstant import WebDirConstant
|
from src.constant.WebDirConstant import WebDirConstant
|
||||||
|
|
||||||
|
|
||||||
@ -50,17 +51,20 @@ class WebServer:
|
|||||||
def get_app(self):
|
def get_app(self):
|
||||||
return self._app
|
return self._app
|
||||||
|
|
||||||
def _get_template_folder(self) -> str:
|
def get_template_folder(self) -> str:
|
||||||
return "{}/{}".format(self._project_dir, WebDirConstant.FOLDER_TEMPLATES)
|
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)
|
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:
|
def _setup_flask_app(self) -> None:
|
||||||
self._app = Flask(
|
self._app = Flask(
|
||||||
__name__,
|
__name__,
|
||||||
template_folder=self._get_template_folder(),
|
template_folder=self.get_template_folder(),
|
||||||
static_folder=self._get_static_folder(),
|
static_folder=self.get_static_folder(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self._app.config['UPLOAD_FOLDER'] = "{}/{}".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_UPLOADS)
|
self._app.config['UPLOAD_FOLDER'] = "{}/{}".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_UPLOADS)
|
||||||
@ -96,6 +100,7 @@ class WebServer:
|
|||||||
|
|
||||||
return decorated_function
|
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)
|
PlayerController(self, self._app, auth_required, self._model_store, self._template_renderer)
|
||||||
SlideshowController(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)
|
SettingsController(self, self._app, auth_required, self._model_store, self._template_renderer)
|
||||||
@ -111,5 +116,5 @@ class WebServer:
|
|||||||
def _setup_web_errors(self) -> None:
|
def _setup_web_errors(self) -> None:
|
||||||
@self._app.errorhandler(404)
|
@self._app.errorhandler(404)
|
||||||
def not_found(e):
|
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
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
</title>
|
</title>
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<meta name="google" content="notranslate">
|
<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="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="60x60" href="{{ STATIC_PREFIX }}favicon/apple-icon-60x60.png">
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ STATIC_PREFIX }}favicon/apple-icon-72x72.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="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="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="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-TileColor" content="#692fbd">
|
||||||
<meta name="msapplication-TileImage" content="{{ STATIC_PREFIX }}favicon/ms-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="{{ STATIC_PREFIX }}favicon/ms-icon-144x144.png">
|
||||||
<meta name="theme-color" content="#692fbd">
|
<meta name="theme-color" content="#692fbd">
|
||||||
@ -81,8 +81,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if AUTH_ENABLED %}
|
{% if AUTH_ENABLED %}
|
||||||
<li>
|
<li class="user-menu">
|
||||||
<a href="{{ url_for('logout') }}" title="{{ l.logout }}">
|
<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>
|
<i class="fa fa-right-from-bracket"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,38 +1,38 @@
|
|||||||
{
|
{
|
||||||
"name": "App",
|
"name": "Obscreen",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "\/favicon/android-icon-36x36.png",
|
"src": "{{ STATIC_PREFIX }}favicon/android-icon-36x36.png",
|
||||||
"sizes": "36x36",
|
"sizes": "36x36",
|
||||||
"type": "image\/png",
|
"type": "image\/png",
|
||||||
"density": "0.75"
|
"density": "0.75"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "\/favicon/android-icon-48x48.png",
|
"src": "{{ STATIC_PREFIX }}favicon/android-icon-48x48.png",
|
||||||
"sizes": "48x48",
|
"sizes": "48x48",
|
||||||
"type": "image\/png",
|
"type": "image\/png",
|
||||||
"density": "1.0"
|
"density": "1.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "\/favicon/android-icon-72x72.png",
|
"src": "{{ STATIC_PREFIX }}favicon/android-icon-72x72.png",
|
||||||
"sizes": "72x72",
|
"sizes": "72x72",
|
||||||
"type": "image\/png",
|
"type": "image\/png",
|
||||||
"density": "1.5"
|
"density": "1.5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "\/favicon/android-icon-96x96.png",
|
"src": "{{ STATIC_PREFIX }}favicon/android-icon-96x96.png",
|
||||||
"sizes": "96x96",
|
"sizes": "96x96",
|
||||||
"type": "image\/png",
|
"type": "image\/png",
|
||||||
"density": "2.0"
|
"density": "2.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "\/favicon/android-icon-144x144.png",
|
"src": "{{ STATIC_PREFIX }}favicon/android-icon-144x144.png",
|
||||||
"sizes": "144x144",
|
"sizes": "144x144",
|
||||||
"type": "image\/png",
|
"type": "image\/png",
|
||||||
"density": "3.0"
|
"density": "3.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "\/favicon/android-icon-192x192.png",
|
"src": "{{ STATIC_PREFIX }}favicon/android-icon-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image\/png",
|
"type": "image\/png",
|
||||||
"density": "4.0"
|
"density": "4.0"
|
||||||
@ -2,9 +2,14 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ l.slideshow_slide_panel_th_name }}</th>
|
<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="tac">{{ l.slideshow_slide_panel_th_enabled }}</th>
|
||||||
<th class="">{{ l.slideshow_slide_panel_th_cron_scheduled }}</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>
|
<th class="tac">{{ l.slideshow_slide_panel_th_activity }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -35,9 +40,13 @@
|
|||||||
{{ slide.name }}
|
{{ slide.name }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="tac">
|
{% if AUTH_ENABLED %}
|
||||||
{{ slide.duration }} {{ l.slideshow_slide_panel_th_duration_unit }}
|
<td class="tac">
|
||||||
</td>
|
<div class="badge">
|
||||||
|
{{ track_updated(slide).username }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td class="tac">
|
<td class="tac">
|
||||||
<label class="pure-material-switch">
|
<label class="pure-material-switch">
|
||||||
<input type="checkbox" {% if slide.enabled %}checked="checked"{% endif %}><span></span>
|
<input type="checkbox" {% if slide.enabled %}checked="checked"{% endif %}><span></span>
|
||||||
@ -59,6 +68,9 @@
|
|||||||
🔄 {{ l.slideshow_slide_panel_td_cron_scheduled_loop }}
|
🔄 {{ l.slideshow_slide_panel_td_cron_scheduled_loop }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="tac">
|
||||||
|
{{ slide.duration }} {{ l.slideshow_slide_panel_th_duration_unit }}
|
||||||
|
</td>
|
||||||
<td class="actions tac">
|
<td class="actions tac">
|
||||||
<a href="javascript:void(0);" class="item-edit slide-edit">
|
<a href="javascript:void(0);" class="item-edit slide-edit">
|
||||||
<i class="fa fa-pencil"></i>
|
<i class="fa fa-pencil"></i>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user