Sync queue management UI, sync/reboot/poweroff all btns

This commit is contained in:
ABelliqueux 2023-01-15 16:32:13 +01:00
parent a029b2e902
commit e44ff71a58
5 changed files with 143 additions and 61 deletions

10
app.py
View File

@ -38,6 +38,9 @@ gui_l10n = {"locale": LOCALE[:2],
"str_sync": _("Sync"),
"str_poweroff": _("Poweroff"),
"str_reboot": _("Reboot"),
# TODO : add to l10n
"str_poweroff_all": _("Poweroff all"),
"str_reboot_all": _("Reboot all"),
"str_blink": _("Blink"),
"str_link": _("Link"),
"str_refresh": _("Refresh"),
@ -631,11 +634,8 @@ def sync(host, arg0):
if arg0 == "stop":
return stop_upload()
elif host == "all":
reset_current_upload()
return sync_media_folder(media_folder_local, media_folder_remote_expanded, host, cmd_port)
# TODO : figure it out for multiple hosts
# ~ for hostl in hosts_available:
# ~ sync_media_folder(media_folder_local, media_folder_remote_expanded, hostl, cmd_port)
# This is done in js for now.
pass
else:
reset_current_upload()
return sync_media_folder(media_folder_local, media_folder_remote_expanded, host, cmd_port)

View File

@ -28,7 +28,7 @@ sha256 : 0fe3fe76d0e56e445124fa20646fa8b3d8c59568786b3ebc8a96d83d92f203e3
* Use nginx reverse proxy + SSL between server and clients ( https://medium.com/@antelle/how-to-generate-a-self-signed-ssl-certificate-for-an-ip-address-f0dd8dddf754 )
* Webgui beautifying
## 0.4 : 2022-10-21-videopi.img.xz
## 0.4 : 2022-10-21-pilpil.img.xz
md5 : dee7af70135994169cab4f073ee51905
sha256 : ec3e17fc9b41f8c5181484e9866be2d1d92cab8403210e3d22f4f689edd4cfde
@ -38,7 +38,7 @@ sha256 : ec3e17fc9b41f8c5181484e9866be2d1d92cab8403210e3d22f4f689edd4cfde
* Add media folder sync (scp, rsync, http upload)
* Install script ; Wifi setup, generate/install SSH keys/ nginx SSL cert/key fore each host, change hostname, static IPs
## 0.5 : 2022-11-17-videopi.img.xz
## 0.5 : 2022-11-17-pilpil.img.xz
md5 : d1dd7404ec05d16c8a4179370a214821
sha256 : 401359a84c6d60902c05602bd52fae70f0b2ecac36d550b52d14e1e3230854a6
@ -55,6 +55,31 @@ sha256 : 401359a84c6d60902c05602bd52fae70f0b2ecac36d550b52d14e1e3230854a6
* FR localisation for server, client and install script
* pep8ification
## 0.6 : 2023-01-06-pilpil.img.xz
md5 :
sha256 :
* os update, cleanup
* server: upload in chunks
* server: file upload : only send new files, get upload status (/sync/status)
* server: pep8fied (except for line length)
* server: better overclock values for RPIs
* server: VLC file caching set to 5000ms for seamless playback
* server: BCM4345 workaround to solve wifi connectivity (https://wiki.arthus.net/?Rpi_wifi_-_BCM4345_and_CTRL-EVENT-ASSOC-REJECT)
* webgui: remove file from timeline (drag&drop to bin)
* webgui: upload progress dialog
* webgui: l10n html + js
* webgui: Add remote file enqueuing
* webgui: Fix timeline UI
* webgui: Add video thumbnails to timeline UI
* webgui: fix timeline/file sync with multiple clients
* webgui: file sync queuing UI
* webgui: sync all live hosts button
* webgui: Add remove from sync queue button
* webgui: Poweroff/Reboot all btn
* client: Only blink when running on rpi
* client: Generate video thumbnails on startup/upload with ffmpeg, serve SVG when file not found, create folder if needed
# FS checklist
@ -70,32 +95,27 @@ sha256 : 401359a84c6d60902c05602bd52fae70f0b2ecac36d550b52d14e1e3230854a6
# DOING NEXT :
* server : remove incomplete files before upload
* webgui/server : interrupt current download ?
* webgui : upload status CSS transition ?
* FIXME : playhead stuck after removing item from playlist
# DONE :
* server : upload in chunks
* webgui: remove file from timeline (drag&drop to bin?) (check bugs)
* webgui: upload progress dialog
* server : file upload : only send new files, get upload status (/sync/status)
* webgui: l10n html + js
* webgui: Add remote file enqueuing
* webgui: Fix timeline UI
* webgui: Add video thumbnails to timeline UI
* client: Only blink when running on rpi
* client: Generate video thumbnail on start with ffmpegthumbnailer, serve SVG when file not found
* server: pep8fied (except for line length)
# TODO :
* ~ Test with several rpis
* ? Scripts hotspot linux : nmcli
win : https://github.com/JamesCullum/Windows-Hotspot
mac : https://apple.stackexchange.com/questions/2488/start-stop-internet-sharing-from-a-script
* check control from wifi client ( smartphone )
* playlist save/load ?
* webgui/server : interrupt current download ?
* server : remove incomplete uploaded files before new upload ?
* webgui : upload status CSS transition ?
* ! Remove git personal details/resolv.conf, remove authorized_keys, ssh config, clean home, re-enable ssh pw login
* ~ Doc
# OTHER:
* ? Scripts hotspot
linux : nmcli
win : https://github.com/JamesCullum/Windows-Hotspot
mac : https://apple.stackexchange.com/questions/2488/start-stop-internet-sharing-from-a-script
* get_client_rssi.sh on server
# À FINANCER
* Shutters/motor control
* XLR support
* UDEV rules : keyboard/mouse = standalone GUI

View File

@ -435,6 +435,8 @@ function update_host_list(infos_array){
host_down.forEach(function(element){
document.getElementById(element).style.display = 'none';
});
currentUser.hosts_up = host_up;
currentUser.hosts_down = host_down;
currentUser.status_all = host_up.length + " client(s).";
return host_up.length;
}
@ -449,10 +451,10 @@ function freeze_ui_host(host, html_attributes, inner_html=0, inner_text=0){
let upload_dialog_cont_element = add_HTML_element("div", html_attributes, 0, host);
container_element.insertBefore(upload_dialog_cont_element, siblings[0]);
if (inner_html){
let child_node = add_HTML_element("div", inner_html, 0, host)
let child_node = add_HTML_element("div", inner_html, 0, host);
let new_node = container_element.children.item(html_attributes.id + host).appendChild(child_node);
if (inner_text){
new_node.innerText = inner_text;
new_node.innerHTML = inner_text;
}
}
}
@ -461,17 +463,43 @@ function freeze_queued_container(command){
freeze_attributes = {"id":"ul_queued_freeze_", "class": "ul_queued_freeze"};
inner_html = {"id":"ul_queued_msg_", "class": "ul_queued_msg"};
inner_text = t9n[LOCALE].item_queued;
freeze_ui_host(command.split("/")[2], freeze_attributes, inner_html, inner_text);
let host = command.split("/")[2];
inner_text += '<buttton id="sync_unqueue_' + host + '" onclick="cancel_sync(this)" class="command btn btn-block btn-lg btn-default btn-unqueue" role="button">X</button';
freeze_ui_host(host, freeze_attributes, inner_html, inner_text);
}
function unfreeze_queued_container(host){
let container = document.getElementById(host);
let exists = container.querySelector("div.ul_queued_freeze");
exists? exists.remove(): false;
if (exists){
exists.remove();
}
}
function unfreeze_all_containers(){
if (currentUser.hosts_up){
currentUser.hosts_up.forEach(function(host){
unfreeze_queued_container(host);
});
}
}
function exec_all_hosts(command){
let authorized_commands = ['reboot', 'poweroff'];
// Only send authorized commands
if (authorized_commands.indexOf(command) == -1 ){
return 0;
}
if ( confirm(t9n[LOCALE].confirmMessage) ) {
if (currentUser.hosts_up){
currentUser.hosts_up.forEach(function(host){
send_ajax_cmd("/" + host + "/" + command);
});
}
}
}
function display_upload_status(command) {
// TODO : First this should set the HTMl with empty values, then call update_upload...
let host = command.split("/")[2];
let container_element = document.getElementById(host);
let siblings = container_element.children;
@ -534,6 +562,7 @@ async function update_upload_status(current_upload) {
// Upload finished
console.log("Upload finished - Destroying dialog for " + currentUser.last_ul_host);
destroy_upload_status(currentUser.last_ul_host);
// Remove first item from command queue
currentUser.ul_queue.shift();
// Upload done, un-freeze
@ -553,6 +582,7 @@ async function update_upload_status(current_upload) {
document.getElementById("ul_status_filename_" + currentUser.last_ul_host).innerHTML = t9n[LOCALE].filename + ": " + current_upload.filename;
document.getElementById("ul_status_filesize_" + currentUser.last_ul_host).innerHTML = t9n[LOCALE].size + ": " + current_upload.size + t9n[LOCALE].size_unit;
// Progress bar CSS
// FIXME : cursor stops updating after upload ?
if (current_upload.transferred_percent) {
document.getElementById("ul_progress_" + currentUser.last_ul_host).innerText = current_upload.transferred_percent + "%";
document.getElementById("ul_progress_" + currentUser.last_ul_host).style.background = "linear-gradient(90deg, " + TIMELINE_COLOR_CURSOR + " " + current_upload.transferred_percent + "%, #fff " + current_upload.transferred_percent + "%)";
@ -613,12 +643,7 @@ function parse_result(command, infos_array) {
//~ console.log("stopping upload...");
//~ console.log(infos_array);
//~ destroy_upload_status();
} //else {
//~ console.log("displaying status");
//~ console.log(infos_array);
//~ if (infos_array.total_count) {
//~ display_upload_status(infos_array)
//}
}
} else {
setTimeout(send_ajax_cmd, 180, "/all/list");
setTimeout(send_ajax_cmd, 180, "/all/status");
@ -685,7 +710,6 @@ function sync_host() {
// Update status
currentUser.status_all = t9n[LOCALE].sync;
// Create dialog
// TODO : display grayed out dialog when host is queued for upload
display_upload_status(command_q);
// Set current host
currentUser.last_ul_host = host;
@ -695,6 +719,7 @@ function sync_host() {
} else {
console.log("Nothing else in the queue !");
// Make sure sane values are set back
unfreeze_all_containers();
currentUser.last_ul_host = 0;
return 0;
}
@ -704,6 +729,20 @@ function sync_host() {
}
function cancel_sync(host_btn_id){
let host = host_btn_id.id.split("_")[2];
let command = "/sync/" + host;
if (currentUser.ul_queue){
let item_index = currentUser.ul_queue.indexOf(command);
// Skip [0]
if (item_index > 0){
console.log(host + " unqueuing");
currentUser.ul_queue.splice(item_index, 1);
unfreeze_queued_container(host);
}
}
}
// UI
addEventListener("DOMContentLoaded", function() {
//~ adjust_timeline();
@ -716,11 +755,11 @@ addEventListener("DOMContentLoaded", function() {
let command = clickedButton.value;
if ( command.indexOf("/reboot" ) > -1 || command.indexOf("/poweroff") > -1 ) {
// TODO : l10n
if (command.indexOf("/all" ) == -1){
if ( !confirm(t9n[LOCALE].confirmMessage) ) {
return 0;
}
}
} else if ( command == "/scan" ) {
document.getElementById("status_all").innerHTML = currentUser.status_all;
@ -741,27 +780,36 @@ addEventListener("DOMContentLoaded", function() {
request.onload = send_ajax_cmd(command);
} else if ( command.indexOf("/sync/") > -1 ) {
console.log("Sync command detected");
if ( command.indexOf("/all") > -1 ) {
console.log("Sync all request");
// If command is /sync/all, add live hosts to sync queue
if (currentUser.hosts_up){
currentUser.hosts_up.forEach(function(host){
let command = "/sync/" + host;
if (currentUser.ul_queue.indexOf(command) == -1){
console.log("Adding " + host + " to sync queue...");
currentUser.ul_queue.push(command);
freeze_queued_container(command);
}
});
}
if (currentUser.ul_queue.length > 1){
sync_host();
}
// Prevent request
return 0;
}
// If command not already in queue, add it
if (!currentUser.ul_queue.includes(command)){
console.log("Queuing command...");
currentUser.ul_queue.push(command);
freeze_queued_container(command);
} else {
console.log("Command already in queue...");
return 0;
}
sync_host();
//~ if (currentUser.ul_queue.length > 1){
//~ if (currentUser.freeze_ul){
//~ console.log("UL freezed, wait for queue to complete.")
// Offload command sending to sync_host()
// Prevent request
return 1;
//~ }
//~ currentUser.status_all = t9n[LOCALE].sync;
//~ // Display dialog
//~ display_upload_status(command);
//~ // Request values to fill dialog
//~ setTimeout(send_ajax_cmd, 1000, command + "/status");
}
else if ( command.indexOf("/browse") > -1 ) {
request.onload = send_ajax_cmd("/all/browse");

View File

@ -63,7 +63,12 @@ tr:nth-child(2n+1) {background-color: #888;}
/*
.client_container .left_col{border-right: #a8a8a8 2px solid;}
*/
.client_container .button{}
.client_container .btn {
min-width:1em;
}
.client_container .right_col .btn {
min-width: 3em;
}
.client_container .upload_dialog_cont {
position: absolute;
@ -122,6 +127,7 @@ tr:nth-child(2n+1) {background-color: #888;}
margin-top:1em;
background-color: #33333361;
}
[id^="tl_cont"] {
float: left;
width: 10%;
@ -152,7 +158,7 @@ tr:nth-child(2n+1) {background-color: #888;}
.buttons {width:75%;margin:auto;text-align: center;padding: 2em;}
.btn {
margin:auto;
width:3em;
min-width:2em;
height: 4em;
display:inline-block;
padding: 0;
@ -182,6 +188,11 @@ tr:nth-child(2n+1) {background-color: #888;}
}
.btn_txt {display: block;font-size: small;}
.btn-unqueue {background: #bbb;
width: 1em;
height: 1em;
}
#delete_btn {
width: 5em;
height: 3em;
@ -199,6 +210,7 @@ tr:nth-child(2n+1) {background-color: #888;}
/*Right column*/
.right_col {width: 71%;display: inline-block;}
/*Left column*/
.left_col {width: 28%;display: inline-block;float: left;clear: left;}
.left_col button {

View File

@ -26,6 +26,8 @@
<button value="/all/clear" class="command btn btn-block btn-lg btn-default" role="button">X<span class="btn_txt">{{gui_l10n['str_clear']}}</span></button>
<button value="/all/move/0/1" class="command btn btn-block btn-lg btn-default" role="button">&#x3b2;<span class="btn_txt">{{gui_l10n['str_sort']}}</span></button>
<button value="/sync/all" class="command btn btn-block btn-lg btn-default" role="button">&#x21ad;<span class="btn_txt">{{gui_l10n['str_sync']}}</span></button>
<button value="/poweroff/all" onclick="exec_all_hosts('poweroff')" class="command btn btn-block btn-lg btn-default" role="button">&#x23fb;<span class="btn_txt">{{gui_l10n['str_poweroff_all']}}</span></button>
<button value="/reboot/all" onclick="exec_all_hosts('reboot')" class="command btn btn-block btn-lg btn-default" role="button">&#x21BA;<span class="btn_txt">{{gui_l10n['str_reboot_all']}}</span></button>
<button id="delete_btn" ondrop="drop(event, this)", ondragover="drag_over_bin(event)" ondragleave="drag_leave_bin(event)" class="delete_btn" role="button">&#x1F5D1;<span class="btn_txt">{{gui_l10n['str_delete']}}</span></button>
</p>
</div>