players explorer ok

This commit is contained in:
jr-k 2024-07-11 20:52:51 +02:00
parent f357b60ceb
commit da57c38ddd
29 changed files with 965 additions and 506 deletions

File diff suppressed because one or more lines are too long

View File

@ -80,6 +80,17 @@
width: 16px; width: 16px;
} }
.explr-item i.fa {
position: absolute;
z-index: 1;
top: 2px;
font-size: 14px;
left: 16px;
color: rgb(187, 187, 187);
width: 18px;
text-align: center;
}
.explr-toggler { .explr-toggler {
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 0 0; background-position: 0 0;
@ -96,7 +107,8 @@
/* Menu icons: */ /* Menu icons: */
.explr-tree .icon-text > li, .explr-tree li.icon-text { background-image: url("../../img/explr/attibutes.png"); } .explr-tree .icon-text > li, .explr-tree li.icon-text { background-image: none; }
.explr-tree .icon-file > li, .explr-tree li.icon-file { background-image: url("../../img/explr/attibutes.png"); }
.explr-tree .icon-address > li, .explr-tree li.icon-address { background-image: url("../../img/explr/address.png"); } .explr-tree .icon-address > li, .explr-tree li.icon-address { background-image: url("../../img/explr/address.png"); }
.explr-tree .icon-archives > li, .explr-tree li.icon-archives { background-image: url("../../img/explr/archives.png"); } .explr-tree .icon-archives > li, .explr-tree li.icon-archives { background-image: url("../../img/explr/archives.png"); }
.explr-tree .icon-badge > li, .explr-tree li.icon-badge { background-image: url("../../img/explr/bestseller.png"); } .explr-tree .icon-badge > li, .explr-tree li.icon-badge { background-image: url("../../img/explr/bestseller.png"); }

View File

@ -0,0 +1,110 @@
jQuery(document).ready(function ($) {
const $tableActive = $('table.active-node-players');
const $tableInactive = $('table.inactive-node-players');
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.node-player-item:visible').length === 0) {
$(this).find('tr.empty-tr').removeClass('hidden');
} else {
$(this).find('tr.empty-tr').addClass('hidden');
}
}).tableDnDUpdate();
updatePositions();
};
const updatePositions = function (table, row) {
const positions = {};
$('.node-player-item').each(function (index) {
positions[getId($(this))] = index;
});
$.ajax({
method: 'POST',
url: '/fleet/node-player/position',
headers: {'Content-Type': 'application/json'},
data: JSON.stringify(positions),
});
};
const main = function () {
$("table").tableDnD({
dragHandle: 'td a.node-player-sort',
onDrop: updatePositions
});
};
$(document).on('change', 'select.group-picker', function () {
document.location.href = $(this).val();
});
$(document).on('change', 'input[type=checkbox]', function () {
$.ajax({
url: '/fleet/node-player/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', '#node-player-add-type', function () {
const value = $(this).val();
const inputType = $(this).find('option').filter(function (i, el) {
return $(el).val() === value;
}).data('input');
$('.node-player-add-object-input')
.addClass('hidden')
.prop('disabled', true)
.filter('#node-player-add-object-input-' + inputType)
.removeClass('hidden')
.prop('disabled', false)
;
});
$(document).on('click', '.node-player-add', function () {
showModal('modal-node-player-add');
$('.modal-node-player-add input:eq(0)').focus().select();
});
$(document).on('click', '.node-player-edit', function () {
const nodePlayer = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
showModal('modal-node-player-edit');
$('.modal-node-player-edit input:visible:eq(0)').focus().select();
$('#node-player-edit-name').val(nodePlayer.name);
$('#node-player-edit-group-id').val(nodePlayer.group_id);
$('#node-player-edit-host').val(nodePlayer.host);
$('#node-player-edit-id').val(nodePlayer.id);
});
$(document).on('click', '.node-player-delete', function () {
if (confirm(l.js_fleet_node_player_delete_confirmation)) {
const $tr = $(this).parents('tr:eq(0)');
$tr.remove();
updateTable();
$.ajax({
method: 'DELETE',
url: '/fleet/node-player/delete',
headers: {'Content-Type': 'application/json'},
data: JSON.stringify({id: getId($(this))}),
});
}
});
main();
});

View File

@ -1,108 +1,120 @@
jQuery(document).ready(function ($) { jQuery(document).ready(function ($) {
const $tableActive = $('table.active-node-players'); const initExplr = function () {
const $tableInactive = $('table.inactive-node-players'); $('.explr').each(function() {
$(this).explr({
classesPlus: 'fa fa-plus',
classesMinus: 'fa fa-minus',
onLoadFinish: function ($tree) {
$tree.removeClass('hidden');
}
});
const getId = function ($el) { // Open complete path in explorer sidebar
return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level'); explrSidebarOpenFromFolder($(this).attr('data-working-folder-id'));
});
}; };
const updateTable = function () { const initDrags = function () {
$('table').each(function () { $('.draggable').each(function() {
if ($(this).find('tbody tr.node-player-item:visible').length === 0) { $(this).draggable({
$(this).find('tr.empty-tr').removeClass('hidden'); revert: "invalid",
} else { });
$(this).find('tr.empty-tr').addClass('hidden');
}
}).tableDnDUpdate();
updatePositions();
};
const updatePositions = function (table, row) {
const positions = {};
$('.node-player-item').each(function (index) {
positions[getId($(this))] = index;
}); });
$.ajax({ $('.droppable').each(function() {
method: 'POST', $(this).droppable({
url: '/fleet/node-player/position', accept: ".draggable",
headers: {'Content-Type': 'application/json'}, over: function (event, ui) {
data: JSON.stringify(positions), $(this).addClass("highlight-drop");
},
out: function (event, ui) {
$(this).removeClass("highlight-drop");
},
drop: function (event, ui) {
$(this).removeClass("highlight-drop");
const $form = $('#folder-move-form');
const $moved = ui.draggable;
const $target = $(this);
$form.find('[name=is_folder]').val($moved.attr('data-folder'))
$form.find('[name=entity_id]').val($moved.attr('data-id'))
$form.find('[name=new_folder_id]').val($target.attr('data-id'))
ui.draggable.position({
my: "center",
at: "center",
of: $(this),
using: function (pos) {
$(this).animate(pos, 50);
}
});
$form.submit();
}
});
}); });
}; };
const main = function () { const main = function () {
$("table").tableDnD({ initExplr();
dragHandle: 'td a.node-player-sort', initDrags();
onDrop: updatePositions
});
}; };
$(document).on('change', 'select.group-picker', function () { $(document).on('click', '.folder-add', function () {
document.location.href = $(this).val(); $('.dirview .new-folder').removeClass('hidden');
$('.page-content').animate({scrollTop: 0}, 0);
$('.dirview input').focus();
}); });
$(document).on('change', 'input[type=checkbox]', function () {
$.ajax({
url: '/fleet/node-player/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', '#node-player-add-type', function () {
const value = $(this).val();
const inputType = $(this).find('option').filter(function (i, el) {
return $(el).val() === value;
}).data('input');
$('.node-player-add-object-input')
.addClass('hidden')
.prop('disabled', true)
.filter('#node-player-add-object-input-' + inputType)
.removeClass('hidden')
.prop('disabled', false)
;
});
$(document).on('click', '.node-player-add', function () { $(document).on('click', '.node-player-add', function () {
showModal('modal-node-player-add'); showModal('modal-node-player-add');
$('.modal-node-player-add input:eq(0)').focus().select(); $('.modal-node-player-add input:eq(0)').focus().select();
}); });
$(document).on('click', '.node-player-edit', function () { $(document).on('click', '.explr-item-edit', function () {
const nodePlayer = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity')); const $item = $('.explr-dirview .highlight-clicked');
showModal('modal-node-player-edit'); const is_folder = $item.attr('data-folder') === '1';
$('.modal-node-player-edit input:visible:eq(0)').focus().select();
$('#node-player-edit-name').val(nodePlayer.name); if (is_folder) {
$('#node-player-edit-group-id').val(nodePlayer.group_id); $item.addClass('renaming');
$('#node-player-edit-host').val(nodePlayer.host); $item.find('input').focus().select();
$('#node-player-edit-id').val(nodePlayer.id); } else {
document.location.href = $(this).attr('data-node-player-route').replace('!c!', $item.attr('data-id'));
}
}); });
$(document).on('click', '.node-player-delete', function () { $(document).on('click', '.explr-item-delete', function () {
const $item = $('.explr-dirview .highlight-clicked');
const is_folder = $item.attr('data-folder') === '1';
let route;
if (is_folder) {
route = $(this).attr('data-folder-route') + '?id=' + $item.attr('data-id');
} else {
route = $(this).attr('data-node-player-route') + '?id=' + $item.attr('data-id');
}
if (confirm(l.js_fleet_node_player_delete_confirmation)) { if (confirm(l.js_fleet_node_player_delete_confirmation)) {
const $tr = $(this).parents('tr:eq(0)'); document.location.href = route;
$tr.remove(); }
updateTable(); });
$.ajax({
method: 'DELETE', $(document).on('click', '.node-player-edit', function () {
url: '/fleet/node-player/delete', const node_player = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
headers: {'Content-Type': 'application/json'}, showModal('modal-node-player-edit');
data: JSON.stringify({id: getId($(this))}),
});
$('.modal-node-player-edit input:visible:eq(0)').focus().select();
$('#node-player-edit-id').val(node_player.id);
});
$(document).on('submit', '.modal-node-player-add form', function () {
const $modal = $(this).parents('.modal:eq(0)');
$modal.find('button[type=submit]').addClass('hidden');
$modal.find('.btn-loading').removeClass('hidden');
});
$(document).keyup(function (e) {
if (e.key === "Escape") {
$('.dirview .new-folder').addClass('hidden');
} }
}); });

View File

@ -123,7 +123,7 @@ jQuery(document).ready(function ($) {
$(document).on('click', '.explr-item-delete', function () { $(document).on('click', '.explr-item-delete', function () {
const $item = $('.explr-dirview .highlight-clicked'); const $item = $('.explr-dirview .highlight-clicked');
const is_folder = $item.attr('data-folder') === '1'; const is_folder = $item.attr('data-folder') === '1';
let route = document.location.href; let route;
if (is_folder) { if (is_folder) {
route = $(this).attr('data-folder-route') + '?id=' + $item.attr('data-id'); route = $(this).attr('data-folder-route') + '?id=' + $item.attr('data-id');
@ -136,16 +136,6 @@ jQuery(document).ready(function ($) {
} }
}); });
$(document).on('click', '.content-edit', function () {
const content = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
showModal('modal-content-edit');
$('.modal-content-edit input:visible:eq(0)').focus().select();
$('#content-edit-id').val(content.id);
});
$(document).on('submit', '.modal-content-add form', function () { $(document).on('submit', '.modal-content-add form', function () {
const $modal = $(this).parents('.modal:eq(0)'); const $modal = $(this).parents('.modal:eq(0)');
$modal.find('button[type=submit]').addClass('hidden'); $modal.find('button[type=submit]').addClass('hidden');

View File

@ -34,5 +34,6 @@
// Import pages styles // Import pages styles
@import 'pages/content'; @import 'pages/content';
@import 'pages/node_player';
//@import 'pages/settings'; //@import 'pages/settings';
//@import 'pages/sysinfo'; //@import 'pages/sysinfo';

View File

@ -0,0 +1,22 @@
.view-node-player-list main .main-container {
}
.view-node-player-edit main .main-container {
.bottom-content {
.page-content {
flex: 1;
.form-holder {
margin: 20px 20px 20px 10px;
}
}
}
}

View File

@ -49,8 +49,6 @@
"slideshow_content_page_title": "Content Library", "slideshow_content_page_title": "Content Library",
"slideshow_content_button_add": "New Content", "slideshow_content_button_add": "New Content",
"slideshow_content_button_add_folder": "New Folder",
"slideshow_content_folder_not_empty_error": "Folder isn't empty, you must delete its content first",
"slideshow_content_referenced_in_slide_error": "Content is referenced in a slide, remove slide first", "slideshow_content_referenced_in_slide_error": "Content is referenced in a slide, remove slide first",
"slideshow_content_panel_active": "Content", "slideshow_content_panel_active": "Content",
"slideshow_content_panel_empty": "Currently, there are no content. %link% now.", "slideshow_content_panel_empty": "Currently, there are no content. %link% now.",
@ -106,6 +104,7 @@
"fleet_node_player_form_label_name": "Name", "fleet_node_player_form_label_name": "Name",
"fleet_node_player_form_label_group_id": "Group", "fleet_node_player_form_label_group_id": "Group",
"fleet_node_player_form_label_host": "Host", "fleet_node_player_form_label_host": "Host",
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancel", "fleet_node_player_form_button_cancel": "Cancel",
"js_fleet_node_player_delete_confirmation": "Are you sure?", "js_fleet_node_player_delete_confirmation": "Are you sure?",
@ -228,6 +227,8 @@
"common_validate": "Validate", "common_validate": "Validate",
"common_apply": "Apply", "common_apply": "Apply",
"common_saved": "Changes have been saved", "common_saved": "Changes have been saved",
"common_new_folder": "New Folder",
"common_folder_not_empty_error": "Folder isn't empty, you must delete its content first",
"logout": "Logout", "logout": "Logout",
"login_error_not_found": "Bad credentials", "login_error_not_found": "Bad credentials",
"login_error_bad_credentials": "Bad credentials", "login_error_bad_credentials": "Bad credentials",
@ -266,6 +267,15 @@
"enum_content_type_video_object_label": "Upload your video (MP4 only)", "enum_content_type_video_object_label": "Upload your video (MP4 only)",
"enum_content_type_picture_object_label": "Upload your image", "enum_content_type_picture_object_label": "Upload your image",
"enum_content_type_youtube_object_label": "Enter Youtube video URL", "enum_content_type_youtube_object_label": "Enter Youtube video URL",
"enum_operating_system_raspbian": "Raspbian",
"enum_operating_system_windows": "Windows",
"enum_operating_system_macos": "MacOS",
"enum_operating_system_fedora": "Fedora",
"enum_operating_system_ubuntu": "Ubuntu",
"enum_operating_system_suse": "Suse",
"enum_operating_system_redhat": "Redhat",
"enum_operating_system_centos": "CentOS",
"enum_operating_system_other": "Other",
"sysinfo_rpi_model": "Raspberry Pi Model", "sysinfo_rpi_model": "Raspberry Pi Model",
"sysinfo_rpi_model_unknown": "Not a Raspberry Pi or model information not available", "sysinfo_rpi_model_unknown": "Not a Raspberry Pi or model information not available",

View File

@ -49,8 +49,6 @@
"slideshow_content_page_title": "Biblioteca de contenidos", "slideshow_content_page_title": "Biblioteca de contenidos",
"slideshow_content_button_add": "Nuevo Contenido", "slideshow_content_button_add": "Nuevo Contenido",
"slideshow_content_button_add_folder": "Nuevo Carpeta",
"slideshow_content_folder_not_empty_error": "La carpeta no está vacía, primero debes eliminar su contenido",
"slideshow_content_referenced_in_slide_error": "Se hace referencia al contenido en una diapositiva; elimine la diapositiva primero", "slideshow_content_referenced_in_slide_error": "Se hace referencia al contenido en una diapositiva; elimine la diapositiva primero",
"slideshow_content_panel_active": "Contenido", "slideshow_content_panel_active": "Contenido",
"slideshow_content_panel_empty": "Actualmente, no hay contenido. %link% ahora.", "slideshow_content_panel_empty": "Actualmente, no hay contenido. %link% ahora.",
@ -106,6 +104,7 @@
"fleet_node_player_form_label_name": "Nombre", "fleet_node_player_form_label_name": "Nombre",
"fleet_node_player_form_label_group_id": "Grupo", "fleet_node_player_form_label_group_id": "Grupo",
"fleet_node_player_form_label_host": "Host", "fleet_node_player_form_label_host": "Host",
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancelar", "fleet_node_player_form_button_cancel": "Cancelar",
"js_fleet_node_player_delete_confirmation": "¿Estás seguro?", "js_fleet_node_player_delete_confirmation": "¿Estás seguro?",
@ -228,6 +227,8 @@
"common_validate": "Validar", "common_validate": "Validar",
"common_apply": "Aplicar", "common_apply": "Aplicar",
"common_saved": "Los cambios se han guardado", "common_saved": "Los cambios se han guardado",
"common_new_folder": "Nuevo Carpeta",
"common_folder_not_empty_error": "La carpeta no está vacía, primero debes eliminar su contenido",
"logout": "Cerrar sesión", "logout": "Cerrar sesión",
"login_error_not_found": "Credenciales incorrectas", "login_error_not_found": "Credenciales incorrectas",
"login_error_bad_credentials": "Credenciales incorrectas", "login_error_bad_credentials": "Credenciales incorrectas",
@ -266,6 +267,15 @@
"enum_content_type_video_object_label": "Sube tu vídeo (solo MP4)", "enum_content_type_video_object_label": "Sube tu vídeo (solo MP4)",
"enum_content_type_picture_object_label": "Sube tu imagen", "enum_content_type_picture_object_label": "Sube tu imagen",
"enum_content_type_youtube_object_label": "Ingrese la URL del vídeo de Youtube", "enum_content_type_youtube_object_label": "Ingrese la URL del vídeo de Youtube",
"enum_operating_system_raspbian": "Raspbian",
"enum_operating_system_windows": "Windows",
"enum_operating_system_macos": "MacOS",
"enum_operating_system_fedora": "Fedora",
"enum_operating_system_ubuntu": "Ubuntu",
"enum_operating_system_suse": "Suse",
"enum_operating_system_redhat": "Redhat",
"enum_operating_system_centos": "CentOS",
"enum_operating_system_other": "Otro",
"sysinfo_rpi_model": "Modelo de Raspberry Pi", "sysinfo_rpi_model": "Modelo de Raspberry Pi",
"sysinfo_rpi_model_unknown": "No es una Raspberry Pi o la información del modelo no está disponible", "sysinfo_rpi_model_unknown": "No es una Raspberry Pi o la información del modelo no está disponible",

View File

@ -49,8 +49,6 @@
"slideshow_content_page_title": "Bibliothèque de contenus", "slideshow_content_page_title": "Bibliothèque de contenus",
"slideshow_content_button_add": "Nouveau Contenu", "slideshow_content_button_add": "Nouveau Contenu",
"slideshow_content_button_add_folder": "Nouveau Dossier",
"slideshow_content_folder_not_empty_error": "Le dossier n'est pas vide, vous devez d'abord supprimer son contenu",
"slideshow_content_referenced_in_slide_error": "Le contenu est référencé dans une slide, supprimez d'abord la slide", "slideshow_content_referenced_in_slide_error": "Le contenu est référencé dans une slide, supprimez d'abord la slide",
"slideshow_content_panel_active": "Contenus", "slideshow_content_panel_active": "Contenus",
"slideshow_content_panel_empty": "Actuellement, il n'y a aucun contenu. %link% maintenant.", "slideshow_content_panel_empty": "Actuellement, il n'y a aucun contenu. %link% maintenant.",
@ -106,6 +104,7 @@
"fleet_node_player_form_label_name": "Nom", "fleet_node_player_form_label_name": "Nom",
"fleet_node_player_form_label_group_id": "Groupe", "fleet_node_player_form_label_group_id": "Groupe",
"fleet_node_player_form_label_host": "Hôte", "fleet_node_player_form_label_host": "Hôte",
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Annuler", "fleet_node_player_form_button_cancel": "Annuler",
"js_fleet_node_player_delete_confirmation": "Êtes-vous sûr ?", "js_fleet_node_player_delete_confirmation": "Êtes-vous sûr ?",
@ -228,6 +227,8 @@
"common_validate": "Valider", "common_validate": "Valider",
"common_apply": "Appliquer", "common_apply": "Appliquer",
"common_saved": "Les modifications ont été enregistrées", "common_saved": "Les modifications ont été enregistrées",
"common_new_folder": "Nouveau Dossier",
"common_folder_not_empty_error": "Le dossier n'est pas vide, vous devez d'abord supprimer son contenu",
"logout": "Déconnexion", "logout": "Déconnexion",
"login_error_not_found": "Identifiants invalides", "login_error_not_found": "Identifiants invalides",
"login_error_bad_credentials": "Identifiants invalides", "login_error_bad_credentials": "Identifiants invalides",
@ -266,6 +267,15 @@
"enum_content_type_video_object_label": "Uploadez votre vidéo (MP4 seulement)", "enum_content_type_video_object_label": "Uploadez votre vidéo (MP4 seulement)",
"enum_content_type_picture_object_label": "Uploadez votre image", "enum_content_type_picture_object_label": "Uploadez votre image",
"enum_content_type_youtube_object_label": "Enrez l'URL de la vidéo Youtube", "enum_content_type_youtube_object_label": "Enrez l'URL de la vidéo Youtube",
"enum_operating_system_raspbian": "Raspbian",
"enum_operating_system_windows": "Windows",
"enum_operating_system_macos": "MacOS",
"enum_operating_system_fedora": "Fedora",
"enum_operating_system_ubuntu": "Ubuntu",
"enum_operating_system_suse": "Suse",
"enum_operating_system_redhat": "Redhat",
"enum_operating_system_centos": "CentOS",
"enum_operating_system_other": "Autre",
"sysinfo_rpi_model": "Modèle du Raspberry Pi", "sysinfo_rpi_model": "Modèle du Raspberry Pi",
"sysinfo_rpi_model_unknown": "Le modèle n'est pas un Raspberry Pi", "sysinfo_rpi_model_unknown": "Le modèle n'est pas un Raspberry Pi",

View File

@ -49,8 +49,6 @@
"slideshow_content_page_title": "Libreria dei contenuti", "slideshow_content_page_title": "Libreria dei contenuti",
"slideshow_content_button_add": "Nuovo Contenuto", "slideshow_content_button_add": "Nuovo Contenuto",
"slideshow_content_button_add_folder": "Nuovo Cartella",
"slideshow_content_folder_not_empty_error": "La cartella non è vuota, devi prima eliminarne il contenuto",
"slideshow_content_referenced_in_slide_error": "Si fa riferimento al contenuto in una diapositiva, rimuovere prima la diapositiva", "slideshow_content_referenced_in_slide_error": "Si fa riferimento al contenuto in una diapositiva, rimuovere prima la diapositiva",
"slideshow_content_panel_active": "Contenuti", "slideshow_content_panel_active": "Contenuti",
"slideshow_content_panel_empty": "Attualmente non ci sono contenuti. %link% adesso.", "slideshow_content_panel_empty": "Attualmente non ci sono contenuti. %link% adesso.",
@ -106,6 +104,7 @@
"fleet_node_player_form_label_name": "Nome", "fleet_node_player_form_label_name": "Nome",
"fleet_node_player_form_label_group_id": "Group", "fleet_node_player_form_label_group_id": "Group",
"fleet_node_player_form_label_host": "Host", "fleet_node_player_form_label_host": "Host",
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancella", "fleet_node_player_form_button_cancel": "Cancella",
"js_fleet_node_player_delete_confirmation": "Sei sicuro?", "js_fleet_node_player_delete_confirmation": "Sei sicuro?",
@ -228,6 +227,8 @@
"common_validate": "Convalida", "common_validate": "Convalida",
"common_apply": "Applica", "common_apply": "Applica",
"common_saved": "Le modifiche sono state salvate", "common_saved": "Le modifiche sono state salvate",
"common_new_folder": "Nuovo Cartella",
"common_folder_not_empty_error": "La cartella non è vuota, devi prima eliminarne il contenuto",
"logout": "Logout", "logout": "Logout",
"login_error_not_found": "Credenziali errate", "login_error_not_found": "Credenziali errate",
"login_error_bad_credentials": "Credenziali errate", "login_error_bad_credentials": "Credenziali errate",
@ -266,6 +267,15 @@
"enum_content_type_video_object_label": "Carica il tuo video (solo MP4)", "enum_content_type_video_object_label": "Carica il tuo video (solo MP4)",
"enum_content_type_picture_object_label": "Carica la tua immagine", "enum_content_type_picture_object_label": "Carica la tua immagine",
"enum_content_type_youtube_object_label": "Inserisci l'URL del video Youtube", "enum_content_type_youtube_object_label": "Inserisci l'URL del video Youtube",
"enum_operating_system_raspbian": "Raspbian",
"enum_operating_system_windows": "Windows",
"enum_operating_system_macos": "MacOS",
"enum_operating_system_fedora": "Fedora",
"enum_operating_system_ubuntu": "Ubuntu",
"enum_operating_system_suse": "Suse",
"enum_operating_system_redhat": "Redhat",
"enum_operating_system_centos": "CentOS",
"enum_operating_system_other": "Altro",
"sysinfo_rpi_model": "Raspberry Pi Model", "sysinfo_rpi_model": "Raspberry Pi Model",
"sysinfo_rpi_model_unknown": "Informazioni Raspberry Pi non disponibili", "sysinfo_rpi_model_unknown": "Informazioni Raspberry Pi non disponibili",

View File

@ -6,7 +6,6 @@ from flask import Flask, render_template, redirect, request, url_for, send_from_
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from src.service.ModelStore import ModelStore from src.service.ModelStore import ModelStore
from src.model.entity.Content import Content from src.model.entity.Content import Content
from src.model.entity.Folder import Folder
from src.model.enum.ContentType import ContentType from src.model.enum.ContentType import ContentType
from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH
from src.interface.ObController import ObController from src.interface.ObController import ObController
@ -18,16 +17,16 @@ class ContentController(ObController):
def register(self): def register(self):
self._app.add_url_rule('/slideshow/content', 'slideshow_content_list', self._auth(self.slideshow_content_list), methods=['GET']) self._app.add_url_rule('/slideshow/content', 'slideshow_content_list', self._auth(self.slideshow_content_list), methods=['GET'])
self._app.add_url_rule('/slideshow/content/add-folder', 'slideshow_content_folder_add', self._auth(self.slideshow_content_folder_add), methods=['POST'])
self._app.add_url_rule('/slideshow/content/move-folder', 'slideshow_content_folder_move', self._auth(self.slideshow_content_folder_move), methods=['POST'])
self._app.add_url_rule('/slideshow/content/rename-folder', 'slideshow_content_folder_rename', self._auth(self.slideshow_content_folder_rename), methods=['POST'])
self._app.add_url_rule('/slideshow/content/delete-folder', 'slideshow_content_folder_delete', self._auth(self.slideshow_content_folder_delete), methods=['GET'])
self._app.add_url_rule('/slideshow/content/add', 'slideshow_content_add', self._auth(self.slideshow_content_add), methods=['GET', 'POST']) self._app.add_url_rule('/slideshow/content/add', 'slideshow_content_add', self._auth(self.slideshow_content_add), methods=['GET', 'POST'])
self._app.add_url_rule('/slideshow/content/edit/<content_id>', 'slideshow_content_edit', self._auth(self.slideshow_content_edit), methods=['GET']) self._app.add_url_rule('/slideshow/content/edit/<content_id>', 'slideshow_content_edit', self._auth(self.slideshow_content_edit), methods=['GET'])
self._app.add_url_rule('/slideshow/content/save/<content_id>', 'slideshow_content_save', self._auth(self.slideshow_content_save), methods=['POST']) self._app.add_url_rule('/slideshow/content/save/<content_id>', 'slideshow_content_save', self._auth(self.slideshow_content_save), methods=['POST'])
self._app.add_url_rule('/slideshow/content/delete', 'slideshow_content_delete', self._auth(self.slideshow_content_delete), methods=['GET']) self._app.add_url_rule('/slideshow/content/delete', 'slideshow_content_delete', self._auth(self.slideshow_content_delete), methods=['GET'])
self._app.add_url_rule('/slideshow/content/show/<content_id>', 'slideshow_content_show', self._auth(self.slideshow_content_show), methods=['GET'])
self._app.add_url_rule('/slideshow/content/cd', 'slideshow_content_cd', self._auth(self.slideshow_content_cd), methods=['GET']) self._app.add_url_rule('/slideshow/content/cd', 'slideshow_content_cd', self._auth(self.slideshow_content_cd), methods=['GET'])
self._app.add_url_rule('/slideshow/content/add-folder', 'slideshow_content_folder_add', self._auth(self.slideshow_content_folder_add), methods=['POST'])
self._app.add_url_rule('/slideshow/content/move-folder', 'slideshow_content_folder_move', self._auth(self.slideshow_content_folder_move), methods=['POST'])
self._app.add_url_rule('/slideshow/content/rename-folder', 'slideshow_content_folder_rename', self._auth(self.slideshow_content_folder_rename), methods=['POST'])
self._app.add_url_rule('/slideshow/content/delete-folder', 'slideshow_content_folder_delete', self._auth(self.slideshow_content_folder_delete), methods=['GET'])
self._app.add_url_rule('/slideshow/content/show/<content_id>', 'slideshow_content_show', self._auth(self.slideshow_content_show), methods=['GET'])
def slideshow_content_list(self): def slideshow_content_list(self):
working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string() working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string()
@ -39,52 +38,11 @@ class ContentController(ObController):
folders_tree=self._model_store.folder().get_folder_tree(FolderEntity.CONTENT), folders_tree=self._model_store.folder().get_folder_tree(FolderEntity.CONTENT),
working_folder_path=working_folder_path, working_folder_path=working_folder_path,
working_folder=working_folder, working_folder=working_folder,
working_folder_children=self._model_store.folder().get_children(working_folder, sort='created_at', ascending=False), working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.CONTENT, sort='created_at', ascending=False),
enum_content_type=ContentType, enum_content_type=ContentType,
enum_folder_entity=FolderEntity, enum_folder_entity=FolderEntity,
) )
def slideshow_content_folder_add(self):
self._model_store.folder().add_folder(
entity=FolderEntity.CONTENT,
name=request.form['name'],
)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_folder_rename(self):
self._model_store.folder().rename_folder(
folder_id=request.form['id'],
name=request.form['name'],
)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_folder_move(self):
self._model_store.folder().move_to_folder(
entity_id=request.form['entity_id'],
folder_id=request.form['new_folder_id'],
entity_is_folder=True if request.form['is_folder'] == '1' else False,
)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_folder_delete(self):
folder = self._model_store.folder().get(request.args.get('id'))
if not folder:
return redirect(url_for('slideshow_content_list'))
content_counter = self._model_store.content().count_contents_for_folder(folder.id)
folder_counter = self._model_store.folder().count_subfolders_for_folder(folder.id)
if content_counter > 0 or folder_counter:
return redirect(url_for('slideshow_content_list', folder_not_empty_error=True))
self._model_store.folder().delete(id=folder.id)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_add(self): def slideshow_content_add(self):
working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string() working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string()
working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.CONTENT) working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.CONTENT)
@ -169,6 +127,47 @@ class ContentController(ObController):
return redirect(url_for('slideshow_content_list', path=path)) return redirect(url_for('slideshow_content_list', path=path))
def slideshow_content_folder_add(self):
self._model_store.folder().add_folder(
entity=FolderEntity.CONTENT,
name=request.form['name'],
)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_folder_rename(self):
self._model_store.folder().rename_folder(
folder_id=request.form['id'],
name=request.form['name'],
)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_folder_move(self):
self._model_store.folder().move_to_folder(
entity_id=request.form['entity_id'],
folder_id=request.form['new_folder_id'],
entity_is_folder=True if request.form['is_folder'] == '1' else False,
)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_folder_delete(self):
folder = self._model_store.folder().get(request.args.get('id'))
if not folder:
return redirect(url_for('slideshow_content_list'))
content_counter = self._model_store.content().count_contents_for_folder(folder.id)
folder_counter = self._model_store.folder().count_subfolders_for_folder(folder.id)
if content_counter > 0 or folder_counter:
return redirect(url_for('slideshow_content_list', folder_not_empty_error=True))
self._model_store.folder().delete(id=folder.id)
return redirect(url_for('slideshow_content_list'))
def slideshow_content_show(self, content_id: int = 0): def slideshow_content_show(self, content_id: int = 0):
content = self._model_store.content().get(content_id) content = self._model_store.content().get(content_id)

View File

@ -1,9 +1,12 @@
import json import json
from flask import Flask, render_template, redirect, request, url_for, jsonify from flask import Flask, render_template, redirect, request, url_for, jsonify, abort
from src.service.ModelStore import ModelStore from src.service.ModelStore import ModelStore
from src.model.entity.NodePlayer import NodePlayer from src.model.entity.NodePlayer import NodePlayer
from src.interface.ObController import ObController from src.interface.ObController import ObController
from src.model.enum.OperatingSystem import OperatingSystem
from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH
from src.util.utils import str_to_enum
class FleetNodePlayerController(ObController): class FleetNodePlayerController(ObController):
@ -17,62 +20,151 @@ class FleetNodePlayerController(ObController):
return decorated_function return decorated_function
def register(self): def register(self):
self._app.add_url_rule('/fleet/node-player/list', 'fleet_node_player_list', self.guard_fleet(self._auth(self.fleet_node_player_list)), methods=['GET']) self._app.add_url_rule('/fleet/node-player', 'fleet_node_player_list', self.guard_fleet(self._auth(self.fleet_node_player_list)), methods=['GET'])
self._app.add_url_rule('/fleet/node-player/group/set/<group_id>', 'fleet_node_player_list_group_use', self._auth(self.fleet_node_player_list), methods=['GET']) self._app.add_url_rule('/fleet/node-player/add', 'fleet_node_player_add', self.guard_fleet(self._auth(self.fleet_node_player_add)), methods=['GET', 'POST'])
self._app.add_url_rule('/fleet/node-player/add', 'fleet_node_player_add', self.guard_fleet(self._auth(self.fleet_node_player_add)), methods=['POST']) self._app.add_url_rule('/fleet/node-player/edit/<node_player_id>', 'fleet_node_player_edit', self._auth(self.fleet_node_player_edit), methods=['GET'])
self._app.add_url_rule('/fleet/node-player/edit', 'fleet_node_player_edit', self.guard_fleet(self._auth(self.fleet_node_player_edit)), methods=['POST']) self._app.add_url_rule('/fleet/node-player/save/<node_player_id>', 'fleet_node_player_save', self._auth(self.fleet_node_player_save), methods=['POST'])
self._app.add_url_rule('/fleet/node-player/toggle', 'fleet_node_player_toggle', self.guard_fleet(self._auth(self.fleet_node_player_toggle)), methods=['POST']) self._app.add_url_rule('/fleet/node-player/delete', 'fleet_node_player_delete', self.guard_fleet(self._auth(self.fleet_node_player_delete)), methods=['GET'])
self._app.add_url_rule('/fleet/node-player/delete', 'fleet_node_player_delete', self.guard_fleet(self._auth(self.fleet_node_player_delete)), methods=['DELETE']) self._app.add_url_rule('/fleet/node-player/cd', 'fleet_node_player_cd', self._auth(self.fleet_node_player_cd), methods=['GET'])
self._app.add_url_rule('/fleet/node-player/position', 'fleet_node_player_position', self.guard_fleet(self._auth(self.fleet_node_player_position)), methods=['POST']) self._app.add_url_rule('/fleet/node-player/add-folder', 'fleet_node_player_folder_add', self._auth(self.fleet_node_player_folder_add), methods=['POST'])
self._app.add_url_rule('/fleet/node-player/move-folder', 'fleet_node_player_folder_move', self._auth(self.fleet_node_player_folder_move), methods=['POST'])
self._app.add_url_rule('/fleet/node-player/rename-folder', 'fleet_node_player_folder_rename', self._auth(self.fleet_node_player_folder_rename), methods=['POST'])
self._app.add_url_rule('/fleet/node-player/delete-folder', 'fleet_node_player_folder_delete', self._auth(self.fleet_node_player_folder_delete), methods=['GET'])
def fleet_node_player_list(self, group_id: int = 0): def fleet_node_player_list(self):
current_group = self._model_store.node_player_group().get(group_id) working_folder_path = self._model_store.variable().get_one_by_name('last_folder_node_player').as_string()
group_id = current_group.id if current_group else None working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.NODE_PLAYER)
return render_template( return render_template(
'fleet/player/list.jinja.html', 'fleet/node-players/list.jinja.html',
current_group=current_group, node_players=self._model_store.node_player().get_all_indexed('folder_id', multiple=True),
groups=self._model_store.node_player_group().get_all_labels_indexed(), folders_tree=self._model_store.folder().get_folder_tree(FolderEntity.NODE_PLAYER),
enabled_node_players=self._model_store.node_player().get_node_players(group_id=group_id, enabled=True), working_folder_path=working_folder_path,
disabled_node_players=self._model_store.node_player().get_node_players(group_id=group_id, enabled=False) working_folder=working_folder,
working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.NODE_PLAYER, sort='created_at', ascending=False),
enum_operating_system=OperatingSystem,
enum_folder_entity=FolderEntity,
) )
def fleet_node_player_add(self): def fleet_node_player_add(self):
node_player = NodePlayer( working_folder_path = self._model_store.variable().get_one_by_name('last_folder_node_player').as_string()
working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.NODE_PLAYER)
self._model_store.node_player().add_form(
NodePlayer(
name=request.form['name'],
host=request.form['host'],
operating_system=str_to_enum(request.form['operating_system'], OperatingSystem),
folder_id=working_folder.id if working_folder else None,
)
)
return redirect(url_for('fleet_node_player_list'))
def fleet_node_player_edit(self, node_player_id: int = 0):
node_player = self._model_store.node_player().get(node_player_id)
if not node_player:
return abort(404)
working_folder_path = self._model_store.variable().get_one_by_name('last_folder_node_player').as_string()
working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.NODE_PLAYER)
return render_template(
'fleet/node-players/edit.jinja.html',
node_player=node_player,
working_folder_path=working_folder_path,
working_folder=working_folder,
enum_operating_system=OperatingSystem,
)
def fleet_node_player_save(self, node_player_id: int = 0):
node_player = self._model_store.node_player().get(node_player_id)
if not node_player:
return redirect(url_for('fleet_node_player_list'))
self._model_store.node_player().update_form(
id=node_player.id,
name=request.form['name'], name=request.form['name'],
operating_system=str_to_enum(request.form['operating_system'], OperatingSystem),
host=request.form['host'], host=request.form['host'],
group_id=request.form['group_id'] if 'group_id' in request.form and request.form['group_id'] else None,
) )
self._model_store.node_player().add_form(node_player) self._post_update()
if node_player.group_id: return redirect(url_for('fleet_node_player_edit', node_player_id=node_player_id, saved=1))
return redirect(url_for('fleet_node_player_list_group_use', group_id=node_player.group_id))
return redirect(url_for('fleet_node_player_list'))
def fleet_node_player_edit(self):
node_player = self._model_store.node_player().update_form(
request.form['id'],
request.form['name'],
request.form['host'],
request.form['group_id']
)
if node_player.group_id:
return redirect(url_for('fleet_node_player_list_group_use', group_id=node_player.group_id))
return redirect(url_for('fleet_node_player_list'))
def fleet_node_player_toggle(self):
data = request.get_json()
self._model_store.node_player().update_enabled(data.get('id'), data.get('enabled'))
return jsonify({'status': 'ok'})
def fleet_node_player_delete(self): def fleet_node_player_delete(self):
data = request.get_json() node_player = self._model_store.node_player().get(request.args.get('id'))
self._model_store.node_player().delete(data.get('id'))
return jsonify({'status': 'ok'})
def fleet_node_player_position(self): if not node_player:
data = request.get_json() return redirect(url_for('fleet_node_player_list'))
self._model_store.node_player().update_positions(data)
return jsonify({'status': 'ok'}) self._model_store.node_player().delete(node_player.id)
self._post_update()
return redirect(url_for('fleet_node_player_list'))
def fleet_node_player_cd(self):
path = request.args.get('path')
if path == FOLDER_ROOT_PATH:
self._model_store.variable().update_by_name("last_folder_node_player", FOLDER_ROOT_PATH)
return redirect(url_for('fleet_node_player_list', path=FOLDER_ROOT_PATH))
if not path:
return abort(404)
cd_folder = self._model_store.folder().get_one_by_path(
path=path,
entity=FolderEntity.NODE_PLAYER
)
if not cd_folder:
return abort(404)
self._model_store.variable().update_by_name("last_folder_node_player", path)
return redirect(url_for('fleet_node_player_list', path=path))
def fleet_node_player_folder_add(self):
self._model_store.folder().add_folder(
entity=FolderEntity.NODE_PLAYER,
name=request.form['name'],
)
return redirect(url_for('fleet_node_player_list'))
def fleet_node_player_folder_rename(self):
self._model_store.folder().rename_folder(
folder_id=request.form['id'],
name=request.form['name'],
)
return redirect(url_for('fleet_node_player_list'))
def fleet_node_player_folder_move(self):
self._model_store.folder().move_to_folder(
entity_id=request.form['entity_id'],
folder_id=request.form['new_folder_id'],
entity_is_folder=True if request.form['is_folder'] == '1' else False,
)
return redirect(url_for('fleet_node_player_list'))
def fleet_node_player_folder_delete(self):
folder = self._model_store.folder().get(request.args.get('id'))
if not folder:
return redirect(url_for('fleet_node_player_list'))
node_player_counter = self._model_store.node_player().count_node_players_for_folder(folder.id)
folder_counter = self._model_store.folder().count_subfolders_for_folder(folder.id)
if node_player_counter > 0 or folder_counter:
return redirect(url_for('fleet_node_player_list', folder_not_empty_error=True))
self._model_store.folder().delete(id=folder.id)
return redirect(url_for('fleet_node_player_list'))
def _post_update(self):
pass

View File

@ -63,7 +63,7 @@ class ContentManager(ModelManager):
return self.hydrate_object(object) return self.hydrate_object(object)
def get_all(self, sort: Optional[str] = 'created_at', ascending=False) -> List[Content]: def get_all(self, sort: Optional[str] = 'created_at', ascending=False) -> List[Content]:
return self.hydrate_list(self._db.get_all(self.TABLE_NAME, sort=sort, ascending=ascending)) return self.hydrate_list(self._db.get_all(table_name=self.TABLE_NAME, sort=sort, ascending=ascending))
def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, Content]: def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, Content]:
index = {} index = {}

View File

@ -4,6 +4,7 @@ from src.model.entity.Folder import Folder
from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH, FOLDER_ROOT_NAME from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH, FOLDER_ROOT_NAME
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
from src.manager.ContentManager import ContentManager from src.manager.ContentManager import ContentManager
from src.manager.NodePlayerManager import NodePlayerManager
from src.manager.LangManager import LangManager from src.manager.LangManager import LangManager
from src.manager.UserManager import UserManager from src.manager.UserManager import UserManager
from src.manager.VariableManager import VariableManager from src.manager.VariableManager import VariableManager
@ -47,11 +48,16 @@ class FolderManager(ModelManager):
def get_by_entity(self, entity: FolderEntity) -> List[Folder]: def get_by_entity(self, entity: FolderEntity) -> List[Folder]:
return self.get_by("entity = '{}'".format(entity.value)) return self.get_by("entity = '{}'".format(entity.value))
def get_children(self, folder: Optional[Folder], sort: Optional[str] = None, ascending=True) -> List[Folder]: def get_children(self, folder: Optional[Folder], entity: Optional[FolderEntity] = None, sort: Optional[str] = None, ascending=True) -> List[Folder]:
if folder: query = " 1=1 "
return self.get_by("parent_id = {}".format(folder.id), sort, ascending)
return self.get_by("parent_id is null", sort, ascending) if entity:
query = "{} {}".format(query, "AND entity = '{}'".format(entity.value))
if folder:
return self.get_by("parent_id = {} AND {}".format(folder.id, query), sort, ascending)
return self.get_by("parent_id is null AND {}".format(query), sort, ascending)
def get_one_by_path(self, path: str, entity: FolderEntity) -> Folder: def get_one_by_path(self, path: str, entity: FolderEntity) -> Folder:
parts = path[1:].split('/') parts = path[1:].split('/')
@ -138,9 +144,16 @@ class FolderManager(ModelManager):
params=(folder_id if folder else None, folder.depth + 1 if folder else 1, entity_id) params=(folder_id if folder else None, folder.depth + 1 if folder else 1, entity_id)
) )
table = None
if folder.entity == FolderEntity.CONTENT: if folder.entity == FolderEntity.CONTENT:
table = ContentManager.TABLE_NAME
elif folder.entity == FolderEntity.NODE_PLAYER:
table = NodePlayerManager.TABLE_NAME
if table:
return self._db.execute_write_query( return self._db.execute_write_query(
query="UPDATE {} set folder_id = ? WHERE id = ?".format(ContentManager.TABLE_NAME), query="UPDATE {} set folder_id = ? WHERE id = ?".format(table),
params=(folder_id, entity_id) params=(folder_id, entity_id)
) )
@ -148,6 +161,8 @@ class FolderManager(ModelManager):
var_name = None var_name = None
if entity == FolderEntity.CONTENT: if entity == FolderEntity.CONTENT:
var_name = "last_folder_content" var_name = "last_folder_content"
elif entity == FolderEntity.NODE_PLAYER:
var_name = "last_folder_node_player"
if not var_name: if not var_name:
raise Error("No variable for entity {}".format(entity.value)) raise Error("No variable for entity {}".format(entity.value))
@ -164,7 +179,7 @@ class FolderManager(ModelManager):
def add_folder(self, entity: FolderEntity, name: str) -> Folder: def add_folder(self, entity: FolderEntity, name: str) -> Folder:
working_folder_path = self.get_working_folder(entity) working_folder_path = self.get_working_folder(entity)
working_folder = self.get_one_by_path(path=working_folder_path, entity=FolderEntity.CONTENT) working_folder = self.get_one_by_path(path=working_folder_path, entity=entity)
folder_path = "{}/{}".format(working_folder_path, name) folder_path = "{}/{}".format(working_folder_path, name)
parts = folder_path[1:].split('/') parts = folder_path[1:].split('/')
depth = len(parts) - 1 depth = len(parts) - 1

View File

@ -1,6 +1,7 @@
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.NodePlayer import NodePlayer from src.model.entity.NodePlayer import NodePlayer
from src.model.enum.OperatingSystem import OperatingSystem
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.manager.UserManager import UserManager
@ -13,10 +14,10 @@ class NodePlayerManager(ModelManager):
TABLE_NAME = "fleet_player" TABLE_NAME = "fleet_player"
TABLE_MODEL = [ TABLE_MODEL = [
"name CHAR(255)", "name CHAR(255)",
"enabled INTEGER DEFAULT 0",
"group_id INTEGER",
"position INTEGER",
"host CHAR(255)", "host CHAR(255)",
"operating_system CHAR(100)",
"folder_id INTEGER",
"group_id INTEGER",
"created_by CHAR(255)", "created_by CHAR(255)",
"updated_by CHAR(255)", "updated_by CHAR(255)",
"created_at INTEGER", "created_at INTEGER",
@ -31,6 +32,11 @@ class NodePlayerManager(ModelManager):
if id: if id:
raw_node_player['id'] = id raw_node_player['id'] = id
[raw_node_player, user_tracker_edits] = self.user_manager.initialize_user_trackers(raw_node_player)
if len(user_tracker_edits) > 0:
self._db.update_by_id(self.TABLE_NAME, raw_node_player['id'], user_tracker_edits)
return NodePlayer(**raw_node_player) return NodePlayer(**raw_node_player)
def hydrate_list(self, raw_node_players: list) -> List[NodePlayer]: def hydrate_list(self, raw_node_players: list) -> List[NodePlayer]:
@ -51,17 +57,22 @@ class NodePlayerManager(ModelManager):
return self.hydrate_object(object) return self.hydrate_object(object)
def get_all(self, sort: bool = False) -> List[NodePlayer]: def get_all(self, sort: Optional[str] = 'created_at', ascending=False) -> List[NodePlayer]:
return self.hydrate_list(self._db.get_all(self.TABLE_NAME, "position" if sort else None)) return self.hydrate_list(self._db.get_all(table_name=self.TABLE_NAME, sort=sort, ascending=ascending))
def get_node_players(self, group_id: Optional[int] = None, enabled: bool = True) -> List[NodePlayer]: def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, NodePlayer]:
query = "enabled = {}".format("1" if enabled else "0") index = {}
if group_id:
query = "{} {}".format(query, "AND group_id = {}".format(group_id))
else:
query = "{} {}".format(query, "AND group_id is NULL")
return self.get_by(query=query, sort="position") for item in self.get_node_players():
id = getattr(item, attribute)
if multiple:
if id not in index:
index[id] = []
index[id].append(item)
else:
index[id] = item
return index
def forget_for_user(self, user_id: int): def forget_for_user(self, user_id: int):
node_players = self.get_by("created_by = '{}' or updated_by = '{}'".format(user_id, user_id)) node_players = self.get_by("created_by = '{}' or updated_by = '{}'".format(user_id, user_id))
@ -70,6 +81,17 @@ class NodePlayerManager(ModelManager):
for node_player_id, edits in edits_node_players.items(): for node_player_id, edits in edits_node_players.items():
self._db.update_by_id(self.TABLE_NAME, node_player_id, edits) self._db.update_by_id(self.TABLE_NAME, node_player_id, edits)
def get_node_players(self, group_id: Optional[int] = None, folder_id: Optional[id] = None) -> List[NodePlayer]:
query = " 1=1 "
if group_id:
query = "{} {}".format(query, "AND group_id = {}".format(group_id))
if folder_id:
query = "{} {}".format(query, "AND folder_id = {}".format(folder_id))
return self.get_by(query=query)
def pre_add(self, node_player: Dict) -> Dict: def pre_add(self, node_player: Dict) -> Dict:
self.user_manager.track_user_on_create(node_player) self.user_manager.track_user_on_create(node_player)
self.user_manager.track_user_on_update(node_player) self.user_manager.track_user_on_update(node_player)
@ -91,21 +113,7 @@ class NodePlayerManager(ModelManager):
def post_delete(self, node_player_id: str) -> str: def post_delete(self, node_player_id: str) -> str:
return node_player_id return node_player_id
def get_enabled_node_players(self) -> List[NodePlayer]: def update_form(self, id: int, name: str, host: str, operating_system: Optional[OperatingSystem] = None, group_id: Optional[int] = None) -> NodePlayer:
return self.get_by(query="enabled = 1", sort="position")
def get_disabled_node_players(self) -> List[NodePlayer]:
return self.get_by(query="enabled = 0", sort="position")
def update_enabled(self, id: int, enabled: bool) -> None:
self._db.update_by_id(self.TABLE_NAME, id, self.pre_update({"enabled": enabled, "position": 999}))
self.post_update(id)
def update_positions(self, positions: list) -> None:
for node_player_id, node_player_position in positions.items():
self._db.update_by_id(self.TABLE_NAME, node_player_id, {"position": node_player_position})
def update_form(self, id: int, name: str, host: str, group_id: Optional[int]) -> NodePlayer:
node_player = self.get(id) node_player = self.get(id)
if not node_player: if not node_player:
@ -114,6 +122,7 @@ class NodePlayerManager(ModelManager):
form = { form = {
"name": name, "name": name,
"host": host, "host": host,
"operating_system": operating_system,
"group_id": group_id if group_id else None "group_id": group_id if group_id else None
} }
@ -141,3 +150,6 @@ class NodePlayerManager(ModelManager):
def count_node_players_for_group(self, id: int) -> int: def count_node_players_for_group(self, id: int) -> int:
return len(self.get_node_players(group_id=id)) return len(self.get_node_players(group_id=id))
def count_node_players_for_folder(self, folder_id: int) -> int:
return len(self.get_node_players(folder_id=folder_id))

View File

@ -133,6 +133,7 @@ class VariableManager:
# Not editable (System information) # Not editable (System information)
{"name": "last_folder_content", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_content')}, {"name": "last_folder_content", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_content')},
{"name": "last_folder_node_player", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_node_player')},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')}, {"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_last_slide_update')}, {"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_last_slide_update')},
{"name": "refresh_player_request", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_refresh_player_request')}, {"name": "refresh_player_request", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_refresh_player_request')},

View File

@ -2,17 +2,19 @@ import json
import time import time
from typing import Optional, Union from typing import Optional, Union
from src.model.enum.OperatingSystem import OperatingSystem
from src.util.utils import str_to_enum
class NodePlayer: class NodePlayer:
def __init__(self, host: str = '', enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None, group_id: Optional[int] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): def __init__(self, host: str = '', name: str = 'Untitled', operating_system: Optional[OperatingSystem] = None, id: Optional[int] = None, group_id: Optional[int] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None, folder_id: Optional[int] = None):
self._id = id if id else None self._id = id if id else None
self._group_id = group_id self._group_id = group_id
self._host = host self._host = host
self._enabled = enabled self._operating_system = str_to_enum(operating_system, OperatingSystem) if isinstance(operating_system, str) else operating_system
self._name = name self._name = name
self._position = position self._folder_id = folder_id
self._created_by = created_by if created_by else None self._created_by = created_by if created_by else None
self._updated_by = updated_by if updated_by else None self._updated_by = updated_by if updated_by else None
self._created_at = int(created_at if created_at else time.time()) self._created_at = int(created_at if created_at else time.time())
@ -38,14 +40,6 @@ class NodePlayer:
def host(self, value: str): def host(self, value: str):
self._host = value self._host = value
@property
def enabled(self) -> bool:
return bool(self._enabled)
@enabled.setter
def enabled(self, value: bool):
self._enabled = bool(value)
@property @property
def name(self) -> str: def name(self) -> str:
return self._name return self._name
@ -54,14 +48,6 @@ class NodePlayer:
def name(self, value: str): def name(self, value: str):
self._name = value self._name = value
@property
def position(self) -> int:
return self._position
@position.setter
def position(self, value: int):
self._position = value
@property @property
def created_by(self) -> str: def created_by(self) -> str:
return self._created_by return self._created_by
@ -78,6 +64,14 @@ class NodePlayer:
def updated_by(self, value: str): def updated_by(self, value: str):
self._updated_by = value self._updated_by = value
@property
def folder_id(self) -> Optional[int]:
return self._folder_id
@folder_id.setter
def folder_id(self, value: Optional[int]):
self._folder_id = value
@property @property
def created_at(self) -> int: def created_at(self) -> int:
return self._created_at return self._created_at
@ -94,18 +88,26 @@ class NodePlayer:
def updated_at(self, value: int): def updated_at(self, value: int):
self._updated_at = value self._updated_at = value
@property
def operating_system(self) -> Optional[OperatingSystem]:
return self._operating_system
@operating_system.setter
def operating_system(self, value: Optional[OperatingSystem]):
self._operating_system = value
def __str__(self) -> str: def __str__(self) -> str:
return f"NodePlayer(" \ return f"NodePlayer(" \
f"id='{self.id}',\n" \ f"id='{self.id}',\n" \
f"group_id='{self.group_id}',\n" \ f"group_id='{self.group_id}',\n" \
f"name='{self.name}',\n" \ f"name='{self.name}',\n" \
f"enabled='{self.enabled}',\n" \ f"operating_system='{self.operating_system}',\n" \
f"position='{self.position}',\n" \
f"host='{self.host}',\n" \ f"host='{self.host}',\n" \
f"created_by='{self.created_by}',\n" \ f"created_by='{self.created_by}',\n" \
f"updated_by='{self.updated_by}',\n" \ f"updated_by='{self.updated_by}',\n" \
f"created_at='{self.created_at}',\n" \ f"created_at='{self.created_at}',\n" \
f"updated_at='{self.updated_at}',\n" \ f"updated_at='{self.updated_at}',\n" \
f"folder_id='{self.folder_id}',\n" \
f")" f")"
def to_json(self, edits: dict = {}) -> str: def to_json(self, edits: dict = {}) -> str:
@ -121,11 +123,11 @@ class NodePlayer:
"id": self.id, "id": self.id,
"group_id": self.group_id, "group_id": self.group_id,
"name": self.name, "name": self.name,
"enabled": self.enabled, "operating_system": self.operating_system.value,
"position": self.position,
"host": self.host, "host": self.host,
"created_by": self.created_by, "created_by": self.created_by,
"updated_by": self.updated_by, "updated_by": self.updated_by,
"created_at": self.created_at, "created_at": self.created_at,
"updated_at": self.updated_at, "updated_at": self.updated_at,
"folder_id": self.folder_id,
} }

