mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-11-25 12:06:11 -05:00
Merge branch 'master' into new-schema
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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">☰</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 & 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 "fall back" 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 "fall back" 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 & 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
28
ui-src/favicon.svg
Normal 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 |
@@ -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
110
ui-src/index.html
Normal 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">☰</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 & 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 "fall
|
||||
back" 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 "fall back" 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 & 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>
|
||||
@@ -33,7 +33,6 @@
|
||||
import NVRApplication from './NVRApplication';
|
||||
|
||||
import $ from 'jquery';
|
||||
import './favicon.ico';
|
||||
|
||||
// On document load, start application
|
||||
$(function() {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user