diff --git a/chapters.py b/chapters.py index 44d37e4cf8d8f1c56fd919d284b399b709b834b1..5eb22d3aa515ddc886b51b027cba1611bed8e419 100644 --- a/chapters.py +++ b/chapters.py @@ -1,3 +1,4 @@ +import json from server import * @app.route('/internal/newchapter/<int:lectureid>', methods=['POST', 'GET']) @@ -31,4 +32,6 @@ def chapters(lectureid): c['start'] = c['time'] c['end'] = last['start'] if last else 9999 last = c + if 'json' in request.values: + return Response(json.dumps([{'time': c['time'], 'text': c['text']} for c in chapters]), mimetype='application/json') return Response(render_template('chapters.srt',chapters=chapters), 200, {'Content-Type':'text/vtt'}) diff --git a/db.py b/db.py index 9ae0d82f465dce55d9dc852763a281773c084414..3965fae7dda6d8604551b11cfa309f9a16d046ca 100644 --- a/db.py +++ b/db.py @@ -42,6 +42,9 @@ if config['DB_ENGINE'] == 'sqlite': params = [(p.replace(microsecond=0) if isinstance(p, datetime) else p) for p in params] return operation, params + def show(operation, host=None): + return {} + elif config['DB_ENGINE'] == 'mysql': import mysql.connector @@ -57,6 +60,29 @@ elif config['DB_ENGINE'] == 'mysql': params = [(p.replace(microsecond=0) if isinstance(p, datetime) else p) for p in params] return operation, params + def show(operation, host=config.get('MYSQL_HOST', None)): + if host: + db = mysql.connector.connect(user=config['MYSQL_USER'], password=config['MYSQL_PASSWD'], host=host, port=config.get('MYSQL_PORT', 3306)) + else: + db = mysql.connector.connect(user=config['MYSQL_USER'], password=config['MYSQL_PASSWD'], unix_socket=config.get('MYSQL_UNIX', None)) + cur = db.cursor() + cur.execute(operation) + rows = [] + try: + rows = cur.fetchall() + except mysql.connector.errors.InterfaceError as ie: + if ie.msg == 'No result set to fetch from.': + # no problem, we were just at the end of the result set + pass + else: + raise + res = {} + for row in rows: + res[row[0]] = row[1] + cur.close() + db.close() + return res + def query(operation, *params, delim="sep"): operation, params = fix_query(operation, params) cur = get_dbcursor() diff --git a/db_schema.sql b/db_schema.sql index 85036ec2148f7d5e7f6b6884b18ba8d2cb012e0f..44ac919a500b8b2dfb18350d02c58c20aa7efd71 100644 --- a/db_schema.sql +++ b/db_schema.sql @@ -61,7 +61,8 @@ CREATE TABLE IF NOT EXISTS `courses_data` ( `description` text NOT NULL DEFAULT '', `internal` text NOT NULL DEFAULT '', `responsible` text NOT NULL DEFAULT '', - `feed_url` text NOT NULL DEFAULT '' + `feed_url` text NOT NULL DEFAULT '', + `external` INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS `filesizes` ( `path` varchar(255) NOT NULL PRIMARY KEY, @@ -98,7 +99,8 @@ CREATE TABLE IF NOT EXISTS `lectures_data` ( `time_updated` datetime NOT NULL, `jumplist` text NOT NULL DEFAULT '', `titlefile` varchar(255) NOT NULL DEFAULT '', - `live` INTEGER NOT NULL DEFAULT 0 + `live` INTEGER NOT NULL DEFAULT 0, + `norecording` INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS `places` ( `place` varchar(20) NOT NULL PRIMARY KEY, diff --git a/edit.py b/edit.py index a00f7a6d6801ddf4ad505c0dc5929f44fa6b0e06..cfd7d1e7debcc85dbd8ef8f29bf259e7def2296d 100644 --- a/edit.py +++ b/edit.py @@ -25,7 +25,8 @@ editable_tables = { 'internal': {'type': 'text'}, 'responsible': {'type': 'shortstring'}, 'deleted': {'type': 'boolean'}, - 'description': {'type': 'text'} }, + 'description': {'type': 'text'}, + 'external': {'type': 'boolean'}}, 'creationtime_fields': ['created_by', 'time_created', 'time_updated'] }, 'lectures': { 'table': 'lectures_data', @@ -41,7 +42,8 @@ editable_tables = { 'duration': {'type': 'duration'}, 'jumplist': {'type': ''}, 'deleted': {'type': 'boolean'}, - 'live': {'type': 'boolean'}}, + 'live': {'type': 'boolean'}, + 'norecording': {'type': 'boolean'}}, 'creationtime_fields': ['course_id', 'time_created', 'time_updated'] }, 'videos': { 'table': 'videos_data', diff --git a/server.py b/server.py index 288b462e67c6524345b8d7c144229e3b101052d4..3cf8f28dc17ff0c58dc8dfecb1193d0a08039d62 100644 --- a/server.py +++ b/server.py @@ -70,7 +70,7 @@ app.jinja_env.globals['gitversion'] = { 'hash': output[1], 'longhash': output[0] if not config.get('SECRET_KEY', None): config['SECRET_KEY'] = os.urandom(24) -from db import query, modify, searchquery, ldapauth, ldapget +from db import query, modify, show, searchquery, ldapauth, ldapget mod_endpoints = [] @@ -325,14 +325,14 @@ def index(): return "Not found", 404 return redirect(url_for('lecture', course=courses[0]['handle'], id=request.args['lectureid']),code=302) - start = date.today() - timedelta(days=1) + start = date.today() end = start + timedelta(days=7) upcomming = query(''' SELECT lectures.*, "course" AS sep, courses.* FROM lectures JOIN courses ON (lectures.course_id = courses.id) - WHERE (time > ?) AND (time < ?) and lectures.visible and courses.visible and courses.listed - ORDER BY time ASC LIMIT 30''',start,end) + WHERE (time > ?) AND (time < ?) AND (? OR (lectures.visible AND courses.visible AND courses.listed)) AND NOT lectures.norecording + ORDER BY time ASC LIMIT 30''', start, end, ismod()) for i in upcomming: i['date'] = i['time'].date() latestvideos=query(''' @@ -509,7 +509,7 @@ def login(): @app.route('/internal/logout', methods=['GET', 'POST']) def logout(): - session.pop('user') + session.pop('user', None) return redirect(request.values.get('ref', url_for('index'))) @app.route('/internal/auth') @@ -617,7 +617,33 @@ def legacy(phpfile=None): return redirect(url_for('feed', handle=request.args.copy().popitem()[0]),code=302) print("Unknown legacy url:",request.url) return redirect(url_for('index'),code=302) - + +import json + +@app.route('/internal/dbstatus') +@register_navbar('DB-Status', icon='ok') +@mod_required +def dbstatus(): + hosts = set() + clusters = {} + status = {} + variables = {} + for host in config.get('MYSQL_DBSTATUS_HOSTS', [])+[config.get('MYSQL_HOST', None)]: + for _host in show('SHOW VARIABLES LIKE "wsrep_cluster_address"', host=host)['wsrep_cluster_address'][len('gcomm://'):].split(','): + hosts.add(_host) + for host in sorted(list(hosts)): + try: + status[host] = show('SHOW GLOBAL STATUS LIKE "wsrep%"', host=host) + variables[host] = show('SHOW GLOBAL VARIABLES LIKE "wsrep%"', host=host) + except: + status[host] = {'wsrep_cluster_state_uuid': '', 'wsrep_local_state_comment': 'Not reachable', 'wsrep_cluster_conf_id': '0', 'wsrep_cluster_status': 'Unknown'} + variables[host] = {'wsrep_node_name': host, 'wsrep_cluster_name': 'unknown'} + cluster = variables[host]['wsrep_cluster_name']+'-'+status[host]['wsrep_cluster_conf_id'] + if cluster not in clusters: + clusters[cluster] = [] + clusters[cluster].append(host) + return render_template('dbstatus.html', clusters=clusters, statuses=status, vars=variables), 200 + import edit import feeds import importer diff --git a/static/videojs/video-js.css b/static/videojs/video-js.css index ef9ff8a37598bf7a1fd12d88f94b57f2378aaa61..821bac9627ed58b0d49383b976f3bcfd7c234dca 100644 --- a/static/videojs/video-js.css +++ b/static/videojs/video-js.css @@ -450,7 +450,7 @@ body.vjs-full-window { line-height: 1.4em; font-size: 1.2em; text-align: center; - text-transform: lowercase; } + /* text-transform: lowercase;}*/ } .vjs-menu li:focus, .vjs-menu li:hover { diff --git a/static/videojs/video.js b/static/videojs/video.js index 9419308b41bce458e90c619ac76ba8c3d21089c9..9cb476d7c5aff8e675d464533d17fe1b74987726 100644 --- a/static/videojs/video.js +++ b/static/videojs/video.js @@ -4404,7 +4404,7 @@ var ChaptersButton = function (_TextTrackButton) { var title = Dom.createEl('li', { className: 'vjs-menu-title', - innerHTML: (0, _toTitleCase2['default'])(this.kind_), + innerHTML: 'Kapitel', tabIndex: -1 }); diff --git a/static/videojs/videojs-markers.js b/static/videojs/videojs-markers.js new file mode 100644 index 0000000000000000000000000000000000000000..d89b4531485cd8fedf83d0a81d389ca5eee55738 --- /dev/null +++ b/static/videojs/videojs-markers.js @@ -0,0 +1,379 @@ +/*! videojs-markers - v0.6.1 - 2016-10-24 +* Copyright (c) 2016 ; Licensed */ +'use strict'; + +(function ($, videojs, undefined) { + // default setting + var defaultSetting = { + markerStyle: { + 'width': '7px', + 'border-radius': '30%', + 'background-color': 'red' + }, + markerTip: { + display: true, + text: function text(marker) { + return "Break: " + marker.text; + }, + time: function time(marker) { + return marker.time; + } + }, + breakOverlay: { + display: false, + displayTime: 3, + text: function text(marker) { + return "Break overlay: " + marker.overlayText; + }, + style: { + 'width': '100%', + 'height': '20%', + 'background-color': 'rgba(0,0,0,0.7)', + 'color': 'white', + 'font-size': '17px' + } + }, + onMarkerClick: function onMarkerClick(marker) {}, + onMarkerReached: function onMarkerReached(marker, index) {}, + markers: [] + }; + + // create a non-colliding random number + function generateUUID() { + var d = new Date().getTime(); + var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c == 'x' ? r : r & 0x3 | 0x8).toString(16); + }); + return uuid; + }; + + var NULL_INDEX = -1; + + function registerVideoJsMarkersPlugin(options) { + /** + * register the markers plugin (dependent on jquery) + */ + + var setting = $.extend(true, {}, defaultSetting, options), + markersMap = {}, + markersList = [], + // list of markers sorted by time + videoWrapper = $(this.el()), + currentMarkerIndex = NULL_INDEX, + player = this, + markerTip = null, + breakOverlay = null, + overlayIndex = NULL_INDEX; + + function sortMarkersList() { + // sort the list by time in asc order + markersList.sort(function (a, b) { + return setting.markerTip.time(a) - setting.markerTip.time(b); + }); + } + + function addMarkers(newMarkers) { + newMarkers.forEach(function (marker) { + marker.key = generateUUID(); + + videoWrapper.find('.vjs-progress-holder').append(createMarkerDiv(marker)); + + // store marker in an internal hash map + markersMap[marker.key] = marker; + markersList.push(marker); + }); + + sortMarkersList(); + } + + function getPosition(marker) { + return setting.markerTip.time(marker) / player.duration() * 100; + } + + function createMarkerDiv(marker) { + var markerDiv = $("<div class='vjs-marker'></div>"); + markerDiv.css(setting.markerStyle).css({ + "margin-left": -parseFloat(markerDiv.css("width")) / 2 + 'px', + "left": getPosition(marker) + '%' + }).attr("data-marker-key", marker.key).attr("data-marker-time", setting.markerTip.time(marker)); + + // add user-defined class to marker + if (marker.class) { + markerDiv.addClass(marker.class); + } + + // bind click event to seek to marker time + markerDiv.on('click', function (e) { + var preventDefault = false; + if (typeof setting.onMarkerClick === "function") { + // if return false, prevent default behavior + preventDefault = setting.onMarkerClick(marker) === false; + } + + if (!preventDefault) { + var key = $(this).data('marker-key'); + player.currentTime(setting.markerTip.time(markersMap[key])); + } + }); + + if (setting.markerTip.display) { + registerMarkerTipHandler(markerDiv); + } + + return markerDiv; + } + + function updateMarkers() { + // update UI for markers whose time changed + markersList.forEach(function (marker) { + var markerDiv = videoWrapper.find(".vjs-marker[data-marker-key='" + marker.key + "']"); + var markerTime = setting.markerTip.time(marker); + + if (markerDiv.data('marker-time') !== markerTime) { + markerDiv.css({ "left": getPosition(marker) + '%' }).attr("data-marker-time", markerTime); + } + }); + sortMarkersList(); + } + + function removeMarkers(indexArray) { + // reset overlay + if (!!breakOverlay) { + overlayIndex = NULL_INDEX; + breakOverlay.css("visibility", "hidden"); + } + currentMarkerIndex = NULL_INDEX; + + var deleteIndexList = []; + indexArray.forEach(function (index) { + var marker = markersList[index]; + if (marker) { + // delete from memory + delete markersMap[marker.key]; + deleteIndexList.push(index); + + // delete from dom + videoWrapper.find(".vjs-marker[data-marker-key='" + marker.key + "']").remove(); + } + }); + + // clean up markers array + deleteIndexList.reverse(); + deleteIndexList.forEach(function (deleteIndex) { + markersList.splice(deleteIndex, 1); + }); + + // sort again + sortMarkersList(); + } + + // attach hover event handler + function registerMarkerTipHandler(markerDiv) { + markerDiv.on('mouseover', function () { + var marker = markersMap[$(markerDiv).data('marker-key')]; + + if (!!markerTip) { + markerTip.find('.vjs-tip-inner').text(setting.markerTip.text(marker)); + + // margin-left needs to minus the padding length to align correctly with the marker + markerTip.css({ + "left": getPosition(marker) + '%', + "margin-left": -parseFloat(markerTip.width()) / 2 - 5 + 'px', + "visibility": "visible" + }); + } + }); + + markerDiv.on('mouseout', function () { + !!markerTip && markerTip.css("visibility", "hidden"); + }); + } + + function initializeMarkerTip() { + markerTip = $("<div class='vjs-tip'><div class='vjs-tip-arrow'></div><div class='vjs-tip-inner'></div></div>"); + videoWrapper.find('.vjs-progress-holder').append(markerTip); + } + + // show or hide break overlays + function updateBreakOverlay() { + if (!setting.breakOverlay.display || currentMarkerIndex < 0) { + return; + } + + var currentTime = player.currentTime(); + var marker = markersList[currentMarkerIndex]; + var markerTime = setting.markerTip.time(marker); + + if (currentTime >= markerTime && currentTime <= markerTime + setting.breakOverlay.displayTime) { + if (overlayIndex !== currentMarkerIndex) { + overlayIndex = currentMarkerIndex; + breakOverlay && breakOverlay.find('.vjs-break-overlay-text').html(setting.breakOverlay.text(marker)); + } + + breakOverlay && breakOverlay.css('visibility', "visible"); + } else { + overlayIndex = NULL_INDEX; + breakOverlay && breakOverlay.css("visibility", "hidden"); + } + } + + // problem when the next marker is within the overlay display time from the previous marker + function initializeOverlay() { + breakOverlay = $("<div class='vjs-break-overlay'><div class='vjs-break-overlay-text'></div></div>").css(setting.breakOverlay.style); + videoWrapper.append(breakOverlay); + overlayIndex = NULL_INDEX; + } + + function onTimeUpdate() { + onUpdateMarker(); + updateBreakOverlay(); + options.onTimeUpdateAfterMarkerUpdate && options.onTimeUpdateAfterMarkerUpdate(); + } + + function onUpdateMarker() { + /* + check marker reached in between markers + the logic here is that it triggers a new marker reached event only if the player + enters a new marker range (e.g. from marker 1 to marker 2). Thus, if player is on marker 1 and user clicked on marker 1 again, no new reached event is triggered) + */ + if (!markersList.length) { + return; + } + + var getNextMarkerTime = function getNextMarkerTime(index) { + if (index < markersList.length - 1) { + return setting.markerTip.time(markersList[index + 1]); + } + // next marker time of last marker would be end of video time + return player.duration(); + }; + var currentTime = player.currentTime(); + var newMarkerIndex = NULL_INDEX; + + if (currentMarkerIndex !== NULL_INDEX) { + // check if staying at same marker + var nextMarkerTime = getNextMarkerTime(currentMarkerIndex); + if (currentTime >= setting.markerTip.time(markersList[currentMarkerIndex]) && currentTime < nextMarkerTime) { + return; + } + + // check for ending (at the end current time equals player duration) + if (currentMarkerIndex === markersList.length - 1 && currentTime === player.duration()) { + return; + } + } + + // check first marker, no marker is selected + if (currentTime < setting.markerTip.time(markersList[0])) { + newMarkerIndex = NULL_INDEX; + } else { + // look for new index + for (var i = 0; i < markersList.length; i++) { + nextMarkerTime = getNextMarkerTime(i); + if (currentTime >= setting.markerTip.time(markersList[i]) && currentTime < nextMarkerTime) { + newMarkerIndex = i; + break; + } + } + } + + // set new marker index + if (newMarkerIndex !== currentMarkerIndex) { + // trigger event if index is not null + if (newMarkerIndex !== NULL_INDEX && options.onMarkerReached) { + options.onMarkerReached(markersList[newMarkerIndex], newMarkerIndex); + } + currentMarkerIndex = newMarkerIndex; + } + } + + // setup the whole thing + function initialize() { + if (setting.markerTip.display) { + initializeMarkerTip(); + } + + // remove existing markers if already initialized + player.markers.removeAll(); + addMarkers(options.markers); + + if (setting.breakOverlay.display) { + initializeOverlay(); + } + onTimeUpdate(); + player.on("timeupdate", onTimeUpdate); + } + + // setup the plugin after we loaded video's meta data + player.on("loadedmetadata", function () { + initialize(); + }); + + // exposed plugin API + player.markers = { + getMarkers: function getMarkers() { + return markersList; + }, + next: function next() { + // go to the next marker from current timestamp + var currentTime = player.currentTime(); + for (var i = 0; i < markersList.length; i++) { + var markerTime = setting.markerTip.time(markersList[i]); + if (markerTime > currentTime) { + player.currentTime(markerTime); + break; + } + } + }, + prev: function prev() { + // go to previous marker + var currentTime = player.currentTime(); + for (var i = markersList.length - 1; i >= 0; i--) { + var markerTime = setting.markerTip.time(markersList[i]); + // add a threshold + if (markerTime + 0.5 < currentTime) { + player.currentTime(markerTime); + return; + } + } + }, + add: function add(newMarkers) { + // add new markers given an array of index + addMarkers(newMarkers); + }, + remove: function remove(indexArray) { + // remove markers given an array of index + removeMarkers(indexArray); + }, + removeAll: function removeAll() { + var indexArray = []; + for (var i = 0; i < markersList.length; i++) { + indexArray.push(i); + } + removeMarkers(indexArray); + }, + updateTime: function updateTime() { + // notify the plugin to update the UI for changes in marker times + updateMarkers(); + }, + reset: function reset(newMarkers) { + // remove all the existing markers and add new ones + player.markers.removeAll(); + addMarkers(newMarkers); + }, + destroy: function destroy() { + // unregister the plugins and clean up even handlers + player.markers.removeAll(); + breakOverlay && breakOverlay.remove(); + markerTip && markerTip.remove(); + player.off("timeupdate", updateBreakOverlay); + delete player.markers; + } + }; + } + + videojs.plugin('markers', registerVideoJsMarkersPlugin); +})(jQuery, window.videojs); +//# sourceMappingURL=videojs-markers.js.map diff --git a/static/videojs/videojs.markers.css b/static/videojs/videojs.markers.css new file mode 100644 index 0000000000000000000000000000000000000000..7655a64f877f1876e46c464435aedb29b5bd99b8 --- /dev/null +++ b/static/videojs/videojs.markers.css @@ -0,0 +1,59 @@ +.vjs-marker { + position: absolute; + left: 0; + bottom: 0em; + opacity: 1; + height: 100%; + transition: opacity .2s ease; + -webkit-transition: opacity .2s ease; + -moz-transition: opacity .2s ease; + z-index: 100; +} +.vjs-marker:hover { + cursor: pointer; + -webkit-transform: scale(1.3, 1.3); + -moz-transform: scale(1.3, 1.3); + -o-transform: scale(1.3, 1.3); + -ms-transform: scale(1.3, 1.3); + transform: scale(1.3, 1.3); +} +.vjs-tip { + visibility: hidden; + display: block; + opacity: 0.8; + padding: 5px; + font-size: 10px; + position: absolute; + bottom: 14px; + z-index: 100000; +} +.vjs-tip .vjs-tip-arrow { + background: url(data:image/gif;base64,R0lGODlhCQAJAIABAAAAAAAAACH5BAEAAAEALAAAAAAJAAkAAAIRjAOnwIrcDJxvwkplPtchVQAAOw==) no-repeat top left; + bottom: 0; + left: 50%; + margin-left: -4px; + background-position: bottom left; + position: absolute; + width: 9px; + height: 5px; +} +.vjs-tip .vjs-tip-inner { + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + padding: 5px 8px 4px 8px; + background-color: black; + color: white; + max-width: 200px; + text-align: center; +} +.vjs-break-overlay { + visibility: hidden; + position: absolute; + z-index: 100000; + top: 0; +} +.vjs-break-overlay .vjs-break-overlay-text { + padding: 9px; + text-align: center; +} diff --git a/templates/base.html b/templates/base.html index 4ac3bad76935eb1eaaa4257b52d2f7bf24e4f486..31bdd63c5df108ddf0271eab367dd16c295d5128 100644 --- a/templates/base.html +++ b/templates/base.html @@ -18,6 +18,7 @@ <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='style.css')}}"> <link rel="stylesheet" href="{{url_for('static', filename='font-awesome/css/font-awesome.css')}}"> <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='videojs/video-js.css')}}"> + <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='videojs/videojs.markers.css')}}"> <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='videojs/videojs-resolution-switcher.css')}}"> <script src="{{url_for('static', filename='jquery.js')}}"></script> @@ -33,6 +34,7 @@ <script src="{{url_for('static', filename='videojs/videojs-resolution-switcher.js')}}"></script> <script src="{{url_for('static', filename='videojs/videojs-contrib-hls.js')}}"></script> <script src="{{url_for('static', filename='videojs/videojs.hotkeys.js')}}"></script> + <script src="{{url_for('static', filename='videojs/videojs-markers.js')}}"></script> {% endblock %} </head> <body> diff --git a/templates/course.html b/templates/course.html index 9868782fac52a58f22e958483f919f012ad6e4a2..6bbb9e915a58af67c96861574ed0937f2ddd3f3a 100644 --- a/templates/course.html +++ b/templates/course.html @@ -41,6 +41,7 @@ <tbody> <tr><td>Sichtbar:</td><td>{{ moderator_checkbox(['courses',course.id,'visible'], course.visible) }}</td></tr> <tr><td>Gelistet:</td><td>{{ moderator_checkbox(['courses',course.id,'listed'], course.listed) }}</td></tr> + <tr><td>Nicht im Drehplan:</td><td>{{ moderator_checkbox(['courses',course.id,'external'], course.external) }}</td></tr> <tr><td>Videos downloadbar:</td><td>{{ moderator_checkbox(['courses',course.id,'downloadable'], course.downloadable) }}</td></tr> <tr><td>Short:</td><td>{{ moderator_editor(['courses',course.id,'short'], course.short) }}</td></tr> <tr><td>Handle:</td><td>{{ moderator_editor(['courses',course.id,'handle'], course.handle) }}</td></tr> diff --git a/templates/dbstatus.html b/templates/dbstatus.html new file mode 100644 index 0000000000000000000000000000000000000000..384e3a84446cd6003c08ae676d673cffb72281fc --- /dev/null +++ b/templates/dbstatus.html @@ -0,0 +1,53 @@ +{% extends "base.html" %} +{% block content %} +<div class="panel panel-default"> + <div class="panel-heading"><h1 class="panel-title">Cluster</b></h1></div> + <ul class="list-group"> + {% for clustername, clusternodes in clusters.items() %} + <li class="list-group-item"><strong>{{vars[clusternodes|first]['wsrep_cluster_name']}}</strong> ({{statuses[clusternodes|first]['wsrep_cluster_status']}}, Größe={{statuses[clusternodes|first]['wsrep_cluster_size']}}, Konfiguration={{statuses[clusternodes|first]['wsrep_cluster_conf_id']}}) + <ul class="list-group"> + {% for host in clusternodes %} + <li class="list-group-item list-group-item-{{{'1': 'warning', '2': 'warning', '3': 'info', '4': 'success'}.get(statuses[host]['wsrep_local_state'], 'danger')}}"><a href="#{{host|tagid}}">{{vars[host]['wsrep_node_name']}}</a> ({{statuses[host]['wsrep_local_state_comment']}}, Letzte Änderung={{statuses[host]['wsrep_last_committed']}}, Recv.-Avg.={{statuses[host]['wsrep_local_recv_queue_avg']}}, Send-Avg.={{statuses[host]['wsrep_local_send_queue_avg']}})</li> + {% endfor %} + </ul> + </li> + {% endfor %} + </ul> + </table> +</div> +{% for host, status in statuses.items() %} +<div class="panel panel-default" id="{{host|tagid}}"> + <div class="panel-heading"><h1 class="panel-title">{{vars[host]['wsrep_node_name']}}</h1></div> + <div class="row" style="margin: 0px;"> + <div class="col-xs-6 table-responsive"> + <table class="table"> + <tr> + <th>Variable</th> + <th>Wert</th> + </tr> + {% for key, value in vars[host].items() %} + <tr> + <td>{{key}}</td> + <td>{{value}}</td> + </tr> + {% endfor %} + </table> + </div> + <div class="col-xs-6 table-responsive"> + <table class="table"> + <tr> + <th>Status-Variable</th> + <th>Wert</th> + </tr> + {% for key, value in status.items() %} + <tr> + <td>{{key}}</td> + <td>{{value}}</td> + </tr> + {% endfor %} + </table> + </div> + </div> +</div> +{% endfor %} +{% endblock %} diff --git a/templates/index.html b/templates/index.html index fbb2cbc3de210ccef6341a03ef077f6e66965da0..70b72bf0971752f5b2c93093936a067a29df8f05 100644 --- a/templates/index.html +++ b/templates/index.html @@ -111,7 +111,7 @@ <ul class="list-group" style="margin: 0px;"> {% for i in g.list %} <li class="list-group-item list-group-item-condensed"> - {{i.time|time}} <a href="{{url_for('course', id=i.course_id)}}">{{i.course.title}}</a>: <a href="{{url_for('course', id=i.course_id)}}#lecture-{{i.id}}">{{i.title}}</a> {{livelabel(i.live, False)}} + {{i.time|time}} <a href="{{url_for('course', handle=i.course.handle)}}">{{i.course.title}}</a>: <a href="{{url_for('course', handle=i.course.handle)}}#lecture-{{i.id}}">{{i.title}}</a> {{livelabel(i.live, False)}} </li> {% endfor %} </ul> diff --git a/templates/macros.html b/templates/macros.html index c0f27fed22d74b2e0e89451c79d6993a3e20b1c7..4acc84304ea6b645338d74701ee4a63fc127a470 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -97,6 +97,25 @@ $(function() { videojs("videoplayer").createModal('',{"uncloseable": true }).contentEl().innerHTML='<div class="hidden-print alert alert-danger" role="alert">{{ msg|safe }}</div>'; {% endfor %} + + //markers + $.ajax({method: "GET", url: "{{url_for('chapters',lectureid=lecture.id, json=1)}}", dataType: "json", + success: function (data) { + videojs("videoplayer").markers({ + markerStyle: { + 'width':'5px', + 'border-radius': '40%', + 'background-color': 'black' + }, + markerTip:{ + display: true, + text: function(marker) { + return marker.text; + } + }, + markers: data}); + }}); + }); </script> {% endmacro %} @@ -165,7 +184,7 @@ $('#embedcodebtn').popover( {% endmacro %} {% macro lecture_list_item(lecture,videos,global_permissions) %} -<li class="list-group-item" id="lecture-{{lecture.id}}"> +<li class="list-group-item{% if lecture.norecording %} text-muted{% endif %}" id="lecture-{{lecture.id}}"> <div class="row"> {% if ismod() or (videos|length > 0) %} <div style="background-image: url('{{ config.VIDEOPREFIX }}/thumbnail/l_{{lecture.id}}.jpg')" class="col-sm-2 col-xs-12 thumbnailimg"> @@ -190,6 +209,7 @@ $('#embedcodebtn').popover( <li>{{ moderator_editor(['lectures',lecture.id,'internal'], lecture.internal) }}</li> <li>Sichtbar: {{ moderator_checkbox(['lectures',lecture.id,'visible'], lecture.visible) }}</li> <li>Livestream geplant: {{ moderator_checkbox(['lectures',lecture.id,'live'], lecture.live) }}</li> + <li>Wird nicht aufgenommen: {{ moderator_checkbox(['lectures',lecture.id,'norecording'], lecture.norecording) }}</li> <li>Hörsaal: {{ moderator_editor(['lectures',lecture.id,'place'], lecture.place) }} </li> {% endif %} </ul> diff --git a/templates/sortlog.html b/templates/sortlog.html index 184af50c9510d00fcd17941ceb9c187ddb532d66..21d05c37147e80c5176e31520c95b368506d9e53 100644 --- a/templates/sortlog.html +++ b/templates/sortlog.html @@ -54,7 +54,7 @@ <td><a href="{{ config.VIDEOPREFIX }}/{{i.path}}">{{i.path}}</a></td> <td><a href="{{url_for('course', id=i.course_id)}}">{{i.course_id}}</a></td> <td><a href="{{url_for('course', id=i.course_id)}}#lecture-{{i.lecture_id}}">{{i.lecture_id}}</a></td> - <td>{{i.id}}</td> + <td>{{i.video_id}}</td> </tr> {% endfor %} </table> diff --git a/timetable.py b/timetable.py index 2ccb548870b4a16c87876ed5b1e0b04eec5511e1..ad2a54dfeeb82a585466efdb0b7c4bc0e4d53cb9 100644 --- a/timetable.py +++ b/timetable.py @@ -26,7 +26,7 @@ def timetable(): SELECT lectures.*, courses.short, "course" AS sep, courses.* FROM lectures JOIN courses ON (lectures.course_id = courses.id) - WHERE time < ? and time > ? + WHERE time < ? AND time > ? AND NOT norecording AND NOT external ORDER BY time ASC''', i['date']+timedelta(weeks=2), i['date']-timedelta(weeks=2)): # we can not use the where clause of sql to match against the time, because sqlite and mysql use a different syntax -.- # we still use it to only get the lectures for a 3 week periode