forked from j62/ctbrec
1
0
Fork 0
ctbrec/server/src/main/resources/html/static/index.html

572 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<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"/>
<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">
<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>
</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>
</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>
<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">
<div class="col-lg-10 mx-auto">
<div class="form-group">
<label for="addModelByUrl">Add Model</label>
<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>
</div>
</div>
<div class="row">
<div class="col-lg-10 mx-auto">
<p class="lead"></p>
<div class="table-responsive">
<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></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"></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>
</td>
<td>
<button class="btn btn-secondary fa fa-trash" title="Remove model" data-bind="click: ctbrec.stop"></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<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>
<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>
</span>
</div>
</div>
<div class="row">
<div class="col-lg-10 mx-auto">
<p class="lead"></p>
<div class="table-responsive">
<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: 'progress'}">Progress</a></th> -->
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'sizeInByte'}">Size</a></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: recordings">
<tr>
<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_progressString"></td> -->
<td data-bind="text: ko_size"></td>
<td>
<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>
</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'),
click: ctbrec.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'),
click: ctbrec.deleteRecording"></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<section id="configuration" class="tab-pane fade" role="tabpanel" aria-labelledby="configuration-tab">
<div class="container">
<form data-bind="submit: saveConfig">
<div class="row">
<div class="col-lg-10 mx-auto">
<p class="lead"></p>
<div class="table-responsive">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
</thead>
<tbody data-bind="foreach: settings">
<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>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-10 mx-auto">
<button type="submit" class="btn btn-primary btn-lg btn-block" style="margin-bottom: 2em">Save</button>
</div>
</div>
</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 class="modal-content">
<span class="close">&times;</span>
<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="recordings.js"></script>
<script src="models.js"></script>
<script src="config.js"></script>
<script>
let cookieJar = cookie.cookie;
cookieJar.defaults.expires = 365 * 100;
cookieJar.defaults.secure = true;
let darkMode = false;
let onlineModels = [];
let observableModelsArray = ko.observableArray();
let observableRecordingsArray = ko.observableArray();
let observableSettingsArray = ko.observableArray();
let space = {
free: ko.observable(0),
total: ko.observable(0),
percent: ko.observable(0),
text: ko.observable('')
};
let throughput = {
bytes: ko.observable(0),
timeframe: ko.observable(0),
text: ko.observable('')
};
let hmac;
function addModelKeyPressed(e) {
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
let val = $('#addModelByUrl').val();
let duration = $('#recordingDuration').val(); // Get the recording duration input
if (charCode === 13) {
// Check if a valid duration is provided
let durationMinutes = duration ? parseInt(duration) : undefined;
ctbrec.add(val, durationMinutes, function() {
$('#addModelByUrl').val('');
$('#recordingDuration').val(''); // Clear the recording duration input
});
} else {
$('#addModelByUrl').autocomplete({
source: ["AmateurTv:", "BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "CherryTv:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MVLive:", "MyFreeCams:", "SecretFriends:", "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);
}
},
rerunProcessing: function(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
});
} catch (e) {
if (console) console.log('Unexpected error', e);
}
},
deleteRecording: function(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
})
.done(function(data) {
if (data.status === 'success') {
$.notify('Removed recording ' + name, 'info');
observableRecordingsArray.remove(recording);
} else {
$.notify('Removing recording ' + name + ' failed', 'error');
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
if (console) console.log(textStatus, errorThrown);
$.notify('Removing recording ' + 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
});
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");
newCss.setAttribute("rel", "stylesheet");
newCss.setAttribute("type", "text/css");
newCss.setAttribute("href", darkMode ? "freelancer-dark.css" : "freelancer.css");
console.log(darkMode, newCss);
$('#mainTheme').remove();
$('head').append(newCss);
$('#dark-mode-toggle').removeClass('fa-moon');
$('#dark-mode-toggle').removeClass('fa-sun');
$('#dark-mode-toggle').addClass(darkMode ? 'fa-sun' : 'fa-moon');
cookieJar.set("darkMode", darkMode);
}
</script>
</body>
</html>