diff --git a/app.py b/app.py deleted file mode 100755 index 8ff259c..0000000 --- a/app.py +++ /dev/null @@ -1,386 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -import sys, os, base64, toml -import http.client, ssl -import xml.etree.ElementTree as ET -from flask import Flask, render_template, request, make_response, jsonify -from waitress import serve - -app = Flask(__name__) - -# Load config defaults, then look for other config files -app.config.from_file("defaults.toml", load=toml.load) -config_locations = ["./", "~/.", "~/.config/"] -for location in config_locations: - # Optional config files, ~ is expanded to $HOME on *nix, %USERPROFILE% on windows - # ~ app.config.from_file("videopi.toml", load=toml.load, silent=True) - if app.config.from_file(os.path.expanduser( location + "pilpil-server.toml"), load=toml.load, silent=True): - print("Found configuration file in " + os.path.expanduser( location )) - # ~ app.config.from_file(os.path.expanduser("~/.config/videopi.toml"), load=toml.load, silent=True) - -### - -hosts_available, hosts_unavailable = [],[] - -# Map vlc cmds -# See https://github.com/videolan/vlc/blob/1336447566c0190c42a1926464fa1ad2e59adc4f/share/lua/http/requests/README.txt -cmd_player = { - "play" : "pl_play", - "resume" : "pl_forceresume", - "pause" : "pl_forcepause", - "tpause" : "pl_pause", - "previous" : "pl_previous", - "next" : "pl_next", - "stop" : "pl_stop", - "enqueue" : "in_enqueue", - "add" : "in_play", - "clear" : "pl_empty", - "delete" : "pl_delete", - "loop" : "pl_loop", - "repeat" : "pl_repeat", - "random" : "pl_random", - "move" : "pl_move", - "sort" : "pl_sort", - "seek" : "seek", - "sync" : "sync", - "status" : "status.xml", - "list" : "playlist.xml", - #"volume" : "volume", - #"ratio" : "aspectratio", - #"dir" : "?dir=", - #"command" : "?command=", - #"key" : "key=", - #"browse" : "browse.xml?uri=file://~" -# System commands : - # ~ "rssi" : "rssi", - # ~ "blink" : "blink", - # ~ "poweroff" : "poweroff", - # ~ "reboot" : "reboot", - -} - -cmd_server = ["blink", "reboot", "poweroff", "rssi"] - -# Set configuration - -DEBUG = app.config['DEFAULT']['DEBUG'] -media_folder_remote = app.config['DEFAULT']['media_folder_remote'] -media_folder_local = os.path.expanduser(app.config['DEFAULT']['media_folder_local']) -media_exts = app.config['DEFAULT']['media_exts'] -auth = str(base64.b64encode(str(":" + app.config['DEFAULT']['auth']).encode('utf-8')), 'utf-8') -cmd_auth = str(base64.b64encode(str(":" + app.config['DEFAULT']['cmd_auth']).encode('utf-8')), 'utf-8') -hosts = app.config['DEFAULT']['hosts'] -port = app.config['DEFAULT']['port'] -cmd_port = app.config['DEFAULT']['cmd_port'] -useSSL = app.config['DEFAULT']['useSSL'] -CAfile = app.config['DEFAULT']['CAfile'] -sync_facility = app.config['DEFAULT']['sync_facility'] - -headers = {"Authorization":"Basic " + auth} - -# Network/link utilities -# https://www.metageek.com/training/resources/understanding-rssi/ - -def isup(host_l, port): - global DEBUG - import socket - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if useSSL: - sslcontext = ssl.create_default_context() - # ~ if os.path.exists(CAfile): - # ~ sslcontext.load_verify_locations(cafile=CAfile) - # ~ else: - # Dont validate cert, we juts want to see if host is live - sslcontext.check_hostname = False - sslcontext.verify_mode = ssl.CERT_NONE - s = sslcontext.wrap_socket(s, server_hostname=host_l) - try: - s.settimeout(3.0) - s.connect((host_l, port)) - if DEBUG: - print( "Port " + str(port) + " reachable") - return 1 - except (socket.error, socket.timeout) as e: - if DEBUG: - print("Error on connection to " + host_l + ":" + str(port) + ": %s" % e) - return 0 - finally: - s.close() - -def checkHosts(host_l): - hostdown, hostup = [], [] - hosts_number = str(len(host_l)) - for lhost in host_l: - if not isup(lhost, port): - hostdown.append(lhost) - else: - hostup.append(lhost) - if DEBUG: - print( str(len(hostup)) + " of " + hosts_number + " hosts found.") - return hostup, hostdown - -# File utilities - -def listMediaFiles(folder): - if os.path.exists(folder): - files = os.listdir(folder); - medias = [] - for fd in files: - if len(fd.split('.')) > 1: - if fd.split('.')[1] in media_exts: - medias.append(fd) - return medias - else: - return [] - -def httpUpload(filename, hostl, trailing_slash=1): - import requests - url = "https://" + hostl + ":" + str(cmd_port) + "/upload" - if not trailing_slash: - filename = "/" + filename - files = { "file":( filename, open( media_folder_local + filename, "rb"), "multipart/form-data") } - if DEBUG: - print(files) - resp = requests.post(url, files=files, headers=headers, verify=CAfile) - if DEBUG: - print(resp.text) - if resp.ok: - return 1 - else: - return 0 - -def syncMediaFolder(media_folder_local, media_folder_remote, hostl, sync_facility=sync_facility): - trailing_slash = 1 - # Check for trailing / and add it if missing - if media_folder_local[-1:] != "/": - media_folder_local += "/" - trailing_slash = 0 - if media_folder_remote[-1:] != "/": - media_folder_remote += "/" - if sync_facility == "http": - media_list = listMediaFiles(media_folder_local) - transfer_ok = 0 - for media in media_list: - transfer_ok += httpUpload(media, hostl, trailing_slash) - return str(transfer_ok) + " files uploaded." - - # Check sync utility exists - elif which(sync_facility): - from shutil import whichdb - # Build subprocess arg list accroding to facility - if sync_facility == "rsync": - scrape_str = "total size is " - sync_args = [sync_facility, "-zharm", "--include='*/'"] - for media_type in media_exts: - sync_args.append( "--include='*." + media_type + "'" ) - sync_args.extend(["--exclude='*'", media_folder_local, hostl + ":" + media_folder_remote]) - if sync_facility == "scp": - media_list = listMediaFiles(media_folder_local) - sync_args = [sync_facility, "-Crp", "-o IdentitiesOnly=yes"] - for media in media_list: - sync_args.append( media_folder_local + media ) - sync_args.append( hostl + ":" + media_folder_remote ) - - sync_proc = subprocess.run( sync_args , capture_output=True) - if len(sync_proc.stdout): - scrape_index = str(sync_proc.stdout).index(scrape_str)+len(scrape_str) - total_size = str(sync_proc.stdout)[ scrape_index: ].split(" ")[0][:-1] - else: - total_size = "N/A"; - return total_size - -# /requests/status.xml?command=in_enqueue&input=file:///home/pi/tst1.mp4 -def writeM3U(m3u_content : str, host : str): - filename = host.replace(".", "_") + ".m3u" - fd = open(filename, "w") - fd.write(m3u_content) - fd.close() - return 1 - -# String utilities - -def escapeStr(uri): - return uri.replace(" ", "%20") - -def sec2min(duration): - return('%02d:%02d' % (duration / 60, duration % 60)) - -# VLC lua utilities - -def sendCommand(host, arg0, arg1, arg2): - portl = port - # Build request - req = "/requests/status.xml" - if arg0 == "list" : - req = "/requests/playlist.xml" - elif arg0 in cmd_server: - req = "/" + str(arg0) - portl = cmd_port - elif arg0 != "status" : - req = req + "?command=" + cmd_player[arg0] - if arg1 != "null" : - if (arg0 == "play") or (arg0 == "delete") or (arg0 == "sort") or (arg0 == "move"): - req = req + "&id=" + arg1 - if (arg0 == "sort") or (arg0 == "move") : - # val possible values : id, title, title nodes first, artist, genre, random, duration, title numeric, album - # https://github.com/videolan/vlc/blob/3.0.17.4/modules/lua/libs/playlist.c#L353-L362 - req = req + "&val=" + escapeStr(arg2) - elif arg0 == "seek" : - req = req + "&val=" + arg1 - elif (arg0 == "enqueue") or (arg0 == "add") : - req = req + "&input=file://" + media_folder_remote + "/" + arg1 - # Send request - if useSSL: - sslcontext = ssl.create_default_context() - if os.path.exists(CAfile): - sslcontext.load_verify_locations(cafile=CAfile) - else: - sslcontext.check_hostname = False - sslcontext.verify_mode = ssl.CERT_NONE - conn = http.client.HTTPSConnection( host + ":" + str(portl), timeout=3, context = sslcontext ) - else: - conn = http.client.HTTPConnection( host + ":" + str(portl), timeout=3 ) - try: - conn.request( "GET", req, headers = headers ) - resp = conn.getresponse() - data = resp.read() - except http.client.HTTPException: - print("Connection to " + host + " timed out") - return "Connection to " + host + " timed out" - except: - return "Error while connecting to " + host + ":" + str(portl) - finally: - conn.close() - # Parse response - # ~ data = resp.read() - - if arg0 == "rssi": - if DEBUG: - print(data) - response_dict = { - 'host': host, - 'rssi': str(data, 'UTF-8') - } - return response_dict - - xml = ET.fromstring(data) - - if arg0 != "list" : - meta = xml.findall("./information/category/") - if meta: - for leaf in meta: - if leaf.get("name") == "filename": - filename = leaf.text - else: - filename = "N/A" - cur_length = int(xml.find('length').text) - cur_time = int(xml.find('time').text) - cur_length_fmtd = sec2min(cur_length) - cur_time_fmtd = sec2min(cur_time) - cur_id = int(xml.find('currentplid').text) - cur_pos = xml.find('position').text - cur_loop = xml.find('loop').text - cur_repeat = xml.find('repeat').text - response_dict = { - 'host': host, - 'file': filename, - 'time': cur_time_fmtd, - 'leng': cur_length_fmtd, - 'pos': cur_pos, - 'loop': cur_loop, - 'repeat': cur_repeat, - # ~ 'pos': xml.find('position').text, - # ~ 'loop': xml.find('loop').text, - # ~ 'repeat': xml.find('repeat').text, - 'id': cur_id, - } - return response_dict - - else: - # Build M3U file from playlist - playlist = [] - playlist_duration = 0 - m3u_hdr = "#EXTM3U\n" - m3u_prefix = "#EXTINF:" - item_list = [] - if xml.find("./node") and xml.find("./node").get('name') == "Playlist": - m3u_content = m3u_hdr - playlist = xml.findall("./node/leaf") - #item_list = [] - for item in playlist: - m3u_content += m3u_prefix + item.get("duration") + ", " + item.get("name") + "\n" + item.get("uri") + "\n" - playlist_duration += int(item.get("duration")) - # ~ item_info = item.get("id") + " : " + item.get("name") + " - " + sec2min( int( item.get("duration") ) ) - item_info = item.get("id") + ";" + item.get("name") + ";" + sec2min( int( item.get("duration") ) ) + ";" - if "current" in item.keys(): - item_info += item.get("current") - item_list.append(item_info) - - playlist_overview = { - "host" : host, - "leng" : str(len(playlist)), - "duration" : sec2min(playlist_duration), - "items" : item_list - } - return playlist_overview - -status_message = "Idle" - -@app.route("/") -def main(): - global hosts - status_message = "Searching network for live hosts..." - # ~ hosts_available, hosts_unavailable = checkHosts(hosts) - templateData = { - 'hosts' : hosts, - 'status_message' : status_message - } - return render_template('main.html', **templateData) - -@app.route("/scan") -def scan(): - global hosts_available, hosts_unavailable - hosts_available, hosts_unavailable = checkHosts(hosts) - hosts_status = [hosts_available, hosts_unavailable] - return hosts_status - -@app.route("/browse") -def browse(): - files = listMediaFiles(media_folder_local) - return files; - - -@app.route("/sync/") -def sync(host): - if host == "all": - for hostl in hosts_available: - size = syncMediaFolder(media_folder_local, media_folder_remote, hostl) - else: - size = syncMediaFolder(media_folder_local, media_folder_remote, host) - return size; - - -@app.route("///", defaults = { "arg1": "null", "arg2": "null" }) -@app.route("////", defaults = { "arg2": "null" }) -@app.route("////") -def action(host, arg0, arg1, arg2): - status_message = "Idle" - if (arg0 not in cmd_player) and (arg0 not in cmd_server): - status_message = "

