// 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.
Only available with activated buffer.
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.
Only available with activated buffer.
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)
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.
(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.
(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.
Warning!!! 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:
- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player
- Small jerking of the streams can be compensated
- HLS / M3U8 support
- RTP / RTPS support (only FFmpeg or VLC)
- Re-streaming
- Separate tuner limit for each playlist"; break; case "buffer.size.kb": text = "Buffer size in MB.
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.
Only change if you know what you are doing.
Leave blank to set default settings."; break; case "vlc.path": text = "Path to VLC / CVLC binary."; break; case "vlc.options": text = "VLC / CVLC options.
Only change if you know what you are doing.
Leave blank to set default settings."; break; case "epgSource": text = "PMS:
- Use EPG data from Plex or Emby

XEPG:
- Use of one or more XMLTV files
- Channel management
- M3U / XMLTV export (HTTP link for IPTV apps)"; break; case "tuner": text = "Number of parallel connections that can be established to the provider.
Available for: Plex, Emby (HDHR), M3U (with active buffer).
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 here"; 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.
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.
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.
Available for: Plex, Emby (HDHR), M3U (with active buffer).
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:
- Use EPG data from Plex or Emby

XEPG:
- Use of one or more XMLTV files
- Channel management
- 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(); }