diff --git a/aura.py b/aura.py index 984022063eb9bc99c2bd56cb40d70ca773624d83..e83a4da9eb57c66f0ab65c99e61a8cac72665f74 100755 --- a/aura.py +++ b/aura.py @@ -64,7 +64,7 @@ class Aura(AuraLogger): def start_web_service(self): try: - Routes() + Routes(self.scheduler, self.liquidsoapcommunicator, self.messenger) except OSError as e: self.messenger.halt() self.logger.critical("AuraEngine already running? Exception: " + e.strerror + ". Exiting...") diff --git a/configuration/engine.ini b/configuration/engine.ini index a7ed1c28e3fad427f57115836262d950d7eb31f5..3997c35d8de4a9cee5a03c857e4f6e9e85d23230 100644 --- a/configuration/engine.ini +++ b/configuration/engine.ini @@ -24,10 +24,26 @@ alsa_buffer_length="7" # alsa_periods => int alsa_periods="0" # frame_duration => double -frame_duration="0.40" +frame_duration="0.60" # frame_size => int frame_size="" +[recording] +# duration in minutes +rec_duration=1 +# flac settings +rec_filetype="flac" +#rec_samplerate=44100 +#rec_channels=2 +#rec_compression=5 +#rec_bits_per_sample=16 +# wav settings +# rec_filetype="wav" +rec_channels=2 +rec_samplesize=16 + + + [database] db_user="engine" db_name="engine" diff --git a/libraries/database/broadcasts.py b/libraries/database/broadcasts.py index 1c2a5be16498c72b9db1a0eedacf5a6e6906390d..daba4c0953fbe098f2b6be3778761f688921cd19 100644 --- a/libraries/database/broadcasts.py +++ b/libraries/database/broadcasts.py @@ -233,6 +233,13 @@ class TrackService(DB.Model, AuraDatabaseModel): tracks = DB.session.query(TrackService).filter(TrackService.start >= str(day), TrackService.start < str(day_plus_one)).all() return tracks + @staticmethod + # ------------------------------------------------------------------------------------------ # + def select_by_range(from_day, to_day): + tracks = DB.session.query(TrackService).filter(TrackService.start >= str(from_day), + TrackService.start < str(to_day)).all() + return tracks + # ------------------------------------------------------------------------------------------ # def __str__(self): return "TrackServiceID: #" + str(self.trackservice_id) + " playlist_id: " + str(self.playlist_id) + " started @ " + str(self.start) + " and played " + self.source diff --git a/libraries/database/database.py b/libraries/database/database.py index 6595904d03b8a809c8c1d66a73e86fc401f00c28..02da3a2274ad5eb3e09a9e815c3cc66491d9e50e 100644 --- a/libraries/database/database.py +++ b/libraries/database/database.py @@ -23,7 +23,6 @@ def create_app(install_dir, uri): return app - def create_database(): """ creates sqlalchemy database connection diff --git a/modules/cli_tool/padavan.py b/modules/cli_tool/padavan.py index 1209b8dca3620f341f992b8271ff10c2369b28a0..a29230d28c21ab6348919921d33b819bb5a3cf10 100644 --- a/modules/cli_tool/padavan.py +++ b/modules/cli_tool/padavan.py @@ -152,6 +152,11 @@ class Padavan: else: self.stringreply += "\nConnection to lqs: " + TerminalColors.RED.value + " " + str(connection_status["lqs"]) + TerminalColors.ENDC.value + if connection_status["lqsr"]: + self.stringreply += "\nConnection to lqsr: " + TerminalColors.GREEN.value + " " + str(connection_status["lqsr"]) + TerminalColors.ENDC.value + else: + self.stringreply += "\nConnection to lqsr: " + TerminalColors.RED.value + " " + str(connection_status["lqsr"]) + TerminalColors.ENDC.value + if connection_status["tank"]: self.stringreply += "\nConnection to tank: " + TerminalColors.GREEN.value + " " + str(connection_status["tank"]) + TerminalColors.ENDC.value else: diff --git a/modules/communication/connection_tester.py b/modules/communication/connection_tester.py index 55af230d8c0b3cd6679f84d68eb0ab9b2cd3cb2c..00e5775ed4f150b9cc087c4a0907c030ecb7419b 100644 --- a/modules/communication/connection_tester.py +++ b/modules/communication/connection_tester.py @@ -1,4 +1,5 @@ import urllib +import logging import simplejson from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator @@ -17,6 +18,7 @@ class ConnectionTester(AuraConfig): status["db"] = self.test_db_conn() status["pv"] = self.test_pv_conn() status["lqs"] = self.test_lqs_conn() + status["lqsr"] = self.test_lqsr_conn() status["tank"] = self.test_tank_conn() status["redis"] = self.test_redis_conn() @@ -33,17 +35,24 @@ class ConnectionTester(AuraConfig): def test_lqs_conn(self): try: lsc = LiquidSoapCommunicator(self.config) - s = lsc.get_mixer_status() + lsc.get_mixer_status() + return True + except Exception as e: - print(e) + logger = logging.getLogger("AuraEngine") + logger.warning(str(e)) return False - # return True if something is in dict - if len(s) > 0: + def test_lqsr_conn(self): + try: + lsc = LiquidSoapCommunicator(self.config) + lsc.get_recorder_status() return True - # if an empty dict came back, return False - return False + except Exception as e: + logger = logging.getLogger("AuraEngine") + logger.warning(str(e)) + return False def test_pv_conn(self): return self.test_url_connection(self.config.get("calendarurl")) diff --git a/modules/communication/liquidsoap/communicator.py b/modules/communication/liquidsoap/communicator.py index 5aef8a409903be3ff9cf39cac5b281e5486ff61b..66f7d85d41fe140c6310fba7374dba5e9a498ebe 100644 --- a/modules/communication/liquidsoap/communicator.py +++ b/modules/communication/liquidsoap/communicator.py @@ -110,6 +110,14 @@ class LiquidSoapCommunicator(ExceptionLogger): return inputstate + # ------------------------------------------------------------------------------------------ # + def get_recorder_status(self): + self.enable_transaction(self.lqcr) + recorder_state = self.__send_lqc_command__(self.lqcr, "record", "status") + self.disable_transaction(self.lqcr) + + return recorder_state + # ------------------------------------------------------------------------------------------ # def http_start_stop(self, start): if start: @@ -938,6 +946,21 @@ class LiquidSoapCommunicator(ExceptionLogger): self.success("00", data) self.notifyClient() + # ------------------------------------------------------------------------------------------ # + def http_start_stop(self, start): + if start: + cmd = "start" + else: + cmd = "stop" + + try: + self.enable_transaction() + self.__send_lqc_command__(self.lqc, "http", cmd) + self.disable_transaction() + except LQConnectionError as e: + # we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further + pass + # ------------------------------------------------------------------------------------------ # def recorder_start(self): """ @@ -1010,7 +1033,7 @@ class LiquidSoapCommunicator(ExceptionLogger): #if self.transaction == 0: # self.enable_transaction() - self.logger.debug("LiquidSoapCommunicator is calling " + str(namespace) + "." + str(command) + str(args)) + self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + "." + str(command) + str(args)) # call wanted function ... func = getattr(lqs_instance, namespace) diff --git a/modules/communication/liquidsoap/recorderclient.py b/modules/communication/liquidsoap/recorderclient.py index b47f3e38c1ccb702677983f59d02d802323be782..2b9827a02b3dd605ef2aff88c4075ac008f3a6a0 100644 --- a/modules/communication/liquidsoap/recorderclient.py +++ b/modules/communication/liquidsoap/recorderclient.py @@ -1,6 +1,13 @@ from modules.communication.liquidsoap.client import LiquidSoapClient class LiquidSoapRecorderClient(LiquidSoapClient): + # ------------------------------------------------------------------------------------------ # + def record(self, command, *args): + if command == "status": + self.command('record', 'status') + + return self.message + # ------------------------------------------------------------------------------------------ # def recorder_setfilename(self, filename): """ diff --git a/modules/communication/redis/adapter.py b/modules/communication/redis/adapter.py index 3fa05d1231e348fec5fe53797cc5417a9939d28e..ca26b405e652b13be773c2710426b6eddc405bdb 100644 --- a/modules/communication/redis/adapter.py +++ b/modules/communication/redis/adapter.py @@ -143,7 +143,6 @@ class ServerRedisAdapter(threading.Thread, RedisMessenger): elif item["data"].find("set_next_file") >= 0: playlist = item["data"].split()[1] playlist = playlist[0:len(playlist)-9] - self.logger.critical("HAVE TO SET NEXT FILE FOR: "+playlist) self.execute(RedisChannel.SNF_REPLY.value, self.scheduler.set_next_file_for, playlist) elif item["data"] == "recreate_db": diff --git a/modules/liquidsoap/engine.liq b/modules/liquidsoap/engine.liq index 2a48c0205cf28db313fc22a668d1ef2b3981265a..bc4d2c41f4a55f4bacde3298e0470a70797224b5 100644 --- a/modules/liquidsoap/engine.liq +++ b/modules/liquidsoap/engine.liq @@ -12,7 +12,7 @@ ini = read_ini("/etc/aura/engine.ini") #input_fs = single(id="fs", "/var/audio/fallback/output.flac") input_fs = request.queue(id="fs") input_http = input.http(id="http", "http://stream.fro.at/fro-128.ogg") -linein_alsa = input.alsa(id="live", device="hw:0", bufferize = false) +input_alsa = input.alsa(id="live", device="hw:0", bufferize = false) # create fallbacks timeslot_fallback = fallback_create(skip=true, "timeslot_fallback", create_timeslot_fallback) @@ -20,7 +20,7 @@ station_fallback = fallback_create(skip=true, "station_fallback", create_station show_fallback = fallback_create(skip=true, "show_fallback", create_show_fallback) # fill the mixer -mixer = mix(id="mixer", [input_fs, input_http, linein_alsa]) +mixer = mix(id="mixer", [input_fs, input_http, input_alsa]) # output source with station_fallback output_source = fallback(track_sensitive=false, [mixer, timeslot_fallback, show_fallback, station_fallback]) diff --git a/modules/liquidsoap/readini.liq b/modules/liquidsoap/readini.liq index a37d2642922d00da9b580393a0e5da801f992863..0eea28dccfb4f3adf1b7db11141e340d8de9c90c 100644 --- a/modules/liquidsoap/readini.liq +++ b/modules/liquidsoap/readini.liq @@ -1,28 +1,29 @@ def read_ini(file) - ret = get_process_lines("cat "^file ) - ret = list.map(string.split(separator="="), ret) + ret = get_process_lines("cat "^file ) + ret = list.map(string.split(separator="="), ret) - # l' => the filling list - def f(l',l)= - if list.length(l) >= 2 then - line = string.extract(pattern='"(.*)"', list.nth(l,1)) + # l' => the filling list + def f(l',l) = + if list.length(l) >= 2 then + line = string.extract(pattern='"(.*)"', list.nth(l,1)) - # print(line) - # print((list.hd(l),line['1'])) + #print(line) + #print((list.hd(l),line['1'])) - list.append([(list.hd(l),line['1'])],l') + list.append([(list.hd(l),line['1'])],l') + else + if list.length(l) >= 1 then + list.append([(list.hd(l),"")],l') else - if list.length(l) >= 1 then - list.append([(list.hd(l),"")],l') - else - directory = dirname() - ignore(directory) - list.add(("install_dir", "/home/gg/PycharmProjects/engine"), l') -# l' - end + l' end end + end - list.fold(f,[],ret) + # append install dir + pwd = get_process_lines("pwd") + install_dir = dirname(dirname(list.hd(pwd))) + + list.fold(f, [("install_dir", install_dir)], ret) end diff --git a/modules/liquidsoap/record.liq b/modules/liquidsoap/record.liq new file mode 100644 index 0000000000000000000000000000000000000000..5b51e946d41904eafcb4f816c738da2b30de052d --- /dev/null +++ b/modules/liquidsoap/record.liq @@ -0,0 +1,172 @@ +# LOG FILE SETTINGS +set("log.file.path", "./<script>.log") + +# TELNET SETTINGS +set("server.telnet", true) +set("server.telnet.bind_addr", "0.0.0.0") +set("server.telnet.port", 2345) + +# SOCKET SETTINGS +set("server.socket", true) +set("server.socket.path", "./<script>.sock") + +inst = if argv(1) != "" then string_of(argv(1)) else 'record' end +instance = ref inst + +%include "readini.liq" +ini = read_ini("/etc/aura/engine.ini") + +audiobase = if !instance == 'record' then list.assoc("audiobase", ini) else list.assoc("altaudiobase", ini) end +rec_filetype = list.assoc("rec_filetype", ini) + +filenamepattern = ref audiobase^"/%Y-%m-%d/%Y-%m-%d-%H-%M.flac" + +# Der aktuelle Dateiname für die Aufnahme +recordingfile = ref "" + +# wir definieren eine Referenz für die Stop-Funktion, die aber bisher noch nichts tun kann +stop_f = ref (fun () -> ()) + +# bewahre uns davor, dass zweimal gleichzeitig die gleiche Date aufgenommen wird +is_record_active = ref false + +# Stop dump - wir definieren die Funktion, die stop_f ausführt +def stop_dump() = + f = !stop_f + f () +end + +def on_start() + recordingfile := list.hd(get_process_lines("date +#{!filenamepattern}")) +end + +# Wav header fixen und ggf. die Aufzeichnung beenden +def on_close(filename) + # es darf wieder aufgenommen werden + is_record_active := false + + # if list.assoc("rec_filetype", ini) == 'wav' + # # Korrekten WAV-Header schreiben + # system("qwavheaderdump -F #{filename}") + + # Naechsten Dateinamen vormerken + recordingfile := list.hd(get_process_lines("date +#{!filenamepattern}")) +end + +# Der input wie oben definiert +def get_input() + input.alsa() +end + +def get_output() + input = get_input() + d = int_of_string(list.assoc("rec_duration", ini)) + if rec_filetype == 'flac' then + log("output file type is FLAC") + output.file( + id="recorder", + %flac(samplerate=44100, channels=2, compression=5, bits_per_sample=16), + perm = 0o664, + on_start=on_start, + !filenamepattern, + on_close=on_close, + reopen_when={ if !instance == 'record' then int_of_float(gettimeofday()/60.) mod d == 0 else false end }, + input + ) + elsif rec_filetype == 'wav' then + log("output file type is WAV") + output.file( + id="recorder", + %wav(stereo=true, channels=2, samplesize=16, header=true), + perm = 0o664, + on_start=on_start, + !filenamepattern, + on_close=on_close, + reopen_when={ if !instance == 'record' then int_of_float(gettimeofday()/60.) mod d == 0 else false end }, + input + ) + else + log("dummy out as rec out") + output.dummy(blank()) + end +end + +# Funktion gibt Auskunft welches File aktuell ist und wieviel Prozent bereits aufgenommen werden +def currecording() + curfile = !recordingfile + if curfile != "" then + percent_done = list.hd(get_process_lines("echo $(($(stat -c%s "^curfile^")/3174777))")) + "#{curfile}, #{percent_done}%" + else + "Nothing is being recorded now" + end +end + +#Funktion zum Start der Aufzeichnung +def start_dump() = + log('start dump') + + # don't record twice is_record_active + if !is_record_active == false then + is_record_active := true + + log('starting to record') + + + + + record = get_output() + + + + + + log('record defined') + # Die Stopfunkton zeigt nun auf die Shutdown-Funktion der aktuellen Source + stop_f := fun () -> begin + log('stop recording') + # Aufnahme sauber beenden + ignore(server.execute('recorder.stop')) + # Source zerstören + source.shutdown(record) + # Variable zurücksetzen + recordingfile := "" + end + else + log("recorder already active") + end +end + + +# Der Server wird durch 3 Funktionen bereichert +# Der User darf die Aufzeichnung manuell starten +server.register(namespace="record", + description="Start recording.", + usage="start", + "start", + fun (s) -> begin start_dump() "OK" end) + +# Der User darf die Aufzeichnung manuell stoppen +server.register(namespace="record", + description="Stop recording.", + usage="stop", + "stop", + fun (s) -> begin stop_dump() "OK" end) + +if !instance != 'record' then + # Der User darf einen Dateinamen für die Aufnahme definieren + server.register(namespace="record", + description="Define filename for output.", + usage="setfilename", + "setfilename", + fun (s) -> begin filenamepattern := audiobase^"/"^string_of(s) "OK" end) +end + +# Der User kann sich über den Fortschritt der Aufnahme informieren +server.register(namespace="record", + description="Show current file.", + usage="curfile", + "curfile", + fun (s) -> currecording() ) + +output.dummy(blank(id="serve")) diff --git a/modules/liquidsoap/settings.liq b/modules/liquidsoap/settings.liq index eb752a73f6c8a6b21fdaf45cf115c17101318320..33b9e53146a97a1fea5d1dd6241514fa56a3c0e9 100644 --- a/modules/liquidsoap/settings.liq +++ b/modules/liquidsoap/settings.liq @@ -1,11 +1,12 @@ # LOG FILE SETTINGS set("log.file.path", "./<script>.log") -# SERVER SETTINGS +# TELNET SETTINGS set("server.telnet", true) set("server.telnet.bind_addr", "0.0.0.0") set("server.telnet.port", 1234) +# SOCKET SETTINGS set("server.socket", true) set("server.socket.path", "./<script>.sock") diff --git a/modules/scheduling/scheduler.py b/modules/scheduling/scheduler.py index 589d9bc202ab5b97539cae20d25baf5a8b5b08e9..bf5afa46ae3deaa3c66a507631f3777b5d6824f9 100644 --- a/modules/scheduling/scheduler.py +++ b/modules/scheduling/scheduler.py @@ -137,7 +137,7 @@ class AuraScheduler(ExceptionLogger, threading.Thread): next_time = datetime.datetime.now() + datetime.timedelta(seconds=seconds_to_wait) # write to logger - self.logger.info("Fetch new programmes every started. Going to start next time " + str(next_time)) + self.logger.info("Fetch new programmes every " + str(seconds_to_wait) + "s started. Going to start next time " + str(next_time)) # fetch new programme self.fetch_new_programme() @@ -383,8 +383,13 @@ class AuraScheduler(ExceptionLogger, threading.Thread): # ------------------------------------------------------------------------------------------ # def set_next_file_for(self, playlistname): - print(playlistname) - return "" + self.logger.critical("HAVE TO SET NEXT FILE FOR: " + playlistname) + self.logger.critical(str(self.get_active_entry())) + self.logger.critical(playlistname) + self.redismessenger.set_next_file_for(playlistname, "/var/audio/blank.flac") + print('return self.config.get("install_dir") + "/configuration/blank.flac"') + import sys + sys.exit(0) # ------------------------------------------------------------------------------------------ # def start_recording(self, data): diff --git a/modules/web/routes.py b/modules/web/routes.py index 4cecba02429d675f8a58b2e9d87d022732b2426c..f2968c485a46f6d07735701173bd6fcdc1457359 100644 --- a/modules/web/routes.py +++ b/modules/web/routes.py @@ -1,30 +1,126 @@ -from flask import request + +import simplejson +import decimal +import traceback +import sqlalchemy +import datetime +import logging + +from flask import request, render_template from libraries.database.database import APP -from libraries.database.broadcasts import TrackService +from libraries.database.broadcasts import TrackService, Schedule + +def alchemyencoder(obj): + logger = logging.getLogger("AuraEngine") + logger.warning(str(type(obj))) + """JSON encoder function for SQLAlchemy special classes.""" + if isinstance(obj, datetime.date): + return obj.isoformat() + elif isinstance(obj, decimal.Decimal): + return float(obj) + elif isinstance(obj, sqlalchemy.orm.state.InstanceState): + return "" + elif isinstance(obj, Schedule): + return simplejson.dumps([obj._asdict()], default=alchemyencoder) + else: + return str(obj) class Routes: error = None + scheduler = None + messenger = None + lqs_communicator = None - def __init__(self): - APP.run() + def __init__(self, scheduler, lqs_communicator, messenger): + self.logger = logging.getLogger("AuraEngine") + + self.scheduler = scheduler + self.messenger = messenger + self.lqs_communicator = lqs_communicator + + APP.run(debug=True) @staticmethod @APP.route('/') @APP.route('/index') def index(): - return "Welcome to Aura. Please use the CLI Tool guru.py to manipulate the server. Web service is in planned..." + return "" + return render_template("index.html") - # request: http://localhost:5000/trackservice?from=2018-01-17T13:30:00&to=2018-01-17T16:00:00 @staticmethod @APP.route("/trackservice", methods=["GET"]) - def trackservice(): + def track_service(): from_time = request.args.get("from") to_time = request.args.get("to") + last = request.args.get("last") now = request.args.get("now") - if now == "": - entry = TrackService.now_playing() + # nothing set => today's playlist + if from_time == None and to_time == None and now == None: + selected_date = datetime.date.today() + trackservice_entries = TrackService.select_by_day(selected_date) + + # from and end time set + elif from_time is not None and to_time is not None: + to_time = datetime.datetime.strptime(to_time, "%Y-%m-%d") + from_time = datetime.datetime.strptime(from_time, "%Y-%m-%d") + trackservice_entries = TrackService.select_by_range(from_time, to_time) + + # now set + elif now == "": + datetime.date.today() + trackservice_entries = TrackService.now_playing() + + return render_template("trackservice.html", + length=len(trackservice_entries), + trackservice_entries=trackservice_entries, + selected_date=selected_date) + + @staticmethod + @APP.route("/test") + def test(): + return render_template("index2.html") + + @staticmethod + @APP.route("/login") + def login(): + return "login" + #return render_template("index.html") + + @staticmethod + @APP.route("/logout") + def logout(): + session.pop("logged_in", None) + return "logout" + #return render_template("index.html") + + @staticmethod + @APP.route("/api/v1/trackservice/<selected_date>", methods=["GET"]) + def api_trackservice(selected_date): + tracks_on_selected_date = None + + # convert date + try: + selected_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d").date() + tracks_on_selected_date = TrackService.select_by_day(selected_date) + + tracks_on_selected_date_in_json = simplejson.dumps([tracks._asdict() for tracks in tracks_on_selected_date], default=alchemyencoder) + except Exception as e: + import traceback + traceback.print_exc() + + logger = logging.getLogger("AuraEngine") + logger.error("Cannot transform programme into JSON String. Reason: " + str(e)) + + return simplejson.dumps({"Error": str(e)}) + + return tracks_on_selected_date_in_json + + @staticmethod + @APP.route("/api/v1/trackservice/", methods=["GET"]) + def api_trackservice_now(): + return simplejson.dumps({'reached': True}) + - return "from: " + str(from_time) + " to: " + str(to_time) + " now: " + str(now) \ No newline at end of file diff --git a/modules/web/templates/modalsearch.html b/modules/web/templates/modalsearch.html new file mode 100644 index 0000000000000000000000000000000000000000..17965ea44e8e58d43cd842f0bf31aa0bc748537e --- /dev/null +++ b/modules/web/templates/modalsearch.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<html lang="de"> + <head> + <link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}"> + <link rel="stylesheet" href="{{ url_for('static', filename='css/lib/bootstrap.min.css') }}" type="text/css"> + <link href="http://fonts.googleapis.com/css?family=Ubuntu:400,300italic" rel="stylesheet" type="text/css"> + <link rel="stylesheet" href="{{ url_for('static', filename='css/lib/jquery-ui.min.css') }}" type="text/css"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + {% block customcss %}{% endblock %} + <title>Comba - {% block title %}{% endblock %}</title> + <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements --> + <!--[if lt IE 9]> + <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> + <![endif]--> + </head> +<body> + + <div class="modal-body"> +<form id="modalSearchForm" classs="form-horizontal" method="POST" action="/search/modal/{{ orig_id }}"> + <div class="form-group"> + <label class="col-sm-2 control-label" for="search">{{ _('Text/Name') }}:</label> + <div class="col-sm-10"> + <input class="form-control" id="search" name="search" type="text" value="{{ query.search }}" /> + </div> + </div> + <div class="clearfix"></div> + <div class="col-sm-12 form-group"><h4>{{ _('Date Search') }}</h4></div> + <div class="form-group"> + <div> + <label class="col-sm-2 control-label" for="from">{{ _('from') }}:</label> + <div class="col-sm-4"> + <input class="datepicker form-control" id="from" placeholder="2014-01-01" name="from" type="text" value="{{ query.from }}" /> + </div> + <label class="col-sm-2 control-label" for="to">{{ _('to') }}:</label> + <div class="col-sm-4"> + <input class="datepicker form-control" id="to" placeholder="2014-12-31" name="to" type="text" value="{{ query.to }}" /> + </div> + </div> + </div> + + <div class="clearfix"></div> + <div class="col-sm-offset-2 col-sm-10"> + <br /> + <div class="btn-toolbar" role="toolbar"> + <div class="btn-group"> + <button class="btn btn-default" type="submit" value="fo"> <span class="glyphicon glyphicon-search"></span> {{ _('Search') }}</button> + <button id="reset_button" class="btn btn-default" type="button" value="reset"> <span class="glyphicon glyphicon-remove"> </span>{{ _('Reset') }}</button> + </div> + + </div> + + </div> + <div class="clearfix"></div> + <br /> +<uL class="list-group"> + {% for event in eventlist %} + <li id="li-{{ event.id }}" class="list-group-item"> + <div class="col-md-6"> + <h3><a style="cursor:hand;cursor:pointer" data-origid="{{ orig_id }}" data-eventid="{{ event.id }}" class="overwrite-close-btn">{{ event.title }}</a></h3> + <div>{{ event.start | formatdate }} - {{ event.end | formatdate }}</div> + <div>{% if event.rerun %}{{ _('Repetition of') }} {{ event.replay_of_datetime | formatdate }}{% endif %}</div> + <div>{{ event.subject }}</div> + </div> +<div class="clearfix"></div> + </li> + {% endfor %} +</uL> + +</form> + +</div> +<script src="{{ url_for('static', filename='js/lib/jquery-1.10.2.min.js') }}"></script> +<script src="{{ url_for('static', filename='js/lib/bootstrap.min.js') }}"></script> +<script src="{{ url_for('static', filename='js/lib/jquery-ui.min.js') }}"></script> +<script> + + + +function overwriteBroadcast(orig_id, replace_id) { + + + $('#myModal').modal('hide') + window.location.reload(true) +} + + $(function() { + $('.overwrite-close-btn').click(function(e) { + e.preventDefault(); + var replace_id = $(this).attr('data-eventid'); + var orig_id = $(this).attr('data-origid'); + console.log(orig_id + " mit " + replace_id + "ueberschreiben") + jQuery.ajax ({ + url: '{{ url_for("search") }}/overwrite/' + orig_id + '/' + replace_id, + cache: false, + complete: function (data) { + window.parent.closeModal(); + } + }); + }); + + $('#reset_button').click(function(e){ + $('#from').val("") + $('#to').val("") + $('#search').val("") + $('#searchForm').submit() + }); + + $('.pagination .link').click(function(ev){ + ev.preventDefault(); + if (!$(this).attr('data-page')) { + return; + } + $('#form-page').val($(this).attr('data-page')); + $('#searchForm').submit(); + }); + $( ".datepicker" ).datepicker({dateFormat: "yy-mm-dd"}); + }); + +</script> +</body> \ No newline at end of file diff --git a/modules/web/templates/monitor.html b/modules/web/templates/monitor.html new file mode 100644 index 0000000000000000000000000000000000000000..c361af0ccf391d228b84aaf697f5119a617c3140 --- /dev/null +++ b/modules/web/templates/monitor.html @@ -0,0 +1,168 @@ +{% extends "layout.html" %} +{% block title %}Monitor{% endblock %} +{% block customcss %}<link rel="stylesheet" href="{{ url_for('static', filename='css/monitor.css') }}" type="text/css">{% endblock %} +{% block pagetitle %}{{ _('Monitor') }}{% endblock %} +{% block body %} + <ul class="nav nav-tabs" role="tablist"> + <li><a href="/" role="tab">Home</a></li> + <li class="active"><a href="#system-tab" id="getsystemdata" role="tab" data-toggle="tab">{{ _('System') }}</a></li> + <li><a href="#scheduler-tab" id="getscheduler" role="tab" data-toggle="tab">{{ _('Scheduler') }}</a></li> + <li><a href="#controller-tab" id="getcontroller" role="tab" data-toggle="tab">{{ _('Controller') }}</a></li> + <li><a href="#data-tab" id="getalldata" role="tab" data-toggle="tab">{{ _('Channels') }}</a></li> + <li><a href="#schedulerdata-tab" id="getschedulerdata" role="tab" data-toggle="tab">{{ _('Scheduler Jobs') }}</a></li> + <li><a href="#streaming-tab" id="getstreamingdata" role="tab" data-toggle="tab">{{ _('Streaming') }}</a></li> + </ul> + <div class="tab-content"> + <div class="tab-pane fade in active" id="system-tab"> + <div id="system-data"> + {% include 'sysinfo.html' %} + </div> + </div> + <div class="tab-pane fade" id="scheduler-tab"> + <div id="scheduler"></div> + </div> + <div class="tab-pane fade" id="controller-tab"> + <div id="controller"></div> + </div> + <div class="tab-pane fade" id="data-tab"> + <div id="controller-data"></div> + </div> + <div class="tab-pane fade" id="schedulerdata-tab"> + <div id="scheduler-data"></div> + </div> + <div class="tab-pane fade" id="streaming-tab"> + <div id="streaming-data"></div> + </div> + </div> +{% endblock %} +{% block customjs %} + <script src="{{ url_for('static', filename='js/lib/jquery-1.10.2.min.js') }}"></script> + <script src="{{ url_for('static', filename='js/lib/bootstrap.min.js') }}"></script> + <script> + +function formatJobs(data, app) { + //var html = "<ul class=\"showcase\">"; + var html = ""; + $.each(data, function( index, job ) { + console.info(job) + var level = job['level'] ? job['level'] : ''; + + var code = job['code'] ? job['code'] : ''; + html = html + '<div class="task">'; + if (job['microtime']) { + var timeStr = new Date(parseFloat(job['microtime']) * 1000).toLocaleString(); + html = html + '<i>Zeit: ' + timeStr + '</i>' + } + + html = html + '<h3><task-title>'+ app + '</task-title> Task: ' + job['job'] +'</h3>'; + html = html + '<div class="description"><strong>Message:</strong> '+job['message'] +'</div>'; + if (job['level'] == 'info' || job['level'] == 'success') { + var bclass = 'bg-' + job['level']; + var erg = 'wurde mit Erfolg abgeschlossen' + } + if (job['level'] == 'warning' ) { + var bclass = 'bg-'+ job['level']; + var erg = 'gab Warnumeldung aus. Status nicht kritisch.' + } + if (job['level'] == 'error' ) { + var bclass = 'bg-'+ job['level']; + var erg = 'gab Fehlermeldung aus. Fehler muss behoben werden.' + } + if (job['level'] == 'fatal' ) { + var bclass = 'bg-danger'; + var erg = 'ist mit kritischem Fehler abgebrochen. Fehler muss dringend behoben werden.' + } + html = html + '<div class="show-job '+bclass+'">' + html = html + '<div class="result"><strong>Resultat:</strong> Der Task <em>"'+ job['job'] + '"</em> ' + erg + ' (Errorcode #' + code +')</div>'; + console.info(job['value']); + if (job['value'] && (typeof job['value']=== 'object')) { + var info = job['value'] + html = html + '<div class="details"><h4>Details:</h4><ul class="details">'; + $.each(job['value'], function( key, val) { + html = html + '<li><strong>'+ key +':</strong> '+ val +'</li>'; + }); + html = html + '</ul></div>'; + } + html = html + '</div></div>' + }); + //html = html + "</ul>"; + return html; +} +function showlogs(app) { + var url='{{ url_for("monitor") }}' + '/events/' + app + + jQuery.ajax ({ + url: url, + cache: false, + success: function (data) { + jQuery('#' + app).html(formatJobs(data, app)); + }, + error: function () { + jQuery('#' + app).html('<strong>Error</strong>'); + } + }); +} +jQuery('#getscheduler').click(function() { + showlogs('scheduler') +}); +jQuery('#getcontroller').click(function() { + showlogs('controller') +}); + + +jQuery('#getalldata').click(function() { + jQuery.ajax ({ + url: '{{ url_for("monitor") }}' + '/channels', + cache: false, + success: function (response) { + jQuery('#controller-data').html(response.data); + + }, + error: function () { + jQuery('#controller-data').html('<strong>Error</strong>'); + } + }); +}); + +jQuery('#getschedulerdata').click(function() { + jQuery.ajax ({ + url: '{{ url_for("monitor") }}' + '/scheduler', + cache: false, + success: function (response) { + jQuery('#scheduler-data').html(response.data); + + }, + error: function () { + jQuery('#scheduler-data').html('<strong>Error</strong>'); + } + }); +}); + +jQuery('#getsystemdata').click(function() { + jQuery.ajax ({ + url: '{{ url_for("monitor") }}' + '/sysinfo', + cache: false, + success: function (response) { + jQuery('#system-data').html(response.data); + + }, + error: function () { + jQuery('#system-data').html('<strong>Error</strong>'); + } + }); +}); +jQuery('#getstreamingdata').click(function() { + jQuery.ajax ({ + url: '{{ url_for("monitor") }}' + '/stream', + cache: false, + success: function (response) { + jQuery('#streaming-data').html(response.data); + + }, + error: function () { + jQuery('#streaming-data').html('<strong>Error</strong>'); + } + }); +}); +</script> +{% endblock %} \ No newline at end of file diff --git a/modules/web/templates/playlist.xml b/modules/web/templates/playlist.xml new file mode 100644 index 0000000000000000000000000000000000000000..510929abcbf472f19b110d23940466dc8eb1238a --- /dev/null +++ b/modules/web/templates/playlist.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<playlist version="1" xmlns="http://xspf.org/ns/0/"> + <trackList> + {% for track in tracklist %} + <track> + <title>{{ track.title }}</title> + <record_at>{{ track.record_at }}</record_at> + <length>{{ track.length }}</length> + <location>{{ track.location }}</location> + <time>{{ track.time }}</time> + <start>{{ track.start }}</start> + <end>{{ track.end }}</end> + <show_at>{{ track.show_at }}</show_at> + <station_name>{{ track.station_name }}</station_name> + <station_id>{{ track.station_id }}</station_id> + <programme_id >{{ track.programme_id }}</programme_id > + </track> + {% endfor %} + </trackList> +</playlist> \ No newline at end of file diff --git a/modules/web/templates/preprod.html b/modules/web/templates/preprod.html new file mode 100644 index 0000000000000000000000000000000000000000..ec7cc81ca793877346f8d2a613443877c5050c7c --- /dev/null +++ b/modules/web/templates/preprod.html @@ -0,0 +1,76 @@ +{% extends "layout.html" %} +{% block customjs %}{% endblock %} +{% block title %}{% endblock %} +{% block pagetitle %}{{ _('Preproduction Title')}}{% endblock %} +{% block body %} + + +<div class="well"> + <div class="pull-right"><a href="{{ url_for('search') }}#li-{{ event.id }}" class="btn btn-default">{{ _('Cancel') + }}</a></div> + + <div> + <h3>{{ event.title }}</h3> + + <div>{{ event.start }} - {{ event.end }}</div> + <div>{% if event.rerun %}{{ _('Repetition of') }} {{ event.replay_of_datetime }}{% endif %}</div> + + <div> + <h4>{{ _('Upload Preproduction')}}</h4> + + <form class="form-inline" method=POST enctype=multipart/form-data action="{{ url_for('preprod_upload') }}"> + <div class="form group"> + <input type="hidden" name="returnid" value="{{ event.id }}"/> + <div class="form-control"><input type="file" name="audio"/></div> + <input class="form-control input-sm" type="submit" name="submit" value="{{ _('Upload')}}"/> + <br /> + </div> + </form> + <br /> + <h4>{{ _('Download Remote Url')}}</h4> + <form class="form-inline" method=POST enctype=multipart/form-data action="{{ url_for('preprod_download_url') }}"> + <div class="form group"> + <input type="hidden" name="returnid" value="{{ event.id }}"/> + <input class="form-control" id="audiourl" type="text" name="audiourl"/> <button type="submit" class="btn btn-default">{{ _('Download')}}</button> + <br /> + </div> + + </form> + {% if message %} + <div class="center-block text-danger">{{ message }}</div> + {% endif %} + <div class="clearfix"><br /></div> + </div> + <div> + <ul class="list-group"> + {% for override in overrides %} + <li class="list-group-item">{{ override.location|basename }} ({{ override.ordering }}) + <div class="pull-right btn-toolbar" role="toolbar"> + <div class="btn-group"> + {% if not loop.last %} + <a href="{{ url_for('preprod_order', preprod_id=override.id, dir='down') }}" class="btn btn-default btn-xs" type="submit" value="fo"> <span style="color:green" class="glyphicon glyphicon-chevron-down"></span> </a> + {% endif %} + {% if not loop.first %} + <a href="{{ url_for('preprod_order', preprod_id=override.id, dir='up') }}" class="btn btn-default btn-xs" type="submit" value="fo"> <span style="color:green" class="glyphicon glyphicon-chevron-up"></span> </a> + {% endif %} + </div> + <div class="btn-group"> + <a href="{{ url_for('preprod_delete', event_id=event.id, preprod_id=override.id) }}" id="reset_button" class="btn btn-default btn-xs" type="button" value="reset"> <span style="color:red" class="glyphicon glyphicon-remove"> </span></a> + </div> + </div> + </li> + {% endfor %} + </ul> + + <div class="progress"> + <div class="progress-bar progress-bar-success" style="width: {{ green }}%"> + <span>{{ procent }}%</span> + </div> + <div class="progress-bar progress-bar-danger" style="width: {{ red }}%"></div> + </div> + </div> + </div> + + <div class="clearfix"></div> +</div> +{% endblock %} \ No newline at end of file diff --git a/modules/web/templates/scheduler.html b/modules/web/templates/scheduler.html new file mode 100644 index 0000000000000000000000000000000000000000..53104f56a467e770f187d2259ffce9c3dabb592e --- /dev/null +++ b/modules/web/templates/scheduler.html @@ -0,0 +1,15 @@ +<h3>{{ _('Scheduler Jobs') }}</h3> +<table class="table table-striped scheduler-jobs"> +<tr> + <th>{{ _('Job') }}</th> + <th>{{ _('Time') }}</th> + <th>{{ _('Until') }}</th> +</tr> +{% for job in jobs %} + <tr> + <td>{{ job.job }}</td> + <td>{{ job.time }}</td> + <td>{{ job.until }}</td> + </tr> +{% endfor %} +</table> \ No newline at end of file diff --git a/modules/web/templates/search.html b/modules/web/templates/search.html new file mode 100644 index 0000000000000000000000000000000000000000..6711235ea6048dc54b1832c20d4aeb61a943020c --- /dev/null +++ b/modules/web/templates/search.html @@ -0,0 +1,191 @@ +{% extends "layout.html" %} +{% block title %}Start{% endblock %} +{% block customcss %}<link rel="stylesheet" href="{{ url_for('static', filename='css/lib/jquery-ui.min.css') }}" type="text/css">{% endblock %} +{% block pagetitle %}{{ _('Search for broadcasts') }}{% endblock %} +{% block body %} +<form id="searchForm" classs="form-horizontal" method="POST" action="/search"> + <div class="form-group"> + <label class="col-sm-2 control-label" for="search">{{ _('Text/Name') }}:</label> + <div class="col-sm-10"> + <input class="form-control" id="search" name="search" type="text" value="{{ query.search }}" /> + </div> + </div> + <div class="clearfix"></div> + <div class="col-sm-12 form-group"><h4>{{ _('Date Search') }}</h4></div> + <div class="form-group"> + <div> + <label class="col-sm-2 control-label" for="from">{{ _('from') }}:</label> + <div class="col-sm-4"> + <input class="datepicker form-control" id="from" placeholder="2014-01-01" name="from" type="text" value="{{ query.from }}" /> + </div> + <label class="col-sm-2 control-label" for="to">{{ _('to') }}:</label> + <div class="col-sm-4"> + <input class="datepicker form-control" id="to" placeholder="2014-12-31" name="to" type="text" value="{{ query.to }}" /> + </div> + </div> + </div> + + + <div class="clearfix"></div> + <div class="col-sm-offset-2 col-sm-10"> + <br /> + <div class="btn-toolbar" role="toolbar"> + <div class="btn-group"> + <button class="btn btn-default" type="submit" value="fo"> <span class="glyphicon glyphicon-search"></span> {{ _('Search') }}</button> + <button id="reset_button" class="btn btn-default" type="button" value="reset"> <span class="glyphicon glyphicon-remove"> </span>{{ _('Reset') }}</button> + </div> + + </div> + + </div> + <div class="clearfix"></div> + <br /> +<uL class="list-group"> + {% for event in eventlist %} + <li id="li-{{ event.id }}" class="list-group-item"> + <div class="col-md-6"> + {% if event.overwrite_event %} + <a href="/search/reset/{{ event.id }}" data-hash="li-{{ event.id }}" class="pull-right btn btn-default btn-sm eventResetBtn" > + Zurücksetzen + </a> + {% else %} + <a href="/search/modal/{{ event.id }}" data-hash="li-{{ event.id }}" class="pull-right btn btn-default btn-sm eventOverwriteBtn" > + Ãœberschreiben + </a> + {% endif %} + <h3 {% if event.overwrite_event %} style="text-decoration:line-through" {% endif %}>{{ event.title }}</h3> <!-- Button trigger modal --> + + {% if event.overwrite_event %} + <h3>{{ event.overwrite_event.title }}</h3> + {% endif %} + <div>{{ event.start | formatdate }} - {{ event.end | formatdate }}</div> + {% if event.overwrite_event %} + <div>{% if event.overwrite_event.rerun %}{{ _('Repetition of') }} {{ event.overwrite_event.replay_of_datetime | formatdate }}{% endif %}</div> + {% else %} + <div>{% if event.rerun %}{{ _('Repetition of') }} {{ event.replay_of_datetime | formatdate }}{% endif %}</div> + {% endif %} + + <div>{{ event.subject }}</div> + {% if event.filename %} + <div><strong>{{ _('File') }}:</strong> {{ event.filename }} <span class="glyphicon {% if event.fileExists() %}glyphicon-ok text-success{% else %}glyphicon-minus text-danger{% endif %}" aria-hidden="true"></span></div> + {% endif %} + <h4><a data-toggle="collapse" href="#{{ event.id }}"><span class="caret"> </span> {{ event.tracks|length }} Tracks </a></h4> + <div class="collapse" id="{{ event.id }}"> + <uL> + {% for track in event.tracks %} + <li>{{ track.filename }} <span class="glyphicon {% if track.fileExists() %}glyphicon-ok text-success{% else %}glyphicon-minus text-danger{% endif %}" aria-hidden="true"></span></li> + {% endfor %} + </uL> + </div> + </div> + <div class="col-md-6"> + <div class="well well-small"> + <h4 class="text-info">{{ _('Preproduction') }}</h4> + + {% if event.overrides.count() > 0 %} + <div class="progress"> + <div class="progress-bar" role="progressbar" aria-valuenow="{{ event.procOverrides }}" aria-valuemax="100" aria-valuemin="0" style="width: {{ event.procOverrides }}%;"> + {{ event.procOverrides }}% + </div> + </div> + {% endif %} + <br /> + <a class="btn btn-xs btn-default" href="{{ url_for('preprod', eventid=event.id ) }}"><span class="glyphicon glyphicon-{% if event.overrides.count() > 0 %}pencil{% else %}plus{% endif %}">{% if event.overrides.count() > 0 %} {{ _('Edit') }}{% else %} {{ _('Add') }}{% endif %}</span></a> + </div> + </div> +<div class="clearfix"></div> + </li> + {% endfor %} +</uL> +<ul class=pagination> + {%- for page in pagination.iter_pages() %} + {% if page %} + {% if page == pagination.page %} + <li class="active"><a class="link active" href="#">{{ page }}</a></li> + {% else %} + <li><a class="link" data-page="{{ page }}" href="{{ url_for('search') }}">{{ page }}</a></li> + {% endif %} + {% else %} + <li><span class=ellipsis>…</span></li> + {% endif %} + {%- endfor %} + </ul> +<input type="hidden" id="form-page" name="page" value="{{ page }}"> +</form> + +<!-- Modal --> +<div class="modal fade" id="myModal" tabindex="-1" data-keyboard="false" data-backdrop="static"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="myModalLabel">Suche</h4> + </div> + <div class="modal-body"> + <iframe src="" style="zoom:0.60" frameborder="0" height="450" width="99.6%"></iframe> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">{{ _('Cancel') }}</button> + </div> + </div> + </div> +</div> +{% endblock %} + +{% block customjs %} +<script src="{{ url_for('static', filename='js/lib/jquery-1.10.2.min.js') }}"></script> +<script src="{{ url_for('static', filename='js/lib/bootstrap.min.js') }}"></script> +<script src="{{ url_for('static', filename='js/lib/jquery-ui.min.js') }}"></script> +<script> + + +function closeModal() { + $('#myModal').modal('hide') + window.location.reload(true) +} +$('.eventOverwriteBtn').click(function(e){ + e.preventDefault() + var link = $(this).attr("href"); + window.location.hash = $(this).attr("data-hash"); + $('iframe').attr("src",link); + $('#myModal').modal({show:true}) +}); + +$('.eventResetBtn').click(function(e){ + e.preventDefault() + var link = $(this).attr("href"); + var hash = $(this).attr("data-hash"); + jQuery.ajax ({ + url: link, + cache: false, + success: function (response) { + window.location.hash = hash; + window.location.reload(true) + }, + error: function () { + console.log('Hat nicht geklappt'); + } + }); + +}); + $(function() { + $('#reset_button').click(function(e){ + $('#from').val("") + $('#to').val("") + $('#search').val("") + $('#searchForm').submit() + }); + + $('.pagination .link').click(function(ev){ + ev.preventDefault(); + if (!$(this).attr('data-page')) { + return; + } + $('#form-page').val($(this).attr('data-page')); + $('#searchForm').submit(); + }); + $( ".datepicker" ).datepicker({dateFormat: "yy-mm-dd"}); + }); + +</script> +{% endblock %} \ No newline at end of file diff --git a/modules/web/templates/stream.html b/modules/web/templates/stream.html new file mode 100644 index 0000000000000000000000000000000000000000..4c300fdbb557f089b5d5e18f0f031f0157f2e169 --- /dev/null +++ b/modules/web/templates/stream.html @@ -0,0 +1,78 @@ +<h3>Streaming</h3> +<div class="info"><span class="text-info">{{ message }}</span></div> +{% for mount in mounts %} +<div> + <h4>{{ _('Current running title') }}: {{ mount.Title }}</h4> +</div> +<table class="table table-striped channels"> + <tbody> + + <tr> + <th>{{ _('ListenURL') }}</th> + <td>{{ mount.ListenURL }}</td> + </tr> + <tr> + <th>{{ _('Mountpoint') }}</th> + <td>{{ mount.Name }}</td> + </tr> + <tr> + <th>{{ _('StreamStart') }}</th> + <td>{{ mount.StreamStart }}</td> + </tr> + <tr> + <th>{{ _('AudioInfo') }}</th> + <td>{{ mount.AudioInfo }}</td> + </tr> + <tr> + <th>{{ _('Genre') }}</th> + <td>{{ mount.Genre }}</td> + </tr> + <tr> + <th>{{ _('ListenerCount') }}</th> + <td>{{ mount.ListenerCount }}</td> + </tr> + <tr> + <th>{{ _('ListenerPeak') }}</th> + <td>{{ mount.ListenerPeak }}</td> + </tr> + <tr> + <th>{{ _('Public') }}</th> + <td>{{ mount.Public }}</td> + </tr> + <tr> + <th>{{ _('ServerDescription') }}</th> + <td>{{ mount.ServerDescription }}</td> + </tr> + <tr> + <th>{{ _('ServerName') }}</th> + <td>{{ mount.ServerName }}</td> + </tr> + <tr> + <th>{{ _('ServerType') }}</th> + <td>{{ mount.ServerType }}</td> + </tr> + <tr> + <th>{{ _('ServerURL') }}</th> + <td>{{ mount.ServerURL }}</td> + </tr> + <tr> + <th>{{ _('SlowListeners') }}</th> + <td>{{ mount.SlowListeners }}</td> + </tr> + <tr> + <th>{{ _('SourceIP') }}</th> + <td>{{ mount.SourceIP }}</td> + </tr> + <tr> + <th>{{ _('TotalBytesRead') }}</th> + <td>{{ mount.TotalBytesRead }}</td> + </tr> + <tr> + <th>{{ _('TotalBytesSent') }}</th> + <td>{{ mount.TotalBytesSent }}</td> + </tr> + </tbody> +</table> +{% endfor %} + + diff --git a/modules/web/templates/sysinfo.html b/modules/web/templates/sysinfo.html new file mode 100644 index 0000000000000000000000000000000000000000..33b2b56eac8601f82c150e585f690a7f8e272c8d --- /dev/null +++ b/modules/web/templates/sysinfo.html @@ -0,0 +1,77 @@ +<div> + <h2>System</h2> + <table class="table table-bordered"> + <tr> + <th>{{ _('Load') }}</th> + <th>{{ _('Free Space') }}</th> + </tr> + <tr> + <td>{{ load }}</td> + <td>{{ diskfree }}</td> + </tr> + </table> +</div> + +<div class="row-fluid"> + <h2>{{ _('Modules') }}</h2> + {% for state in componentStates %} + <div class="col-sm-4 col-md-3 col-lg-2"> + <div id="controller-alive" class="alive {% if state.state %}bg-success{% else %}bg-danger{% endif %}"> + <div class="center-block"> + <h4>{{ state.title }}</h4> + <p>{% if state.state %} + {{ state.title }} is alive + {% else %} + {{ state.title }} is down + {% endif %} + </p> + </div> + </div> + </div> + {% endfor %} +</div> +<div class="clearfix"></div> +<div> + <table class="table table-striped"> + <tr> + <th>{{ _('Playlist Status') }}</th> + <td> + {% if playerState %} + <div><strong>{{ _('File') }}:</strong> {{ playerState.file }}</div> + {% if not playerState.complete %} + <div class="text-warning"> + <strong>{{ _('Warning: recording was interrupted at') }} {{ playerState.recorded }}%!!!</strong> + </div> + {% endif %} + {% else %} + {{ _('Playlist not playing') }} + + {% endif %} + </td> + <td> + {% if playerState %} + {{ _('Next event') }}: <i>play audio <strong>{{ trackStart.location }}</strong> at {{ trackStart.starts }}</i> + {% else %} + {{ _('Next Start') }} {{ playlistStart }} + {% endif %} + </td> + </tr> + <tr> + <th width="20%">{{ _('Recorder Status') }}</th> + <td>{% if recorderState %} + <div class="center-block text-center">{{ recorderState.file }}</div> + <div class="clearfix"></div> + <div class="progress"> + <div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" aria-valuenow="{{ recorderState.recorded }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ recorderState.recorded }}%;"> + {{ recorderState.recorded }}% + </div> + </div> + {% else %} + {{ _('Not recording') }} + {% endif %}</td> + <td> + {{ _('Next event') }}: <i>record <strong>{{ recordStart.location }}</strong> at {{ recordStart.starts }}</i> + </td> + </tr> + </table> +</div> diff --git a/modules/web/templates/trackservice.html b/modules/web/templates/trackservice.html new file mode 100644 index 0000000000000000000000000000000000000000..2014019b354504dd553eeec363bb280efc55b2e8 --- /dev/null +++ b/modules/web/templates/trackservice.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Title</title> +</head> +<body> +{{ length }} trackservice entries on {{ selected_date }} found +{% for entry in trackservice_entries %} + {{ entry.start }} +{% endfor %} +</body> +</html> \ No newline at end of file