Wrong command

" - elif host == "all": - resp = [] - for hostl in hosts_available: - resp.append( sendCommand(hostl, arg0, arg1, arg2) ) - status_message = resp - elif host not in hosts_available: - status_message = "

Host is not reachable

" - else: - status_message = sendCommand(host, arg0, arg1, arg2) - if DEBUG: - print(status_message) - return status_message - -if __name__ == '__main__': - # ~ app.run() - serve(app, host='127.0.0.1', port=8080) \ No newline at end of file diff --git a/changelog_todo.md b/changelog_todo.md deleted file mode 100644 index 8048944..0000000 --- a/changelog_todo.md +++ /dev/null @@ -1,79 +0,0 @@ -## 0.1 : 2022-07-19-videopi.img.xz -md5sum : 7e80ede8ac4eed8b8088a3b075bdc1f2 -sha256 : 03de0272c71bd4614678b05c076d0e77df3f49039ad357ef9152374c748e7f1c - - * VLC installed, H264 1080p playback ok, wifi ok, remote control via telnet script - * Run `ssh-keygen -A` at first boot for the ssh server to work - -## 0.2 : 2022-09-24-videopi.img.xz -md5sum : f859f269c44f614e22e4fe601c3bb134 -sha256 : b6fd8ef4eb726d4ce7d196b9aebf910f32327ecd43f0d78140b8647d328ded22 - - * Switch VLC to use http lua control - * Add systemd unit for running VLC on startup - * Add RTL8821CU driver for rpi 1/3 - * Boot is now totally silent (blank screen) - * Disable Bluetooth - -## 0.3 : 2022-10-09-videopi.img.xz -md5 : 8e5e5b474af47519785d5a4696db04e2 -sha256 : 0fe3fe76d0e56e445124fa20646fa8b3d8c59568786b3ebc8a96d83d92f203e3 - - * Add rtl8192eu driver for rpi1/3 - * Add http server for custom commands : reboot, shutdown, wifi signal, led blinking - * Add vlc.playlist.move method to VLC http lua (httprequests.luac) - * Add playlist to Webgui - * config file parsing ( look for 'videopi.toml' in ./, ~/., ~/.config/) - * Add VLC/waitress systemd units for automatic startup - * 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 -md5 : dee7af70135994169cab4f073ee51905 -sha256 : ec3e17fc9b41f8c5181484e9866be2d1d92cab8403210e3d22f4f689edd4cfde - - * Switch to rpi os Bullseye - * Switch to user 'pil', pw 'pilpoil' - * client config file parsing ( look for 'pilpil-client.toml' in ./, ~/., ~/.config/) - * 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-10-XX-videopi.img.xz -md5 : -sha256 : - - * Add rt8821cu driver back - * Use safe overclocking settings for rpi1, 3, 4, better memory split - * Add blink function to pilpil - -# FS checklist - * /etc/dhcpcd.conf - * /etc/ssh/sshd_config - * ~/.ssh/authorized_keys - * ~/Videos/* - * /etc/hostname, /etc/hosts - * /etc/ssl/private/nginx-selfsigned.key - * /etc/ssl/certs/nginx-selfsigned.crt - * /etc/wpa_supplicant/wpa_supplicant.conf - * dd if=/dev/zero of=/home/pil/Videos/remove_me bs=4MB count=100 - - -# DOING NEXT : - * ~ Test with several rpis - * Define http auth secret at setup - - -# DONE : - * Increase live host scan when host first found - -# OTHER: - * get_client_rssi.sh on server - -# TODO : - * FR localisation - * GUI : Btn hover/press ? - - * ? Scripts hotspot linux/win/mac - * ? Config sync - * ! Remove git personal details/resolv.conf, remove authorized_keys, ssh config, clean home, re-enable ssh pw login - * ~ Doc diff --git a/defaults.toml b/defaults.toml deleted file mode 100644 index 01093bf..0000000 --- a/defaults.toml +++ /dev/null @@ -1,16 +0,0 @@ -[DEFAULT] -DEBUG = 0 -useSSL = false -CAfile = "selfCA.crt" -# scp, rsync, http -sync_facility = "http" -media_folder_local = "~/Videos" -media_folder_remote = "~/Videos" -media_exts = [] -auth = "secret" -cmd_auth = "secret" -hosts = [] -# VLC http LUA port -port = 0 -# Clients cmd port -cmd_port = 0 \ No newline at end of file diff --git a/pilpil-server.toml b/pilpil-server.toml deleted file mode 100644 index 27f233a..0000000 --- a/pilpil-server.toml +++ /dev/null @@ -1,17 +0,0 @@ -[DEFAULT] -DEBUG = 0 -useSSL = true -CAfile = "selfCA.crt" -# scp, rsync, http -sync_facility = "http" -media_folder_local = "../medias" -media_folder_remote = "/home/pil/Videos" -media_exts = ["mp4", "avi", "mkv"] -auth = "secret" -# OnNlY3JldA== -cmd_auth = "secret" -hosts = ["10.42.0.10", "10.42.0.11"] -# VLC http LUA port -port = 8887 -# Clients cmd port -cmd_port = 8888 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 389e227..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -flask -waitress -toml \ No newline at end of file diff --git a/static/script.js b/static/script.js deleted file mode 100644 index f9d8d42..0000000 --- a/static/script.js +++ /dev/null @@ -1,326 +0,0 @@ -// Timeline drag and drop -const tl_cont_attr = {id:"tl_cont", ondrop: "drop(event, this)", ondragover:"allowDrop(event)"}; -const tl_drag_attr = {id:"tl_drag", draggable:"true", ondragstart:"drag(event, this)"}; - -// Config -var timeline_color_cursor = "#FF8839"; -var timeline_color_bg = "#2EB8E6"; - -var src_id = ""; -var medias_status = {}; - -function updatePlaylist(){ - var new_list = []; - var media_count = document.getElementById("timeline").children.length; - for (i=media_count,l=0;i>l;i--) { - //~ new_list.push(document.getElementById("timeline").children[i].children[0].getAttribute("media_id")); - toMove = document.getElementById("timeline").children[i-1].children[0].getAttribute("media_id"); - console.log(toMove); - sendCmd("/all/move/" + toMove + "/1"); - } -} - -function findTargetIndex(element, index, array, thisArg){ - if (element == this) { - return index+1; - } - return 0; -}; - -function moveElements(target) { - var elem_list = Array.from(src_id.parentElement.children); - var elem_list_slice = elem_list; - var source_idx = elem_list.findIndex(findTargetIndex, src_id); - var target_idx = elem_list.findIndex(findTargetIndex, target); - var idx; - if (source_idx < target_idx) { - elem_list_slice = elem_list.slice(source_idx+1, target_idx+1); - idx = source_idx; - } else { - elem_list_slice = elem_list.slice(target_idx, source_idx ); - idx = target_idx+1; - } - for (i=0, l=elem_list_slice.length; i-1){ - elem = elem.children[child]; - } - var att = document.createAttribute(attr); - att.value = val; - elem.setAttributeNode(att); -}; - -function addElement(type, attr, meta = 0, j = 0){ - var elem = document.createElement(type); - var keys_array = Object.keys(attr); - for (i=0, l=keys_array.length;i -1 || command.indexOf("/poweroff") > -1 ) { - if ( !confirm("Êtes vous certain de vouloir effectuer cette action ?") ) { - return 0; - } - } else if ( command == "/scan" ) { - document.getElementById("status_all").innerHTML = "Searching network for live hosts..."; - } else if ( command.indexOf("/sort") > -1 ){ - if (command.indexOf('/1/') > -1 ) { - clickedButton.value = clickedButton.value.replace('/1/','/0/') - } else { - clickedButton.value = clickedButton.value.replace('/0/','/1/') - } - } else if ( command.indexOf("/move") > -1 ) { - const test_array = [21,19,20]; - for (i=test_array.length, l=0;i>l;i--){ - console.log(test_array[i-1]); - sendCmd("/all/move/" + test_array[i-1] + "/1"); - }; - sendCmd("/all/list"); - //setInterval( sendCmd, scanInterval, "/all/move/16/1"); - }; - - // On envoie la commande en AJAX - var request = new XMLHttpRequest(); - if ( command == "/scan" ) { - request.onload = sendCmd(command); - } - // On construit la commande - request.open("GET", command, true); - // et on l'envoie - request.send(); - }); - } -}, true); - -// Affichage des infos -function parseResult(command, infos_array) { - switch (command) { - case "/all/status": - // Iterate over array - for (var i = 0, l=infos_array.length; i " + infos_array[i].time + " / " + infos_array[i].leng; - medias_status[infos_array[i].id] = infos_array[i].pos; - // Find currently playing element - //~ var pl_length = document.getElementById("timeline").getAttribute("length"); - //~ for (j=0,k=pl_length;j" + - "Id" + - "Filename" + - "Duration" + - ""; - for (var j = 0, k=items_array.length; j" + item_meta[0] + "" + - "" + item_meta[1] + "" + - "" + item_meta[2] + "" + - "" ; - // Timeline - var child_node = addElement("div", tl_drag_attr, item_meta, j); - var len = document.getElementById("timeline").children.length; - addAttr("timeline", "length", len); - if ( len < items_array.length ) { - document.getElementById("timeline").appendChild( addElement("div", tl_cont_attr, 0, len) ); - } - document.getElementById(tl_cont_attr.id + j).replaceChildren(child_node); - // Adjust elements width - adjustTl(); - // Highlight currently playing element - if (item_meta[3] != ""){ - document.getElementById(tl_cont_attr.id + j).children[0].style.borderBottom = "4px solid " + timeline_color_cursor; - document.getElementById(tl_cont_attr.id + j).children[0].style.fontWeight = "bold"; - var pos = medias_status[item_meta[0]] * 100; - //~ pos = pos.toPrecision(2); - var pos1 = pos-1 + "%"; - pos = pos + "%"; - //console.log( "linear-gradient(90deg," + timeline_color2 + " " + pos1 + ", " + timeline_color1 + " " + pos + ", " + timeline_color2 + " " + pos + ")" ); - document.getElementById(tl_cont_attr.id + j).children[0].style.background = "linear-gradient(90deg," + timeline_color_bg + " " + pos1 + ", " + timeline_color_cursor + " " + pos + ", " + timeline_color_bg + " " + pos + ")"; - } - } - html_table += ""; - document.getElementById("playlist_"+infos_array[i].host).innerHTML += html_table; - }; - break; - case "/scan": - var host_up = infos_array[0]; - var host_down = infos_array[1]; - for ( var i=0, l=host_up.length; i" + - "Filename" + - "Duration" + - ""; - for (var j = 0, k=infos_array.length; j" + infos_array[j] + "" + - "" + "00:00" + "" + - "" ; - } - html_table += ""; - document.getElementById("filelist").innerHTML += html_table; - break; - case "/all/rssi": - var signal_color = 40; - var best_rssi = 30; - var worst_rssi = 70; - for (var j = 0, k=infos_array.length; j .btn, .btn-lg { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} - -#master_remote { - background: linear-gradient(0deg, #222 10%, #444 80%); - min-height: 20em; -} -#master_remote .right_col {background-color:transparent;} -.client_container { - border-bottom: #222 solid 1px; - display:none; - background: linear-gradient(0deg, #999 10%, #666 80%); - } -.client_container .button{} -/* -.timeline {height: 3em;background-color: #0f0;margin: 2em 0;} -*/ -#timeline { - height: 75px; - width:100%; - margin-top:1em; -} -[id^="tl_cont"] { - float: left; - width: 10%; - height: 75px; - margin: 0; - padding: 0; - border: 1px solid #aaaaaa; -} -[id^="tl_drag"] { - cursor: grab; - text-align:center; - height:75px; - line-height: 75px; - width:100%; - background-color:#1F7B99; -} -[id^="tl_cont"]:nth-child(2n+1) [id^="tl_drag"] { - background-color:#255E70; -} - -.client_container:nth-child(2n+1) {background-color:#444;} -.command {margin: 0 !important;} -.buttons {width:75%;margin:auto;text-align: center;padding: 2em;} -.btn { - margin:auto; - width:3em; - height: 4em; - display:inline-block; - padding: 0; - font-weight: 400; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - -ms-touch-action: manipulation; - touch-action: manipulation; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: 1px solid transparent; - border-radius: 4px; - background: linear-gradient(0deg, #b9b9b9 10%, #f9f9f9 80%); -} -.btn:hover { - background:#C8FFBD; - box-shadow: 0 0 10px #A5FF9F; -} -.btn:active { - background: #91FF7C; - box-shadow: 0 0 18px #91FF7C; -} -.btn_txt {display: block;font-size: small;} -/*Right column*/ -.right_col {width: 79.9%;display: inline-block;} -/*Left column*/ -.left_col {width: 20%;display: inline-block;float: left;clear: left;} -.left_col button { - width: 2em; - height: 2em; - padding: 0; - line-height: 2em; -} -.left_col button .btn_txt { - display:none; - color: #fff; - font-weight: bold; - font-size: medium; - background-color: #ff3030; - border: 1px solid #fff; - padding: 0 .5%; - width: 100px; - position: absolute; -} -.left_col button:hover .btn_txt {display:block;} -.col_1 { - width: 40%; - float: left; - padding: 3% 0 0 5%; -} -} -.col_2 { - width: 60%; - display: inline-block; - clear: right; -} -.indicator { - display:inline-block; - background-color: #C32600; - margin: 0 0 0 5%; - padding: 0.3em; - } -.wl_indicator { - display: inline-block; - background-color: #bbb; - vertical-align: bottom; - margin: 0 1px; - padding: 0; - height: .5em; - width: 5%; -} -#wl_0 {height:.5em;} -#wl_1 {height:.65em;} -#wl_2 {height:.80em;} -#wl_3 {height:.95em;} - - -@media screen and (max-width: 800px) { - table {margin:0} - td {padding:0;} - .btn-group-lg > .btn, .btn-lg { - font-size:14px; - } - .left_col {width: 30%;} - .right_col {width: 69.9%;} - .col_2 { - overflow: scroll; - font-size: .9em; - } -} \ No newline at end of file diff --git a/templates/main.html b/templates/main.html deleted file mode 100644 index 056789b..0000000 --- a/templates/main.html +++ /dev/null @@ -1,84 +0,0 @@ - - - RPi Web Server - - - - - - -
-
-

Videopi commander

-

{{status_message}}

-

No files.

-
-
-

- - - - - - - - - - - - - - -

-
-
- {% for host in hosts %} -
-
-

{{ host }}

-
- - - -

{{status_message}}

-

- Link: - - - - -

-

Loop

-

Repeat

-
-
-

No files queued.

-
-
-
-
- -
-
- - - - - - - - - -
-
-
- {% endfor %} - - -