View File

@ -33,7 +33,7 @@ class ContentType(Enum):
return ContentInputType.TEXT return ContentInputType.TEXT
@staticmethod @staticmethod
def get_fa_icon(value: Enum) -> ContentInputType: def get_fa_icon(value: Enum) -> str:
if value == ContentType.PICTURE: if value == ContentType.PICTURE:
return 'fa-regular fa-image' return 'fa-regular fa-image'
elif value == ContentType.VIDEO: elif value == ContentType.VIDEO:
@ -46,7 +46,7 @@ class ContentType(Enum):
return 'fa-file' return 'fa-file'
@staticmethod @staticmethod
def get_color_icon(value: Enum) -> ContentInputType: def get_color_icon(value: Enum) -> str:
if value == ContentType.PICTURE: if value == ContentType.PICTURE:
return 'info' return 'info'
elif value == ContentType.VIDEO: elif value == ContentType.VIDEO:

View File

@ -0,0 +1,37 @@
from enum import Enum
class OperatingSystem(Enum):
RASPBIAN = 'raspbian'
WINDOWS = 'windows'
MACOS = 'macos'
FEDORA = 'fedora'
UBUNTU = 'ubuntu'
SUSE = 'suse'
REDHAT = 'redhat'
CENTOS = 'centos'
OTHER = 'other'
@staticmethod
def get_fa_icon(value: Enum) -> str:
if value == OperatingSystem.RASPBIAN:
return 'fa-brands fa-raspberry-pi'
elif value == OperatingSystem.WINDOWS:
return 'fa-brands fa-windows'
elif value == OperatingSystem.MACOS:
return 'fa-brands fa-apple'
elif value == OperatingSystem.FEDORA:
return 'fa-brands fa-fedora'
elif value == OperatingSystem.UBUNTU:
return 'fa-brands fa-ubuntu'
elif value == OperatingSystem.SUSE:
return 'fa-brands fa-suse'
elif value == OperatingSystem.REDHAT:
return 'fa-brands fa-redhat'
elif value == OperatingSystem.CENTOS:
return 'fa-brands fa-centos'
elif value == OperatingSystem.OTHER:
return 'fa-server'
return 'fa-server'

