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
+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"
]
}