add fleet mode + split views in controllers
This commit is contained in:
parent
e51061c984
commit
eec10c81dc
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,7 +5,7 @@
|
||||
out/
|
||||
data/uploads/*
|
||||
!data/uploads/reclame.jpg
|
||||
data/slideshow.json
|
||||
data/db/slideshow.json
|
||||
config.py
|
||||
*.lock
|
||||
__pycache__/
|
||||
|
||||
@ -3,5 +3,6 @@ config = {
|
||||
"port": 5000, # Application port
|
||||
"reverse_proxy_mode": False, # True if you want to use nginx on port 80
|
||||
"lang": "en", # Language for manage view "fr" or "en"
|
||||
"lx_file": '/home/pi/.config/lxsession/LXDE-pi/autostart' # Path to lx autostart file
|
||||
"lx_file": '/home/pi/.config/lxsession/LXDE-pi/autostart' # Path to lx autostart file,
|
||||
"fleet_enabled": False # Enable fleet management view
|
||||
}
|
||||
|
||||
23
data/db/fleet.json
Normal file
23
data/db/fleet.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": 2,
|
||||
"keys": [
|
||||
"address",
|
||||
"enabled",
|
||||
"name",
|
||||
"position"
|
||||
],
|
||||
"data": {
|
||||
"179670684589565125": {
|
||||
"name": "aaaxx",
|
||||
"enabled": false,
|
||||
"position": 0,
|
||||
"address": "bbbz"
|
||||
},
|
||||
"460215553937488565": {
|
||||
"name": "ff",
|
||||
"enabled": false,
|
||||
"position": 1,
|
||||
"address": "ee"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -220,19 +220,19 @@ button.purple:hover {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.panel td a.slide-sort {
|
||||
.panel td a.item.sort {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.panel td a.slide-name {
|
||||
.panel td a.item-name {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.panel-inactive td a.slide-name {
|
||||
.panel-inactive td a.item-name {
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
.panel td a.slide-name:hover {
|
||||
.panel td a.item-name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@ -253,12 +253,12 @@ button.purple:hover {
|
||||
border-color: #0eef5f;
|
||||
}
|
||||
|
||||
.panel td.actions a.slide-edit:hover {
|
||||
.panel td.actions a.item-edit:hover {
|
||||
color: #bc48ff;
|
||||
border-color: #bc48ff;
|
||||
}
|
||||
|
||||
.panel td.actions a.slide-delete:hover {
|
||||
.panel td.actions a.item-delete:hover {
|
||||
color: #ef0e0e;
|
||||
border-color: #ef0e0e;
|
||||
}
|
||||
|
||||
125
data/www/js/fleet.js
Normal file
125
data/www/js/fleet.js
Normal file
@ -0,0 +1,125 @@
|
||||
jQuery(document).ready(function ($) {
|
||||
const $tableActive = $('table.active-screens');
|
||||
const $tableInactive = $('table.inactive-screens');
|
||||
const $modalsRoot = $('.modals');
|
||||
|
||||
const getId = function ($el) {
|
||||
return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level');
|
||||
};
|
||||
|
||||
const updateTable = function () {
|
||||
$('table').each(function () {
|
||||
if ($(this).find('tbody tr.screen-item:visible').length === 0) {
|
||||
$(this).find('tr.empty-tr').removeClass('hidden');
|
||||
} else {
|
||||
$(this).find('tr.empty-tr').addClass('hidden');
|
||||
}
|
||||
}).tableDnDUpdate();
|
||||
updatePositions();
|
||||
}
|
||||
|
||||
const showModal = function (modalClass) {
|
||||
$modalsRoot.removeClass('hidden').find('form').trigger('reset');
|
||||
$modalsRoot.find('.modal').addClass('hidden');
|
||||
$modalsRoot.find('.modal.' + modalClass).removeClass('hidden');
|
||||
};
|
||||
|
||||
const hideModal = function () {
|
||||
$modalsRoot.addClass('hidden').find('form').trigger('reset');
|
||||
};
|
||||
|
||||
const updatePositions = function (table, row) {
|
||||
const positions = {};
|
||||
$('.screen-item').each(function (index) {
|
||||
positions[getId($(this))] = index;
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/fleet/screen/position',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify(positions),
|
||||
});
|
||||
};
|
||||
|
||||
const main = function () {
|
||||
$("table").tableDnD({
|
||||
dragHandle: 'td a.screen-sort',
|
||||
onDrop: updatePositions
|
||||
});
|
||||
};
|
||||
|
||||
$(document).on('change', 'input[type=checkbox]', function () {
|
||||
$.ajax({
|
||||
url: 'fleet/screen/toggle',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify({id: getId($(this)), enabled: $(this).is(':checked')}),
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
const $tr = $(this).parents('tr:eq(0)').remove().clone();
|
||||
|
||||
if ($(this).is(':checked')) {
|
||||
$tableActive.append($tr);
|
||||
} else {
|
||||
$tableInactive.append($tr);
|
||||
}
|
||||
|
||||
updateTable();
|
||||
});
|
||||
|
||||
$(document).on('change', '#screen-add-type', function () {
|
||||
const value = $(this).val();
|
||||
const inputType = $(this).find('option').filter(function (i, el) {
|
||||
return $(el).val() === value;
|
||||
}).data('input');
|
||||
|
||||
$('.screen-add-object-input')
|
||||
.addClass('hidden')
|
||||
.prop('disabled', true)
|
||||
.filter('#screen-add-object-input-' + inputType)
|
||||
.removeClass('hidden')
|
||||
.prop('disabled', false)
|
||||
;
|
||||
});
|
||||
|
||||
$(document).on('click', '.modal-close', function () {
|
||||
hideModal();
|
||||
});
|
||||
|
||||
$(document).on('click', '.screen-add', function () {
|
||||
showModal('modal-screen-add');
|
||||
$('.modal-screen-add input:eq(0)').focus().select();
|
||||
});
|
||||
|
||||
$(document).on('click', '.screen-edit', function () {
|
||||
const screen = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
|
||||
showModal('modal-screen-edit');
|
||||
$('.modal-screen-edit input:visible:eq(0)').focus().select();
|
||||
$('#screen-edit-name').val(screen.name);
|
||||
$('#screen-edit-address').val(screen.address);
|
||||
$('#screen-edit-id').val(screen.id);
|
||||
});
|
||||
|
||||
$(document).on('click', '.screen-delete', function () {
|
||||
if (confirm(l.fleet_screen_delete_confirmation)) {
|
||||
const $tr = $(this).parents('tr:eq(0)');
|
||||
$tr.remove();
|
||||
updateTable();
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: '/fleet/screen/delete',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify({id: getId($(this))}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).keyup(function (e) {
|
||||
if (e.key === "Escape") {
|
||||
hideModal();
|
||||
}
|
||||
});
|
||||
|
||||
main();
|
||||
});
|
||||
@ -36,7 +36,7 @@ jQuery(document).ready(function ($) {
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/manage/slide/position',
|
||||
url: '/slideshow/slide/position',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify(positions),
|
||||
});
|
||||
@ -51,7 +51,7 @@ jQuery(document).ready(function ($) {
|
||||
|
||||
$(document).on('change', 'input[type=checkbox]', function () {
|
||||
$.ajax({
|
||||
url: 'manage/slide/toggle',
|
||||
url: 'slideshow/slide/toggle',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify({id: getId($(this)), enabled: $(this).is(':checked')}),
|
||||
method: 'POST',
|
||||
@ -104,13 +104,13 @@ jQuery(document).ready(function ($) {
|
||||
});
|
||||
|
||||
$(document).on('click', '.slide-delete', function () {
|
||||
if (confirm(l.manage_slide_delete_confirmation)) {
|
||||
if (confirm(l.slideshow_slide_delete_confirmation)) {
|
||||
const $tr = $(this).parents('tr:eq(0)');
|
||||
$tr.remove();
|
||||
updateTable();
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: '/manage/slide/delete',
|
||||
url: '/slideshow/slide/delete',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify({id: getId($(this))}),
|
||||
});
|
||||
70
lang/en.json
70
lang/en.json
@ -1,29 +1,47 @@
|
||||
{
|
||||
"manage_page_title": "Schedule Overview",
|
||||
"manage_slide_button_add": "Add a slide",
|
||||
"manage_slide_panel_active": "Active slides",
|
||||
"manage_slide_panel_inactive": "Inactive slides",
|
||||
"manage_slide_panel_empty": "Currently, there are no slides. %link% now.",
|
||||
"manage_slide_panel_th_name": "Name",
|
||||
"manage_slide_panel_th_duration": "Duration",
|
||||
"manage_slide_panel_th_duration_unit": "sec",
|
||||
"manage_slide_panel_th_enabled": "Enabled",
|
||||
"manage_slide_panel_th_activity": "Activity",
|
||||
"manage_slide_form_add_title": "Add Slide",
|
||||
"manage_slide_form_add_submit": "Add",
|
||||
"manage_slide_form_edit_title": "Edit Slide",
|
||||
"manage_slide_form_edit_submit": "Save",
|
||||
"manage_slide_form_label_name": "Name",
|
||||
"manage_slide_form_label_location": "Location",
|
||||
"manage_slide_form_label_type": "Type",
|
||||
"manage_slide_form_label_type_url": "URL",
|
||||
"manage_slide_form_label_type_video": "Video",
|
||||
"manage_slide_form_label_type_picture": "Picture",
|
||||
"manage_slide_form_label_object": "Object",
|
||||
"manage_slide_form_label_duration": "Duration",
|
||||
"manage_slide_form_label_duration_unit": "seconds",
|
||||
"manage_slide_form_button_cancel": "Cancel",
|
||||
"js_manage_slide_delete_confirmation": "Are you sure?",
|
||||
"slideshow_page_title": "Schedule Overview",
|
||||
"slideshow_slide_button_add": "Add a slide",
|
||||
"slideshow_slide_panel_active": "Active slides",
|
||||
"slideshow_slide_panel_inactive": "Inactive slides",
|
||||
"slideshow_slide_panel_empty": "Currently, there are no slides. %link% now.",
|
||||
"slideshow_slide_panel_th_name": "Name",
|
||||
"slideshow_slide_panel_th_duration": "Duration",
|
||||
"slideshow_slide_panel_th_duration_unit": "sec",
|
||||
"slideshow_slide_panel_th_enabled": "Enabled",
|
||||
"slideshow_slide_panel_th_activity": "Activity",
|
||||
"slideshow_slide_form_add_title": "Add Slide",
|
||||
"slideshow_slide_form_add_submit": "Add",
|
||||
"slideshow_slide_form_edit_title": "Edit Slide",
|
||||
"slideshow_slide_form_edit_submit": "Save",
|
||||
"slideshow_slide_form_label_name": "Name",
|
||||
"slideshow_slide_form_label_location": "Location",
|
||||
"slideshow_slide_form_label_type": "Type",
|
||||
"slideshow_slide_form_label_type_url": "URL",
|
||||
"slideshow_slide_form_label_type_video": "Video",
|
||||
"slideshow_slide_form_label_type_picture": "Picture",
|
||||
"slideshow_slide_form_label_object": "Object",
|
||||
"slideshow_slide_form_label_duration": "Duration",
|
||||
"slideshow_slide_form_label_duration_unit": "seconds",
|
||||
"slideshow_slide_form_button_cancel": "Cancel",
|
||||
"js_slideshow_slide_delete_confirmation": "Are you sure?",
|
||||
|
||||
"fleet_page_title": "Devices",
|
||||
"fleet_screen_button_add": "Add a screen",
|
||||
"fleet_screen_panel_active": "Active screens",
|
||||
"fleet_screen_panel_inactive": "Inactive screens",
|
||||
"fleet_screen_panel_empty": "Currently, there are no screens. %link% now.",
|
||||
"fleet_screen_panel_th_name": "Name",
|
||||
"fleet_screen_panel_th_address": "Address",
|
||||
"fleet_screen_panel_th_enabled": "Enabled",
|
||||
"fleet_screen_panel_th_activity": "Activity",
|
||||
"fleet_screen_form_add_title": "Add Screen",
|
||||
"fleet_screen_form_add_submit": "Add",
|
||||
"fleet_screen_form_edit_title": "Edit Screen",
|
||||
"fleet_screen_form_edit_submit": "Save",
|
||||
"fleet_screen_form_label_name": "Name",
|
||||
"fleet_screen_form_label_address": "Address",
|
||||
"fleet_screen_form_button_cancel": "Cancel",
|
||||
"js_fleet_screen_delete_confirmation": "Are you sure?",
|
||||
|
||||
"sysinfo_page_title": "System infos",
|
||||
"sysinfo_panel_title": "Infos",
|
||||
@ -31,7 +49,5 @@
|
||||
"sysinfo_panel_th_value": "Value",
|
||||
"sysinfo_panel_td_ipaddr": "IP Address",
|
||||
|
||||
|
||||
|
||||
"settings_page_title": "Settings"
|
||||
}
|
||||
|
||||
68
lang/fr.json
68
lang/fr.json
@ -1,29 +1,47 @@
|
||||
{
|
||||
"manage_page_title": "Vue Planning",
|
||||
"manage_slide_button_add": "Ajouter une slide",
|
||||
"manage_slide_panel_active": "Slides actives",
|
||||
"manage_slide_panel_inactive": "Slides inactives",
|
||||
"manage_slide_panel_empty": "Actuellement, il n'y a aucune slide. %link% maintenant.",
|
||||
"manage_slide_panel_th_name": "Nom",
|
||||
"manage_slide_panel_th_duration": "Durée",
|
||||
"manage_slide_panel_th_duration_unit": "sec",
|
||||
"manage_slide_panel_th_enabled": "Activé",
|
||||
"manage_slide_panel_th_activity": "Options",
|
||||
"manage_slide_form_add_title": "Ajouter d'une slide",
|
||||
"manage_slide_form_add_submit": "Ajouter",
|
||||
"manage_slide_form_edit_title": "Modification d'une slide",
|
||||
"manage_slide_form_edit_submit": "Enregistrer",
|
||||
"manage_slide_form_label_name": "Nom",
|
||||
"manage_slide_form_label_location": "Chemin",
|
||||
"manage_slide_form_label_type": "Type",
|
||||
"manage_slide_form_label_type_url": "URL",
|
||||
"manage_slide_form_label_type_video": "Vidéo",
|
||||
"manage_slide_form_label_type_picture": "Image",
|
||||
"manage_slide_form_label_object": "Objet",
|
||||
"manage_slide_form_label_duration": "Durée",
|
||||
"manage_slide_form_label_duration_unit": "secondes",
|
||||
"manage_slide_form_button_cancel": "Annuler",
|
||||
"js_manage_slide_delete_confirmation": "Êtes-vous sûr ?",
|
||||
"slideshow_page_title": "Vue Planning",
|
||||
"slideshow_slide_button_add": "Ajouter une slide",
|
||||
"slideshow_slide_panel_active": "Slides actives",
|
||||
"slideshow_slide_panel_inactive": "Slides inactives",
|
||||
"slideshow_slide_panel_empty": "Actuellement, il n'y a aucune slide. %link% maintenant.",
|
||||
"slideshow_slide_panel_th_name": "Nom",
|
||||
"slideshow_slide_panel_th_duration": "Durée",
|
||||
"slideshow_slide_panel_th_duration_unit": "sec",
|
||||
"slideshow_slide_panel_th_enabled": "Activé",
|
||||
"slideshow_slide_panel_th_activity": "Options",
|
||||
"slideshow_slide_form_add_title": "Ajouter d'une slide",
|
||||
"slideshow_slide_form_add_submit": "Ajouter",
|
||||
"slideshow_slide_form_edit_title": "Modification d'une slide",
|
||||
"slideshow_slide_form_edit_submit": "Enregistrer",
|
||||
"slideshow_slide_form_label_name": "Nom",
|
||||
"slideshow_slide_form_label_location": "Chemin",
|
||||
"slideshow_slide_form_label_type": "Type",
|
||||
"slideshow_slide_form_label_type_url": "URL",
|
||||
"slideshow_slide_form_label_type_video": "Vidéo",
|
||||
"slideshow_slide_form_label_type_picture": "Image",
|
||||
"slideshow_slide_form_label_object": "Objet",
|
||||
"slideshow_slide_form_label_duration": "Durée",
|
||||
"slideshow_slide_form_label_duration_unit": "secondes",
|
||||
"slideshow_slide_form_button_cancel": "Annuler",
|
||||
"js_slideshow_slide_delete_confirmation": "Êtes-vous sûr ?",
|
||||
|
||||
"fleet_page_title": "Appareils",
|
||||
"fleet_screen_button_add": "Ajouter un écran",
|
||||
"fleet_screen_panel_active": "Écrans actifs",
|
||||
"fleet_screen_panel_inactive": "Écrans inactifs",
|
||||
"fleet_screen_panel_empty": "Actuellement, il n'y a pas d'écrans. %link% maintenant.",
|
||||
"fleet_screen_panel_th_name": "Nom",
|
||||
"fleet_screen_panel_th_address": "Adresse",
|
||||
"fleet_screen_panel_th_enabled": "Activé",
|
||||
"fleet_screen_panel_th_activity": "Options",
|
||||
"fleet_screen_form_add_title": "Ajout d'un écran",
|
||||
"fleet_screen_form_add_submit": "Ajouter",
|
||||
"fleet_screen_form_edit_title": "Modification d'un écran",
|
||||
"fleet_screen_form_edit_submit": "Enregistrer",
|
||||
"fleet_screen_form_label_name": "Nom",
|
||||
"fleet_screen_form_label_address": "Adresse",
|
||||
"fleet_screen_form_button_cancel": "Annuler",
|
||||
"js_fleet_screen_delete_confirmation": "Êtes-vous sûr ?",
|
||||
|
||||
"sysinfo_page_title": "Système",
|
||||
"sysinfo_panel_title": "Informations",
|
||||
|
||||
113
obscreen.py
113
obscreen.py
@ -7,18 +7,18 @@ import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
from enum import Enum
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask import Flask, send_from_directory
|
||||
from config import config
|
||||
from src.SlideManager import SlideManager
|
||||
from src.model.Slide import Slide
|
||||
from src.model.SlideType import SlideType
|
||||
from src.utils import str_to_enum
|
||||
|
||||
from src.ScreenManager import ScreenManager
|
||||
from src.controller.PlayerController import PlayerController
|
||||
from src.controller.SlideshowController import SlideshowController
|
||||
from src.controller.FleetController import FleetController
|
||||
from src.controller.SysinfoController import SysinfoController
|
||||
|
||||
# <config>
|
||||
PLAYER_URL = 'http://localhost:{}'.format(config['port'])
|
||||
screen_manager = ScreenManager()
|
||||
slide_manager = SlideManager()
|
||||
with open('./lang/{}.json'.format(config['lang']), 'r') as file:
|
||||
LANGDICT = json.load(file)
|
||||
@ -69,110 +69,21 @@ if config['lx_file']:
|
||||
# </xenv>
|
||||
|
||||
|
||||
# <utils>
|
||||
def get_ip_address():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["ip", "-4", "route", "get", "8.8.8.8"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
ip_address = result.stdout.split()[6]
|
||||
return ip_address
|
||||
except Exception as e:
|
||||
print(f"Error obtaining IP address: {e}")
|
||||
return 'Unknown'
|
||||
# </utils>
|
||||
|
||||
|
||||
# <web>
|
||||
@app.context_processor
|
||||
def inject_global_vars():
|
||||
return dict(
|
||||
FLEET_MODE=config['fleet_enabled'],
|
||||
LANG=config['lang'],
|
||||
STATIC_PREFIX='/data/www/'
|
||||
)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('player/player.jinja.html', items=json.dumps(slide_manager.to_dict(slide_manager.get_enabled_slides())))
|
||||
|
||||
@app.route('/playlist')
|
||||
def playlist():
|
||||
return jsonify(slide_manager.to_dict(slide_manager.get_enabled_slides()))
|
||||
PlayerController(app, LANGDICT, slide_manager)
|
||||
SlideshowController(app, LANGDICT, slide_manager)
|
||||
FleetController(app, LANGDICT, screen_manager)
|
||||
SysinfoController(app, LANGDICT)
|
||||
|
||||
@app.route('/slide/default')
|
||||
def slide_default():
|
||||
return render_template('player/default.jinja.html', ipaddr=get_ip_address())
|
||||
|
||||
@app.route('/manage')
|
||||
def manage():
|
||||
return render_template(
|
||||
'manager/manage.jinja.html',
|
||||
l=LANGDICT,
|
||||
enabled_slides=slide_manager.get_enabled_slides(),
|
||||
disabled_slides=slide_manager.get_disabled_slides(),
|
||||
)
|
||||
|
||||
@app.route('/manage/sysinfo')
|
||||
def manage_sysinfo():
|
||||
return render_template(
|
||||
'manager/sysinfo.jinja.html',
|
||||
ipaddr=get_ip_address(),
|
||||
l=LANGDICT,
|
||||
)
|
||||
|
||||
@app.route('/manage/slide/add', methods=['GET', 'POST'])
|
||||
def manage_slide_add():
|
||||
slide = Slide(
|
||||
name=request.form['name'],
|
||||
type=str_to_enum(request.form['type'], SlideType),
|
||||
duration=request.form['duration'],
|
||||
)
|
||||
|
||||
if slide.has_file():
|
||||
if 'object' not in request.files:
|
||||
return redirect(request.url)
|
||||
|
||||
object = request.files['object']
|
||||
|
||||
if object.filename == '':
|
||||
return redirect(request.url)
|
||||
|
||||
if object:
|
||||
object_name = secure_filename(object.filename)
|
||||
object_path = os.path.join(app.config['UPLOAD_FOLDER'], object_name)
|
||||
object.save(object_path)
|
||||
slide.location = object_path
|
||||
else:
|
||||
slide.location = request.form['object']
|
||||
|
||||
slide_manager.add_form(slide)
|
||||
|
||||
return redirect(url_for('manage'))
|
||||
|
||||
@app.route('/manage/slide/edit', methods=['POST'])
|
||||
def manage_slide_edit():
|
||||
slide_manager.update_form(request.form['id'], request.form['name'], request.form['duration'])
|
||||
return redirect(url_for('manage'))
|
||||
|
||||
@app.route('/manage/slide/toggle', methods=['POST'])
|
||||
def manage_slide_toggle():
|
||||
data = request.get_json()
|
||||
slide_manager.update_enabled(data.get('id'), data.get('enabled'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
@app.route('/manage/slide/delete', methods=['DELETE'])
|
||||
def manage_slide_delete():
|
||||
data = request.get_json()
|
||||
slide_manager.delete(data.get('id'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
@app.route('/manage/slide/position', methods=['POST'])
|
||||
def manage_slide_position():
|
||||
data = request.get_json()
|
||||
slide_manager.update_positions(data)
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
|
||||
66
src/ScreenManager.py
Normal file
66
src/ScreenManager.py
Normal file
@ -0,0 +1,66 @@
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
from src.model.Screen import Screen
|
||||
from pysondb import PysonDB
|
||||
|
||||
|
||||
class ScreenManager:
|
||||
|
||||
DB_FILE = "data/db/fleet.json"
|
||||
|
||||
def __init__(self):
|
||||
self._db = PysonDB(self.DB_FILE)
|
||||
|
||||
@staticmethod
|
||||
def hydrate_object(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()]
|
||||
|
||||
@staticmethod
|
||||
def hydrate_list(raw_screens: list) -> List[Screen]:
|
||||
return [ScreenManager.hydrate_object(raw_screen) for raw_screen in raw_screens]
|
||||
|
||||
def get(self, id: str) -> Optional[Screen]:
|
||||
return self.hydrate_object(self._db.get_by_id(id), id)
|
||||
|
||||
def get_all(self, sort: bool = False) -> List[Screen]:
|
||||
raw_screens = self._db.get_all()
|
||||
|
||||
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 ScreenManager.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]
|
||||
|
||||
def get_disabled_screens(self) -> List[Screen]:
|
||||
return [screen for screen in self.get_all(sort=True) if not screen.enabled]
|
||||
|
||||
def update_enabled(self, id: str, enabled: bool) -> None:
|
||||
self._db.update_by_id(id, {"enabled": enabled, "position": 999})
|
||||
|
||||
def update_positions(self, positions: list) -> None:
|
||||
for screen_id, screen_position in positions.items():
|
||||
self._db.update_by_id(screen_id, {"position": screen_position})
|
||||
|
||||
def update_form(self, id: str, name: str, address: int) -> None:
|
||||
self._db.update_by_id(id, {"name": name, "address": address})
|
||||
|
||||
def add_form(self, screen: Screen) -> None:
|
||||
db_screen = screen.to_dict()
|
||||
del db_screen['id']
|
||||
self._db.add(db_screen)
|
||||
|
||||
def delete(self, id: str) -> None:
|
||||
self._db.delete_by_id(id)
|
||||
|
||||
def to_dict(self, screens: List[Screen]) -> dict:
|
||||
return [screen.to_dict() for screen in screens]
|
||||
@ -1,4 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
from src.model.Slide import Slide
|
||||
@ -6,15 +6,15 @@ from src.utils import str_to_enum
|
||||
from pysondb import PysonDB
|
||||
|
||||
|
||||
class SlideManager():
|
||||
class SlideManager:
|
||||
|
||||
DB_FILE = "data/slideshow.json"
|
||||
DB_FILE = "data/db/slideshow.json"
|
||||
|
||||
def __init__(self):
|
||||
self._db = PysonDB(self.DB_FILE)
|
||||
|
||||
@staticmethod
|
||||
def hydrate_object(raw_slide: dict, id: Union[int, str] = None) -> Slide:
|
||||
def hydrate_object(raw_slide: dict, id: str = None) -> Slide:
|
||||
if id:
|
||||
raw_slide['id'] = id
|
||||
|
||||
@ -28,6 +28,9 @@ class SlideManager():
|
||||
def hydrate_list(raw_slides: list) -> List[Slide]:
|
||||
return [SlideManager.hydrate_object(raw_slide) for raw_slide in raw_slides]
|
||||
|
||||
def get(self, id: str) -> Optional[Slide]:
|
||||
return self.hydrate_object(self._db.get_by_id(id), id)
|
||||
|
||||
def get_all(self, sort: bool = False) -> List[Slide]:
|
||||
raw_slides = self._db.get_all()
|
||||
|
||||
@ -44,7 +47,7 @@ class SlideManager():
|
||||
def get_disabled_slides(self) -> List[Slide]:
|
||||
return [slide for slide in self.get_all(sort=True) if not slide.enabled]
|
||||
|
||||
def update_enabled(self, id: int, enabled: bool) -> None:
|
||||
def update_enabled(self, id: str, enabled: bool) -> None:
|
||||
self._db.update_by_id(id, {"enabled": enabled, "position": 999})
|
||||
|
||||
def update_positions(self, positions: list) -> None:
|
||||
@ -59,7 +62,12 @@ class SlideManager():
|
||||
del db_slide['id']
|
||||
self._db.add(db_slide)
|
||||
|
||||
def delete(self, id: int) -> None:
|
||||
def delete(self, id: str) -> None:
|
||||
slide = self.get(id)
|
||||
|
||||
if slide:
|
||||
if slide.has_file():
|
||||
os.unlink(slide.location)
|
||||
self._db.delete_by_id(id)
|
||||
|
||||
def to_dict(self, slides: List[Slide]) -> dict:
|
||||
|
||||
55
src/controller/FleetController.py
Normal file
55
src/controller/FleetController.py
Normal file
@ -0,0 +1,55 @@
|
||||
import json
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for, jsonify
|
||||
from src.model.Screen import Screen
|
||||
|
||||
|
||||
class FleetController:
|
||||
|
||||
def __init__(self, app, l, screen_manager):
|
||||
self._app = app
|
||||
self._l = l
|
||||
self._screen_manager = screen_manager
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
self._app.add_url_rule('/fleet', 'fleet', self.fleet, methods=['GET'])
|
||||
self._app.add_url_rule('/fleet/screen/add', 'fleet_screen_add', self.fleet_screen_add, methods=['POST'])
|
||||
self._app.add_url_rule('/fleet/screen/edit', 'fleet_screen_edit', self.fleet_screen_edit, methods=['POST'])
|
||||
self._app.add_url_rule('/fleet/screen/toggle', 'fleet_screen_toggle', self.fleet_screen_toggle, methods=['POST'])
|
||||
self._app.add_url_rule('/fleet/screen/delete', 'fleet_screen_delete', self.fleet_screen_delete, methods=['DELETE'])
|
||||
self._app.add_url_rule('/fleet/screen/position', 'fleet_screen_position', self.fleet_screen_position, methods=['POST'])
|
||||
|
||||
def fleet(self):
|
||||
return render_template(
|
||||
'fleet/fleet.jinja.html',
|
||||
l=self._l,
|
||||
enabled_screens=self._screen_manager.get_enabled_screens(),
|
||||
disabled_screens=self._screen_manager.get_disabled_screens(),
|
||||
)
|
||||
|
||||
def fleet_screen_add(self):
|
||||
self._screen_manager.add_form(Screen(
|
||||
name=request.form['name'],
|
||||
address=request.form['address'],
|
||||
))
|
||||
return redirect(url_for('fleet'))
|
||||
|
||||
def fleet_screen_edit(self):
|
||||
self._screen_manager.update_form(request.form['id'], request.form['name'], request.form['address'])
|
||||
return redirect(url_for('fleet'))
|
||||
|
||||
def fleet_screen_toggle(self):
|
||||
data = request.get_json()
|
||||
self._screen_manager.update_enabled(data.get('id'), data.get('enabled'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def fleet_screen_delete(self):
|
||||
data = request.get_json()
|
||||
self._screen_manager.delete(data.get('id'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def fleet_screen_position(self):
|
||||
data = request.get_json()
|
||||
self._screen_manager.update_positions(data)
|
||||
return jsonify({'status': 'ok'})
|
||||
30
src/controller/PlayerController.py
Normal file
30
src/controller/PlayerController.py
Normal file
@ -0,0 +1,30 @@
|
||||
import json
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
||||
from src.utils import get_ip_address
|
||||
|
||||
|
||||
class PlayerController:
|
||||
|
||||
def __init__(self, app, l, slide_manager):
|
||||
self._app = app
|
||||
self._l = l
|
||||
self._slide_manager = slide_manager
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
self._app.add_url_rule('/', 'player', self.player, methods=['GET'])
|
||||
self._app.add_url_rule('/player/default', 'player_default', self.player_default, methods=['GET'])
|
||||
self._app.add_url_rule('/player/playlist', 'player_playlist', self.player_playlist, methods=['GET'])
|
||||
|
||||
def player(self):
|
||||
return render_template(
|
||||
'player/player.jinja.html',
|
||||
items=json.dumps(self._slide_manager.to_dict(self._slide_manager.get_enabled_slides()))
|
||||
)
|
||||
|
||||
def player_default(self):
|
||||
return render_template('player/default.jinja.html', ipaddr=get_ip_address())
|
||||
|
||||
def player_playlist(self):
|
||||
return jsonify(self._slide_manager.to_dict(self._slide_manager.get_enabled_slides()))
|
||||
80
src/controller/SlideshowController.py
Normal file
80
src/controller/SlideshowController.py
Normal file
@ -0,0 +1,80 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
||||
from werkzeug.utils import secure_filename
|
||||
from src.model.Slide import Slide
|
||||
from src.model.SlideType import SlideType
|
||||
from src.utils import str_to_enum
|
||||
|
||||
|
||||
class SlideshowController:
|
||||
|
||||
def __init__(self, app, l, slide_manager):
|
||||
self._app = app
|
||||
self._l = l
|
||||
self._slide_manager = slide_manager
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
self._app.add_url_rule('/slideshow', 'slideshow', self.slideshow, methods=['GET'])
|
||||
self._app.add_url_rule('/slideshow/slide/add', 'slideshow_slide_add', self.slideshow_slide_add, methods=['POST'])
|
||||
self._app.add_url_rule('/slideshow/slide/edit', 'slideshow_slide_edit', self.slideshow_slide_edit, methods=['POST'])
|
||||
self._app.add_url_rule('/slideshow/slide/toggle', 'slideshow_slide_toggle', self.slideshow_slide_toggle, methods=['POST'])
|
||||
self._app.add_url_rule('/slideshow/slide/delete', 'slideshow_slide_delete', self.slideshow_slide_delete, methods=['DELETE'])
|
||||
self._app.add_url_rule('/slideshow/slide/position', 'slideshow_slide_position', self.slideshow_slide_position, methods=['POST'])
|
||||
|
||||
def slideshow(self):
|
||||
return render_template(
|
||||
'slideshow/slideshow.jinja.html',
|
||||
l=self._l,
|
||||
enabled_slides=self._slide_manager.get_enabled_slides(),
|
||||
disabled_slides=self._slide_manager.get_disabled_slides(),
|
||||
)
|
||||
|
||||
def slideshow_slide_add(self):
|
||||
slide = Slide(
|
||||
name=request.form['name'],
|
||||
type=str_to_enum(request.form['type'], SlideType),
|
||||
duration=request.form['duration'],
|
||||
)
|
||||
|
||||
if slide.has_file():
|
||||
if 'object' not in request.files:
|
||||
return redirect(request.url)
|
||||
|
||||
object = request.files['object']
|
||||
|
||||
if object.filename == '':
|
||||
return redirect(request.url)
|
||||
|
||||
if object:
|
||||
object_name = secure_filename(object.filename)
|
||||
object_path = os.path.join(self._app.config['UPLOAD_FOLDER'], object_name)
|
||||
object.save(object_path)
|
||||
slide.location = object_path
|
||||
else:
|
||||
slide.location = request.form['object']
|
||||
|
||||
self._slide_manager.add_form(slide)
|
||||
|
||||
return redirect(url_for('slideshow'))
|
||||
|
||||
def slideshow_slide_edit(self):
|
||||
self._slide_manager.update_form(request.form['id'], request.form['name'], request.form['duration'])
|
||||
return redirect(url_for('slideshow'))
|
||||
|
||||
def slideshow_slide_toggle(self):
|
||||
data = request.get_json()
|
||||
self._slide_manager.update_enabled(data.get('id'), data.get('enabled'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def slideshow_slide_delete(self):
|
||||
data = request.get_json()
|
||||
self._slide_manager.delete(data.get('id'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def slideshow_slide_position(self):
|
||||
data = request.get_json()
|
||||
self._slide_manager.update_positions(data)
|
||||
return jsonify({'status': 'ok'})
|
||||
22
src/controller/SysinfoController.py
Normal file
22
src/controller/SysinfoController.py
Normal file
@ -0,0 +1,22 @@
|
||||
import json
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
||||
from src.utils import get_ip_address
|
||||
|
||||
|
||||
class SysinfoController:
|
||||
|
||||
def __init__(self, app, l):
|
||||
self._app = app
|
||||
self._l = l
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
self._app.add_url_rule('/sysinfo', 'sysinfo', self.sysinfo, methods=['GET'])
|
||||
|
||||
def sysinfo(self):
|
||||
return render_template(
|
||||
'sysinfo/sysinfo.jinja.html',
|
||||
ipaddr=get_ip_address(),
|
||||
l=self._l,
|
||||
)
|
||||
70
src/model/Screen.py
Normal file
70
src/model/Screen.py
Normal file
@ -0,0 +1,70 @@
|
||||
import json
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
class Screen:
|
||||
|
||||
def __init__(self, address: str = '', enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[str] = None):
|
||||
self._id = id if id else None
|
||||
self._address = address
|
||||
self._enabled = enabled
|
||||
self._name = name
|
||||
self._position = position
|
||||
|
||||
@property
|
||||
def id(self) -> Union[int, str]:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return self._address
|
||||
|
||||
@address.setter
|
||||
def address(self, value: str):
|
||||
self._address = value
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, value: bool):
|
||||
self._enabled = value
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
self._name = value
|
||||
|
||||
@property
|
||||
def position(self) -> int:
|
||||
return self._position
|
||||
|
||||
@position.setter
|
||||
def position(self, value: int):
|
||||
self._position = value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Slide(" \
|
||||
f"id='{self.id}',\n" \
|
||||
f"name='{self.name}',\n" \
|
||||
f"enabled='{self.enabled}',\n" \
|
||||
f"position='{self.position}',\n" \
|
||||
f"address='{self.address}',\n" \
|
||||
f")"
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"name": self.name,
|
||||
"id": self.id,
|
||||
"enabled": self.enabled,
|
||||
"position": self.position,
|
||||
"address": self.address,
|
||||
}
|
||||
@ -7,10 +7,8 @@ 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: Union[int, str] = 999, id: Optional[int] = None):
|
||||
if id:
|
||||
self._id = id
|
||||
|
||||
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):
|
||||
self._id = id if id else None
|
||||
self._location = location
|
||||
self._duration = duration
|
||||
self._type = str_to_enum(type, SlideType) if isinstance(type, str) else type
|
||||
@ -19,7 +17,7 @@ class Slide:
|
||||
self._position = position
|
||||
|
||||
@property
|
||||
def id(self) -> Union[int, str]:
|
||||
def id(self) -> Optional[str]:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
|
||||
13
src/utils.py
13
src/utils.py
@ -4,3 +4,16 @@ def str_to_enum(str_val: str, enum_class):
|
||||
if enum_item.value == str_val:
|
||||
return enum_item
|
||||
raise ValueError(f"{str_val} is not a valid {enum_class.__name__} item")
|
||||
|
||||
def get_ip_address():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["ip", "-4", "route", "get", "8.8.8.8"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
ip_address = result.stdout.split()[6]
|
||||
return ip_address
|
||||
except Exception as e:
|
||||
print(f"Error obtaining IP address: {e}")
|
||||
return 'Unknown'
|
||||
@ -24,18 +24,25 @@
|
||||
</h1>
|
||||
<menu>
|
||||
<ul>
|
||||
<li class="{{ 'active' if request.url_rule.endpoint == 'manage' }}">
|
||||
<a href="{{ url_for('manage') }}">
|
||||
<i class="fa-regular fa-clock"></i> {{ l.manage_page_title }}
|
||||
<li class="{{ 'active' if request.url_rule.endpoint == 'slideshow' }}">
|
||||
<a href="{{ url_for('slideshow') }}">
|
||||
<i class="fa-regular fa-clock"></i> {{ l.slideshow_page_title }}
|
||||
</a>
|
||||
</li>
|
||||
{# <li class="{{ 'active' if request.url_rule.endpoint == 'manage_settings' }}">#}
|
||||
{% if FLEET_MODE %}
|
||||
<li class="{{ 'active' if request.url_rule.endpoint == 'fleet' }}">
|
||||
<a href="{{ url_for('fleet') }}">
|
||||
<i class="fa fa-tv"></i> {{ l.fleet_page_title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{# <li class="{{ 'active' if request.url_rule.endpoint == 'settings' }}">#}
|
||||
{# <a href="#">#}
|
||||
{# <i class="fa-solid fa-gear"></i> {{ l.settings_page_title }}#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
<li class="{{ 'active' if request.url_rule.endpoint == 'manage_sysinfo' }}">
|
||||
<a href="{{ url_for('manage_sysinfo') }}">
|
||||
<li class="{{ 'active' if request.url_rule.endpoint == 'sysinfo' }}">
|
||||
<a href="{{ url_for('sysinfo') }}">
|
||||
<i class="fa-solid fa-list-check"></i> {{ l.sysinfo_page_title }}
|
||||
</a>
|
||||
</li>
|
||||
@ -54,7 +61,8 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
<script>
|
||||
var l = {'js_manage_slide_delete_confirmation': '{{ l.manage_slide_delete_confirmation }}'};
|
||||
var l = {'js_slideshow_slide_delete_confirmation': '{{ l.slideshow_slide_delete_confirmation }}'};
|
||||
var l = {'js_fleet_screen_delete_confirmation': '{{ l.js_fleet_screen_delete_confirmation }}'};
|
||||
</script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||
{% block add_js %}{% endblock %}
|
||||
52
views/fleet/component/table.jinja.html
Normal file
52
views/fleet/component/table.jinja.html
Normal file
@ -0,0 +1,52 @@
|
||||
<table class="{{ tclass }}-screens">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ l.fleet_screen_panel_th_name }}</th>
|
||||
<th class="tac">{{ l.fleet_screen_panel_th_address }}</th>
|
||||
<th class="tac">{{ l.fleet_screen_panel_th_enabled }}</th>
|
||||
<th class="tac">{{ l.fleet_screen_panel_th_activity }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="empty-tr {% if screens|length != 0 %}hidden{% endif %}">
|
||||
<td colspan="4">
|
||||
{{ l.fleet_screen_panel_empty|replace(
|
||||
'%link%',
|
||||
('<a href="javascript:void(0);" class="item-add">'~l.fleet_screen_button_add~'</a>')|safe
|
||||
) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% for screen in screens %}
|
||||
<tr class="screen-item" data-level="{{ screen.id }}" data-entity="{{ screen.to_json() }}">
|
||||
<td class="infos">
|
||||
<div class="inner">
|
||||
<a href="javascript:void(0);" class="item-sort screen-sort">
|
||||
<i class="fa fa-sort icon-left"></i>
|
||||
</a>
|
||||
<i class="fa fa-tv icon-left"></i>
|
||||
{{ screen.name }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="tac">
|
||||
{{ screen.address }}
|
||||
</td>
|
||||
<td class="tac">
|
||||
<label class="pure-material-switch">
|
||||
<input type="checkbox" {% if screen.enabled %}checked="checked"{% endif %}><span></span>
|
||||
</label>
|
||||
</td>
|
||||
<td class="actions tac">
|
||||
<a href="javascript:void(0);" class="item-edit screen-edit">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
<a href="{{ screen.address }}" class="item-download screen-download" target="_blank">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
<a href="javascript:void(0);" class="item-delete screen-delete">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
52
views/fleet/fleet.jinja.html
Normal file
52
views/fleet/fleet.jinja.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends 'base.jinja.html' %}
|
||||
|
||||
|
||||
{% block page_title %}
|
||||
{{ l.fleet_page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block add_js %}
|
||||
<script src="{{ STATIC_PREFIX }}js/tablednd-fixed.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/fleet.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<div class="toolbar">
|
||||
<h2>{{ l.fleet_page_title }}</h2>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
<button class="purple screen-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.fleet_screen_button_add }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<h3>{{ l.fleet_screen_panel_active }}</h3>
|
||||
|
||||
{% with tclass='active', screens=enabled_screens %}
|
||||
{% include 'fleet/component/table.jinja.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-inactive">
|
||||
<div class="panel-body">
|
||||
<h3>{{ l.fleet_screen_panel_inactive }}</h3>
|
||||
|
||||
{% with tclass='inactive', screens=disabled_screens %}
|
||||
{% include 'fleet/component/table.jinja.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modals hidden">
|
||||
<div class="modals-outer">
|
||||
<a href="javascript:void(0);" class="modal-close">
|
||||
<i class="fa fa-close"></i>
|
||||
</a>
|
||||
<div class="modals-inner">
|
||||
{% include 'fleet/modal/add.jinja.html' %}
|
||||
{% include 'fleet/modal/edit.jinja.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
30
views/fleet/modal/add.jinja.html
Normal file
30
views/fleet/modal/add.jinja.html
Normal file
@ -0,0 +1,30 @@
|
||||
<div class="modal modal-screen-add">
|
||||
<h2>
|
||||
{{ l.fleet_screen_form_add_title }}
|
||||
</h2>
|
||||
|
||||
<form action="/fleet/screen/add" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="screen-add-name">{{ l.fleet_screen_form_label_name }}</label>
|
||||
<div class="widget">
|
||||
<input name="name" type="text" id="screen-add-name" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="screen-add-address">{{ l.fleet_screen_form_label_address }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="address" id="screen-add-address" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="modal-close">
|
||||
{{ l.fleet_screen_form_button_cancel }}
|
||||
</button>
|
||||
<button type="submit" class="green">
|
||||
<i class="fa fa-plus icon-left"></i> {{ l.fleet_screen_form_add_submit }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
32
views/fleet/modal/edit.jinja.html
Normal file
32
views/fleet/modal/edit.jinja.html
Normal file
@ -0,0 +1,32 @@
|
||||
<div class="modal modal-screen-edit hidden">
|
||||
<h2>
|
||||
{{ l.fleet_screen_form_edit_submit }}
|
||||
</h2>
|
||||
|
||||
<form action="/fleet/screen/edit" method="POST">
|
||||
<input type="hidden" name="id" id="screen-edit-id"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="screen-edit-name">{{ l.fleet_screen_form_label_name }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="name" id="screen-edit-name" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="screen-edit-address">{{ l.fleet_screen_form_label_address }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="address" id="screen-edit-address" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="modal-close">
|
||||
{{ l.fleet_screen_form_button_cancel }}
|
||||
</button>
|
||||
<button type="submit" class="green">
|
||||
<i class="fa fa-save icon-left"></i>{{ l.fleet_screen_form_edit_submit }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -14,10 +14,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="FirstSlide" class="slide" style="visibility: hidden;">
|
||||
<iframe src="/slide/default"></iframe>
|
||||
<iframe src="/player/default"></iframe>
|
||||
</div>
|
||||
<div id="SecondSlide" style="visibility: visible;">
|
||||
<iframe src="/slide/default"></iframe>
|
||||
<iframe src="/player/default"></iframe>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var items = {{items | safe}};
|
||||
@ -26,7 +26,7 @@
|
||||
var curUrl = 0;
|
||||
var nextReady = true;
|
||||
var itemCheck = setInterval(function () {
|
||||
fetch('playlist').then(function(response) {
|
||||
fetch('player/playlist').then(function(response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
<table class="{{ tclass }}-slides">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ l.manage_slide_panel_th_name }}</th>
|
||||
<th class="tac">{{ l.manage_slide_panel_th_duration }}</th>
|
||||
<th class="tac">{{ l.manage_slide_panel_th_enabled }}</th>
|
||||
<th class="tac">{{ l.manage_slide_panel_th_activity }}</th>
|
||||
<th>{{ l.slideshow_slide_panel_th_name }}</th>
|
||||
<th class="tac">{{ l.slideshow_slide_panel_th_duration }}</th>
|
||||
<th class="tac">{{ l.slideshow_slide_panel_th_enabled }}</th>
|
||||
<th class="tac">{{ l.slideshow_slide_panel_th_activity }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="empty-tr {% if slides|length != 0 %}hidden{% endif %}">
|
||||
<td colspan="4">
|
||||
{{ l.manage_slide_panel_empty|replace(
|
||||
{{ l.slideshow_slide_panel_empty|replace(
|
||||
'%link%',
|
||||
('<a href="javascript:void(0);" class="slide-add">'~l.manage_slide_button_add~'</a>')|safe
|
||||
('<a href="javascript:void(0);" class="item-add">'~l.slideshow_slide_button_add~'</a>')|safe
|
||||
) }}
|
||||
</td>
|
||||
</tr>
|
||||
@ -20,7 +20,7 @@
|
||||
<tr class="slide-item" data-level="{{ slide.id }}" data-entity="{{ slide.to_json() }}">
|
||||
<td class="infos">
|
||||
<div class="inner">
|
||||
<a href="javascript:void(0);" class="slide-sort">
|
||||
<a href="javascript:void(0);" class="item-sort slide-sort">
|
||||
<i class="fa fa-sort icon-left"></i>
|
||||
</a>
|
||||
{% set icon_type = 'globe' %}
|
||||
@ -35,7 +35,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="tac">
|
||||
{{ slide.duration }} {{ l.manage_slide_panel_th_duration_unit }}
|
||||
{{ slide.duration }} {{ l.slideshow_slide_panel_th_duration_unit }}
|
||||
</td>
|
||||
<td class="tac">
|
||||
<label class="pure-material-switch">
|
||||
@ -43,13 +43,13 @@
|
||||
</label>
|
||||
</td>
|
||||
<td class="actions tac">
|
||||
<a href="javascript:void(0);" class="slide-edit">
|
||||
<a href="javascript:void(0);" class="item-edit slide-edit">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
<a href="{{ slide.location }}" class="slide-download" target="_blank">
|
||||
<a href="{{ slide.location }}" class="item-download slide-download" target="_blank">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
<a href="javascript:void(0);" class="slide-delete">
|
||||
<a href="javascript:void(0);" class="item-delete slide-delete">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
@ -1,28 +1,28 @@
|
||||
<div class="modal modal-slide-add">
|
||||
<h2>
|
||||
{{ l.manage_slide_form_add_title }}
|
||||
{{ l.slideshow_slide_form_add_title }}
|
||||
</h2>
|
||||
|
||||
<form action="/manage/slide/add" method="POST" enctype="multipart/form-data">
|
||||
<form action="/slideshow/slide/add" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="slide-add-name">{{ l.manage_slide_form_label_name }}</label>
|
||||
<label for="slide-add-name">{{ l.slideshow_slide_form_label_name }}</label>
|
||||
<div class="widget">
|
||||
<input name="name" type="text" id="slide-add-name" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="slide-add-type">{{ l.manage_slide_form_label_type }}</label>
|
||||
<label for="slide-add-type">{{ l.slideshow_slide_form_label_type }}</label>
|
||||
<div class="widget">
|
||||
<select name="type" id="slide-add-type">
|
||||
<option value="url" data-input="text">{{ l.manage_slide_form_label_type_url }}</option>
|
||||
<option value="video" data-input="upload">{{ l.manage_slide_form_label_type_video }}</option>
|
||||
<option value="picture" data-input="upload">{{ l.manage_slide_form_label_type_picture }}</option>
|
||||
<option value="url" data-input="text">{{ l.slideshow_slide_form_label_type_url }}</option>
|
||||
<option value="video" data-input="upload">{{ l.slideshow_slide_form_label_type_video }}</option>
|
||||
<option value="picture" data-input="upload">{{ l.slideshow_slide_form_label_type_picture }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group object-input">
|
||||
<label for="slide-add-duration">{{ l.manage_slide_form_label_object }}</label>
|
||||
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_object }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="object" id="slide-add-object-input-text" class="slide-add-object-input"/>
|
||||
<input type="file" name="object" id="slide-add-object-input-upload"
|
||||
@ -31,19 +31,19 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-add-duration">{{ l.manage_slide_form_label_duration }}</label>
|
||||
<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.manage_slide_form_label_duration_unit }}</span>
|
||||
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="modal-close">
|
||||
{{ l.manage_slide_form_button_cancel }}
|
||||
{{ l.slideshow_slide_form_button_cancel }}
|
||||
</button>
|
||||
<button type="submit" class="green">
|
||||
<i class="fa fa-plus icon-left"></i> {{ l.manage_slide_form_add_submit }}
|
||||
<i class="fa fa-plus icon-left"></i> {{ l.slideshow_slide_form_add_submit }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,49 +1,49 @@
|
||||
<div class="modal modal-slide-edit hidden">
|
||||
<h2>
|
||||
{{ l.manage_slide_form_edit_submit }}
|
||||
{{ l.slideshow_slide_form_edit_submit }}
|
||||
</h2>
|
||||
|
||||
<form action="/manage/slide/edit" method="POST">
|
||||
<form action="/slideshow/slide/edit" method="POST">
|
||||
<input type="hidden" name="id" id="slide-edit-id"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-name">{{ l.manage_slide_form_label_name }}</label>
|
||||
<label for="slide-edit-name">{{ l.slideshow_slide_form_label_name }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="name" id="slide-edit-name" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-type">{{ l.manage_slide_form_label_type }}</label>
|
||||
<label for="slide-edit-type">{{ l.slideshow_slide_form_label_type }}</label>
|
||||
<div class="widget">
|
||||
<select id="slide-edit-type" name="type" disabled="disabled">
|
||||
<option value="url" data-input="text">{{ l.manage_slide_form_label_type_url }}</option>
|
||||
<option value="video" data-input="upload">{{ l.manage_slide_form_label_type_video }}</option>
|
||||
<option value="picture" data-input="upload">{{ l.manage_slide_form_label_type_picture }}</option>
|
||||
<option value="url" data-input="text">{{ l.slideshow_slide_form_label_type_url }}</option>
|
||||
<option value="video" data-input="upload">{{ l.slideshow_slide_form_label_type_video }}</option>
|
||||
<option value="picture" data-input="upload">{{ l.slideshow_slide_form_label_type_picture }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-location">{{ l.manage_slide_form_label_location }}</label>
|
||||
<label for="slide-edit-location">{{ l.slideshow_slide_form_label_location }}</label>
|
||||
<div class="widget">
|
||||
<input type="text" name="location" id="slide-edit-location" disabled="disabled"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slide-edit-duration">{{ l.manage_slide_form_label_duration }}</label>
|
||||
<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.manage_slide_form_label_duration_unit }}</span>
|
||||
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="modal-close">
|
||||
{{ l.manage_slide_form_button_cancel }}
|
||||
{{ l.slideshow_slide_form_button_cancel }}
|
||||
</button>
|
||||
<button type="submit" class="green">
|
||||
<i class="fa fa-save icon-left"></i>{{ l.manage_slide_form_edit_submit }}
|
||||
<i class="fa fa-save icon-left"></i>{{ l.slideshow_slide_form_edit_submit }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -1,38 +1,38 @@
|
||||
{% extends 'manager/base.jinja.html' %}
|
||||
{% extends 'base.jinja.html' %}
|
||||
|
||||
|
||||
{% block page_title %}
|
||||
{{ l.manage_page_title }}
|
||||
{{ l.slideshow_page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block add_js %}
|
||||
<script src="{{ STATIC_PREFIX }}js/tablednd-fixed.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/manage.js"></script>
|
||||
<script src="{{ STATIC_PREFIX }}js/slideshow.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<div class="toolbar">
|
||||
<h2>{{ l.manage_page_title }}</h2>
|
||||
<h2>{{ l.slideshow_page_title }}</h2>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
<button class="purple slide-add"><i class="fa fa-plus icon-left"></i>{{ l.manage_slide_button_add }}</button>
|
||||
<button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<h3>{{ l.manage_slide_panel_active }}</h3>
|
||||
<h3>{{ l.slideshow_slide_panel_active }}</h3>
|
||||
|
||||
{% with tclass='active', slides=enabled_slides %}
|
||||
{% include 'manager/manage-table.jinja.html' %}
|
||||
{% include 'slideshow/component/table.jinja.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-inactive">
|
||||
<div class="panel-body">
|
||||
<h3>{{ l.manage_slide_panel_inactive }}</h3>
|
||||
<h3>{{ l.slideshow_slide_panel_inactive }}</h3>
|
||||
|
||||
{% with tclass='inactive', slides=disabled_slides %}
|
||||
{% include 'manager/manage-table.jinja.html' %}
|
||||
{% include 'slideshow/component/table.jinja.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
@ -44,8 +44,8 @@
|
||||
<i class="fa fa-close"></i>
|
||||
</a>
|
||||
<div class="modals-inner">
|
||||
{% include 'manager/modal/slide-add.jinja.html' %}
|
||||
{% include 'manager/modal/slide-edit.jinja.html' %}
|
||||
{% include 'slideshow/modal/add.jinja.html' %}
|
||||
{% include 'slideshow/modal/edit.jinja.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends 'manager/base.jinja.html' %}
|
||||
{% extends 'base.jinja.html' %}
|
||||
|
||||
|
||||
{% block page_title %}
|
||||
Loading…
Reference in New Issue
Block a user