Merge branch 'master' into new-schema

This commit is contained in:
Scott Lamb
2020-06-02 22:58:11 -07:00
20 changed files with 2979 additions and 1480 deletions

View File

@@ -46,7 +46,7 @@ import 'jquery-ui/themes/base/tooltip.css';
import 'jquery-ui/themes/base/theme.css';
// This causes our custom css to be loaded after the above!
import './assets/index.css';
import './index.css';
// Get ui widgets themselves
import 'jquery-ui/ui/widgets/tooltip';
@@ -384,7 +384,7 @@ export default class NVRApplication {
* Start the application.
*/
start() {
let nav = $('#nav');
const nav = $('#nav');
$('#toggle-nav').click(() => {
nav.toggle('slide');

View File

@@ -1,104 +0,0 @@
<!DOCTYPE html>
<!-- vim: set et: -->
<html lang="en">
<head>
<title>Moonfire NVR</title>
</head>
<body>
<div id="top">
<a id="toggle-nav">&#x2630;</a>
<span id="session"></div>
</div>
<div id="nav">
<form action="#">
<fieldset>
<legend>Streams</legend>
<table id="streams"></table>
</fieldset>
<fieldset id="datetime">
<legend>Date &amp; Time Range</legend>
<div id="from">
<div id="start-date"></div>
<div id="st">
<label for="start-time">Time:</label>
<input id="start-time" name="start-time" type="text" title="Starting
time within the day. Blank for the beginning of the day. Otherwise
HH:mm[:ss[:FFFFF]][+-HH:mm], where F is 90,000ths of a second.
Timezone is normally left out; it's useful once a year during the
ambiguous times of the &quot;fall back&quot; hour."></div>
</div>
<div id="range">Range:
<input type="radio" name="end-date-type" id="end-date-same" checked>
<label for="end-date-same">Single Day</label>
<input type="radio" name="end-date-type" id="end-date-other">
<label for="end-date-other">Multi Day</label><br/>
</div>
<div id="to">
<div id="end-date"></div>
<label for="start-time">Time:</label>
<input id="end-time" name="end-time" type="text" title="Ending
time within the day. Blank for the end of the day. Otherwise
HH:mm[:ss[:FFFFF]][+-HH:mm], where F is 90,000ths of a second.
Timezone is normally left out; it's useful once a year during the
ambiguous times of the &quot;fall back&quot; hour.">
</div>
</fieldset>
<fieldset>
<legend>Recordings Display</legend>
<label for="split">Max Video Duration:</label>
<select name="split" id="split">
<option value="324000000">1 hour</option>
<option value="1296000000">4 hours</option>
<option value="7776000000">24 hours</option>
<option value="infinite">infinite</option>
</select><br>
<input type="checkbox" checked id="trim" name="trim">
<label for="trim">Trim Segment Start &amp; End</label><br>
<input type="checkbox" checked id="ts" name="ts">
<label for="ts">Timestamp Track</label><br>
<label for="timefmt">Time Format:</label>
<select name="timefmt" id="timefmt">
<option value="MM/DD/YY hh:mm A">US-short</option>
<option value="MM/DD/YYYY hh:mm:ss A">US</option>
<option value="MM/DD/YY HH:mm" selected>Military-short</option>
<option value="MM/DD/YYYY HH:mm:ss">Military</option>
<option value="DD.MM.YY HH:mm">EU-short</option>
<option value="DD-MM-YYYY HH:mm:ss">EU</option>
<option value="YY-MM-DD hh:mm A">ISO-short (12h)</option>
<option value="YY-MM-DD HH:mm">ISO-short (24h)</option>
<option value="YYYY-MM-DD hh:mm:ss A">ISO (12h)</option>
<option value="YYYY-MM-DD HH:mm:ss">ISO (24h)</option>
<option value="YYYY-MM-DD HH:mm:ss">ISO 8601-like (No TZ)</option>
<option value="YYYY-MM-DDTHH:mm:ss">ISO 8601 (No TZ)</option>
<option value="YYYY-MM-DDTHH:mm:ssZ">ISO 8601</option>
<option value="YYYY-MM-DDTHH:mm:ss:FFFFFZ">Internal</option>
</select>
</fieldset>
</form>
</div>
<table id="videos"></table>
<div id="login">
<form>
<fieldset>
<table>
<tr>
<td><label for="login-username">Username:</label></td>
<td><input type="text" id="login-username" name="username"
autocomplete="username"></td>
</tr>
<tr>
<td><label for="login-password">Password:</label></td>
<td><input type="password" id="login-password" name="password"
autocomplete="current-password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" tabindex="-1" style="position:absolute; top:-1000px"></td>
</tr>
</table>
<p id="login-error"></p>
</fieldset>
</form>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

28
ui-src/favicon.svg Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px"
height="512px" viewBox="0.5 536.5 512 512" enable-background="new 0.5 536.5 512 512" xml:space="preserve">
<g id="Layer_2">
<circle fill="#E04E1B" cx="256.5" cy="792.5" r="256"/>
</g>
<g id="Layer_1">
<g>
<defs>
<circle id="SVGID_1_" cx="256.5" cy="792.5" r="256"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<g clip-path="url(#SVGID_2_)">
<path d="M422.611,604.539c-1.387-2.881-3.834-5.157-6.836-6.246c-3.026-1.092-6.367-0.97-9.244,0.436L11.497,782.899
c-4.261,1.985-6.973,6.248-6.997,10.944c-0.024,4.697,2.688,8.961,6.949,10.994l33.682,15.835L437.98,637.521L422.611,604.539z"
/>
<path d="M484.004,736.265l-35.806-76.807L73.655,834.082l108.938,51.293c1.624,0.725,3.391,1.136,5.158,1.136
c1.744,0,3.511-0.403,5.109-1.136l285.313-133.034C484.229,749.509,486.845,742.316,484.004,736.265z"/>
<path d="M378.002,880.649v-54.908l-72.642,33.876v77.453c0,4.843,2.929,9.275,7.439,11.164l230.034,96.854
c1.526,0.612,3.121,0.944,4.667,0.944v-94.096L378.002,880.649L378.002,880.649z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -18,6 +18,7 @@ body {
}
#nav {
float: left;
margin: 0 0.5em 0.5ex 0;
}
#session {
float: right;
@@ -32,17 +33,15 @@ body {
#videos {
display: inline-block;
padding-top: 0.5em;
padding-left: 1em;
padding-right: 1em;
}
#videos tbody:after {
content: "";
display: block;
height: 3ex;
}
table.videos {
table#videos {
border-collapse: collapse;
/*border-spacing: 0.5em 0.5ex;*/
}
tbody tr.name {
font-size: 110%;
@@ -65,8 +64,8 @@ tr.r td {
tr.r th,
tr.r td {
border: 0;
padding: 0.5ex 1.5em;
text-align: right;
padding: 0.5ex 1.5em;
}
fieldset {
@@ -80,9 +79,6 @@ fieldset legend {
#from, #to {
padding-right: 0.75em;
}
#st {
width: 100%;
}
#range {
padding: 0.5em 0;
@@ -96,3 +92,17 @@ video {
width: 100%;
height: 100%;
}
@media only screen and (max-width: 768px) {
#nav {
float: none;
display: none;
}
.resolution, .frameRate, .size {
display: none;
}
tr.r th,
tr.r td {
padding: 0.5ex 0.5em;
}
}

110
ui-src/index.html Normal file
View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<head>
<title>Moonfire NVR</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<div id="top">
<a id="toggle-nav">&#x2630;</a>
<span id="session"></div>
</div>
<div id="nav">
<form action="#">
<fieldset>
<legend>Streams</legend>
<table id="streams"></table>
</fieldset>
<fieldset id="datetime">
<legend>Date &amp; Time Range</legend>
<div id="from">
<legend>From</legend>
<div id="start-date"></div>
<label for="start-time">Time:</label>
<input id="start-time" name="start-time" type="text"
max-length="20"
title="Starting time within the day. Blank for the beginning of
the day. Otherwise HH:mm[:ss[:FFFFF]][+-HH:mm], where F is
90,000ths of a second. Timezone is normally left out; it's useful
once a year during the ambiguous times of the &quot;fall
back&quot; hour.">
</div>
<div id="to">
<legend>To</legend>
<div id="range">
<input type="radio" name="end-date-type" id="end-date-same" checked>
<label for="end-date-same">Same Day</label>
<input type="radio" name="end-date-type" id="end-date-other">
<label for="end-date-other">Other Day</label><br/>
</div>
<div id="end-date"></div>
<label for="end-time">Time:</label>
<input id="end-time" name="end-time" type="text" max-length="20"
title="Ending time within the day. Blank for the end of the day.
Otherwise HH:mm[:ss[:FFFFF]][+-HH:mm], where F is 90,000ths of a
second. Timezone is normally left out; it's useful once a year
during the ambiguous times of the &quot;fall back&quot; hour.">
</div>
</fieldset>
<fieldset>
<legend>Recordings Display</legend>
<label for="split">Max Video Duration:</label>
<select name="split" id="split">
<option value="324000000">1 hour</option>
<option value="1296000000">4 hours</option>
<option value="7776000000">24 hours</option>
<option value="infinite">infinite</option>
</select><br>
<input type="checkbox" checked id="trim" name="trim">
<label for="trim" title="Trim each segment of video so that it is fully
contained within the select time range. When this is not selected,
all segments will overlap with the selected time range but may start
and/or end outside it.">Trim Segment Start &amp; End</label><br>
<input type="checkbox" checked id="ts" name="ts">
<label for="ts" title="Include a text track in each .mp4 with the
timestamp at which the video was recorded.">Timestamp Track</label><br>
<label for="timefmt" title="The time format to use when displaying start
and end times in the video segment list. Note this currently doesn't
apply to the start/entry inputs.">Time Format:</label>
<select name="timefmt" id="timefmt">
<option value="MM/DD/YY hh:mm A">US-short</option>
<option value="MM/DD/YYYY hh:mm:ss A">US</option>
<option value="MM/DD/YY HH:mm" selected>Military-short</option>
<option value="MM/DD/YYYY HH:mm:ss">Military</option>
<option value="DD.MM.YY HH:mm">EU-short</option>
<option value="DD-MM-YYYY HH:mm:ss">EU</option>
<option value="YY-MM-DD hh:mm A">ISO-short (12h)</option>
<option value="YY-MM-DD HH:mm">ISO-short (24h)</option>
<option value="YYYY-MM-DD hh:mm:ss A">ISO (12h)</option>
<option value="YYYY-MM-DD HH:mm:ss">ISO (24h)</option>
<option value="YYYY-MM-DD HH:mm:ss">ISO 8601-like (No TZ)</option>
<option value="YYYY-MM-DDTHH:mm:ss">ISO 8601 (No TZ)</option>
<option value="YYYY-MM-DDTHH:mm:ssZ">ISO 8601</option>
<option value="YYYY-MM-DDTHH:mm:ss:FFFFFZ">Internal</option>
</select>
</fieldset>
</form>
</div>
<table id="videos"></table>
<div id="login">
<form>
<fieldset>
<table>
<tr>
<td><label for="login-username">Username:</label></td>
<td><input type="text" id="login-username" name="username"
autocomplete="username"></td>
</tr>
<tr>
<td><label for="login-password">Password:</label></td>
<td><input type="password" id="login-password" name="password"
autocomplete="current-password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" tabindex="-1" style="position:absolute; top:-1000px"></td>
</tr>
</table>
<p id="login-error"></p>
</fieldset>
</form>
</div>

View File

@@ -33,7 +33,6 @@
import NVRApplication from './NVRApplication';
import $ from 'jquery';
import './favicon.ico';
// On document load, start application
$(function() {

View File

@@ -146,16 +146,14 @@ export default class MoonfireAPI {
* Start a new AJAX request with the specified URL.
*
* @param {String} url URL to use
* @param {String} cacheOk True if cached results are OK
* @return {Request} jQuery request type
*/
request(url, cacheOk = false) {
request(url) {
return $.ajax(url, {
dataType: 'json',
headers: {
Accept: 'application/json',
},
cache: cacheOk,
});
}

View File

@@ -112,7 +112,8 @@ export default class RecordingsView {
$('<tr class="hdr">').append(
$(
_columnOrder
.map((name) => '<th>' + _columnLabels[name] + '</th>')
.map((name) =>
`<th class="${name}">${_columnLabels[name]}</th>`)
.join('')
)
),
@@ -271,7 +272,7 @@ export default class RecordingsView {
$('tr.r', tbody).remove();
this.recordings_.forEach((r) => {
const row = $('<tr class="r" />');
row.append(_columnOrder.map(() => $('<td/>')));
row.append(_columnOrder.map((c) => $(`<td class="${c}"/>`)));
row.on('click', () => {
console.log('Video clicked');
if (this.clickHandler_ !== null) {

View File

@@ -52,7 +52,7 @@ export default class VideoDialogView {
* This does not attach the player to the DOM anywhere! In fact, construction
* of the necessary video element is delayed until an attach is requested.
* Since the close of the video removes all traces of it in the DOM, this
* apprach allows repeated use by calling attach again!
* approach allows repeated use by calling attach again!
*/
constructor() {}
@@ -80,13 +80,13 @@ export default class VideoDialogView {
* @return {VideoDialogView} Returns "this" for chaining.
*/
play(title, width, url) {
const videoDomElement = this.videoElement_[0];
this.dialogElement_.dialog({
title: title,
width: width,
close: () => {
const videoDOMElement = this.videoElement_[0];
videoDOMElement.pause();
videoDOMElement.src = ''; // Remove current source to stop loading
videoDomElement.pause();
videoDomElement.src = ''; // Remove current source to stop loading
this.videoElement_ = null;
this.dialogElement_.remove();
this.dialogElement_ = null;
@@ -95,6 +95,21 @@ export default class VideoDialogView {
// Now that dialog is up, set the src so video starts
console.log('Video url: ' + url);
this.videoElement_.attr('src', url);
// On narrow displays (as defined by index.css), play videos in
// full-screen mode. When the user exits full-screen mode, close the
// dialog.
const narrowWindow = $('#nav').css('float') == 'none';
if (narrowWindow) {
console.log('Narrow window; starting video in full-screen mode.');
videoDomElement.requestFullscreen();
videoDomElement.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement !== videoDomElement) {
console.log('Closing video because user exited full-screen mode.');
this.dialogElement_.dialog('close');
}
});
}
return this;
}
}