View File

@ -0,0 +1,105 @@
{% set active_pill_route='fleet_node_player_list' %}
{% extends 'base.jinja.html' %}
{% block page_title %}
{{ l.fleet_node_player_page_title }}
{% endblock %}
{% block add_css %}
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/flatpickr.min.css"/>
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/>
{{ HOOK(H_SLIDESHOW_CONTENT_CSS) }}
{% endblock %}
{% block add_js %}
<script src="{{ STATIC_PREFIX }}js/lib/jquery-explr-1.4.js"></script>
<script src="{{ STATIC_PREFIX }}js/fleet/node-players.js"></script>
{{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }}
{% endblock %}
{% block body_class %}view-node-player-edit edit-page{% endblock %}
{% block page %}
<div class="top-content">
<h1>
{{ l.fleet_node_player_form_edit_title }}
</h1>
</div>
{% if request.args.get('saved') %}
<div class="alert alert-success alert-timeout">
<i class="fa fa-check icon-left"></i>
{{ l.common_saved }}
</div>
{% endif %}
<div class="bottom-content">
<div class="page-content">
<div class="inner dirview">
<div class="breadcrumb-container">
<ul class="breadcrumb">
{% set ns = namespace(breadpath='') %}
{% for dir in working_folder_path[1:].split('/') %}
{% set ns.breadpath = ns.breadpath ~ '/' ~ dir %}
<li>
<a href="{{ url_for('fleet_node_player_cd', path=ns.breadpath) }}">
<i class="explr-icon explr-icon-folder"></i>
{{ truncate(dir, 25, '...') }}
</a>
</li>
{% if not loop.last %}
<li class="divider">
<i class="fa fa-chevron-right"></i>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="horizontal">
<div class="form-holder">
<form class="form" action="{{ url_for('fleet_node_player_save', node_player_id=node_player.id) }}" method="POST">
<div class="form-group">
<label for="node-player-edit-name">{{ l.fleet_node_player_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="node-player-edit-name" required="required" value="{{ node_player.name }}" />
</div>
</div>
<div class="form-group">
<label for="node-player-edit-host">{{ l.fleet_node_player_form_label_host }}</label>
<div class="widget">
<input type="text" name="host" id="node-player-edit-host" required="required" value="{{ node_player.host }}" />
</div>
</div>
<div class="form-group">
<label for="node-player-add-operating-system">{{ l.fleet_node_player_form_label_operating_system }}</label>
<div class="widget">
<select name="operating_system" id="node-player-add-operating-system">
{% for os in enum_operating_system %}
<option value="{{ os.value }}">
{{ t(os) }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="actions actions-left">
<button type="submit" class="folder-edit btn-info">
<i class="fa fa-save icon-left"></i>
{{ l.common_save }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,195 @@
{% extends 'base.jinja.html' %}
{% block page_title %}
{{ l.fleet_node_player_page_title }}
{% endblock %}
{% block add_css %}
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/>
{{ HOOK(H_SLIDESHOW_CONTENT_CSS) }}
{% endblock %}
{% block add_js %}
<script src="{{ STATIC_PREFIX }}js/lib/jquery-explr-1.4.js"></script>
<script src="{{ STATIC_PREFIX }}js/fleet/node-players.js"></script>
<script src="{{ STATIC_PREFIX }}js/lib/jquery-ui.min.js"></script>
{{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }}
{% endblock %}
{% block body_class %}view-node-player-list{% endblock %}
{% block page %}
<div class="top-content">
<h1>
{{ l.fleet_node_player_page_title }}
</h1>
<div class="top-actions">
{{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START) }}
<div class="explr-selection-actions">
<button class="explr-item-edit btn-info" data-node-player-route="{{ url_for('fleet_node_player_edit', node_player_id='!c!') }}">
<i class="fa fa-pencil"></i>
</button>
<button class="explr-item-delete btn-danger-alt" data-folder-route="{{ url_for('fleet_node_player_folder_delete') }}" data-node-player-route="{{ url_for('fleet_node_player_delete') }}">
<i class="fa fa-trash-alt"></i>
</button>
</div>
<button class="btn btn-info node-player-add item-add">
<i class="fa fa-file-circle-plus icon-left"></i>
{{ l.fleet_node_player_button_add }}
</button>
<button class="folder-add btn-neutral">
<i class="fa fa-folder-plus icon-left"></i>
{{ l.common_new_folder }}
</button>
{{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END) }}
</div>
</div>
{% if request.args.get('folder_not_empty_error') %}
<div class="alert alert-danger">
<i class="fa fa-warning icon-left"></i>
{{ l.common_folder_not_empty_error }}
</div>
{% endif %}
{% if request.args.get('referenced_in_node_player_group_error') %}
<div class="alert alert-danger">
<i class="fa fa-warning icon-left"></i>
{{ l.fleet_node_player_referenced_in_node_player_group_error }}
</div>
{% endif %}
<div class="bottom-content">
<div class="page-panel left-panel explr-explorer">
{% macro render_folder(folder) %}
{% set node_player_children = node_players[folder.id]|default([]) %}
{% set has_children = folder.children or node_player_children %}
<li class="icon-folder li-explr-folder li-explr-folder-{{ folder.id }}">
<a href="{{ url_for('fleet_node_player_cd') }}?path={{ folder.path }}" class="{% if folder.path == working_folder_path %}active{% endif %}">
{{ folder.name }}
</a>
{% if has_children %}
<ul>
{% for child in folder.children %}
{{ render_folder(child) }}
{% endfor %}
{% for node_player in node_player_children %}
{% set icon = enum_operating_system.get_fa_icon(node_player.operating_system) %}
<li class="explr-item">
<i class="fa {{ icon }}"></i>
<a href="{{ url_for('fleet_node_player_edit', node_player_id=node_player.id) }}">
{{ node_player.name }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endmacro %}
<ul class="explr hidden" id="tree" data-working-folder-id="{{ working_folder.id }}">
{{ render_folder(folders_tree) }}
</ul>
</div>
<form id="folder-move-form" action="{{ url_for('fleet_node_player_folder_move') }}" class="hidden" method="POST">
<input type="hidden" name="entity_id" />
<input type="hidden" name="new_folder_id" />
<input type="hidden" name="is_folder" />
</form>
<div class="page-content">
<div class="inner dirview">
<div class="breadcrumb-container">
<ul class="breadcrumb">
{% set ns = namespace(breadpath='') %}
{% for dir in working_folder_path[1:].split('/') %}
{% set ns.breadpath = ns.breadpath ~ '/' ~ dir %}
<li>
{% if loop.last %}
<span>
<i class="explr-icon explr-icon-folder"></i>
{{ truncate(dir, 25, '...') }}
</span>
{% else %}
<a href="{{ url_for('fleet_node_player_cd', path=ns.breadpath) }}">
<i class="explr-icon explr-icon-folder"></i>
{{ truncate(dir, 25, '...') }}
</a>
{% endif %}
</li>
{% if not loop.last %}
<li class="divider">
<i class="fa fa-chevron-right"></i>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<ul class="explr-dirview">
<li class="new-folder hidden">
<a href="javascript:void(0);">
<i class="fa fa-folder"></i>
<form action="{{ url_for('fleet_node_player_folder_add') }}" method="POST">
<input type="text" name="name" autocomplete="off" />
</form>
</a>
</li>
{% set parent_path = '/'.join(working_folder_path.rstrip('/').split('/')[:-1]) %}
{% if parent_path %}
<li class="previous-folder droppable" data-path="{{ parent_path }}" data-id="{{ working_folder.parent_id }}" data-folder="1">
<a href="{{ url_for('fleet_node_player_cd', path=parent_path) }}" class="explr-link">
<i class="fa fa-folder"></i>
..
</a>
</li>
{% endif %}
{% for folder in working_folder_children %}
{% set folder_path = working_folder_path ~ '/' ~ folder.name %}
<li class="draggable droppable" data-path="{{ folder_path }}" data-id="{{ folder.id }}" data-folder="1">
<a href="{{ url_for('fleet_node_player_cd', path=folder_path) }}" class="explr-link explr-item-selectable">
<i class="fa fa-folder"></i>
<span>{{ truncate(folder.name, 25, '...') }}</span>
<form action="{{ url_for('fleet_node_player_folder_rename') }}" method="POST">
<input type="text" name="name" value="{{ folder.name }}" autocomplete="off" />
<input type="hidden" name="id" value="{{ folder.id }}" />
</form>
</a>
</li>
{% endfor %}
{% for node_player in node_players[working_folder.id|default(None)]|default([]) %}
{% set icon = enum_operating_system.get_fa_icon(node_player.operating_system) %}
<li class="draggable" data-path="{{ working_folder_path }}" data-id="{{ node_player.id }}" data-folder="0">
<a href="{{ url_for('fleet_node_player_edit', node_player_id=node_player.id) }}" target="_blank" class="explr-link explr-item-selectable">
<i class="fa {{ icon }}"></i>
{{ truncate(node_player.name, 25, '...') }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="modals hidden">
<div class="modals-outer">
<div class="modals-inner">
{% include 'fleet/node-players/modal/add.jinja.html' %}
{% include 'core/utrack.jinja.html' %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,47 @@
<div class="modal modal-node-player-add modal-node-player">
<h2>
{{ l.fleet_node_player_form_add_title }}
</h2>
<form class="form" action="{{ url_for('fleet_node_player_add') }}" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="node-player-add-name">{{ l.fleet_node_player_form_label_name }}</label>
<div class="widget">
<input name="name" type="text" id="node-player-add-name" required="required"/>
</div>
</div>
<div class="form-group">
<label for="node-player-add-host">{{ l.fleet_node_player_form_label_host }}</label>
<div class="widget">
<input name="host" type="text" id="node-player-add-host" required="required"/>
</div>
</div>
<div class="form-group">
<label for="node-player-add-operating-system">{{ l.fleet_node_player_form_label_operating_system }}</label>
<div class="widget">
<select name="operating_system" id="node-player-add-operating-system">
{% for os in enum_operating_system %}
<option value="{{ os.value }}">
{{ t(os) }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="actions">
<button type="button" class="btn btn-naked modal-close">
{{ l.common_close }}
</button>
<button type="submit" class="btn btn-info">
<i class="fa fa-save icon-left"></i>{{ l.common_save }}
</button>
<button type="button" disabled="disabled" class="btn btn-naked hidden btn-loading">
{{ l.common_loading }}
</button>
</div>
</form>
</div>

View File

@ -1,67 +0,0 @@
<table class="{{ tclass }}-node-players">
<thead>
<tr>
<th>{{ l.fleet_node_player_panel_th_name }}</th>
{% if AUTH_ENABLED %}
<th class="tac">
<i class="fa fa-user"></i>
</th>
{% endif %}
<th class="tac">{{ l.fleet_node_player_panel_th_host }}</th>
<th class="tac">{{ l.fleet_node_player_panel_th_enabled }}</th>
<th class="tac">{{ l.fleet_node_player_panel_th_activity }}</th>
</tr>
</thead>
<tbody>
<tr class="empty-tr {% if node_players|length != 0 %}hidden{% endif %}">
<td colspan="4">
{{ l.fleet_node_player_panel_empty|replace(
'%link%',
('<a href="javascript:void(0);" class="item-add node-player-add">'~l.fleet_node_player_button_add~'</a>')|safe
) }}
</td>
</tr>
{% for node_player in node_players %}
<tr class="node-player-item" data-level="{{ node_player.id }}" data-entity="{{ node_player.to_json({"created_by": track_created(node_player).username, "updated_by": track_updated(node_player).username}) }}">
<td class="infos">
<div class="inner">
<a href="javascript:void(0);" class="item-sort node-player-sort">
<i class="fa fa-sort icon-left"></i>
</a>
<div class="badge"><i class="fa fa-key icon-left"></i> {{ node_player.id }}</div>
<i class="fa fa-tv icon-left"></i>
{{ node_player.name }}
</div>
</td>
{% if AUTH_ENABLED %}
<td class="tac">
{% set creator = track_created(node_player) %}
{% if creator.username %}
<a href="javascript:void(0);" class="badge item-utrack node-player-utrack {% if not creator.enabled %}anonymous{% endif %}">
{{ creator.username }}
</a>
{% endif %}
</td>
{% endif %}
<td class="tac">
{{ node_player.host }}
</td>
<td class="tac">
<label class="pure-material-switch">
<input type="checkbox" {% if node_player.enabled %}checked="checked"{% endif %}><span></span>
</label>
</td>
<td class="actions tac">
<a href="javascript:void(0);" class="item-edit node-player-edit">
<i class="fa fa-pencil"></i>
</a>
<a href="javascript:void(0);" class="item-delete node-player-delete">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -1,73 +0,0 @@
{% extends 'base.jinja.html' %}
{% block page_title %}
{{ l.fleet_node_player_page_title }}
{% endblock %}
{% block add_css %}
{{ HOOK(H_FLEET_NODE_PLAYER_CSS) }}
{% endblock %}
{% block add_js %}
<script src="{{ STATIC_PREFIX }}js/lib/tablednd-fixed.js"></script>
<script src="{{ STATIC_PREFIX }}js/fleet/node-players.js"></script>
{{ HOOK(H_FLEET_NODE_PLAYER_JAVASCRIPT) }}
{% endblock %}
{% block page %}
<div class="toolbar">
<h2><i class="fa fa-tv icon-left"></i>{{ l.fleet_node_player_page_title }}</h2>
<div class="toolbar-actions">
{{ HOOK(H_FLEET_NODE_PLAYER_TOOLBAR_ACTIONS_START) }}
<button class="purple node-player-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.fleet_node_player_button_add }}</button>
<select class="select-item-picker group-picker">
<option value="{{ url_for('fleet_node_player_list') }}" {% if not current_group %}selected="selected"{% endif %}>
{{ l.common_default_node_player_group }}
</option>
{% for group_id, group_name in groups.items() %}
{% set is_active_group = str(current_group.id) == str(group_id) %}
<option value="{{ url_for('fleet_node_player_list_group_use', group_id=group_id) }}" {% if is_active_group %}selected="selected"{% endif %}>
{{ group_name }}
</option>
{% endfor %}
</select>
{{ HOOK(H_FLEET_NODE_PLAYER_TOOLBAR_ACTIONS_END) }}
</div>
</div>
<div class="panel">
<div class="panel-body">
<h3>{{ l.fleet_node_player_panel_active }}</h3>
{% with tclass='active', node_players=enabled_node_players %}
{% include 'fleet/player/component/table.jinja.html' %}
{% endwith %}
</div>
</div>
<div class="panel panel-inactive">
<div class="panel-body">
<h3>{{ l.fleet_node_player_panel_inactive }}</h3>
{% with tclass='inactive', node_players=disabled_node_players %}
{% include 'fleet/player/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/player/modal/add.jinja.html' %}
{% include 'fleet/player/modal/edit.jinja.html' %}
{% include 'core/utrack.jinja.html' %}
</div>
</div>
</div>
{% endblock %}

View File

@ -1,42 +0,0 @@
<div class="modal modal-node-player-add">
<h2>
{{ l.fleet_node_player_form_add_title }}
</h2>
<form class="form" action="/fleet/node-player/add" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="node-player-add-name">{{ l.fleet_node_player_form_label_name }}</label>
<div class="widget">
<input name="name" type="text" id="node-player-add-name" required="required" />
</div>
</div>
<div class="form-group">
<label for="node-player-add-group-id">{{ l.fleet_node_player_group_form_label_group_id }}</label>
<div class="widget">
<select name="group_id" id="node-player-add-group-id">
<option value="" {% if not current_group.id %}selected="selected"{% endif %}>{{ l.common_default_node_player_group }}</option>
{% for group_id, group_name in groups.items() %}
<option value="{{ group_id }}" {% if current_group.id == str(group_id) %}selected="selected"{% endif %}>{{ group_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label for="node-player-add-host">{{ l.fleet_node_player_form_label_host }}</label>
<div class="widget">
<input type="text" name="host" id="node-player-add-host" required="required" />
</div>
</div>
<div class="actions">
<button type="button" class="btn-normal modal-close">
{{ l.fleet_node_player_form_button_cancel }}
</button>
<button type="submit" class="green">
<i class="fa fa-plus icon-left"></i> {{ l.fleet_node_player_form_add_submit }}
</button>
</div>
</form>
</div>

View File

@ -1,44 +0,0 @@
<div class="modal modal-node-player-edit hidden">
<h2>
{{ l.fleet_node_player_form_edit_title }}
</h2>
<form class="form" action="/fleet/node-player/edit" method="POST">
<input type="hidden" name="id" id="node-player-edit-id" />
<div class="form-group">
<label for="node-player-edit-name">{{ l.fleet_node_player_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="node-player-edit-name" required="required" />
</div>
</div>
<div class="form-group">
<label for="node-player-edit-group-id">{{ l.fleet_node_player_group_form_label_group_id }}</label>
<div class="widget">
<select name="group_id" id="node-player-edit-group-id">
<option value="">{{ l.common_default_node_player_group }}</option>
{% for group_id, group_name in groups.items() %}
<option value="{{ group_id }}">{{ group_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label for="node-player-edit-host">{{ l.fleet_node_player_form_label_host }}</label>
<div class="widget">
<input type="text" name="host" id="node-player-edit-host" required="required" />
</div>
</div>
<div class="actions">
<button type="button" class="btn-normal modal-close">
{{ l.fleet_node_player_form_button_cancel }}
</button>
<button type="submit" class="green">
<i class="fa fa-save icon-left"></i>{{ l.fleet_node_player_form_edit_submit }}
</button>
</div>
</form>
</div>

View File

@ -41,9 +41,9 @@
<i class="fa fa-file-circle-plus icon-left"></i> <i class="fa fa-file-circle-plus icon-left"></i>
{{ l.slideshow_content_button_add }} {{ l.slideshow_content_button_add }}
</button> </button>
<button class="folder-add btn-success-alt"> <button class="folder-add btn-neutral">
<i class="fa fa-folder-plus icon-left"></i> <i class="fa fa-folder-plus icon-left"></i>
{{ l.slideshow_content_button_add_folder }} {{ l.common_new_folder }}
</button> </button>
{{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END) }} {{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END) }}
</div> </div>
@ -52,7 +52,7 @@
{% if request.args.get('folder_not_empty_error') %} {% if request.args.get('folder_not_empty_error') %}
<div class="alert alert-danger"> <div class="alert alert-danger">
<i class="fa fa-warning icon-left"></i> <i class="fa fa-warning icon-left"></i>
{{ l.slideshow_content_folder_not_empty_error }} {{ l.common_folder_not_empty_error }}
</div> </div>
{% endif %} {% endif %}
@ -80,19 +80,11 @@
{% for child in folder.children %} {% for child in folder.children %}
{{ render_folder(child) }} {{ render_folder(child) }}
{% endfor %} {% endfor %}
{% for content in content_children %} {% for content in content_children %}
{% set icon = 'icon-folder' %} {% set icon = enum_content_type.get_fa_icon(content.type) %}
{% if content.type.value == 'picture' %} {% set color = enum_content_type.get_color_icon(content.type) %}
{% set icon = 'icon-landscape' %} <li class="explr-item">
{% elif content.type.value == 'video' %} <i class="fa {{ icon }} {{ color }}"></i>
{% set icon = 'icon-video' %}
{% elif content.type.value == 'url' %}
{% set icon = 'icon-chain' %}
{% elif content.type.value == 'youtube' %}
{% set icon = 'icon-youtube' %}
{% endif %}
<li class="{{ icon }}">
<a href="{{ url_for('slideshow_content_show', content_id=content.id) }}" target="_blank"> <a href="{{ url_for('slideshow_content_show', content_id=content.id) }}" target="_blank">
{{ content.name }} {{ content.name }}
</a> </a>
@ -178,11 +170,12 @@
{% for content in contents[working_folder.id|default(None)]|default([]) %} {% for content in contents[working_folder.id|default(None)]|default([]) %}
{% set icon = enum_content_type.get_fa_icon(content.type) ~ ' ' ~ enum_content_type.get_color_icon(content.type) %} {% set icon = enum_content_type.get_fa_icon(content.type) %}
{% set color = enum_content_type.get_color_icon(content.type) %}
<li class="draggable" data-path="{{ working_folder_path }}" data-id="{{ content.id }}" data-folder="0"> <li class="draggable" data-path="{{ working_folder_path }}" data-id="{{ content.id }}" data-folder="0">
<a href="{{ url_for('slideshow_content_show', content_id=content.id) }}" target="_blank" class="explr-link explr-item-selectable"> <a href="{{ url_for('slideshow_content_show', content_id=content.id) }}" target="_blank" class="explr-link explr-item-selectable">
<i class="fa {{ icon }}"></i> <i class="fa {{ icon }} {{ color }}"></i>
{{ truncate(content.name, 25, '...') }} {{ truncate(content.name, 25, '...') }}
</a> </a>
</li> </li>

View File

@ -49,7 +49,7 @@
</td> </td>
<td class=""> <td class="">
{% if slide.cron_schedule %} {% if slide.cron_schedule %}
{% set cron_desc = cron_descriptor(slide.cron_schedule) %} {% set cron_desc = cron_descriptor(slide.cron_schedule) %}
{% if cron_desc %} {% if cron_desc %}
{% if is_valid_cron_date_time(slide.cron_schedule) %} {% if is_valid_cron_date_time(slide.cron_schedule) %}
{% if slide.is_notification %} {% if slide.is_notification %}
@ -69,7 +69,7 @@
</td> </td>
<td class=""> <td class="">
{% if slide.cron_schedule_end %} {% if slide.cron_schedule_end %}
{% set cron_desc_end = cron_descriptor(slide.cron_schedule_end) %} {% set cron_desc_end = cron_descriptor(slide.cron_schedule_end) %}
{% if cron_desc_end %} {% if cron_desc_end %}
{% if is_valid_cron_date_time(slide.cron_schedule_end) %} {% if is_valid_cron_date_time(slide.cron_schedule_end) %}
{% if slide.is_notification %} {% if slide.is_notification %}