Phase 3: embed web UI with go:embed, pinned TypeScript toolchain, drop i18n, add /healthz
continuous-integration/drone/push Build encountered an error

- html/embed.go embeds html/ (pages, css, img, js, video); src/assets.go
  serves it, with os.DirFS("html") under -dev. Static assets get an ETag
  and Cache-Control: no-cache; HTML pages are still templated (only the
  login error message is substituted now).
- Delete the generated src/webUI.go (783 KB base64), src/html-build.go and
  cmd/webui-gen; Dockerfile no longer runs a generator.
- Language layer removed: 254 {{.x}} placeholders inlined as English
  strings in ts/*.ts and the two auth pages; html/lang/en.json, the
  LanguageUI struct and the 'language' setting are gone.
- ts/tsconfig.json + package.json pin typescript 5.9.3; the seven sources
  compile (ES2020, global scripts) into one committed html/js/app.js.
  Ten unreferenced legacy scripts under html/js/ deleted; all pages load
  js/app.js.
- Fix the six type errors that blocked a clean compile, including a real
  bug: a missing semicolon in the search shortcut handler made the code
  call the result of preventDefault(), so the shortcut threw instead of
  focusing the search box.
- /healthz liveness endpoint; Dockerfile healthcheck and README use it.
- Drone: go vet, and a webui-check step that rebuilds the bundle and fails
  if the committed app.js is stale.
- README-DEV.md documents build, UI toolchain, -dev, versioning, CI.
This commit is contained in:
2026-09-26 12:46:45 +10:00
parent 51c7830067
commit ddc70e170a
47 changed files with 2208 additions and 7373 deletions
+9
View File
@@ -7,8 +7,17 @@ steps:
image: cache.coadcorp.com/library/golang:1.27.1
commands:
- go mod download
- go vet ./...
- go test ./...
- name: webui-check
image: cache.coadcorp.com/library/node:22-alpine
commands:
- cp html/js/app.js /tmp/app.js.committed
- npm ci --no-audit --no-fund
- npm run build
- cmp /tmp/app.js.committed html/js/app.js || (echo "html/js/app.js is stale - run 'npm run build' and commit the result" && exit 1)
- name: go-build
image: cache.coadcorp.com/library/golang:1.27.1
commands:
+1
View File
@@ -11,3 +11,4 @@ de.json
.gocache/
agent.md
skill.md
node_modules/
+1 -3
View File
@@ -10,8 +10,6 @@ RUN go mod download
COPY . .
RUN go run ./cmd/webui-gen
ARG TARGETOS
ARG TARGETARCH
RUN XTEVE_VERSION="$(grep -m1 '^#### ' changelog-beta.md | cut -d' ' -f2 | sed 's/-beta$//')" \
@@ -47,6 +45,6 @@ ENV XTEVE_CONFIG=/xteve/config
ENV XTEVE_PORT=34400
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget -qO- "http://127.0.0.1:${XTEVE_PORT}/lineup_status.json" > /dev/null || exit 1
CMD wget -qO- "http://127.0.0.1:${XTEVE_PORT}/healthz" > /dev/null || exit 1
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
+57 -1
View File
@@ -1 +1,57 @@
# Information for the developers will come soon
# Developer notes
## Layout
| Path | What it is |
|---|---|
| `xteve.go` | `main`: flags, version, start-up |
| `src/` | the application (one Go package `src`) |
| `src/internal/` | authentication, image cache, M3U parser |
| `html/` | web interface: HTML pages, CSS, images, the compiled `js/app.js`, and `embed.go` which embeds the lot |
| `ts/` | TypeScript sources for the web interface |
| `docker/` | container entrypoint |
| `tasks/` | improvement plan and checklist |
## Build the binary
Go 1.27.1 is pinned in `go.mod`; the go command downloads it automatically.
```bash
go build ./...
go vet ./...
go test ./...
```
The web interface is embedded with `go:embed` from `html/`, so a bare clone builds a complete binary.
## Web interface
The UI is plain TypeScript compiled as global scripts (no modules yet) into a single bundle, `html/js/app.js`, which is committed. CI rebuilds it and fails if the committed file is stale.
```bash
npm ci # installs the pinned TypeScript compiler
npm run build # ts/*.ts -> html/js/app.js
npm run check # type-check only
```
File order in the bundle is fixed in `ts/tsconfig.json` and matches the old per-page script order. All pages load the same bundle.
English strings are inlined in the TypeScript. There is no language layer.
## Run from source
```bash
go run . -config /path/to/config -port 34400
```
Add `-dev` to serve the web interface from the local `html/` directory instead of the embedded copy, so CSS and HTML edits show up on reload. JavaScript still needs `npm run build`.
## Versioning and release
The version lives in two places that must agree: `var Version` in `xteve.go` and the first `#### ` heading in `changelog-beta.md` (without the `-beta` suffix). CI fails on drift.
Drone (`.drone.yml`) runs vet, tests, the bundle check, hadolint and compose validation on every push. Pushes to `master` publish `registry.coadcorp.com/nathan/xteve:latest` and `:<sha>`; other branches publish `:<branch>` and `:<sha>`.
## Endpoints useful for operations
* `GET /healthz` returns `{"status":"ok","version":...,"scanInProgress":...}` with no authentication. The Docker healthcheck uses it.
+1 -1
View File
@@ -97,7 +97,7 @@ Host networking improves LAN discovery behavior (SSDP/DLNA) for Plex/Emby in man
* Multi-stage build (Go builder + minimal Alpine runtime)
* Runs as non-root user (`xteve`)
* Built-in healthcheck against `http://127.0.0.1:${XTEVE_PORT}/lineup_status.json`
* Built-in healthcheck against `http://127.0.0.1:${XTEVE_PORT}/healthz`
---
-15
View File
@@ -1,15 +0,0 @@
package main
import (
"log"
"os"
"xteve/src"
)
func main() {
src.HTMLInit("webUI", "src", "html"+string(os.PathSeparator), "src"+string(os.PathSeparator)+"webUI.go")
if err := src.BuildGoFile(); err != nil {
log.Fatal(err)
}
}
+1 -5
View File
@@ -6,11 +6,7 @@
<title>xTeVe</title>
<link rel="stylesheet" href="css/screen.css" type="text/css">
<link rel="stylesheet" href="css/base.css" type="text/css">
<script language="javascript" type="text/javascript" src="js/configuration_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/menu_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/settings_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/base_ts.js"></script>
<script src="js/app.js"></script>
</head>
<body class="auth-screen wizard-screen" onload="javascript: readyForConfiguration(0);">
+6 -7
View File
@@ -6,8 +6,7 @@
<title>xTeVe</title>
<link rel="stylesheet" href="css/screen.css" type="text/css">
<link rel="stylesheet" href="css/base.css" type="text/css">
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
<script src="js/app.js"></script>
</head>
<body class="auth-screen">
@@ -17,7 +16,7 @@
<main id="box" role="main" aria-labelledby="head-text">
<div id="headline">
<h1 id="head-text" class="center">{{.account.headline}}</h1>
<h1 id="head-text" class="center">Create user account</h1>
</div>
<p id="err" class="errorMsg center" role="alert" aria-live="assertive" aria-atomic="true"></p>
@@ -26,11 +25,11 @@
<form id="authentication" action="" method="post" aria-describedby="err" novalidate>
<label for="username">{{.account.username.title}}:</label>
<label for="username">Username:</label>
<input id="username" type="text" name="username" placeholder="Username" value="" autocomplete="username">
<label for="password">{{.account.password.title}}:</label>
<label for="password">Password:</label>
<input id="password" type="password" name="password" placeholder="Password" value="" autocomplete="new-password">
<label for="confirm">{{.account.confirm.title}}:</label>
<label for="confirm">Confirm:</label>
<input id="confirm" type="password" name="confirm" placeholder="Confirm" value="" autocomplete="new-password">
</form>
@@ -38,7 +37,7 @@
</div>
<div id="box-footer">
<input id="submit" class="" type="button" value="{{.button.craeteAccount}}" aria-label="{{.button.craeteAccount}}" onclick="javascript: login();">
<input id="submit" class="" type="button" value="Create Account" aria-label="Create Account" onclick="javascript: login();">
</div>
+13
View File
@@ -0,0 +1,13 @@
// Package webui embeds the static web interface (HTML, CSS, the compiled
// JavaScript bundle, images and the tuner-limit video) into the binary.
//
// Build the JavaScript bundle with `npm run build` (see README-DEV.md); the
// result, html/js/app.js, is committed so `go build` works from a bare clone.
package webui
import "embed"
// FS holds the web interface files, rooted at the html/ directory.
//
//go:embed *.html css img js video
var FS embed.FS
+1 -6
View File
@@ -6,12 +6,7 @@
<title>xTeVe</title>
<link rel="stylesheet" href="css/screen.css" type="text/css">
<link rel="stylesheet" href="css/base.css" type="text/css">
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/menu_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/settings_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/logs_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/base_ts.js"></script>
<script src="js/app.js"></script>
</head>
File diff suppressed because it is too large Load Diff
-42
View File
@@ -1,42 +0,0 @@
function createFirstAccount(elm) {
var err = false;
var div = document.getElementById(elm);
console.log(div);
var form = document.getElementById('authentication');
const username = document.getElementById('username');
const password = document.getElementById('password');
const confirm = document.getElementById('confirm');
var inputs = div.getElementsByTagName('INPUT')
console.log(confirm);
switch(confirm) {
case null: break;
default:
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].value.length == 0) {
inputs[i].style.borderColor = 'red';
err = true
}
}
switch(err) {
case true: return; break;
case false:
if (password.value != confirm.value) {
confirm.style.borderColor = 'red';
return;
}
break;
}
}
form.submit();
return;
}
-68
View File
@@ -1,68 +0,0 @@
function login() {
var err = false;
var firstInvalid = null;
var data = new Object();
var div = document.getElementById("content");
var form = document.getElementById("authentication");
var errElement = document.getElementById("err");
if (errElement != null) {
errElement.innerHTML = "";
}
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";
inputs[i].setAttribute("aria-invalid", "true");
if (firstInvalid == null) {
firstInvalid = inputs[i];
}
err = true;
}
else {
inputs[i].style.borderColor = "";
inputs[i].setAttribute("aria-invalid", "false");
}
data[key] = value;
}
if (err == true) {
if (firstInvalid != null) {
firstInvalid.focus();
}
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('password').setAttribute("aria-invalid", "true");
document.getElementById('confirm').setAttribute("aria-invalid", "true");
document.getElementById("err").innerHTML = "{{.account.failed}}";
document.getElementById('password').focus();
return;
}
}
console.log(data);
form.submit();
}
document.addEventListener("DOMContentLoaded", function () {
var form = document.getElementById("authentication");
if (form == null) {
return;
}
var inputs = form.getElementsByTagName("INPUT");
for (var i = 0; i < inputs.length; i++) {
inputs[i].addEventListener("keydown", function (event) {
if (event.key == "Enter") {
event.preventDefault();
login();
}
});
}
if (inputs.length > 0) {
inputs[0].focus();
}
});
-331
View File
@@ -1,331 +0,0 @@
var config = new Object();
var menu = new Object();
var subMenu = new Object();
var activeStreams = new Object();
var xEPG = new Object();
var users = new Object();
var log = new Object();
var undo = new Object();
var webSockets = true;
var closeLog, version, activeMenu;
var columnToSort = 0
if (window.WebSocket === undefined) {
alert("Your browser does not support WebSockets");
webSockets = false;
}
function pageReady() {
var data = new Object();
data["cmd"] = "getServerConfig";
xTeVe(data);
//showLoadingScreen(false);
var resizeHandle = document.getElementById("openStreams");
var box = document.getElementById("myStreamsBox");
resizeHandle.addEventListener("mousedown", initialiseResize, false);
function initialiseResize(e) {
window.addEventListener("mousemove", startResizing, false);
window.addEventListener("mouseup", stopResizing, false);
}
function startResizing(e) {
box.style.height = (e.clientY - box.offsetTop) + "px";
var elm = document.getElementById("allStreams");
if (e.clientY > 120) {
elm.className = "visible";
} else {
elm.className = "notVisible";
}
calculateWrapperHeight();
}
function stopResizing(e) {
window.removeEventListener('mousemove', startResizing, false);
window.removeEventListener('mouseup', stopResizing, false);
calculateWrapperHeight();
}
window.addEventListener("resize", function(){
calculateWrapperHeight();
}, true);
}
function getObjKeys(obj) {
var keys = new Array();
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
keys.push(i);
}
}
return keys;
}
function createElement(item) {
//console.log(item);
var element = document.createElement(item["_element"]);
if (item.hasOwnProperty("_text")) {
//element.innerHTML = "<p>" + item["_text"] + "</p>";
element.innerHTML = item["_text"];
}
var keys = getObjKeys(item);
for (var i = 0; i < keys.length; i++) {
if (keys[i].charAt(0) != "_") {
//console.log(keys[i], item[keys[i]]);
element.setAttribute(keys[i], item[keys[i]]);
}
}
//console.log(element);
return element;
}
function modifyOption(id, options, values) {
var select = document.getElementById(id);
select.innerHTML = "";
for (var i = 0; i < options.length; i++) {
var element = document.createElement("OPTION")
element.value = values[i];
element.innerHTML = options[i];
document.getElementById(id).appendChild(element);
}
}
function startWebSocket() {
if (webSockets == false) {
return;
}
//ws.send('{"cmd": "getServerConfig1"}');
}
function checkErr(obj) {
//alert(obj["err"])
//screenLog(obj["err"], "error")
console.log(obj);
var newObj = new Object();
var newErr = new Object();
newErr["key"] = "Error";
newErr["value"] = obj["err"];
newErr["type"] = "error";
newObj[0] = newErr
showLog(newObj);
return
}
function screenLog(msg, msgType, show) {
return
clearTimeout(closeLog)
var div = document.getElementById("screenLog");
var newMsg = new Object();
newMsg["_element"] = "P";
switch(msgType) {
case "error": newMsg["class"] = "errorMsg"; break;
case "warning": newMsg["class"] = "warningMsg"; break;
//default: newMsg["class"] = "infoMsg"
}
newMsg["_text"] = msg;
div.appendChild(createElement(newMsg));
div.scrollTop = div.scrollHeight;
if (show == false) {
return;
}
div.className = ""
closeLog = setTimeout(closeScreenLog, 10000);
}
function closeScreenLog() {
var div = document.getElementById("screenLog");
div.className = "screenLogHidden"
}
function showScreenLog() {
clearTimeout(closeLog)
var div = document.getElementById("screenLog");
var currentClass = div.className;
div.className = "screenLogHidden"
switch(currentClass) {
case "screenLogHidden": div.className = ""; break;
case "": div.className = "screenLogHidden"; break;
}
}
function showLoadingScreen(elm) {
var div = document.getElementById("loading");
switch(elm) {
case true: div.className = "block"; break;
case false: div.className = "none"; break;
/*
case true: div.style.display = "block"; break;
case false: div.style.display = "none"; break;
*/
}
}
function createClintInfo(obj) {
//console.log(obj);
var keys = getObjKeys(obj);
for (var i = 0; i < keys.length; i++) {
if(document.getElementById(keys[i])){
document.getElementById(keys[i]).innerHTML = obj[keys[i]];
}
}
//document.getElementById("clientInfo").className = "visible";
}
function showElement(elmID, type) {
switch(type) {
case true: cssClass = "block"; break;
case false: cssClass = "none"; break;
}
document.getElementById(elmID).className = cssClass;
}
function showPopUpElement(elm) {
var allElements = new Array("deleteUserDetail", "mapping-detail", "user-detail", "file-detail");
for (var i = 0; i < allElements.length; i++) {
showElement(allElements[i], false)
}
showElement(elm, true)
setTimeout(function(){
showElement("popup", true);
}, 10);
}
// body...
function showStreams(force) {
var elmBox = document.getElementById("myStreamsBox");
var elm = document.getElementById("allStreams");
//console.log(elm);
show = elm.className;
switch(force) {
case true: show = "notVisible"; break;
case false: show = "visible"; break;
}
switch(show) {
case "notVisible":
elm.className = "visible";
elmBox.style.height = "100px";
break;
default:
elm.className = "notVisible";
elmBox.style.height = "20px";
break;
}
var show = elm.style.display; {
//console.log(elm.style.display);
}
calculateWrapperHeight();
}
function xteveBackup() {
console.log("xteveBackup");
var data = new Object();
data["cmd"] = "xteveBackup";
xTeVe(data);
}
function xteveRestore(elm) {
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
//console.log(restore.srcElement.files[0]);
var check = confirm("File: " + filename + "\nAll data will be replaced with those from the backup.\nShould 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();
data["cmd"] = "xteveRestore"
data["base64"] = reader.result
xTeVe(data);
return
};
} else {
alert("File could not be loaded")
}
}
};
}
function getBase64(file) {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
console.log(reader.result);
};
reader.onerror = function(error) {
console.log('Error: ', error);
};
}
function logout() {
document.cookie.split(';').forEach(function(c) {
document.cookie = c.trim().split('=')[0] + '=;' + 'expires=Thu, 01 Jan 1970 00:00:00 UTC;';
});
location.reload();
}
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}
function setCookie(token) {
//console.log(token);
document.cookie = "Token=" + token
}
-615
View File
@@ -1,615 +0,0 @@
var SERVER = new Object();
var BULK_EDIT = false;
var COLUMN_TO_SORT;
var SEARCH_MAPPING = new Object();
var UNDO = new Object();
var SERVER_CONNECTION = false;
var WS_AVAILABLE = false;
var ACTIVE_MENU_ID = "";
var LAST_FOCUSED_ELEMENT = null;
var LAST_BULK_CHECKBOX = null;
// Menu
var menuItems = new Array();
menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}"));
//menuItems.push(new MainMenuItem("pmsID", "{{.mainMenu.item.pmsID}}", "number.png", "{{.mainMenu.headline.pmsID}}"))
menuItems.push(new MainMenuItem("filter", "{{.mainMenu.item.filter}}", "filter.png", "{{.mainMenu.headline.filter}}"));
menuItems.push(new MainMenuItem("xmltv", "{{.mainMenu.item.xmltv}}", "xmltv.png", "{{.mainMenu.headline.xmltv}}"));
menuItems.push(new MainMenuItem("mapping", "{{.mainMenu.item.mapping}}", "mapping.png", "{{.mainMenu.headline.mapping}}"));
menuItems.push(new MainMenuItem("users", "{{.mainMenu.item.users}}", "users.png", "{{.mainMenu.headline.users}}"));
menuItems.push(new MainMenuItem("settings", "{{.mainMenu.item.settings}}", "settings.png", "{{.mainMenu.headline.settings}}"));
menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.mainMenu.headline.log}}"));
menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}"));
// Kategorien für die Einstellungen
var settingsCategory = new Array();
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "tuner,epgSource,api,use_plexAPI,plex.url,plex.token"));
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.missing.epg.mode,xepg.replace.missing.images"));
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,udpxy,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"));
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"));
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.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;
element.setAttribute("aria-hidden", type == true ? "false" : "true");
if (elmID == "loading" && document.body != null) {
document.body.setAttribute("aria-busy", type == true ? "true" : "false");
}
if (elmID == "popup") {
var popupContent_1 = document.getElementById("popup-custom");
if (type == true) {
LAST_FOCUSED_ELEMENT = document.activeElement;
if (popupContent_1 != null) {
setTimeout(function () {
popupContent_1.focus();
}, 20);
}
}
else {
if (LAST_FOCUSED_ELEMENT != null && LAST_FOCUSED_ELEMENT.focus != undefined) {
setTimeout(function () {
LAST_FOCUSED_ELEMENT.focus();
}, 20);
}
LAST_FOCUSED_ELEMENT = null;
}
}
}
function announceToScreenReader(message) {
if (message == undefined || message.length == 0) {
return;
}
var region = document.getElementById("sr-announcer");
if (region == null) {
return;
}
region.innerText = "";
setTimeout(function () {
region.innerText = message;
}, 20);
}
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;
indicator.setAttribute("aria-label", "Connection status: " + label);
announceToScreenReader("Connection status " + 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");
for (var h = 0; h < tableItems.length; h++) {
if (tableItems[h].getAttribute("role") == "columnheader") {
tableItems[h].setAttribute("aria-sort", "none");
}
}
var sortObj = new Object();
var x, xValue;
var tableHeader;
var sortByString = false;
if (column > 0 && COLUMN_TO_SORT > 0) {
tableItems[COLUMN_TO_SORT].classList.remove("sortThis");
tableItems[COLUMN_TO_SORT].classList.add("pointer");
tableItems[column].classList.remove("pointer");
tableItems[column].classList.add("sortThis");
}
COLUMN_TO_SORT = column;
var mobileSort = document.getElementById("mapping-sort-mobile");
if (mobileSort != null && (column == 1 || column == 3 || column == 4 || column == 5)) {
mobileSort.value = column.toString();
}
if (tableItems[column] != undefined && tableItems[column].getAttribute("role") == "columnheader") {
tableItems[column].setAttribute("aria-sort", "ascending");
}
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 == "" || xValue == NaN) {
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(function (id) {
SEARCH_MAPPING[id] = "";
channelKeys.forEach(function (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];
if (element == undefined) {
continue;
}
switch (element.toLowerCase().includes(searchValue.toLowerCase())) {
case true:
document.getElementById(id).style.display = "";
break;
case false:
document.getElementById(id).style.display = "none";
break;
}
}
announceToScreenReader("Search updated");
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("{{.alert.invalidChannelNumber}}");
return;
}
channels.forEach(function (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(function (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 + "\n{{.confirm.restore}}");
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() {
console.log("TOKEN");
var server = new Server("updateLog");
server.request(new Object());
}
-40
View File
@@ -1,40 +0,0 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var MainMenu = /** @class */ (function () {
function MainMenu() {
this.DocumentID = "main-menu";
this.HTMLTag = "LI";
}
MainMenu.prototype.create = function () {
console.log(this.DocumentID);
};
return MainMenu;
}());
var MainMenuItem = /** @class */ (function (_super) {
__extends(MainMenuItem, _super);
function MainMenuItem() {
return _super !== null && _super.apply(this, arguments) || this;
}
MainMenuItem.prototype.create2 = function () {
var element = document.createElement(this.HTMLTag);
element.innerText = this.Value;
console.log(element);
};
return MainMenuItem;
}(MainMenu));
function pageReady() {
var item = new MainMenuItem();
item.Value = "Test";
item.create2();
}
-294
View File
@@ -1,294 +0,0 @@
var configMenu = new Object();
var wizard = new Array("key", "tuner", "epgSource", "m3u", "complete");
var activeWizard;
var dvrIP
var configMenu_tuner = new Object();
configMenu_tuner["_element"] = "SELECT";
configMenu_tuner["_menuType"] = "singleInput";
configMenu_tuner["_configKey"] = "tuner";
configMenu_tuner["_label"] = "Available tuners";
configMenu_tuner["name"] = "tuner";
configMenu_tuner["id"] = "Tuner";
configMenu_tuner["placeholder"] = "Tuner";
configMenu_tuner["_usage"] = "This setting is only used by Plex and Emby.<br>The number of concurrent streams allowed by the IPTV provider."
var optionValues = new Array();
for (var i = 1; i <= 100; i++) {
optionValues.push(i)
}
configMenu_tuner["_optionValues"] = optionValues;
var configMenu_epg = new Object();
configMenu_epg["_element"] = "SELECT";
configMenu_epg["_menuType"] = "singleInput";
configMenu_epg["_configKey"] = "epgSource";
configMenu_epg["_label"] = "Selection of the EPG source";
configMenu_epg["name"] = "epgSource";
configMenu_epg["id"] = "EPG source";
configMenu_epg["placeholder"] = "EPG source";
configMenu_epg["_optionValues"] = new Array("PMS", "XEPG");
configMenu_epg["_usage"] = "PMS: Use EPG data from Plex or Emby<br>XEPG: Use of external EPG data (XMLTV)<br> Several XMLTV sources possible<br> Allows editing and order channels<br> M3U / XMLTV export (HTTP link for IPTV apps)"
var configMenu_m3u = new Object();
configMenu_m3u["_element"] = "INPUT";
configMenu_m3u["_menuType"] = "inputArray";
configMenu_m3u["_configKey"] = "file";
configMenu_m3u["_label"] = "M3U File: local or remote";
configMenu_m3u["name"] = "file";
configMenu_m3u["id"] = "m3u";
configMenu_m3u["type"] = "text";
configMenu_m3u["placeholder"] = "M3U File";
configMenu_m3u["_usage"] = "Remote playlist: http://your.provider.com/file.m3u<br>Local playlist: /path/to/file.m3u"
configMenu_m3u["value"] = "http://websrv.local:8080/kabel.m3u";
var configMenu_complete = new Object();
configMenu_complete["_element"] = "H2";
configMenu_complete["_menuType"] = "inputArray";
configMenu_complete["_configKey"] = "file";
configMenu_complete["_text"] = "xTeVe was successfully set up";
configMenu_complete["name"] = "complete";
configMenu_complete["id"] = "complete";
configMenu_complete["type"] = "text";
configMenu_complete["class"] = "center";
configMenu["tuner"] = configMenu_tuner;
configMenu["epgSource"] = configMenu_epg;
configMenu["m3u"] = configMenu_m3u;
configMenu["complete"] = configMenu_complete;
function readyForConfiguration() {
var data = new Object();
data["cmd"] = "getServerConfig";
xTeVe(data);
showLoadingScreen(false);
}
function createConfiguration(elm) {
activeWizard = elm;
var item = configMenu[elm];
var div = document.getElementById("content");
div.innerHTML = "";
div.setAttribute("data-configKey", item["_configKey"]);
div.setAttribute("data-menuType", item["_menuType"]);
switch(item.hasOwnProperty("_label")) {
case true:
var newItem = new Object();
newItem["_element"] = "LABEL";
newItem["_text"] = item["_label"];
newItem["for"] = item["id"];
div.appendChild(createElement(newItem));
break
}
switch(item["_element"]) {
case "SELECT":
div.appendChild(createElement(item));
var selectElement = div.getElementsByTagName("SELECT")[0];
var values = item["_optionValues"];
for (var i = 0; i < values.length; i++) {
var newEntry = new Object;
newEntry["_element"] = "OPTION";
newEntry["_text"] = item["id"] + ": " + values[i];
newEntry["value"] = values[i];
selectElement.appendChild(createElement(newEntry));
}
//return
break;
default:
div.appendChild(createElement(item));
break;
}
//alert()
switch(item.hasOwnProperty("_usage")) {
case true:
var usageItem = new Object();
usageItem["_element"] = "PRE"
usageItem["_text"] = item["_usage"];
div.appendChild(createElement(usageItem));
}
if (activeWizard == "complete") {
document.getElementById("next").value = "Finished"
//document.getElementById("next").setAttribute("onclick", "javascript: location.reload();")
}
//div.appendChild(createElement(item));
}
function saveData() {
var div = document.getElementById("content");
var inputs = div.getElementsByTagName("INPUT");
var selects = div.getElementsByTagName("SELECT");
var value;
var data = new Object();
var valueArr = new Array();
var newData = false;
if (activeWizard == "complete") {
data["cmd"] = "wizardCompleted";
showLoadingScreen(true)
xTeVe(data);
return
}
for (var i = 0; i < inputs.length; i++) {
var menuType = inputs[i].parentElement.getAttribute("data-menutype");
if (inputs[i].value != undefined && inputs[i].value != "" ) {
newData = true;
console.log(inputs[i].id)
switch(inputs[i].id) {
case "m3u":
var newPlaylist = new Object();
newPlaylist["file.source"] = inputs[i].value;
//newPlaylist["name"] = inputs[i].value;
newPlaylist["type"] = "m3u";
newPlaylist["new"] = true;
data["files"] = new Object();
data["files"]["m3u"] = new Object();
data["files"]["m3u"]["-"] = newPlaylist;
data["cmd"] = "saveFilesM3U";
xTeVe(data)
return
}
/*
switch(menuType) {
case "singleInput":
data[inputs[i].name] = inputs[i].value; break;
case "inputArray":
valueArr.push(inputs[i].value);
data[inputs[i].name] = valueArr; break
}
*/
} else {
inputs[i].style.borderBottomColor = "red";
return;
}
}
for (var i = 0; i < selects.length; i++) {
var value = selects[i].options[selects[i].selectedIndex].value;
if (isNaN(value) == false) {
value = parseInt(value);
data[selects[i].name] = value;
newData = true;
break;
}
data[selects[i].name] = value;
newData = true;
}
//console.log(data, newData);
if (newData == true) {
config = data
data["cmd"] = "saveConfig";
xTeVe(data);
}
}
function xTeVe(data) {
if (webSockets == false) {
alert("Your browser does not support WebSockets");
return;
}
if (activeWizard == "m3u" || activeWizard == "epgSource") {
showLoadingScreen(true);
}
var protocolWS
switch(window.location.protocol) {
case "http:": protocolWS = "ws://"; break;
case "https:": protocolWS = "wss://"; break;
}
var ws = new WebSocket(protocolWS + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token"));
ws.onopen = function() {
ws.send(JSON.stringify(data));
}
ws.onmessage = function (e) {
var response = JSON.parse(e.data);
if (response.hasOwnProperty("clientInfo")) {
createClintInfo(response["clientInfo"]);
}
if (response.hasOwnProperty("status")) {
if (response["status"] == false) {
document.getElementById("headline").style.borderColor = "red";
showErr(response["err"]);
showLoadingScreen(false)
return
} else {
document.getElementById("err").innerHTML = "";
document.getElementById("headline").style.borderColor = "lawngreen";
}
dvrIP = response["DVR"]
switch(response["configurationWizard"]) {
case true:
if (activeWizard == undefined) {
activeWizard = wizard[0]
}
var n = wizard.indexOf(activeWizard);
n++;
activeWizard = wizard[n]
if (activeWizard == undefined) {
data["cmd"] = "wizardCompleted";
xTeVe(data)
} else {
//console.log(activeWizard);
createConfiguration(activeWizard);
}
break;
}
switch(response["reload"]) {
case true:
setTimeout(function(){
location.reload();
}, 100);
//location.reload();
break;
}
}
setTimeout(function(){ showLoadingScreen(false); }, 300);
}
}
function showErr(elm) {
document.getElementById("err").innerHTML = elm;
}
-182
View File
@@ -1,182 +0,0 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var WizardCategory = /** @class */ (function () {
function WizardCategory() {
this.DocumentID = "content";
}
WizardCategory.prototype.createCategoryHeadline = function (value) {
var element = document.createElement("H4");
element.innerHTML = value;
return element;
};
return WizardCategory;
}());
var WizardItem = /** @class */ (function (_super) {
__extends(WizardItem, _super);
function WizardItem(key, headline) {
var _this = _super.call(this) || this;
_this.headline = headline;
_this.key = key;
return _this;
}
WizardItem.prototype.createWizard = function () {
var headline = this.createCategoryHeadline(this.headline);
var key = this.key;
var content = new PopupContent();
var description;
var wizardField = null;
var doc = document.getElementById(this.DocumentID);
doc.setAttribute("aria-busy", "true");
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);
wizardField = select;
description = "{{.wizard.tuner.description}}";
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);
wizardField = select;
description = "{{.wizard.epgSource.description}}";
break;
case "m3u":
var input = content.createInput("text", key, "");
input.setAttribute("placeholder", "{{.wizard.m3u.placeholder}}");
input.setAttribute("class", "wizard");
input.id = key;
doc.appendChild(input);
wizardField = input;
description = "{{.wizard.m3u.description}}";
break;
case "xmltv":
var input = content.createInput("text", key, "");
input.setAttribute("placeholder", "{{.wizard.xmltv.placeholder}}");
input.setAttribute("class", "wizard");
input.id = key;
doc.appendChild(input);
wizardField = input;
description = "{{.wizard.xmltv.description}}";
break;
default:
console.log(key);
break;
}
var pre = document.createElement("PRE");
pre.id = "wizard-description-" + key;
pre.innerHTML = description;
doc.appendChild(pre);
if (wizardField != null) {
wizardField.setAttribute("aria-label", this.headline);
wizardField.setAttribute("aria-describedby", pre.id);
setTimeout(function () {
wizardField.focus();
}, 20);
}
doc.setAttribute("aria-busy", "false");
if (typeof announceToScreenReader == "function") {
announceToScreenReader(this.headline + " step");
}
console.log(headline, key);
};
return WizardItem;
}(WizardCategory));
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() + ": " + "{{.alert.missingInput}}";
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", "{{.wizard.tuner.title}}"));
configurationWizard.push(new WizardItem("epgSource", "{{.wizard.epgSource.title}}"));
configurationWizard.push(new WizardItem("m3u", "{{.wizard.m3u.title}}"));
configurationWizard.push(new WizardItem("xmltv", "{{.wizard.xmltv.title}}"));
document.addEventListener("DOMContentLoaded", function () {
var container = document.getElementById("content");
if (container == null) {
return;
}
container.addEventListener("keydown", function (event) {
if (event.key != "Enter") {
return;
}
var target = event.target;
if (target == null || target.tagName != "INPUT") {
return;
}
event.preventDefault();
saveWizard();
});
});
-329
View File
@@ -1,329 +0,0 @@
function showConfig(obj) {
config = obj;
//setMenuItem();
createMenu();
//document.getElementById("page").className = "";
}
showMyStreams
function showMyStreams(allStreamsObj) {
var streamTypeKeys = getObjKeys(allStreamsObj)
for (var s = 0; s < streamTypeKeys.length; s++) {
var streamType = streamTypeKeys[s];
var obj = new Object();
obj = allStreamsObj[streamType];
switch(streamType) {
case "activeStreams": activeStreams = obj; break;
}
document.getElementById(streamType).innerHTML = "";
var streamsObj = new Object();
var streamsNames = new Array();
var keys = getObjKeys(obj)
// Create Object (streamsObj) for the streams and sort by name (streamsNames)
for (var i = 0; i < keys.length; i++) {
var name = obj[keys[i]]["name"];
var tmp = new Object();
var streamKey = getObjKeys(obj[keys[i]]);
for (var j = 0; j < streamKey.length; j++) {
tmp[streamKey[j]] = obj[keys[i]][streamKey[j]];
}
streamsObj[name] = tmp;
streamsNames.push(name)
}
streamsNames.sort();
// Create Table for activeStreams
var table = document.getElementById(streamType);
for (var i = 0; i < streamsNames.length; i++) {
var newEntry = new Object();
newEntry["_element"] = "TR";
table.appendChild(createElement(newEntry));
var line = table.lastChild;
var tmp = streamsObj[streamsNames[i]]
var keys = getObjKeys(tmp)
var newKey = new Object()
newKey["_element"] = "TD";
//newKey["_text"] = streamsNames[i];
switch(streamType) {
case "activeStreams": newKey["_text"] = "Channel (+):"; break;
case "inactiveStreams": newKey["_text"] = "Channel (-):"; break;
}
newKey["class"] = "tdKey";
console.log();
var newVal = new Object()
newVal["_element"] = "TD";
newVal["_text"] = streamsNames[i];
newVal["class"] = "tdVal";
//newVal["_text"] = value;
line.appendChild(createElement(newKey));
line.appendChild(createElement(newVal));
}
}
return
}
function showActiveStreams(obj) {
document.getElementById("activeStreams").innerHTML = "";
activeStreams = obj;
var streamsObj = new Object();
var streamsNames = new Array();
var keys = getObjKeys(obj)
// Create Object (streamsObj) for the streams and sort by name (streamsNames)
for (var i = 0; i < keys.length; i++) {
var name = obj[keys[i]]["name"];
var tmp = new Object();
var streamKey = getObjKeys(obj[keys[i]]);
for (var j = 0; j < streamKey.length; j++) {
tmp[streamKey[j]] = obj[keys[i]][streamKey[j]];
}
streamsObj[name] = tmp;
streamsNames.push(name)
}
streamsNames.sort();
// Create Table for activeStreams
var table = document.getElementById("activeStreams");
for (var i = 0; i < streamsNames.length; i++) {
var newEntry = new Object();
newEntry["_element"] = "TR";
table.appendChild(createElement(newEntry));
var line = table.lastChild;
var tmp = streamsObj[streamsNames[i]]
var keys = getObjKeys(tmp)
var newKey = new Object()
newKey["_element"] = "TD";
//newKey["_text"] = streamsNames[i];
newKey["_text"] = "Channel:";
newKey["class"] = "tdKey";
console.log();
var newVal = new Object()
newVal["_element"] = "TD";
newVal["_text"] = streamsNames[i];
newVal["class"] = "tdVal";
//newVal["_text"] = value;
line.appendChild(createElement(newKey));
line.appendChild(createElement(newVal));
}
}
function parseLogs(obj) {
log = obj
var keys = getObjKeys(obj)
var msgType;
for (var i = 0; i < keys.length; i++) {
switch(keys[i]) {
case "warnings": msgType = "warningMsg"; break;
case "errors": msgType = "errorMsg"; break;
}
switch(obj[keys[i]]) {
case 0: msgType = "tdVal"; break;
default: break;
}
if(document.getElementById(keys[i])){
document.getElementById(keys[i]).className = msgType;
}
}
return
}
function cancelData(element) {
createMenu();
}
function saveData(element) {
var data = new Object();
var div = element.parentNode.parentNode;
var inputs = div.getElementsByTagName("INPUT");
var configKey = div.getAttribute("data-configkey");
var menuType = div.getAttribute("data-menutype");
var value;
var valueArr = new Array();
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].type == "text" && inputs[i].value != undefined && inputs[i].value != "" ) {
console.log(inputs[i].value, menuType)
switch(menuType) {
case "inputArray": valueArr.push(inputs[i].value); break;
case "singleInput": value = inputs[i].value; break;
}
}
}
switch(menuType) {
case "inputArray": data[configKey] = valueArr; break;
case "singleInput":
if (isNaN(value) == false) {
value = parseInt(value);
data[configKey] = value;
break;
}
if (value == undefined) {
data["delete"] = configKey;
} else {
data[configKey] = value
}
break;
}
data["cmd"] = "saveConfig";
console.log(data);
xTeVe(data)
}
function xTeVe(data) {
if (webSockets == false) {
alert("Your browser does not support WebSockets");
return;
} else {
if (data["cmd"] != "getLog") {
showLoadingScreen(true)
}
}
delete undo["epgMapping"];
var protocolWS
switch(window.location.protocol) {
case "http:": protocolWS = "ws://"; break;
case "https:": protocolWS = "wss://"; break;
}
var ws = new WebSocket(protocolWS + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token"));
ws.onopen = function() {
console.log(data)
ws.send(JSON.stringify(data));
}
ws.onmessage = function (e) {
var response = JSON.parse(e.data);
console.log(response);
if (response.hasOwnProperty("clientInfo")) {
createClintInfo(response["clientInfo"]);
}
if (response.hasOwnProperty("log")) {
createClintInfo(response["log"]);
}
if (response.hasOwnProperty("status")) {
if (response["status"] == false) {
alert(response["err"])
if(response.hasOwnProperty("reload")) {
location.reload();
}
//checkErr(response)
console.log(response);
updateXteveStatus(response);
setTimeout(function(){ showLoadingScreen(false); }, 300);
return
}
updateXteveStatus(response)
//console.log(data["cmd"]);
switch(data["cmd"]) {
case "saveUserData": createMenu(); break;
case "saveNewUser": createMenu(); break;
case "saveFilesXMLTV": //createMenu(); break;
case "saveFilesM3U": //createMenu(); return; break;
case "saveConfig":
data = new Object();
data["cmd"] = "checkToken";
xTeVe(data);
break;
case "emptyLog": writeLogInDiv(); break;
case "getLog": return; break;
}
}
if (config["files"] == undefined || config["files"].length == 0) {
createMenu();
document.getElementById(10).click()
}
setTimeout(function(){ showLoadingScreen(false); }, 0);
}
}
function updateXteveStatus(response) {
var keys = getObjKeys(response);
//console.log(keys);
for (var i = 0; i < keys.length; i++) {
switch(keys[i]) {
case "alert": alert(response[keys[i]]); break;
case "config": showConfig(response[keys[i]]); break;
case "log": parseLogs(response[keys[i]]); break;
case "myStreams": showMyStreams(response[keys[i]]); break;
case "xEPG": xEPG = response[keys[i]]; break;
case "users": users = response[keys[i]]; break;
case "token": document.cookie = "Token=" + response[keys[i]]; break;
case "reload": location.reload(); break;
case "openLink": window.location = response["openLink"]; break;
//case "version": version = response[keys[i]]; break;
}
}
}
function getValueFromProviderFile(xXmltvFile, fileType, key) {
var fileID = xXmltvFile.substring(0, xXmltvFile.lastIndexOf('.'))
if (config["files"][fileType].hasOwnProperty(fileID) == true) {
var data = config["files"][fileType][fileID];
return data[key]
}
}
-379
View File
@@ -1,379 +0,0 @@
function openFiles(elm, fileType) {
//document.getElementById("settings").innerHTML = "Test";
columnToSort = 0;
var newDiv = document.getElementById("settings");
var newEntry = new Object();
newEntry["_element"] = "HR";
newDiv.appendChild(createElement(newEntry));
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
newEntry["class"] = "button";
newEntry["value"] = "New";
newEntry["onclick"] = 'fileDetail("-", "' + fileType + '")';
newDiv.appendChild(createElement(newEntry));
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
newEntry["class"] = "button";
newEntry["value"] = "Update";
newEntry["onclick"] = "fileDetail(0)";
//newDiv.appendChild(createElement(newEntry));
var div = document.getElementById("settings");
// Build table
var newTable = new Object();
newTable["_element"] = "TABLE";
newTable["id"] = "id_mapping";
newTable["class"] = "table-mapping";
div.appendChild(createElement(newTable));
setTimeout(function(){
createFilesTable(fileType);
}, 10);
}
function createFilesTable(fileType) {
var table = document.getElementById("id_mapping");
var availableFileTypes = new Array();
table.innerHTML = "";
var newTR = new Object();
newTR["_element"] = "TR";
newTR["class"] = "table-mapping-header";
table.appendChild(createElement(newTR));
var tr = table.lastChild;
switch(fileType) {
case "xmltv":
availableFileTypes = new Array("xmltv");
var trHeadlines = new Array("Guide", "Last Update", "Availability %", "Channels", "Programs")
var compatibilityKeys = new Array("xmltv.channels", "xmltv.programs")
break;
case "m3u":
availableFileTypes = new Array("m3u", "hdhr");
var trHeadlines = new Array("Playlist", "Last Update", "Availability %", "Type", "Streams", "group-title %", "tvg-id %", "Unique ID %");
var compatibilityKeys = new Array("streams", "group.title", "tvg.id", "stream.id");
break;
}
for (var i = 0; i < trHeadlines.length; i++) {
var newTD = new Object();
newTD["_element"] = "TD";
newTD["_text"] = trHeadlines[i];
tr.appendChild(createElement(newTD));
}
for (var i = 0; i < availableFileTypes.length; i++) {
var fileType = availableFileTypes[i]
var data = config["files"][fileType];
var allFiles = getObjKeys(data)
for (var f = 0; f < allFiles.length; f++) {
var elm = data[allFiles[f]];
var table = document.getElementById("id_mapping");
var fileID = elm["id.provider"];
var name = elm["name"];
var lastUpdate = elm["last.update"];
var availability = elm["provider.availability"];
var type = elm["type"].toUpperCase();
var compatibility = elm["compatibility"];
// Create TR
var newTR = new Object();
newTR["_element"] = "TR";
newTR["class"] = "";
newTR["id"] = fileID;
newTR["onclick"] = 'javascript: fileDetail("' + fileID + '","' + fileType + '");';
table.appendChild(createElement(newTR));
var tr = table.lastChild;
// Create file name TD
var newTD = new Object();
newTD["_element"] = "P";
newTD["_text"] = name;
createNewTD(newTD, tr);
// Create last update TD
var newTD = new Object();
newTD["_element"] = "P";
newTD["_text"] = lastUpdate;
createNewTD(newTD, tr);
// Create availability TD
var newTD = new Object();
newTD["_element"] = "P";
newTD["_text"] = availability;
createNewTD(newTD, tr);
if (fileType == "m3u" || fileType == "hdhr") {
// Create Type TD
var newTD = new Object();
newTD["_element"] = "P";
newTD["_text"] = type;
createNewTD(newTD, tr);
}
// Create all compatibility TDs
for (var j = 0; j < compatibilityKeys.length; j++) {
var newTD = new Object();
newTD["_element"] = "P";
newTD["_text"] = compatibility[compatibilityKeys[j]];
createNewTD(newTD, tr);
}
}
}
sortTable(0)
// usage Info
var div = document.getElementById("settings");
switch(menu[activeMenu.id].hasOwnProperty("_usage")) {
case true:
var usageItem = new Object();
usageItem["_element"] = "PRE"
usageItem["_text"] = menu[activeMenu.id]["_usage"];
var newHR = new Object();
newHR["_element"] = "HR"
div.appendChild(createElement(newHR));
div.appendChild(createElement(usageItem));
break;
}
calculateWrapperHeight();
return;
}
function fileDetail(fileID, fileType) {
optionsText = new Array("M3U", "HDHomeRun - [Experimental]")
optionsValue = new Array("m3u", "hdhr")
switch (fileType) {
case "m3u":
document.getElementById("name").setAttribute("placeholder", "Playlist name");
document.getElementById("description").setAttribute("placeholder", "Description of this playlist");
document.getElementById("file-detail-headline").innerHTML = "M3U Playlist";
document.getElementById("file-path").innerHTML = "M3U File:";
document.getElementById("file.source").setAttribute("placeholder", "Local or remote");
break;
case "hdhr":
document.getElementById("name").setAttribute("placeholder", "HDHomeRun name");
document.getElementById("description").setAttribute("placeholder", "Description of this HDHomeRun tuner");
document.getElementById("file-detail-headline").innerHTML = "HDHomeRun";
document.getElementById("file-path").innerHTML = "HDHomeRun IP:";
document.getElementById("file.source").setAttribute("placeholder", "IP address and port of the tuner (192.168.1.10:5004)");
break;
case "xmltv":
document.getElementById("name").setAttribute("placeholder", "XMLTV name");
document.getElementById("description").setAttribute("placeholder", "Description of this XMLTV file");
document.getElementById("file-detail-headline").innerHTML = "XMLTV File";
document.getElementById("file-path").innerHTML = "XMLTV File:";
document.getElementById("file.source").setAttribute("placeholder", "Local or remote");
optionsText = new Array("XMLTV")
optionsValue = new Array("xmltv")
break;
}
modifyOption("type", optionsText, optionsValue)
showPopUpElement('file-detail');
document.getElementById("saveFileDetail").setAttribute("onclick", 'javascript: saveFileDetail("' + fileID + '","' + fileType + '", false)');
document.getElementById("updateFileDetail").setAttribute("onclick", 'javascript: updateFile("' + fileID + '","' + fileType + '", false)');
document.getElementById("deleteFileDetail").setAttribute("onclick", 'javascript: saveFileDetail("' + fileID + '","' + fileType + '", true)');
var data = new Object();
switch(fileID) {
case "-": // New file
data["name"] = "";
data["description"] = "";
data["file.source"] = "";
data["type"] = fileType;
document.getElementById("deleteFileDetail").className = "delete";
document.getElementById("type").setAttribute("onchange", "changeFileType(this);")
document.getElementById("type").setAttribute("data-id", fileID)
showElement("deleteFileDetail", false);
showElement("updateFileDetail", false);
if (fileType == "xmltv") {
showElement("type", false);
showElement("file-type", false);
} else {
showElement("type", true);
showElement("file-type", true);
}
break;
default:
data = config["files"][fileType][fileID];
document.getElementById("deleteFileDetail").className = "delete";
showElement("updateFileDetail", true);
showElement("type", false);
showElement("file-type", false);
break;
}
var keys = getObjKeys(data);
for (var i = 0; i < keys.length; i++) {
if(document.getElementById(keys[i])){
document.getElementById(keys[i]).value = data[keys[i]];
}
}
}
function changeFileType(elm) {
var fileID = elm.getAttribute("data-id");
var fileType = elm.options[elm.selectedIndex].value;
fileDetail(fileID, fileType)
}
function saveFileDetail(fileID, fileType, deleteFile) {
if (fileID == undefined) {
alert("ID is missing!!!");
return
}
var inputs = document.getElementById("file-detail").getElementsByTagName("INPUT");
var selects = document.getElementById("file-detail").getElementsByTagName("SELECT");
var newFileData = new Object();
var data = new Object();
for (var i = 0; i < inputs.length; i++) {
switch(inputs[i].type) {
case "text": newFileData[inputs[i].name] = inputs[i].value; break;
}
}
for (var i = 0; i < selects.length; i++) {
newFileData[selects[i].id] = selects[i].options[selects[i].selectedIndex].value;
}
if (deleteFile == true) {
switch(fileType) {
case "m3u": var alertText = "Delete this playlist?"; break;
case "hdhr": var alertText = "Delete this HDHomeRun tuner?"; break;
case "xmltv": var alertText = "Delete this XMLTV file?"; break;
}
if (confirm(alertText)) {
newFileData["delete"] = true
data = buildFilesObj(fileType, fileID, newFileData);
console.log(data);
} else {
showElement("popup", false);
return
}
} else {
switch(config["files"][fileType].hasOwnProperty(fileID)) {
case true:
data = config["files"][fileType][fileID];
if (data["file.source"] != newFileData["file.source"]) {
data["update"] = true
} else {
data["updatePlaylistName"] = true;
}
break;
case false:
newFileData["new"] = true;
data = buildFilesObj(fileType, fileID, newFileData);
break
}
}
switch(fileType) {
case "m3u": data["cmd"] = "saveFilesM3U"; break;
case "hdhr": data["cmd"] = "saveFilesHDHR"; break;
case "xmltv": data["cmd"] = "saveFilesXMLTV"; break;
}
//console.log(data);
xTeVe(data);
return
}
function updateFile(fileID, fileType, allFiles) {
switch(config["files"][fileType].hasOwnProperty(fileID)) {
case true:
var data = new Object();
var data = buildFilesObj(fileType, fileID, config["files"][fileType][fileID])
data["new"] = true
switch(fileType) {
case "m3u": data["cmd"] = "updateFileM3U"; break;
case "hdhr": data["cmd"] = "updateFileHDHR"; break;
case "xmltv": data["cmd"] = "updateFileXMLTV"; break;
}
xTeVe(data);
break;
}
}
function buildFilesObj(fileType, fileID, obj) {
var data = new Object();
data["files"] = new Object();
data["files"][fileType] = new Object();
data["files"][fileType][fileID] = obj
return data
}
-114
View File
@@ -1,114 +0,0 @@
var logInterval
function updateLog() {
var data = new Object();
data["cmd"] = "getLog";
xTeVe(data);
writeLogInDiv();
return
}
function writeLogInDiv() {
var logs = log["log"];
var div = document.getElementById("settings").lastChild.lastChild;
div.innerHTML = "";
var max = 50;
for (var i = 0; i < logs.length; i++) {
var newEntry = new Object();
newEntry["_element"] = "P";
if (logs[i].includes("ERROR")) {
// case "warnings": msgType = "warningMsg"; break;
newEntry["class"] = "errorMsg";
}
if (logs[i].includes("WARNING")) {
// case "warnings": msgType = "warningMsg"; break;
newEntry["class"] = "warningMsg";
}
newEntry["_text"] = logs[i];
div.appendChild(createElement(newEntry));
}
calculateWrapperHeight();
var scrollDiv = document.getElementById("box-wrapper");
scrollDiv.scrollTop = scrollDiv.scrollHeight;
}
function showLog(obj) {
//logInterval = setInterval(updateLog, 5000);
var logs = log["log"];
var div = document.getElementById("settings");
var newEntry = new Object();
newEntry["_element"] = "HR";
div.appendChild(createElement(newEntry));
//div = div.lastChild;
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
newEntry["class"] = "button";
newEntry["value"] = "Empty Log";
newEntry["onclick"] = "emptyLog()";
div.appendChild(createElement(newEntry));
var newEntry = new Object();
newEntry["_element"] = "code";
newEntry["_text"] = "Update Log: ";
div.appendChild(createElement(newEntry));
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "checkbox";
//newEntry["checked"] = "checkbox";
newEntry["onclick"] = "logUpdates(this)";
div.appendChild(createElement(newEntry));
var newEntry = new Object();
newEntry["_element"] = "HR";
div.appendChild(createElement(newEntry));
var newWrapper = new Object();
newWrapper["_element"] = "DIV";
newWrapper["id"] = "box-wrapper";
div.appendChild(createElement(newWrapper));
div = div.lastChild;
var newPre = new Object();
newPre["_element"] = "PRE";
newPre["id"] = "logScreen";
div.appendChild(createElement(newPre));
div = div.lastChild;
writeLogInDiv()
return
}
function emptyLog() {
var data = new Object();
data["cmd"] = "emptyLog";
xTeVe(data);
return
}
function logUpdates(elm) {
switch(elm.checked) {
case false: clearInterval(logInterval); break;
case true: logInterval = setInterval(updateLog, 5000); break;
}
}
-49
View File
@@ -1,49 +0,0 @@
var Log = /** @class */ (function () {
function Log() {
}
Log.prototype.createLog = function (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.innerHTML = entry;
return element;
};
return Log;
}());
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(function (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);
}
File diff suppressed because it is too large Load Diff
-754
View File
@@ -1,754 +0,0 @@
function setMenuItem() {
menu = new Object();
subMenu = new Object();
var menu_m3u = new Object();
menu_m3u["_menuType"] = "inputArray";
menu_m3u["_element"] = "LI";
menu_m3u["_configKey"] = "files.m3u";
menu_m3u["_text"] = "Playlist";
menu_m3u["_icon"] = "img/m3u.png";
menu_m3u["_headline"] = "Playlists: Local or remote";
menu_m3u["_usage"] = "<b>Info</b><br>Availability: File availability in percent<br>Streams: Number of streams in the file.<br>group-title: Streams that are assigned to a group. Simplifies filtering streams<br>tvg-id: This ID is used for automatic mapping, must match with the channel ID in the XMLTV file.<br>Unique ID: Streams with a unique ID to identify them. Allows channel name changes in the M3U without losing the XMLTV mapping (PPV / live events).<br><br><b>Usage M3U:</b><br>Remote playlist: http://your.iptv.provider.com/file.m3u<br>Local playlist: /path/to/file.m3u<br><br><b>Usage HDHomeRun:</b><br>IP: 192.168.1.10:5004<br>"
menu_m3u["name"] = "file";
menu_m3u["id"] = "file";
menu_m3u["value"] = menu_m3u["name"];
menu_m3u["placeholder"] = "Playlist: local or remote";
menu_m3u["onclick"] = "javascript: toggleMenu(this);";
menu_m3u["class"] = "menu-notActive";
var menu_filter = new Object();
menu_filter["_menuType"] = "inputArray";
menu_filter["_element"] = "LI";
menu_filter["_configKey"] = "filter";
menu_filter["_text"] = "Filter";
menu_filter["_icon"] = "img/filter.png";
menu_filter["_headline"] = "Filter by M3U parameters, e.g. group-title";
menu_filter["_usage"] = "<b>Usage:</b><br>Sport - All sports channels<br>Sport {HD} - All HD sports channels<br>Sport {HD} !{ES,DE} - All HD sports channels, but no Spanish and German<br><br>To filter the streams of a HDHomeRun, the playlist name can be entered:<br>My tuner {HD}"
//menu_filter["_usage"] = "<b>Usage:</b><br>All sports channels: Sport<br>All HD sports channels: Sport {HD}<br>All HD sports channels, but no Spanish and German: Sport {HD} !{ES,DE}"
menu_filter["name"] = "filter";
menu_filter["id"] = "M3U";
menu_filter["value"] = menu_filter["name"];
menu_filter["placeholder"] = "Filter streams: Sport";
menu_filter["onclick"] = "javascript: toggleMenu(this);";
menu_filter["class"] = "menu-notActive";
var menu_id = new Object();
menu_id["_menuType"] = "inputArray";
menu_id["_element"] = "LI";
menu_id["_configKey"] = "id";
menu_id["_text"] = "PMS ID";
menu_id["_icon"] = "img/number.png";
menu_id["_headline"] = "Setup PMS guide number";
menu_id["_usage"] = 'Some playlists have unique channel IDs.<br>Enter the keyword of the ID. The channel assignment in PMS will change as a result.<br><br>e.g. channelID<br>#EXTINF:0 type="stream" <b>channelId</b>="81", My Streaming Channel HD<br><br>Only enter here if you know what you are doing!'
menu_id["name"] = "id";
menu_id["id"] = "id";
menu_id["value"] = menu_id["name"];
menu_id["placeholder"] = "Unique ID from the M3U file";
menu_id["onclick"] = "javascript: toggleMenu(this);";
menu_id["class"] = "menu-notActive";
var menu_xmltv = new Object();
menu_xmltv["_menuType"] = "inputArray";
menu_xmltv["_element"] = "LI";
menu_xmltv["_configKey"] = "files.xmltv";
menu_xmltv["_text"] = "XMLTV";
menu_xmltv["_icon"] = "img/xmltv.png";
menu_xmltv["_headline"] = "XMLTV files: Local or remote";
menu_xmltv["_usage"] = "<b>Info:</b><br>Availability: File availability in percent<br>Channels: Number of channels in the file<br>Programs: Number of EPG data<br><br><b>Usage:</b><br>Remote XMLTV file: http://your.epg.provider.com/guide.xml<br>Local XMLTV file: /path/to/guide.xml"
menu_xmltv["name"] = "xmltv";
menu_xmltv["id"] = "xmltv";
menu_xmltv["value"] = menu_xmltv["name"];
menu_xmltv["placeholder"] = "XMLTV File: local or remote";
menu_xmltv["onclick"] = "javascript: toggleMenu(this);";
menu_xmltv["class"] = "menu-notActive";
menu_mapping = new Object();
menu_mapping["_element"] = "LI";
menu_mapping["_text"] = "Mapping";
menu_mapping["_icon"] = "img/mapping.png";
menu_mapping["_configKey"] = "mapping";
menu_mapping["_headline"] = "XMLTV assignment and sorting of channels";
menu_mapping["id"] = "mapping";
menu_mapping["onclick"] = "javascript: toggleMenu(this);";
menu_mapping["class"] = "menu-notActive phone";
menu_users = new Object();
menu_users["_element"] = "LI";
menu_users["_text"] = "Users";
menu_users["_icon"] = "img/users.png";
menu_users["_configKey"] = "users";
menu_users["_headline"] = "Administration of users and permissions";
menu_users["id"] = "users";
menu_users["onclick"] = "javascript: toggleMenu(this);";
menu_users["class"] = "menu-notActive";
menu_users["_usage"] = "<b>Authorization groups:</b><br>WEB: Users can log in to the web interface<br>PMS: Programs like Plex can access the channel list. Login via DVR IP: username:password@xteve.ip:port<br>M3U: Allows clients to download the M3U playlist.<br>XML: Allows clients to download the XMLTV file.<br>API: Allows clients to use the API interface.<br><br>!!! For PMS authentication, only the following special characters are valid: !$()=.,-:;<br><br>The individual authentication groups can be activated / deactivated in the settings menu."
menu_settings = new Object();
menu_settings["_element"] = "LI";
menu_settings["_text"] = "Settings";
menu_settings["_icon"] = "img/settings.png";
menu_settings["_configKey"] = "settings";
menu_settings["_headline"] = "Settings";
menu_settings["_subMenu"] = "701,702,703,704,705,706,707,708,799,710,711,712,713,714";
menu_settings["id"] = "settings";
menu_settings["onclick"] = "javascript: toggleMenu(this);";
menu_settings["class"] = "menu-notActive";
menu_log = new Object();
menu_log["_element"] = "LI";
menu_log["_text"] = "Log";
menu_log["_icon"] = "img/log.png";
menu_log["_headline"] = "Log";
menu_log["_configKey"] = "log";
menu_log["id"] = "log";
menu_log["onclick"] = "javascript: toggleMenu(this);";
menu_log["class"] = "menu-notActive";
menu_logout = new Object();
menu_logout["_element"] = "LI";
menu_logout["_text"] = "Logout";
menu_logout["_icon"] = "img/logout.png";
menu_logout["id"] = "logout";
menu_logout["onclick"] = "javascript: logout();";
menu_logout["class"] = "menu-notActive";
var menu_schedule = new Object();
menu_schedule["_menuType"] = "inputArray";
menu_schedule["_element"] = "LI";
menu_schedule["_configKey"] = "update";
menu_schedule["_text"] = "Schedule";
menu_schedule["_icon"] = "img/schedule.png";
menu_schedule["_headline"] = "Schedule for updating M3U, XMLTV files and creating a local backup";
menu_schedule["_usage"] = "<b>Usage:</b><br>0815 = 8:15 am<br>1930 = 7:30 pm"
menu_schedule["name"] = "update";
menu_schedule["id"] = "update";
menu_schedule["value"] = menu_id["name"];
menu_schedule["placeholder"]= "time of day (24-hour clock)";
menu_schedule["onclick"] = "javascript: toggleMenu(this);";
menu_schedule["class"] = "menu-notActive";
var menu_filesUpdate = new Object();
menu_filesUpdate["_element"] = "LI";
menu_filesUpdate["_menuType"] = "checkbox";
menu_filesUpdate["_configKey"] = "files.update";
menu_filesUpdate["_label"] = "Update the provider files at system startup";
menu_filesUpdate["_headline"] = "Update the provider files at system startup";
menu_filesUpdate["_usage"] = "Playlists and XMLTV files are updated by xTeVe at system startup."
menu_filesUpdate["name"] = "files.update";
menu_filesUpdate["id"] = "files.update";
menu_filesUpdate["value"] = menu_filesUpdate["name"];
menu_filesUpdate["onclick"] = "javascript: toggleMenu(this);";
menu_filesUpdate["class"] = "menu-notActive";
var menu_tuner = new Object();
menu_tuner["_element"] = "LI";
menu_tuner["_menuType"] = "select";
menu_tuner["_configKey"] = "tuner";
menu_tuner["_label"] = "Available tuners";
menu_tuner["_text"] = "Tuner";
menu_tuner["_icon"] = "img/tuner.png";
menu_tuner["_headline"] = "Number of tuners";
menu_tuner["_usage"] = "This setting is only used by Plex and Emby.<br>The number of concurrent streams allowed by the IPTV provider.<br>After a change, xTeVe must be delete in the PMS DVR settings and set up again."
menu_tuner["name"] = "tuner";
menu_tuner["id"] = "tuner";
menu_tuner["value"] = menu_tuner["name"];
menu_tuner["placeholder"] = "Number of tuners";
menu_tuner["onclick"] = "javascript: toggleMenu(this);";
menu_tuner["class"] = "menu-notActive";
var optionValues = new Array();
for (var i = 1; i <= 100; i++) {
optionValues.push(i)
}
menu_tuner["_optionValues"] = optionValues;
var menu_epg = new Object();
menu_epg["_element"] = "LI";
menu_epg["_menuType"] = "select";
menu_epg["_configKey"] = "epgSource";
menu_epg["_label"] = "Selection of the EPG source";
menu_epg["_text"] = "EPG source";
menu_epg["_headline"] = "Selection of the EPG source";
menu_epg["_usage"] = "PMS: Use EPG data from Plex or Emby.<br>XEPG: Use of external EPG data (XMLTV).<br> Several XMLTV sources possible.<br> Allows editing and order channels.<br> M3U / XMLTV export (HTTP link for IPTV apps)."
menu_epg["name"] = "epgSource";
menu_epg["id"] = "epgSource";
menu_epg["value"] = menu_epg["name"];
menu_epg["placeholder"] = "EPG source";
menu_epg["onclick"] = "javascript: toggleMenu(this);";
menu_epg["class"] = "menu-notActive";
menu_epg["_optionValues"] = new Array("PMS", "XEPG");
var menu_xepg = new Object();
menu_xepg["_element"] = "LI";
menu_xepg["_menuType"] = "checkbox";
menu_xepg["_configKey"] = "xteveAutoUpdate";
menu_xepg["_label"] = "Automatic update of xTeVe";
menu_xepg["_headline"] = "Automatic update of xTeVe";
menu_xepg["_usage"] = "If a new version of xTeVe is available, it will be automatically installed."
menu_xepg["name"] = "xteveAutoUpdate";
menu_xepg["id"] = "xteveAutoUpdate";
menu_xepg["value"] = menu_xepg["name"];
menu_xepg["onclick"] = "javascript: toggleMenu(this);";
menu_xepg["class"] = "menu-notActive";
var menu_autoBackupPath = new Object();
menu_autoBackupPath["_element"] = "LI";
menu_autoBackupPath["_menuType"] = "singleInput";
menu_autoBackupPath["_configKey"] = "backup.path";
menu_autoBackupPath["_label"] = "Location for automatic backups";
menu_autoBackupPath["_headline"] = "Location for automatic backups";
menu_autoBackupPath["_usage"] = "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."
menu_autoBackupPath["name"] = "backup.path";
menu_autoBackupPath["id"] = "backup.path";
menu_autoBackupPath["value"] = menu_autoBackupPath["name"];
menu_autoBackupPath["onclick"] = "javascript: toggleMenu(this);";
menu_autoBackupPath["class"] = "menu-notActive";
var menu_autoBackupKeep = new Object();
menu_autoBackupKeep["_element"] = "LI";
menu_autoBackupKeep["_menuType"] = "select";
menu_autoBackupKeep["_configKey"] = "backup.keep";
menu_autoBackupKeep["_text"] = "Keep";
menu_autoBackupKeep["_label"] = "Number of backups to keep";
menu_autoBackupKeep["_headline"] = "Number of backups to keep";
menu_autoBackupKeep["_usage"] = ""
menu_autoBackupKeep["name"] = "backup.keep";
menu_autoBackupKeep["id"] = "backup.keep";
menu_autoBackupKeep["value"] = menu_autoBackupKeep["name"];
menu_autoBackupKeep["onclick"] = "javascript: toggleMenu(this);";
menu_autoBackupKeep["class"] = "menu-notActive";
var optionValues = new Array(5, 10, 20, 30, 40, 50);
menu_autoBackupKeep["_optionValues"] = optionValues;
var menu_buffer = new Object();
menu_buffer["_element"] = "LI";
menu_buffer["_menuType"] = "checkbox";
menu_buffer["_configKey"] = "buffer";
menu_buffer["_label"] = "Stream buffering [Experimental]";
menu_buffer["_headline"] = "Stream buffering [Experimental]";
menu_buffer["_usage"] = "With activated buffer, streams can be played and recorded more fluently.<br>The stream is passed from xTeVe to Plex / Emby"
menu_buffer["name"] = "buffer";
menu_buffer["id"] = "buffer";
menu_buffer["value"] = menu_buffer["name"];
menu_buffer["onclick"] = "javascript: toggleMenu(this);";
menu_buffer["class"] = "menu-notActive";
var menu_api = new Object();
menu_api["_element"] = "LI";
menu_api["_menuType"] = "checkbox";
menu_api["_configKey"] = "api";
menu_api["_label"] = "API interface";
menu_api["_headline"] = "API interface";
menu_api["_usage"] = 'Via API interface it is possible to send commands to xTeVe. API documentation is available <a href="https://xteve.de?scroll=api">here</a> '
//menu_api["_usage"] = 'Via API interface it is possible to send commands to xTeVe. API documentation is available <a href="http://localhost:1313?scroll=api">here</a> '
menu_api["name"] = "api";
menu_api["id"] = "api";
menu_api["value"] = menu_api["name"];
menu_api["onclick"] = "javascript: toggleMenu(this);";
menu_api["class"] = "menu-notActive";
var menu_authenticationWeb = new Object();
menu_authenticationWeb["_element"] = "LI";
menu_authenticationWeb["_menuType"] = "checkbox";
menu_authenticationWeb["_configKey"] = "authentication.web";
menu_authenticationWeb["_label"] = "User authentication";
menu_authenticationWeb["_headline"] = "User authentication";
menu_authenticationWeb["_usage"] = "Access to xTeVe requires authentication."
menu_authenticationWeb["name"] = "authentication.web";
menu_authenticationWeb["id"] = "authentication.web";
menu_authenticationWeb["value"] = menu_authenticationWeb["name"];
menu_authenticationWeb["onclick"] = "javascript: toggleMenu(this);";
menu_authenticationWeb["class"] = "menu-notActive";
var menu_authenticationPms = new Object();
menu_authenticationPms["_element"] = "LI";
menu_authenticationPms["_menuType"] = "checkbox";
menu_authenticationPms["_configKey"] = "authentication.pms";
menu_authenticationPms["_label"] = "Plex authentication.";
menu_authenticationPms["_headline"] = "Plex authentication.";
menu_authenticationPms["_usage"] = "Plex requests are only possible with authentication.<br>Warning!!! After activating this function xTeVe must be delete in the PMS DVR settings and set up again."
menu_authenticationPms["name"] = "authentication.pms";
menu_authenticationPms["id"] = "authentication.pms";
menu_authenticationPms["value"] = menu_authenticationPms["name"];
menu_authenticationPms["onclick"] = "javascript: toggleMenu(this);";
menu_authenticationPms["class"] = "menu-notActive";
var menu_authenticationM3u = new Object();
menu_authenticationM3u["_element"] = "LI";
menu_authenticationM3u["_menuType"] = "checkbox";
menu_authenticationM3u["_configKey"] = "authentication.m3u";
menu_authenticationM3u["_label"] = "M3U authentication.";
menu_authenticationM3u["_headline"] = "M3U authentication.";
menu_authenticationM3u["_usage"] = "Downloading the M3U file via an HTTP request is only possible with authentication."
menu_authenticationM3u["name"] = "authentication.m3u";
menu_authenticationM3u["id"] = "authentication.m3u";
menu_authenticationM3u["value"] = menu_authenticationM3u["name"];
menu_authenticationM3u["onclick"] = "javascript: toggleMenu(this);";
menu_authenticationM3u["class"] = "menu-notActive";
var menu_authenticationXml = new Object();
menu_authenticationXml["_element"] = "LI";
menu_authenticationXml["_menuType"] = "checkbox";
menu_authenticationXml["_configKey"] = "authentication.xml";
menu_authenticationXml["_label"] = "XEPG authentication";
menu_authenticationXml["_headline"] = "XEPG authentication";
menu_authenticationXml["_usage"] = "Downloading the XEPG (XMLTV) file via an HTTP request is only possible with authentication."
menu_authenticationXml["name"] = "authentication.xml";
menu_authenticationXml["id"] = "authentication.xml";
menu_authenticationXml["value"] = menu_authenticationXml["name"];
menu_authenticationXml["onclick"] = "javascript: toggleMenu(this);";
menu_authenticationXml["class"] = "menu-notActive";
var menu_authenticationApi = new Object();
menu_authenticationApi["_element"] = "LI";
menu_authenticationApi["_menuType"] = "checkbox";
menu_authenticationApi["_configKey"] = "authentication.api";
menu_authenticationApi["_label"] = "API authentication";
menu_authenticationApi["_headline"] = "API authentication";
menu_authenticationApi["_usage"] = "Access to the API interface is only possible with authentication."
menu_authenticationApi["name"] = "authentication.api";
menu_authenticationApi["id"] = "authentication.api";
menu_authenticationApi["value"] = menu_authenticationApi["name"];
menu_authenticationApi["onclick"] = "javascript: toggleMenu(this);";
menu_authenticationApi["class"] = "menu-notActive";
// Main menu
menu[10] = menu_m3u;
switch(config["epgSource"]) {
case "PMS":
menu[20] = menu_id;
break;
case "XMLTV":
menu[40] = menu_xmltv;
break;
case "XEPG":
menu[40] = menu_xmltv;
menu[50] = menu_mapping;
break;
}
menu[30] = menu_filter;
if (config["authentication.web"] == true) {
menu[60] = menu_users;
}
menu[70] = menu_settings;
menu[80] = menu_log;
if (config["authentication.web"] == true) {
menu[100] = menu_logout;
}
// Sub-Menu
subMenu[701] = menu_schedule;
subMenu[702] = menu_filesUpdate;
subMenu[703] = menu_tuner;
subMenu[704] = menu_epg;
subMenu[705] = menu_xepg;
subMenu[706] = menu_autoBackupPath;
subMenu[707] = menu_autoBackupKeep;
subMenu[708] = menu_buffer;
subMenu[710] = menu_authenticationWeb;
if (config["authentication.web"] == true) {
subMenu[711] = menu_authenticationPms;
subMenu[712] = menu_authenticationM3u;
subMenu[713] = menu_authenticationXml;
subMenu[714] = menu_authenticationApi;
}
subMenu[799] = menu_api;
return
}
function createMenu() {
showElement("popup", false);
//console.log(config);
setMenuItem();
var menuItems = getObjKeys(menu)
var nav = document.getElementsByTagName("NAV")[0];
nav.innerHTML = "";
var newItem = new Object();
for (var i = 0; i < menuItems.length; i++) {
var newItem = menu[menuItems[i]];
newItem["id"] = menuItems[i];
switch(newItem.hasOwnProperty("_icon")) {
case true:
var itemText = newItem["_text"];
delete newItem["_text"]
nav.appendChild(createElement(newItem));
newItem["_text"] = itemText;
var newIcon = new Object();
newIcon["_element"] = "IMG";
newIcon["src"] = newItem["_icon"];
var currentElement = document.getElementById(menuItems[i]);
currentElement.appendChild(createElement(newIcon));
var text = new Object();
text["_element"] = "P"
text["_text"] = itemText;
text["class"] = "nav-text"
currentElement.appendChild(createElement(text));
break;
default:
nav.appendChild(createElement(newIcon));
break;
}
}
if (activeMenu != undefined) {
//console.log(activeMenu);
toggleMenu(activeMenu);
}
return
}
function toggleMenu(elm) {
//showStreams(false);
clearInterval(logInterval)
activeMenu = elm;
var item = menu[elm.id]
var div = document.getElementById("settings");
div.innerHTML = "";
// Set Headline
var headline = new Object();
headline["_element"] = "H4";
headline["_text"] = item["_headline"];
div.appendChild(createElement(headline));
// Sub-Menu
if (item.hasOwnProperty("_subMenu") == true) {
openSubMenu(item);
return
}
// Mapping, Users, Log, Files
switch(item["_configKey"]) {
case "mapping": openMappingEditor(item); return; break;
case "users": openUsers(item); return; break;
case "log": showLog(item); return; break;
case "files.m3u": openFiles(item, "m3u"); return; break;
case "files.xmltv": openFiles(item, "xmltv"); return; break;
case "filter": showStreams(true); break;
}
var newHR = new Object();
newHR["_element"] = "HR"
div.appendChild(createElement(newHR));
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
//newEntry["class"] = "save";
newEntry["value"] = "Save";
newEntry["onclick"] = "saveData2('settings')"
div.appendChild(createElement(newEntry));
var newWrapper = new Object();
newWrapper["_element"] = "DIV";
newWrapper["id"] = "box-wrapper";
div.appendChild(createElement(newWrapper));
div = div.lastChild;
div.appendChild(createMenuItem(item))
// usage Info
switch(menu[activeMenu.id].hasOwnProperty("_usage")) {
case true:
var usageItem = new Object();
usageItem["_element"] = "PRE"
usageItem["_text"] = menu[activeMenu.id]["_usage"];
div.appendChild(createElement(usageItem));
}
calculateWrapperHeight();
}
function createMenuItem(item) {
var element = document.createElement("DIV");
switch(item["_menuType"]) {
case "inputArray":
if (config.hasOwnProperty(item["_configKey"]) == true) {
var value = config[item["_configKey"]];
} else {
var value = new Array();
}
for (var i = 0; i < value.length; i++) {
var newEntry = new Object();
newEntry = item
delete newEntry["onclick"];
newEntry["_element"] = "INPUT";
newEntry["value"] = value[i];
newEntry["type"] = "search";
newEntry["data-menutype"] = item["_menuType"];
newEntry["data-menukey"] = item["_configKey"];
element.appendChild(createElement(newEntry));
}
// New entry for array
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "search";
newEntry["name"] = item["name"];
newEntry["placeholder"] = item["placeholder"];
newEntry["value"] = "";
newEntry["data-menutype"] = item["_menuType"];
newEntry["data-menukey"] = item["_configKey"];
element.appendChild(createElement(newEntry));
break;
case "singleInput":
var value = config[item["_configKey"]];
if (value == undefined) {
value = "";
}
var newEntry = new Object();
newEntry = item;
delete newEntry["onclick"];
newEntry["_element"] = "INPUT";
newEntry["value"] = value;
newEntry["type"] = "search";
newEntry["data-menutype"] = item["_menuType"];
newEntry["data-menukey"] = item["_configKey"];
element.appendChild(createElement(newEntry));
break;
case "checkbox":
var value = config[item["_configKey"]];
if (value == undefined) {
value = false;
}
var newEntry = new Object();
newEntry = item;
delete newEntry["onclick"];
newEntry["_element"] = "INPUT";
newEntry["value"] = value;
newEntry["type"] = "checkbox";
newEntry["data-menutype"] = item["_menuType"];
newEntry["data-menukey"] = item["_configKey"];
element.appendChild(createElement(newEntry));
element.getElementsByTagName("INPUT")[0].checked = value;
break;
case "select":
var value = config[item["_configKey"]];
var newEntry = new Object();
newEntry = item;
delete newEntry["onclick"]
newEntry["_element"] = "SELECT";
element.appendChild(createElement(newEntry));
var selectElement = element.getElementsByTagName("SELECT")[0];
var values = item["_optionValues"];
for (var i = 0; i < values.length; i++) {
var newEntry = new Object;
newEntry["_element"] = "OPTION";
newEntry["_text"] = item["_text"] + ": " + values[i];
newEntry["value"] = values[i];
selectElement.appendChild(createElement(newEntry));
}
selectElement.value = value;
break;
}
return element;
}
function openSubMenu(item) {
var entrys = item["_subMenu"].split(",");
var div = document.getElementById("settings");
var newHR = new Object();
newHR["_element"] = "HR"
div.appendChild(createElement(newHR));
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
//newEntry["class"] = "save";
newEntry["value"] = "Save";
newEntry["onclick"] = "saveData2('settings')"
div.appendChild(createElement(newEntry));
if (item["_configKey"] == "settings") {
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
//newEntry["class"] = "save";
newEntry["value"] = "Backup";
newEntry["onclick"] = "xteveBackup()"
div.appendChild(createElement(newEntry));
}
if (item["_configKey"] == "settings") {
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
//newEntry["class"] = "save";
newEntry["value"] = "Restore";
newEntry["onclick"] = "xteveRestore(this)"
div.appendChild(createElement(newEntry));
}
var newWrapper = new Object();
newWrapper["_element"] = "DIV";
newWrapper["id"] = "box-wrapper";
div.appendChild(createElement(newWrapper));
div = div.lastChild;
for (var i = 0; i < entrys.length; i++) {
var item = subMenu[entrys[i]];
if (item == undefined) {
break;
}
var container = new Object();
container["_element"] = "DIV";
div.appendChild(createElement(container));
var divContainer = div.lastChild;
var headline = new Object();
headline["_element"] = "H5";
headline["_text"] = item["_headline"];
divContainer.appendChild(createElement(headline));
divContainer.appendChild(createMenuItem(item))
switch(item.hasOwnProperty("_usage")) {
case true:
var usageItem = new Object();
usageItem["_element"] = "PRE"
usageItem["_text"] = item["_usage"];
divContainer.appendChild(createElement(usageItem));
}
var hr = new Object();
hr["_element"] = "HR";
divContainer.appendChild(createElement(hr));
}
calculateWrapperHeight();
return
}
function saveData2(elm) {
var div = document.getElementById(elm);
var inputs = div.getElementsByTagName("INPUT");
var selects = div.getElementsByTagName("SELECT");
var value, configKey;
var data = new Object();
var valueArr = new Array();
var newData = false;
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].type != "button") {
var menuType = inputs[i].getAttribute("data-menutype");
//console.log(menuType);
switch(menuType) {
case "singleInput":
value = inputs[i].value;
if (value == "" || value == undefined) {
data = new Object();
data["delete"] = inputs[i].name
newData = true;
} else {
newData = true;
data[inputs[i].name] = value;
console.log(data);
}
break;
case "inputArray":
value = inputs[i].value;
if (value != "" && value != undefined) {
newData = true;
valueArr.push(value)
data[inputs[i].name] = valueArr;
configKey = inputs[i].name;
}
break;
case "checkbox":
value = inputs[i].checked
data[inputs[i].name] = value;
}
}
}
// Delete config key
if (valueArr.length == 0 && newData == false) {
newData = true;
data = new Object();
data["delete"] = configKey;
}
for (var i = 0; i < selects.length; i++) {
var value = selects[i].options[selects[i].selectedIndex].value;
switch(isNaN(value)) {
case false: value = parseInt(value); break;
}
data[selects[i].name] = value;
newData = true;
}
//console.log(data, newData);
if (newData == true) {
data["cmd"] = "saveConfig";
if (!data.hasOwnProperty('filter')) {
data["filter"] = config["filter"]
}
var settings = new Object();
settings["cmd"] = data["cmd"];
settings["settings"] = data;
console.log(settings);
xTeVe(settings);
}
}
-172
View File
@@ -1,172 +0,0 @@
var Server = /** @class */ (function () {
function Server(cmd) {
this.cmd = cmd;
}
Server.prototype.request = function (data) {
if (SERVER_CONNECTION == true) {
return;
}
SERVER_CONNECTION = true;
console.log(data);
if (this.cmd != "updateLog") {
showElement("loading", true);
UNDO = new Object();
setConnectionState("busy");
}
switch (window.location.protocol) {
case "http:":
this.protocol = "ws://";
break;
case "https:":
this.protocol = "wss://";
break;
}
var wsHost = window.location.host;
if (wsHost == undefined || wsHost.length < 1) {
wsHost = window.location.hostname;
}
var url = this.protocol + wsHost + "/data/" + "?Token=" + getCookie("Token");
data["cmd"] = this.cmd;
var requestCmd = data["cmd"];
var ws = new WebSocket(url);
var isLogUpdate = data["cmd"] == "updateLog";
var responseReceived = false;
var requestFinished = false;
var timeoutMs = 12000;
var requestTimeout;
var finishRequest = function (state, responseSuccess) {
if (responseSuccess === void 0) { responseSuccess = false; }
if (requestFinished == true) {
return;
}
requestFinished = true;
SERVER_CONNECTION = false;
window.clearTimeout(requestTimeout);
if (responseSuccess == true) {
if (state == "online") {
WS_FAILURE_COUNT = 0;
}
}
else {
WS_FAILURE_COUNT++;
}
if (isLogUpdate == false) {
showElement("loading", false);
}
if (state != "") {
setConnectionState(state);
}
};
requestTimeout = window.setTimeout(function () {
console.log("Websocket request timed out.");
var timeoutState = "offline";
if (isLogUpdate == true && WS_FAILURE_COUNT < 2) {
timeoutState = "idle";
}
finishRequest(timeoutState, false);
try {
ws.close();
}
catch (err) {
console.log(err);
}
}, timeoutMs);
ws.onopen = function () {
WS_AVAILABLE = true;
if (data["cmd"] != "updateLog") {
setConnectionState("busy");
}
console.log("REQUEST (JS):");
console.log(data);
console.log("REQUEST: (JSON)");
console.log(JSON.stringify(data));
this.send(JSON.stringify(data));
};
ws.onerror = function (e) {
console.log("No websocket connection to xTeVe could be established. Check your network configuration.");
var errorState = "offline";
if (isLogUpdate == true && WS_FAILURE_COUNT < 2) {
errorState = "idle";
}
finishRequest(errorState, false);
if (WS_AVAILABLE == false && isLogUpdate == false && requestCmd != "getServerConfig") {
alert("No websocket connection to xTeVe could be established. Check your network configuration.");
}
};
ws.onmessage = function (e) {
responseReceived = true;
finishRequest("online", true);
console.log("RESPONSE:");
var response = JSON.parse(e.data);
console.log(response);
if (response.hasOwnProperty("token")) {
document.cookie = "Token=" + response["token"];
}
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 (data["cmd"]) {
case "updateLog":
SERVER["log"] = response["log"];
if (document.getElementById("content_log")) {
showLogs(false);
}
return;
break;
default:
SERVER = new Object();
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();
};
ws.onclose = function () {
if (responseReceived == true) {
return;
}
var closeState = "offline";
if (isLogUpdate == true && WS_FAILURE_COUNT < 2) {
closeState = "idle";
}
finishRequest(closeState, false);
};
};
return Server;
}());
var WS_FAILURE_COUNT = 0;
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2)
return parts.pop().split(";").shift();
}
-557
View File
@@ -1,557 +0,0 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var SettingsCategory = /** @class */ (function () {
function SettingsCategory() {
this.DocumentID = "content_settings";
}
SettingsCategory.prototype.createCategoryHeadline = function (value) {
var element = document.createElement("H4");
element.innerHTML = value;
return element;
};
SettingsCategory.prototype.createHR = function () {
var element = document.createElement("HR");
return element;
};
SettingsCategory.prototype.createSettings = function (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 = "{{.settings.update.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "update", data.toString());
input.setAttribute("placeholder", "{{.settings.update.placeholder}}");
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 = "{{.settings.backupPath.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "backup.path", data);
input.setAttribute("placeholder", "{{.settings.backupPath.placeholder}}");
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 = "{{.settings.tempPath.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "temp.path", data);
input.setAttribute("placeholder", "{{.settings.tmpPath.placeholder}}");
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 = "{{.settings.userAgent.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "user.agent", data);
input.setAttribute("placeholder", "{{.settings.userAgent.placeholder}}");
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 = "{{.settings.plexURL.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "plex.url", data);
input.setAttribute("placeholder", "{{.settings.plexURL.placeholder}}");
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 = "{{.settings.plexToken.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("password", "plex.token", data);
input.setAttribute("placeholder", "{{.settings.plexToken.placeholder}}");
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 = "{{.settings.bufferTimeout.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "buffer.timeout", data);
input.setAttribute("placeholder", "{{.settings.bufferTimeout.placeholder}}");
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 = "{{.settings.ffmpegPath.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "ffmpeg.path", data);
input.setAttribute("placeholder", "{{.settings.ffmpegPath.placeholder}}");
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 = "{{.settings.ffmpegOptions.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "ffmpeg.options", data);
input.setAttribute("placeholder", "{{.settings.ffmpegOptions.placeholder}}");
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 = "{{.settings.vlcPath.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "vlc.path", data);
input.setAttribute("placeholder", "{{.settings.vlcPath.placeholder}}");
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 = "{{.settings.vlcOptions.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "vlc.options", data);
input.setAttribute("placeholder", "{{.settings.vlcOptions.placeholder}}");
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 = "{{.settings.authenticationWEB.title}}" + ":";
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 = "{{.settings.authenticationPMS.title}}" + ":";
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 = "{{.settings.authenticationM3U.title}}" + ":";
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 = "{{.settings.authenticationXML.title}}" + ":";
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 = "{{.settings.authenticationAPI.title}}" + ":";
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 = "{{.settings.filesUpdate.title}}" + ":";
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 = "{{.settings.cacheImages.title}}" + ":";
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 = "{{.settings.replaceEmptyImages.title}}" + ":";
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 = "{{.settings.api.title}}" + ":";
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 = "{{.settings.usePlexAPI.title}}" + ":";
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 = "{{.settings.xepgMissingEPGMode.title}}" + ":";
var tdRight = document.createElement("TD");
var text = ["{{.settings.xepgMissingEPGMode.info_strict}}", "{{.settings.xepgMissingEPGMode.info_relaxed}}"];
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 = "{{.settings.tuner.title}}" + ":";
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 = "{{.settings.epgSource.title}}" + ":";
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 = "{{.settings.backupKeep.title}}" + ":";
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 = "{{.settings.bufferSize.title}}" + ":";
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 = "{{.settings.streamBuffering.title}}" + ":";
var tdRight = document.createElement("TD");
var text = ["{{.settings.streamBuffering.info_false}}", "xTeVe: ({{.settings.streamBuffering.info_xteve}})", "FFmpeg: ({{.settings.streamBuffering.info_ffmpeg}})", "VLC: ({{.settings.streamBuffering.info_vlc}})"];
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 = "{{.settings.udpxy.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "udpxy", data);
input.setAttribute("placeholder", "{{.settings.udpxy.placeholder}}");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
}
return setting;
};
SettingsCategory.prototype.createDescription = function (settingsKey) {
var description = document.createElement("TR");
var text;
switch (settingsKey) {
case "authentication.web":
text = "{{.settings.authenticationWEB.description}}";
break;
case "authentication.m3u":
text = "{{.settings.authenticationM3U.description}}";
break;
case "authentication.pms":
text = "{{.settings.authenticationPMS.description}}";
break;
case "authentication.xml":
text = "{{.settings.authenticationXML.description}}";
break;
case "authentication.api":
if (SERVER["settings"]["authentication.web"] == true) {
text = "{{.settings.authenticationAPI.description}}";
}
break;
case "backup.keep":
text = "{{.settings.backupKeep.description}}";
break;
case "backup.path":
text = "{{.settings.backupPath.description}}";
break;
case "temp.path":
text = "{{.settings.tempPath.description}}";
break;
case "buffer":
text = "{{.settings.streamBuffering.description}}";
break;
case "buffer.size.kb":
text = "{{.settings.bufferSize.description}}";
break;
case "buffer.timeout":
text = "{{.settings.bufferTimeout.description}}";
break;
case "user.agent":
text = "{{.settings.userAgent.description}}";
break;
case "plex.url":
text = "{{.settings.plexURL.description}}";
break;
case "plex.token":
text = "{{.settings.plexToken.description}}";
break;
case "ffmpeg.path":
text = "{{.settings.ffmpegPath.description}}";
break;
case "ffmpeg.options":
text = "{{.settings.ffmpegOptions.description}}";
break;
case "vlc.path":
text = "{{.settings.vlcPath.description}}";
break;
case "vlc.options":
text = "{{.settings.vlcOptions.description}}";
break;
case "epgSource":
text = "{{.settings.epgSource.description}}";
break;
case "tuner":
text = "{{.settings.tuner.description}}";
break;
case "update":
text = "{{.settings.update.description}}";
break;
case "api":
text = "{{.settings.api.description}}";
break;
case "use_plexAPI":
text = "{{.settings.usePlexAPI.description}}";
break;
case "files.update":
text = "{{.settings.filesUpdate.description}}";
break;
case "cache.images":
text = "{{.settings.cacheImages.description}}";
break;
case "xepg.replace.missing.images":
text = "{{.settings.replaceEmptyImages.description}}";
break;
case "xepg.missing.epg.mode":
text = "{{.settings.xepgMissingEPGMode.description}}";
break;
case "udpxy":
text = "{{.settings.udpxy.description}}";
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;
};
return SettingsCategory;
}());
var SettingsCategoryItem = /** @class */ (function (_super) {
__extends(SettingsCategoryItem, _super);
function SettingsCategoryItem(headline, settingsKeys) {
var _this = _super.call(this) || this;
_this.headline = headline;
_this.settingsKeys = settingsKeys;
return _this;
}
SettingsCategoryItem.prototype.createCategory = function () {
var _this = this;
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(function (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());
};
return SettingsCategoryItem;
}(SettingsCategory));
function showSettings() {
console.log("SETTINGS");
for (var 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 (var 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);
}
-341
View File
@@ -1,341 +0,0 @@
function openUsers(elm) {
colomnSort = 0;
var newDiv = document.getElementById("settings");
var newEntry = new Object();
newEntry["_element"] = "HR";
newDiv.appendChild(createElement(newEntry));
var newEntry = new Object();
newEntry["_element"] = "INPUT";
newEntry["type"] = "button";
newEntry["class"] = "button";
newEntry["value"] = "New";
newEntry["onclick"] = "userDetail(0)";
newDiv.appendChild(createElement(newEntry));
var div = document.getElementById("settings");
// Build table
var newTable = new Object();
newTable["_element"] = "TABLE";
newTable["id"] = "id_mapping";
newTable["class"] = "table-mapping";
div.appendChild(createElement(newTable));
setTimeout(function(){
createUsersTable();
}, 10);
}
function createUsersTable() {
var table = document.getElementById("id_mapping");
table.innerHTML = "";
var newTR = new Object();
newTR["_element"] = "TR";
newTR["class"] = "table-mapping-header";
table.appendChild(createElement(newTR));
var tr = table.lastChild;
var trHeadlines = new Array("Username", "Password", "WEB", "PMS", "M3U", "XML", "API")
for (var i = 0; i < trHeadlines.length; i++) {
var newTD = new Object();
newTD["_element"] = "TD";
newTD["_text"] = trHeadlines[i];
tr.appendChild(createElement(newTD));
}
// Sort users
var userIds = getObjKeys(users);
var userObj = new Object();
for (var i = 0; i < userIds.length; i++) {
var username = users[userIds[i]]["data"]["username"];
userObj[username] = userIds[i];
}
var allUsers = getObjKeys(userObj);
allUsers.sort();
// --
for (var i = 0; i < allUsers.length; i++) {
var table = document.getElementById("id_mapping");
var userID = userObj[allUsers[i]];
var username = allUsers[i];
var item = users[userID]["data"];
// Create TR
var newTR = new Object();
newTR["_element"] = "TR";
newTR["class"] = "";
newTR["id"] = userID;
newTR["onclick"] = 'javascript: userDetail("' + userID + '");';
table.appendChild(createElement(newTR));
var tr = table.lastChild;
// Create username TD
var newTD = new Object();
newTD["_element"] = "P";
newTD["_text"] = username;
createNewTD(newTD, tr);
// Create password TD
var newTD = new Object();
newTD["_element"] = "P";
newTD["_text"] = ".....";
createNewTD(newTD, tr);
// Create web access
var newTD = new Object();
newTD["_element"] = "P";
switch(item["authentication.web"]){
case true: newTD["_text"] = "✓"; break;
default: newTD["_text"] = "-"; break;
}
createNewTD(newTD, tr);
// Create PMS access
var newTD = new Object();
newTD["_element"] = "P";
switch(item["authentication.pms"]){
case true: newTD["_text"] = "✓"; break;
default: newTD["_text"] = "-"; break;
}
createNewTD(newTD, tr);
// Create M3U access
var newTD = new Object();
newTD["_element"] = "P";
switch(item["authentication.m3u"]){
case true: newTD["_text"] = "✓"; break;
default: newTD["_text"] = "-"; break;
}
createNewTD(newTD, tr);
// Create XMLTV access
var newTD = new Object();
newTD["_element"] = "P";
switch(item["authentication.xml"]){
case true: newTD["_text"] = "✓"; break;
default: newTD["_text"] = "-"; break;
}
createNewTD(newTD, tr);
// Create API access
var newTD = new Object();
newTD["_element"] = "P";
switch(item["authentication.api"]){
case true: newTD["_text"] = "✓"; break;
default: newTD["_text"] = "-"; break;
}
createNewTD(newTD, tr);
}
// usage Info
var div = document.getElementById("settings");
switch(menu[activeMenu.id].hasOwnProperty("_usage")) {
case true:
var usageItem = new Object();
usageItem["_element"] = "PRE"
usageItem["_text"] = menu[activeMenu.id]["_usage"];
var newHR = new Object();
newHR["_element"] = "HR"
div.appendChild(createElement(newHR));
div.appendChild(createElement(usageItem));
}
sortTable(0);
}
function userDetail(userID) {
showPopUpElement('user-detail');
setTimeout(function(){
showElement("popup", true);
}, 10);
var defaultUser;
document.getElementById("saveUserDetail").setAttribute("onclick", 'javascript: saveUserDetail("' + userID + '", false)');
document.getElementById("deleteUserDetail").setAttribute("onclick", 'javascript: saveUserDetail("' + userID + '", true)');
var data = new Object();
switch(userID) {
case 0: // New User
data["username"] = "";
data["authentication.web"] = false;
data["authentication.pms"] = true;
data["authentication.xml"] = true;
data["authentication.m3u"] = false;
data["authentication.api"] = false;
data["defaultUser"] = false;
setTimeout(function(){
showElement("deleteUserDetail", false)
}, 1);
break;
default:
data = users[userID]["data"];
showElement("deleteUserDetail", true)
document.getElementById("deleteUserDetail").className = "delete";
break
}
var username = data["username"];
data["password"] = "";
data["confirm"] = "";
var keys = getObjKeys(data);
defaultUser = data["defaultUser"];
if (data.hasOwnProperty("defaultUser")) {
defaultUser = JSON.parse(data["defaultUser"]);
}
for (var i = 0; i < keys.length; i++) {
if(document.getElementById(keys[i])){
var td = document.getElementById(keys[i])
} else {
var td = undefined;
}
var newItem = new Object();
newItem["_element"] = "INPUT";
newItem["value"] = data[keys[i]];
newItem["name"] = keys[i];
switch(keys[i].indexOf("authentication")) {
case -1:
if (keys[i] == "password" || keys[i] == "confirm") {
newItem["type"] = "password";
} else {
newItem["type"] = "text";
}
break;
default:
newItem["type"] = "checkbox";
if (keys[i] == "authentication.web" && defaultUser == true) {
newItem["onclick"] = "return false";
}
if (data[keys[i]] == true) {
newItem["checked"] = data[keys[i]];
}
break;
}
switch(keys[i]) {
case "defaultUser":
//if (data[keys[i]] == true) {
newItem["type"] = "hidden";
//}
}
if (td != undefined) {
td.innerHTML = "";
var element = createNewElement(newItem)
//console.log(element);
td.appendChild(element);
}
}
if (defaultUser == true) {
showElement("deleteUserDetail", false)
} else {
showElement("deleteUserDetail", true)
document.getElementById("deleteUserDetail").className = "delete";
}
}
function saveUserDetail(userID, deleteUser) {
var inputs = document.getElementById("user-detail-table").getElementsByTagName("INPUT");
var newUserData = new Object();
for (var i = 0; i < inputs.length; i++) {
switch(inputs[i].type) {
case "checkbox": newUserData[inputs[i].name] = inputs[i].checked; break;
default: newUserData[inputs[i].name] = inputs[i].value; break;
}
if (inputs["username"].value.length == 0) {
inputs["username"].style.border = "solid 1px red";
return;
}
switch(userID) {
case "0":
if (inputs["password"].value.length == 0) {
console.log(inputs["password"].value.length);
inputs["password"].style.border = "solid 1px red";
return
}
break;
}
if (inputs["password"].value.length > 0) {
if (inputs["password"].value != inputs["confirm"].value) {
inputs["password"].style.border = "solid 1px red";
inputs["confirm"].style.border = "solid 1px red";
return;
}
}
}
var data = new Object();
switch(userID) {
case "0":
//data = newUserData
data["userData"] = newUserData
data["cmd"] = "saveNewUser"; break;
default:
var thisUser = new Object();
if (deleteUser == true) {
if (confirm('Delete the selected user?')) {
data["deleteUser"] = true;
} else {
showElement("popup", false);
return
}
}
thisUser[userID] = newUserData;
data["userData"] = thisUser;
data["cmd"] = "saveUserData"; break;
}
xTeVe(data);
//createUsersTable()
showElement("popup", false);
}
-569
View File
@@ -1,569 +0,0 @@
{
"mainMenu":
{
"item":
{
"playlist": "Playlist",
"pmsID": "PMS ID",
"filter": "Filter",
"xmltv": "XMLTV",
"mapping": "Mapping",
"users": "Users",
"settings": "Settings",
"log": "Log",
"logout": "Logout"
},
"headline":
{
"playlist": "Local or remote playlists",
"filter": "Filter playlist",
"xmltv": "Local or remote XMLTV files",
"mapping": "Map playlist channels to EPG channels",
"users": "User management",
"settings": "Settings",
"log": "Log",
"logout": "Logout"
}
},
"confirm":
{
"restore": "All data will be replaced with those from the backup. Should the files be restored?"
},
"alert":
{
"fileLoadingError": "File couldn't be loaded",
"invalidChannelNumber": "Invalid channel number",
"missingInput": "Missing input"
},
"button":
{
"back": "Back",
"backup": "Backup",
"bulkEdit": "Bulk Edit",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"done": "Done",
"login": "Login",
"new": "New",
"next": "Next",
"restore": "Restore",
"save": "Save",
"search": "Search",
"update": "Update",
"craeteAccount": "Create Account",
"resetLogs": "Reset Logs",
"uploadLogo": "Upload Logo"
},
"filter":
{
"table":
{
"name": "Filter Name",
"type": "Filter Type",
"filter": "Filter"
},
"custom": "Custom",
"group": "Group",
"name":
{
"title": "Filter Name",
"placeholder": "Filter name",
"description": ""
},
"description":
{
"title": "Description",
"placeholder": "Description",
"description": ""
},
"type":
{
"title": "Type",
"groupTitle": "Group Title",
"customFilter": "Custom Filter"
},
"caseSensitive":
{
"title": "Case Sensitive",
"placeholder": "",
"description": ""
},
"filterRule":
{
"title": "Filter Rule",
"placeholder": "Sport {HD} !{ES,IT}",
"description": ""
},
"filterGroup":
{
"title": "Group Title",
"placeholder": "",
"description": "Select a M3U group. (Counter)<br>Changing the group title in the M3U invalidates the filter."
},
"include":
{
"title": "Include",
"placeholder": "FHD,UHD",
"description": "Channel name must include.<br>(Comma separated) Comma means or"
},
"exclude":
{
"title": "Exclude",
"placeholder": "ES,IT",
"description": "Channel name must not contain.<br>(Comma separated) Comma means or"
}
},
"playlist":
{
"table":
{
"playlist": "Playlist",
"tuner": "Tuner",
"lastUpdate": "Last Update",
"availability": "Availability",
"type": "Type",
"streams": "Streams",
"groupTitle": "group-title",
"tvgID": "tvg-id",
"uniqueID": "Unique ID"
},
"playlistType":
{
"title": "Playlist type",
"placeholder": "",
"description": ""
},
"type":
{
"title": "Type",
"placeholder": "",
"description": ""
},
"name":
{
"title": "Name",
"placeholder": "Playlist name",
"description": ""
},
"description":
{
"title": "Description",
"placeholder": "Description",
"description": ""
},
"fileM3U":
{
"title": "M3U File",
"placeholder": "File path or URL of the M3U",
"description": ""
},
"fileHDHR":
{
"title": "HDHomeRun IP",
"placeholder": "IP address and port (192.168.1.10:5004)",
"description": ""
},
"tuner":
{
"title": "Tuner / Streams",
"placeholder": "",
"description": "Number of parallel connections that can be established to the provider. <br>Only available with activated buffer.<br>New settings will only be applied after quitting all streams."
}
},
"xmltv":
{
"table":
{
"guide": "Guide",
"lastUpdate": "Last Update",
"availability": "Availability",
"channels": "Channels",
"programs": "Programs"
},
"name":
{
"title": "Name",
"placeholder": "Guide name",
"description": ""
},
"description":
{
"title": "Description",
"placeholder": "Description",
"description": ""
},
"fileXMLTV":
{
"title": "XMLTV File",
"placeholder": "File path or URL of the XMLTV",
"description": ""
}
},
"mapping":
{
"table":
{
"chNo": "Ch. No.",
"logo": "Logo",
"channelName": "Channel Name",
"playlist": "Playlist",
"groupTitle": "Group Title",
"xmltvFile": "XMLTV File",
"xmltvID": "XMLTV ID",
"edit": "Edit"
},
"active":
{
"title": "Active",
"placeholder": "",
"description": ""
},
"channelName":
{
"title": "Channel Name",
"placeholder": "",
"description": ""
},
"description":
{
"title": "Channel Description",
"placeholder": "Used by the Dummy as an XML description",
"description": ""
},
"updateChannelName":
{
"title": "Update Channel Name",
"placeholder": "",
"description": ""
},
"channelLogo":
{
"title": "Logo URL",
"placeholder": "",
"description": ""
},
"updateChannelLogo":
{
"title": "Update Channel Logo",
"placeholder": "",
"description": ""
},
"epgCategory":
{
"title": "EPG Category",
"placeholder": "",
"description": ""
},
"m3uGroupTitle":
{
"title": "Group Title (xteve.m3u)",
"placeholder": "",
"description": ""
},
"xmltvFile":
{
"title": "XMLTV File",
"placeholder": "",
"description": ""
},
"xmltvChannel":
{
"title": "XMLTV Channel",
"placeholder": "",
"description": ""
}
},
"users":
{
"table":
{
"username": "Username",
"password": "Password",
"web": "WEB",
"pms": "PMS",
"m3u": "M3U",
"xml": "XML",
"api": "API"
},
"username":
{
"title": "Username",
"placeholder": "Username",
"description": ""
},
"password":
{
"title": "Password",
"placeholder": "Password",
"description": ""
},
"confirm":
{
"title": "Confirm",
"placeholder": "Password confirm",
"description": ""
},
"web":
{
"title": "Web Access",
"placeholder": "",
"description": ""
},
"pms":
{
"title": "PMS Access",
"placeholder": "",
"description": ""
},
"m3u":
{
"title": "M3U Access",
"placeholder": "",
"description": ""
},
"xml":
{
"title": "XML Access",
"placeholder": "",
"description": ""
},
"api":
{
"title": "API Access",
"placeholder": "",
"description": ""
}
},
"settings":
{
"category":
{
"general": "General",
"files": "Files",
"streaming": "Streaming",
"backup": "Backup",
"authentication": "Authentication"
},
"update":
{
"title": "Schedule for updating (Playlist, XMLTV, Backup)",
"placeholder": "0000,1000,2000",
"description": "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."
},
"api":
{
"title": "API Interface",
"description": "Via API interface it is possible to send commands to xTeVe. API documentation is <a href='https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md#api'>here</a>"
},
"usePlexAPI":
{
"title": "Use Plex API Refresh",
"description": "When enabled, xTeVe calls Plex directly to refresh DVR guide data after lineup or XEPG updates."
},
"plexURL":
{
"title": "Plex Server URL",
"description": "Base URL of your Plex server. Example: http://192.168.1.10:32400",
"placeholder": "http://plex-host:32400"
},
"plexToken":
{
"title": "Plex API Token",
"description": "Plex token used for authenticated API calls to refresh your DVR guide.",
"placeholder": "Plex X-Plex-Token"
},
"epgSource":
{
"title": "EPG Source",
"description": "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
},
"tuner":
{
"title": "Number of Tuners",
"description": "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
},
"filesUpdate":
{
"title": "Updates all files at startup",
"description": "Updates all playlists, tuner and XMLTV files at startup."
},
"cacheImages":
{
"title": "Image Caching",
"description": "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.<br>Downloading the images may take a while and will be done in the background."
},
"replaceEmptyImages":
{
"title": "Replace missing program images",
"description": "If the poster in the XMLTV program is missing, the channel logo will be used."
},
"xepgMissingEPGMode":
{
"title": "Missing EPG Handling",
"description": "Strict: channels are deactivated when XMLTV mappings disappear.<br>Relaxed: channels stay active with last-known mappings and fall back to a dummy guide whenever guide data cannot be resolved.",
"info_strict": "Strict (deactivate channels)",
"info_relaxed": "Relaxed (keep active / dummy guide)"
},
"streamBuffering":
{
"title": "Stream Buffer",
"description": "Functions of the buffer:<br>- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support<br>- RTP / RTPS support (only FFmpeg or VLC)<br>- Re-streaming<br>- Separate tuner limit for each playlist",
"info_false": "No Buffer (Client connects to the streaming server)",
"info_xteve": "xTeVe connects to the streaming server",
"info_ffmpeg": "FFmpeg connects to the streaming server",
"info_vlc": "VLC connects to the streaming server"
},
"udpxy":
{
"title": "UDPxy address",
"description": "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.",
"placeholder": "host:port"
},
"ffmpegPath":
{
"title": "FFmpeg Binary Path",
"description": "Path to FFmpeg binary.",
"placeholder": "/path/to/ffmpeg"
},
"ffmpegOptions":
{
"title": "FFmpeg Options",
"description": "FFmpeg options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.",
"placeholder": "Leave blank to set default settings"
},
"vlcPath":
{
"title": "VLC / CVLC Binary Path",
"description": "Path to VLC / CVLC binary.",
"placeholder": "/path/to/cvlc"
},
"vlcOptions":
{
"title": "VLC / CVLC Options",
"description": "VLC / CVLC options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.",
"placeholder": "Leave blank to set default settings"
},
"bufferSize":
{
"title": "Buffer Size",
"description": "Buffer size in MB.<br>M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used."
},
"bufferTimeout":
{
"title": "Timeout for new client connections",
"description": "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds.",
"placeholder": "100"
},
"userAgent":
{
"title": "User Agent",
"description": "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.",
"placeholder": "xTeVe"
},
"backupPath":
{
"title": "Location for automatic backups",
"placeholder": "/mnt/data/backup/xteve/",
"description": "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."
},
"tempPath":
{
"title": "Location for the temporary files",
"placeholder": "/tmp/xteve/",
"description": "Location for the buffer files."
},
"backupKeep":
{
"title": "Number of backups to keep",
"description": "Number of backups to keep. Older backups are automatically deleted."
},
"authenticationWEB":
{
"title": "WEB Authentication",
"description": "Access to the web interface only possible with credentials."
},
"authenticationPMS":
{
"title": "PMS Authentication",
"description": "Plex requests are only possible with authentication. <br><b>Warning!!!</b> After activating this function xTeVe must be delete in the PMS DVR settings and set up again."
},
"authenticationM3U":
{
"title": "M3U Authentication",
"description": "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."
},
"authenticationXML":
{
"title": "XML Authentication",
"description": "Downloading the xteve.xml file via an HTTP request is only possible with authentication"
},
"authenticationAPI":
{
"title": "API Authentication",
"description": "Access to the API interface is only possible with authentication."
}
},
"wizard":
{
"epgSource":
{
"title": "EPG Source",
"description": "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
},
"tuner":
{
"title": "Number of tuners",
"description": "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
},
"m3u":
{
"title": "M3U Playlist",
"placeholder": "File path or URL of the M3U",
"description": "Local or remote playlists"
},
"xmltv":
{
"title": "XMLTV File",
"placeholder": "File path or URL of the XMLTV",
"description": "Local or remote XMLTV file"
}
},
"login":
{
"failed": "User authentication failed",
"headline": "Login",
"username":
{
"title": "Username",
"placeholder": "Username"
},
"password":
{
"title": "Password",
"placeholder": "Password"
}
},
"account":
{
"failed": "Password does not match",
"headline": "Create user account",
"username":
{
"title": "Username",
"placeholder": "Username"
},
"password":
{
"title": "Password",
"placeholder": "Password"
},
"confirm":
{
"title": "Confirm",
"placeholder": "Confirm"
}
}
}
+5 -6
View File
@@ -6,8 +6,7 @@
<title>xTeVe</title>
<link rel="stylesheet" href="css/screen.css" type="text/css">
<link rel="stylesheet" href="css/base.css" type="text/css">
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
<script src="js/app.js"></script>
</head>
<body class="auth-screen">
@@ -17,7 +16,7 @@
<main id="box" role="main" aria-labelledby="head-text">
<div id="headline">
<h1 id="head-text" class="center">{{.login.headline}}</h1>
<h1 id="head-text" class="center">Login</h1>
</div>
<p id="err" class="errorMsg center" role="alert" aria-live="assertive" aria-atomic="true">{{.authenticationErr}}</p>
@@ -26,9 +25,9 @@
<form id="authentication" action="" method="post" aria-describedby="err" novalidate>
<label for="username">{{.login.username.title}}:</label>
<label for="username">Username:</label>
<input id="username" type="text" name="username" placeholder="Username" value="" autocomplete="username">
<label for="password">{{.login.password.title}}:</label>
<label for="password">Password:</label>
<input id="password" type="password" name="password" placeholder="Password" value="" autocomplete="current-password">
</form>
@@ -36,7 +35,7 @@
</div>
<div id="box-footer">
<input id="submit" class="" type="button" value="{{.button.login}}" aria-label="{{.button.login}}" onclick="javascript: login();">
<input id="submit" class="" type="button" value="Login" aria-label="Login" onclick="javascript: login();">
</div>
</main>
+27
View File
@@ -0,0 +1,27 @@
{
"name": "xteve-webui",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "xteve-webui",
"devDependencies": {
"typescript": "5.9.3"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "xteve-webui",
"private": true,
"description": "Build tooling for the xTeVe web interface (TypeScript -> html/js/app.js)",
"scripts": {
"build": "tsc -p ts/tsconfig.json",
"check": "tsc -p ts/tsconfig.json --noEmit"
},
"devDependencies": {
"typescript": "5.9.3"
}
}
+52
View File
@@ -0,0 +1,52 @@
package src
import (
"crypto/sha256"
"encoding/hex"
"io/fs"
"os"
"path"
"strings"
"sync"
webui "xteve/html"
)
// webFS serves the web interface. It is the embedded copy by default and the
// local html/ directory when xTeVe runs with -dev.
var webFS fs.FS = webui.FS
var webETags sync.Map // path -> ETag, only used when not in dev mode
// initWebFS : selects embedded or on-disk web files.
func initWebFS(dev bool) {
if dev {
webFS = os.DirFS("html")
return
}
webFS = webui.FS
}
// readWebFile : reads a file from the web interface, name relative to html/.
func readWebFile(name string) ([]byte, error) {
name = strings.TrimPrefix(path.Clean("/"+name), "/")
if name == "" || name == "." {
return nil, fs.ErrNotExist
}
return fs.ReadFile(webFS, name)
}
// webFileETag : content hash used for HTTP caching of static web assets.
func webFileETag(name string, content []byte) string {
if !System.Dev {
if v, ok := webETags.Load(name); ok {
return v.(string)
}
}
sum := sha256.Sum256(content)
etag := `"` + hex.EncodeToString(sum[:16]) + `"`
if !System.Dev {
webETags.Store(name, etag)
}
return etag
}
+1 -4
View File
@@ -153,10 +153,7 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - No new connections available. Tuner = %d", playlist.PlaylistName, playlist.Tuner))
if value, ok := webUI["html/video/stream-limit.ts"]; ok {
var content string
content = GetHTMLString(value.(string))
if content, err := readWebFile("video/stream-limit.ts"); err == nil {
w.WriteHeader(200)
w.Header().Set("Content-type", "video/mpeg")
+2 -13
View File
@@ -188,16 +188,8 @@ func Init() (err error) {
System.URLBase = fmt.Sprintf("%s://%s:%s", System.ServerProtocol.WEB, System.IPAddress, Settings.Port)
// HTML Dateien erstellen, mit dev == true werden die lokalen HTML Dateien verwendet
if System.Dev == true {
HTMLInit("webUI", "src", "html"+string(os.PathSeparator), "src"+string(os.PathSeparator)+"webUI.go")
err = BuildGoFile()
if err != nil {
return
}
}
// Mit dev == true werden die lokalen HTML Dateien verwendet, sonst die eingebetteten
initWebFS(System.Dev)
// DLNA Server starten
err = SSDP()
@@ -205,9 +197,6 @@ func Init() (err error) {
return
}
// HTML Datein laden
loadHTMLMap()
return
}
-148
View File
@@ -1,148 +0,0 @@
package src
import (
"bufio"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"strings"
)
var htmlFolder string
var goFile string
var mapName string
var packageName string
var blankMap = make(map[string]any)
// HTMLInit : Dateipfade festlegen
// mapName = Name der zu erstellenden map
// htmlFolder: Ordner der HTML Dateien
// packageName: Name des package
func HTMLInit(name, pkg, folder, file string) {
htmlFolder = folder
goFile = file
mapName = name
packageName = pkg
}
// BuildGoFile : Erstellt das GO Dokument
func BuildGoFile() error {
var err = checkHTMLFile(htmlFolder)
if err != nil {
return err
}
var content string
content += `package ` + packageName + "\n\n"
content += `var ` + mapName + ` = make(map[string]interface{})` + "\n\n"
content += "func loadHTMLMap() {" + "\n\n"
content += createMapFromFiles(htmlFolder) + "\n"
content += "}" + "\n\n"
writeStringToFile(goFile, content)
return nil
}
// GetHTMLString : base64 -> string
func GetHTMLString(base string) string {
content, _ := base64.StdEncoding.DecodeString(base)
return string(content)
}
func createMapFromFiles(folder string) string {
var path = getLocalPath(folder)
err := filepath.Walk(path, readFilesToMap)
if err != nil {
checkErr(err)
}
var content strings.Builder
for key := range blankMap {
var newKey = key
content.WriteString(` ` + mapName + `["` + newKey + `"` + `] = "` + blankMap[key].(string) + `"` + "\n")
}
return content.String()
}
func readFilesToMap(path string, info os.FileInfo, err error) error {
if info.IsDir() == false {
var base64Str = fileToBase64(getLocalPath(path))
blankMap[path] = base64Str
}
return nil
}
func fileToBase64(file string) string {
imgFile, _ := os.Open(file)
defer imgFile.Close()
// create a new buffer base on file size
fInfo, _ := imgFile.Stat()
var size = fInfo.Size()
buf := make([]byte, int64(size))
// read file content into buffer
fReader := bufio.NewReader(imgFile)
fReader.Read(buf)
imgBase64Str := base64.StdEncoding.EncodeToString(buf)
return imgBase64Str
}
func getLocalPath(filename string) string {
path, file := filepath.Split(filename)
var newPath = filepath.Dir(path)
var newFileName = newPath + "/" + file
return newFileName
}
func writeStringToFile(filename, content string) error {
err := ioutil.WriteFile(getPlatformFile(filename), []byte(content), 0644)
if err != nil {
checkErr(err)
return err
}
return nil
}
func checkHTMLFile(filename string) error {
if _, err := os.Stat(getLocalPath(filename)); os.IsNotExist(err) {
fmt.Println(filename)
checkErr(err)
return err
}
return nil
}
func checkErr(err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
log.Println("ERROR: [", err, "] in ", file, line)
}
}
-7
View File
@@ -267,7 +267,6 @@ type SettingsStruct struct {
FilesUpdate bool `json:"files.update"`
Filter map[int64]any `json:"filter"`
Key string `json:"key,omitempty"`
Language string `json:"language"`
LogEntriesRAM int `json:"log.entries.ram"`
M3U8AdaptiveBandwidthMBPS int `json:"m3u8.adaptive.bandwidth.mbps"`
MappingFirstChannel float64 `json:"mapping.first.channel"`
@@ -285,9 +284,3 @@ type SettingsStruct struct {
WizardCompleted bool `json:"wizard.completed"`
}
// LanguageUI : Sprache für das WebUI
type LanguageUI struct {
Login struct {
Failed string
}
}
-1
View File
@@ -138,7 +138,6 @@ func loadSettings() (settings SettingsStruct, err error) {
defaults["files.update"] = true
defaults["filter"] = make(map[string]any)
defaults["git.branch"] = System.Branch
defaults["language"] = "en"
defaults["log.entries.ram"] = 500
defaults["mapping.first.channel"] = 1000
defaults["xepg.missing.epg.mode"] = "strict"
-50
View File
File diff suppressed because one or more lines are too long
+56 -75
View File
@@ -38,6 +38,7 @@ func StartWebserver() (err error) {
http.HandleFunc("/api/", API)
http.HandleFunc("/images/", Images)
http.HandleFunc("/data_images/", DataImages)
http.HandleFunc("/healthz", Healthz)
//http.HandleFunc("/auto/", Auto)
@@ -595,44 +596,36 @@ func WS(w http.ResponseWriter, r *http.Request) {
return
}
// Healthz : Liveness endpoint for container healthchecks and monitoring.
// Unauthenticated on purpose; it exposes only the version and whether a scan is running.
func Healthz(w http.ResponseWriter, r *http.Request) {
var status = map[string]any{
"status": "ok",
"version": System.Version + "." + System.Build,
"scanInProgress": System.ScanInProgress == 1,
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
w.WriteHeader(http.StatusOK)
w.Write([]byte(mapToJSON(status)))
}
// Web : Web Server /web/
func Web(w http.ResponseWriter, r *http.Request) {
var lang = make(map[string]any)
var err error
var requestFile = strings.TrimPrefix(r.URL.Path, "/web")
requestFile = strings.TrimPrefix(requestFile, "/")
var requestFile = strings.Replace(r.URL.Path, "/web", "html", -1)
var content, contentType, file string
var language LanguageUI
// Values substituted into the HTML pages (JS and CSS are served verbatim).
var tmpl = map[string]any{"authenticationErr": ""}
setGlobalDomain(r.Host)
if System.Dev == true {
if requestFile == "" {
lang, err = loadJSONFileToMap(fmt.Sprintf("html/lang/%s.json", Settings.Language))
if err != nil {
ShowError(err, 000)
}
} else {
var languageFile = "html/lang/en.json"
if value, ok := webUI[languageFile].(string); ok {
content = GetHTMLString(value)
lang = jsonToMap(content)
}
}
err = json.Unmarshal([]byte(mapToJSON(lang)), &language)
if err != nil {
ShowError(err, 000)
return
}
if getFilenameFromPath(requestFile) == "html" {
var file string
if System.ScanInProgress == 0 {
@@ -645,16 +638,16 @@ func Web(w http.ResponseWriter, r *http.Request) {
switch System.ConfigurationWizard {
case true:
file = requestFile + "configuration.html"
file = "configuration.html"
Settings.AuthenticationWEB = false
case false:
file = requestFile + "index.html"
file = "index.html"
}
if System.ScanInProgress == 1 {
file = requestFile + "maintenance.html"
file = "maintenance.html"
}
switch Settings.AuthenticationWEB {
@@ -692,8 +685,8 @@ func Web(w http.ResponseWriter, r *http.Request) {
var token, err = authentication.UserAuthentication(username, password)
if err != nil {
file = requestFile + "login.html"
lang["authenticationErr"] = language.Login.Failed
file = "login.html"
tmpl["authenticationErr"] = "User authentication failed"
break
}
@@ -708,17 +701,16 @@ func Web(w http.ResponseWriter, r *http.Request) {
return
case "GET":
lang["authenticationErr"] = ""
_, token, err := authentication.CheckTheValidityOfTheTokenFromHTTPHeader(w, r)
if err != nil {
file = requestFile + "login.html"
file = "login.html"
break
}
err = checkAuthorizationLevel(token, "authentication.web")
if err != nil {
file = requestFile + "login.html"
file = "login.html"
break
}
@@ -732,58 +724,47 @@ func Web(w http.ResponseWriter, r *http.Request) {
}
if len(allUserData) == 0 && Settings.AuthenticationWEB == true {
file = requestFile + "create-first-user.html"
file = "create-first-user.html"
}
}
requestFile = file
if value, ok := webUI[requestFile]; ok {
}
content = GetHTMLString(value.(string))
content, err := readWebFile(requestFile)
if err != nil {
httpStatusError(w, r, 404)
return
}
if contentType == "text/plain" {
w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(requestFile))
}
var contentType = getContentType(requestFile)
} else {
switch contentType {
httpStatusError(w, r, 404)
case "text/html":
content = []byte(parseTemplate(string(content), tmpl))
w.Header().Set("Cache-Control", "no-store")
case "text/plain":
w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(requestFile))
default:
// Static asset: let the browser revalidate cheaply with an ETag.
var etag = webFileETag(requestFile, content)
w.Header().Set("ETag", etag)
w.Header().Set("Cache-Control", "no-cache")
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
}
if value, ok := webUI[requestFile].(string); ok {
content = GetHTMLString(value)
contentType = getContentType(requestFile)
if contentType == "text/plain" {
w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(requestFile))
}
} else {
httpStatusError(w, r, 404)
return
}
contentType = getContentType(requestFile)
if System.Dev == true {
// Lokale Webserver Dateien werden geladen, nur für die Entwicklung
content, _ = readStringFromFile(requestFile)
}
w.Header().Add("Content-Type", contentType)
w.WriteHeader(200)
if contentType == "text/html" || contentType == "application/javascript" {
content = parseTemplate(content, lang)
}
w.Write([]byte(content))
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusOK)
w.Write(content)
}
// API : API request /api/
+6 -6
View File
@@ -43,14 +43,14 @@ Status: Phase 0 committed on branch `improvements` 2026-09-26.
- [ ] Error hygiene: no-op closures, `os.Exit`/`panic` outside main, ignored results
## Phase 3: Build, embed, CI, Docker
- [ ] `//go:embed html`, delete `webUI.go`, `html-build.go`, `cmd/webui-gen`; cache headers
- [ ] Drop i18n: inline English strings into TS, delete `en.json`, template only HTML pages
- [ ] `package.json` + `tsconfig.json` + esbuild bundle (committed, CI-verified); delete 10 dead JS files
- [ ] CI: vet, lint, gofmt, `test -race`, `tsc --noEmit`, generated-output check
- [x] `//go:embed` via `html/embed.go`; deleted `webUI.go`, `html-build.go`, `cmd/webui-gen`; ETag + Cache-Control on static assets
- [x] i18n dropped: 254 placeholders inlined, `en.json` deleted, only HTML pages templated (`authenticationErr`)
- [x] `package.json` + `ts/tsconfig.json` (tsc 5.9.3, ES2020, single outFile `html/js/app.js`, committed, CI-verified); 10 dead JS files deleted; 6 tsc errors fixed incl. a real ASI bug in the search shortcut
- [ ] CI: vet (done), lint, gofmt, `test -race`; bundle freshness check (done)
- [ ] Version-tagged images (amd64 only)
- [ ] PUID/PGID via su-exec; pin static-ffmpeg; `VOLUME`; fix `LEGACY_CONFIG_DIRS`
- [ ] `/healthz` endpoint
- [ ] `README-DEV.md` and fork section in `README.md`
- [x] `/healthz` endpoint; Dockerfile healthcheck uses it
- [ ] `README-DEV.md` (done) and fork section in `README.md`
- [ ] Deduplicate and delete dead code; drop `ioutil`, `osext`, `rand.Seed` (after go:embed)
- [ ] Resolve missing `docs/design-system/` referenced by `agent.md`
+1 -1
View File
@@ -35,7 +35,7 @@ function login() {
document.getElementById('password').style.borderColor = "red"
document.getElementById('confirm').style.borderColor = "red"
document.getElementById("err").innerHTML = "{{.account.failed}}"
document.getElementById("err").innerHTML = "Password does not match"
return
}
+16 -16
View File
@@ -11,22 +11,22 @@ var LAST_BULK_CHECKBOX:HTMLInputElement = null
// Menü
var menuItems = new Array()
menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}"))
//menuItems.push(new MainMenuItem("pmsID", "{{.mainMenu.item.pmsID}}", "number.png", "{{.mainMenu.headline.pmsID}}"))
menuItems.push(new MainMenuItem("filter", "{{.mainMenu.item.filter}}", "filter.png", "{{.mainMenu.headline.filter}}"))
menuItems.push(new MainMenuItem("xmltv", "{{.mainMenu.item.xmltv}}", "xmltv.png", "{{.mainMenu.headline.xmltv}}"))
menuItems.push(new MainMenuItem("mapping", "{{.mainMenu.item.mapping}}", "mapping.png", "{{.mainMenu.headline.mapping}}"))
menuItems.push(new MainMenuItem("users", "{{.mainMenu.item.users}}", "users.png", "{{.mainMenu.headline.users}}"))
menuItems.push(new MainMenuItem("settings", "{{.mainMenu.item.settings}}", "settings.png", "{{.mainMenu.headline.settings}}"))
menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.mainMenu.headline.log}}"))
menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}"))
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("{{.settings.category.general}}", "tuner,epgSource,api,use_plexAPI,plex.url,plex.token"));settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.missing.epg.mode,xepg.replace.missing.images"))
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,udpxy,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"))
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"))
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"))
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) {
@@ -342,7 +342,7 @@ function sortTable(column) {
default: console.log(x.childNodes[0].tagName);
}
if (xValue == "" || xValue == NaN) {
if (xValue == "" || Number.isNaN(xValue)) {
xValue = i
sortObj[i] = rows[i];
@@ -502,7 +502,7 @@ function changeChannelNumber(element) {
var channels = getObjKeys(data)
if (isNaN(newNumber)) {
alert("{{.alert.invalidChannelNumber}}")
alert("Invalid channel number")
return
}
@@ -628,7 +628,7 @@ function restore() {
restore.onchange = function() {
var filename = (restore as HTMLInputElement).files[0].name
var check = confirm("File: " + filename + "\n{{.confirm.restore}}");
var check = confirm("File: " + filename + "\nAll data will be replaced with those from the backup. Should the files be restored?");
if (check == true) {
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
tsc *.ts --outDir ../html/js/
+11 -11
View File
@@ -43,7 +43,7 @@ class WizardItem extends WizardCategory {
select.id = key
doc.appendChild(select)
description = "{{.wizard.tuner.description}}"
description = "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
break;
@@ -56,29 +56,29 @@ class WizardItem extends WizardCategory {
select.id = key
doc.appendChild(select)
description = "{{.wizard.epgSource.description}}"
description = "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
break
case "m3u":
var input = content.createInput("text", key, "")
input.setAttribute("placeholder", "{{.wizard.m3u.placeholder}}")
input.setAttribute("placeholder", "File path or URL of the M3U")
input.setAttribute("class", "wizard")
input.id = key
doc.appendChild(input)
description = "{{.wizard.m3u.description}}"
description = "Local or remote playlists"
break
case "xmltv":
var input = content.createInput("text", key, "")
input.setAttribute("placeholder", "{{.wizard.xmltv.placeholder}}")
input.setAttribute("placeholder", "File path or URL of the XMLTV")
input.setAttribute("class", "wizard")
input.id = key
doc.appendChild(input)
description = "{{.wizard.xmltv.description}}"
description = "Local or remote XMLTV file"
break
@@ -143,7 +143,7 @@ function saveWizard() {
value = (config[i] as HTMLInputElement).value
if (value.length == 0) {
var msg = name.toUpperCase() + ": " + "{{.alert.missingInput}}"
var msg = name.toUpperCase() + ": " + "Missing input"
alert(msg)
return
}
@@ -171,7 +171,7 @@ function saveWizard() {
// Wizard
var configurationWizard = new Array()
configurationWizard.push(new WizardItem("tuner", "{{.wizard.tuner.title}}"))
configurationWizard.push(new WizardItem("epgSource", "{{.wizard.epgSource.title}}"))
configurationWizard.push(new WizardItem("m3u", "{{.wizard.m3u.title}}"))
configurationWizard.push(new WizardItem("xmltv", "{{.wizard.xmltv.title}}"))
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"))
+123 -123
View File
@@ -49,23 +49,23 @@ class MainMenuItem extends MainMenu {
switch(this.menuKey) {
case "playlist":
this.tableHeader = ["{{.playlist.table.playlist}}", "{{.playlist.table.tuner}}", "{{.playlist.table.lastUpdate}}", "{{.playlist.table.availability}} %", "{{.playlist.table.type}}", "{{.playlist.table.streams}}", "{{.playlist.table.groupTitle}} %", "{{.playlist.table.tvgID}} %", "{{.playlist.table.uniqueID}} %"]
this.tableHeader = ["Playlist", "Tuner", "Last Update", "Availability %", "Type", "Streams", "group-title %", "tvg-id %", "Unique ID %"]
break
case "xmltv":
this.tableHeader = ["{{.xmltv.table.guide}}", "{{.xmltv.table.lastUpdate}}", "{{.xmltv.table.availability}} %", "{{.xmltv.table.channels}}", "{{.xmltv.table.programs}}"]
this.tableHeader = ["Guide", "Last Update", "Availability %", "Channels", "Programs"]
break
case "filter":
this.tableHeader = ["{{.filter.table.name}}", "{{.filter.table.type}}", "{{.filter.table.filter}}"]
this.tableHeader = ["Filter Name", "Filter Type", "Filter"]
break
case "users":
this.tableHeader = ["{{.users.table.username}}", "{{.users.table.password}}", "{{.users.table.web}}", "{{.users.table.pms}}", "{{.users.table.m3u}}", "{{.users.table.xml}}", "{{.users.table.api}}"]
this.tableHeader = ["Username", "Password", "WEB", "PMS", "M3U", "XML", "API"]
break
case "mapping":
this.tableHeader = ["BULK", "{{.mapping.table.chNo}}", "{{.mapping.table.logo}}", "{{.mapping.table.channelName}}", "{{.mapping.table.playlist}}", "{{.mapping.table.groupTitle}}", "{{.mapping.table.xmltvFile}}", "{{.mapping.table.xmltvID}}", "{{.mapping.table.edit}}"]
this.tableHeader = ["BULK", "Ch. No.", "Logo", "Channel Name", "Playlist", "Group Title", "XMLTV File", "XMLTV ID", "Edit"]
break
}
@@ -225,11 +225,11 @@ class Content {
cell.childType = "P"
switch (data[key]["type"]) {
case "custom-filter":
cell.value = "{{.filter.custom}}"
cell.value = "Custom"
break;
case "group-title":
cell.value = "{{.filter.group}}"
cell.value = "Group"
break;
default:
@@ -480,7 +480,7 @@ class Content {
var cell:Cell = new Cell()
cell.child = true
cell.childType = "EDIT"
cell.value = "{{.button.edit}}"
cell.value = "Edit"
var editTd = cell.createCell()
var editButton = (editTd.firstChild as HTMLInputElement)
editButton.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
@@ -644,14 +644,14 @@ class ShowContent extends Content {
var interaction = document.getElementById(this.interactionID)
switch (menuKey) {
case "playlist":
var input = this.createInput("button", menuKey, "{{.button.new}}")
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, "{{.button.new}}")
var input = this.createInput("button", menuKey, "New")
input.setAttribute("id", -1)
input.setAttribute("onclick", 'javascript: openPopUp("filter", this)')
interaction.appendChild(input)
@@ -659,14 +659,14 @@ class ShowContent extends Content {
case "xmltv":
var input = this.createInput("button", menuKey, "{{.button.new}}")
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, "{{.button.new}}")
var input = this.createInput("button", menuKey, "New")
input.setAttribute("id", "users")
input.setAttribute("onclick", 'javascript: openPopUp("users")')
interaction.appendChild(input)
@@ -674,32 +674,32 @@ class ShowContent extends Content {
case "mapping":
showElement("loading", true)
var input = this.createInput("button", menuKey, "{{.button.save}}")
var input = this.createInput("button", menuKey, "Save")
input.setAttribute("onclick", 'javascript: savePopupData("mapping", "", "")')
interaction.appendChild(input)
var input = this.createInput("button", menuKey, "{{.button.bulkEdit}}")
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", "{{.button.search}}")
input.setAttribute("placeholder", "Search")
input.className = "search"
input.setAttribute("oninput", 'javascript: searchInMapping()')
interaction.appendChild(input)
break;
case "settings":
var input = this.createInput("button", menuKey, "{{.button.save}}")
var input = this.createInput("button", menuKey, "Save")
input.setAttribute("onclick", 'javascript: saveSettings();')
interaction.appendChild(input)
var input = this.createInput("button", menuKey, "{{.button.backup}}")
var input = this.createInput("button", menuKey, "Backup")
input.setAttribute("onclick", 'javascript: backup();')
interaction.appendChild(input)
var input = this.createInput("button", menuKey, "{{.button.restore}}")
var input = this.createInput("button", menuKey, "Restore")
input.setAttribute("onclick", 'javascript: restore();')
interaction.appendChild(input)
@@ -717,7 +717,7 @@ class ShowContent extends Content {
break
case "log":
var input = this.createInput("button", menuKey, "{{.button.resetLogs}}")
var input = this.createInput("button", menuKey, "Reset Logs")
input.setAttribute("onclick", 'javascript: resetLogs();')
interaction.appendChild(input)
@@ -770,23 +770,23 @@ class ShowContent extends Content {
if (menuKey == "mapping") {
if (element == "{{.mapping.table.chNo}}") {
if (element == "Ch. No.") {
cell.onclick = true
cell.onclickFunktion = "javascript: sortTable(1);"
cell.tdClassName = "sortThis"
}
if (element == "{{.mapping.table.channelName}}") {
if (element == "Channel Name") {
cell.onclick = true
cell.onclickFunktion = "javascript: sortTable(3);"
}
if (element == "{{.mapping.table.playlist}}") {
if (element == "Playlist") {
cell.onclick = true
cell.onclickFunktion = "javascript: sortTable(4);"
}
if (element == "{{.mapping.table.groupTitle}}") {
if (element == "Group Title") {
cell.onclick = true
cell.onclickFunktion = "javascript: sortTable(5);"
}
@@ -975,7 +975,7 @@ function initShellLayout() {
var search = document.getElementById("searchMapping")
if (search != null) {
event.preventDefault()
event.preventDefault();
(search as HTMLInputElement).focus()
}
}
@@ -1072,7 +1072,7 @@ function setClientInfoValue(key:string, value:any) {
if ((key == "m3u-url" || key == "xepg-url") && isClientInfoHttpURL(textValue)) {
element.innerHTML = ""
var anchor = document.createElement("A")
var anchor = document.createElement("a")
anchor.href = textValue
anchor.target = "_blank"
anchor.rel = "noopener noreferrer"
@@ -1140,7 +1140,7 @@ function createLayout() {
if (ACTIVE_MENU_ID.length == 0 && content != null && menu != null) {
if (content.innerHTML.replace(/\\s/g, "").length == 0) {
var firstItem = menu.getElementsByTagName("LI")[0]
var firstItem = menu.getElementsByTagName("LI")[0] as HTMLElement
if (firstItem != undefined) {
firstItem.click()
}
@@ -1330,24 +1330,24 @@ function openPopUp(dataType, element) {
switch (dataType) {
case "playlist":
content.createHeadline("{{.playlist.playlistType.title}}")
content.createHeadline("Playlist type")
// Type
var text:string[] = ["M3U", "HDHomeRun"]
var values:string[] = ["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("{{.playlist.type.title}}", select)
content.appendRow("Type", select)
// Interaktion
content.createInteraction()
// Abbrechen
var input = content.createInput("button", "cancel", "{{.button.cancel}}")
var input = content.createInput("button", "cancel", "Cancel")
input.setAttribute("onclick", 'javascript: showElement("popup", false);')
content.addInteraction(input)
// Weiter
var input = content.createInput("button", "next", "{{.button.next}}")
var input = content.createInput("button", "next", "Next")
input.setAttribute("onclick", 'javascript: openPopUp("m3u")')
input.setAttribute("id", 'next')
content.addInteraction(input)
@@ -1358,20 +1358,20 @@ function openPopUp(dataType, element) {
// Name
var dbKey:string = "name"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.playlist.name.placeholder}}")
content.appendRow("{{.playlist.name.title}}", input)
input.setAttribute("placeholder", "Playlist name")
content.appendRow("Name", input)
// Beschreibung
var dbKey:string = "description"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.playlist.description.placeholder}}")
content.appendRow("{{.playlist.description.title}}", input)
input.setAttribute("placeholder", "Description")
content.appendRow("Description", input)
// URL
var dbKey:string = "file.source"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}")
content.appendRow("{{.playlist.fileM3U.title}}", input)
input.setAttribute("placeholder", "File path or URL of the M3U")
content.appendRow("M3U File", input)
// Tuner
if (SERVER["settings"]["buffer"] != "-") {
@@ -1386,7 +1386,7 @@ function openPopUp(dataType, element) {
var dbKey:string = "tuner"
var select = content.createSelect(text, values, data[dbKey], dbKey)
select.setAttribute("onfocus", "javascript: return;")
content.appendRow("{{.playlist.tuner.title}}", select)
content.appendRow("Tuner / Streams", select)
} else {
var dbKey:string = "tuner"
if (data[dbKey] == undefined) {
@@ -1395,39 +1395,39 @@ function openPopUp(dataType, element) {
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("readonly", "true")
input.className = "notAvailable"
content.appendRow("{{.playlist.tuner.title}}", input)
content.appendRow("Tuner / Streams", input)
}
content.description("{{.playlist.tuner.description}}")
content.description("Number of parallel connections that can be established to the provider. <br>Only available with activated buffer.<br>New settings will only be applied after quitting all streams.")
// Interaktion
content.createInteraction()
// Löschen
if (data["id.provider"]!= "-") {
var input = content.createInput("button", "delete", "{{.button.delete}}")
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", "{{.button.back}}")
var input = content.createInput("button", "back", "Back")
input.setAttribute("onclick", 'javascript: openPopUp("playlist")')
content.addInteraction(input)
}
// Abbrechen
var input = content.createInput("button", "cancel", "{{.button.cancel}}")
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", "{{.button.update}}")
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", "{{.button.save}}")
var input = content.createInput("button", "save", "Save")
input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 0)')
content.addInteraction(input)
break
@@ -1437,20 +1437,20 @@ function openPopUp(dataType, element) {
// Name
var dbKey:string = "name"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.playlist.name.placeholder}}")
content.appendRow("{{.playlist.name.title}}", input)
input.setAttribute("placeholder", "Playlist name")
content.appendRow("Name", input)
// Beschreibung
var dbKey:string = "description"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.playlist.description.placeholder}}")
content.appendRow("{{.playlist.description.placeholder}}", input)
input.setAttribute("placeholder", "Description")
content.appendRow("Description", input)
// URL
var dbKey:string = "file.source"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}")
content.appendRow("{{.playlist.fileHDHR.title}}", input)
input.setAttribute("placeholder", "IP address and port (192.168.1.10:5004)")
content.appendRow("HDHomeRun IP", input)
// Tuner
if (SERVER["settings"]["buffer"] != "-") {
@@ -1465,7 +1465,7 @@ function openPopUp(dataType, element) {
var dbKey:string = "tuner"
var select = content.createSelect(text, values, data[dbKey], dbKey)
select.setAttribute("onfocus", "javascript: return;")
content.appendRow("{{.playlist.tuner.title}}", select)
content.appendRow("Tuner / Streams", select)
} else {
var dbKey:string = "tuner"
if (data[dbKey] == undefined) {
@@ -1474,39 +1474,39 @@ function openPopUp(dataType, element) {
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("readonly", "true")
input.className = "notAvailable"
content.appendRow("{{.playlist.tuner.title}}", input)
content.appendRow("Tuner / Streams", input)
}
content.description("{{.playlist.tuner.description}}")
content.description("Number of parallel connections that can be established to the provider. <br>Only available with activated buffer.<br>New settings will only be applied after quitting all streams.")
// Interaktion
content.createInteraction()
// Löschen
if (data["id.provider"]!= "-") {
var input = content.createInput("button", "delete", "{{.button.delete}}")
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", "{{.button.back}}")
var input = content.createInput("button", "back", "Back")
input.setAttribute("onclick", 'javascript: openPopUp("playlist")')
content.addInteraction(input)
}
// Abbrechen
var input = content.createInput("button", "cancel", "{{.button.cancel}}")
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", "{{.button.update}}")
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", "{{.button.save}}")
var input = content.createInput("button", "save", "Save")
input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 0)')
content.addInteraction(input)
break
@@ -1516,22 +1516,22 @@ function openPopUp(dataType, element) {
// Type
var dbKey:string = "type"
var text:string[] = ["M3U: " + "{{.filter.type.groupTitle}}", "xTeVe: " + "{{.filter.type.customFilter}}"]
var text:string[] = ["M3U: " + "Group Title", "xTeVe: " + "Custom Filter"]
var values:string[] = ["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("{{.filter.type.title}}", select)
content.appendRow("Type", select)
// Interaktion
content.createInteraction()
// Abbrechen
var input = content.createInput("button", "cancel", "{{.button.cancel}}")
var input = content.createInput("button", "cancel", "Cancel")
input.setAttribute("onclick", 'javascript: showElement("popup", false);')
content.addInteraction(input)
// Weiter
var input = content.createInput("button", "next", "{{.button.next}}")
var input = content.createInput("button", "next", "Next")
input.setAttribute("onclick", 'javascript: openPopUp("group-title")')
input.setAttribute("id", 'next')
content.addInteraction(input)
@@ -1542,25 +1542,25 @@ function openPopUp(dataType, element) {
switch (dataType) {
case "custom-filter":
content.createHeadline("{{.filter.custom}}")
content.createHeadline("Custom")
break;
case "group-title":
content.createHeadline("{{.filter.group}}")
content.createHeadline("Group")
break;
}
// Name
var dbKey:string = "name"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.filter.name.placeholder}}")
content.appendRow("{{.filter.name.title}}", input)
input.setAttribute("placeholder", "Filter name")
content.appendRow("Filter Name", input)
// Beschreibung
var dbKey:string = "description"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.filter.description.placeholder}}")
content.appendRow("{{.filter.description.title}}", input)
input.setAttribute("placeholder", "Description")
content.appendRow("Description", input)
// Typ
var dbKey:string = "type"
@@ -1576,13 +1576,13 @@ function openPopUp(dataType, element) {
var dbKey:string = "caseSensitive"
var input = content.createCheckbox(dbKey)
input.checked = data[dbKey]
content.appendRow("{{.filter.caseSensitive.title}}", input)
content.appendRow("Case Sensitive", input)
// Filterregel (Benutzerdefiniert)
var dbKey:string = "filter"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.filter.filterRule.placeholder}}")
content.appendRow("{{.filter.filterRule.title}}", input)
input.setAttribute("placeholder", "Sport {HD} !{ES,IT}")
content.appendRow("Filter Rule", input)
break;
@@ -1596,28 +1596,28 @@ function openPopUp(dataType, element) {
var select = content.createSelect(text, values, data[dbKey], dbKey)
select.setAttribute("onchange", "javascript: this.className = 'changed'")
content.appendRow("{{.filter.filterGroup.title}}", select)
content.description("{{.filter.filterGroup.description}}")
content.appendRow("Group Title", select)
content.description("Select a M3U group. (Counter)<br>Changing the group title in the M3U invalidates the filter.")
// Groß- Kleinschreibung beachten
var dbKey:string = "caseSensitive"
var input = content.createCheckbox(dbKey)
input.checked = data[dbKey]
content.appendRow("{{.filter.caseSensitive.title}}", input)
content.appendRow("Case Sensitive", input)
var dbKey:string = "include"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.filter.include.placeholder}}")
input.setAttribute("placeholder", "FHD,UHD")
content.appendRow("{{.filter.include.title}}", input)
content.description("{{.filter.include.description}}")
content.appendRow("Include", input)
content.description("Channel name must include.<br>(Comma separated) Comma means or")
var dbKey:string = "exclude"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.filter.exclude.placeholder}}")
content.appendRow("{{.filter.exclude.title}}", input)
content.description("{{.filter.exclude.description}}")
input.setAttribute("placeholder", "ES,IT")
content.appendRow("Exclude", input)
content.description("Channel name must not contain.<br>(Comma separated) Comma means or")
break
@@ -1629,18 +1629,18 @@ function openPopUp(dataType, element) {
content.createInteraction()
// Löschen
var input = content.createInput("button", "delete", "{{.button.delete}}")
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", "{{.button.cancel}}")
var input = content.createInput("button", "cancel", "Cancel")
input.setAttribute("onclick", 'javascript: showElement("popup", false);')
content.addInteraction(input)
// Speichern
var input = content.createInput("button", "save", "{{.button.save}}")
var input = content.createInput("button", "save", "Save")
input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", false, 0)')
content.addInteraction(input)
@@ -1651,68 +1651,68 @@ function openPopUp(dataType, element) {
// Name
var dbKey:string = "name"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.xmltv.name.placeholder}}")
content.appendRow("{{.xmltv.name.title}}", input)
input.setAttribute("placeholder", "Guide name")
content.appendRow("Name", input)
// Beschreibung
var dbKey:string = "description"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.xmltv.description.placeholder}}")
content.appendRow("{{.xmltv.description.title}}", input)
input.setAttribute("placeholder", "Description")
content.appendRow("Description", input)
// URL
var dbKey:string = "file.source"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.xmltv.fileXMLTV.placeholder}}")
content.appendRow("{{.xmltv.fileXMLTV.title}}", input)
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", "{{.button.delete}}")
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", "{{.button.cancel}}")
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", "{{.button.update}}")
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", "{{.button.save}}")
var input = content.createInput("button", "save", "Save")
input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 0)')
content.addInteraction(input)
break
case "users":
content.createHeadline("{{.mainMenu.item.users}}")
content.createHeadline("Users")
// Benutzername
var dbKey:string = "username"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.users.username.placeholder}}")
content.appendRow("{{.users.username.title}}", input)
input.setAttribute("placeholder", "Username")
content.appendRow("Username", input)
// Neues Passwort
var dbKey:string = "password"
var input = content.createInput("password", dbKey, "")
input.setAttribute("placeholder", "{{.users.password.placeholder}}")
content.appendRow("{{.users.password.title}}", input)
input.setAttribute("placeholder", "Password")
content.appendRow("Password", input)
// Bestätigung
var dbKey:string = "confirm"
var input = content.createInput("password", dbKey, "")
input.setAttribute("placeholder", "{{.users.confirm.placeholder}}")
content.appendRow("{{.users.confirm.title}}", input)
input.setAttribute("placeholder", "Password confirm")
content.appendRow("Confirm", input)
// Berechtigung WEB
var dbKey:string = "authentication.web"
@@ -1721,57 +1721,57 @@ function openPopUp(dataType, element) {
if (data["defaultUser"] == true) {
input.setAttribute("onclick", "javascript: return false")
}
content.appendRow("{{.users.web.title}}", input)
content.appendRow("Web Access", input)
// Berechtigung PMS
var dbKey:string = "authentication.pms"
var input = content.createCheckbox(dbKey)
input.checked = data[dbKey]
content.appendRow("{{.users.pms.title}}", input)
content.appendRow("PMS Access", input)
// Berechtigung M3U
var dbKey:string = "authentication.m3u"
var input = content.createCheckbox(dbKey)
input.checked = data[dbKey]
content.appendRow("{{.users.m3u.title}}", input)
content.appendRow("M3U Access", input)
// Berechtigung XML
var dbKey:string = "authentication.xml"
var input = content.createCheckbox(dbKey)
input.checked = data[dbKey]
content.appendRow("{{.users.xml.title}}", input)
content.appendRow("XML Access", input)
// Berechtigung API
var dbKey:string = "authentication.api"
var input = content.createCheckbox(dbKey)
input.checked = data[dbKey]
content.appendRow("{{.users.api.title}}", input)
content.appendRow("API Access", input)
// Interaktion
content.createInteraction()
// Löschen
if (data["defaultUser"]!= true && id != "-") {
var input = content.createInput("button", "delete", "{{.button.delete}}")
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", "{{.button.cancel}}")
var input = content.createInput("button", "cancel", "Cancel")
input.setAttribute("onclick", 'javascript: showElement("popup", false);')
content.addInteraction(input)
// Speichern
var input = content.createInput("button", "save", "{{.button.save}}")
var input = content.createInput("button", "save", "Save")
input.setAttribute("onclick", 'javascript: savePopupData("' + dataType + '", "' + id + '", "false");')
content.addInteraction(input)
break
case "mapping":
content.createHeadline("{{.mainMenu.item.mapping}}")
content.createHeadline("Mapping")
// Aktiv
var dbKey:string = "x-active"
var input = content.createCheckbox(dbKey)
@@ -1779,7 +1779,7 @@ function openPopUp(dataType, element) {
input.id = "active"
//input.setAttribute("onchange", "javascript: this.className = 'changed'")
input.setAttribute("onchange", "javascript: toggleChannelStatus('" + id + "', this)")
content.appendRow("{{.mapping.active.title}}", input)
content.appendRow("Active", input)
// Kanalname
var dbKey:string = "x-name"
@@ -1789,16 +1789,16 @@ function openPopUp(dataType, element) {
input.style.border = "solid 1px red"
input.setAttribute("readonly", "true")
}
content.appendRow("{{.mapping.channelName.title}}", input)
content.appendRow("Channel Name", input)
content.description(data["name"])
// Beschreibung
var dbKey:string = "x-description"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.mapping.description.placeholder}}")
input.setAttribute("placeholder", "Used by the Dummy as an XML description")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
content.appendRow("{{.mapping.description.title}}", input)
content.appendRow("Channel Description", input)
// Aktualisierung des Kanalnamens
if (data.hasOwnProperty("_uuid.key")) {
@@ -1807,7 +1807,7 @@ function openPopUp(dataType, element) {
var input = content.createCheckbox(dbKey)
input.setAttribute("onchange", "javascript: this.className = 'changed'")
input.checked = data[dbKey]
content.appendRow("{{.mapping.updateChannelName.title}}", input)
content.appendRow("Update Channel Name", input)
}
}
@@ -1816,7 +1816,7 @@ function openPopUp(dataType, element) {
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("onchange", "javascript: this.className = 'changed'")
input.setAttribute("id", "channel-icon")
content.appendRow("{{.mapping.channelLogo.title}}", input)
content.appendRow("Logo URL", input)
// Aktualisierung des Kanallogos
var dbKey:string = "x-update-channel-icon"
@@ -1824,7 +1824,7 @@ function openPopUp(dataType, element) {
input.checked = data[dbKey]
input.setAttribute("id", "update-icon")
input.setAttribute("onchange", "javascript: this.className = 'changed'; changeChannelLogo('" + id + "');")
content.appendRow("{{.mapping.updateChannelLogo.title}}", input)
content.appendRow("Update Channel Logo", input)
// Erweitern der EPG Kategorie
var dbKey:string = "x-category"
@@ -1832,13 +1832,13 @@ function openPopUp(dataType, element) {
var values:string[] = ["", "Kids", "News", "Movie", "Series", "Sports"]
var select = content.createSelect(text, values, data[dbKey], dbKey)
select.setAttribute("onchange", "javascript: this.className = 'changed'")
content.appendRow("{{.mapping.epgCategory.title}}", select)
content.appendRow("EPG Category", select)
// M3U Gruppentitel
var dbKey:string = "x-group-title"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("onchange", "javascript: this.className = 'changed'")
content.appendRow("{{.mapping.m3uGroupTitle.title}}", input)
content.appendRow("Group Title (xteve.m3u)", input)
if (data["group-title"] != undefined) {
content.description(data["group-title"])
@@ -1852,7 +1852,7 @@ function openPopUp(dataType, element) {
select.setAttribute("name", dbKey)
select.setAttribute("id", "popup-xmltv")
select.setAttribute("onchange", "javascript: this.className = 'changed'; setXmltvChannel('" + id + "',this);")
content.appendRow("{{.mapping.xmltvFile.title}}", select)
content.appendRow("XMLTV File", select)
var file = data[dbKey]
// XMLTV Mapping
@@ -1864,18 +1864,18 @@ function openPopUp(dataType, element) {
select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');")
sortSelect(select)
content.appendRow("{{.mapping.xmltvChannel.title}}", select)
content.appendRow("XMLTV Channel", select)
// Interaktion
content.createInteraction()
// Logo hochladen
var input = content.createInput("button", "cancel", "{{.button.uploadLogo}}")
var input = content.createInput("button", "cancel", "Upload Logo")
input.setAttribute("onclick", 'javascript: uploadLogo();')
content.addInteraction(input)
// Abbrechen
var input = content.createInput("button", "cancel", "{{.button.cancel}}")
var input = content.createInput("button", "cancel", "Cancel")
input.setAttribute("onclick", 'javascript: showElement("popup", false);')
content.addInteraction(input)
@@ -1886,7 +1886,7 @@ function openPopUp(dataType, element) {
ids.push(id)
}
var input = content.createInput("button", "save", "{{.button.done}}")
var input = content.createInput("button", "save", "Done")
input.setAttribute("onclick", 'javascript: donePopupData("' + dataType + '", "' + ids + '", "false");')
content.addInteraction(input)
break
+70 -70
View File
@@ -21,11 +21,11 @@ class SettingsCategory {
// Texteingaben
case "update":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.update.title}}" + ":"
tdLeft.innerHTML = "Schedule for updating (Playlist, XMLTV, Backup)" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "update", data.toString())
input.setAttribute("placeholder", "{{.settings.update.placeholder}}")
input.setAttribute("placeholder", "0000,1000,2000")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -35,11 +35,11 @@ class SettingsCategory {
case "backup.path":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.backupPath.title}}" + ":"
tdLeft.innerHTML = "Location for automatic backups" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "backup.path", data)
input.setAttribute("placeholder", "{{.settings.backupPath.placeholder}}")
input.setAttribute("placeholder", "/mnt/data/backup/xteve/")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -49,11 +49,11 @@ class SettingsCategory {
case "temp.path":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.tempPath.title}}" + ":"
tdLeft.innerHTML = "Location for the temporary files" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "temp.path", data)
input.setAttribute("placeholder", "{{.settings.tmpPath.placeholder}}")
input.setAttribute("placeholder", "/tmp/xteve/")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -63,11 +63,11 @@ class SettingsCategory {
case "user.agent":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.userAgent.title}}" + ":"
tdLeft.innerHTML = "User Agent" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "user.agent", data)
input.setAttribute("placeholder", "{{.settings.userAgent.placeholder}}")
input.setAttribute("placeholder", "xTeVe")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -77,11 +77,11 @@ class SettingsCategory {
case "plex.url":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.plexURL.title}}" + ":"
tdLeft.innerHTML = "Plex Server URL" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "plex.url", data)
input.setAttribute("placeholder", "{{.settings.plexURL.placeholder}}")
input.setAttribute("placeholder", "http://plex-host:32400")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -91,11 +91,11 @@ class SettingsCategory {
case "plex.token":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.plexToken.title}}" + ":"
tdLeft.innerHTML = "Plex API Token" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("password", "plex.token", data)
input.setAttribute("placeholder", "{{.settings.plexToken.placeholder}}")
input.setAttribute("placeholder", "Plex X-Plex-Token")
input.setAttribute("autocomplete", "off")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -106,11 +106,11 @@ class SettingsCategory {
case "buffer.timeout":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":"
tdLeft.innerHTML = "Timeout for new client connections" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "buffer.timeout", data)
input.setAttribute("placeholder", "{{.settings.bufferTimeout.placeholder}}")
input.setAttribute("placeholder", "100")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -120,11 +120,11 @@ class SettingsCategory {
case "ffmpeg.path":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.ffmpegPath.title}}" + ":"
tdLeft.innerHTML = "FFmpeg Binary Path" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "ffmpeg.path", data)
input.setAttribute("placeholder", "{{.settings.ffmpegPath.placeholder}}")
input.setAttribute("placeholder", "/path/to/ffmpeg")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -134,11 +134,11 @@ class SettingsCategory {
case "ffmpeg.options":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.ffmpegOptions.title}}" + ":"
tdLeft.innerHTML = "FFmpeg Options" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "ffmpeg.options", data)
input.setAttribute("placeholder", "{{.settings.ffmpegOptions.placeholder}}")
input.setAttribute("placeholder", "Leave blank to set default settings")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -148,11 +148,11 @@ class SettingsCategory {
case "vlc.path":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.vlcPath.title}}" + ":"
tdLeft.innerHTML = "VLC / CVLC Binary Path" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "vlc.path", data)
input.setAttribute("placeholder", "{{.settings.vlcPath.placeholder}}")
input.setAttribute("placeholder", "/path/to/cvlc")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -162,11 +162,11 @@ class SettingsCategory {
case "vlc.options":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.vlcOptions.title}}" + ":"
tdLeft.innerHTML = "VLC / CVLC Options" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "vlc.options", data)
input.setAttribute("placeholder", "{{.settings.vlcOptions.placeholder}}")
input.setAttribute("placeholder", "Leave blank to set default settings")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -177,7 +177,7 @@ class SettingsCategory {
// Checkboxen
case "authentication.web":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.authenticationWEB.title}}" + ":"
tdLeft.innerHTML = "WEB Authentication" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -191,7 +191,7 @@ class SettingsCategory {
case "authentication.pms":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.authenticationPMS.title}}" + ":"
tdLeft.innerHTML = "PMS Authentication" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -205,7 +205,7 @@ class SettingsCategory {
case "authentication.m3u":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.authenticationM3U.title}}" + ":"
tdLeft.innerHTML = "M3U Authentication" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -219,7 +219,7 @@ class SettingsCategory {
case "authentication.xml":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.authenticationXML.title}}" + ":"
tdLeft.innerHTML = "XML Authentication" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -233,7 +233,7 @@ class SettingsCategory {
case "authentication.api":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.authenticationAPI.title}}" + ":"
tdLeft.innerHTML = "API Authentication" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -247,7 +247,7 @@ class SettingsCategory {
case "files.update":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.filesUpdate.title}}" + ":"
tdLeft.innerHTML = "Updates all files at startup" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -261,7 +261,7 @@ class SettingsCategory {
case "cache.images":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.cacheImages.title}}" + ":"
tdLeft.innerHTML = "Image Caching" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -275,7 +275,7 @@ class SettingsCategory {
case "xepg.replace.missing.images":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.replaceEmptyImages.title}}" + ":"
tdLeft.innerHTML = "Replace missing program images" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -289,7 +289,7 @@ class SettingsCategory {
case "api":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
tdLeft.innerHTML = "API Interface" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -303,7 +303,7 @@ class SettingsCategory {
case "use_plexAPI":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.usePlexAPI.title}}" + ":"
tdLeft.innerHTML = "Use Plex API Refresh" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
@@ -318,10 +318,10 @@ class SettingsCategory {
// Select
case "xepg.missing.epg.mode":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.xepgMissingEPGMode.title}}" + ":"
tdLeft.innerHTML = "Missing EPG Handling" + ":"
var tdRight = document.createElement("TD")
var text:any[] = ["{{.settings.xepgMissingEPGMode.info_strict}}", "{{.settings.xepgMissingEPGMode.info_relaxed}}"]
var text:any[] = ["Strict (deactivate channels)", "Relaxed (keep active / dummy guide)"]
var values:any[] = ["strict", "relaxed"]
var select = content.createSelect(text, values, data, settingsKey)
@@ -334,7 +334,7 @@ class SettingsCategory {
case "tuner":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":"
tdLeft.innerHTML = "Number of Tuners" + ":"
var tdRight = document.createElement("TD")
var text = new Array()
@@ -355,7 +355,7 @@ class SettingsCategory {
case "epgSource":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.epgSource.title}}" + ":"
tdLeft.innerHTML = "EPG Source" + ":"
var tdRight = document.createElement("TD")
var text:any[] = ["PMS", "XEPG"]
@@ -371,7 +371,7 @@ class SettingsCategory {
case "backup.keep":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.backupKeep.title}}" + ":"
tdLeft.innerHTML = "Number of backups to keep" + ":"
var tdRight = document.createElement("TD")
var text:any[] = ["5", "10", "20", "30", "40", "50"]
@@ -387,7 +387,7 @@ class SettingsCategory {
case "buffer.size.kb":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.bufferSize.title}}" + ":"
tdLeft.innerHTML = "Buffer Size" + ":"
var tdRight = document.createElement("TD")
var text:any[] = ["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"]
@@ -403,10 +403,10 @@ class SettingsCategory {
case "buffer":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":"
tdLeft.innerHTML = "Stream Buffer" + ":"
var tdRight = document.createElement("TD")
var text:any[] = ["{{.settings.streamBuffering.info_false}}", "xTeVe: ({{.settings.streamBuffering.info_xteve}})", "FFmpeg: ({{.settings.streamBuffering.info_ffmpeg}})", "VLC: ({{.settings.streamBuffering.info_vlc}})"]
var text:any[] = ["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:any[] = ["-", "xteve", "ffmpeg", "vlc"]
var select = content.createSelect(text, values, data, settingsKey)
@@ -420,11 +420,11 @@ class SettingsCategory {
case "udpxy":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.udpxy.title}}" + ":"
tdLeft.innerHTML = "UDPxy address" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "udpxy", data)
input.setAttribute("placeholder", "{{.settings.udpxy.placeholder}}")
input.setAttribute("placeholder", "host:port")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
@@ -446,117 +446,117 @@ class SettingsCategory {
switch (settingsKey) {
case "authentication.web":
text = "{{.settings.authenticationWEB.description}}"
text = "Access to the web interface only possible with credentials."
break
case "authentication.m3u":
text = "{{.settings.authenticationM3U.description}}"
text = "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."
break
case "authentication.pms":
text = "{{.settings.authenticationPMS.description}}"
text = "Plex requests are only possible with authentication. <br><b>Warning!!!</b> After activating this function xTeVe must be delete in the PMS DVR settings and set up again."
break
case "authentication.xml":
text = "{{.settings.authenticationXML.description}}"
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 = "{{.settings.authenticationAPI.description}}"
text = "Access to the API interface is only possible with authentication."
}
break
case "backup.keep":
text = "{{.settings.backupKeep.description}}"
text = "Number of backups to keep. Older backups are automatically deleted."
break
case "backup.path":
text = "{{.settings.backupPath.description}}"
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 = "{{.settings.tempPath.description}}"
text = "Location for the buffer files."
break
case "buffer":
text = "{{.settings.streamBuffering.description}}"
text = "Functions of the buffer:<br>- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support<br>- RTP / RTPS support (only FFmpeg or VLC)<br>- Re-streaming<br>- Separate tuner limit for each playlist"
break
case "buffer.size.kb":
text = "{{.settings.bufferSize.description}}"
text = "Buffer size in MB.<br>M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used."
break
case "buffer.timeout":
text = "{{.settings.bufferTimeout.description}}"
text = "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds."
break
case "user.agent":
text = "{{.settings.userAgent.description}}"
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 = "{{.settings.plexURL.description}}"
text = "Base URL of your Plex server. Example: http://192.168.1.10:32400"
break
case "plex.token":
text = "{{.settings.plexToken.description}}"
text = "Plex token used for authenticated API calls to refresh your DVR guide."
break
case "ffmpeg.path":
text = "{{.settings.ffmpegPath.description}}"
text = "Path to FFmpeg binary."
break
case "ffmpeg.options":
text = "{{.settings.ffmpegOptions.description}}"
text = "FFmpeg options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings."
break
case "vlc.path":
text = "{{.settings.vlcPath.description}}"
text = "Path to VLC / CVLC binary."
break
case "vlc.options":
text = "{{.settings.vlcOptions.description}}"
text = "VLC / CVLC options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings."
break
case "epgSource":
text = "{{.settings.epgSource.description}}"
text = "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
break
case "tuner":
text = "{{.settings.tuner.description}}"
text = "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
break
case "update":
text = "{{.settings.update.description}}"
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 = "{{.settings.api.description}}"
text = "Via API interface it is possible to send commands to xTeVe. API documentation is <a href='https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md#api'>here</a>"
break
case "use_plexAPI":
text = "{{.settings.usePlexAPI.description}}"
text = "When enabled, xTeVe calls Plex directly to refresh DVR guide data after lineup or XEPG updates."
break
case "files.update":
text = "{{.settings.filesUpdate.description}}"
text = "Updates all playlists, tuner and XMLTV files at startup."
break
case "cache.images":
text = "{{.settings.cacheImages.description}}"
text = "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.<br>Downloading the images may take a while and will be done in the background."
break
case "xepg.replace.missing.images":
text = "{{.settings.replaceEmptyImages.description}}"
text = "If the poster in the XMLTV program is missing, the channel logo will be used."
break
case "xepg.missing.epg.mode":
text = "{{.settings.xepgMissingEPGMode.description}}"
text = "Strict: channels are deactivated when XMLTV mappings disappear.<br>Relaxed: channels stay active with last-known mappings and fall back to a dummy guide whenever guide data cannot be resolved."
break
case "udpxy":
text = "{{.settings.udpxy.description}}"
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:
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "None",
"outFile": "../html/js/app.js",
"strict": false,
"noEmitOnError": true,
"skipLibCheck": true,
"newLine": "lf",
"removeComments": false
},
"files": [
"network_ts.ts",
"menu_ts.ts",
"settings_ts.ts",
"logs_ts.ts",
"base_ts.ts",
"configuration_ts.ts",
"authentication_ts.ts"
]
}