Replace ajax calls with fetch
This commit is contained in:
parent
a9fa16051e
commit
23cf5e127a
|
@ -47,7 +47,7 @@
|
|||
<body id="page-top">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-bottom: 3rem">
|
||||
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-bottom: 0rem">
|
||||
<div class="container">
|
||||
<a class="navbar-brand js-scroll-trigger" href="@{/docs/index.md}"><img src="@{/static/icon64.png}" alt="Logo"/>CTB Recorder</a>
|
||||
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse"
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
function loadConfig() {
|
||||
try {
|
||||
$.ajax({
|
||||
type : 'GET',
|
||||
url : '../config',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
headers : {
|
||||
'CTBREC-HMAC' : CryptoJS.HmacSHA256('', hmac)
|
||||
const headers = {};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256('', hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
}).done(function(data, textStatus, jqXHR) {
|
||||
if (textStatus === 'success') {
|
||||
|
||||
fetch('../config', {
|
||||
method: 'GET',
|
||||
headers: headers,
|
||||
cache: 'no-cache'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
while (observableSettingsArray().length > 0) {
|
||||
observableSettingsArray.pop();
|
||||
}
|
||||
|
@ -19,50 +25,73 @@ function loadConfig() {
|
|||
param.ko_value = ko.observable(param.value);
|
||||
observableSettingsArray.push(param);
|
||||
}
|
||||
} else {
|
||||
if (console)
|
||||
console.log('request failed', data);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console)
|
||||
console.log(jqXHR, textStatus, errorThrown);
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to load config:', error);
|
||||
});
|
||||
} catch (e) {
|
||||
if (console)
|
||||
console.log('Unexpected error', e);
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function saveConfig() {
|
||||
try {
|
||||
let msg = JSON.stringify(observableSettingsArray());
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '../config',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
headers : {
|
||||
'CTBREC-HMAC' : CryptoJS.HmacSHA256(msg, hmac)
|
||||
// Create new array with updated values
|
||||
let settings = observableSettingsArray().map(param => ({
|
||||
key: param.key,
|
||||
type: param.type,
|
||||
value: param.ko_value() // Use observable value
|
||||
}));
|
||||
let msg = JSON.stringify(settings);
|
||||
console.log('Saving config JSON:', msg);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const timeoutId = setTimeout(() => abortController.abort(), 60000); // 60 seconds timeout
|
||||
|
||||
fetch('../config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'CTBREC-HMAC': CryptoJS.HmacSHA256(msg, hmac)
|
||||
},
|
||||
data : msg
|
||||
}).done(function(data, textStatus, jqXHR) {
|
||||
if (textStatus === 'success') {
|
||||
body: msg,
|
||||
signal: abortController.signal
|
||||
})
|
||||
.then(response => {
|
||||
clearTimeout(timeoutId); // Clear timeout on success
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Update param.value to reflect saved values
|
||||
observableSettingsArray().forEach(param => {
|
||||
param.value = param.ko_value();
|
||||
});
|
||||
loadConfig();
|
||||
$.notify('Configuration saved. You might have to restart the server.', 'info');
|
||||
} else {
|
||||
if (console)
|
||||
console.log('request failed', data);
|
||||
$.notify('Error: ' + jqXHR.responseText, 'error');
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console)
|
||||
console.log(jqXHR, textStatus, errorThrown);
|
||||
$.notify(errorThrown + ': ' + jqXHR.responseText, 'error');
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(timeoutId); // Clear timeout on error
|
||||
if (console) console.log('Request failed:', error);
|
||||
$.notify(error.message || 'Unknown error: ' + (error.responseText || 'No response'), 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console)
|
||||
console.log('Unexpected error', e);
|
||||
$.notify(errorThrown + ': ' + jqXHR.responseText, 'error');
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
$.notify('Unexpected error: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function notify(msg, type = 'info') {
|
||||
const toastEl = document.getElementById('toast');
|
||||
toastEl.querySelector('.toast-body').textContent = msg;
|
||||
toastEl.classList.remove('bg-success', 'bg-danger', 'bg-info');
|
||||
toastEl.classList.add(type === 'error' ? 'bg-danger' : type === 'info' ? 'bg-info' : 'bg-success', 'text-white');
|
||||
const toast = new bootstrap.Toast(toastEl, {
|
||||
autohide: true,
|
||||
delay: 5000
|
||||
});
|
||||
toast.show();
|
||||
document.getElementById('alert-container').appendChild(toastEl.parentElement);
|
||||
}
|
|
@ -1 +1,61 @@
|
|||
!function(o){"use strict";o('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function(){if(location.pathname.replace(/^\//,"")==this.pathname.replace(/^\//,"")&&location.hostname==this.hostname){var t=o(this.hash);if((t=t.length?t:o("[name="+this.hash.slice(1)+"]")).length)return o("html, body").animate({scrollTop:t.offset().top-70},1e3,"easeInOutExpo"),!1}}),o(document).scroll(function(){o(this).scrollTop()>100?o(".scroll-to-top").fadeIn():o(".scroll-to-top").fadeOut()}),o(".js-scroll-trigger").click(function(){o(".navbar-collapse").collapse("hide")}),o("body").scrollspy({target:"#mainNav",offset:80});var t=function(){o("#mainNav").offset().top>100?o("#mainNav").addClass("navbar-shrink"):o("#mainNav").removeClass("navbar-shrink")};t(),o(window).scroll(t),o(".portfolio-item").magnificPopup({type:"inline",preloader:!1,focus:"#username",modal:!0}),o(document).on("click",".portfolio-modal-dismiss",function(t){t.preventDefault(),o.magnificPopup.close()}),o(function(){o("body").on("input propertychange",".floating-label-form-group",function(t){o(this).toggleClass("floating-label-form-group-with-value",!!o(t.target).val())}).on("focus",".floating-label-form-group",function(){o(this).addClass("floating-label-form-group-with-focus")}).on("blur",".floating-label-form-group",function(){o(this).removeClass("floating-label-form-group-with-focus")})})}(jQuery);
|
||||
!(function (o) {
|
||||
"use strict";
|
||||
o('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function () {
|
||||
if (
|
||||
location.pathname.replace(/^\//, "") ==
|
||||
this.pathname.replace(/^\//, "") &&
|
||||
location.hostname == this.hostname
|
||||
) {
|
||||
var t = o(this.hash);
|
||||
if ((t = t.length ? t : o("[name=" + this.hash.slice(1) + "]")).length)
|
||||
return (
|
||||
o("html, body").animate(
|
||||
{ scrollTop: t.offset().top - 70 },
|
||||
1e3,
|
||||
"easeInOutExpo",
|
||||
),
|
||||
!1
|
||||
);
|
||||
}
|
||||
}),
|
||||
o(document).scroll(function () {
|
||||
o(this).scrollTop() > 100
|
||||
? o(".scroll-to-top").fadeIn()
|
||||
: o(".scroll-to-top").fadeOut();
|
||||
}),
|
||||
o(".js-scroll-trigger").click(function () {
|
||||
o(".navbar-collapse").collapse("hide");
|
||||
}),
|
||||
o("body").scrollspy({ target: "#mainNav", offset: 80 });
|
||||
var t = function () {
|
||||
o("#mainNav").offset().top > 100
|
||||
? o("#mainNav").addClass("navbar-shrink")
|
||||
: o("#mainNav").removeClass("navbar-shrink");
|
||||
};
|
||||
t(),
|
||||
o(window).scroll(t),
|
||||
o(".portfolio-item").magnificPopup({
|
||||
type: "inline",
|
||||
preloader: !1,
|
||||
focus: "#username",
|
||||
modal: !0,
|
||||
}),
|
||||
o(document).on("click", ".portfolio-modal-dismiss", function (t) {
|
||||
t.preventDefault(), o.magnificPopup.close();
|
||||
}),
|
||||
o(function () {
|
||||
o("body")
|
||||
.on("input propertychange", ".floating-label-form-group", function (t) {
|
||||
o(this).toggleClass(
|
||||
"floating-label-form-group-with-value",
|
||||
!!o(t.target).val(),
|
||||
);
|
||||
})
|
||||
.on("focus", ".floating-label-form-group", function () {
|
||||
o(this).addClass("floating-label-form-group-with-focus");
|
||||
})
|
||||
.on("blur", ".floating-label-form-group", function () {
|
||||
o(this).removeClass("floating-label-form-group-with-focus");
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
|
|
@ -1,87 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=yes">
|
||||
<meta name="description" content="CTB Recorder is a free recording software for Chaturbate">
|
||||
<meta name="author" content="">
|
||||
<meta name="version" content="${project.version}">
|
||||
|
||||
<title>CTB Recorder ${project.version}</title>
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom fonts for this template -->
|
||||
<link href="vendor/font-awesome/css/all.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Plugin CSS -->
|
||||
<link href="vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
|
||||
<link href="vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<link id="mainTheme" href="freelancer.css" rel="stylesheet">
|
||||
|
||||
<!-- Flowplayer -->
|
||||
<link rel="stylesheet" href="vendor/flowplayer/skin/skin.css">
|
||||
|
||||
<!-- custom css -->
|
||||
<link rel="stylesheet" href="custom.css">
|
||||
|
||||
<link rel="shortcut icon" href="favicon.svg" type="image/x-icon"/>
|
||||
|
||||
<link rel="shortcut icon" href="favicon.svg" type="image/x-icon" />
|
||||
<style>
|
||||
.ui-front {
|
||||
z-index: 2000;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body id="page-top">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav">
|
||||
</head>
|
||||
<body id="page-top">
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav">
|
||||
<div class="container">
|
||||
<a class="navbar-brand js-scroll-trigger" href="index.html"><img src="ctbrec.svg" alt="Logo" height="56"/></a>
|
||||
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse"
|
||||
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
Menu <i class="fa fa-bars"></i>
|
||||
<a class="navbar-brand js-scroll-trigger" href="index.html">
|
||||
<img src="ctbrec.svg" alt="Logo" height="56" />
|
||||
</a>
|
||||
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> Menu <i class="fa fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav nav ml-auto" role="tablist">
|
||||
<!--
|
||||
<li class="nav-item mx-0 mx-lg-1">
|
||||
<div class="ui-widget">
|
||||
<label for="search">Search: </label>
|
||||
<input id="search" style="margin-top: .7rem" placeholder="Search">
|
||||
</div>
|
||||
<div class="ui-widget"><input id="search" class="ui-autocomplete-input" autocomplete="off" placeholder="Search" style="margin-top: .7rem"></div>
|
||||
<a id="models-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded active" href="#models" role="tab" aria-controls="models" aria-selected="true">Models</a>
|
||||
</li>
|
||||
<li class="nav-item mx-0 mx-lg-1">
|
||||
<a id="recordings-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#recordings" role="tab" aria-controls="recordings" aria-selected="false">Recordings</a>
|
||||
</li>
|
||||
<li class="nav-item mx-0 mx-lg-1">
|
||||
<a id="configuration-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#configuration" role="tab" aria-controls="settings" aria-selected="false">Settings</a>
|
||||
</li>
|
||||
<li class="nav-item mx-0 mx-lg-1">
|
||||
<a id="help-page" class="nav-link py-3 px-0 px-lg-3 rounded" href="../docs/index.md" target="_blank" aria-controls="help" aria-selected="false">Help</a>
|
||||
</li>
|
||||
<li class="mx-0 mx-lg-1">
|
||||
<a id="dark-mode-toggle" class="nav-link py-3 px-0 px-lg-3 rounded fa fa-moon" href="#configuration" role="tab" aria-controls="settings" aria-selected="false"></a>
|
||||
</li>
|
||||
-->
|
||||
<li class="nav-item mx-0 mx-lg-1"><a id="models-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded active" href="#models"
|
||||
role="tab" aria-controls="models" aria-selected="true">Models</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a id="recordings-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#recordings"
|
||||
role="tab" aria-controls="recordings" aria-selected="false">Recordings</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a id="configuration-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#configuration"
|
||||
role="tab" aria-controls="settings" aria-selected="false">Settings</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a id="help-page" class="nav-link py-3 px-0 px-lg-3 rounded" href="../docs/index.md" target="_blank"
|
||||
aria-controls="help" aria-selected="false">Help</a></li>
|
||||
<li class="mx-0 mx-lg-1"><a id="dark-mode-toggle" class="nav-link py-3 px-0 px-lg-3 rounded fa fa-moon" href="#configuration" role="tab"
|
||||
aria-controls="settings" aria-selected="false"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="alert-container"></div>
|
||||
|
||||
<!-- Contact sheet modal -->
|
||||
<div class="modal fade" id="contactsheetModal" tabindex="-1" aria-labelledby="contactsheetModalLabel" aria-hidden="true">
|
||||
</nav>
|
||||
<div id="alert-container"></div>
|
||||
<!-- Contact sheet modal -->
|
||||
<div class="modal fade" id="contactsheetModal" tabindex="-1" aria-labelledby="contactsheetModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" style="max-width: 80vw;">
|
||||
<div class="modal-content modal-content-contactsheet">
|
||||
<div class="modal-header">
|
||||
|
@ -92,13 +71,14 @@
|
|||
<img id="imageModalImage" src="" alt="Contact Sheet" class="img-fluid" style="max-width: 100%; height: auto;" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="deleteRecordingBtn" type="button" class="btn btn-danger"><i class="fa fa-trash"></i></button>
|
||||
<button id="deleteRecordingBtn" type="button" class="btn btn-danger">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="myTabContent">
|
||||
</div>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<section id="models" class="tab-pane fade active show" role="tabpanel" aria-labelledby="models-tab">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
@ -106,15 +86,12 @@
|
|||
<div class="form-group">
|
||||
<label for="addModelByUrl">Add Model</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="addModelByUrl"
|
||||
placeholder="e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/" onKeyUp="addModelKeyPressed(event)">
|
||||
<input type="text" class="form-control" id="addModelByUrl" placeholder="e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/" onKeyUp="addModelKeyPressed(event)">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="toggleStopAtOptions">
|
||||
<i class="fa fa-clock"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary" type="button" onclick="addModel()">
|
||||
Add
|
||||
</button>
|
||||
<button class="btn btn-primary" type="button" onclick="addModel()"> Add </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -146,27 +123,39 @@
|
|||
<table class="table table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="#" data-bind="orderable: {collection: 'models', field: 'ko_name'}">Model</a></th>
|
||||
<th><a href="#" data-bind="orderable: {collection: 'models', field: 'ko_online'}">Online</a></th>
|
||||
<th><a href="#" data-bind="orderable: {collection: 'models', field: 'ko_recording'}">Recording</a></th>
|
||||
<th>
|
||||
<a href="#" data-bind="orderable: {collection: 'models', field: 'ko_name'}">Model</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="#" data-bind="orderable: {collection: 'models', field: 'ko_online'}">Online</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="#" data-bind="orderable: {collection: 'models', field: 'ko_recording'}">Recording</a>
|
||||
</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: models">
|
||||
<tr>
|
||||
<td><a data-bind="attr: { href: ko_url, title: ko_name }, text: ko_name"></a></td>
|
||||
<td><span data-bind="checked: ko_online, class: ko_online() ? `fa fa-check-square checkmark-green` : ``"
|
||||
style="font-size: 2em"></span></td>
|
||||
<td><span data-bind="checked: ko_recording, class: ko_recording() ? `fa fa-circle red` : ``" style="font-size: 2em; width: 40px; display: inline-block; text-align: center;"></span><span data-bind="if: ko_recordUntil && ko_recordUntil() !== undefined && ko_recordUntil() !== null && ko_recordUntil() !== 9000000000000000000"><i class="fa fa-clock" title="Scheduled stop time" style="font-size: 2em; color: #ffffff;"></i></span></td>
|
||||
<td>
|
||||
<button class="btn btn-secondary fa fa-play" title="Resume recording"
|
||||
data-bind="click: ctbrec.resume, visible: ko_suspended"></button>
|
||||
<button class="btn btn-secondary fa fa-pause" title="Pause recording"
|
||||
data-bind="click: ctbrec.suspend, hidden: ko_suspended"></button>
|
||||
<a data-bind="attr: { href: ko_url, title: ko_name }, text: ko_name"></a>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary fa fa-trash" title="Remove model" data-bind="click: ctbrec.stop"></button>
|
||||
<span data-bind="checked: ko_online, class: ko_online() ? `fa fa-check-square checkmark-green` : ``" style="font-size: 2em"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span data-bind="checked: ko_recording, class: ko_recording() ? `fa fa-circle red` : ``" style="font-size: 2em; width: 40px; display: inline-block; text-align: center;"></span>
|
||||
<span data-bind="if: ko_recordUntil && ko_recordUntil() !== undefined && ko_recordUntil() !== null && ko_recordUntil() !== 9000000000000000000">
|
||||
<i class="fa fa-clock" title="Scheduled stop time" style="font-size: 2em; color: #ffffff;"></i>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary fa fa-play" title="Resume recording" data-bind="click: resume, visible: ko_suspended"></button>
|
||||
<button class="btn btn-secondary fa fa-pause" title="Pause recording" data-bind="click: suspend, hidden: ko_suspended"></button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary fa fa-trash" title="Remove model" data-bind="click: stop"></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -179,14 +168,12 @@
|
|||
<section id="recordings" class="tab-pane fade" role="tabpanel" aria-labelledby="recordings-tab">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mx-auto">
|
||||
Space left: <span data-bind="text: space.text()"></span>
|
||||
<div class="col-lg-4 mx-auto"> Space left: <span data-bind="text: space.text()"></span>
|
||||
<span data-bind="text: throughput.text()" style="float:right"></span>
|
||||
<span class="progress">
|
||||
<span class="progress-bar progress-bar-striped" role="progressbar" aria-valuemin="0" data-bind="attr: {'aria-valuemax': space.total, 'aria-valuenow': space.free},
|
||||
style: { width: space.percent() + '%' },
|
||||
text: space.percent() + '%'">
|
||||
</span>
|
||||
text: space.percent() + '%'"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -197,10 +184,18 @@
|
|||
<table class="table table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'model.name'}">Model</a></th>
|
||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'startDate'}">Date</a></th>
|
||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'ko_status'}">Status</a></th>
|
||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'sizeInByte'}">Size</a></th>
|
||||
<th>
|
||||
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'model.name'}">Model</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'startDate'}">Date</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'ko_status'}">Status</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'sizeInByte'}">Size</a>
|
||||
</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
|
@ -210,7 +205,9 @@
|
|||
</thead>
|
||||
<tbody data-bind="foreach: recordings">
|
||||
<tr>
|
||||
<td><a data-bind="attr: { href: model.url, title: model.name }, text: model.name"></a></td>
|
||||
<td>
|
||||
<a data-bind="attr: { href: model.url, title: model.name }, text: model.name"></a>
|
||||
</td>
|
||||
<td data-bind="text: ko_date"></td>
|
||||
<td data-bind="text: ko_status"></td>
|
||||
<td data-bind="text: ko_size"></td>
|
||||
|
@ -221,17 +218,14 @@
|
|||
<button class="btn btn-secondary fa fa-play" title="Play recording" data-bind="click: play"></button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary fa fa-download" title="Download recording"
|
||||
data-bind="enable: ko_status() == 'FINISHED' && singleFile, click: download"></button>
|
||||
<button class="btn btn-secondary fa fa-download" title="Download recording" data-bind="enable: ko_status() == 'FINISHED' && singleFile, click: download"></button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary fa fa-recycle" title="Rerun processing"
|
||||
data-bind="enable: (ko_status() == 'WAITING' || ko_status() == 'FAILED' || ko_status() == 'FINISHED'),
|
||||
<button class="btn btn-secondary fa fa-recycle" title="Rerun processing" data-bind="enable: (ko_status() == 'WAITING' || ko_status() == 'FAILED' || ko_status() == 'FINISHED'),
|
||||
click: rerunProcessing"></button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary fa fa-trash" title="Delete recording"
|
||||
data-bind="enable: (ko_status() == 'FINISHED' || ko_status() == 'WAITING' || ko_status() == 'FAILED'),
|
||||
<button class="btn btn-secondary fa fa-trash" title="Delete recording" data-bind="enable: (ko_status() == 'FINISHED' || ko_status() == 'WAITING' || ko_status() == 'FAILED'),
|
||||
click: deleteRecording"></button>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -260,12 +254,7 @@
|
|||
<tr>
|
||||
<td data-bind="text: name"></td>
|
||||
<td>
|
||||
<div data-bind="ifnot: type === 'STRING_LIST'">
|
||||
<input class="form-control" data-bind="value: ko_value" style="width: 100%;" />
|
||||
</div>
|
||||
<div data-bind="if: type === 'STRING_LIST'">
|
||||
<textarea rows="3" class="form-control" data-bind="value: ko_value" style="width: 100%;"></textarea>
|
||||
</div>
|
||||
<input class="form-control" data-bind="value: ko_value, valueUpdate: 'input'" style="width: 100%" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -281,47 +270,37 @@
|
|||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript -->
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Plugin JavaScript -->
|
||||
<script src="vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
||||
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
<script src="vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
||||
<script src="vendor/notify.js/notify.min.js"></script>
|
||||
<script src="vendor/cookie_js/dist/cookie.umd.min.js"></script>
|
||||
|
||||
<!-- knockout -->
|
||||
<script src="vendor/knockout/knockout-3.5.0.js"></script>
|
||||
<script src="vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
||||
|
||||
<!-- Custom scripts for this template -->
|
||||
<script src="freelancer.min.js"></script>
|
||||
|
||||
<div id="player-window" class="modal">
|
||||
</div>
|
||||
<!-- Bootstrap core JavaScript -->
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Plugin JavaScript -->
|
||||
<script src="vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
||||
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
<script src="vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
||||
<script src="vendor/notify.js/notify.min.js"></script>
|
||||
<script src="vendor/cookie_js/dist/cookie.umd.min.js"></script>
|
||||
<!-- knockout -->
|
||||
<script src="vendor/knockout/knockout-3.5.0.js"></script>
|
||||
<script src="vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
||||
<!-- Custom scripts for this template -->
|
||||
<script src="freelancer.min.js"></script>
|
||||
<div id="player-window" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<video id="player" preload="none" controls width="100%">
|
||||
</video>
|
||||
<video id="player" preload="none" controls width="100%"></video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Player -->
|
||||
<script src="vendor/hls.js/hls.js"></script>
|
||||
<script src="modal.js"></script>
|
||||
|
||||
<!-- CryptoJS for HMAc authentication -->
|
||||
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
||||
|
||||
<!-- ctbrec stuff -->
|
||||
<script src="config.js"></script>
|
||||
<script src="recordings.js"></script>
|
||||
<script src="models.js"></script>
|
||||
|
||||
<script>
|
||||
</div>
|
||||
<!-- Video Player -->
|
||||
<script src="vendor/hls.js/hls.js"></script>
|
||||
<script src="modal.js"></script>
|
||||
<!-- CryptoJS for HMAc authentication -->
|
||||
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
||||
<!-- ctbrec stuff -->
|
||||
<script src="config.js"></script>
|
||||
<script src="recordings.js"></script>
|
||||
<script src="models.js"></script>
|
||||
<script>
|
||||
let cookieJar = cookie.cookie;
|
||||
cookieJar.defaults.expires = 365 * 100;
|
||||
cookieJar.defaults.secure = true;
|
||||
|
@ -342,46 +321,102 @@
|
|||
text: ko.observable('')
|
||||
};
|
||||
let hmac;
|
||||
|
||||
// Toggle visibility of stopAt options
|
||||
$(document).ready(function() {
|
||||
if (localStorage !== undefined && localStorage.hmac !== undefined) {
|
||||
if (console) console.log('using hmac from local storage');
|
||||
hmac = localStorage.hmac;
|
||||
} else {
|
||||
if (console) console.log('hmac not found in local storage. requesting hmac from server');
|
||||
const abortController = new AbortController();
|
||||
const timeoutId = setTimeout(() => abortController.abort(), 60000); // 60 seconds timeout
|
||||
fetch('../secured/hmac', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
signal: abortController.signal
|
||||
}).then(response => {
|
||||
clearTimeout(timeoutId); // Clear timeout on success
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}).then(data => {
|
||||
hmac = data.hmac;
|
||||
if (localStorage !== undefined) {
|
||||
if (console) console.log('saving hmac to local storage');
|
||||
localStorage.setItem("hmac", hmac);
|
||||
}
|
||||
}).catch(error => {
|
||||
clearTimeout(timeoutId); // Clear timeout on error
|
||||
if (console) console.log('Request failed:', error);
|
||||
$.notify('Could not get HMAC', 'error');
|
||||
hmac = '';
|
||||
});
|
||||
}
|
||||
|
||||
function tab(id) {
|
||||
$(id).css('display: block');
|
||||
}
|
||||
var navMain = $("#mainNav");
|
||||
navMain.on("click", "a", null, function() {
|
||||
navMain.collapse('hide');
|
||||
$('#navbarResponsive').collapse('hide');
|
||||
});
|
||||
ko.applyBindings({
|
||||
models: observableModelsArray,
|
||||
recordings: observableRecordingsArray,
|
||||
settings: observableSettingsArray,
|
||||
space,
|
||||
throughput
|
||||
});
|
||||
// Required for contact sheets
|
||||
window.onload = function() {
|
||||
loadConfig();
|
||||
};
|
||||
updateOnlineModels();
|
||||
updateRecordings();
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
|
||||
let selectedTab = e.target.attributes['id'].value;
|
||||
if (selectedTab === 'configuration-tab') {
|
||||
loadConfig();
|
||||
}
|
||||
});
|
||||
loadConfig();
|
||||
$('#dark-mode-toggle').click(toggleDarkMode);
|
||||
if (cookieJar.get('darkMode') === 'true') {
|
||||
toggleDarkMode();
|
||||
}
|
||||
// Toggle visibility of stopAt options
|
||||
$('#toggleStopAtOptions').click(function() {
|
||||
$('#stopAtOptionsRow').toggle();
|
||||
|
||||
// Set default datetime to current time + 1 hour when showing options
|
||||
if ($('#stopAtOptionsRow').is(':visible') && !$('#recordUntilDate').val()) {
|
||||
let now = new Date();
|
||||
now.setHours(now.getHours() + 1);
|
||||
|
||||
// Format the date for datetime-local input
|
||||
let year = now.getFullYear();
|
||||
let month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
let day = now.getDate().toString().padStart(2, '0');
|
||||
let hours = now.getHours().toString().padStart(2, '0');
|
||||
let minutes = now.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
$('#recordUntilDate').val(`${year}-${month}-${day}T${hours}:${minutes}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Function to add model (shared by Enter key and Add button)
|
||||
function addModel() {
|
||||
let val = $('#addModelByUrl').val();
|
||||
|
||||
if (!val) return; // Don't do anything if the input is empty
|
||||
|
||||
// Store if we need to add stopAt parameters
|
||||
let useStopAt = $('#stopAtOptionsRow').is(':visible') && $('#recordUntilDate').val();
|
||||
let stopAtDate = useStopAt ? $('#recordUntilDate').val() : null;
|
||||
let stopAtAction = useStopAt ? $('#recordUntilAction').val() : null;
|
||||
let modelInput = val;
|
||||
|
||||
// Step 1: Add the model using the standard approach
|
||||
ctbrec.add(val, undefined, function() {
|
||||
add(val, undefined, function() {
|
||||
console.log("Model added successfully");
|
||||
$('#addModelByUrl').val('');
|
||||
|
||||
if (useStopAt) {
|
||||
// Step 2: After a delay, set stopAt parameters
|
||||
setTimeout(function() {
|
||||
|
@ -392,78 +427,74 @@
|
|||
} else if (modelName.includes('/')) {
|
||||
modelName = modelName.split('/').filter(Boolean).pop();
|
||||
}
|
||||
|
||||
console.log("Looking for model: " + modelName);
|
||||
console.log("Models in array:", observableModelsArray());
|
||||
|
||||
// Try to find the model in the observableModelsArray
|
||||
let existingModel = null;
|
||||
|
||||
// Try various ways to find the model
|
||||
for (let i = 0; i < observableModelsArray().length; i++) {
|
||||
let model = observableModelsArray()[i];
|
||||
|
||||
if (model.name === modelName) {
|
||||
existingModel = model;
|
||||
break;
|
||||
}
|
||||
|
||||
if (model.ko_name && typeof model.ko_name === 'function' && model.ko_name() === modelName) {
|
||||
existingModel = model;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if URL contains the model name
|
||||
if (model.url && model.url.includes(modelName)) {
|
||||
existingModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!existingModel) {
|
||||
console.log("Could not find the model. Creating a simple model object instead.");
|
||||
// If we can't find the model, create a simple object with just the necessary properties
|
||||
existingModel = { name: modelName };
|
||||
existingModel = {
|
||||
name: modelName
|
||||
};
|
||||
}
|
||||
|
||||
// Add stopAt parameters to the model
|
||||
// Always use milliseconds for recordUntil
|
||||
let dateTimestamp = new Date(stopAtDate).getTime();
|
||||
existingModel.recordUntil = dateTimestamp;
|
||||
existingModel.recordUntilSubsequentAction = stopAtAction;
|
||||
|
||||
console.log("Using milliseconds for recordUntil. Value: " + existingModel.recordUntil);
|
||||
|
||||
console.log("Model with stopAt parameters:", existingModel);
|
||||
|
||||
// Send the stopAt action
|
||||
let stopAtMsg = '{"action": "stopAt", "model": ' + JSON.stringify(existingModel) + '}';
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '../rec',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000,
|
||||
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(stopAtMsg, hmac)},
|
||||
data: stopAtMsg
|
||||
})
|
||||
.done(function(data) {
|
||||
const abortController = new AbortController();
|
||||
const timeoutId = setTimeout(() => abortController.abort(), 60000); // 60 seconds timeout
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'CTBREC-HMAC': CryptoJS.HmacSHA256(stopAtMsg, hmac)
|
||||
},
|
||||
body: stopAtMsg,
|
||||
signal: abortController.signal
|
||||
}).then(response => {
|
||||
clearTimeout(timeoutId); // Clear timeout on success
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}).then(data => {
|
||||
console.log("StopAt response:", data);
|
||||
if (data.status === 'success') {
|
||||
// Use simple date formatting
|
||||
let date = new Date(stopAtDate).toLocaleString();
|
||||
$.notify('Recording will stop at ' + date, 'info');
|
||||
|
||||
// Reset the datetime input and hide the options
|
||||
$('#recordUntilDate').val('');
|
||||
$('#stopAtOptionsRow').hide();
|
||||
} else {
|
||||
$.notify('Setting stopAt parameters failed', 'error');
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
console.log(textStatus, errorThrown);
|
||||
}).catch(error => {
|
||||
clearTimeout(timeoutId); // Clear timeout on error
|
||||
console.log('Request failed:', error);
|
||||
$.notify('Setting stopAt parameters failed', 'error');
|
||||
});
|
||||
}, 3000); // Wait 3 seconds to make sure the model is registered
|
||||
|
@ -473,212 +504,15 @@
|
|||
|
||||
function addModelKeyPressed(e) {
|
||||
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
|
||||
|
||||
if (charCode === 13) {
|
||||
addModel();
|
||||
} else {
|
||||
$('#addModelByUrl').autocomplete({
|
||||
source: ["AmateurTv:", "BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "CherryTv:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MVLive:", "MyFreeCams:", "SecretFriends:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
|
||||
source: ["BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "MyFreeCams:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let ctbrec = {
|
||||
add: function(input, duration, onsuccess) {
|
||||
try {
|
||||
let model = {
|
||||
type: null,
|
||||
name: '',
|
||||
url: input
|
||||
};
|
||||
|
||||
if (console) console.log(model);
|
||||
let action = input.startsWith('http') ? 'startByUrl' : 'startByName';
|
||||
let msg = '{"action": "' + action + '", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '../rec',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000,
|
||||
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(msg, hmac)},
|
||||
data: msg
|
||||
})
|
||||
.done(function(data) {
|
||||
console.log(data);
|
||||
if (data.status === 'success') {
|
||||
if (onsuccess) {
|
||||
onsuccess.call(data);
|
||||
}
|
||||
$.notify('Model added', 'info');
|
||||
} else {
|
||||
$.notify('Adding model failed', 'error');
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console) console.log(textStatus, errorThrown);
|
||||
$.notify('Adding model failed', 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error', e);
|
||||
}
|
||||
},
|
||||
|
||||
resume: function(model) {
|
||||
try {
|
||||
let action = '{"action": "resume", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '../rec',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000,
|
||||
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
||||
data: action
|
||||
})
|
||||
.done(function(data) {
|
||||
if (data.status === 'success') {
|
||||
$.notify('Recording of ' + model.name + ' resumed', 'info');
|
||||
} else {
|
||||
$.notify('Resuming recording of model ' + model.name + ' failed', 'error');
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console) console.log(textStatus, errorThrown);
|
||||
$.notify('Resuming recording of model ' + model.name + ' failed', 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error', e);
|
||||
}
|
||||
},
|
||||
|
||||
suspend: function(model) {
|
||||
try {
|
||||
let action = '{"action": "suspend", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '../rec',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000,
|
||||
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
||||
data: action
|
||||
})
|
||||
.done(function(data) {
|
||||
if (data.status === 'success') {
|
||||
$.notify('Recording of ' + model.name + ' suspended', 'info');
|
||||
} else {
|
||||
$.notify('Suspending recording of model ' + model.name + ' failed', 'error');
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console) console.log(textStatus, errorThrown);
|
||||
$.notify('Suspending recording of model ' + model.name + ' failed', 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error', e);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function(model) {
|
||||
try {
|
||||
let action = '{"action": "stop", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '../rec',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000,
|
||||
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
||||
data: action
|
||||
})
|
||||
.done(function(data) {
|
||||
if (data.status === 'success') {
|
||||
$.notify('Removed ' + model.name, 'info');
|
||||
observableModelsArray.remove(model);
|
||||
} else {
|
||||
$.notify('Removing model ' + model.name + ' failed', 'error');
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console) console.log(textStatus, errorThrown);
|
||||
$.notify('Removing model ' + model.name + ' failed', 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error', e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
if (localStorage !== undefined && localStorage.hmac !== undefined) {
|
||||
if (console) console.log('using hmac from local storage');
|
||||
hmac = localStorage.hmac;
|
||||
} else {
|
||||
if (console) console.log('hmac not found in local storage. requesting hmac from server');
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '../secured/hmac',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000
|
||||
})
|
||||
.done(function(data) {
|
||||
hmac = data.hmac;
|
||||
if (localStorage !== undefined) {
|
||||
if (console) console.log('saving hmac to local storage');
|
||||
localStorage.setItem("hmac", hmac);
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console) console.log(textStatus, errorThrown);
|
||||
$.notify('Could not get HMAC', 'error');
|
||||
hmac = '';
|
||||
});
|
||||
}
|
||||
|
||||
function tab(id) {
|
||||
$(id).css('display: block');
|
||||
}
|
||||
|
||||
var navMain = $("#mainNav");
|
||||
navMain.on("click", "a", null, function() {
|
||||
navMain.collapse('hide');
|
||||
$('#navbarResponsive').collapse('hide');
|
||||
});
|
||||
|
||||
ko.applyBindings({
|
||||
models: observableModelsArray,
|
||||
recordings: observableRecordingsArray,
|
||||
settings: observableSettingsArray,
|
||||
space,
|
||||
throughput
|
||||
});
|
||||
|
||||
// Required for contact sheets
|
||||
window.onload = function() {
|
||||
loadConfig();
|
||||
};
|
||||
|
||||
updateOnlineModels();
|
||||
updateRecordings();
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
|
||||
let selectedTab = e.target.attributes['id'].value;
|
||||
if (selectedTab === 'configuration-tab') {
|
||||
loadConfig();
|
||||
}
|
||||
});
|
||||
loadConfig();
|
||||
|
||||
$('#dark-mode-toggle').click(toggleDarkMode);
|
||||
|
||||
if (cookieJar.get('darkMode') === 'true') {
|
||||
toggleDarkMode();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function toggleDarkMode() {
|
||||
darkMode = !darkMode;
|
||||
var newCss = document.createElement("link");
|
||||
|
@ -693,8 +527,6 @@
|
|||
$('#dark-mode-toggle').addClass(darkMode ? 'fa-sun' : 'fa-moon');
|
||||
cookieJar.set("darkMode", darkMode);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,37 +1,209 @@
|
|||
function add(input, duration, onsuccess) {
|
||||
try {
|
||||
let model = {
|
||||
type: null,
|
||||
name: '',
|
||||
url: input
|
||||
};
|
||||
if (console) console.log(model);
|
||||
let action = input.startsWith('http') ? 'startByUrl' : 'startByName';
|
||||
const msg = JSON.stringify({ action: action, model: model });
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(msg, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: msg
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (console) console.log(data);
|
||||
if (data.status === 'success') {
|
||||
if (onsuccess) {
|
||||
onsuccess.call(data);
|
||||
}
|
||||
$.notify('Model added', 'info');
|
||||
} else {
|
||||
$.notify('Adding model failed', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to add model:', error);
|
||||
$.notify('Adding model failed: ' + error.message, 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function resume(model) {
|
||||
try {
|
||||
const action = JSON.stringify({ action: "resume", model: model });
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
$.notify('Recording of ' + model.name + ' resumed', 'info');
|
||||
} else {
|
||||
$.notify('Resuming recording of model ' + model.name + ' failed', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to resume recording:', error);
|
||||
$.notify('Resuming recording of model ' + model.name + ' failed: ' + error.message, 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function suspend(model) {
|
||||
try {
|
||||
const action = JSON.stringify({ action: "suspend", model: model });
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
$.notify('Recording of ' + model.name + ' suspended', 'info');
|
||||
} else {
|
||||
$.notify('Suspending recording of model ' + model.name + ' failed', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to suspend recording:', error);
|
||||
$.notify('Suspending recording of model ' + model.name + ' failed: ' + error.message, 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function stop(model) {
|
||||
try {
|
||||
const action = JSON.stringify({ action: "stop", model: model });
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
$.notify('Removed ' + model.name, 'info');
|
||||
observableModelsArray.remove(model);
|
||||
} else {
|
||||
$.notify('Removing model ' + model.name + ' failed', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to remove model:', error);
|
||||
$.notify('Removing model ' + model.name + ' failed: ' + error.message, 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateOnlineModels() {
|
||||
try {
|
||||
let action = '{"action": "listOnline"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
headers : {
|
||||
'CTBREC-HMAC' : CryptoJS.HmacSHA256(action, hmac)
|
||||
},
|
||||
data : action
|
||||
}).done(function(data, textStatus, jqXHR) {
|
||||
const action = '{"action": "listOnline"}';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
onlineModels = data.models;
|
||||
} else {
|
||||
if (console)
|
||||
console.log('request failed', data);
|
||||
if (console) console.log('Request failed:', data);
|
||||
}
|
||||
updateModels();
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console)
|
||||
console.log(jqXHR, textStatus, errorThrown);
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to fetch online models:', error);
|
||||
});
|
||||
} catch (e) {
|
||||
if (console)
|
||||
console.log('Unexpected error', e);
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
setTimeout(updateOnlineModels, 3000);
|
||||
}
|
||||
|
||||
function isModelInArray(array, model) {
|
||||
for ( let idx in array) {
|
||||
for (let idx in array) {
|
||||
let m = array[idx];
|
||||
if (m.url === model.url) {
|
||||
return true;
|
||||
|
@ -58,7 +230,7 @@ function syncModels(models) {
|
|||
model.ko_name = ko.observable(model.name);
|
||||
model.ko_url = ko.observable(model.url);
|
||||
model.ko_online = ko.observable(false);
|
||||
for ( let i in onlineModels) {
|
||||
for (let i in onlineModels) {
|
||||
let onlineModel = onlineModels[i];
|
||||
if (onlineModel.url === model.url) {
|
||||
model.ko_online(true);
|
||||
|
@ -66,7 +238,6 @@ function syncModels(models) {
|
|||
}
|
||||
}
|
||||
model.ko_recording = ko.observable(model.online && !model.suspended);
|
||||
//model.ko_recording_class = ko.observable( (model.online && !model.suspended) ? 'fa fa-circle red' : '' );
|
||||
model.ko_suspended = ko.observable(model.suspended);
|
||||
model.ko_recordUntil = ko.observable(model.recordUntil);
|
||||
model.swallowEvents = false;
|
||||
|
@ -86,7 +257,7 @@ function syncModels(models) {
|
|||
m.ko_name(model.name);
|
||||
m.ko_url(model.url);
|
||||
let onlineState = false;
|
||||
for ( let i in onlineModels) {
|
||||
for (let i in onlineModels) {
|
||||
let onlineModel = onlineModels[i];
|
||||
if (onlineModel.url === model.url) {
|
||||
onlineState = true;
|
||||
|
@ -94,7 +265,6 @@ function syncModels(models) {
|
|||
}
|
||||
}
|
||||
m.ko_online(onlineState);
|
||||
//m.ko_recording_class( (model.online && !model.suspended) ? 'fa fa-circle red' : '');
|
||||
m.swallowEvents = true;
|
||||
m.ko_suspended(model.suspended);
|
||||
m.swallowEvents = false;
|
||||
|
@ -112,30 +282,36 @@ function syncModels(models) {
|
|||
|
||||
function updateModels() {
|
||||
try {
|
||||
let action = '{"action": "list"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
headers : {
|
||||
'CTBREC-HMAC' : CryptoJS.HmacSHA256(action, hmac)
|
||||
},
|
||||
data : action
|
||||
}).done(function(data) {
|
||||
const action = '{"action": "list"}';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
syncModels(data.models);
|
||||
} else {
|
||||
if (console)
|
||||
console.log('request failed', data);
|
||||
if (console) console.log('Request failed:', data);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console)
|
||||
console.log(textStatus, errorThrown);
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to fetch models:', error);
|
||||
});
|
||||
} catch (e) {
|
||||
if (console)
|
||||
console.log('Unexpected error', e);
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
|
@ -47,6 +47,42 @@ function play(recording) {
|
|||
}
|
||||
}
|
||||
|
||||
function show(recording) {
|
||||
if (console) console.log("Show: " + recording.ko_contactSheet());
|
||||
let localFilePath = recording.ko_contactSheet();
|
||||
let src = getImageUrl(localFilePath);
|
||||
|
||||
if (src) {
|
||||
let localFilePath = recording.ko_contactSheet();
|
||||
let src = getImageUrl(recording.ko_contactSheet());
|
||||
let hmacOfPath = CryptoJS.HmacSHA256(src, hmac);
|
||||
src = '..' + src;
|
||||
if (console) console.log("Path", src, "HMAC", hmacOfPath);
|
||||
if (hmac.length > 0) {
|
||||
src += "?hmac=" + hmacOfPath;
|
||||
}
|
||||
if (console) console.log("Final image URL: " + src);
|
||||
let modalImage = document.getElementById('imageModalImage');
|
||||
modalImage.src = src;
|
||||
|
||||
let fileName = localFilePath.split('\\').pop().split('/').pop();
|
||||
|
||||
let modalTitle = document.getElementById('contactsheetModalLabel');
|
||||
modalTitle.innerText = fileName + " - " + recording.ko_selectedResolution() + "p";
|
||||
|
||||
$('#contactsheetModal').modal('show');
|
||||
|
||||
document.getElementById('deleteRecordingBtn').onclick = function() {
|
||||
$('#contactsheetModal').modal('hide');
|
||||
deleteRecording(recording);
|
||||
};
|
||||
} else {
|
||||
console.log('No contact sheet available');
|
||||
$.notify('No contact sheet available', 'error');
|
||||
}
|
||||
document.activeElement.blur();
|
||||
}
|
||||
|
||||
function download(recording) {
|
||||
let src = recording.singleFile ? '/hls/' + recording.id : recording.playlist;
|
||||
let hmacOfPath = CryptoJS.HmacSHA256(src, hmac);
|
||||
|
@ -61,48 +97,78 @@ function download(recording) {
|
|||
function rerunProcessing(recording) {
|
||||
let name = recording.model.name + ' ' + recording.ko_date();
|
||||
try {
|
||||
let action = '{"action": "rerunPostProcessing", "recording": ' + JSON.stringify(recording) + '}';
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '../rec',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000,
|
||||
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
||||
data: action
|
||||
const action = JSON.stringify({ action: "rerunPostProcessing", recording: recording });
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status !== 'success') {
|
||||
if (console) console.log('Request failed:', data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to rerun post-processing:', error);
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error', e);
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteRecording(recording) {
|
||||
let name = recording.model.name + ' ' + recording.ko_date();
|
||||
try {
|
||||
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '../rec',
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
timeout: 60000,
|
||||
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
||||
data: action
|
||||
const action = JSON.stringify({ action: "delete", recording: recording });
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.done(function(data) {
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
$.notify('Removed recording ' + name, 'info');
|
||||
observableRecordingsArray.remove(recording);
|
||||
} else {
|
||||
if (console) console.log('Request failed:', data);
|
||||
$.notify('Removing recording ' + name + ' failed', 'error');
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console) console.log(textStatus, errorThrown);
|
||||
$.notify('Removing recording ' + name + ' failed', 'error');
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to delete recording:', error);
|
||||
$.notify(`Removing recording ${name} failed: ${error.message}`, 'error');
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error', e);
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
$.notify(`Removing recording ${name} failed: ${e.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,32 +198,6 @@ function isRecordingInArray(array, recording) {
|
|||
return false;
|
||||
}
|
||||
|
||||
function show(recording) {
|
||||
if (console) console.log("Show: " + recording.ko_contactSheet());
|
||||
let localFilePath = recording.ko_contactSheet();
|
||||
let src = getImageUrl(recording.ko_contactSheet());
|
||||
if (console) console.log("Show: " + src);
|
||||
if (src) {
|
||||
if (console) console.log("Image: " + src);
|
||||
// Update the modal's image source and display the modal
|
||||
let modalImage = document.getElementById('imageModalImage');
|
||||
modalImage.src = src;
|
||||
let fileName = localFilePath.split('\\').pop().split('/').pop();
|
||||
let modalTitle = document.getElementById('contactsheetModalLabel');
|
||||
modalTitle.innerText = fileName; // Set the title to the file name
|
||||
// Show the modal (assuming you are using Bootstrap's modal)
|
||||
$('#contactsheetModal').modal('show');
|
||||
// Update the delete button to use this recording
|
||||
document.getElementById('deleteRecordingBtn').onclick = function() {
|
||||
$('#contactsheetModal').modal('hide');
|
||||
deleteRecording(recording);
|
||||
};
|
||||
} else {
|
||||
console.log('No contact sheet available');
|
||||
}
|
||||
document.activeElement.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes recordings from the server with the displayed knockout recordings table
|
||||
*/
|
||||
|
@ -188,6 +228,8 @@ function syncRecordings(recordings) {
|
|||
});
|
||||
recording.ko_size = ko.observable(calculateSize(recording.sizeInByte));
|
||||
recording.ko_status = ko.observable(recording.status);
|
||||
recording.ko_singleFile = ko.observable(recording.singleFile);
|
||||
recording.ko_selectedResolution = ko.observable(recording.selectedResolution);
|
||||
recording.ko_contactSheet = ko.observable(
|
||||
recording.associatedFiles?.find(file => file.endsWith('.jpg') || file.endsWith('.png')) || null
|
||||
);
|
||||
|
@ -209,7 +251,9 @@ function syncRecordings(recordings) {
|
|||
r.progress = recording.progress;
|
||||
r.sizeInByte = recording.sizeInByte;
|
||||
r.status = recording.status;
|
||||
r.selectedResolution = recording.selectedResolution;
|
||||
r.startDate = recording.startDate;
|
||||
r.singleFile = recording.singleFile;
|
||||
r.ko_size(calculateSize(recording.sizeInByte));
|
||||
r.ko_status(recording.status);
|
||||
r.ko_contactSheet(
|
||||
|
@ -224,49 +268,68 @@ function syncRecordings(recordings) {
|
|||
|
||||
function updateRecordings() {
|
||||
try {
|
||||
let action = '{"action": "recordings"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
headers : {
|
||||
'CTBREC-HMAC' : CryptoJS.HmacSHA256(action, hmac)
|
||||
},
|
||||
data : action
|
||||
}).done(function(data) {
|
||||
const action = '{"action": "recordings"}';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
syncRecordings(data.recordings);
|
||||
updateDiskSpace();
|
||||
} else {
|
||||
if (console)
|
||||
console.log('request failed', data);
|
||||
if (console) console.log('Request failed:', data);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console)
|
||||
console.log(textStatus, errorThrown);
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to fetch recordings:', error);
|
||||
});
|
||||
} catch (e) {
|
||||
if (console)
|
||||
console.log('Unexpected error', e);
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
setTimeout(updateRecordings, 3000);
|
||||
}
|
||||
|
||||
function updateDiskSpace() {
|
||||
let action = '{"action": "space"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
headers : {
|
||||
'CTBREC-HMAC' : CryptoJS.HmacSHA256(action, hmac)
|
||||
},
|
||||
data : action
|
||||
}).done(function(data) {
|
||||
try {
|
||||
const action = '{"action": "space"}';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (hmac && hmac.length > 0) {
|
||||
headers['CTBREC-HMAC'] = CryptoJS.HmacSHA256(action, hmac).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
fetch('../rec', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: action
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
space.total(data.spaceTotal);
|
||||
space.free(data.spaceFree);
|
||||
|
@ -277,13 +340,15 @@ function updateDiskSpace() {
|
|||
let bytesPerSecond = data.throughput / data.throughputTimeframe * 1000;
|
||||
throughput.text(calculateSize(bytesPerSecond) + '/s');
|
||||
} else {
|
||||
if (console)
|
||||
console.log('request failed', data);
|
||||
if (console) console.log('Request failed:', data);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (console)
|
||||
console.log(textStatus, errorThrown);
|
||||
})
|
||||
.catch(error => {
|
||||
if (console) console.log('Failed to fetch disk space:', error);
|
||||
});
|
||||
} catch (e) {
|
||||
if (console) console.log('Unexpected error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function getImageUrl(localFilePath) {
|
||||
|
@ -292,18 +357,17 @@ function getImageUrl(localFilePath) {
|
|||
let recordingsDirEntry = ko.utils.arrayFirst(observableSettingsArray(), item => item.key === 'recordingsDir');
|
||||
if (!recordingsDirEntry) return null; // Can be removed since it has to exist in the config
|
||||
let recordingsDir = recordingsDirEntry.ko_value();
|
||||
// Normalize paths to use forward slashes
|
||||
const normalizedLocalPath = localFilePath.replace(/\\/g, '/');
|
||||
const normalizedRecordingsDir = recordingsDir.replace(/\\/g, '/');
|
||||
// Normalize paths to use forward slashes and convert to lowercase for comparison
|
||||
const normalizedLocalPath = localFilePath.replace(/\\/g, '/').toLowerCase();
|
||||
const normalizedRecordingsDir = recordingsDir.replace(/\\/g, '/').toLowerCase();
|
||||
const basePath = normalizedRecordingsDir.endsWith('/') ?
|
||||
normalizedRecordingsDir :
|
||||
normalizedRecordingsDir + '/';
|
||||
if (console) console.log("normalizedLocalPath: " + normalizedLocalPath);
|
||||
if (console) console.log("normalizedRecordingsDir: " + normalizedRecordingsDir);
|
||||
if (console) console.log("basePath: " + basePath);
|
||||
// Check if localFilePath starts with recordingsDir and replace it
|
||||
if (normalizedLocalPath.startsWith(basePath)) {
|
||||
return "/image/recording/" + normalizedLocalPath.substring(basePath.length);
|
||||
// Use original path case for URL
|
||||
const originalNormalizedLocalPath = localFilePath.replace(/\\/g, '/');
|
||||
return "/image/recording/" + originalNormalizedLocalPath.substring(basePath.length);
|
||||
}
|
||||
return null; // Return null if the path doesn't match - in theory shouldn't happen
|
||||
return null; // Return null if the path doesn't match
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue