Merge branch 'develop'
This commit is contained in:
commit
d01f2e4a88
@ -1,4 +1,4 @@
|
||||
DEBUG=false
|
||||
PORT=5000
|
||||
SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||
AUTOCONFIGURE_LX_FILE=/home/pi/.config/lxsession/LXDE-pi/autostart # Replace by "./var/run/dummy" if not needed
|
||||
AUTOCONFIGURE_LX_FILE=/home/pi/.config/lxsession/LXDE-pi/autostart # Replace by "/dev/null" if not needed
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,5 @@ __pycache__/
|
||||
*.log
|
||||
var/run/*
|
||||
!var/run/.gitkeep
|
||||
!var/run/dummy
|
||||
*.swp
|
||||
.env
|
||||
|
||||
12
README.md
12
README.md
@ -29,8 +29,10 @@ Use a RaspberryPi to show a full-screen slideshow (Kiosk-mode)
|
||||
mkdir -p obscreen/data/db obscreen/data/uploads && cd obscreen
|
||||
|
||||
# Run the Docker container
|
||||
# 🚨 If you ARE NOT on a RaspberryPi ignore the line (-v /home/pi/...)
|
||||
# 🚨 Else make sure that /home/pi/.config/lxsession/LXDE-pi/autostart file exists and is writeable !
|
||||
# 🚨 If you ARE NOT on a RaspberryPi
|
||||
# - replace '/home/pi/.config/lxsession/LXDE-pi/autostart' with '/dev/null'
|
||||
# 🚨 Else make sure that
|
||||
# - file '/home/pi/.config/lxsession/LXDE-pi/autostart' exists and is writeable
|
||||
docker run --rm --name obscreen --pull=always \
|
||||
-e DEBUG=false \
|
||||
-e PORT=5000 \
|
||||
@ -49,11 +51,11 @@ docker run --rm --name obscreen --pull=always \
|
||||
mkdir -p obscreen/data/db obscreen/data/uploads && cd obscreen
|
||||
|
||||
# Download docker-compose.yml
|
||||
# 🚨 If you ARE NOT on a RaspberryPi
|
||||
curl https://raw.githubusercontent.com/jr-k/obscreen/master/docker-compose.norpi.yml > docker-compose.yml
|
||||
# 🚨 If you ARE on a RaspberryPi
|
||||
curl https://raw.githubusercontent.com/jr-k/obscreen/master/docker-compose.yml > docker-compose.yml
|
||||
|
||||
# If you ARE NOT on a RaspberryPi execute the line below
|
||||
uname | grep -q 'Darwin' && sed -i '' '/\/home\/pi/s/^/#/' docker-compose.yml || sed -i '/\/home\/pi/s/^/#/' docker-compose.yml
|
||||
|
||||
# Run
|
||||
docker compose up
|
||||
```
|
||||
|
||||
@ -85,13 +85,18 @@ jQuery(document).ready(function ($) {
|
||||
$(document).on('click', '.user-delete', function () {
|
||||
if (confirm(l.js_auth_user_delete_confirmation)) {
|
||||
const $tr = $(this).parents('tr:eq(0)');
|
||||
$tr.remove();
|
||||
updateTable();
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: '/auth/user/delete',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify({id: getId($(this))}),
|
||||
success: function(data) {
|
||||
$tr.remove();
|
||||
},
|
||||
error: function(data) {
|
||||
$('.alert-error').html(data.responseJSON.message).removeClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -115,8 +115,11 @@ jQuery(document).ready(function ($) {
|
||||
});
|
||||
|
||||
$(document).on('change', '.modal-slide select.trigger', function () {
|
||||
const $modal = $(this).parents('.modal-slide:eq(0)');
|
||||
const $target = $(this).parents('.widget:eq(0)').find('.target');
|
||||
const $datetimepicker = $(this).parents('.widget:eq(0)').find('.datetimepicker');
|
||||
const $durationGroup = $modal.find('.slide-duration-group');
|
||||
const $scheduleEndGroup = $modal.find('.slide-schedule-end-group');
|
||||
|
||||
const isDateTime = $(this).val() === 'datetime';
|
||||
const isLoop = $(this).val() === 'loop';
|
||||
@ -126,7 +129,9 @@ jQuery(document).ready(function ($) {
|
||||
const hideDateTimeField = !isDateTime;
|
||||
|
||||
$target.toggleClass('hidden', hideCronField);
|
||||
$datetimepicker.toggleClass('hidden', hideDateTimeField)
|
||||
$datetimepicker.toggleClass('hidden', hideDateTimeField);
|
||||
// $durationGroup.toggleClass('hidden', !isLoop);
|
||||
// $scheduleEndGroup.toggleClass('hidden', isLoop);
|
||||
|
||||
if (flushValue) {
|
||||
$target.val('');
|
||||
|
||||
@ -13,6 +13,6 @@ services:
|
||||
- SECRET_KEY=${SECRET_KEY-ANY_SECRET_KEY_HERE}
|
||||
volumes:
|
||||
- .:/app
|
||||
- ${AUTOCONFIGURE_LX_FILE-./var/run/dummy}:/app/var/run/lxfile
|
||||
- ${AUTOCONFIGURE_LX_FILE-/dev/null}:/app/var/run/lxfile
|
||||
ports:
|
||||
- ${PORT}:${PORT}
|
||||
16
docker-compose.norpi.yml
Normal file
16
docker-compose.norpi.yml
Normal file
@ -0,0 +1,16 @@
|
||||
services:
|
||||
webapp:
|
||||
container_name: obscreen
|
||||
restart: unless-stopped
|
||||
image: jierka/obscreen:latest
|
||||
environment:
|
||||
- DEBUG=false
|
||||
- PORT=5000
|
||||
- AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile
|
||||
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||
volumes:
|
||||
- /dev/null:/app/var/run/lxfile
|
||||
- ./data/db:/app/data/db
|
||||
- ./data/uploads:/app/data/uploads
|
||||
ports:
|
||||
- 5000:5000
|
||||
@ -9,7 +9,6 @@ services:
|
||||
- AUTOCONFIGURE_LX_FILE=/app/var/run/lxfile
|
||||
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||
volumes:
|
||||
# If you aren't on a RaspberryPi comment the line below
|
||||
- /home/pi/.config/lxsession/LXDE-pi/autostart:/app/var/run/lxfile
|
||||
- ./data/db:/app/data/db
|
||||
- ./data/uploads:/app/data/uploads
|
||||
|
||||
@ -74,6 +74,7 @@
|
||||
"auth_user_form_label_username": "Username",
|
||||
"auth_user_form_label_password": "Password",
|
||||
"auth_user_form_button_cancel": "Cancel",
|
||||
"auth_user_delete_at_least_one_account": "You must have at least one active user while using authentication feature",
|
||||
"js_auth_user_delete_confirmation": "Are you sure?",
|
||||
|
||||
"settings_page_title": "Settings",
|
||||
|
||||
@ -74,6 +74,7 @@
|
||||
"auth_user_form_label_username": "Nom d'utilisateur",
|
||||
"auth_user_form_label_password": "Mot de passe",
|
||||
"auth_user_form_button_cancel": "Annuler",
|
||||
"auth_user_delete_at_least_one_account": "Vous devez avoir au moins un utilisateur actif lorsque vous activez la gestion de l'authentification",
|
||||
"js_auth_user_delete_confirmation": "Êtes-vous sûr ?",
|
||||
|
||||
"settings_page_title": "Paramètres",
|
||||
|
||||
@ -70,6 +70,8 @@ class AuthController(ObController):
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def auth_user_delete(self):
|
||||
if self._model_store.user().count_all_enabled() == 1:
|
||||
return jsonify({'status': 'error', 'message': self.t('auth_user_delete_at_least_one_account')}), 400
|
||||
data = request.get_json()
|
||||
self._model_store.user().delete(data.get('id'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
@ -21,8 +21,8 @@ class SettingsController(ObController):
|
||||
|
||||
def settings_variable_edit(self):
|
||||
self._model_store.variable().update_form(request.form['id'], request.form['value'])
|
||||
self._post_update(request.form['id'])
|
||||
return redirect(url_for('settings_variable_list'))
|
||||
forward = self._post_update(request.form['id'])
|
||||
return forward if forward is not None else redirect(url_for('settings_variable_list'))
|
||||
|
||||
def _post_update(self, id: str):
|
||||
variable = self._model_store.variable().get(id)
|
||||
@ -38,6 +38,8 @@ class SettingsController(ObController):
|
||||
|
||||
if variable.name == 'auth_enabled':
|
||||
self.reload_web_server()
|
||||
if variable.as_bool():
|
||||
return redirect(url_for('logout'))
|
||||
|
||||
if variable.name == 'lang':
|
||||
self._model_store.lang().set_lang(variable.value)
|
||||
|
||||
@ -28,4 +28,7 @@ class ObController(abc.ABC):
|
||||
return self._plugin
|
||||
|
||||
def reload_web_server(self):
|
||||
self._web_server.reload()
|
||||
self._web_server.reload()
|
||||
|
||||
def t(self, token) -> Union[Dict, str]:
|
||||
return self._model_store.lang().translate(token)
|
||||
|
||||
@ -56,8 +56,8 @@ class UserManager(ModelManager):
|
||||
def get_one_by_username(self, username: str, enabled: bool = None) -> Optional[User]:
|
||||
return self.get_one_by(query=lambda v: v['username'] == username and (enabled is None or v['enabled'] == enabled))
|
||||
|
||||
def count_all(self):
|
||||
return len(self.get_all())
|
||||
def count_all_enabled(self):
|
||||
return len(self.get_enabled_users())
|
||||
|
||||
def get_all(self, sort: bool = False) -> List[User]:
|
||||
raw_users = self._db.get_all()
|
||||
|
||||
@ -22,6 +22,7 @@ class WebServer:
|
||||
|
||||
def __init__(self, project_dir: str, model_store: ModelStore, template_renderer: TemplateRenderer):
|
||||
self._app = None
|
||||
self._auth_enabled = False
|
||||
self._login_manager = None
|
||||
self._project_dir = project_dir
|
||||
self._model_store = model_store
|
||||
@ -40,6 +41,7 @@ class WebServer:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
self._auth_enabled = self._model_store.variable().map().get('auth_enabled').as_bool()
|
||||
self._setup_flask_app()
|
||||
self._setup_web_globals()
|
||||
self._setup_web_errors()
|
||||
@ -69,29 +71,22 @@ class WebServer:
|
||||
if self._debug:
|
||||
self._app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||||
|
||||
def _setup_flask_login(self) -> bool:
|
||||
auth_module = self._model_store.variable().map().get('auth_enabled').as_bool()
|
||||
|
||||
if not auth_module:
|
||||
return auth_module
|
||||
|
||||
def _setup_flask_login(self):
|
||||
self._app.config['SECRET_KEY'] = self._model_store.config().map().get('secret_key')
|
||||
self._login_manager = LoginManager()
|
||||
self._login_manager.init_app(self._app)
|
||||
self._login_manager.login_view = 'login'
|
||||
|
||||
if self._model_store.user().count_all() == 0:
|
||||
if self._auth_enabled and self._model_store.user().count_all_enabled() == 0:
|
||||
self._model_store.user().add_form(User(username="admin", password="admin", enabled=True))
|
||||
|
||||
@self._login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return self._model_store.user().get(user_id)
|
||||
|
||||
return auth_module
|
||||
|
||||
def _setup_web_controllers(self) -> None:
|
||||
def auth_required(f):
|
||||
if not self._login_manager:
|
||||
if not self._auth_enabled:
|
||||
return f
|
||||
|
||||
def decorated_function(*args, **kwargs):
|
||||
|
||||
@ -160,7 +160,7 @@ def get_ip_address() -> Optional[str]:
|
||||
|
||||
|
||||
def get_yt_video_id(url: str) -> str:
|
||||
if url.startswith(('youtu', 'www')):
|
||||
if not url.startswith('http'):
|
||||
url = 'http://' + url
|
||||
|
||||
query = urlparse(url)
|
||||
@ -176,4 +176,4 @@ def get_yt_video_id(url: str) -> str:
|
||||
except:
|
||||
return ''
|
||||
|
||||
return ''
|
||||
return ''
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
|
||||
@lxpanel --profile LXDE-pi
|
||||
@pcmanfm --desktop --profile LXDE-pi
|
||||
@xscreensaver -no-splash
|
||||
#@point-rpi
|
||||
@xset s off
|
||||
@xset -dpms
|
||||
@xset s noblank
|
||||
@unclutter -display :0 -noevents -grab
|
||||
@sed -i 's/"exited_cleanly": false/"exited_cleanly": true/' ~/.config/chromium/Default/Preferences
|
||||
#@sleep 10
|
||||
@chromium-browser --disable-features=Translate --ignore-certificate-errors --disable-web-security --disable-restore-session-state --autoplay-policy=no-user-gesture-required --start-maximized --allow-running-insecure-content --remember-cert-error-decisions --disable-restore-session-state --noerrdialogs --kiosk --incognito --window-position=0,0 --display=:0 http://localhost:5000
|
||||
|
||||
@ -23,6 +23,11 @@
|
||||
{{ HOOK(H_AUTH_TOOLBAR_ACTIONS_END) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-error hidden">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<h3>{{ l.auth_user_panel_active }}</h3>
|
||||
|
||||
@ -46,7 +46,8 @@
|
||||
Obscreen
|
||||
</a>
|
||||
</h1>
|
||||
{% if (current_user and current_user.is_authenticated) or not current_user %}
|
||||
|
||||
{% if not AUTH_ENABLED or (current_user and current_user.is_authenticated) %}
|
||||
<nav>
|
||||
<ul>
|
||||
{{ HOOK(H_ROOT_NAV_ELEMENT_START) }}
|
||||
|
||||
@ -41,14 +41,6 @@
|
||||
{{ l.slideshow_slide_form_section_scheduling }}
|
||||
</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_duration }}</label>
|
||||
<div class="widget">
|
||||
<input type="number" name="duration" id="slide-add-duration" required="required" />
|
||||
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-add-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
|
||||
<div class="widget">
|
||||
@ -62,11 +54,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group hidden">
|
||||
<div class="form-group slide-schedule-end-group hidden">
|
||||
<label for="slide-add-cron-schedule-end">{{ l.slideshow_slide_form_label_cron_scheduled_end }}</label>
|
||||
<div class="widget">
|
||||
<select id="slide-add-cron-schedule-end-trigger" class="trigger">
|
||||
<option value="loop">{{ l.slideshow_slide_form_label_cron_scheduled_loop }}</option>
|
||||
<option value="datetime">{{ l.slideshow_slide_form_label_cron_scheduled_datetime }}</option>
|
||||
<option value="cron">{{ l.slideshow_slide_form_label_cron_scheduled_cron }}</option>
|
||||
</select>
|
||||
@ -75,6 +66,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group slide-duration-group">
|
||||
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_duration }}</label>
|
||||
<div class="widget">
|
||||
<input type="number" name="duration" id="slide-add-duration" required="required" />
|
||||
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="modal-close">
|
||||
{{ l.slideshow_slide_form_button_cancel }}
|
||||
|
||||
@ -43,14 +43,6 @@
|
||||
{{ l.slideshow_slide_form_section_scheduling }}
|
||||
</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-duration">{{ l.slideshow_slide_form_label_duration }}</label>
|
||||
<div class="widget">
|
||||
<input type="number" name="duration" id="slide-edit-duration" required="required" />
|
||||
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-cron-schedule">{{ l.slideshow_slide_form_label_cron_scheduled }}</label>
|
||||
<div class="widget">
|
||||
@ -64,7 +56,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group hidden">
|
||||
<div class="form-group slide-schedule-end-group hidden">
|
||||
<label for="slide-edit-cron-schedule-end">{{ l.slideshow_slide_form_label_cron_scheduled_end }}</label>
|
||||
<div class="widget">
|
||||
<select id="slide-edit-cron-schedule-end-trigger" class="trigger">
|
||||
@ -77,6 +69,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group slide-duration-group">
|
||||
<label for="slide-edit-duration">{{ l.slideshow_slide_form_label_duration }}</label>
|
||||
<div class="widget">
|
||||
<input type="number" name="duration" id="slide-edit-duration" required="required" />
|
||||
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="modal-close">
|
||||
{{ l.slideshow_slide_form_button_cancel }}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user