continuous-integration/drone/push Build encountered an error
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.
3506 lines
140 KiB
JavaScript
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();
|
|
}
|