Files
xTeVe/html/js/app.js
T
nathan 3a3ba861dc
continuous-integration/drone/push Build encountered an error
Phase 5a: persistent websocket with request ids and reconnect
Client (ts/network_ts.ts): one WebSocket per page. Commands queue and go
out one at a time with a client-chosen id; the response is matched on the
echoed id (or to the in-flight request for older servers). 12 s timeout
per request, exponential-backoff reconnect (0.5 s to 10 s), the in-flight
request is retried after a reconnect, and log polls are de-duplicated so
they cannot pile up behind a stalled connection. The old global flag that
silently dropped any request made while another was in flight is gone.

Server (src/webserver.go): the /data/ handler now serves any number of
commands on one connection (it used to break out of its loop after the
first reply without closing the socket, leaving it open and deaf; the old
client papered over that by opening a new socket per request). Connection
closed on exit, request id echoed in the response.

Test: TestWSServesMultipleCommandsPerConnection.
2026-09-26 13:19:38 +10:00

3506 lines
140 KiB
JavaScript

// Websocket transport between the web UI and xTeVe.
//
// One socket stays open for the life of the page. Requests are queued and
// sent one at a time; each carries an id that the server echoes back, and
// the response is matched on it (or, for older servers that close after a
// single reply, to the oldest in-flight request). Connection loss triggers
// a reconnect with exponential backoff, and queued requests survive it.
class WSClient {
constructor() {
this.socket = null;
this.queue = [];
this.inFlight = null;
this.nextID = 1;
this.reconnectDelay = 500;
this.reconnectTimer = 0;
this.closedByUs = false;
this.requestTimeoutMs = 12000;
}
url() {
var protocol = window.location.protocol == "https:" ? "wss://" : "ws://";
var host = window.location.host;
if (host == undefined || host.length < 1) {
host = window.location.hostname;
}
return protocol + host + "/data/";
}
// send queues a command and resolves with the parsed response.
send(payload) {
var client = this;
return new Promise(function (resolve, reject) {
var req = {
id: String(client.nextID++),
payload: payload,
resolve: resolve,
reject: reject,
timer: 0,
sent: false
};
client.queue.push(req);
client.pump();
});
}
// hasQueued reports whether a command of this kind is already waiting.
hasQueued(cmd) {
if (this.inFlight != null && this.inFlight.payload["cmd"] == cmd) {
return true;
}
for (var i = 0; i < this.queue.length; i++) {
if (this.queue[i].payload["cmd"] == cmd) {
return true;
}
}
return false;
}
connect() {
if (this.socket != null && (this.socket.readyState == WebSocket.OPEN || this.socket.readyState == WebSocket.CONNECTING)) {
return;
}
var client = this;
var socket = new WebSocket(this.url());
this.socket = socket;
socket.onopen = function () {
WS_AVAILABLE = true;
client.reconnectDelay = 500;
client.pump();
};
socket.onmessage = function (e) {
client.handleMessage(e.data);
};
socket.onerror = function () {
// onclose follows; nothing to do here.
};
socket.onclose = function () {
if (client.socket == socket) {
client.socket = null;
}
// An in-flight request that never got its answer goes back to the
// front of the queue and is retried on the next connection.
if (client.inFlight != null) {
var req = client.inFlight;
client.inFlight = null;
req.sent = false;
client.queue.unshift(req);
}
client.scheduleReconnect();
};
}
scheduleReconnect() {
if (this.closedByUs || this.reconnectTimer != 0) {
return;
}
if (this.queue.length == 0) {
// Idle: reconnect lazily on the next request.
return;
}
var client = this;
this.reconnectTimer = window.setTimeout(function () {
client.reconnectTimer = 0;
client.connect();
}, this.reconnectDelay);
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 10000);
}
// pump sends the next queued request if the socket is open and idle.
pump() {
if (this.inFlight != null || this.queue.length == 0) {
return;
}
if (this.socket == null || this.socket.readyState != WebSocket.OPEN) {
this.connect();
return;
}
var req = this.queue.shift();
this.inFlight = req;
req.sent = true;
req.payload["id"] = req.id;
var client = this;
req.timer = window.setTimeout(function () {
if (client.inFlight == req) {
client.inFlight = null;
req.reject(new Error("timeout"));
// A socket that stopped answering is not trusted any more.
client.resetSocket();
client.pump();
}
}, this.requestTimeoutMs);
try {
this.socket.send(JSON.stringify(req.payload));
}
catch (err) {
window.clearTimeout(req.timer);
this.inFlight = null;
req.sent = false;
this.queue.unshift(req);
this.resetSocket();
}
}
resetSocket() {
var socket = this.socket;
this.socket = null;
if (socket != null) {
socket.onclose = null;
try {
socket.close();
}
catch (err) { /* ignore */ }
}
this.scheduleReconnect();
}
handleMessage(raw) {
var response;
try {
response = JSON.parse(raw);
}
catch (err) {
return;
}
var req = this.inFlight;
if (req == null) {
return;
}
// Servers that echo the id let us drop stray late answers.
if (response.hasOwnProperty("id") && response["id"] != req.id) {
return;
}
window.clearTimeout(req.timer);
this.inFlight = null;
req.resolve(response);
this.pump();
}
}
var WS = new WSClient();
class Server {
constructor(cmd) {
this.cmd = cmd;
}
request(data) {
var isLogUpdate = this.cmd == "updateLog";
// Log polling must never pile up behind a stalled connection.
if (isLogUpdate && WS.hasQueued("updateLog")) {
return;
}
if (!isLogUpdate) {
showElement("loading", true);
UNDO = new Object();
setConnectionState("busy");
}
data["cmd"] = this.cmd;
var requestCmd = this.cmd;
WS.send(data).then(function (response) {
WS_FAILURE_COUNT = 0;
if (!isLogUpdate) {
showElement("loading", false);
}
setConnectionState("online");
if (response["status"] == false) {
setConnectionState("offline");
alert(response["err"]);
if (response.hasOwnProperty("reload")) {
location.reload();
}
return;
}
if (response.hasOwnProperty("logoURL")) {
var div = document.getElementById("channel-icon");
div.value = response["logoURL"];
div.className = "changed";
return;
}
switch (requestCmd) {
case "updateLog":
SERVER["log"] = response["log"];
if (document.getElementById("content_log")) {
showLogs(false);
}
return;
default:
SERVER = response;
break;
}
if (response.hasOwnProperty("openMenu")) {
var menu = document.getElementById(response["openMenu"]);
menu.click();
showElement("popup", false);
}
if (response.hasOwnProperty("openLink")) {
window.location = response["openLink"];
}
if (response.hasOwnProperty("alert")) {
alert(response["alert"]);
}
if (response.hasOwnProperty("reload")) {
location.reload();
}
if (response.hasOwnProperty("wizard")) {
createLayout();
configurationWizard[response["wizard"]].createWizard();
return;
}
createLayout();
}, function (err) {
WS_FAILURE_COUNT++;
if (!isLogUpdate) {
showElement("loading", false);
}
var state = "offline";
if (isLogUpdate && WS_FAILURE_COUNT < 2) {
state = "idle";
}
setConnectionState(state);
if (WS_AVAILABLE == false && !isLogUpdate && requestCmd != "getServerConfig") {
alert("No websocket connection to xTeVe could be established. Check your network configuration.");
}
});
}
}
var WS_FAILURE_COUNT = 0;
class MainMenu {
constructor() {
this.DocumentID = "main-menu";
this.HTMLTag = "LI";
this.ImagePath = "img/";
}
createIMG(src) {
var element = document.createElement("IMG");
element.setAttribute("src", this.ImagePath + src);
return element;
}
createValue(value) {
var element = document.createElement("P");
element.textContent = value;
return element;
}
}
class MainMenuItem extends MainMenu {
constructor(menuKey, value, image, headline) {
super();
this.menuKey = menuKey;
this.value = value;
this.imgSrc = image;
this.headline = headline;
}
createItem() {
var item = document.createElement("LI");
item.setAttribute("onclick", "javascript: openThisMenu(this)");
item.setAttribute("id", this.id);
item.setAttribute("data-menu", this.menuKey);
var img = this.createIMG(this.imgSrc);
var value = this.createValue(this.value);
item.appendChild(img);
item.appendChild(value);
var doc = document.getElementById(this.DocumentID);
doc.appendChild(item);
switch (this.menuKey) {
case "playlist":
this.tableHeader = ["Playlist", "Tuner", "Last Update", "Availability %", "Type", "Streams", "group-title %", "tvg-id %", "Unique ID %"];
break;
case "xmltv":
this.tableHeader = ["Guide", "Last Update", "Availability %", "Channels", "Programs"];
break;
case "filter":
this.tableHeader = ["Filter Name", "Filter Type", "Filter"];
break;
case "users":
this.tableHeader = ["Username", "Password", "WEB", "PMS", "M3U", "XML", "API"];
break;
case "mapping":
this.tableHeader = ["BULK", "Ch. No.", "Logo", "Channel Name", "Playlist", "Group Title", "XMLTV File", "XMLTV ID", "Edit"];
break;
}
//console.log(this.menuKey, this.tableHeader);
}
}
class Content {
constructor() {
this.DocumentID = "content";
this.TableID = "content_table";
this.headerClass = "content_table_header";
this.interactionID = "content-interaction";
}
createHeadline(value) {
var element = document.createElement("H3");
element.textContent = value;
return element;
}
createHR() {
var element = document.createElement("HR");
return element;
}
createInteraction() {
var element = document.createElement("DIV");
element.setAttribute("id", this.interactionID);
return element;
}
createDIV() {
var element = document.createElement("DIV");
element.id = this.DivID;
return element;
}
createTABLE() {
var element = document.createElement("TABLE");
element.id = this.TableID;
return element;
}
createTableRow() {
var element = document.createElement("TR");
element.className = this.headerClass;
return element;
}
createTableContent(menuKey) {
var data = new Object();
var rows = new Array();
switch (menuKey) {
case "playlist":
var fileTypes = new Array("m3u", "hdhr");
fileTypes.forEach(fileType => {
data = SERVER["settings"]["files"][fileType];
var keys = getObjKeys(data);
keys.forEach(key => {
var tr = document.createElement("TR");
tr.id = key;
tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)');
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["name"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (SERVER["settings"]["buffer"] != "-") {
cell.value = data[key]["tuner"];
}
else {
cell.value = "-";
}
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["last.update"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["provider.availability"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["type"].toUpperCase();
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["compatibility"]["streams"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["compatibility"]["group.title"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["compatibility"]["tvg.id"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["compatibility"]["stream.id"];
tr.appendChild(cell.createCell());
rows.push(tr);
});
});
break;
case "filter":
delete SERVER["settings"]["filter"][-1];
data = SERVER["settings"]["filter"];
var keys = getObjKeys(data);
keys.forEach(key => {
var tr = document.createElement("TR");
tr.id = key;
tr.setAttribute('onclick', 'javascript: openPopUp("' + data[key]["type"] + '", this)');
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["name"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
switch (data[key]["type"]) {
case "custom-filter":
cell.value = "Custom";
break;
case "group-title":
cell.value = "Group";
break;
default:
break;
}
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["filter"];
tr.appendChild(cell.createCell());
rows.push(tr);
});
break;
case "xmltv":
var fileTypes = new Array("xmltv");
fileTypes.forEach(fileType => {
data = SERVER["settings"]["files"][fileType];
var keys = getObjKeys(data);
keys.forEach(key => {
var tr = document.createElement("TR");
tr.id = key;
tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)');
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["name"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["last.update"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["provider.availability"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["compatibility"]["xmltv.channels"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["compatibility"]["xmltv.programs"];
tr.appendChild(cell.createCell());
rows.push(tr);
});
});
break;
case "users":
var fileTypes = new Array("users");
fileTypes.forEach(fileType => {
data = SERVER[fileType];
var keys = getObjKeys(data);
keys.forEach(key => {
var tr = document.createElement("TR");
tr.id = key;
tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)');
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["data"]["username"];
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = "******";
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (data[key]["data"]["authentication.web"] == true) {
cell.value = "✓";
}
else {
cell.value = "-";
}
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (data[key]["data"]["authentication.pms"] == true) {
cell.value = "✓";
}
else {
cell.value = "-";
}
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (data[key]["data"]["authentication.m3u"] == true) {
cell.value = "✓";
}
else {
cell.value = "-";
}
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (data[key]["data"]["authentication.xml"] == true) {
cell.value = "✓";
}
else {
cell.value = "-";
}
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (data[key]["data"]["authentication.api"] == true) {
cell.value = "✓";
}
else {
cell.value = "-";
}
tr.appendChild(cell.createCell());
rows.push(tr);
});
});
break;
case "mapping":
BULK_EDIT = false;
createSearchObj();
checkUndo("epgMapping");
console.log("MAPPING");
data = SERVER["xepg"]["epgMapping"];
var keys = getObjKeys(data);
keys.forEach(key => {
var tr = document.createElement("TR");
tr.id = key;
//tr.setAttribute('oncontextmenu', 'javascript: rightClick(this)')
switch (data[key]["x-active"]) {
case true:
tr.className = "activeEPG";
break;
case false:
tr.className = "notActiveEPG";
break;
}
// Bulk
var cell = new Cell();
cell.child = true;
cell.childType = "BULK";
cell.value = false;
tr.appendChild(cell.createCell());
// Kanalnummer
var cell = new Cell();
cell.child = true;
cell.childType = "INPUTCHANNEL";
cell.value = data[key]["x-channelID"];
//td.setAttribute('onclick', 'javascript: changeChannelNumber("' + key + '", this)')
tr.appendChild(cell.createCell());
// Logo
var cell = new Cell();
cell.child = true;
cell.childType = "IMG";
cell.imageURL = data[key]["tvg-logo"];
tr.appendChild(cell.createCell());
// Kanalname
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.className = data[key]["x-category"];
cell.value = data[key]["x-name"];
tr.appendChild(cell.createCell());
// Playlist
var cell = new Cell();
cell.child = true;
cell.childType = "P";
//cell.value = data[key]["_file.m3u.name"]
cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name");
tr.appendChild(cell.createCell());
// Gruppe (group-title)
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = data[key]["x-group-title"];
tr.appendChild(cell.createCell());
// XMLTV Datei
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (data[key]["x-xmltv-file"] != "-") {
cell.value = getValueFromProviderFile(data[key]["x-xmltv-file"], "xmltv", "name");
}
else {
cell.value = data[key]["x-xmltv-file"];
}
tr.appendChild(cell.createCell());
// XMLTV Kanal
var cell = new Cell();
cell.child = true;
cell.childType = "P";
//var value = str.substring(1, 4);
var value = data[key]["x-mapping"];
if (value.length > 20) {
value = data[key]["x-mapping"].substring(0, 20) + "...";
}
cell.value = value;
tr.appendChild(cell.createCell());
var cell = new Cell();
cell.child = true;
cell.childType = "EDIT";
cell.value = "Edit";
var editTd = cell.createCell();
var editButton = editTd.firstChild;
editButton.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
editButton.setAttribute("id", key);
editButton.setAttribute("aria-label", "Edit " + data[key]["x-name"]);
tr.appendChild(editTd);
rows.push(tr);
});
break;
case "settings":
alert();
break;
default:
console.log("Table content (menuKey):", menuKey);
break;
}
return rows;
}
}
class Cell {
createCell() {
var td = document.createElement("TD");
if (this.child == true) {
var element;
switch (this.childType) {
case "P":
element = document.createElement(this.childType);
element.textContent = this.value;
element.className = this.className;
break;
case "INPUT":
element = document.createElement(this.childType);
element.value = this.value;
element.type = "text";
break;
case "INPUTCHANNEL":
element = document.createElement("INPUT");
element.setAttribute("onchange", "javscript: changeChannelNumber(this)");
element.value = this.value;
element.type = "text";
break;
case "BULK":
element = document.createElement("INPUT");
element.checked = this.value;
element.type = "checkbox";
element.className = "bulk hideBulk";
element.addEventListener("click", function (event) {
scheduleChannelRangeSelection(element, event);
});
break;
case "BULK_HEAD":
element = document.createElement("INPUT");
element.checked = this.value;
element.type = "checkbox";
element.className = "bulk hideBulk";
element.setAttribute("onclick", "javascript: selectAllChannels()");
break;
case "IMG":
element = document.createElement(this.childType);
element.setAttribute("src", this.imageURL);
if (this.imageURL != "") {
element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''");
//onerror="this.onerror=null;this.src='missing.gif';"
}
break;
case "EDIT":
element = document.createElement("INPUT");
element.type = "button";
element.value = this.value;
element.className = "mapping-edit-button";
break;
}
td.appendChild(element);
}
else {
td.textContent = this.value;
}
if (this.onclick == true) {
td.setAttribute("onclick", this.onclickFunktion);
td.className = "pointer";
}
if (this.tdClassName != undefined) {
td.className = this.tdClassName;
}
return td;
}
}
class ShowContent extends Content {
constructor(menuID) {
super();
this.menuID = menuID;
}
createInput(type, name, value) {
var input = document.createElement("INPUT");
input.setAttribute("type", type);
input.setAttribute("name", name);
input.setAttribute("value", value);
return input;
}
show() {
COLUMN_TO_SORT = -1;
// Alten Inhalt löschen
var doc = document.getElementById(this.DocumentID);
doc.innerHTML = "";
showPreview(false);
// Überschrift
var headline = menuItems[this.menuID].headline;
var menuKey = menuItems[this.menuID].menuKey;
var h = this.createHeadline(headline);
doc.appendChild(h);
var hr = this.createHR();
doc.appendChild(hr);
// Interaktion
var div = this.createInteraction();
doc.appendChild(div);
var interaction = document.getElementById(this.interactionID);
switch (menuKey) {
case "playlist":
var input = this.createInput("button", menuKey, "New");
input.setAttribute("id", "-");
input.setAttribute("onclick", 'javascript: openPopUp("playlist")');
interaction.appendChild(input);
break;
case "filter":
var input = this.createInput("button", menuKey, "New");
input.setAttribute("id", -1);
input.setAttribute("onclick", 'javascript: openPopUp("filter", this)');
interaction.appendChild(input);
break;
case "xmltv":
var input = this.createInput("button", menuKey, "New");
input.setAttribute("id", "xmltv");
input.setAttribute("onclick", 'javascript: openPopUp("xmltv")');
interaction.appendChild(input);
break;
case "users":
var input = this.createInput("button", menuKey, "New");
input.setAttribute("id", "users");
input.setAttribute("onclick", 'javascript: openPopUp("users")');
interaction.appendChild(input);
break;
case "mapping":
showElement("loading", true);
var input = this.createInput("button", menuKey, "Save");
input.setAttribute("onclick", 'javascript: savePopupData("mapping", "", "")');
interaction.appendChild(input);
var input = this.createInput("button", menuKey, "Bulk Edit");
input.setAttribute("onclick", 'javascript: bulkEdit()');
interaction.appendChild(input);
var input = this.createInput("search", "search", "");
input.setAttribute("id", "searchMapping");
input.setAttribute("placeholder", "Search");
input.className = "search";
input.setAttribute("oninput", 'javascript: searchInMapping()');
interaction.appendChild(input);
break;
case "settings":
var input = this.createInput("button", menuKey, "Save");
input.setAttribute("onclick", 'javascript: saveSettings();');
interaction.appendChild(input);
var input = this.createInput("button", menuKey, "Backup");
input.setAttribute("onclick", 'javascript: backup();');
interaction.appendChild(input);
var input = this.createInput("button", menuKey, "Restore");
input.setAttribute("onclick", 'javascript: restore();');
interaction.appendChild(input);
var wrapper = document.createElement("DIV");
wrapper.setAttribute("id", "box-wrapper");
doc.appendChild(wrapper);
this.DivID = "content_settings";
var settings = this.createDIV();
wrapper.appendChild(settings);
showSettings();
return;
break;
case "log":
var input = this.createInput("button", menuKey, "Reset Logs");
input.setAttribute("onclick", 'javascript: resetLogs();');
interaction.appendChild(input);
var wrapper = document.createElement("DIV");
wrapper.setAttribute("id", "box-wrapper");
doc.appendChild(wrapper);
this.DivID = "content_log";
var logs = this.createDIV();
wrapper.appendChild(logs);
showLogs(true);
return;
break;
case "logout":
location.reload();
document.cookie = "Token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
break;
default:
console.log("Show content (menuKey):", menuKey);
break;
}
// Tabelle erstellen (falls benötigt)
var tableHeader = menuItems[this.menuID].tableHeader;
if (tableHeader.length > 0) {
var wrapper = document.createElement("DIV");
doc.appendChild(wrapper);
wrapper.setAttribute("id", "box-wrapper");
var table = this.createTABLE();
wrapper.appendChild(table);
var header = this.createTableRow();
table.appendChild(header);
// Kopfzeile der Tablle
tableHeader.forEach(element => {
var cell = new Cell();
cell.child = true;
cell.childType = "P";
cell.value = element;
if (element == "BULK") {
cell.childType = "BULK_HEAD";
cell.value = false;
}
if (menuKey == "mapping") {
if (element == "Ch. No.") {
cell.onclick = true;
cell.onclickFunktion = "javascript: sortTable(1);";
cell.tdClassName = "sortThis";
}
if (element == "Channel Name") {
cell.onclick = true;
cell.onclickFunktion = "javascript: sortTable(3);";
}
if (element == "Playlist") {
cell.onclick = true;
cell.onclickFunktion = "javascript: sortTable(4);";
}
if (element == "Group Title") {
cell.onclick = true;
cell.onclickFunktion = "javascript: sortTable(5);";
}
}
header.appendChild(cell.createCell());
});
table.appendChild(header);
// Inhalt der Tabelle
var rows = this.createTableContent(menuKey);
rows.forEach(tr => {
table.appendChild(tr);
});
}
switch (menuKey) {
case "mapping":
sortTable(1);
break;
case "filter":
showPreview(true);
sortTable(0);
break;
default:
COLUMN_TO_SORT = -1;
sortTable(0);
break;
}
showElement("loading", false);
}
}
var SHELL_LAYOUT_READY = false;
function setLayoutMenuState(open) {
if (document.body == null) {
return;
}
if (open == true) {
document.body.classList.add("menu-open");
}
else {
document.body.classList.remove("menu-open");
}
}
function toggleLayoutMenu() {
if (document.body == null) {
return;
}
var isOpen = document.body.classList.contains("menu-open");
setLayoutMenuState(!isOpen);
}
function closeLayoutMenuIfMobile() {
if (window.innerWidth <= 900) {
setLayoutMenuState(false);
}
}
function setActiveMenu(menuID) {
ACTIVE_MENU_ID = menuID.toString();
var menu = document.getElementById("main-menu");
if (menu == null) {
return;
}
var items = menu.getElementsByTagName("LI");
for (var i = 0; i < items.length; i++) {
items[i].classList.remove("menu-active");
}
var activeItem = document.getElementById(ACTIVE_MENU_ID);
var activeMenuKey = "";
if (activeItem != null) {
activeItem.classList.add("menu-active");
var menuKeyValue = activeItem.getAttribute("data-menu");
if (menuKeyValue != null) {
activeMenuKey = menuKeyValue;
}
}
if (document.body != null) {
if (activeMenuKey.length > 0) {
document.body.setAttribute("data-active-menu", activeMenuKey);
}
else {
document.body.removeAttribute("data-active-menu");
}
if (activeMenuKey == "log") {
document.body.classList.add("menu-log-focus");
}
else {
document.body.classList.remove("menu-log-focus");
}
}
}
function renderStatusCards() {
var wrapper = document.getElementById("status-cards");
if (wrapper == null || SERVER.hasOwnProperty("clientInfo") == false) {
return;
}
var info = SERVER["clientInfo"];
var errors = parseInt(info["errors"], 10);
var warnings = parseInt(info["warnings"], 10);
var cards = [
{ label: "Streams", value: info["streams"], tone: "ok" },
{ label: "EPG Source", value: info["epgSource"], tone: "neutral" },
{ label: "XEPG Channels", value: info["xepg"], tone: "ok" },
{ label: "Errors", value: info["errors"], tone: errors > 0 ? "error" : "ok" },
{ label: "Warnings", value: info["warnings"], tone: warnings > 0 ? "warn" : "ok" },
{ label: "DVR", value: info["DVR"], tone: "neutral" },
];
wrapper.innerHTML = "";
cards.forEach(card => {
var box = document.createElement("DIV");
box.className = "status-card status-card-" + card.tone;
var label = document.createElement("P");
label.className = "status-card-label";
label.innerText = card.label;
var value = document.createElement("P");
value.className = "status-card-value";
if (card.value == undefined || card.value == "") {
value.innerText = "-";
}
else {
value.innerText = card.value;
}
box.appendChild(label);
box.appendChild(value);
wrapper.appendChild(box);
});
}
function initShellLayout() {
if (SHELL_LAYOUT_READY == true) {
return;
}
var toggle = document.getElementById("menu-toggle");
if (toggle != null) {
toggle.onclick = function () {
toggleLayoutMenu();
};
}
var overlay = document.getElementById("layout-overlay");
if (overlay != null) {
overlay.onclick = function () {
setLayoutMenuState(false);
};
}
document.addEventListener("keydown", function (event) {
if (event.key == "Escape") {
setLayoutMenuState(false);
showElement("popup", false);
return;
}
if (event.key == "/") {
var target = event.target;
var onInput = target.tagName == "INPUT" || target.tagName == "TEXTAREA" || target.tagName == "SELECT";
if (onInput == true) {
return;
}
var search = document.getElementById("searchMapping");
if (search != null) {
event.preventDefault();
search.focus();
}
}
});
setConnectionState("idle");
SHELL_LAYOUT_READY = true;
}
function shouldPollLogs() {
if (document.hidden == true) {
return false;
}
if (document.getElementById("content_log") == null) {
return false;
}
if (ACTIVE_MENU_ID.length < 1) {
return false;
}
var activeItem = document.getElementById(ACTIVE_MENU_ID);
if (activeItem == null) {
return false;
}
return activeItem.getAttribute("data-menu") == "log";
}
function PageReady() {
initShellLayout();
var server = new Server("getServerConfig");
server.request(new Object());
var bootstrapAttempts = 0;
var maxBootstrapAttempts = 5;
var bootstrapTimer = window.setInterval(function () {
if (SERVER.hasOwnProperty("clientInfo") == true) {
window.clearInterval(bootstrapTimer);
return;
}
if (WS.hasQueued("getServerConfig")) {
return;
}
bootstrapAttempts++;
var retryServer = new Server("getServerConfig");
retryServer.request(new Object());
if (bootstrapAttempts >= maxBootstrapAttempts) {
window.clearInterval(bootstrapTimer);
}
}, 3000);
window.addEventListener("resize", function () {
if (window.innerWidth > 900) {
setLayoutMenuState(false);
}
calculateWrapperHeight();
}, true);
setInterval(function () {
if (shouldPollLogs() == false) {
return;
}
updateLog();
}, 10000);
return;
}
function isClientInfoHttpURL(value) {
return /^https?:\/\//i.test(value);
}
function setClientInfoValue(key, value) {
var element = document.getElementById(key);
if (element == null) {
return;
}
var textValue = "";
if (value != undefined && value != null) {
textValue = String(value);
}
if ((key == "m3u-url" || key == "xepg-url") && isClientInfoHttpURL(textValue)) {
element.innerHTML = "";
var anchor = document.createElement("a");
anchor.href = textValue;
anchor.target = "_blank";
anchor.rel = "noopener noreferrer";
anchor.textContent = textValue;
element.appendChild(anchor);
return;
}
element.textContent = textValue;
}
function createLayout() {
// Client Info
var obj = SERVER["clientInfo"];
var keys = getObjKeys(obj);
for (var i = 0; i < keys.length; i++) {
setClientInfoValue(keys[i], obj[keys[i]]);
}
renderStatusCards();
if (!document.getElementById("main-menu")) {
return;
}
// Menü erstellen
document.getElementById("main-menu").innerHTML = "";
for (let i = 0; i < menuItems.length; i++) {
menuItems[i].id = i;
switch (menuItems[i]["menuKey"]) {
case "users":
case "logout":
if (SERVER["settings"]["authentication.web"] == true) {
menuItems[i].createItem();
}
break;
case "mapping":
case "xmltv":
if (SERVER["clientInfo"]["epgSource"] == "XEPG") {
menuItems[i].createItem();
}
break;
default:
menuItems[i].createItem();
break;
}
}
if (ACTIVE_MENU_ID.length > 0 && document.getElementById(ACTIVE_MENU_ID)) {
setActiveMenu(ACTIVE_MENU_ID);
}
var content = document.getElementById("content");
var menu = document.getElementById("main-menu");
if (ACTIVE_MENU_ID.length == 0 && content != null && menu != null) {
if (content.innerHTML.replace(/\\s/g, "").length == 0) {
var firstItem = menu.getElementsByTagName("LI")[0];
if (firstItem != undefined) {
firstItem.click();
}
}
}
return;
}
function openThisMenu(element) {
var id = element.id;
var content = new ShowContent(id);
setActiveMenu(id);
content.show();
closeLayoutMenuIfMobile();
calculateWrapperHeight();
return;
}
class PopupWindow {
constructor() {
this.DocumentID = "popup-custom";
this.InteractionID = "interaction";
this.doc = document.getElementById(this.DocumentID);
}
createTitle(title) {
var td = document.createElement("TD");
td.className = "left";
td.innerHTML = title + ":";
return td;
}
createContent(element) {
var td = document.createElement("TD");
td.appendChild(element);
return td;
}
createInteraction() {
var div = document.createElement("div");
div.setAttribute("id", "popup-interaction");
div.className = "interaction";
this.doc.appendChild(div);
}
}
class PopupContent extends PopupWindow {
constructor() {
super(...arguments);
this.table = document.createElement("TABLE");
}
createHeadline(headline) {
this.doc.innerHTML = "";
var element = document.createElement("H3");
element.innerHTML = headline.toUpperCase();
this.doc.appendChild(element);
// Tabelle erstellen
this.table = document.createElement("TABLE");
this.doc.appendChild(this.table);
}
appendRow(title, element) {
var tr = document.createElement("TR");
// Bezeichnung
if (title.length != 0) {
tr.appendChild(this.createTitle(title));
}
// Content
tr.appendChild(this.createContent(element));
this.table.appendChild(tr);
}
createInput(type, name, value) {
var input = document.createElement("INPUT");
if (value == undefined) {
value = "";
}
input.setAttribute("type", type);
input.setAttribute("name", name);
input.setAttribute("value", value);
return input;
}
createCheckbox(name) {
var input = document.createElement("INPUT");
input.setAttribute("type", "checkbox");
input.setAttribute("name", name);
return input;
}
createSelect(text, values, set, dbKey) {
var select = document.createElement("SELECT");
select.setAttribute("name", dbKey);
for (let i = 0; i < text.length; i++) {
var option = document.createElement("OPTION");
option.setAttribute("value", values[i]);
option.innerText = text[i];
select.appendChild(option);
}
if (set != "") {
select.value = set;
}
if (set == undefined) {
select.value = values[0];
}
return select;
}
selectOption(select, value) {
//select.selectedOptions = value
var s = select;
s.options[s.selectedIndex].value = value;
return select;
}
// isHTML must only be true for static text written in the source; never for data.
description(value, isHTML = false) {
var tr = document.createElement("TR");
var td = document.createElement("TD");
var span = document.createElement("PRE");
if (isHTML) {
span.innerHTML = value;
}
else {
span.textContent = value;
}
tr.appendChild(td);
tr.appendChild(this.createContent(span));
this.table.appendChild(tr);
}
// Interaktion
addInteraction(element) {
var interaction = document.getElementById("popup-interaction");
interaction.appendChild(element);
}
}
function openPopUp(dataType, element) {
var data = new Object();
var id;
switch (element) {
case undefined:
switch (dataType) {
case "group-title":
if (id == undefined) {
id = -1;
}
data = getLocalData("filter", id);
data["type"] = "group-title";
break;
case "custom-filter":
if (id == undefined) {
id = -1;
}
data = getLocalData("filter", id);
data["type"] = "custom-filter";
break;
default:
data["id.provider"] = "-";
data["type"] = dataType;
id = "-";
break;
}
break;
default:
id = element.id;
data = getLocalData(dataType, id);
break;
}
var content = new PopupContent();
switch (dataType) {
case "playlist":
content.createHeadline("Playlist type");
// Type
var text = ["M3U", "HDHomeRun"];
var values = ["javascript: openPopUp('m3u')", "javascript: openPopUp('hdhr')"];
var select = content.createSelect(text, values, "", "type");
select.setAttribute("id", "type");
select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick")'); // changeButtonAction
content.appendRow("Type", select);
// Interaktion
content.createInteraction();
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Weiter
var input = content.createInput("button", "next", "Next");
input.setAttribute("onclick", 'javascript: openPopUp("m3u")');
input.setAttribute("id", 'next');
content.addInteraction(input);
break;
case "m3u":
content.createHeadline(dataType);
// Name
var dbKey = "name";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Playlist name");
content.appendRow("Name", input);
// Beschreibung
var dbKey = "description";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Description");
content.appendRow("Description", input);
// URL
var dbKey = "file.source";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "File path or URL of the M3U");
content.appendRow("M3U File", input);
// Tuner
if (SERVER["settings"]["buffer"] != "-") {
var text = new Array();
var values = new Array();
for (var i = 1; i <= 100; i++) {
text.push(i.toString());
values.push(i.toString());
}
var dbKey = "tuner";
var select = content.createSelect(text, values, data[dbKey], dbKey);
select.setAttribute("onfocus", "javascript: return;");
content.appendRow("Tuner / Streams", select);
}
else {
var dbKey = "tuner";
if (data[dbKey] == undefined) {
data[dbKey] = 1;
}
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("readonly", "true");
input.className = "notAvailable";
content.appendRow("Tuner / Streams", input);
}
content.description("Number of parallel connections that can be established to the provider. <br>Only available with activated buffer.<br>New settings will only be applied after quitting all streams.", true);
// Interaktion
content.createInteraction();
// Löschen
if (data["id.provider"] != "-") {
var input = content.createInput("button", "delete", "Delete");
input.className = "delete";
input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", true, 0)');
content.addInteraction(input);
}
else {
var input = content.createInput("button", "back", "Back");
input.setAttribute("onclick", 'javascript: openPopUp("playlist")');
content.addInteraction(input);
}
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Aktualisieren
if (data["id.provider"] != "-") {
var input = content.createInput("button", "update", "Update");
input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 1)');
content.addInteraction(input);
}
// Speichern
var input = content.createInput("button", "save", "Save");
input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 0)');
content.addInteraction(input);
break;
case "hdhr":
content.createHeadline(dataType);
// Name
var dbKey = "name";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Playlist name");
content.appendRow("Name", input);
// Beschreibung
var dbKey = "description";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Description");
content.appendRow("Description", input);
// URL
var dbKey = "file.source";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "IP address and port (192.168.1.10:5004)");
content.appendRow("HDHomeRun IP", input);
// Tuner
if (SERVER["settings"]["buffer"] != "-") {
var text = new Array();
var values = new Array();
for (var i = 1; i <= 100; i++) {
text.push(i.toString());
values.push(i.toString());
}
var dbKey = "tuner";
var select = content.createSelect(text, values, data[dbKey], dbKey);
select.setAttribute("onfocus", "javascript: return;");
content.appendRow("Tuner / Streams", select);
}
else {
var dbKey = "tuner";
if (data[dbKey] == undefined) {
data[dbKey] = 1;
}
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("readonly", "true");
input.className = "notAvailable";
content.appendRow("Tuner / Streams", input);
}
content.description("Number of parallel connections that can be established to the provider. <br>Only available with activated buffer.<br>New settings will only be applied after quitting all streams.", true);
// Interaktion
content.createInteraction();
// Löschen
if (data["id.provider"] != "-") {
var input = content.createInput("button", "delete", "Delete");
input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", true, 0)');
input.className = "delete";
content.addInteraction(input);
}
else {
var input = content.createInput("button", "back", "Back");
input.setAttribute("onclick", 'javascript: openPopUp("playlist")');
content.addInteraction(input);
}
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Aktualisieren
if (data["id.provider"] != "-") {
var input = content.createInput("button", "update", "Update");
input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 1)');
content.addInteraction(input);
}
// Speichern
var input = content.createInput("button", "save", "Save");
input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 0)');
content.addInteraction(input);
break;
case "filter":
content.createHeadline(dataType);
// Type
var dbKey = "type";
var text = ["M3U: " + "Group Title", "xTeVe: " + "Custom Filter"];
var values = ["javascript: openPopUp('group-title')", "javascript: openPopUp('custom-filter')"];
var select = content.createSelect(text, values, "javascript: openPopUp('group-title')", dbKey);
select.setAttribute("id", id);
select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick");'); // changeButtonAction
content.appendRow("Type", select);
// Interaktion
content.createInteraction();
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Weiter
var input = content.createInput("button", "next", "Next");
input.setAttribute("onclick", 'javascript: openPopUp("group-title")');
input.setAttribute("id", 'next');
content.addInteraction(input);
break;
case "custom-filter":
case "group-title":
switch (dataType) {
case "custom-filter":
content.createHeadline("Custom");
break;
case "group-title":
content.createHeadline("Group");
break;
}
// Name
var dbKey = "name";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Filter name");
content.appendRow("Filter Name", input);
// Beschreibung
var dbKey = "description";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Description");
content.appendRow("Description", input);
// Typ
var dbKey = "type";
var input = content.createInput("hidden", dbKey, data[dbKey]);
content.appendRow("", input);
var filterType = data[dbKey];
switch (filterType) {
case "custom-filter":
// Groß- Kleinschreibung beachten
var dbKey = "caseSensitive";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
content.appendRow("Case Sensitive", input);
// Filterregel (Benutzerdefiniert)
var dbKey = "filter";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Sport {HD} !{ES,IT}");
content.appendRow("Filter Rule", input);
break;
case "group-title":
//alert(dbKey + " " + filterType)
// Filter basierend auf den Gruppen in der M3U
var dbKey = "filter";
var groupsM3U = getLocalData("m3uGroups", "");
var text = groupsM3U["text"];
var values = groupsM3U["value"];
var select = content.createSelect(text, values, data[dbKey], dbKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
content.appendRow("Group Title", select);
content.description("Select a M3U group. (Counter)<br>Changing the group title in the M3U invalidates the filter.", true);
// Groß- Kleinschreibung beachten
var dbKey = "caseSensitive";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
content.appendRow("Case Sensitive", input);
var dbKey = "include";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "FHD,UHD");
content.appendRow("Include", input);
content.description("Channel name must include.<br>(Comma separated) Comma means or", true);
var dbKey = "exclude";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "ES,IT");
content.appendRow("Exclude", input);
content.description("Channel name must not contain.<br>(Comma separated) Comma means or", true);
break;
default:
break;
}
// Interaktion
content.createInteraction();
// Löschen
var input = content.createInput("button", "delete", "Delete");
input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", true, 0)');
input.className = "delete";
content.addInteraction(input);
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Speichern
var input = content.createInput("button", "save", "Save");
input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", false, 0)');
content.addInteraction(input);
break;
case "xmltv":
content.createHeadline(dataType);
// Name
var dbKey = "name";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Guide name");
content.appendRow("Name", input);
// Beschreibung
var dbKey = "description";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Description");
content.appendRow("Description", input);
// URL
var dbKey = "file.source";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "File path or URL of the XMLTV");
content.appendRow("XMLTV File", input);
// Interaktion
content.createInteraction();
// Löschen
if (data["id.provider"] != "-") {
var input = content.createInput("button", "delete", "Delete");
input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", true, 0)');
input.className = "delete";
content.addInteraction(input);
}
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Aktualisieren
if (data["id.provider"] != "-") {
var input = content.createInput("button", "update", "Update");
input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 1)');
content.addInteraction(input);
}
// Speichern
var input = content.createInput("button", "save", "Save");
input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 0)');
content.addInteraction(input);
break;
case "users":
content.createHeadline("Users");
// Benutzername
var dbKey = "username";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Username");
content.appendRow("Username", input);
// Neues Passwort
var dbKey = "password";
var input = content.createInput("password", dbKey, "");
input.setAttribute("placeholder", "Password");
content.appendRow("Password", input);
// Bestätigung
var dbKey = "confirm";
var input = content.createInput("password", dbKey, "");
input.setAttribute("placeholder", "Password confirm");
content.appendRow("Confirm", input);
// Berechtigung WEB
var dbKey = "authentication.web";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
if (data["defaultUser"] == true) {
input.setAttribute("onclick", "javascript: return false");
}
content.appendRow("Web Access", input);
// Berechtigung PMS
var dbKey = "authentication.pms";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
content.appendRow("PMS Access", input);
// Berechtigung M3U
var dbKey = "authentication.m3u";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
content.appendRow("M3U Access", input);
// Berechtigung XML
var dbKey = "authentication.xml";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
content.appendRow("XML Access", input);
// Berechtigung API
var dbKey = "authentication.api";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
content.appendRow("API Access", input);
// Interaktion
content.createInteraction();
// Löschen
if (data["defaultUser"] != true && id != "-") {
var input = content.createInput("button", "delete", "Delete");
input.className = "delete";
input.setAttribute('onclick', 'javascript: savePopupData("' + dataType + '", "' + id + '", true, 0)');
content.addInteraction(input);
}
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Speichern
var input = content.createInput("button", "save", "Save");
input.setAttribute("onclick", 'javascript: savePopupData("' + dataType + '", "' + id + '", "false");');
content.addInteraction(input);
break;
case "mapping":
content.createHeadline("Mapping");
// Aktiv
var dbKey = "x-active";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
input.id = "active";
//input.setAttribute("onchange", "javascript: this.className = 'changed'")
input.setAttribute("onchange", "javascript: toggleChannelStatus('" + id + "', this)");
content.appendRow("Active", input);
// Kanalname
var dbKey = "x-name";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("onchange", "javascript: this.className = 'changed'");
if (BULK_EDIT == true) {
input.style.border = "solid 1px red";
input.setAttribute("readonly", "true");
}
content.appendRow("Channel Name", input);
content.description(data["name"]);
// Beschreibung
var dbKey = "x-description";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "Used by the Dummy as an XML description");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
content.appendRow("Channel Description", input);
// Aktualisierung des Kanalnamens
if (data.hasOwnProperty("_uuid.key")) {
if (data["_uuid.key"] != "") {
var dbKey = "x-update-channel-name";
var input = content.createCheckbox(dbKey);
input.setAttribute("onchange", "javascript: this.className = 'changed'");
input.checked = data[dbKey];
content.appendRow("Update Channel Name", input);
}
}
// Logo URL (Kanal)
var dbKey = "tvg-logo";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("onchange", "javascript: this.className = 'changed'");
input.setAttribute("id", "channel-icon");
content.appendRow("Logo URL", input);
// Aktualisierung des Kanallogos
var dbKey = "x-update-channel-icon";
var input = content.createCheckbox(dbKey);
input.checked = data[dbKey];
input.setAttribute("id", "update-icon");
input.setAttribute("onchange", "javascript: this.className = 'changed'; changeChannelLogo('" + id + "');");
content.appendRow("Update Channel Logo", input);
// Erweitern der EPG Kategorie
var dbKey = "x-category";
var text = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"];
var values = ["", "Kids", "News", "Movie", "Series", "Sports"];
var select = content.createSelect(text, values, data[dbKey], dbKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
content.appendRow("EPG Category", select);
// M3U Gruppentitel
var dbKey = "x-group-title";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("onchange", "javascript: this.className = 'changed'");
content.appendRow("Group Title (xteve.m3u)", input);
if (data["group-title"] != undefined) {
content.description(data["group-title"]);
}
// XMLTV Datei
var dbKey = "x-xmltv-file";
var xmlFile = data[dbKey];
var xmltv = new XMLTVFile();
var select = xmltv.getFiles(data[dbKey]);
select.setAttribute("name", dbKey);
select.setAttribute("id", "popup-xmltv");
select.setAttribute("onchange", "javascript: this.className = 'changed'; setXmltvChannel('" + id + "',this);");
content.appendRow("XMLTV File", select);
var file = data[dbKey];
// XMLTV Mapping
var dbKey = "x-mapping";
var xmltv = new XMLTVFile();
var select = xmltv.getPrograms(file, data[dbKey]);
select.setAttribute("name", dbKey);
select.setAttribute("id", "popup-mapping");
select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');");
sortSelect(select);
content.appendRow("XMLTV Channel", select);
// Interaktion
content.createInteraction();
// Logo hochladen
var input = content.createInput("button", "cancel", "Upload Logo");
input.setAttribute("onclick", 'javascript: uploadLogo();');
content.addInteraction(input);
// Abbrechen
var input = content.createInput("button", "cancel", "Cancel");
input.setAttribute("onclick", 'javascript: showElement("popup", false);');
content.addInteraction(input);
// Fertig
var ids = new Array();
ids = getAllSelectedChannels();
if (ids.length == 0) {
ids.push(id);
}
var input = content.createInput("button", "save", "Done");
input.setAttribute("onclick", 'javascript: donePopupData("' + dataType + '", "' + ids + '", "false");');
content.addInteraction(input);
break;
default:
break;
}
showPopUpElement('popup-custom');
}
class XMLTVFile {
getFiles(set) {
var fileIDs = getObjKeys(SERVER["xepg"]["xmltvMap"]);
var values = new Array("-");
var text = new Array("-");
for (let i = 0; i < fileIDs.length; i++) {
if (fileIDs[i] != "xTeVe Dummy") {
values.push(getValueFromProviderFile(fileIDs[i], "xmltv", "file.xteve"));
text.push(getValueFromProviderFile(fileIDs[i], "xmltv", "name"));
}
else {
values.push(fileIDs[i]);
text.push(fileIDs[i]);
}
}
var select = document.createElement("SELECT");
for (let i = 0; i < text.length; i++) {
var option = document.createElement("OPTION");
option.setAttribute("value", values[i]);
option.innerText = text[i];
select.appendChild(option);
}
if (set != "") {
select.value = set;
}
return select;
}
getPrograms(file, set) {
//var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"])
var values = getObjKeys(SERVER["xepg"]["xmltvMap"][file]);
var text = new Array();
var displayName;
for (let i = 0; i < values.length; i++) {
if (SERVER["xepg"]["xmltvMap"][file][values[i]].hasOwnProperty('display-name') == true) {
displayName = SERVER["xepg"]["xmltvMap"][file][values[i]]["display-name"];
}
else {
displayName = "-";
}
text[i] = displayName + " (" + values[i] + ")";
}
text.unshift("-");
values.unshift("-");
var select = document.createElement("SELECT");
for (let i = 0; i < text.length; i++) {
var option = document.createElement("OPTION");
option.setAttribute("value", values[i]);
option.innerText = text[i];
select.appendChild(option);
}
if (set != "") {
select.value = set;
}
if (select.value != set) {
select.value = "-";
}
return select;
}
}
function getValueFromProviderFile(file, fileType, key) {
if (file == "xTeVe Dummy") {
return file;
}
var fileID;
var indicator = file.charAt(0);
switch (indicator) {
case "M":
fileType = "m3u";
fileID = file;
break;
case "H":
fileType = "hdhr";
fileID = file;
break;
case "X":
fileType = "xmltv";
fileID = file.substring(0, file.lastIndexOf('.'));
break;
}
if (SERVER["settings"]["files"][fileType].hasOwnProperty(fileID) == true) {
var data = SERVER["settings"]["files"][fileType][fileID];
return data[key];
}
return;
}
function setXmltvChannel(id, element) {
var xmltv = new XMLTVFile();
var xmlFile = element.value;
var tvgId = SERVER["xepg"]["epgMapping"][id]["tvg-id"];
var td = document.getElementById("popup-mapping").parentElement;
td.innerHTML = "";
var select = xmltv.getPrograms(element.value, tvgId);
select.setAttribute("name", "x-mapping");
select.setAttribute("id", "popup-mapping");
select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');");
select.className = "changed";
sortSelect(select);
td.appendChild(select);
checkXmltvChannel(id, select, xmlFile);
}
function checkXmltvChannel(id, element, xmlFile) {
var value = element.value;
var bool;
var checkbox = document.getElementById('active');
var channel = SERVER["xepg"]["epgMapping"][id];
var updateLogo;
if (value == "-") {
bool = false;
}
else {
bool = true;
}
checkbox.checked = bool;
checkbox.className = "changed";
console.log(xmlFile);
// Kanallogo aktualisieren
/*
updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked
console.log(updateLogo);
*/
if (xmlFile != "xTeVe Dummy" && bool == true) {
//(document.getElementById("update-icon") as HTMLInputElement).checked = true;
//(document.getElementById("update-icon") as HTMLInputElement).className = "changed";
console.log("ID", id);
changeChannelLogo(id);
return;
}
if (xmlFile == "xTeVe Dummy") {
document.getElementById("update-icon").checked = false;
document.getElementById("update-icon").className = "changed";
}
return;
}
function changeChannelLogo(id) {
var updateLogo;
var channel = SERVER["xepg"]["epgMapping"][id];
var f = document.getElementById("popup-xmltv");
var xmltvFile = f.options[f.selectedIndex].value;
var m = document.getElementById("popup-mapping");
var xMapping = m.options[m.selectedIndex].value;
var xmltvLogo = SERVER["xepg"]["xmltvMap"][xmltvFile][xMapping]["icon"];
updateLogo = document.getElementById("update-icon").checked;
if (updateLogo == true && xmltvFile != "xTeVe Dummy") {
if (SERVER["xepg"]["xmltvMap"][xmltvFile].hasOwnProperty(xMapping)) {
var logo = xmltvLogo;
}
else {
logo = channel["tvg-logo"];
}
var logoInput = document.getElementById("channel-icon");
logoInput.value = logo;
if (BULK_EDIT == false) {
logoInput.className = "changed";
}
}
}
function savePopupData(dataType, id, remove, option) {
if (dataType == "mapping") {
var data = new Object();
console.log("Save mapping data");
cmd = "saveEpgMapping";
data["epgMapping"] = SERVER["xepg"]["epgMapping"];
console.log("SEND TO SERVER");
var server = new Server(cmd);
server.request(data);
delete UNDO["epgMapping"];
return;
}
console.log("Save popup data");
var div = document.getElementById("popup-custom");
var inputs = div.getElementsByTagName("TABLE")[0].getElementsByTagName("INPUT");
var selects = div.getElementsByTagName("TABLE")[0].getElementsByTagName("SELECT");
var input = new Object();
var confirmMsg;
for (let i = 0; i < selects.length; i++) {
var name;
name = selects[i].name;
var value = selects[i].value;
switch (name) {
case "tuner":
input[name] = parseInt(value);
break;
default:
input[name] = value;
break;
}
}
for (let i = 0; i < inputs.length; i++) {
switch (inputs[i].type) {
case "checkbox":
name = inputs[i].name;
input[name] = inputs[i].checked;
break;
case "text":
case "hidden":
case "password":
name = inputs[i].name;
switch (name) {
case "tuner":
input[name] = parseInt(inputs[i].value);
break;
default:
input[name] = inputs[i].value;
break;
}
break;
}
}
var data = new Object();
var cmd;
if (remove == true) {
input["delete"] = true;
}
switch (dataType) {
case "users":
confirmMsg = "Delete this user?";
if (id == "-") {
cmd = "saveNewUser";
data["userData"] = input;
}
else {
cmd = "saveUserData";
var d = new Object();
d[id] = input;
data["userData"] = d;
}
break;
case "m3u":
confirmMsg = "Delete this playlist?";
switch (option) {
// Popup: Save
case 0:
cmd = "saveFilesM3U";
break;
// Popup: Update
case 1:
cmd = "updateFileM3U";
break;
}
data["files"] = new Object;
data["files"][dataType] = new Object;
data["files"][dataType][id] = input;
break;
case "hdhr":
confirmMsg = "Delete this HDHomeRun tuner?";
switch (option) {
// Popup: Save
case 0:
cmd = "saveFilesHDHR";
break;
// Popup: Update
case 1:
cmd = "updateFileHDHR";
break;
}
data["files"] = new Object;
data["files"][dataType] = new Object;
data["files"][dataType][id] = input;
break;
case "xmltv":
confirmMsg = "Delete this XMLTV file?";
switch (option) {
// Popup: Save
case 0:
cmd = "saveFilesXMLTV";
break;
// Popup: Update
case 1:
cmd = "updateFileXMLTV";
break;
}
data["files"] = new Object;
data["files"][dataType] = new Object;
data["files"][dataType][id] = input;
break;
case "filter":
confirmMsg = "Delete this filter?";
cmd = "saveFilter";
data["filter"] = new Object;
data["filter"][id] = input;
break;
default:
console.log(dataType, id);
return;
break;
}
if (remove == true) {
if (!confirm(confirmMsg)) {
showElement("popup", false);
return;
}
}
console.log("SEND TO SERVER");
console.log(data);
var server = new Server(cmd);
server.request(data);
}
function donePopupData(dataType, idsStr) {
var ids = idsStr.split(',');
var div = document.getElementById("popup-custom");
var inputs = div.getElementsByClassName("changed");
ids.forEach(id => {
var input = new Object();
input = SERVER["xepg"]["epgMapping"][id];
console.log(input);
for (let i = 0; i < inputs.length; i++) {
var name;
var value;
switch (inputs[i].tagName) {
case "INPUT":
switch (inputs[i].type) {
case "checkbox":
name = inputs[i].name;
value = inputs[i].checked;
input[name] = value;
break;
case "text":
name = inputs[i].name;
value = inputs[i].value;
input[name] = value;
break;
}
break;
case "SELECT":
name = inputs[i].name;
value = inputs[i].value;
input[name] = value;
break;
}
switch (name) {
case "tvg-logo":
//(document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", value)
break;
case "x-name":
document.getElementById(id).childNodes[3].firstChild.textContent = value;
break;
case "x-category":
document.getElementById(id).childNodes[3].firstChild.className = value;
break;
case "x-group-title":
document.getElementById(id).childNodes[5].firstChild.textContent = value;
break;
case "x-xmltv-file":
if (value != "xTeVe Dummy" && value != "-") {
value = getValueFromProviderFile(value, "xmltv", "name");
}
if (value == "-") {
input["x-active"] = false;
}
document.getElementById(id).childNodes[6].firstChild.textContent = value;
break;
case "x-mapping":
if (value == "-") {
input["x-active"] = false;
}
document.getElementById(id).childNodes[7].firstChild.textContent = value;
break;
default:
}
createSearchObj();
searchInMapping();
}
if (input["x-active"] == false) {
document.getElementById(id).className = "notActiveEPG";
}
else {
document.getElementById(id).className = "activeEPG";
}
console.log(input["tvg-logo"]);
document.getElementById(id).childNodes[2].firstChild.setAttribute("src", input["tvg-logo"]);
});
showElement("popup", false);
return;
}
function showPreview(element) {
var div = document.getElementById("myStreamsBox");
switch (element) {
case false:
div.className = "notVisible";
return;
break;
}
var streams = ["activeStreams", "inactiveStreams"];
streams.forEach(preview => {
var table = document.getElementById(preview);
table.innerHTML = "";
var obj = SERVER["data"]["StreamPreviewUI"][preview];
obj.forEach(channel => {
var tr = document.createElement("TR");
var tdKey = document.createElement("TD");
var tdVal = document.createElement("TD");
tdKey.className = "tdKey";
tdVal.className = "tdVal";
switch (preview) {
case "activeStreams":
tdKey.innerText = "Channel: (+)";
break;
case "inactiveStreams":
tdKey.innerText = "Channel: (-)";
break;
}
tdVal.innerText = channel;
tr.appendChild(tdKey);
tr.appendChild(tdVal);
table.appendChild(tr);
});
});
showElement("loading", false);
div.className = "visible";
return;
}
class SettingsCategory {
constructor() {
this.DocumentID = "content_settings";
}
createCategoryHeadline(value) {
var element = document.createElement("H4");
element.innerHTML = value;
return element;
}
createHR() {
var element = document.createElement("HR");
return element;
}
createSettings(settingsKey) {
var setting = document.createElement("TR");
var content = new PopupContent();
var data = SERVER["settings"][settingsKey];
switch (settingsKey) {
// Texteingaben
case "update":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Schedule for updating (Playlist, XMLTV, Backup)" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "update", data.toString());
input.setAttribute("placeholder", "0000,1000,2000");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "backup.path":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Location for automatic backups" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "backup.path", data);
input.setAttribute("placeholder", "/mnt/data/backup/xteve/");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "temp.path":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Location for the temporary files" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "temp.path", data);
input.setAttribute("placeholder", "/tmp/xteve/");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "user.agent":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "User Agent" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "user.agent", data);
input.setAttribute("placeholder", "xTeVe");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "plex.url":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Plex Server URL" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "plex.url", data);
input.setAttribute("placeholder", "http://plex-host:32400");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "plex.token":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Plex API Token" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("password", "plex.token", data);
input.setAttribute("placeholder", "Plex X-Plex-Token");
input.setAttribute("autocomplete", "off");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "buffer.timeout":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Timeout for new client connections" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "buffer.timeout", data);
input.setAttribute("placeholder", "100");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "ffmpeg.path":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "FFmpeg Binary Path" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "ffmpeg.path", data);
input.setAttribute("placeholder", "/path/to/ffmpeg");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "ffmpeg.options":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "FFmpeg Options" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "ffmpeg.options", data);
input.setAttribute("placeholder", "Leave blank to set default settings");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "vlc.path":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "VLC / CVLC Binary Path" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "vlc.path", data);
input.setAttribute("placeholder", "/path/to/cvlc");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "vlc.options":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "VLC / CVLC Options" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "vlc.options", data);
input.setAttribute("placeholder", "Leave blank to set default settings");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
// Checkboxen
case "authentication.web":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "WEB Authentication" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "authentication.pms":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "PMS Authentication" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "authentication.m3u":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "M3U Authentication" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "authentication.xml":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "XML Authentication" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "authentication.api":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "API Authentication" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "files.update":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Updates all files at startup" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "cache.images":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Image Caching" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "xepg.replace.missing.images":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Replace missing program images" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "api":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "API Interface" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "use_plexAPI":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Use Plex API Refresh" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
// Select
case "xepg.missing.epg.mode":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Missing EPG Handling" + ":";
var tdRight = document.createElement("TD");
var text = ["Strict (deactivate channels)", "Relaxed (keep active / dummy guide)"];
var values = ["strict", "relaxed"];
var select = content.createSelect(text, values, data, settingsKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(select);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "tuner":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Number of Tuners" + ":";
var tdRight = document.createElement("TD");
var text = new Array();
var values = new Array();
for (var i = 1; i <= 100; i++) {
text.push(i);
values.push(i);
}
var select = content.createSelect(text, values, data, settingsKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(select);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "epgSource":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "EPG Source" + ":";
var tdRight = document.createElement("TD");
var text = ["PMS", "XEPG"];
var values = ["PMS", "XEPG"];
var select = content.createSelect(text, values, data, settingsKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(select);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "backup.keep":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Number of backups to keep" + ":";
var tdRight = document.createElement("TD");
var text = ["5", "10", "20", "30", "40", "50"];
var values = ["5", "10", "20", "30", "40", "50"];
var select = content.createSelect(text, values, data, settingsKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(select);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "buffer.size.kb":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Buffer Size" + ":";
var tdRight = document.createElement("TD");
var text = ["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"];
var values = ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"];
var select = content.createSelect(text, values, data, settingsKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(select);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "buffer":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "Stream Buffer" + ":";
var tdRight = document.createElement("TD");
var text = ["No Buffer (Client connects to the streaming server)", "xTeVe: (xTeVe connects to the streaming server)", "FFmpeg: (FFmpeg connects to the streaming server)", "VLC: (VLC connects to the streaming server)"];
var values = ["-", "xteve", "ffmpeg", "vlc"];
var select = content.createSelect(text, values, data, settingsKey);
select.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(select);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "udpxy":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "UDPxy address" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "udpxy", data);
input.setAttribute("placeholder", "host:port");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
}
return setting;
}
createDescription(settingsKey) {
var description = document.createElement("TR");
var text;
switch (settingsKey) {
case "authentication.web":
text = "Access to the web interface only possible with credentials.";
break;
case "authentication.m3u":
text = "Downloading the xteve.m3u file via an HTTP request is only possible with authentication.";
break;
case "authentication.pms":
text = "Plex requests are only possible with authentication. <br><b>Warning!!!</b> After activating this function xTeVe must be delete in the PMS DVR settings and set up again.";
break;
case "authentication.xml":
text = "Downloading the xteve.xml file via an HTTP request is only possible with authentication";
break;
case "authentication.api":
if (SERVER["settings"]["authentication.web"] == true) {
text = "Access to the API interface is only possible with authentication.";
}
break;
case "backup.keep":
text = "Number of backups to keep. Older backups are automatically deleted.";
break;
case "backup.path":
text = "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder.";
break;
case "temp.path":
text = "Location for the buffer files.";
break;
case "buffer":
text = "Functions of the buffer:<br>- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support<br>- RTP / RTPS support (only FFmpeg or VLC)<br>- Re-streaming<br>- Separate tuner limit for each playlist";
break;
case "buffer.size.kb":
text = "Buffer size in MB.<br>M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used.";
break;
case "buffer.timeout":
text = "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds.";
break;
case "user.agent":
text = "User Agent for HTTP requests. For every HTTP connection, this value is used for the user agent. Should only be changed if xTeVe is blocked.";
break;
case "plex.url":
text = "Base URL of your Plex server. Example: http://192.168.1.10:32400";
break;
case "plex.token":
text = "Plex token used for authenticated API calls to refresh your DVR guide.";
break;
case "ffmpeg.path":
text = "Path to FFmpeg binary.";
break;
case "ffmpeg.options":
text = "FFmpeg options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.";
break;
case "vlc.path":
text = "Path to VLC / CVLC binary.";
break;
case "vlc.options":
text = "VLC / CVLC options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.";
break;
case "epgSource":
text = "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)";
break;
case "tuner":
text = "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again.";
break;
case "update":
text = "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated. Leave this field empty if no updates are to be carried out.";
break;
case "api":
text = "Via API interface it is possible to send commands to xTeVe. API documentation is <a href='https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md#api'>here</a>";
break;
case "use_plexAPI":
text = "When enabled, xTeVe calls Plex directly to refresh DVR guide data after lineup or XEPG updates.";
break;
case "files.update":
text = "Updates all playlists, tuner and XMLTV files at startup.";
break;
case "cache.images":
text = "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.<br>Downloading the images may take a while and will be done in the background.";
break;
case "xepg.replace.missing.images":
text = "If the poster in the XMLTV program is missing, the channel logo will be used.";
break;
case "xepg.missing.epg.mode":
text = "Strict: channels are deactivated when XMLTV mappings disappear.<br>Relaxed: channels stay active with last-known mappings and fall back to a dummy guide whenever guide data cannot be resolved.";
break;
case "udpxy":
text = "The address of your UDPxy server. If set, and the channel URLs in the m3u is multicast, xTeVe will rewrite it so that it is accessed via the UDPxy service.";
break;
default:
text = "";
break;
}
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "";
var tdRight = document.createElement("TD");
var pre = document.createElement("PRE");
pre.innerHTML = text;
tdRight.appendChild(pre);
description.appendChild(tdLeft);
description.appendChild(tdRight);
return description;
}
}
class SettingsCategoryItem extends SettingsCategory {
constructor(headline, settingsKeys) {
super();
this.headline = headline;
this.settingsKeys = settingsKeys;
}
createCategory() {
var headline = this.createCategoryHeadline(this.headline);
var settingsKeys = this.settingsKeys;
var doc = document.getElementById(this.DocumentID);
doc.appendChild(headline);
// Tabelle für die Kategorie erstellen
var table = document.createElement("TABLE");
var keys = settingsKeys.split(",");
keys.forEach(settingsKey => {
switch (settingsKey) {
case "authentication.pms":
case "authentication.m3u":
case "authentication.xml":
case "authentication.api":
if (SERVER["settings"]["authentication.web"] == false) {
break;
}
default:
var item = this.createSettings(settingsKey);
var description = this.createDescription(settingsKey);
table.appendChild(item);
table.appendChild(description);
break;
}
});
doc.appendChild(table);
doc.appendChild(this.createHR());
}
}
function showSettings() {
console.log("SETTINGS");
for (let i = 0; i < settingsCategory.length; i++) {
settingsCategory[i].createCategory();
}
}
function saveSettings() {
console.log("Save Settings");
var cmd = "saveSettings";
var div = document.getElementById("content_settings");
var settings = div.getElementsByClassName("changed");
var newSettings = new Object();
for (let i = 0; i < settings.length; i++) {
var name;
var value;
switch (settings[i].tagName) {
case "INPUT":
switch (settings[i].type) {
case "checkbox":
name = settings[i].name;
value = settings[i].checked;
newSettings[name] = value;
break;
case "text":
name = settings[i].name;
value = settings[i].value;
switch (name) {
case "update":
value = value.split(",");
value = value.filter(function (e) { return e; });
break;
case "buffer.timeout":
value = parseFloat(value);
}
newSettings[name] = value;
break;
}
break;
case "SELECT":
name = settings[i].name;
value = settings[i].value;
// Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert
if (isNaN(value)) {
newSettings[name] = value;
}
else {
newSettings[name] = parseInt(value);
}
break;
}
}
var data = new Object();
data["settings"] = newSettings;
var server = new Server(cmd);
server.request(data);
}
class Log {
createLog(entry) {
var element = document.createElement("PRE");
if (entry.indexOf("WARNING") != -1) {
element.className = "warningMsg";
}
if (entry.indexOf("ERROR") != -1) {
element.className = "errorMsg";
}
if (entry.indexOf("DEBUG") != -1) {
element.className = "debugMsg";
}
element.textContent = entry;
return element;
}
}
function showLogs(bottom) {
var log = new Log();
var logs = SERVER["log"]["log"];
var div = document.getElementById("content_log");
var wrapper = document.getElementById("box-wrapper");
var shouldStickToBottom = bottom;
div.innerHTML = "";
if (wrapper != null && shouldStickToBottom == false) {
var distanceToBottom = wrapper.scrollHeight - wrapper.scrollTop - wrapper.clientHeight;
if (distanceToBottom < 80) {
shouldStickToBottom = true;
}
}
var keys = getObjKeys(logs);
keys.forEach(logID => {
var entry = log.createLog(logs[logID]);
div.append(entry);
});
setTimeout(function () {
if (shouldStickToBottom == true && wrapper != null) {
wrapper.scrollTop = wrapper.scrollHeight;
}
}, 10);
}
function resetLogs() {
var cmd = "resetLogs";
var data = new Object();
var server = new Server(cmd);
server.request(data);
}
var SERVER = new Object();
var BULK_EDIT = false;
var COLUMN_TO_SORT;
var SEARCH_MAPPING = new Object();
var UNDO = new Object();
var WS_AVAILABLE = false;
var ACTIVE_MENU_ID = "";
var LAST_BULK_CHECKBOX = null;
// Menü
var menuItems = new Array();
menuItems.push(new MainMenuItem("playlist", "Playlist", "m3u.png", "Local or remote playlists"));
//menuItems.push(new MainMenuItem("pmsID", "PMS ID", "number.png", ""))
menuItems.push(new MainMenuItem("filter", "Filter", "filter.png", "Filter playlist"));
menuItems.push(new MainMenuItem("xmltv", "XMLTV", "xmltv.png", "Local or remote XMLTV files"));
menuItems.push(new MainMenuItem("mapping", "Mapping", "mapping.png", "Map playlist channels to EPG channels"));
menuItems.push(new MainMenuItem("users", "Users", "users.png", "User management"));
menuItems.push(new MainMenuItem("settings", "Settings", "settings.png", "Settings"));
menuItems.push(new MainMenuItem("log", "Log", "log.png", "Log"));
menuItems.push(new MainMenuItem("logout", "Logout", "logout.png", "Logout"));
// Kategorien für die Einstellungen
var settingsCategory = new Array();
settingsCategory.push(new SettingsCategoryItem("General", "tuner,epgSource,api,use_plexAPI,plex.url,plex.token"));
settingsCategory.push(new SettingsCategoryItem("Files", "update,files.update,temp.path,cache.images,xepg.missing.epg.mode,xepg.replace.missing.images"));
settingsCategory.push(new SettingsCategoryItem("Streaming", "buffer,udpxy,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"));
settingsCategory.push(new SettingsCategoryItem("Backup", "backup.path,backup.keep"));
settingsCategory.push(new SettingsCategoryItem("Authentication", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
function showPopUpElement(elm) {
var allElements = new Array("popup-custom");
for (var i = 0; i < allElements.length; i++) {
showElement(allElements[i], false);
}
showElement(elm, true);
setTimeout(function () {
showElement("popup", true);
}, 10);
return;
}
function showElement(elmID, type) {
var cssClass;
switch (type) {
case true:
cssClass = "block";
break;
case false:
cssClass = "none";
break;
}
var element = document.getElementById(elmID);
if (element == null) {
return;
}
element.className = cssClass;
}
function setConnectionState(state, text = "") {
var label = text;
if (label == undefined || label.length == 0) {
switch (state) {
case "online":
label = "Connected";
break;
case "busy":
label = "Syncing";
break;
case "offline":
label = "Offline";
break;
default:
label = "Connecting";
break;
}
}
var indicator = document.getElementById("connection-indicator");
if (indicator == null) {
return;
}
indicator.className = "status-" + state;
indicator.innerText = label;
}
function changeButtonAction(element, buttonID, attribute) {
var value = element.options[element.selectedIndex].value;
document.getElementById(buttonID).setAttribute(attribute, value);
}
function getLocalData(dataType, id) {
var data = new Object();
switch (dataType) {
case "m3u":
data = SERVER["settings"]["files"][dataType][id];
break;
case "hdhr":
data = SERVER["settings"]["files"][dataType][id];
break;
case "filter":
case "custom-filter":
case "group-title":
if (id == -1) {
data["active"] = true;
data["caseSensitive"] = false;
data["description"] = "";
data["exclude"] = "";
data["filter"] = "";
data["include"] = "";
data["name"] = "";
data["type"] = "group-title";
SERVER["settings"]["filter"][id] = data;
}
data = SERVER["settings"]["filter"][id];
break;
case "xmltv":
data = SERVER["settings"]["files"][dataType][id];
break;
case "users":
data = SERVER["users"][id]["data"];
break;
case "mapping":
data = SERVER["xepg"]["epgMapping"][id];
break;
case "m3uGroups":
data = SERVER["data"]["playlist"]["m3u"]["groups"];
break;
}
return data;
}
function getObjKeys(obj) {
var keys = new Array();
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
keys.push(i);
}
}
return keys;
}
function getAllSelectedChannels() {
var channels = new Array();
if (BULK_EDIT == false) {
return channels;
}
var trs = document.getElementById("content_table").getElementsByTagName("TR");
for (var i = 1; i < trs.length; i++) {
if (trs[i].style.display != "none") {
if (trs[i].firstChild.firstChild.checked == true) {
channels.push(trs[i].id);
}
}
}
return channels;
}
function scheduleChannelRangeSelection(checkbox, event) {
var shiftPressed = false;
if (event != undefined && event.shiftKey == true) {
shiftPressed = true;
}
// Run after the native checkbox toggle so we copy the final checked state.
setTimeout(function () {
selectChannelRange(checkbox, shiftPressed);
}, 0);
}
function selectChannelRange(checkbox, shiftPressed) {
if (BULK_EDIT == false || checkbox == undefined || checkbox == null) {
return;
}
var table = document.getElementById("content_table");
if (table == null) {
return;
}
var trs = table.getElementsByTagName("TR");
var visibleCheckboxes = new Array();
for (var i = 1; i < trs.length; i++) {
if (trs[i].style.display != "none") {
var bulkCheckbox = trs[i].querySelector("input.bulk");
if (bulkCheckbox != null) {
visibleCheckboxes.push(bulkCheckbox);
}
}
}
var currentIndex = visibleCheckboxes.indexOf(checkbox);
var previousIndex = -1;
if (LAST_BULK_CHECKBOX != null) {
previousIndex = visibleCheckboxes.indexOf(LAST_BULK_CHECKBOX);
}
if (shiftPressed == true && previousIndex > -1 && currentIndex > -1) {
var start = Math.min(previousIndex, currentIndex);
var end = Math.max(previousIndex, currentIndex);
for (var i = start; i <= end; i++) {
visibleCheckboxes[i].checked = checkbox.checked;
}
}
LAST_BULK_CHECKBOX = checkbox;
}
function selectAllChannels() {
var bulk = false;
var trs = document.getElementById("content_table").getElementsByTagName("TR");
if (trs[0].firstChild.firstChild.checked == true) {
bulk = true;
}
for (var i = 1; i < trs.length; i++) {
if (trs[i].style.display != "none") {
switch (bulk) {
case true:
trs[i].firstChild.firstChild.checked = true;
break;
case false:
trs[i].firstChild.firstChild.checked = false;
break;
}
}
}
LAST_BULK_CHECKBOX = null;
return;
}
function bulkEdit() {
BULK_EDIT = !BULK_EDIT;
var className;
var rows = document.getElementsByClassName("bulk");
switch (BULK_EDIT) {
case true:
className = "bulk showBulk";
break;
case false:
className = "bulk hideBulk";
break;
}
for (var i = 0; i < rows.length; i++) {
rows[i].className = className;
rows[i].checked = false;
}
LAST_BULK_CHECKBOX = null;
return;
}
function sortTable(column) {
//console.log(columm);
if (column == COLUMN_TO_SORT) {
return;
}
var table = document.getElementById("content_table");
var tableHead = table.getElementsByTagName("TR")[0];
var tableItems = tableHead.getElementsByTagName("TD");
var sortObj = new Object();
var x, xValue;
var tableHeader;
var sortByString = false;
if (column > 0 && COLUMN_TO_SORT > 0) {
tableItems[COLUMN_TO_SORT].className = "pointer";
tableItems[column].className = "sortThis";
}
COLUMN_TO_SORT = column;
var rows = table.rows;
if (rows[1] != undefined) {
tableHeader = rows[0];
x = rows[1].getElementsByTagName("TD")[column];
for (i = 1; i < rows.length; i++) {
x = rows[i].getElementsByTagName("TD")[column];
switch (x.childNodes[0].tagName.toLowerCase()) {
case "input":
xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase();
break;
case "p":
xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase();
break;
default: console.log(x.childNodes[0].tagName);
}
if (xValue == "" || Number.isNaN(xValue)) {
xValue = i;
sortObj[i] = rows[i];
}
else {
switch (isNaN(xValue)) {
case false:
xValue = parseFloat(xValue);
sortObj[xValue] = rows[i];
break;
case true:
sortByString = true;
sortObj[xValue.toLowerCase() + i] = rows[i];
break;
}
}
}
while (table.firstChild) {
table.removeChild(table.firstChild);
}
var sortValues = getObjKeys(sortObj);
if (sortByString == true) {
sortValues.sort();
console.log(sortValues);
}
else {
function sortFloat(a, b) {
return a - b;
}
sortValues.sort(sortFloat);
}
table.appendChild(tableHeader);
for (var i = 0; i < sortValues.length; i++) {
table.appendChild(sortObj[sortValues[i]]);
}
}
return;
}
function createSearchObj() {
SEARCH_MAPPING = new Object();
var data = SERVER["xepg"]["epgMapping"];
var channels = getObjKeys(data);
var channelKeys = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title", "x-xmltv-file"];
channels.forEach(id => {
channelKeys.forEach(key => {
if (key == "x-active") {
switch (data[id][key]) {
case true:
SEARCH_MAPPING[id] = "online ";
break;
case false:
SEARCH_MAPPING[id] = "offline ";
break;
}
}
else {
if (key == "x-xmltv-file") {
var xmltvFile = getValueFromProviderFile(data[id][key], "xmltv", "name");
if (xmltvFile != undefined) {
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + xmltvFile + " ";
}
}
else {
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " ";
}
}
});
});
return;
}
function searchInMapping() {
var searchValue = document.getElementById("searchMapping").value;
var trs = document.getElementById("content_table").getElementsByTagName("TR");
for (var i = 1; i < trs.length; ++i) {
var id = trs[i].getAttribute("id");
var element = SEARCH_MAPPING[id];
switch (element.toLowerCase().includes(searchValue.toLowerCase())) {
case true:
document.getElementById(id).style.display = "";
break;
case false:
document.getElementById(id).style.display = "none";
break;
}
}
return;
}
function calculateWrapperHeight() {
var elm = document.getElementById("box-wrapper");
var content = document.getElementById("content");
if (elm != null && content != null) {
var contentTop = content.getBoundingClientRect().top;
var freeSpace = window.innerHeight - contentTop - 26;
if (freeSpace < 180) {
freeSpace = 180;
}
elm.style.height = freeSpace + "px";
}
return;
}
function changeChannelNumber(element) {
var dbID = element.parentNode.parentNode.id;
var newNumber = parseFloat(element.value);
var channelNumbers = [];
var data = SERVER["xepg"]["epgMapping"];
var channels = getObjKeys(data);
if (isNaN(newNumber)) {
alert("Invalid channel number");
return;
}
channels.forEach(id => {
var channelNumber = parseFloat(data[id]["x-channelID"]);
channelNumbers.push(channelNumber);
});
for (var i = 0; i < channelNumbers.length; i++) {
if (channelNumbers.indexOf(newNumber) == -1) {
break;
}
if (Math.floor(newNumber) == newNumber) {
newNumber = newNumber + 1;
}
else {
newNumber = newNumber + 0.1;
newNumber.toFixed(1);
newNumber = Math.round(newNumber * 10) / 10;
}
}
data[dbID]["x-channelID"] = newNumber.toString();
element.value = newNumber;
console.log(data[dbID]["x-channelID"]);
if (COLUMN_TO_SORT == 1) {
COLUMN_TO_SORT = -1;
sortTable(1);
}
return;
}
function backup() {
var data = new Object();
console.log("Backup data");
var cmd = "xteveBackup";
console.log("SEND TO SERVER");
console.log(data);
var server = new Server(cmd);
server.request(data);
return;
}
function toggleChannelStatus(id) {
var element;
var status;
if (document.getElementById("active")) {
var checkbox = document.getElementById("active");
status = (checkbox).checked;
}
var ids = getAllSelectedChannels();
if (ids.length == 0) {
ids.push(id);
}
ids.forEach(id => {
var channel = SERVER["xepg"]["epgMapping"][id];
channel["x-active"] = status;
switch (channel["x-active"]) {
case true:
if (channel["x-xmltv-file"] == "-" || channel["x-mapping"] == "-") {
if (BULK_EDIT == false) {
alert(channel["x-name"] + ": Missing XMLTV file / channel");
checkbox.checked = false;
}
channel["x-active"] = false;
}
break;
case false:
// code...
break;
}
if (channel["x-active"] == false) {
document.getElementById(id).className = "notActiveEPG";
}
else {
document.getElementById(id).className = "activeEPG";
}
});
}
function restore() {
if (document.getElementById('upload')) {
document.getElementById('upload').remove();
}
var restore = document.createElement("INPUT");
restore.setAttribute("type", "file");
restore.setAttribute("class", "notVisible");
restore.setAttribute("name", "");
restore.id = "upload";
document.body.appendChild(restore);
restore.click();
restore.onchange = function () {
var filename = restore.files[0].name;
var check = confirm("File: " + filename + "\nAll data will be replaced with those from the backup. Should the files be restored?");
if (check == true) {
var reader = new FileReader();
var file = document.querySelector('input[type=file]').files[0];
if (file) {
reader.readAsDataURL(file);
reader.onload = function () {
console.log(reader.result);
var data = new Object();
var cmd = "xteveRestore";
data["base64"] = reader.result;
var server = new Server(cmd);
server.request(data);
};
}
else {
alert("File could not be loaded");
}
restore.remove();
return;
}
};
return;
}
function uploadLogo() {
if (document.getElementById('upload')) {
document.getElementById('upload').remove();
}
var upload = document.createElement("INPUT");
upload.setAttribute("type", "file");
upload.setAttribute("class", "notVisible");
upload.setAttribute("name", "");
upload.id = "upload";
document.body.appendChild(upload);
upload.click();
upload.onblur = function () {
alert();
};
upload.onchange = function () {
var filename = upload.files[0].name;
var reader = new FileReader();
var file = document.querySelector('input[type=file]').files[0];
if (file) {
reader.readAsDataURL(file);
reader.onload = function () {
console.log(reader.result);
var data = new Object();
var cmd = "uploadLogo";
data["base64"] = reader.result;
data["filename"] = file.name;
var server = new Server(cmd);
server.request(data);
var updateLogo = document.getElementById('update-icon');
updateLogo.checked = false;
updateLogo.className = "changed";
};
}
else {
alert("File could not be loaded");
}
upload.remove();
return;
};
}
function checkUndo(key) {
switch (key) {
case "epgMapping":
if (UNDO.hasOwnProperty(key)) {
SERVER["xepg"][key] = JSON.parse(JSON.stringify(UNDO[key]));
}
else {
UNDO[key] = JSON.parse(JSON.stringify(SERVER["xepg"][key]));
}
break;
default:
break;
}
return;
}
function sortSelect(elem) {
var tmpAry = [];
var selectedValue = elem[elem.selectedIndex].value;
for (var i = 0; i < elem.options.length; i++)
tmpAry.push(elem.options[i]);
tmpAry.sort(function (a, b) { return (a.text < b.text) ? -1 : 1; });
while (elem.options.length > 0)
elem.options[0] = null;
var newSelectedIndex = 0;
for (var i = 0; i < tmpAry.length; i++) {
elem.options[i] = tmpAry[i];
if (elem.options[i].value == selectedValue)
newSelectedIndex = i;
}
elem.selectedIndex = newSelectedIndex; // Set new selected index after sorting
return;
}
function updateLog() {
var server = new Server("updateLog");
server.request(new Object());
}
class WizardCategory {
constructor() {
this.DocumentID = "content";
}
createCategoryHeadline(value) {
var element = document.createElement("H4");
element.innerHTML = value;
return element;
}
}
class WizardItem extends WizardCategory {
constructor(key, headline) {
super();
this.headline = headline;
this.key = key;
}
createWizard() {
var headline = this.createCategoryHeadline(this.headline);
var key = this.key;
var content = new PopupContent();
var description;
var doc = document.getElementById(this.DocumentID);
doc.innerHTML = "";
doc.appendChild(headline);
switch (key) {
case "tuner":
var text = new Array();
var values = new Array();
for (var i = 1; i <= 100; i++) {
text.push(i);
values.push(i);
}
var select = content.createSelect(text, values, "1", key);
select.setAttribute("class", "wizard");
select.id = key;
doc.appendChild(select);
description = "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again.";
break;
case "epgSource":
var text = ["PMS", "XEPG"];
var values = ["PMS", "XEPG"];
var select = content.createSelect(text, values, "XEPG", key);
select.setAttribute("class", "wizard");
select.id = key;
doc.appendChild(select);
description = "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)";
break;
case "m3u":
var input = content.createInput("text", key, "");
input.setAttribute("placeholder", "File path or URL of the M3U");
input.setAttribute("class", "wizard");
input.id = key;
doc.appendChild(input);
description = "Local or remote playlists";
break;
case "xmltv":
var input = content.createInput("text", key, "");
input.setAttribute("placeholder", "File path or URL of the XMLTV");
input.setAttribute("class", "wizard");
input.id = key;
doc.appendChild(input);
description = "Local or remote XMLTV file";
break;
default:
console.log(key);
break;
}
var pre = document.createElement("PRE");
pre.innerHTML = description;
doc.appendChild(pre);
console.log(headline, key);
}
}
function readyForConfiguration(wizard) {
var server = new Server("getServerConfig");
server.request(new Object());
showElement("loading", false);
configurationWizard[wizard].createWizard();
}
function saveWizard() {
var cmd = "saveWizard";
var div = document.getElementById("content");
var config = div.getElementsByClassName("wizard");
var wizard = new Object();
for (var i = 0; i < config.length; i++) {
var name;
var value;
switch (config[i].tagName) {
case "SELECT":
name = config[i].name;
value = config[i].value;
// Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert
if (isNaN(value)) {
wizard[name] = value;
}
else {
wizard[name] = parseInt(value);
}
break;
case "INPUT":
switch (config[i].type) {
case "text":
name = config[i].name;
value = config[i].value;
if (value.length == 0) {
var msg = name.toUpperCase() + ": " + "Missing input";
alert(msg);
return;
}
wizard[name] = value;
break;
}
break;
default:
// code...
break;
}
}
var data = new Object();
data["wizard"] = wizard;
var server = new Server(cmd);
server.request(data);
console.log(data);
}
// Wizard
var configurationWizard = new Array();
configurationWizard.push(new WizardItem("tuner", "Number of tuners"));
configurationWizard.push(new WizardItem("epgSource", "EPG Source"));
configurationWizard.push(new WizardItem("m3u", "M3U Playlist"));
configurationWizard.push(new WizardItem("xmltv", "XMLTV File"));
function login() {
var err = false;
var data = new Object();
var div = document.getElementById("content");
var form = document.getElementById("authentication");
var inputs = div.getElementsByTagName("INPUT");
console.log(inputs);
for (var i = inputs.length - 1; i >= 0; i--) {
var key = inputs[i].name;
var value = inputs[i].value;
if (value.length == 0) {
inputs[i].style.borderColor = "red";
err = true;
}
else {
inputs[i].style.borderColor = "";
}
data[key] = value;
}
if (err == true) {
data = new Object();
return;
}
if (data.hasOwnProperty("confirm")) {
if (data["confirm"] != data["password"]) {
document.getElementById('password').style.borderColor = "red";
document.getElementById('confirm').style.borderColor = "red";
document.getElementById("err").innerHTML = "Password does not match";
return;
}
}
console.log(data);
form.submit();
}