Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f558a855ae | |||
| 125b0bb35f | |||
| 32c3d779c0 | |||
| ce5b12d8b8 | |||
| 76183bfaa2 | |||
| c577d354e7 | |||
| e48a061ca0 | |||
| ffd43d5217 | |||
| 9bd2b32003 | |||
| 8698983bbf | |||
| 60af423335 | |||
| 57b6be74e2 | |||
| c5545cbf08 | |||
| 339d2d0aa5 | |||
| a04b0ede50 | |||
| 84de46a2f2 | |||
| 5eaf5efdb6 | |||
| fb62353de4 | |||
| fa9d41e10c | |||
| e07d4dd878 | |||
| 6053207034 | |||
| 6a8b4bed28 | |||
| 45685ce592 | |||
| 43a9cf5a7e | |||
| a23cc7a183 | |||
| b069d5bee8 | |||
| 8cb9e43a72 |
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
docker-data
|
||||||
|
|
||||||
|
README-DEV.md
|
||||||
|
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
63
.drone.yml
Normal file
63
.drone.yml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: go-test
|
||||||
|
image: cache.coadcorp.com/library/golang
|
||||||
|
commands:
|
||||||
|
- go mod download
|
||||||
|
- go test ./...
|
||||||
|
|
||||||
|
- name: go-build
|
||||||
|
image: cache.coadcorp.com/library/golang
|
||||||
|
commands:
|
||||||
|
- XTEVE_VERSION="$(grep -m1 '^#### ' changelog-beta.md | cut -d' ' -f2 | sed 's/-beta$//')"
|
||||||
|
- test -n "$XTEVE_VERSION" || (echo "Could not parse version from changelog-beta.md" && exit 1)
|
||||||
|
- echo "Building xTeVe version $XTEVE_VERSION from changelog-beta.md"
|
||||||
|
- go build -v -ldflags "-X main.Version=$XTEVE_VERSION" ./...
|
||||||
|
|
||||||
|
- name: dockerfile-lint
|
||||||
|
image: cache.coadcorp.com/hadolint/hadolint:v2.12.0-alpine
|
||||||
|
commands:
|
||||||
|
- hadolint -t error Dockerfile
|
||||||
|
|
||||||
|
- name: compose-validate
|
||||||
|
image: cache.coadcorp.com/library/docker:27-cli
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache docker-cli-compose
|
||||||
|
- docker compose -f docker-compose.yml config -q
|
||||||
|
- docker compose -f docker-compose.host.yml config -q
|
||||||
|
|
||||||
|
- name: docker-build-validate
|
||||||
|
image: gcr.io/kaniko-project/executor:v1.23.2-debug
|
||||||
|
commands:
|
||||||
|
- /kaniko/executor --context "${DRONE_WORKSPACE}" --dockerfile "${DRONE_WORKSPACE}/Dockerfile" --no-push --destination xteve:validate --build-arg TARGETOS=linux --build-arg TARGETARCH=amd64 --build-arg XTEVE_UID=1000 --build-arg XTEVE_GID=1000
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
- name: docker-publish
|
||||||
|
image: plugins/docker
|
||||||
|
environment:
|
||||||
|
XTEVE_UID: "1000"
|
||||||
|
XTEVE_GID: "1000"
|
||||||
|
settings:
|
||||||
|
registry: registry.coadcorp.com
|
||||||
|
repo: registry.coadcorp.com/nathan/xteve
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
username: nathan
|
||||||
|
password:
|
||||||
|
from_secret: registry_password
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- ${DRONE_COMMIT_SHA}
|
||||||
|
build_args:
|
||||||
|
- TARGETOS=linux
|
||||||
|
- TARGETARCH=amd64
|
||||||
|
build_args_from_env:
|
||||||
|
- XTEVE_UID
|
||||||
|
- XTEVE_GID
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
52
Dockerfile
Normal file
52
Dockerfile
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
FROM golang:1.26-alpine AS builder
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
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$//')" \
|
||||||
|
&& test -n "$XTEVE_VERSION" \
|
||||||
|
&& CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
||||||
|
go build -trimpath -ldflags="-s -w -X main.Version=$XTEVE_VERSION" -o /out/xteve ./xteve.go
|
||||||
|
|
||||||
|
FROM mwader/static-ffmpeg:latest AS ffmpeg
|
||||||
|
|
||||||
|
FROM alpine:3.23
|
||||||
|
|
||||||
|
ARG XTEVE_UID=1000
|
||||||
|
ARG XTEVE_GID=1000
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata \
|
||||||
|
&& addgroup -S -g "${XTEVE_GID}" xteve \
|
||||||
|
&& adduser -S -D -H -u "${XTEVE_UID}" -G xteve xteve \
|
||||||
|
&& mkdir -p /xteve/config \
|
||||||
|
&& chown -R xteve:xteve /xteve
|
||||||
|
|
||||||
|
WORKDIR /xteve
|
||||||
|
|
||||||
|
COPY --from=builder /out/xteve /usr/local/bin/xteve
|
||||||
|
COPY --from=ffmpeg /ffmpeg /usr/local/bin/ffmpeg
|
||||||
|
COPY --from=ffmpeg /ffprobe /usr/local/bin/ffprobe
|
||||||
|
COPY docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
USER xteve
|
||||||
|
|
||||||
|
EXPOSE 34400/tcp
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
54
README.md
54
README.md
@@ -48,6 +48,59 @@ Documentation for setup and configuration is [here](https://github.com/xteve-pro
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Project Analysis (UI + Operations)
|
||||||
|
|
||||||
|
The core architecture is strong: a Go backend with websocket-driven UI updates, filesystem-based state, and very low runtime overhead.
|
||||||
|
The weakest points are mostly operational and UX-focused:
|
||||||
|
|
||||||
|
* UI was historically utility-first and desktop-biased, with limited responsive behavior and visual hierarchy.
|
||||||
|
* Container usage was documented externally but there was no first-party Dockerfile/compose setup in this repository.
|
||||||
|
* Static web assets are generated into `src/webUI.go`, which works, but creates large diffs and a heavier edit/build cycle.
|
||||||
|
|
||||||
|
### Recommended next technical improvements
|
||||||
|
|
||||||
|
1. Replace generated `src/webUI.go` with Go `embed` for simpler static asset management and cleaner PR diffs.
|
||||||
|
2. Add CI checks (`go test ./...`, build on Linux/arm64/amd64, docker build smoke test).
|
||||||
|
3. Add a dedicated health endpoint (for example `/healthz`) to decouple health checks from HDHomeRun endpoints.
|
||||||
|
4. Add integration tests around websocket commands that mutate settings/files to reduce regression risk.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Container-First Run (Included In This Repo)
|
||||||
|
|
||||||
|
### Build image
|
||||||
|
```bash
|
||||||
|
docker build -t xteve:local .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run with Docker Compose (bridge mode)
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose file: `docker-compose.yml`
|
||||||
|
Persistent config volume: `./docker-data/config:/xteve/config`
|
||||||
|
|
||||||
|
### Run with Docker Compose (host networking, Linux recommended for discovery)
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.host.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Host networking improves LAN discovery behavior (SSDP/DLNA) for Plex/Emby in many setups.
|
||||||
|
|
||||||
|
### Container environment variables
|
||||||
|
|
||||||
|
* `XTEVE_CONFIG` (default: `/xteve/config`)
|
||||||
|
* `XTEVE_PORT` (default: `34400`)
|
||||||
|
|
||||||
|
### Image details
|
||||||
|
|
||||||
|
* 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`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Downloads v2 | 64 Bit only
|
## Downloads v2 | 64 Bit only
|
||||||
#### 64 Bit Intel / AMD
|
#### 64 Bit Intel / AMD
|
||||||
|
|
||||||
@@ -156,4 +209,3 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
#### 2.2.0.0201-beta
|
||||||
|
Add UI enhancement for range selecting checkboxes
|
||||||
|
|
||||||
|
#### 2.2.0.0200-beta
|
||||||
|
```diff
|
||||||
|
+ Major web UI redesign focused on cleaner layout, better information hierarchy, and faster daily workflows.
|
||||||
|
+ Large mobile UX overhaul: responsive navigation, improved spacing, touch-friendly controls, and better small-screen mapping/config flows.
|
||||||
|
+ Accessibility pass across auth, configuration, and main app screens (focus visibility, keyboard flow, ARIA/announcer updates, contrast and status feedback improvements).
|
||||||
|
+ Settings UX improvements and additional polish in frontend behavior for menu, configuration, and authentication interactions.
|
||||||
|
+ Added optional Plex API refresh integration with new settings: use_plexAPI, plex.url, plex.token.
|
||||||
|
+ Added debounced/queued Plex DVR guide reload workflow after lineup and XEPG updates to reduce manual refresh work in Plex.
|
||||||
|
+ Container/runtime improvements and validation updates for easier, safer container usage.
|
||||||
|
+ Added Drone CI pipeline for tests/build checks, Docker/Compose validation, and Docker image publishing to registry.coadcorp.com via plugins/docker.
|
||||||
|
+ Pipeline build version is now derived from changelog-beta.md and validated against source version to prevent release/version drift.
|
||||||
|
```
|
||||||
|
|
||||||
#### 2.1.1.0116-beta
|
#### 2.1.1.0116-beta
|
||||||
If no user agent is specified, the default FFmpeg or VLC user agent is used.
|
If no user agent is specified, the default FFmpeg or VLC user agent is used.
|
||||||
|
|
||||||
|
|||||||
15
cmd/webui-gen/main.go
Normal file
15
cmd/webui-gen/main.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
docker-compose.host.yml
Normal file
18
docker-compose.host.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
services:
|
||||||
|
xteve:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
XTEVE_UID: ${XTEVE_UID:-1000}
|
||||||
|
XTEVE_GID: ${XTEVE_GID:-1000}
|
||||||
|
container_name: xteve
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
environment:
|
||||||
|
XTEVE_CONFIG: /xteve/config
|
||||||
|
XTEVE_PORT: "34400"
|
||||||
|
XTEVE_UID: ${XTEVE_UID:-1000}
|
||||||
|
XTEVE_GID: ${XTEVE_GID:-1000}
|
||||||
|
volumes:
|
||||||
|
- ./docker-data/config:/xteve/config
|
||||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
services:
|
||||||
|
xteve:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
XTEVE_UID: ${XTEVE_UID:-1000}
|
||||||
|
XTEVE_GID: ${XTEVE_GID:-1000}
|
||||||
|
container_name: xteve
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
XTEVE_CONFIG: /xteve/config
|
||||||
|
XTEVE_PORT: "34400"
|
||||||
|
XTEVE_UID: ${XTEVE_UID:-1000}
|
||||||
|
XTEVE_GID: ${XTEVE_GID:-1000}
|
||||||
|
ports:
|
||||||
|
- "34400:34400/tcp"
|
||||||
|
- "1900:1900/udp"
|
||||||
|
volumes:
|
||||||
|
- ./docker-data/config:/xteve/config
|
||||||
97
docker/entrypoint.sh
Executable file
97
docker/entrypoint.sh
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DEFAULT_CONFIG_DIR="/xteve/config"
|
||||||
|
LEGACY_CONFIG_DIRS="/config /xteve /home/xteve/.xteve"
|
||||||
|
|
||||||
|
resolve_config_dir() {
|
||||||
|
if [ -n "${XTEVE_CONFIG:-}" ] && [ "${XTEVE_CONFIG}" != "${DEFAULT_CONFIG_DIR}" ]; then
|
||||||
|
printf "%s" "${XTEVE_CONFIG}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "${DEFAULT_CONFIG_DIR}/settings.json" ]; then
|
||||||
|
printf "%s" "${DEFAULT_CONFIG_DIR}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for dir in ${LEGACY_CONFIG_DIRS}; do
|
||||||
|
if [ -f "${dir}/settings.json" ]; then
|
||||||
|
printf "%s" "${dir}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s" "${DEFAULT_CONFIG_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_if_missing() {
|
||||||
|
src="$1"
|
||||||
|
dst="$2"
|
||||||
|
|
||||||
|
if [ -e "${src}" ] && [ ! -e "${dst}" ]; then
|
||||||
|
cp -R "${src}" "${dst}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_initialized() {
|
||||||
|
file="$1"
|
||||||
|
|
||||||
|
if [ ! -s "${file}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q '"uuid"' "${file}" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_DIR="$(resolve_config_dir)"
|
||||||
|
PORT="${XTEVE_PORT:-34400}"
|
||||||
|
|
||||||
|
mkdir -p "${CONFIG_DIR}"
|
||||||
|
|
||||||
|
if ! settings_initialized "${CONFIG_DIR}/settings.json"; then
|
||||||
|
for legacy_dir in ${LEGACY_CONFIG_DIRS}; do
|
||||||
|
if [ "${legacy_dir}" = "${CONFIG_DIR}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if settings_initialized "${legacy_dir}/settings.json"; then
|
||||||
|
echo "[entrypoint] Migrating existing configuration from ${legacy_dir} to ${CONFIG_DIR}"
|
||||||
|
|
||||||
|
for file in authentication.json pms.json settings.json xepg.json urls.json; do
|
||||||
|
if [ "${file}" = "settings.json" ] || [ "${file}" = "xepg.json" ] || [ "${file}" = "urls.json" ]; then
|
||||||
|
cp -f "${legacy_dir}/${file}" "${CONFIG_DIR}/${file}" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
copy_if_missing "${legacy_dir}/${file}" "${CONFIG_DIR}/${file}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for dir_name in data cache backup tmp; do
|
||||||
|
copy_if_missing "${legacy_dir}/${dir_name}" "${CONFIG_DIR}/${dir_name}"
|
||||||
|
done
|
||||||
|
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! touch "${CONFIG_DIR}/.xteve-write-test" 2>/dev/null; then
|
||||||
|
echo "[entrypoint] ERROR: Config directory is not writable: ${CONFIG_DIR}" >&2
|
||||||
|
echo "[entrypoint] Running as UID:GID $(id -u):$(id -g)" >&2
|
||||||
|
ls -ld "${CONFIG_DIR}" >&2 || true
|
||||||
|
echo "[entrypoint] Hint: ensure host path ownership/permissions allow this UID:GID to write, or set matching container UID/GID at build time." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -f "${CONFIG_DIR}/.xteve-write-test"
|
||||||
|
|
||||||
|
echo "[entrypoint] Using config directory: ${CONFIG_DIR}"
|
||||||
|
echo "[entrypoint] Running as UID:GID $(id -u):$(id -g)"
|
||||||
|
if [ -f "${CONFIG_DIR}/settings.json" ]; then
|
||||||
|
echo "[entrypoint] settings.json details: $(ls -l "${CONFIG_DIR}/settings.json" | awk '{print $1, $3, $4, $5, $9}')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /usr/local/bin/xteve -config "${CONFIG_DIR}" -port "${PORT}" "$@"
|
||||||
13
go.mod
13
go.mod
@@ -1,9 +1,14 @@
|
|||||||
module xteve
|
module xteve
|
||||||
|
|
||||||
go 1.16
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||||
github.com/koron/go-ssdp v0.0.2 // indirect
|
github.com/koron/go-ssdp v0.1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/net v0.50.0 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -1,16 +1,28 @@
|
|||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o=
|
github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o=
|
||||||
github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs=
|
github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs=
|
||||||
|
github.com/koron/go-ssdp v0.1.0 h1:ckl5x5H6qSNFmi+wCuROvvGUu2FQnMbQrU95IHCcv3Y=
|
||||||
|
github.com/koron/go-ssdp v0.1.0/go.mod h1:GltaDBjtK1kemZOusWYLGotV0kBeEf59Bp0wtSB0uyU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||||
|
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
@@ -13,16 +13,16 @@
|
|||||||
<script language="javascript" type="text/javascript" src="js/base_ts.js"></script>
|
<script language="javascript" type="text/javascript" src="js/base_ts.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body onload="javascript: readyForConfiguration(0);">
|
<body class="auth-screen wizard-screen" onload="javascript: readyForConfiguration(0);">
|
||||||
|
|
||||||
<div id="loading" class="block">
|
<div id="loading" class="block" role="status" aria-live="polite" aria-label="Loading" aria-hidden="false">
|
||||||
<div class="loader"></div>
|
<div class="loader"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="header" class="imgCenter"></div>
|
<div id="header" class="imgCenter"></div>
|
||||||
<div id="box">
|
<main id="box" role="main" aria-labelledby="head-text">
|
||||||
|
|
||||||
<table id="clientInfo" class="visible">
|
<table id="clientInfo" class="visible" aria-label="Server information">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tdKey">Version:</td>
|
<td class="tdKey">Version:</td>
|
||||||
<td id="version" class="tdVal"> </td>
|
<td id="version" class="tdVal"> </td>
|
||||||
@@ -46,13 +46,14 @@
|
|||||||
<div id="headline">
|
<div id="headline">
|
||||||
<h1 id="head-text" class="center">Configuration</h1>
|
<h1 id="head-text" class="center">Configuration</h1>
|
||||||
</div>
|
</div>
|
||||||
<p id="err" class="errorMsg center"></p>
|
<p id="err" class="errorMsg center" role="alert" aria-live="assertive" aria-atomic="true"></p>
|
||||||
<div id="content">
|
<div id="content" role="region" aria-live="polite" aria-busy="false" tabindex="-1" aria-label="Configuration step">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<p id="sr-announcer" class="sr-only" aria-live="polite" aria-atomic="true"></p>
|
||||||
<div id="box-footer">
|
<div id="box-footer">
|
||||||
<input id="next" class="" type="button" name="next" value="Next" onclick="javascript: saveWizard();">
|
<input id="next" class="" type="button" name="next" value="Next" aria-controls="content" onclick="javascript: saveWizard();">
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -10,38 +10,38 @@
|
|||||||
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="auth-screen">
|
||||||
|
|
||||||
<div id="header" class="imgCenter"></div>
|
<div id="header" class="imgCenter"></div>
|
||||||
|
|
||||||
<div id="box">
|
<main id="box" role="main" aria-labelledby="head-text">
|
||||||
|
|
||||||
<div id="headline">
|
<div id="headline">
|
||||||
<h1 id="head-text" class="center">{{.account.headline}}</h1>
|
<h1 id="head-text" class="center">{{.account.headline}}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="err" class="errorMsg center"></p>
|
<p id="err" class="errorMsg center" role="alert" aria-live="assertive" aria-atomic="true"></p>
|
||||||
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
|
||||||
<form id="authentication" action="" method="post">
|
<form id="authentication" action="" method="post" aria-describedby="err" novalidate>
|
||||||
|
|
||||||
<h5>{{.account.username.title}}:</h5>
|
<label for="username">{{.account.username.title}}:</label>
|
||||||
<input id="username" type="text" name="username" placeholder="Username" value="">
|
<input id="username" type="text" name="username" placeholder="Username" value="" autocomplete="username">
|
||||||
<h5>{{.account.password.title}}:</h5>
|
<label for="password">{{.account.password.title}}:</label>
|
||||||
<input id="password" type="password" name="password" placeholder="Password" value="">
|
<input id="password" type="password" name="password" placeholder="Password" value="" autocomplete="new-password">
|
||||||
<h5>{{.account.confirm.title}}:</h5>
|
<label for="confirm">{{.account.confirm.title}}:</label>
|
||||||
<input id="confirm" type="password" name="confirm" placeholder="Confirm" value="">
|
<input id="confirm" type="password" name="confirm" placeholder="Confirm" value="" autocomplete="new-password">
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="box-footer">
|
<div id="box-footer">
|
||||||
<input id="submit" class="" type="button" value="{{.button.craeteAccount}}" onclick="javascript: login();">
|
<input id="submit" class="" type="button" value="{{.button.craeteAccount}}" aria-label="{{.button.craeteAccount}}" onclick="javascript: login();">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
1348
html/css/screen.css
1348
html/css/screen.css
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<!---
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
-->
|
|
||||||
<title>xTeVe</title>
|
<title>xTeVe</title>
|
||||||
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
||||||
<link rel="stylesheet" href="css/base.css" type="text/css">
|
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||||
@@ -17,16 +15,19 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body onload="javascript: PageReady();">
|
<body class="app-shell" onload="javascript: PageReady();">
|
||||||
|
|
||||||
<div id="loading" class="none">
|
<a class="skip-link" href="#content">Skip to main content</a>
|
||||||
|
|
||||||
|
<div id="loading" class="none" role="status" aria-live="polite" aria-label="Loading" aria-hidden="true">
|
||||||
<div class="loader"></div>
|
<div class="loader"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="popup" class="none">
|
<div id="popup" class="none" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1">
|
||||||
<div id="popup-custom"></div>
|
<div id="popup-custom" role="document" tabindex="-1"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="layout-overlay" aria-hidden="true" tabindex="-1"></div>
|
||||||
<div id="layout">
|
<div id="layout">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -40,55 +41,62 @@
|
|||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div id="menu-wrapper" class="layout-left">
|
<aside id="menu-wrapper" class="layout-left" aria-label="Sidebar menu">
|
||||||
<div id= "branch"></div>
|
<div id= "branch"></div>
|
||||||
<div id="logo"></div>
|
<div id="logo"></div>
|
||||||
<nav id="main-menu"></nav>
|
<nav id="main-menu" role="menubar" aria-label="Main navigation"></nav>
|
||||||
</div>
|
</aside>
|
||||||
|
|
||||||
<div class="layout-right">
|
<main id="shell-main" class="layout-right">
|
||||||
|
<header id="shell-header">
|
||||||
|
<button id="menu-toggle" type="button" aria-expanded="false" aria-controls="menu-wrapper" aria-label="Toggle navigation menu">Menu</button>
|
||||||
|
<h2 id="shell-title">xTeVe Control Panel</h2>
|
||||||
|
<p id="connection-indicator" class="status-idle" role="status" aria-live="polite" aria-atomic="true">Connecting...</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
<table id="clientInfo" class="">
|
<table id="clientInfo" class="" aria-label="Server information">
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tdKey">xTeVe:</td>
|
<td class="tdKey">xTeVe:</td>
|
||||||
<td id="version" class="tdVal"> </td>
|
<td id="version" class="tdVal" data-label="xTeVe"> </td>
|
||||||
<td class="tdKey">OS:</td>
|
<td class="tdKey">OS:</td>
|
||||||
<td id="os" class="tdVal"> </td>
|
<td id="os" class="tdVal" data-label="OS"> </td>
|
||||||
<td class="tdKey phone">DVR IP:</td>
|
<td class="tdKey phone">DVR IP:</td>
|
||||||
<td id="DVR" class="tdVal phone"> </td>
|
<td id="DVR" class="tdVal phone" data-label="DVR IP"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tdKey">UUID:</td>
|
<td class="tdKey">UUID:</td>
|
||||||
<td id="uuid" class="tdVal"> </td>
|
<td id="uuid" class="tdVal" data-label="UUID"> </td>
|
||||||
<td class="tdKey">Arch:</td>
|
<td class="tdKey">Arch:</td>
|
||||||
<td id="arch" class="tdVal"> </td>
|
<td id="arch" class="tdVal" data-label="Arch"> </td>
|
||||||
<td class="tdKey phone">M3U URL:</td>
|
<td class="tdKey phone">M3U URL:</td>
|
||||||
<td id="m3u-url" class="tdVal phone"> </td>
|
<td id="m3u-url" class="tdVal phone" data-label="M3U URL"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tdKey">Available Streams:</td>
|
<td class="tdKey">Available Streams:</td>
|
||||||
<td id="streams" class="tdVal"> </td>
|
<td id="streams" class="tdVal" data-label="Available Streams"> </td>
|
||||||
<td class="tdKey">EPG Source:</td>
|
<td class="tdKey">EPG Source:</td>
|
||||||
<td id="epgSource" class="tdVal"> </td>
|
<td id="epgSource" class="tdVal" data-label="EPG Source"> </td>
|
||||||
<td class="tdKey phone">XEPG URL:</td>
|
<td class="tdKey phone">XEPG URL:</td>
|
||||||
<td id="xepg-url" class="tdVal phone"> </td>
|
<td id="xepg-url" class="tdVal phone" data-label="XEPG URL"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tdKey">XEPG Channels:</td>
|
<td class="tdKey">XEPG Channels:</td>
|
||||||
<td id="xepg" class="tdVal"> </td>
|
<td id="xepg" class="tdVal" data-label="XEPG Channels"> </td>
|
||||||
<td class="tdKey">Errors:</td>
|
<td class="tdKey">Errors:</td>
|
||||||
<td id="errors" class="tdVal"> </td>
|
<td id="errors" class="tdVal" data-label="Errors"> </td>
|
||||||
<td class="tdKey">Warnings:</td>
|
<td class="tdKey">Warnings:</td>
|
||||||
<td id="warnings" class="tdVal"> </td>
|
<td id="warnings" class="tdVal" data-label="Warnings"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="myStreamsBox" class="notVisible">
|
<div id="status-cards" class="dashboard-cards" role="list" aria-live="polite" aria-label="System summary"></div>
|
||||||
|
|
||||||
|
<div id="myStreamsBox" class="notVisible" aria-live="polite" aria-label="Stream details">
|
||||||
|
|
||||||
<div id="allStreams">
|
<div id="allStreams">
|
||||||
<table id="activeStreams"></table>
|
<table id="activeStreams"></table>
|
||||||
@@ -97,9 +105,10 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="content" class=""></div>
|
<div id="content" class="" role="region" aria-live="polite" aria-busy="false" tabindex="-1" aria-label="Main content"></div>
|
||||||
|
<p id="sr-announcer" class="sr-only" aria-live="polite" aria-atomic="true"></p>
|
||||||
|
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
function login() {
|
function login() {
|
||||||
var err = false;
|
var err = false;
|
||||||
|
var firstInvalid = null;
|
||||||
var data = new Object();
|
var data = new Object();
|
||||||
var div = document.getElementById("content");
|
var div = document.getElementById("content");
|
||||||
var form = document.getElementById("authentication");
|
var form = document.getElementById("authentication");
|
||||||
|
var errElement = document.getElementById("err");
|
||||||
|
if (errElement != null) {
|
||||||
|
errElement.innerHTML = "";
|
||||||
|
}
|
||||||
var inputs = div.getElementsByTagName("INPUT");
|
var inputs = div.getElementsByTagName("INPUT");
|
||||||
console.log(inputs);
|
console.log(inputs);
|
||||||
for (var i = inputs.length - 1; i >= 0; i--) {
|
for (var i = inputs.length - 1; i >= 0; i--) {
|
||||||
@@ -10,23 +15,54 @@ function login() {
|
|||||||
var value = inputs[i].value;
|
var value = inputs[i].value;
|
||||||
if (value.length == 0) {
|
if (value.length == 0) {
|
||||||
inputs[i].style.borderColor = "red";
|
inputs[i].style.borderColor = "red";
|
||||||
|
inputs[i].setAttribute("aria-invalid", "true");
|
||||||
|
if (firstInvalid == null) {
|
||||||
|
firstInvalid = inputs[i];
|
||||||
|
}
|
||||||
err = true;
|
err = true;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
inputs[i].style.borderColor = "";
|
||||||
|
inputs[i].setAttribute("aria-invalid", "false");
|
||||||
|
}
|
||||||
data[key] = value;
|
data[key] = value;
|
||||||
}
|
}
|
||||||
if (err == true) {
|
if (err == true) {
|
||||||
|
if (firstInvalid != null) {
|
||||||
|
firstInvalid.focus();
|
||||||
|
}
|
||||||
data = new Object();
|
data = new Object();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty("confirm")) {
|
if (data.hasOwnProperty("confirm")) {
|
||||||
if (data["confirm"] != data["password"]) {
|
if (data["confirm"] != data["password"]) {
|
||||||
alert("sdafsd");
|
|
||||||
document.getElementById('password').style.borderColor = "red";
|
document.getElementById('password').style.borderColor = "red";
|
||||||
document.getElementById('confirm').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("err").innerHTML = "{{.account.failed}}";
|
||||||
|
document.getElementById('password').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(data);
|
console.log(data);
|
||||||
form.submit();
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ var SEARCH_MAPPING = new Object();
|
|||||||
var UNDO = new Object();
|
var UNDO = new Object();
|
||||||
var SERVER_CONNECTION = false;
|
var SERVER_CONNECTION = false;
|
||||||
var WS_AVAILABLE = false;
|
var WS_AVAILABLE = false;
|
||||||
// Menü
|
var ACTIVE_MENU_ID = "";
|
||||||
|
var LAST_FOCUSED_ELEMENT = null;
|
||||||
|
var LAST_BULK_CHECKBOX = null;
|
||||||
|
// Menu
|
||||||
var menuItems = new Array();
|
var menuItems = new Array();
|
||||||
menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}"));
|
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("pmsID", "{{.mainMenu.item.pmsID}}", "number.png", "{{.mainMenu.headline.pmsID}}"))
|
||||||
@@ -18,8 +21,8 @@ menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.
|
|||||||
menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}"));
|
menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}"));
|
||||||
// Kategorien für die Einstellungen
|
// Kategorien für die Einstellungen
|
||||||
var settingsCategory = new Array();
|
var settingsCategory = new Array();
|
||||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"));
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api,use_plexAPI,plex.url,plex.token"));
|
||||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"));
|
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.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.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("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
|
||||||
@@ -44,7 +47,74 @@ function showElement(elmID, type) {
|
|||||||
cssClass = "none";
|
cssClass = "none";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
document.getElementById(elmID).className = cssClass;
|
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) {
|
function changeButtonAction(element, buttonID, attribute) {
|
||||||
var value = element.options[element.selectedIndex].value;
|
var value = element.options[element.selectedIndex].value;
|
||||||
@@ -114,6 +184,48 @@ function getAllSelectedChannels() {
|
|||||||
}
|
}
|
||||||
return channels;
|
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() {
|
function selectAllChannels() {
|
||||||
var bulk = false;
|
var bulk = false;
|
||||||
var trs = document.getElementById("content_table").getElementsByTagName("TR");
|
var trs = document.getElementById("content_table").getElementsByTagName("TR");
|
||||||
@@ -132,6 +244,7 @@ function selectAllChannels() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LAST_BULK_CHECKBOX = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
function bulkEdit() {
|
function bulkEdit() {
|
||||||
@@ -150,6 +263,7 @@ function bulkEdit() {
|
|||||||
rows[i].className = className;
|
rows[i].className = className;
|
||||||
rows[i].checked = false;
|
rows[i].checked = false;
|
||||||
}
|
}
|
||||||
|
LAST_BULK_CHECKBOX = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
function sortTable(column) {
|
function sortTable(column) {
|
||||||
@@ -160,15 +274,29 @@ function sortTable(column) {
|
|||||||
var table = document.getElementById("content_table");
|
var table = document.getElementById("content_table");
|
||||||
var tableHead = table.getElementsByTagName("TR")[0];
|
var tableHead = table.getElementsByTagName("TR")[0];
|
||||||
var tableItems = tableHead.getElementsByTagName("TD");
|
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 sortObj = new Object();
|
||||||
var x, xValue;
|
var x, xValue;
|
||||||
var tableHeader;
|
var tableHeader;
|
||||||
var sortByString = false;
|
var sortByString = false;
|
||||||
if (column > 0 && COLUMN_TO_SORT > 0) {
|
if (column > 0 && COLUMN_TO_SORT > 0) {
|
||||||
tableItems[COLUMN_TO_SORT].className = "pointer";
|
tableItems[COLUMN_TO_SORT].classList.remove("sortThis");
|
||||||
tableItems[column].className = "sortThis";
|
tableItems[COLUMN_TO_SORT].classList.add("pointer");
|
||||||
|
tableItems[column].classList.remove("pointer");
|
||||||
|
tableItems[column].classList.add("sortThis");
|
||||||
}
|
}
|
||||||
COLUMN_TO_SORT = column;
|
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;
|
var rows = table.rows;
|
||||||
if (rows[1] != undefined) {
|
if (rows[1] != undefined) {
|
||||||
tableHeader = rows[0];
|
tableHeader = rows[0];
|
||||||
@@ -228,6 +356,7 @@ function createSearchObj() {
|
|||||||
var channels = getObjKeys(data);
|
var channels = getObjKeys(data);
|
||||||
var channelKeys = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title", "x-xmltv-file"];
|
var channelKeys = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title", "x-xmltv-file"];
|
||||||
channels.forEach(function (id) {
|
channels.forEach(function (id) {
|
||||||
|
SEARCH_MAPPING[id] = "";
|
||||||
channelKeys.forEach(function (key) {
|
channelKeys.forEach(function (key) {
|
||||||
if (key == "x-active") {
|
if (key == "x-active") {
|
||||||
switch (data[id][key]) {
|
switch (data[id][key]) {
|
||||||
@@ -260,6 +389,9 @@ function searchInMapping() {
|
|||||||
for (var i = 1; i < trs.length; ++i) {
|
for (var i = 1; i < trs.length; ++i) {
|
||||||
var id = trs[i].getAttribute("id");
|
var id = trs[i].getAttribute("id");
|
||||||
var element = SEARCH_MAPPING[id];
|
var element = SEARCH_MAPPING[id];
|
||||||
|
if (element == undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
switch (element.toLowerCase().includes(searchValue.toLowerCase())) {
|
switch (element.toLowerCase().includes(searchValue.toLowerCase())) {
|
||||||
case true:
|
case true:
|
||||||
document.getElementById(id).style.display = "";
|
document.getElementById(id).style.display = "";
|
||||||
@@ -269,17 +401,19 @@ function searchInMapping() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
announceToScreenReader("Search updated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
function calculateWrapperHeight() {
|
function calculateWrapperHeight() {
|
||||||
if (document.getElementById("box-wrapper")) {
|
|
||||||
var elm = document.getElementById("box-wrapper");
|
var elm = document.getElementById("box-wrapper");
|
||||||
var divs = new Array("myStreamsBox", "clientInfo", "content");
|
var content = document.getElementById("content");
|
||||||
var elementsHeight = 0 - elm.offsetHeight;
|
if (elm != null && content != null) {
|
||||||
for (var i = 0; i < divs.length; i++) {
|
var contentTop = content.getBoundingClientRect().top;
|
||||||
elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight;
|
var freeSpace = window.innerHeight - contentTop - 26;
|
||||||
|
if (freeSpace < 180) {
|
||||||
|
freeSpace = 180;
|
||||||
}
|
}
|
||||||
elm.style.height = window.innerHeight - elementsHeight + "px";
|
elm.style.height = freeSpace + "px";
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
var key = this.key;
|
var key = this.key;
|
||||||
var content = new PopupContent();
|
var content = new PopupContent();
|
||||||
var description;
|
var description;
|
||||||
|
var wizardField = null;
|
||||||
var doc = document.getElementById(this.DocumentID);
|
var doc = document.getElementById(this.DocumentID);
|
||||||
|
doc.setAttribute("aria-busy", "true");
|
||||||
doc.innerHTML = "";
|
doc.innerHTML = "";
|
||||||
doc.appendChild(headline);
|
doc.appendChild(headline);
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -50,6 +52,7 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
select.setAttribute("class", "wizard");
|
select.setAttribute("class", "wizard");
|
||||||
select.id = key;
|
select.id = key;
|
||||||
doc.appendChild(select);
|
doc.appendChild(select);
|
||||||
|
wizardField = select;
|
||||||
description = "{{.wizard.tuner.description}}";
|
description = "{{.wizard.tuner.description}}";
|
||||||
break;
|
break;
|
||||||
case "epgSource":
|
case "epgSource":
|
||||||
@@ -59,6 +62,7 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
select.setAttribute("class", "wizard");
|
select.setAttribute("class", "wizard");
|
||||||
select.id = key;
|
select.id = key;
|
||||||
doc.appendChild(select);
|
doc.appendChild(select);
|
||||||
|
wizardField = select;
|
||||||
description = "{{.wizard.epgSource.description}}";
|
description = "{{.wizard.epgSource.description}}";
|
||||||
break;
|
break;
|
||||||
case "m3u":
|
case "m3u":
|
||||||
@@ -67,6 +71,7 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
input.setAttribute("class", "wizard");
|
input.setAttribute("class", "wizard");
|
||||||
input.id = key;
|
input.id = key;
|
||||||
doc.appendChild(input);
|
doc.appendChild(input);
|
||||||
|
wizardField = input;
|
||||||
description = "{{.wizard.m3u.description}}";
|
description = "{{.wizard.m3u.description}}";
|
||||||
break;
|
break;
|
||||||
case "xmltv":
|
case "xmltv":
|
||||||
@@ -75,6 +80,7 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
input.setAttribute("class", "wizard");
|
input.setAttribute("class", "wizard");
|
||||||
input.id = key;
|
input.id = key;
|
||||||
doc.appendChild(input);
|
doc.appendChild(input);
|
||||||
|
wizardField = input;
|
||||||
description = "{{.wizard.xmltv.description}}";
|
description = "{{.wizard.xmltv.description}}";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -82,8 +88,20 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var pre = document.createElement("PRE");
|
var pre = document.createElement("PRE");
|
||||||
|
pre.id = "wizard-description-" + key;
|
||||||
pre.innerHTML = description;
|
pre.innerHTML = description;
|
||||||
doc.appendChild(pre);
|
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);
|
console.log(headline, key);
|
||||||
};
|
};
|
||||||
return WizardItem;
|
return WizardItem;
|
||||||
@@ -145,3 +163,20 @@ configurationWizard.push(new WizardItem("tuner", "{{.wizard.tuner.title}}"));
|
|||||||
configurationWizard.push(new WizardItem("epgSource", "{{.wizard.epgSource.title}}"));
|
configurationWizard.push(new WizardItem("epgSource", "{{.wizard.epgSource.title}}"));
|
||||||
configurationWizard.push(new WizardItem("m3u", "{{.wizard.m3u.title}}"));
|
configurationWizard.push(new WizardItem("m3u", "{{.wizard.m3u.title}}"));
|
||||||
configurationWizard.push(new WizardItem("xmltv", "{{.wizard.xmltv.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -21,15 +21,22 @@ function showLogs(bottom) {
|
|||||||
var log = new Log();
|
var log = new Log();
|
||||||
var logs = SERVER["log"]["log"];
|
var logs = SERVER["log"]["log"];
|
||||||
var div = document.getElementById("content_log");
|
var div = document.getElementById("content_log");
|
||||||
|
var wrapper = document.getElementById("box-wrapper");
|
||||||
|
var shouldStickToBottom = bottom;
|
||||||
div.innerHTML = "";
|
div.innerHTML = "";
|
||||||
|
if (wrapper != null && shouldStickToBottom == false) {
|
||||||
|
var distanceToBottom = wrapper.scrollHeight - wrapper.scrollTop - wrapper.clientHeight;
|
||||||
|
if (distanceToBottom < 80) {
|
||||||
|
shouldStickToBottom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
var keys = getObjKeys(logs);
|
var keys = getObjKeys(logs);
|
||||||
keys.forEach(function (logID) {
|
keys.forEach(function (logID) {
|
||||||
var entry = log.createLog(logs[logID]);
|
var entry = log.createLog(logs[logID]);
|
||||||
div.append(entry);
|
div.append(entry);
|
||||||
});
|
});
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (bottom == true) {
|
if (shouldStickToBottom == true && wrapper != null) {
|
||||||
var wrapper = document.getElementById("box-wrapper");
|
|
||||||
wrapper.scrollTop = wrapper.scrollHeight;
|
wrapper.scrollTop = wrapper.scrollHeight;
|
||||||
}
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|||||||
@@ -43,7 +43,14 @@ var MainMenuItem = /** @class */ (function (_super) {
|
|||||||
var item = document.createElement("LI");
|
var item = document.createElement("LI");
|
||||||
item.setAttribute("onclick", "javascript: openThisMenu(this)");
|
item.setAttribute("onclick", "javascript: openThisMenu(this)");
|
||||||
item.setAttribute("id", this.id);
|
item.setAttribute("id", this.id);
|
||||||
|
item.setAttribute("data-menu", this.menuKey);
|
||||||
|
item.setAttribute("role", "menuitem");
|
||||||
|
item.setAttribute("tabindex", "0");
|
||||||
|
item.setAttribute("aria-controls", "content");
|
||||||
|
item.setAttribute("aria-label", this.value);
|
||||||
|
item.setAttribute("onkeydown", "if(event.key==='Enter' || event.key===' '){event.preventDefault();openThisMenu(this);}");
|
||||||
var img = this.createIMG(this.imgSrc);
|
var img = this.createIMG(this.imgSrc);
|
||||||
|
img.setAttribute("alt", "");
|
||||||
var value = this.createValue(this.value);
|
var value = this.createValue(this.value);
|
||||||
item.appendChild(img);
|
item.appendChild(img);
|
||||||
item.appendChild(value);
|
item.appendChild(value);
|
||||||
@@ -63,7 +70,7 @@ var MainMenuItem = /** @class */ (function (_super) {
|
|||||||
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 = ["{{.users.table.username}}", "{{.users.table.password}}", "{{.users.table.web}}", "{{.users.table.pms}}", "{{.users.table.m3u}}", "{{.users.table.xml}}", "{{.users.table.api}}"];
|
||||||
break;
|
break;
|
||||||
case "mapping":
|
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}}"];
|
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}}"];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//console.log(this.menuKey, this.tableHeader);
|
//console.log(this.menuKey, this.tableHeader);
|
||||||
@@ -356,39 +363,27 @@ var Content = /** @class */ (function () {
|
|||||||
cell.child = true;
|
cell.child = true;
|
||||||
cell.childType = "IMG";
|
cell.childType = "IMG";
|
||||||
cell.imageURL = data[key]["tvg-logo"];
|
cell.imageURL = data[key]["tvg-logo"];
|
||||||
var td = cell.createCell();
|
tr.appendChild(cell.createCell());
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
|
|
||||||
td.id = key;
|
|
||||||
tr.appendChild(td);
|
|
||||||
// Kanalname
|
// Kanalname
|
||||||
var cell = new Cell();
|
var cell = new Cell();
|
||||||
cell.child = true;
|
cell.child = true;
|
||||||
cell.childType = "P";
|
cell.childType = "P";
|
||||||
cell.className = data[key]["x-category"];
|
cell.className = data[key]["x-category"];
|
||||||
cell.value = data[key]["x-name"];
|
cell.value = data[key]["x-name"];
|
||||||
var td = cell.createCell();
|
tr.appendChild(cell.createCell());
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
|
|
||||||
td.id = key;
|
|
||||||
tr.appendChild(td);
|
|
||||||
// Playlist
|
// Playlist
|
||||||
var cell = new Cell();
|
var cell = new Cell();
|
||||||
cell.child = true;
|
cell.child = true;
|
||||||
cell.childType = "P";
|
cell.childType = "P";
|
||||||
//cell.value = data[key]["_file.m3u.name"]
|
//cell.value = data[key]["_file.m3u.name"]
|
||||||
cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name");
|
cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name");
|
||||||
var td = cell.createCell();
|
tr.appendChild(cell.createCell());
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
|
|
||||||
td.id = key;
|
|
||||||
tr.appendChild(td);
|
|
||||||
// Gruppe (group-title)
|
// Gruppe (group-title)
|
||||||
var cell = new Cell();
|
var cell = new Cell();
|
||||||
cell.child = true;
|
cell.child = true;
|
||||||
cell.childType = "P";
|
cell.childType = "P";
|
||||||
cell.value = data[key]["x-group-title"];
|
cell.value = data[key]["x-group-title"];
|
||||||
var td = cell.createCell();
|
tr.appendChild(cell.createCell());
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
|
|
||||||
td.id = key;
|
|
||||||
tr.appendChild(td);
|
|
||||||
// XMLTV Datei
|
// XMLTV Datei
|
||||||
var cell = new Cell();
|
var cell = new Cell();
|
||||||
cell.child = true;
|
cell.child = true;
|
||||||
@@ -399,10 +394,7 @@ var Content = /** @class */ (function () {
|
|||||||
else {
|
else {
|
||||||
cell.value = data[key]["x-xmltv-file"];
|
cell.value = data[key]["x-xmltv-file"];
|
||||||
}
|
}
|
||||||
var td = cell.createCell();
|
tr.appendChild(cell.createCell());
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
|
|
||||||
td.id = key;
|
|
||||||
tr.appendChild(td);
|
|
||||||
// XMLTV Kanal
|
// XMLTV Kanal
|
||||||
var cell = new Cell();
|
var cell = new Cell();
|
||||||
cell.child = true;
|
cell.child = true;
|
||||||
@@ -413,10 +405,17 @@ var Content = /** @class */ (function () {
|
|||||||
value = data[key]["x-mapping"].substring(0, 20) + "...";
|
value = data[key]["x-mapping"].substring(0, 20) + "...";
|
||||||
}
|
}
|
||||||
cell.value = value;
|
cell.value = value;
|
||||||
var td = cell.createCell();
|
tr.appendChild(cell.createCell());
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
|
var cell = new Cell();
|
||||||
td.id = key;
|
cell.child = true;
|
||||||
tr.appendChild(td);
|
cell.childType = "EDIT";
|
||||||
|
cell.value = "{{.button.edit}}";
|
||||||
|
var editTd = cell.createCell();
|
||||||
|
var editButton = editTd.firstChild;
|
||||||
|
editButton.setAttribute('onclick', 'javascript: openPopUp("mapping", this)');
|
||||||
|
editButton.setAttribute("id", key);
|
||||||
|
editButton.setAttribute("aria-label", "Edit " + data[key]["x-name"]);
|
||||||
|
tr.appendChild(editTd);
|
||||||
rows.push(tr);
|
rows.push(tr);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -451,7 +450,7 @@ var Cell = /** @class */ (function () {
|
|||||||
break;
|
break;
|
||||||
case "INPUTCHANNEL":
|
case "INPUTCHANNEL":
|
||||||
element = document.createElement("INPUT");
|
element = document.createElement("INPUT");
|
||||||
element.setAttribute("onchange", "javscript: changeChannelNumber(this)");
|
element.setAttribute("onchange", "javascript: changeChannelNumber(this)");
|
||||||
element.value = this.value;
|
element.value = this.value;
|
||||||
element.type = "text";
|
element.type = "text";
|
||||||
break;
|
break;
|
||||||
@@ -460,6 +459,9 @@ var Cell = /** @class */ (function () {
|
|||||||
element.checked = this.value;
|
element.checked = this.value;
|
||||||
element.type = "checkbox";
|
element.type = "checkbox";
|
||||||
element.className = "bulk hideBulk";
|
element.className = "bulk hideBulk";
|
||||||
|
element.addEventListener("click", function (event) {
|
||||||
|
scheduleChannelRangeSelection(element, event);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "BULK_HEAD":
|
case "BULK_HEAD":
|
||||||
element = document.createElement("INPUT");
|
element = document.createElement("INPUT");
|
||||||
@@ -475,6 +477,12 @@ var Cell = /** @class */ (function () {
|
|||||||
element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''");
|
element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''");
|
||||||
//onerror="this.onerror=null;this.src='missing.gif';"
|
//onerror="this.onerror=null;this.src='missing.gif';"
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case "EDIT":
|
||||||
|
element = document.createElement("INPUT");
|
||||||
|
element.type = "button";
|
||||||
|
element.value = this.value;
|
||||||
|
element.className = "mapping-edit-button";
|
||||||
}
|
}
|
||||||
td.appendChild(element);
|
td.appendChild(element);
|
||||||
}
|
}
|
||||||
@@ -483,11 +491,19 @@ var Cell = /** @class */ (function () {
|
|||||||
}
|
}
|
||||||
if (this.onclick == true) {
|
if (this.onclick == true) {
|
||||||
td.setAttribute("onclick", this.onclickFunktion);
|
td.setAttribute("onclick", this.onclickFunktion);
|
||||||
td.className = "pointer";
|
td.className = "pointer keyboard-clickable";
|
||||||
|
td.setAttribute("tabindex", "0");
|
||||||
|
td.setAttribute("role", "button");
|
||||||
|
td.setAttribute("onkeydown", "if(event.key==='Enter' || event.key===' '){event.preventDefault();this.click();}");
|
||||||
}
|
}
|
||||||
if (this.tdClassName != undefined) {
|
if (this.tdClassName != undefined) {
|
||||||
|
if (td.className.length > 0) {
|
||||||
|
td.className = td.className + " " + this.tdClassName;
|
||||||
|
}
|
||||||
|
else {
|
||||||
td.className = this.tdClassName;
|
td.className = this.tdClassName;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return td;
|
return td;
|
||||||
};
|
};
|
||||||
return Cell;
|
return Cell;
|
||||||
@@ -510,6 +526,7 @@ var ShowContent = /** @class */ (function (_super) {
|
|||||||
COLUMN_TO_SORT = -1;
|
COLUMN_TO_SORT = -1;
|
||||||
// Alten Inhalt löschen
|
// Alten Inhalt löschen
|
||||||
var doc = document.getElementById(this.DocumentID);
|
var doc = document.getElementById(this.DocumentID);
|
||||||
|
doc.setAttribute("aria-busy", "true");
|
||||||
doc.innerHTML = "";
|
doc.innerHTML = "";
|
||||||
showPreview(false);
|
showPreview(false);
|
||||||
// Überschrift
|
// Überschrift
|
||||||
@@ -556,11 +573,33 @@ var ShowContent = /** @class */ (function (_super) {
|
|||||||
var input = this.createInput("button", menuKey, "{{.button.bulkEdit}}");
|
var input = this.createInput("button", menuKey, "{{.button.bulkEdit}}");
|
||||||
input.setAttribute("onclick", 'javascript: bulkEdit()');
|
input.setAttribute("onclick", 'javascript: bulkEdit()');
|
||||||
interaction.appendChild(input);
|
interaction.appendChild(input);
|
||||||
|
var sortSelect = document.createElement("SELECT");
|
||||||
|
sortSelect.setAttribute("id", "mapping-sort-mobile");
|
||||||
|
sortSelect.className = "mobile-only-control";
|
||||||
|
sortSelect.setAttribute("aria-label", "Sort mapping");
|
||||||
|
var sortOptions = [
|
||||||
|
{ label: "{{.mapping.table.chNo}}", value: "1" },
|
||||||
|
{ label: "{{.mapping.table.channelName}}", value: "3" },
|
||||||
|
{ label: "{{.mapping.table.playlist}}", value: "4" },
|
||||||
|
{ label: "{{.mapping.table.groupTitle}}", value: "5" }
|
||||||
|
];
|
||||||
|
sortOptions.forEach(function (optionData) {
|
||||||
|
var option = document.createElement("OPTION");
|
||||||
|
option.innerText = optionData.label;
|
||||||
|
option.value = optionData.value;
|
||||||
|
sortSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
sortSelect.value = "1";
|
||||||
|
sortSelect.onchange = function () {
|
||||||
|
sortTable(parseInt(this.value, 10));
|
||||||
|
};
|
||||||
|
interaction.appendChild(sortSelect);
|
||||||
var input = this.createInput("search", "search", "");
|
var input = this.createInput("search", "search", "");
|
||||||
input.setAttribute("id", "searchMapping");
|
input.setAttribute("id", "searchMapping");
|
||||||
input.setAttribute("placeholder", "{{.button.search}}");
|
input.setAttribute("placeholder", "{{.button.search}}");
|
||||||
|
input.setAttribute("aria-label", "{{.button.search}}");
|
||||||
input.className = "search";
|
input.className = "search";
|
||||||
input.setAttribute("onchange", 'javascript: searchInMapping()');
|
input.setAttribute("oninput", 'javascript: searchInMapping()');
|
||||||
interaction.appendChild(input);
|
interaction.appendChild(input);
|
||||||
break;
|
break;
|
||||||
case "settings":
|
case "settings":
|
||||||
@@ -580,6 +619,7 @@ var ShowContent = /** @class */ (function (_super) {
|
|||||||
var settings = this.createDIV();
|
var settings = this.createDIV();
|
||||||
wrapper.appendChild(settings);
|
wrapper.appendChild(settings);
|
||||||
showSettings();
|
showSettings();
|
||||||
|
finalizeContentAccessibility(headline);
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
case "log":
|
case "log":
|
||||||
@@ -593,6 +633,7 @@ var ShowContent = /** @class */ (function (_super) {
|
|||||||
var logs = this.createDIV();
|
var logs = this.createDIV();
|
||||||
wrapper.appendChild(logs);
|
wrapper.appendChild(logs);
|
||||||
showLogs(true);
|
showLogs(true);
|
||||||
|
finalizeContentAccessibility(headline);
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
case "logout":
|
case "logout":
|
||||||
@@ -665,30 +706,392 @@ var ShowContent = /** @class */ (function (_super) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
showElement("loading", false);
|
showElement("loading", false);
|
||||||
|
finalizeContentAccessibility(headline);
|
||||||
};
|
};
|
||||||
return ShowContent;
|
return ShowContent;
|
||||||
}(Content));
|
}(Content));
|
||||||
|
var SHELL_LAYOUT_READY = false;
|
||||||
|
function isKeyboardActivationKey(event) {
|
||||||
|
return event.key == "Enter" || event.key == " ";
|
||||||
|
}
|
||||||
|
function makeKeyboardClickable(element, label) {
|
||||||
|
if (element == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (element.getAttribute("data-keyboard-ready") == "true") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tagName = element.tagName.toUpperCase();
|
||||||
|
if (tagName == "INPUT" || tagName == "BUTTON" || tagName == "SELECT" || tagName == "TEXTAREA" || tagName == "A") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.setAttribute("data-keyboard-ready", "true");
|
||||||
|
if (element.getAttribute("tabindex") == null) {
|
||||||
|
element.setAttribute("tabindex", "0");
|
||||||
|
}
|
||||||
|
if (element.getAttribute("role") == null) {
|
||||||
|
element.setAttribute("role", "button");
|
||||||
|
}
|
||||||
|
element.classList.add("keyboard-clickable");
|
||||||
|
if (label != undefined && label.length > 0) {
|
||||||
|
if (element.getAttribute("aria-label") == null || element.getAttribute("aria-label").length == 0) {
|
||||||
|
element.setAttribute("aria-label", label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.addEventListener("keydown", function (event) {
|
||||||
|
if (isKeyboardActivationKey(event) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
this.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function applyTableAccessibility(table, sectionName) {
|
||||||
|
if (table == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
table.setAttribute("role", "table");
|
||||||
|
var rows = table.getElementsByTagName("TR");
|
||||||
|
var headerLabels = [];
|
||||||
|
if (rows.length > 0) {
|
||||||
|
var headerCells = rows[0].getElementsByTagName("TD");
|
||||||
|
for (var h = 0; h < headerCells.length; h++) {
|
||||||
|
var headerLabel = headerCells[h].innerText;
|
||||||
|
if (headerLabel == undefined || headerLabel.length == 0) {
|
||||||
|
headerLabel = "Value";
|
||||||
|
}
|
||||||
|
if (headerLabel == "BULK") {
|
||||||
|
headerLabel = "Select";
|
||||||
|
}
|
||||||
|
headerLabels.push(headerLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
rows[i].setAttribute("role", "row");
|
||||||
|
if (rows[i].getAttribute("onclick") != null) {
|
||||||
|
var rowLabel = rows[i].innerText;
|
||||||
|
if (rowLabel == undefined || rowLabel.length == 0) {
|
||||||
|
rowLabel = sectionName + " row";
|
||||||
|
}
|
||||||
|
makeKeyboardClickable(rows[i], rowLabel);
|
||||||
|
}
|
||||||
|
var cells = rows[i].getElementsByTagName("TD");
|
||||||
|
for (var c = 0; c < cells.length; c++) {
|
||||||
|
if (i == 0) {
|
||||||
|
cells[c].setAttribute("role", "columnheader");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cells[c].setAttribute("role", "cell");
|
||||||
|
}
|
||||||
|
var dataLabel = headerLabels[c];
|
||||||
|
if (dataLabel == undefined || dataLabel.length == 0) {
|
||||||
|
dataLabel = "Value";
|
||||||
|
}
|
||||||
|
cells[c].setAttribute("data-label", dataLabel);
|
||||||
|
var checkbox = cells[c].querySelector('input[type="checkbox"]');
|
||||||
|
if (checkbox != null) {
|
||||||
|
cells[c].setAttribute("data-cell-type", "checkbox");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var image = cells[c].querySelector("img");
|
||||||
|
if (image != null) {
|
||||||
|
cells[c].setAttribute("data-cell-type", "image");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var actionButton = cells[c].querySelector("input.mapping-edit-button, button.mapping-edit-button");
|
||||||
|
if (actionButton != null) {
|
||||||
|
cells[c].setAttribute("data-cell-type", "action");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cells[c].removeAttribute("data-cell-type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cells[c].getAttribute("onclick") != null) {
|
||||||
|
var cellLabel = cells[c].innerText;
|
||||||
|
if (cellLabel == undefined || cellLabel.length == 0) {
|
||||||
|
cellLabel = sectionName + " details";
|
||||||
|
}
|
||||||
|
makeKeyboardClickable(cells[c], cellLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function finalizeContentAccessibility(sectionName) {
|
||||||
|
var content = document.getElementById("content");
|
||||||
|
if (content == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
content.setAttribute("aria-busy", "false");
|
||||||
|
var heading = content.getElementsByTagName("H3")[0];
|
||||||
|
if (heading != null) {
|
||||||
|
heading.setAttribute("tabindex", "-1");
|
||||||
|
setTimeout(function () {
|
||||||
|
heading.focus();
|
||||||
|
}, 20);
|
||||||
|
}
|
||||||
|
applyTableAccessibility(document.getElementById("content_table"), sectionName);
|
||||||
|
if (sectionName != undefined && sectionName.length > 0) {
|
||||||
|
announceToScreenReader(sectionName + " loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function setLayoutMenuState(open) {
|
||||||
|
if (document.body == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (open == true) {
|
||||||
|
document.body.classList.add("menu-open");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.body.classList.remove("menu-open");
|
||||||
|
}
|
||||||
|
var toggle = document.getElementById("menu-toggle");
|
||||||
|
if (toggle != null) {
|
||||||
|
toggle.setAttribute("aria-expanded", open == true ? "true" : "false");
|
||||||
|
toggle.setAttribute("aria-label", open == true ? "Close navigation menu" : "Open navigation menu");
|
||||||
|
}
|
||||||
|
var overlay = document.getElementById("layout-overlay");
|
||||||
|
if (overlay != null) {
|
||||||
|
overlay.setAttribute("aria-hidden", open == true ? "false" : "true");
|
||||||
|
}
|
||||||
|
var wrapper = document.getElementById("menu-wrapper");
|
||||||
|
if (wrapper != null) {
|
||||||
|
if (window.innerWidth <= 900) {
|
||||||
|
wrapper.setAttribute("aria-hidden", open == true ? "false" : "true");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wrapper.setAttribute("aria-hidden", "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (window.innerWidth <= 900 && open == false && toggle != null && wrapper != null && wrapper.contains(document.activeElement)) {
|
||||||
|
toggle.focus();
|
||||||
|
}
|
||||||
|
if (window.innerWidth <= 900 && open == true && wrapper != null) {
|
||||||
|
var firstMenuItem = wrapper.querySelector("#main-menu li");
|
||||||
|
if (firstMenuItem != null) {
|
||||||
|
setTimeout(function () {
|
||||||
|
firstMenuItem.focus();
|
||||||
|
}, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function toggleLayoutMenu() {
|
||||||
|
if (document.body == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var isOpen = document.body.classList.contains("menu-open");
|
||||||
|
setLayoutMenuState(!isOpen);
|
||||||
|
}
|
||||||
|
function closeLayoutMenuIfMobile() {
|
||||||
|
if (window.innerWidth <= 900) {
|
||||||
|
setLayoutMenuState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function setActiveMenu(menuID) {
|
||||||
|
ACTIVE_MENU_ID = menuID.toString();
|
||||||
|
var menu = document.getElementById("main-menu");
|
||||||
|
if (menu == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var items = menu.getElementsByTagName("LI");
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
items[i].classList.remove("menu-active");
|
||||||
|
items[i].removeAttribute("aria-current");
|
||||||
|
}
|
||||||
|
var activeItem = document.getElementById(ACTIVE_MENU_ID);
|
||||||
|
var activeMenuKey = "";
|
||||||
|
if (activeItem != null) {
|
||||||
|
activeItem.classList.add("menu-active");
|
||||||
|
activeItem.setAttribute("aria-current", "page");
|
||||||
|
var menuKeyValue = activeItem.getAttribute("data-menu");
|
||||||
|
if (menuKeyValue != null) {
|
||||||
|
activeMenuKey = menuKeyValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (document.body != null) {
|
||||||
|
if (activeMenuKey.length > 0) {
|
||||||
|
document.body.setAttribute("data-active-menu", activeMenuKey);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.body.removeAttribute("data-active-menu");
|
||||||
|
}
|
||||||
|
if (activeMenuKey == "log") {
|
||||||
|
document.body.classList.add("menu-log-focus");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.body.classList.remove("menu-log-focus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function renderStatusCards() {
|
||||||
|
var wrapper = document.getElementById("status-cards");
|
||||||
|
if (wrapper == null || SERVER.hasOwnProperty("clientInfo") == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var info = SERVER["clientInfo"];
|
||||||
|
var errors = parseInt(info["errors"], 10);
|
||||||
|
var warnings = parseInt(info["warnings"], 10);
|
||||||
|
var cards = [
|
||||||
|
{ label: "Streams", value: info["streams"], tone: "ok" },
|
||||||
|
{ label: "EPG Source", value: info["epgSource"], tone: "neutral" },
|
||||||
|
{ label: "XEPG Channels", value: info["xepg"], tone: "ok" },
|
||||||
|
{ label: "Errors", value: info["errors"], tone: errors > 0 ? "error" : "ok" },
|
||||||
|
{ label: "Warnings", value: info["warnings"], tone: warnings > 0 ? "warn" : "ok" },
|
||||||
|
{ label: "DVR", value: info["DVR"], tone: "neutral" }
|
||||||
|
];
|
||||||
|
wrapper.innerHTML = "";
|
||||||
|
cards.forEach(function (card) {
|
||||||
|
var box = document.createElement("DIV");
|
||||||
|
box.className = "status-card status-card-" + card.tone;
|
||||||
|
box.setAttribute("role", "listitem");
|
||||||
|
var label = document.createElement("P");
|
||||||
|
label.className = "status-card-label";
|
||||||
|
label.innerText = card.label;
|
||||||
|
var value = document.createElement("P");
|
||||||
|
value.className = "status-card-value";
|
||||||
|
if (card.value == undefined || card.value == "") {
|
||||||
|
value.innerText = "-";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value.innerText = card.value;
|
||||||
|
}
|
||||||
|
box.appendChild(label);
|
||||||
|
box.appendChild(value);
|
||||||
|
box.setAttribute("aria-label", card.label + ": " + value.innerText);
|
||||||
|
wrapper.appendChild(box);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function initShellLayout() {
|
||||||
|
if (SHELL_LAYOUT_READY == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var toggle = document.getElementById("menu-toggle");
|
||||||
|
if (toggle != null) {
|
||||||
|
toggle.onclick = function () {
|
||||||
|
toggleLayoutMenu();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var overlay = document.getElementById("layout-overlay");
|
||||||
|
if (overlay != null) {
|
||||||
|
overlay.onclick = function () {
|
||||||
|
setLayoutMenuState(false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
document.addEventListener("keydown", function (event) {
|
||||||
|
if (event.key == "Escape") {
|
||||||
|
setLayoutMenuState(false);
|
||||||
|
showElement("popup", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.key == "/") {
|
||||||
|
var target = event.target;
|
||||||
|
var tagName = target != null && target.tagName != undefined ? target.tagName : "";
|
||||||
|
var onInput = tagName == "INPUT" || tagName == "TEXTAREA" || tagName == "SELECT";
|
||||||
|
if (onInput == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var search = document.getElementById("searchMapping");
|
||||||
|
if (search != null) {
|
||||||
|
event.preventDefault();
|
||||||
|
search.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setLayoutMenuState(false);
|
||||||
|
setConnectionState("idle");
|
||||||
|
SHELL_LAYOUT_READY = true;
|
||||||
|
}
|
||||||
|
function shouldPollLogs() {
|
||||||
|
if (document.hidden == true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (document.getElementById("content_log") == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ACTIVE_MENU_ID.length < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var activeItem = document.getElementById(ACTIVE_MENU_ID);
|
||||||
|
if (activeItem == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return activeItem.getAttribute("data-menu") == "log";
|
||||||
|
}
|
||||||
function PageReady() {
|
function PageReady() {
|
||||||
|
initShellLayout();
|
||||||
var server = new Server("getServerConfig");
|
var server = new Server("getServerConfig");
|
||||||
server.request(new Object());
|
server.request(new Object());
|
||||||
|
var bootstrapAttempts = 0;
|
||||||
|
var maxBootstrapAttempts = 5;
|
||||||
|
var bootstrapTimer = window.setInterval(function () {
|
||||||
|
if (SERVER.hasOwnProperty("clientInfo") == true) {
|
||||||
|
window.clearInterval(bootstrapTimer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (SERVER_CONNECTION == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bootstrapAttempts++;
|
||||||
|
var retryServer = new Server("getServerConfig");
|
||||||
|
retryServer.request(new Object());
|
||||||
|
if (bootstrapAttempts >= maxBootstrapAttempts) {
|
||||||
|
window.clearInterval(bootstrapTimer);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
window.addEventListener("resize", function () {
|
window.addEventListener("resize", function () {
|
||||||
|
if (window.innerWidth > 900) {
|
||||||
|
setLayoutMenuState(false);
|
||||||
|
}
|
||||||
calculateWrapperHeight();
|
calculateWrapperHeight();
|
||||||
}, true);
|
}, true);
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
|
if (shouldPollLogs() == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
updateLog();
|
updateLog();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
function isClientInfoHttpURL(value) {
|
||||||
|
return /^https?:\/\//i.test(value);
|
||||||
|
}
|
||||||
|
function setClientInfoValue(key, value) {
|
||||||
|
var element = document.getElementById(key);
|
||||||
|
if (element == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var textValue = "";
|
||||||
|
if (value != undefined && value != null) {
|
||||||
|
textValue = String(value);
|
||||||
|
}
|
||||||
|
if ((key == "m3u-url" || key == "xepg-url") && isClientInfoHttpURL(textValue)) {
|
||||||
|
element.innerHTML = "";
|
||||||
|
var anchor = document.createElement("A");
|
||||||
|
anchor.href = textValue;
|
||||||
|
anchor.target = "_blank";
|
||||||
|
anchor.rel = "noopener noreferrer";
|
||||||
|
anchor.textContent = textValue;
|
||||||
|
element.appendChild(anchor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.innerHTML = textValue;
|
||||||
|
}
|
||||||
function createLayout() {
|
function createLayout() {
|
||||||
|
var contentRegion = document.getElementById("content");
|
||||||
|
if (contentRegion != null) {
|
||||||
|
contentRegion.setAttribute("aria-busy", "true");
|
||||||
|
}
|
||||||
// Client Info
|
// Client Info
|
||||||
var obj = SERVER["clientInfo"];
|
var obj = SERVER["clientInfo"];
|
||||||
var keys = getObjKeys(obj);
|
var keys = getObjKeys(obj);
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
if (document.getElementById(keys[i])) {
|
setClientInfoValue(keys[i], obj[keys[i]]);
|
||||||
document.getElementById(keys[i]).innerHTML = obj[keys[i]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
renderStatusCards();
|
||||||
if (!document.getElementById("main-menu")) {
|
if (!document.getElementById("main-menu")) {
|
||||||
|
if (contentRegion != null) {
|
||||||
|
contentRegion.setAttribute("aria-busy", "false");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Menü erstellen
|
// Menü erstellen
|
||||||
@@ -713,12 +1116,35 @@ function createLayout() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ACTIVE_MENU_ID.length > 0 && document.getElementById(ACTIVE_MENU_ID)) {
|
||||||
|
setActiveMenu(ACTIVE_MENU_ID);
|
||||||
|
}
|
||||||
|
setLayoutMenuState(document.body.classList.contains("menu-open"));
|
||||||
|
var content = document.getElementById("content");
|
||||||
|
var menu = document.getElementById("main-menu");
|
||||||
|
if (ACTIVE_MENU_ID.length == 0 && content != null && menu != null) {
|
||||||
|
if (content.innerHTML.replace(/\\s/g, "").length == 0) {
|
||||||
|
var firstItem = menu.getElementsByTagName("LI")[0];
|
||||||
|
if (firstItem != undefined) {
|
||||||
|
firstItem.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (contentRegion != null) {
|
||||||
|
contentRegion.setAttribute("aria-busy", "false");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
function openThisMenu(element) {
|
function openThisMenu(element) {
|
||||||
var id = element.id;
|
var id = element.id;
|
||||||
var content = new ShowContent(id);
|
var content = new ShowContent(id);
|
||||||
|
setActiveMenu(id);
|
||||||
content.show();
|
content.show();
|
||||||
|
var contentArea = document.getElementById("content");
|
||||||
|
if (contentArea != null) {
|
||||||
|
contentArea.scrollTop = 0;
|
||||||
|
}
|
||||||
|
closeLayoutMenuIfMobile();
|
||||||
calculateWrapperHeight();
|
calculateWrapperHeight();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -756,9 +1182,26 @@ var PopupContent = /** @class */ (function (_super) {
|
|||||||
}
|
}
|
||||||
PopupContent.prototype.createHeadline = function (headline) {
|
PopupContent.prototype.createHeadline = function (headline) {
|
||||||
this.doc.innerHTML = "";
|
this.doc.innerHTML = "";
|
||||||
|
var titleBar = document.createElement("DIV");
|
||||||
|
titleBar.className = "popup-title";
|
||||||
var element = document.createElement("H3");
|
var element = document.createElement("H3");
|
||||||
|
element.id = "popup-title-text";
|
||||||
element.innerHTML = headline.toUpperCase();
|
element.innerHTML = headline.toUpperCase();
|
||||||
this.doc.appendChild(element);
|
titleBar.appendChild(element);
|
||||||
|
var closeButton = document.createElement("BUTTON");
|
||||||
|
closeButton.setAttribute("type", "button");
|
||||||
|
closeButton.className = "popup-close";
|
||||||
|
closeButton.setAttribute("aria-label", "Close dialog");
|
||||||
|
closeButton.innerHTML = "×";
|
||||||
|
closeButton.onclick = function () {
|
||||||
|
showElement("popup", false);
|
||||||
|
};
|
||||||
|
titleBar.appendChild(closeButton);
|
||||||
|
this.doc.appendChild(titleBar);
|
||||||
|
var popup = document.getElementById("popup");
|
||||||
|
if (popup != null) {
|
||||||
|
popup.setAttribute("aria-labelledby", "popup-title-text");
|
||||||
|
}
|
||||||
// Tabelle erstellen
|
// Tabelle erstellen
|
||||||
this.table = document.createElement("TABLE");
|
this.table = document.createElement("TABLE");
|
||||||
this.doc.appendChild(this.table);
|
this.doc.appendChild(this.table);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ var Server = /** @class */ (function () {
|
|||||||
if (this.cmd != "updateLog") {
|
if (this.cmd != "updateLog") {
|
||||||
showElement("loading", true);
|
showElement("loading", true);
|
||||||
UNDO = new Object();
|
UNDO = new Object();
|
||||||
|
setConnectionState("busy");
|
||||||
}
|
}
|
||||||
switch (window.location.protocol) {
|
switch (window.location.protocol) {
|
||||||
case "http:":
|
case "http:":
|
||||||
@@ -20,11 +21,61 @@ var Server = /** @class */ (function () {
|
|||||||
this.protocol = "wss://";
|
this.protocol = "wss://";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var url = this.protocol + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token");
|
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;
|
data["cmd"] = this.cmd;
|
||||||
|
var requestCmd = data["cmd"];
|
||||||
var ws = new WebSocket(url);
|
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.onopen = function () {
|
||||||
WS_AVAILABLE = true;
|
WS_AVAILABLE = true;
|
||||||
|
if (data["cmd"] != "updateLog") {
|
||||||
|
setConnectionState("busy");
|
||||||
|
}
|
||||||
console.log("REQUEST (JS):");
|
console.log("REQUEST (JS):");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
console.log("REQUEST: (JSON)");
|
console.log("REQUEST: (JSON)");
|
||||||
@@ -33,14 +84,18 @@ var Server = /** @class */ (function () {
|
|||||||
};
|
};
|
||||||
ws.onerror = function (e) {
|
ws.onerror = function (e) {
|
||||||
console.log("No websocket connection to xTeVe could be established. Check your network configuration.");
|
console.log("No websocket connection to xTeVe could be established. Check your network configuration.");
|
||||||
SERVER_CONNECTION = false;
|
var errorState = "offline";
|
||||||
if (WS_AVAILABLE == false) {
|
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.");
|
alert("No websocket connection to xTeVe could be established. Check your network configuration.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ws.onmessage = function (e) {
|
ws.onmessage = function (e) {
|
||||||
SERVER_CONNECTION = false;
|
responseReceived = true;
|
||||||
showElement("loading", false);
|
finishRequest("online", true);
|
||||||
console.log("RESPONSE:");
|
console.log("RESPONSE:");
|
||||||
var response = JSON.parse(e.data);
|
var response = JSON.parse(e.data);
|
||||||
console.log(response);
|
console.log(response);
|
||||||
@@ -48,6 +103,7 @@ var Server = /** @class */ (function () {
|
|||||||
document.cookie = "Token=" + response["token"];
|
document.cookie = "Token=" + response["token"];
|
||||||
}
|
}
|
||||||
if (response["status"] == false) {
|
if (response["status"] == false) {
|
||||||
|
setConnectionState("offline");
|
||||||
alert(response["err"]);
|
alert(response["err"]);
|
||||||
if (response.hasOwnProperty("reload")) {
|
if (response.hasOwnProperty("reload")) {
|
||||||
location.reload();
|
location.reload();
|
||||||
@@ -94,9 +150,20 @@ var Server = /** @class */ (function () {
|
|||||||
}
|
}
|
||||||
createLayout();
|
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;
|
return Server;
|
||||||
}());
|
}());
|
||||||
|
var WS_FAILURE_COUNT = 0;
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
var value = "; " + document.cookie;
|
var value = "; " + document.cookie;
|
||||||
var parts = value.split("; " + name + "=");
|
var parts = value.split("; " + name + "=");
|
||||||
|
|||||||
@@ -74,6 +74,29 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
setting.appendChild(tdLeft);
|
setting.appendChild(tdLeft);
|
||||||
setting.appendChild(tdRight);
|
setting.appendChild(tdRight);
|
||||||
break;
|
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":
|
case "buffer.timeout":
|
||||||
var tdLeft = document.createElement("TD");
|
var tdLeft = document.createElement("TD");
|
||||||
tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":";
|
tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":";
|
||||||
@@ -240,7 +263,30 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
setting.appendChild(tdLeft);
|
setting.appendChild(tdLeft);
|
||||||
setting.appendChild(tdRight);
|
setting.appendChild(tdRight);
|
||||||
break;
|
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
|
// 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":
|
case "tuner":
|
||||||
var tdLeft = document.createElement("TD");
|
var tdLeft = document.createElement("TD");
|
||||||
tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":";
|
tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":";
|
||||||
@@ -364,6 +410,12 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
case "user.agent":
|
case "user.agent":
|
||||||
text = "{{.settings.userAgent.description}}";
|
text = "{{.settings.userAgent.description}}";
|
||||||
break;
|
break;
|
||||||
|
case "plex.url":
|
||||||
|
text = "{{.settings.plexURL.description}}";
|
||||||
|
break;
|
||||||
|
case "plex.token":
|
||||||
|
text = "{{.settings.plexToken.description}}";
|
||||||
|
break;
|
||||||
case "ffmpeg.path":
|
case "ffmpeg.path":
|
||||||
text = "{{.settings.ffmpegPath.description}}";
|
text = "{{.settings.ffmpegPath.description}}";
|
||||||
break;
|
break;
|
||||||
@@ -388,6 +440,9 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
case "api":
|
case "api":
|
||||||
text = "{{.settings.api.description}}";
|
text = "{{.settings.api.description}}";
|
||||||
break;
|
break;
|
||||||
|
case "use_plexAPI":
|
||||||
|
text = "{{.settings.usePlexAPI.description}}";
|
||||||
|
break;
|
||||||
case "files.update":
|
case "files.update":
|
||||||
text = "{{.settings.filesUpdate.description}}";
|
text = "{{.settings.filesUpdate.description}}";
|
||||||
break;
|
break;
|
||||||
@@ -397,6 +452,9 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
case "xepg.replace.missing.images":
|
case "xepg.replace.missing.images":
|
||||||
text = "{{.settings.replaceEmptyImages.description}}";
|
text = "{{.settings.replaceEmptyImages.description}}";
|
||||||
break;
|
break;
|
||||||
|
case "xepg.missing.epg.mode":
|
||||||
|
text = "{{.settings.xepgMissingEPGMode.description}}";
|
||||||
|
break;
|
||||||
case "udpxy":
|
case "udpxy":
|
||||||
text = "{{.settings.udpxy.description}}";
|
text = "{{.settings.udpxy.description}}";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"backup": "Backup",
|
"backup": "Backup",
|
||||||
"bulkEdit": "Bulk Edit",
|
"bulkEdit": "Bulk Edit",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"edit": "Edit",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
@@ -210,7 +211,8 @@
|
|||||||
"playlist": "Playlist",
|
"playlist": "Playlist",
|
||||||
"groupTitle": "Group Title",
|
"groupTitle": "Group Title",
|
||||||
"xmltvFile": "XMLTV File",
|
"xmltvFile": "XMLTV File",
|
||||||
"xmltvID": "XMLTV ID"
|
"xmltvID": "XMLTV ID",
|
||||||
|
"edit": "Edit"
|
||||||
},
|
},
|
||||||
"active":
|
"active":
|
||||||
{
|
{
|
||||||
@@ -355,6 +357,23 @@
|
|||||||
"title": "API Interface",
|
"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>"
|
"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":
|
"epgSource":
|
||||||
{
|
{
|
||||||
"title": "EPG Source",
|
"title": "EPG Source",
|
||||||
@@ -380,6 +399,13 @@
|
|||||||
"title": "Replace missing program images",
|
"title": "Replace missing program images",
|
||||||
"description": "If the poster in the XMLTV program is missing, the channel logo will be used."
|
"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)"
|
||||||
|
},
|
||||||
"xteveAutoUpdate":
|
"xteveAutoUpdate":
|
||||||
{
|
{
|
||||||
"title": "Automatic update of xTeVe",
|
"title": "Automatic update of xTeVe",
|
||||||
|
|||||||
@@ -10,36 +10,36 @@
|
|||||||
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="auth-screen">
|
||||||
|
|
||||||
<div id="header" class="imgCenter"></div>
|
<div id="header" class="imgCenter"></div>
|
||||||
|
|
||||||
<div id="box">
|
<main id="box" role="main" aria-labelledby="head-text">
|
||||||
|
|
||||||
<div id="headline">
|
<div id="headline">
|
||||||
<h1 id="head-text" class="center">{{.login.headline}}</h1>
|
<h1 id="head-text" class="center">{{.login.headline}}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="err" class="errorMsg center">{{.authenticationErr}}</p>
|
<p id="err" class="errorMsg center" role="alert" aria-live="assertive" aria-atomic="true">{{.authenticationErr}}</p>
|
||||||
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
|
||||||
<form id="authentication" action="" method="post">
|
<form id="authentication" action="" method="post" aria-describedby="err" novalidate>
|
||||||
|
|
||||||
<h5>{{.login.username.title}}:</h5>
|
<label for="username">{{.login.username.title}}:</label>
|
||||||
<input id="username" type="text" name="username" placeholder="Username" value="">
|
<input id="username" type="text" name="username" placeholder="Username" value="" autocomplete="username">
|
||||||
<h5>{{.login.password.title}}:</h5>
|
<label for="password">{{.login.password.title}}:</label>
|
||||||
<input id="password" type="password" name="password" placeholder="Password" value="">
|
<input id="password" type="password" name="password" placeholder="Password" value="" autocomplete="current-password">
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="box-footer">
|
<div id="box-footer">
|
||||||
<input id="submit" class="" type="button" value="{{.button.login}}" onclick="javascript: login();">
|
<input id="submit" class="" type="button" value="{{.button.login}}" aria-label="{{.button.login}}" onclick="javascript: login();">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<link rel="stylesheet" href="css/base.css" type="text/css">
|
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="auth-screen">
|
||||||
|
|
||||||
<div id="header" class="imgCenter"></div>
|
<div id="header" class="imgCenter"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func activatedSystemAuthentication() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaults = make(map[string]interface{})
|
var defaults = make(map[string]any)
|
||||||
defaults["authentication.web"] = false
|
defaults["authentication.web"] = false
|
||||||
defaults["authentication.pms"] = false
|
defaults["authentication.pms"] = false
|
||||||
defaults["authentication.xml"] = false
|
defaults["authentication.xml"] = false
|
||||||
@@ -43,7 +43,7 @@ func createFirstUserForAuthentication(username, password string) (token string,
|
|||||||
token, err = authentication.CheckTheValidityOfTheToken(token)
|
token, err = authentication.CheckTheValidityOfTheToken(token)
|
||||||
authenticationErr(err)
|
authenticationErr(err)
|
||||||
|
|
||||||
var userData = make(map[string]interface{})
|
var userData = make(map[string]any)
|
||||||
userData["username"] = username
|
userData["username"] = username
|
||||||
userData["authentication.web"] = true
|
userData["authentication.web"] = true
|
||||||
userData["authentication.pms"] = true
|
userData["authentication.pms"] = true
|
||||||
|
|||||||
@@ -657,7 +657,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
|
|||||||
Redirect:
|
Redirect:
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", currentURL, nil)
|
req, err := http.NewRequest("GET", currentURL, nil)
|
||||||
req.Header.Set("User-Agent", Settings.UserAgent)
|
req.Header.Set("User-Agent", getUserAgent())
|
||||||
req.Header.Set("Connection", "close")
|
req.Header.Set("Connection", "close")
|
||||||
//req.Header.Set("Range", "bytes=0-")
|
//req.Header.Set("Range", "bytes=0-")
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
@@ -1423,14 +1423,16 @@ func thirdPartyBuffer(streamID int, playlistID string) {
|
|||||||
// User-Agent setzen
|
// User-Agent setzen
|
||||||
var args []string
|
var args []string
|
||||||
|
|
||||||
|
var userAgent = getUserAgent()
|
||||||
|
|
||||||
for i, a := range strings.Split(options, " ") {
|
for i, a := range strings.Split(options, " ") {
|
||||||
|
|
||||||
switch bufferType {
|
switch bufferType {
|
||||||
case "FFMPEG":
|
case "FFMPEG":
|
||||||
a = strings.Replace(a, "[URL]", url, -1)
|
a = strings.Replace(a, "[URL]", url, -1)
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
if len(Settings.UserAgent) != 0 {
|
if len(userAgent) != 0 {
|
||||||
args = []string{"-user_agent", Settings.UserAgent}
|
args = []string{"-user_agent", userAgent}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1441,8 +1443,8 @@ func thirdPartyBuffer(streamID int, playlistID string) {
|
|||||||
a = strings.Replace(a, "[URL]", url, -1)
|
a = strings.Replace(a, "[URL]", url, -1)
|
||||||
args = append(args, a)
|
args = append(args, a)
|
||||||
|
|
||||||
if len(Settings.UserAgent) != 0 {
|
if len(userAgent) != 0 {
|
||||||
args = append(args, fmt.Sprintf(":http-user-agent=%s", Settings.UserAgent))
|
args = append(args, fmt.Sprintf(":http-user-agent=%s", userAgent))
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
116
src/data.go
116
src/data.go
@@ -23,6 +23,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
var reloadData = false
|
var reloadData = false
|
||||||
var cacheImages = false
|
var cacheImages = false
|
||||||
var createXEPGFiles = false
|
var createXEPGFiles = false
|
||||||
|
var triggerPlexGuideReload = false
|
||||||
var debug string
|
var debug string
|
||||||
|
|
||||||
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
|
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
|
||||||
@@ -36,6 +37,21 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
case "tuner":
|
case "tuner":
|
||||||
showWarning(2105)
|
showWarning(2105)
|
||||||
|
|
||||||
|
case "use_plexAPI":
|
||||||
|
triggerPlexGuideReload = true
|
||||||
|
|
||||||
|
case "plex.url":
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
value = strings.TrimRight(strings.TrimSpace(v), "/")
|
||||||
|
}
|
||||||
|
triggerPlexGuideReload = true
|
||||||
|
|
||||||
|
case "plex.token":
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
value = strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
triggerPlexGuideReload = true
|
||||||
|
|
||||||
case "epgSource":
|
case "epgSource":
|
||||||
reloadData = true
|
reloadData = true
|
||||||
|
|
||||||
@@ -43,7 +59,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
// Leerzeichen aus den Werten entfernen und Formatierung der Uhrzeit überprüfen (0000 - 2359)
|
// Leerzeichen aus den Werten entfernen und Formatierung der Uhrzeit überprüfen (0000 - 2359)
|
||||||
var newUpdateTimes = make([]string, 0)
|
var newUpdateTimes = make([]string, 0)
|
||||||
|
|
||||||
for _, v := range value.([]interface{}) {
|
for _, v := range value.([]any) {
|
||||||
|
|
||||||
v = strings.Replace(v.(string), " ", "", -1)
|
v = strings.Replace(v.(string), " ", "", -1)
|
||||||
|
|
||||||
@@ -69,6 +85,16 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
case "xepg.replace.missing.images":
|
case "xepg.replace.missing.images":
|
||||||
createXEPGFiles = true
|
createXEPGFiles = true
|
||||||
|
|
||||||
|
case "xepg.missing.epg.mode":
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
mode := strings.ToLower(strings.TrimSpace(v))
|
||||||
|
if mode != "relaxed" {
|
||||||
|
mode = "strict"
|
||||||
|
}
|
||||||
|
value = mode
|
||||||
|
}
|
||||||
|
reloadData = true
|
||||||
|
|
||||||
case "backup.path":
|
case "backup.path":
|
||||||
value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator)
|
value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator)
|
||||||
err = checkFolder(value.(string))
|
err = checkFolder(value.(string))
|
||||||
@@ -119,6 +145,9 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
|
|
||||||
oldSettings[key] = value
|
oldSettings[key] = value
|
||||||
|
|
||||||
|
if key == "plex.token" {
|
||||||
|
debug = fmt.Sprintf("Save Setting:Key: %s | Value: ******** (%T)", key, value)
|
||||||
|
} else {
|
||||||
switch fmt.Sprintf("%T", value) {
|
switch fmt.Sprintf("%T", value) {
|
||||||
|
|
||||||
case "bool":
|
case "bool":
|
||||||
@@ -136,6 +165,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
default:
|
default:
|
||||||
debug = fmt.Sprintf("%T", value)
|
debug = fmt.Sprintf("%T", value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
showDebug(debug, 1)
|
showDebug(debug, 1)
|
||||||
|
|
||||||
@@ -250,6 +280,10 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if triggerPlexGuideReload == true {
|
||||||
|
queuePlexGuideRefresh("settings change")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -258,8 +292,8 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
|||||||
// Providerdaten speichern (WebUI)
|
// Providerdaten speichern (WebUI)
|
||||||
func saveFiles(request RequestStruct, fileType string) (err error) {
|
func saveFiles(request RequestStruct, fileType string) (err error) {
|
||||||
|
|
||||||
var filesMap = make(map[string]interface{})
|
var filesMap = make(map[string]any)
|
||||||
var newData = make(map[string]interface{})
|
var newData = make(map[string]any)
|
||||||
var indicator string
|
var indicator string
|
||||||
var reloadData = false
|
var reloadData = false
|
||||||
|
|
||||||
@@ -281,7 +315,7 @@ func saveFiles(request RequestStruct, fileType string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(filesMap) == 0 {
|
if len(filesMap) == 0 {
|
||||||
filesMap = make(map[string]interface{})
|
filesMap = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
for dataID, data := range newData {
|
for dataID, data := range newData {
|
||||||
@@ -290,15 +324,15 @@ func saveFiles(request RequestStruct, fileType string) (err error) {
|
|||||||
|
|
||||||
// Neue Providerdatei
|
// Neue Providerdatei
|
||||||
dataID = indicator + randomString(19)
|
dataID = indicator + randomString(19)
|
||||||
data.(map[string]interface{})["new"] = true
|
data.(map[string]any)["new"] = true
|
||||||
filesMap[dataID] = data
|
filesMap[dataID] = data
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Bereits vorhandene Providerdatei
|
// Bereits vorhandene Providerdatei
|
||||||
for key, value := range data.(map[string]interface{}) {
|
for key, value := range data.(map[string]any) {
|
||||||
|
|
||||||
var oldData = filesMap[dataID].(map[string]interface{})
|
var oldData = filesMap[dataID].(map[string]any)
|
||||||
oldData[key] = value
|
oldData[key] = value
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -319,11 +353,11 @@ func saveFiles(request RequestStruct, fileType string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Neue Providerdatei
|
// Neue Providerdatei
|
||||||
if _, ok := data.(map[string]interface{})["new"]; ok {
|
if _, ok := data.(map[string]any)["new"]; ok {
|
||||||
|
|
||||||
reloadData = true
|
reloadData = true
|
||||||
err = getProviderData(fileType, dataID)
|
err = getProviderData(fileType, dataID)
|
||||||
delete(data.(map[string]interface{}), "new")
|
delete(data.(map[string]any), "new")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
delete(filesMap, dataID)
|
delete(filesMap, dataID)
|
||||||
@@ -332,7 +366,7 @@ func saveFiles(request RequestStruct, fileType string) (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
if _, ok := data.(map[string]any)["delete"]; ok {
|
||||||
|
|
||||||
deleteLocalProviderFiles(dataID, fileType)
|
deleteLocalProviderFiles(dataID, fileType)
|
||||||
reloadData = true
|
reloadData = true
|
||||||
@@ -365,7 +399,7 @@ func saveFiles(request RequestStruct, fileType string) (err error) {
|
|||||||
// Providerdaten manuell aktualisieren (WebUI)
|
// Providerdaten manuell aktualisieren (WebUI)
|
||||||
func updateFile(request RequestStruct, fileType string) (err error) {
|
func updateFile(request RequestStruct, fileType string) (err error) {
|
||||||
|
|
||||||
var updateData = make(map[string]interface{})
|
var updateData = make(map[string]any)
|
||||||
|
|
||||||
switch fileType {
|
switch fileType {
|
||||||
|
|
||||||
@@ -395,7 +429,7 @@ func updateFile(request RequestStruct, fileType string) (err error) {
|
|||||||
// Providerdaten löschen (WebUI)
|
// Providerdaten löschen (WebUI)
|
||||||
func deleteLocalProviderFiles(dataID, fileType string) {
|
func deleteLocalProviderFiles(dataID, fileType string) {
|
||||||
|
|
||||||
var removeData = make(map[string]interface{})
|
var removeData = make(map[string]any)
|
||||||
var fileExtension string
|
var fileExtension string
|
||||||
|
|
||||||
switch fileType {
|
switch fileType {
|
||||||
@@ -424,8 +458,8 @@ func deleteLocalProviderFiles(dataID, fileType string) {
|
|||||||
// Filtereinstellungen speichern (WebUI)
|
// Filtereinstellungen speichern (WebUI)
|
||||||
func saveFilter(request RequestStruct) (settings SettingsStruct, err error) {
|
func saveFilter(request RequestStruct) (settings SettingsStruct, err error) {
|
||||||
|
|
||||||
var filterMap = make(map[int64]interface{})
|
var filterMap = make(map[int64]any)
|
||||||
var newData = make(map[int64]interface{})
|
var newData = make(map[int64]any)
|
||||||
var defaultFilter FilterStruct
|
var defaultFilter FilterStruct
|
||||||
var newFilter = false
|
var newFilter = false
|
||||||
|
|
||||||
@@ -458,15 +492,15 @@ func saveFilter(request RequestStruct) (settings SettingsStruct, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter aktualisieren / löschen
|
// Filter aktualisieren / löschen
|
||||||
for key, value := range data.(map[string]interface{}) {
|
for key, value := range data.(map[string]any) {
|
||||||
|
|
||||||
// Filter löschen
|
// Filter löschen
|
||||||
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
if _, ok := data.(map[string]any)["delete"]; ok {
|
||||||
delete(filterMap, dataID)
|
delete(filterMap, dataID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter, ok := data.(map[string]interface{})["filter"].(string); ok {
|
if filter, ok := data.(map[string]any)["filter"].(string); ok {
|
||||||
|
|
||||||
if len(filter) == 0 {
|
if len(filter) == 0 {
|
||||||
|
|
||||||
@@ -480,7 +514,7 @@ func saveFilter(request RequestStruct) (settings SettingsStruct, err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldData, ok := filterMap[dataID].(map[string]interface{}); ok {
|
if oldData, ok := filterMap[dataID].(map[string]any); ok {
|
||||||
oldData[key] = value
|
oldData[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,7 +607,7 @@ func saveUserData(request RequestStruct) (err error) {
|
|||||||
|
|
||||||
var userData = request.UserData
|
var userData = request.UserData
|
||||||
|
|
||||||
var newCredentials = func(userID string, newUserData map[string]interface{}) (err error) {
|
var newCredentials = func(userID string, newUserData map[string]any) (err error) {
|
||||||
|
|
||||||
var newUsername, newPassword string
|
var newUsername, newPassword string
|
||||||
if username, ok := newUserData["username"].(string); ok {
|
if username, ok := newUserData["username"].(string); ok {
|
||||||
@@ -593,7 +627,7 @@ func saveUserData(request RequestStruct) (err error) {
|
|||||||
|
|
||||||
for userID, newUserData := range userData {
|
for userID, newUserData := range userData {
|
||||||
|
|
||||||
err = newCredentials(userID, newUserData.(map[string]interface{}))
|
err = newCredentials(userID, newUserData.(map[string]any))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -603,16 +637,16 @@ func saveUserData(request RequestStruct) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(newUserData.(map[string]interface{}), "password")
|
delete(newUserData.(map[string]any), "password")
|
||||||
delete(newUserData.(map[string]interface{}), "confirm")
|
delete(newUserData.(map[string]any), "confirm")
|
||||||
|
|
||||||
if _, ok := newUserData.(map[string]interface{})["delete"]; ok {
|
if _, ok := newUserData.(map[string]any)["delete"]; ok {
|
||||||
|
|
||||||
authentication.RemoveUser(userID)
|
authentication.RemoveUser(userID)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
err = authentication.WriteUserData(userID, newUserData.(map[string]interface{}))
|
err = authentication.WriteUserData(userID, newUserData.(map[string]any))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -662,11 +696,11 @@ func saveWizard(request RequestStruct) (nextStep int, err error) {
|
|||||||
|
|
||||||
case "m3u", "xmltv":
|
case "m3u", "xmltv":
|
||||||
|
|
||||||
var filesMap = make(map[string]interface{})
|
var filesMap = make(map[string]any)
|
||||||
var data = make(map[string]interface{})
|
var data = make(map[string]any)
|
||||||
var indicator, dataID string
|
var indicator, dataID string
|
||||||
|
|
||||||
filesMap = make(map[string]interface{})
|
filesMap = make(map[string]any)
|
||||||
|
|
||||||
data["type"] = key
|
data["type"] = key
|
||||||
data["new"] = true
|
data["new"] = true
|
||||||
@@ -737,6 +771,10 @@ func saveWizard(request RequestStruct) (nextStep int, err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nextStep == 10 {
|
||||||
|
Settings.WizardCompleted = true
|
||||||
|
}
|
||||||
|
|
||||||
err = saveSettings(Settings)
|
err = saveSettings(Settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -797,9 +835,9 @@ func buildDatabaseDVR() (err error) {
|
|||||||
|
|
||||||
System.ScanInProgress = 1
|
System.ScanInProgress = 1
|
||||||
|
|
||||||
Data.Streams.All = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
Data.Streams.All = make([]any, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Streams.Active = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
Data.Streams.Active = make([]any, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Streams.Inactive = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
Data.Streams.Inactive = make([]any, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Playlist.M3U.Groups.Text = []string{}
|
Data.Playlist.M3U.Groups.Text = []string{}
|
||||||
Data.Playlist.M3U.Groups.Value = []string{}
|
Data.Playlist.M3U.Groups.Value = []string{}
|
||||||
Data.StreamPreviewUI.Active = []string{}
|
Data.StreamPreviewUI.Active = []string{}
|
||||||
@@ -820,7 +858,7 @@ func buildDatabaseDVR() (err error) {
|
|||||||
|
|
||||||
for n, i := range playlistFile {
|
for n, i := range playlistFile {
|
||||||
|
|
||||||
var channels []interface{}
|
var channels []any
|
||||||
var groupTitle, tvgID, uuid int = 0, 0, 0
|
var groupTitle, tvgID, uuid int = 0, 0, 0
|
||||||
var keys = []string{"group-title", "tvg-id", "uuid"}
|
var keys = []string{"group-title", "tvg-id", "uuid"}
|
||||||
var compatibility = make(map[string]int)
|
var compatibility = make(map[string]int)
|
||||||
@@ -957,7 +995,7 @@ func buildDatabaseDVR() (err error) {
|
|||||||
|
|
||||||
if len(Data.Streams.Active) == 0 && len(Data.Streams.All) <= System.UnfilteredChannelLimit && len(Settings.Filter) == 0 {
|
if len(Data.Streams.Active) == 0 && len(Data.Streams.All) <= System.UnfilteredChannelLimit && len(Settings.Filter) == 0 {
|
||||||
Data.Streams.Active = Data.Streams.All
|
Data.Streams.Active = Data.Streams.All
|
||||||
Data.Streams.Inactive = make([]interface{}, 0)
|
Data.Streams.Inactive = make([]any, 0)
|
||||||
|
|
||||||
Data.StreamPreviewUI.Active = Data.StreamPreviewUI.Inactive
|
Data.StreamPreviewUI.Active = Data.StreamPreviewUI.Inactive
|
||||||
Data.StreamPreviewUI.Inactive = []string{}
|
Data.StreamPreviewUI.Inactive = []string{}
|
||||||
@@ -980,6 +1018,10 @@ func buildDatabaseDVR() (err error) {
|
|||||||
sort.Strings(Data.StreamPreviewUI.Active)
|
sort.Strings(Data.StreamPreviewUI.Active)
|
||||||
sort.Strings(Data.StreamPreviewUI.Inactive)
|
sort.Strings(Data.StreamPreviewUI.Inactive)
|
||||||
|
|
||||||
|
if Settings.EpgSource != "XEPG" {
|
||||||
|
queuePlexGuideRefresh("lineup update")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,7 +1029,7 @@ func buildDatabaseDVR() (err error) {
|
|||||||
func getLocalProviderFiles(fileType string) (localFiles []string) {
|
func getLocalProviderFiles(fileType string) (localFiles []string) {
|
||||||
|
|
||||||
var fileExtension string
|
var fileExtension string
|
||||||
var dataMap = make(map[string]interface{})
|
var dataMap = make(map[string]any)
|
||||||
|
|
||||||
switch fileType {
|
switch fileType {
|
||||||
|
|
||||||
@@ -1015,7 +1057,7 @@ func getLocalProviderFiles(fileType string) (localFiles []string) {
|
|||||||
// Providerparameter anhand von dem Key ausgeben
|
// Providerparameter anhand von dem Key ausgeben
|
||||||
func getProviderParameter(id, fileType, key string) (s string) {
|
func getProviderParameter(id, fileType, key string) (s string) {
|
||||||
|
|
||||||
var dataMap = make(map[string]interface{})
|
var dataMap = make(map[string]any)
|
||||||
|
|
||||||
switch fileType {
|
switch fileType {
|
||||||
case "m3u":
|
case "m3u":
|
||||||
@@ -1028,7 +1070,7 @@ func getProviderParameter(id, fileType, key string) (s string) {
|
|||||||
dataMap = Settings.Files.XMLTV
|
dataMap = Settings.Files.XMLTV
|
||||||
}
|
}
|
||||||
|
|
||||||
if data, ok := dataMap[id].(map[string]interface{}); ok {
|
if data, ok := dataMap[id].(map[string]any); ok {
|
||||||
|
|
||||||
if v, ok := data[key].(string); ok {
|
if v, ok := data[key].(string); ok {
|
||||||
s = v
|
s = v
|
||||||
@@ -1046,7 +1088,7 @@ func getProviderParameter(id, fileType, key string) (s string) {
|
|||||||
// Provider Statistiken Kompatibilität aktualisieren
|
// Provider Statistiken Kompatibilität aktualisieren
|
||||||
func setProviderCompatibility(id, fileType string, compatibility map[string]int) {
|
func setProviderCompatibility(id, fileType string, compatibility map[string]int) {
|
||||||
|
|
||||||
var dataMap = make(map[string]interface{})
|
var dataMap = make(map[string]any)
|
||||||
|
|
||||||
switch fileType {
|
switch fileType {
|
||||||
case "m3u":
|
case "m3u":
|
||||||
@@ -1059,7 +1101,7 @@ func setProviderCompatibility(id, fileType string, compatibility map[string]int)
|
|||||||
dataMap = Settings.Files.XMLTV
|
dataMap = Settings.Files.XMLTV
|
||||||
}
|
}
|
||||||
|
|
||||||
if data, ok := dataMap[id].(map[string]interface{}); ok {
|
if data, ok := dataMap[id].(map[string]any); ok {
|
||||||
|
|
||||||
data["compatibility"] = compatibility
|
data["compatibility"] = compatibility
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeInteraceFromHDHR(content []byte, playlistName, id string) (channels []interface{}, err error) {
|
func makeInteraceFromHDHR(content []byte, playlistName, id string) (channels []any, err error) {
|
||||||
|
|
||||||
var hdhrData []interface{}
|
var hdhrData []any
|
||||||
|
|
||||||
err = json.Unmarshal(content, &hdhrData)
|
err = json.Unmarshal(content, &hdhrData)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -17,7 +17,7 @@ func makeInteraceFromHDHR(content []byte, playlistName, id string) (channels []i
|
|||||||
for _, d := range hdhrData {
|
for _, d := range hdhrData {
|
||||||
|
|
||||||
var channel = make(map[string]string)
|
var channel = make(map[string]string)
|
||||||
var data = d.(map[string]interface{})
|
var data = d.(map[string]any)
|
||||||
|
|
||||||
channel["group-title"] = playlistName
|
channel["group-title"] = playlistName
|
||||||
channel["name"] = data["GuideName"].(string)
|
channel["name"] = data["GuideName"].(string)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var htmlFolder string
|
var htmlFolder string
|
||||||
@@ -16,7 +17,7 @@ var goFile string
|
|||||||
var mapName string
|
var mapName string
|
||||||
var packageName string
|
var packageName string
|
||||||
|
|
||||||
var blankMap = make(map[string]interface{})
|
var blankMap = make(map[string]any)
|
||||||
|
|
||||||
// HTMLInit : Dateipfade festlegen
|
// HTMLInit : Dateipfade festlegen
|
||||||
// mapName = Name der zu erstellenden map
|
// mapName = Name der zu erstellenden map
|
||||||
@@ -68,14 +69,14 @@ func createMapFromFiles(folder string) string {
|
|||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var content string
|
var content strings.Builder
|
||||||
|
|
||||||
for key := range blankMap {
|
for key := range blankMap {
|
||||||
var newKey = key
|
var newKey = key
|
||||||
content += ` ` + mapName + `["` + newKey + `"` + `] = "` + blankMap[key].(string) + `"` + "\n"
|
content.WriteString(` ` + mapName + `["` + newKey + `"` + `] = "` + blankMap[key].(string) + `"` + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return content
|
return content.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFilesToMap(path string, info os.FileInfo, err error) error {
|
func readFilesToMap(path string, info os.FileInfo, err error) error {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func ShowSystemInfo() {
|
|||||||
fmt.Println(fmt.Sprintf("Files Update: %t", Settings.FilesUpdate))
|
fmt.Println(fmt.Sprintf("Files Update: %t", Settings.FilesUpdate))
|
||||||
fmt.Println(fmt.Sprintf("Folder (tmp): %s", Settings.TempPath))
|
fmt.Println(fmt.Sprintf("Folder (tmp): %s", Settings.TempPath))
|
||||||
fmt.Println(fmt.Sprintf("Image Chaching: %t", Settings.CacheImages))
|
fmt.Println(fmt.Sprintf("Image Chaching: %t", Settings.CacheImages))
|
||||||
|
fmt.Println(fmt.Sprintf("Missing EPG Mode: %s", Settings.XepgMissingEPGMode))
|
||||||
fmt.Println(fmt.Sprintf("Replace EPG Image: %t", Settings.XepgReplaceMissingImages))
|
fmt.Println(fmt.Sprintf("Replace EPG Image: %t", Settings.XepgReplaceMissingImages))
|
||||||
|
|
||||||
println("---")
|
println("---")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -27,8 +28,9 @@ var database string
|
|||||||
|
|
||||||
var databaseFile = "authentication.json"
|
var databaseFile = "authentication.json"
|
||||||
|
|
||||||
var data = make(map[string]interface{})
|
var data = make(map[string]any)
|
||||||
var tokens = make(map[string]interface{})
|
var tokens = make(map[string]any)
|
||||||
|
var tokensMu sync.RWMutex
|
||||||
|
|
||||||
var initAuthentication = false
|
var initAuthentication = false
|
||||||
|
|
||||||
@@ -133,10 +135,10 @@ func Init(databasePath string, validity int) (err error) {
|
|||||||
// Check if the database already exists
|
// Check if the database already exists
|
||||||
if _, err = os.Stat(database); os.IsNotExist(err) {
|
if _, err = os.Stat(database); os.IsNotExist(err) {
|
||||||
// Create an empty database
|
// Create an empty database
|
||||||
var defaults = make(map[string]interface{})
|
var defaults = make(map[string]any)
|
||||||
defaults["dbVersion"] = "1.0"
|
defaults["dbVersion"] = "1.0"
|
||||||
defaults["hash"] = "sha256"
|
defaults["hash"] = "sha256"
|
||||||
defaults["users"] = make(map[string]interface{})
|
defaults["users"] = make(map[string]any)
|
||||||
|
|
||||||
if saveDatabase(defaults) != nil {
|
if saveDatabase(defaults) != nil {
|
||||||
return
|
return
|
||||||
@@ -160,7 +162,7 @@ func CreateDefaultUser(username, password string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var users = data["users"].(map[string]interface{})
|
var users = data["users"].(map[string]any)
|
||||||
// Check if the default user exists
|
// Check if the default user exists
|
||||||
if len(users) > 0 {
|
if len(users) > 0 {
|
||||||
err = createError(001)
|
err = createError(001)
|
||||||
@@ -182,7 +184,7 @@ func CreateNewUser(username, password string) (userID string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkIfTheUserAlreadyExists = func(username string, userData map[string]interface{}) (err error) {
|
var checkIfTheUserAlreadyExists = func(username string, userData map[string]any) (err error) {
|
||||||
var salt = userData["_salt"].(string)
|
var salt = userData["_salt"].(string)
|
||||||
var loginUsername = userData["_username"].(string)
|
var loginUsername = userData["_username"].(string)
|
||||||
|
|
||||||
@@ -193,9 +195,9 @@ func CreateNewUser(username, password string) (userID string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var users = data["users"].(map[string]interface{})
|
var users = data["users"].(map[string]any)
|
||||||
for _, userData := range users {
|
for _, userData := range users {
|
||||||
err = checkIfTheUserAlreadyExists(username, userData.(map[string]interface{}))
|
err = checkIfTheUserAlreadyExists(username, userData.(map[string]any))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -218,7 +220,7 @@ func UserAuthentication(username, password string) (token string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var login = func(username, password string, loginData map[string]interface{}) (err error) {
|
var login = func(username, password string, loginData map[string]any) (err error) {
|
||||||
err = createError(010)
|
err = createError(010)
|
||||||
|
|
||||||
var salt = loginData["_salt"].(string)
|
var salt = loginData["_salt"].(string)
|
||||||
@@ -234,9 +236,9 @@ func UserAuthentication(username, password string) (token string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var users = data["users"].(map[string]interface{})
|
var users = data["users"].(map[string]any)
|
||||||
for id, loginData := range users {
|
for id, loginData := range users {
|
||||||
err = login(username, password, loginData.(map[string]interface{}))
|
err = login(username, password, loginData.(map[string]any))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
token = setToken(id, "-")
|
token = setToken(id, "-")
|
||||||
return
|
return
|
||||||
@@ -256,20 +258,21 @@ func CheckTheValidityOfTheToken(token string) (newToken string, err error) {
|
|||||||
|
|
||||||
err = createError(011)
|
err = createError(011)
|
||||||
|
|
||||||
|
tokensMu.Lock()
|
||||||
|
defer tokensMu.Unlock()
|
||||||
|
|
||||||
if v, ok := tokens[token]; ok {
|
if v, ok := tokens[token]; ok {
|
||||||
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
expires := v.(map[string]any)["expires"].(time.Time)
|
||||||
var userID = v.(map[string]interface{})["id"].(string)
|
|
||||||
|
|
||||||
if expires.Sub(time.Now().Local()) < 0 {
|
if expires.Sub(time.Now().Local()) < 0 {
|
||||||
|
delete(tokens, token)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newToken = setToken(userID, token)
|
// Keep a stable token per session and only refresh expiration.
|
||||||
|
v.(map[string]any)["expires"] = time.Now().Local().Add(time.Minute * time.Duration(tokenValidity))
|
||||||
|
newToken = token
|
||||||
err = nil
|
err = nil
|
||||||
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -285,11 +288,15 @@ func GetUserID(token string) (userID string, err error) {
|
|||||||
|
|
||||||
err = createError(002)
|
err = createError(002)
|
||||||
|
|
||||||
|
tokensMu.Lock()
|
||||||
|
defer tokensMu.Unlock()
|
||||||
|
|
||||||
if v, ok := tokens[token]; ok {
|
if v, ok := tokens[token]; ok {
|
||||||
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
expires := v.(map[string]any)["expires"].(time.Time)
|
||||||
userID = v.(map[string]interface{})["id"].(string)
|
userID = v.(map[string]any)["id"].(string)
|
||||||
|
|
||||||
if expires.Sub(time.Now().Local()) < 0 {
|
if expires.Sub(time.Now().Local()) < 0 {
|
||||||
|
delete(tokens, token)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +307,7 @@ func GetUserID(token string) (userID string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteUserData : save user date
|
// WriteUserData : save user date
|
||||||
func WriteUserData(userID string, userData map[string]interface{}) (err error) {
|
func WriteUserData(userID string, userData map[string]any) (err error) {
|
||||||
|
|
||||||
err = checkInit()
|
err = checkInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -309,7 +316,7 @@ func WriteUserData(userID string, userData map[string]interface{}) (err error) {
|
|||||||
|
|
||||||
err = createError(030)
|
err = createError(030)
|
||||||
|
|
||||||
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
if v, ok := data["users"].(map[string]any)[userID].(map[string]any); ok {
|
||||||
|
|
||||||
v["data"] = userData
|
v["data"] = userData
|
||||||
err = saveDatabase(data)
|
err = saveDatabase(data)
|
||||||
@@ -322,7 +329,7 @@ func WriteUserData(userID string, userData map[string]interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadUserData : load user date
|
// ReadUserData : load user date
|
||||||
func ReadUserData(userID string) (userData map[string]interface{}, err error) {
|
func ReadUserData(userID string) (userData map[string]any, err error) {
|
||||||
|
|
||||||
err = checkInit()
|
err = checkInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -331,8 +338,8 @@ func ReadUserData(userID string) (userData map[string]interface{}, err error) {
|
|||||||
|
|
||||||
err = createError(031)
|
err = createError(031)
|
||||||
|
|
||||||
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
if v, ok := data["users"].(map[string]any)[userID].(map[string]any); ok {
|
||||||
userData = v["data"].(map[string]interface{})
|
userData = v["data"].(map[string]any)
|
||||||
err = nil
|
err = nil
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -351,9 +358,9 @@ func RemoveUser(userID string) (err error) {
|
|||||||
|
|
||||||
err = createError(032)
|
err = createError(032)
|
||||||
|
|
||||||
if _, ok := data["users"].(map[string]interface{})[userID]; ok {
|
if _, ok := data["users"].(map[string]any)[userID]; ok {
|
||||||
|
|
||||||
delete(data["users"].(map[string]interface{}), userID)
|
delete(data["users"].(map[string]any), userID)
|
||||||
err = saveDatabase(data)
|
err = saveDatabase(data)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -363,13 +370,13 @@ func RemoveUser(userID string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultUserData : set default user data
|
// SetDefaultUserData : set default user data
|
||||||
func SetDefaultUserData(defaults map[string]interface{}) (err error) {
|
func SetDefaultUserData(defaults map[string]any) (err error) {
|
||||||
|
|
||||||
allUserData, err := GetAllUserData()
|
allUserData, err := GetAllUserData()
|
||||||
|
|
||||||
for _, d := range allUserData {
|
for _, d := range allUserData {
|
||||||
var data = d.(map[string]interface{})["data"].(map[string]interface{})
|
var data = d.(map[string]any)["data"].(map[string]any)
|
||||||
var userID = d.(map[string]interface{})["_id"].(string)
|
var userID = d.(map[string]any)["_id"].(string)
|
||||||
|
|
||||||
for k, v := range defaults {
|
for k, v := range defaults {
|
||||||
if _, ok := data[k]; ok {
|
if _, ok := data[k]; ok {
|
||||||
@@ -392,16 +399,16 @@ func ChangeCredentials(userID, username, password string) (err error) {
|
|||||||
|
|
||||||
err = createError(032)
|
err = createError(032)
|
||||||
|
|
||||||
if userData, ok := data["users"].(map[string]interface{})[userID]; ok {
|
if userData, ok := data["users"].(map[string]any)[userID]; ok {
|
||||||
//var userData = tmp.(map[string]interface{})
|
//var userData = tmp.(map[string]interface{})
|
||||||
var salt = userData.(map[string]interface{})["_salt"].(string)
|
var salt = userData.(map[string]any)["_salt"].(string)
|
||||||
|
|
||||||
if len(username) > 0 {
|
if len(username) > 0 {
|
||||||
userData.(map[string]interface{})["_username"] = SHA256(username, salt)
|
userData.(map[string]any)["_username"] = SHA256(username, salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(password) > 0 {
|
if len(password) > 0 {
|
||||||
userData.(map[string]interface{})["_password"] = SHA256(password, salt)
|
userData.(map[string]any)["_password"] = SHA256(password, salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = saveDatabase(data)
|
err = saveDatabase(data)
|
||||||
@@ -411,7 +418,7 @@ func ChangeCredentials(userID, username, password string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAllUserData : get all user data
|
// GetAllUserData : get all user data
|
||||||
func GetAllUserData() (allUserData map[string]interface{}, err error) {
|
func GetAllUserData() (allUserData map[string]any, err error) {
|
||||||
|
|
||||||
err = checkInit()
|
err = checkInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -419,15 +426,15 @@ func GetAllUserData() (allUserData map[string]interface{}, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
var defaults = make(map[string]interface{})
|
var defaults = make(map[string]any)
|
||||||
defaults["dbVersion"] = "1.0"
|
defaults["dbVersion"] = "1.0"
|
||||||
defaults["hash"] = "sha256"
|
defaults["hash"] = "sha256"
|
||||||
defaults["users"] = make(map[string]interface{})
|
defaults["users"] = make(map[string]any)
|
||||||
saveDatabase(defaults)
|
saveDatabase(defaults)
|
||||||
data = defaults
|
data = defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
allUserData = data["users"].(map[string]interface{})
|
allUserData = data["users"].(map[string]any)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +464,7 @@ func checkInit() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveDatabase(tmpMap interface{}) (err error) {
|
func saveDatabase(tmpMap any) (err error) {
|
||||||
|
|
||||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
|
|
||||||
@@ -544,21 +551,26 @@ func createError(errCode int) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultsForNewUser(username, password string) map[string]interface{} {
|
func defaultsForNewUser(username, password string) map[string]any {
|
||||||
var defaults = make(map[string]interface{})
|
var defaults = make(map[string]any)
|
||||||
var salt = randomString(saltLength)
|
var salt = randomString(saltLength)
|
||||||
defaults["_username"] = SHA256(username, salt)
|
defaults["_username"] = SHA256(username, salt)
|
||||||
defaults["_password"] = SHA256(password, salt)
|
defaults["_password"] = SHA256(password, salt)
|
||||||
defaults["_salt"] = salt
|
defaults["_salt"] = salt
|
||||||
defaults["_id"] = "id-" + randomID(idLength)
|
defaults["_id"] = "id-" + randomID(idLength)
|
||||||
//defaults["_one.time.token"] = randomString(tokenLength)
|
//defaults["_one.time.token"] = randomString(tokenLength)
|
||||||
defaults["data"] = make(map[string]interface{})
|
defaults["data"] = make(map[string]any)
|
||||||
|
|
||||||
return defaults
|
return defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
func setToken(id, oldToken string) (newToken string) {
|
func setToken(id, oldToken string) (newToken string) {
|
||||||
|
tokensMu.Lock()
|
||||||
|
defer tokensMu.Unlock()
|
||||||
|
|
||||||
|
if oldToken != "-" {
|
||||||
delete(tokens, oldToken)
|
delete(tokens, oldToken)
|
||||||
|
}
|
||||||
|
|
||||||
loopToken:
|
loopToken:
|
||||||
newToken = randomString(tokenLength)
|
newToken = randomString(tokenLength)
|
||||||
@@ -566,7 +578,7 @@ loopToken:
|
|||||||
goto loopToken
|
goto loopToken
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp = make(map[string]interface{})
|
var tmp = make(map[string]any)
|
||||||
tmp["id"] = id
|
tmp["id"] = id
|
||||||
tmp["expires"] = time.Now().Local().Add(time.Minute * time.Duration(tokenValidity))
|
tmp["expires"] = time.Now().Local().Add(time.Minute * time.Duration(tokenValidity))
|
||||||
|
|
||||||
@@ -575,7 +587,7 @@ loopToken:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToJSON(tmpMap interface{}) string {
|
func mapToJSON(tmpMap any) string {
|
||||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "{}"
|
return "{}"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func TestStream1(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkStream(streamInterface []interface{}) (err error) {
|
func checkStream(streamInterface []any) (err error) {
|
||||||
|
|
||||||
for i, s := range streamInterface {
|
for i, s := range streamInterface {
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// MakeInterfaceFromM3U :
|
// MakeInterfaceFromM3U :
|
||||||
func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err error) {
|
func MakeInterfaceFromM3U(byteStream []byte) (allChannels []any, err error) {
|
||||||
|
|
||||||
var content = string(byteStream)
|
var content = string(byteStream)
|
||||||
var channelName string
|
var channelName string
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type ClientInfo struct {
|
|||||||
OS string `json:"os,required"`
|
OS string `json:"os,required"`
|
||||||
URL string `json:"url,required"`
|
URL string `json:"url,required"`
|
||||||
|
|
||||||
Response ServerResponse `json:"response,omitempty"`
|
Response ServerResponse `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerResponse : Response from server after client request
|
// ServerResponse : Response from server after client request
|
||||||
@@ -106,7 +106,7 @@ func serverRequest() (err error) {
|
|||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
//fmt.Println(resp.StatusCode, Updater.URL, Updater.CMD)
|
//fmt.Println(resp.StatusCode, Updater.URL, Updater.CMD)
|
||||||
err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), Updater.URL))
|
err = fmt.Errorf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), Updater.URL)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Playlisten parsen
|
// Playlisten parsen
|
||||||
func parsePlaylist(filename, fileType string) (channels []interface{}, err error) {
|
func parsePlaylist(filename, fileType string) (channels []any, err error) {
|
||||||
|
|
||||||
content, err := readByteFromFile(filename)
|
content, err := readByteFromFile(filename)
|
||||||
var id = strings.TrimSuffix(getFilenameFromPath(filename), path.Ext(getFilenameFromPath(filename)))
|
var id = strings.TrimSuffix(getFilenameFromPath(filename), path.Ext(getFilenameFromPath(filename)))
|
||||||
@@ -34,7 +34,7 @@ func parsePlaylist(filename, fileType string) (channels []interface{}, err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Streams filtern
|
// Streams filtern
|
||||||
func filterThisStream(s interface{}) (status bool) {
|
func filterThisStream(s any) (status bool) {
|
||||||
|
|
||||||
status = false
|
status = false
|
||||||
var stream = s.(map[string]string)
|
var stream = s.(map[string]string)
|
||||||
|
|||||||
363
src/plex_api.go
Normal file
363
src/plex_api.go
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var plexRefreshState = struct {
|
||||||
|
sync.Mutex
|
||||||
|
lastRun time.Time
|
||||||
|
scheduled bool
|
||||||
|
inProgress bool
|
||||||
|
queued bool
|
||||||
|
reason string
|
||||||
|
missingConfigLogged bool
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// queuePlexGuideRefresh schedules a debounced Plex DVR guide refresh.
|
||||||
|
func queuePlexGuideRefresh(reason string) {
|
||||||
|
|
||||||
|
if Settings.UsePlexAPI == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plexRefreshState.Lock()
|
||||||
|
|
||||||
|
if len(strings.TrimSpace(reason)) > 0 {
|
||||||
|
plexRefreshState.reason = reason
|
||||||
|
}
|
||||||
|
|
||||||
|
if plexRefreshState.scheduled == true || plexRefreshState.inProgress == true {
|
||||||
|
plexRefreshState.queued = true
|
||||||
|
plexRefreshState.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plexRefreshState.scheduled = true
|
||||||
|
delay := plexRefreshDelayLocked()
|
||||||
|
plexRefreshState.Unlock()
|
||||||
|
|
||||||
|
go runPlexGuideRefresh(delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPlexGuideRefresh(initialDelay time.Duration) {
|
||||||
|
|
||||||
|
if initialDelay > 0 {
|
||||||
|
time.Sleep(initialDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
plexRefreshState.Lock()
|
||||||
|
reason := strings.TrimSpace(plexRefreshState.reason)
|
||||||
|
if len(reason) == 0 {
|
||||||
|
reason = "update"
|
||||||
|
}
|
||||||
|
plexRefreshState.scheduled = false
|
||||||
|
plexRefreshState.inProgress = true
|
||||||
|
plexRefreshState.queued = false
|
||||||
|
plexRefreshState.Unlock()
|
||||||
|
|
||||||
|
err := refreshPlexGuide(reason)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
plexRefreshState.Lock()
|
||||||
|
plexRefreshState.lastRun = time.Now()
|
||||||
|
runAgain := plexRefreshState.queued
|
||||||
|
plexRefreshState.inProgress = false
|
||||||
|
nextDelay := time.Duration(0)
|
||||||
|
|
||||||
|
if runAgain == true && Settings.UsePlexAPI == true {
|
||||||
|
plexRefreshState.scheduled = true
|
||||||
|
nextDelay = plexRefreshDelayLocked()
|
||||||
|
}
|
||||||
|
plexRefreshState.Unlock()
|
||||||
|
|
||||||
|
if runAgain == false || Settings.UsePlexAPI == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(nextDelay)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func plexRefreshDelayLocked() (delay time.Duration) {
|
||||||
|
|
||||||
|
const minDelay = 2 * time.Second
|
||||||
|
const cooldown = 20 * time.Second
|
||||||
|
|
||||||
|
if plexRefreshState.lastRun.IsZero() == true {
|
||||||
|
return minDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
since := time.Since(plexRefreshState.lastRun)
|
||||||
|
if since >= cooldown {
|
||||||
|
return minDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
return cooldown - since
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshPlexGuide(reason string) (err error) {
|
||||||
|
|
||||||
|
baseURL, token, ready := getPlexConfig()
|
||||||
|
if ready == false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dvrs, err := discoverPlexDVRs(baseURL, token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var refreshed int
|
||||||
|
var failed int
|
||||||
|
|
||||||
|
for _, dvr := range dvrs {
|
||||||
|
|
||||||
|
endpoints := getPlexReloadEndpoints(dvr)
|
||||||
|
if len(endpoints) == 0 {
|
||||||
|
failed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpointErr error
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
|
||||||
|
status, _, requestErr := doPlexRequest("POST", baseURL, endpoint, token)
|
||||||
|
if requestErr != nil {
|
||||||
|
endpointErr = requestErr
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if status >= 200 && status < 300 {
|
||||||
|
refreshed++
|
||||||
|
endpointErr = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointErr = fmt.Errorf("Plex API returned HTTP %d for %s", status, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpointErr != nil {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if refreshed == 0 {
|
||||||
|
if failed == 0 {
|
||||||
|
return errors.New("Plex API guide refresh failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Plex API guide refresh failed for %d DVR(s)", failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo(fmt.Sprintf("Plex API:Guide reload requested for %d DVR(s) (%s)", refreshed, reason))
|
||||||
|
if failed > 0 {
|
||||||
|
showInfo(fmt.Sprintf("Plex API:Guide reload failed for %d DVR(s)", failed))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlexConfig() (baseURL, token string, ready bool) {
|
||||||
|
|
||||||
|
baseURL = strings.TrimSpace(Settings.PlexURL)
|
||||||
|
token = strings.TrimSpace(Settings.PlexToken)
|
||||||
|
|
||||||
|
if strings.HasPrefix(baseURL, "http://") == false && strings.HasPrefix(baseURL, "https://") == false && len(baseURL) > 0 {
|
||||||
|
baseURL = "http://" + baseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL = strings.TrimRight(baseURL, "/")
|
||||||
|
|
||||||
|
plexRefreshState.Lock()
|
||||||
|
defer plexRefreshState.Unlock()
|
||||||
|
|
||||||
|
if len(baseURL) == 0 || len(token) == 0 {
|
||||||
|
|
||||||
|
if plexRefreshState.missingConfigLogged == false {
|
||||||
|
showInfo("Plex API:Skipped refresh because plex.url or plex.token is empty")
|
||||||
|
plexRefreshState.missingConfigLogged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
plexRefreshState.missingConfigLogged = false
|
||||||
|
|
||||||
|
return baseURL, token, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func discoverPlexDVRs(baseURL, token string) (dvrs []map[string]any, err error) {
|
||||||
|
|
||||||
|
status, body, err := doPlexRequest("GET", baseURL, "/livetv/dvrs", token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if status < 200 || status >= 300 {
|
||||||
|
err = fmt.Errorf("Plex API returned HTTP %d for /livetv/dvrs", status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = make(map[string]any)
|
||||||
|
err = json.Unmarshal(body, &payload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaContainer, ok := payload["MediaContainer"].(map[string]any)
|
||||||
|
if ok == false {
|
||||||
|
err = errors.New("Plex API response missing MediaContainer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range []string{"Dvr", "DVR", "Directory", "Metadata"} {
|
||||||
|
if raw, found := mediaContainer[key]; found == true {
|
||||||
|
if list, ok := raw.([]any); ok == true {
|
||||||
|
dvrs = convertToPlexMapSlice(list)
|
||||||
|
if len(dvrs) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.New("Plex API returned no DVR entries")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToPlexMapSlice(list []any) (dvrs []map[string]any) {
|
||||||
|
|
||||||
|
dvrs = make([]map[string]any, 0, len(list))
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
if dvr, ok := item.(map[string]any); ok == true {
|
||||||
|
dvrs = append(dvrs, dvr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlexReloadEndpoints(dvr map[string]any) (endpoints []string) {
|
||||||
|
|
||||||
|
endpoints = make([]string, 0, 6)
|
||||||
|
added := make(map[string]bool)
|
||||||
|
|
||||||
|
add := func(endpoint string) {
|
||||||
|
endpoint = strings.TrimSpace(endpoint)
|
||||||
|
if len(endpoint) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(endpoint, "/") == false {
|
||||||
|
endpoint = "/" + endpoint
|
||||||
|
}
|
||||||
|
if added[endpoint] == true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
added[endpoint] = true
|
||||||
|
endpoints = append(endpoints, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key := getPlexMapString(dvr, "key"); len(key) > 0 {
|
||||||
|
if strings.HasPrefix(key, "/") == true {
|
||||||
|
add(strings.TrimRight(key, "/") + "/reloadGuide")
|
||||||
|
} else {
|
||||||
|
add("/livetv/dvrs/" + url.PathEscape(key) + "/reloadGuide")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range []string{"uuid", "identifier", "machineIdentifier", "clientIdentifier", "id"} {
|
||||||
|
if value := getPlexMapString(dvr, field); len(value) > 0 {
|
||||||
|
add("/livetv/dvrs/" + url.PathEscape(value) + "/reloadGuide")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlexMapString(data map[string]any, field string) string {
|
||||||
|
|
||||||
|
for key, value := range data {
|
||||||
|
if strings.EqualFold(key, field) == false {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
|
||||||
|
case string:
|
||||||
|
return strings.TrimSpace(v)
|
||||||
|
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPlexRequest(method, baseURL, endpoint, token string) (status int, body []byte, err error) {
|
||||||
|
|
||||||
|
requestURL := strings.TrimRight(baseURL, "/") + "/" + strings.TrimLeft(endpoint, "/")
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, requestURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := req.URL.Query()
|
||||||
|
if len(token) > 0 {
|
||||||
|
query.Set("X-Plex-Token", token)
|
||||||
|
req.URL.RawQuery = query.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("X-Plex-Token", token)
|
||||||
|
|
||||||
|
if len(System.DeviceID) > 0 {
|
||||||
|
req.Header.Set("X-Plex-Client-Identifier", System.DeviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(System.Name) > 0 {
|
||||||
|
req.Header.Set("X-Plex-Product", System.Name)
|
||||||
|
req.Header.Set("X-Plex-Device-Name", System.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(System.Version) > 0 {
|
||||||
|
req.Header.Set("X-Plex-Version", System.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
status = resp.StatusCode
|
||||||
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -17,13 +17,13 @@ func getProviderData(fileType, fileID string) (err error) {
|
|||||||
var fileExtension, serverFileName string
|
var fileExtension, serverFileName string
|
||||||
var body = make([]byte, 0)
|
var body = make([]byte, 0)
|
||||||
var newProvider = false
|
var newProvider = false
|
||||||
var dataMap = make(map[string]interface{})
|
var dataMap = make(map[string]any)
|
||||||
|
|
||||||
var saveDateFromProvider = func(fileSource, serverFileName, id string, body []byte) (err error) {
|
var saveDateFromProvider = func(fileSource, serverFileName, id string, body []byte) (err error) {
|
||||||
|
|
||||||
var data = make(map[string]interface{})
|
var data = make(map[string]any)
|
||||||
|
|
||||||
if value, ok := dataMap[id].(map[string]interface{}); ok {
|
if value, ok := dataMap[id].(map[string]any); ok {
|
||||||
data = value
|
data = value
|
||||||
} else {
|
} else {
|
||||||
data["id.provider"] = id
|
data["id.provider"] = id
|
||||||
@@ -65,7 +65,7 @@ func getProviderData(fileType, fileID string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "compatibility":
|
case "compatibility":
|
||||||
data[key] = make(map[string]interface{})
|
data[key] = make(map[string]any)
|
||||||
|
|
||||||
case "counter.download":
|
case "counter.download":
|
||||||
data[key] = 0.0
|
data[key] = 0.0
|
||||||
@@ -142,7 +142,7 @@ func getProviderData(fileType, fileID string) (err error) {
|
|||||||
|
|
||||||
for dataID, d := range dataMap {
|
for dataID, d := range dataMap {
|
||||||
|
|
||||||
var data = d.(map[string]interface{})
|
var data = d.(map[string]any)
|
||||||
var fileSource = data["file.source"].(string)
|
var fileSource = data["file.source"].(string)
|
||||||
newProvider = false
|
newProvider = false
|
||||||
|
|
||||||
@@ -220,8 +220,8 @@ func getProviderData(fileType, fileID string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fehler Counter um 1 erhöhen
|
// Fehler Counter um 1 erhöhen
|
||||||
var data = make(map[string]interface{})
|
var data = make(map[string]any)
|
||||||
if value, ok := dataMap[dataID].(map[string]interface{}); ok {
|
if value, ok := dataMap[dataID].(map[string]any); ok {
|
||||||
|
|
||||||
data = value
|
data = value
|
||||||
data["counter.error"] = data["counter.error"].(float64) + 1
|
data["counter.error"] = data["counter.error"].(float64) + 1
|
||||||
@@ -238,9 +238,9 @@ func getProviderData(fileType, fileID string) (err error) {
|
|||||||
// Berechnen der Fehlerquote
|
// Berechnen der Fehlerquote
|
||||||
if newProvider == false {
|
if newProvider == false {
|
||||||
|
|
||||||
if value, ok := dataMap[dataID].(map[string]interface{}); ok {
|
if value, ok := dataMap[dataID].(map[string]any); ok {
|
||||||
|
|
||||||
var data = make(map[string]interface{})
|
var data = make(map[string]any)
|
||||||
data = value
|
data = value
|
||||||
|
|
||||||
if data["counter.error"].(float64) == 0 {
|
if data["counter.error"].(float64) == 0 {
|
||||||
@@ -282,15 +282,23 @@ func downloadFileFromServer(providerURL string) (filename string, body []byte, e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Get(providerURL)
|
req, err := http.NewRequest(http.MethodGet, providerURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Header.Set("User-Agent", Settings.UserAgent)
|
req.Header.Set("User-Agent", getUserAgent())
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
err = fmt.Errorf(fmt.Sprintf("%d: %s "+http.StatusText(resp.StatusCode), resp.StatusCode, providerURL))
|
err = fmt.Errorf("%d: %s %s", resp.StatusCode, providerURL, http.StatusText(resp.StatusCode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
|
|
||||||
// Tuner
|
// Tuner
|
||||||
case 2105:
|
case 2105:
|
||||||
errMsg = fmt.Sprintf("The number of tuners has changed, you have to delete " + System.Name + " in Plex / Emby HDHR and set it up again.")
|
errMsg = "The number of tuners has changed, you have to delete " + System.Name + " in Plex / Emby HDHR and set it up again."
|
||||||
case 2106:
|
case 2106:
|
||||||
errMsg = fmt.Sprintf("This function is only available with XEPG as EPG source")
|
errMsg = fmt.Sprintf("This function is only available with XEPG as EPG source")
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,7 @@ type LineupStatus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lineup : HDHR Lineup /lineup.json
|
// Lineup : HDHR Lineup /lineup.json
|
||||||
type Lineup []interface {
|
type Lineup []any
|
||||||
//GuideName string `json:"GuideName"`
|
|
||||||
//GuideNumber string `json:"GuideNumber"`
|
|
||||||
//URL string `json:"URL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LineupStream : HDHR einzelner Stream im Lineup
|
// LineupStream : HDHR einzelner Stream im Lineup
|
||||||
type LineupStream struct {
|
type LineupStream struct {
|
||||||
|
|||||||
@@ -149,18 +149,18 @@ type DataStruct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Streams struct {
|
Streams struct {
|
||||||
Active []interface{}
|
Active []any
|
||||||
All []interface{}
|
All []any
|
||||||
Inactive []interface{}
|
Inactive []any
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLTV struct {
|
XMLTV struct {
|
||||||
Files []string
|
Files []string
|
||||||
Mapping map[string]interface{}
|
Mapping map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
XEPG struct {
|
XEPG struct {
|
||||||
Channels map[string]interface{}
|
Channels map[string]any
|
||||||
XEPGCount int64
|
XEPGCount int64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,6 +254,9 @@ type Notification struct {
|
|||||||
// SettingsStruct : Inhalt der settings.json
|
// SettingsStruct : Inhalt der settings.json
|
||||||
type SettingsStruct struct {
|
type SettingsStruct struct {
|
||||||
API bool `json:"api"`
|
API bool `json:"api"`
|
||||||
|
UsePlexAPI bool `json:"use_plexAPI"`
|
||||||
|
PlexURL string `json:"plex.url"`
|
||||||
|
PlexToken string `json:"plex.token"`
|
||||||
AuthenticationAPI bool `json:"authentication.api"`
|
AuthenticationAPI bool `json:"authentication.api"`
|
||||||
AuthenticationM3U bool `json:"authentication.m3u"`
|
AuthenticationM3U bool `json:"authentication.m3u"`
|
||||||
AuthenticationPMS bool `json:"authentication.pms"`
|
AuthenticationPMS bool `json:"authentication.pms"`
|
||||||
@@ -275,13 +278,13 @@ type SettingsStruct struct {
|
|||||||
FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)
|
FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)
|
||||||
|
|
||||||
Files struct {
|
Files struct {
|
||||||
HDHR map[string]interface{} `json:"hdhr"`
|
HDHR map[string]any `json:"hdhr"`
|
||||||
M3U map[string]interface{} `json:"m3u"`
|
M3U map[string]any `json:"m3u"`
|
||||||
XMLTV map[string]interface{} `json:"xmltv"`
|
XMLTV map[string]any `json:"xmltv"`
|
||||||
} `json:"files"`
|
} `json:"files"`
|
||||||
|
|
||||||
FilesUpdate bool `json:"files.update"`
|
FilesUpdate bool `json:"files.update"`
|
||||||
Filter map[int64]interface{} `json:"filter"`
|
Filter map[int64]any `json:"filter"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
LogEntriesRAM int `json:"log.entries.ram"`
|
LogEntriesRAM int `json:"log.entries.ram"`
|
||||||
@@ -297,8 +300,10 @@ type SettingsStruct struct {
|
|||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
UDPxy string `json:"udpxy"`
|
UDPxy string `json:"udpxy"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
XepgMissingEPGMode string `json:"xepg.missing.epg.mode"`
|
||||||
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
|
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
|
||||||
XteveAutoUpdate bool `json:"xteveAutoUpdate"`
|
XteveAutoUpdate bool `json:"xteveAutoUpdate"`
|
||||||
|
WizardCompleted bool `json:"wizard.completed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LanguageUI : Sprache für das WebUI
|
// LanguageUI : Sprache für das WebUI
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ type RequestStruct struct {
|
|||||||
|
|
||||||
// Benutzer
|
// Benutzer
|
||||||
DeleteUser bool `json:"deleteUser,omitempty"`
|
DeleteUser bool `json:"deleteUser,omitempty"`
|
||||||
UserData map[string]interface{} `json:"userData,omitempty"`
|
UserData map[string]any `json:"userData,omitempty"`
|
||||||
|
|
||||||
// Mapping
|
// Mapping
|
||||||
EpgMapping map[string]interface{} `json:"epgMapping,omitempty"`
|
EpgMapping map[string]any `json:"epgMapping,omitempty"`
|
||||||
|
|
||||||
// Restore
|
// Restore
|
||||||
Base64 string `json:"base64,omitempty"`
|
Base64 string `json:"base64,omitempty"`
|
||||||
@@ -18,6 +18,9 @@ type RequestStruct struct {
|
|||||||
// Neue Werte für die Einstellungen (settings.json)
|
// Neue Werte für die Einstellungen (settings.json)
|
||||||
Settings struct {
|
Settings struct {
|
||||||
API *bool `json:"api,omitempty"`
|
API *bool `json:"api,omitempty"`
|
||||||
|
UsePlexAPI *bool `json:"use_plexAPI,omitempty"`
|
||||||
|
PlexURL *string `json:"plex.url,omitempty"`
|
||||||
|
PlexToken *string `json:"plex.token,omitempty"`
|
||||||
AuthenticationAPI *bool `json:"authentication.api,omitempty"`
|
AuthenticationAPI *bool `json:"authentication.api,omitempty"`
|
||||||
AuthenticationM3U *bool `json:"authentication.m3u,omitempty"`
|
AuthenticationM3U *bool `json:"authentication.m3u,omitempty"`
|
||||||
AuthenticationPMS *bool `json:"authentication.pms,omitempty"`
|
AuthenticationPMS *bool `json:"authentication.pms,omitempty"`
|
||||||
@@ -40,24 +43,25 @@ type RequestStruct struct {
|
|||||||
UDPxy *string `json:"udpxy,omitempty"`
|
UDPxy *string `json:"udpxy,omitempty"`
|
||||||
Update *[]string `json:"update,omitempty"`
|
Update *[]string `json:"update,omitempty"`
|
||||||
UserAgent *string `json:"user.agent,omitempty"`
|
UserAgent *string `json:"user.agent,omitempty"`
|
||||||
|
XepgMissingEPGMode *string `json:"xepg.missing.epg.mode,omitempty"`
|
||||||
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
|
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
|
||||||
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
|
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
|
||||||
SchemeM3U *string `json:"scheme.m3u,omitempty"`
|
SchemeM3U *string `json:"scheme.m3u,omitempty"`
|
||||||
SchemeXML *string `json:"scheme.xml,omitempty"`
|
SchemeXML *string `json:"scheme.xml,omitempty"`
|
||||||
} `json:"settings,omitempty"`
|
} `json:"settings"`
|
||||||
|
|
||||||
// Upload Logo
|
// Upload Logo
|
||||||
Filename string `json:"filename,omitempty"`
|
Filename string `json:"filename,omitempty"`
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
Filter map[int64]interface{} `json:"filter,omitempty"`
|
Filter map[int64]any `json:"filter,omitempty"`
|
||||||
|
|
||||||
// Dateien (M3U, HDHR, XMLTV)
|
// Dateien (M3U, HDHR, XMLTV)
|
||||||
Files struct {
|
Files struct {
|
||||||
HDHR map[string]interface{} `json:"hdhr,omitempty"`
|
HDHR map[string]any `json:"hdhr,omitempty"`
|
||||||
M3U map[string]interface{} `json:"m3u,omitempty"`
|
M3U map[string]any `json:"m3u,omitempty"`
|
||||||
XMLTV map[string]interface{} `json:"xmltv,omitempty"`
|
XMLTV map[string]any `json:"xmltv,omitempty"`
|
||||||
} `json:"files,omitempty"`
|
} `json:"files"`
|
||||||
|
|
||||||
// Wizard
|
// Wizard
|
||||||
Wizard struct {
|
Wizard struct {
|
||||||
@@ -65,7 +69,7 @@ type RequestStruct struct {
|
|||||||
M3U *string `json:"m3u,omitempty"`
|
M3U *string `json:"m3u,omitempty"`
|
||||||
Tuner *int `json:"tuner,omitempty"`
|
Tuner *int `json:"tuner,omitempty"`
|
||||||
XMLTV *string `json:"xmltv,omitempty"`
|
XMLTV *string `json:"xmltv,omitempty"`
|
||||||
} `json:"wizard,omitempty"`
|
} `json:"wizard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResponseStruct : Antworten an den Client (WEB)
|
// ResponseStruct : Antworten an den Client (WEB)
|
||||||
@@ -84,7 +88,7 @@ type ResponseStruct struct {
|
|||||||
Warnings int `json:"warnings"`
|
Warnings int `json:"warnings"`
|
||||||
XEPGCount int64 `json:"xepg"`
|
XEPGCount int64 `json:"xepg"`
|
||||||
XML string `json:"xepg-url,required"`
|
XML string `json:"xepg-url,required"`
|
||||||
} `json:"clientInfo,omitempty"`
|
} `json:"clientInfo"`
|
||||||
|
|
||||||
Data struct {
|
Data struct {
|
||||||
Playlist struct {
|
Playlist struct {
|
||||||
@@ -113,9 +117,9 @@ type ResponseStruct struct {
|
|||||||
Settings SettingsStruct `json:"settings,required"`
|
Settings SettingsStruct `json:"settings,required"`
|
||||||
Status bool `json:"status,required"`
|
Status bool `json:"status,required"`
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
Users map[string]interface{} `json:"users,omitempty"`
|
Users map[string]any `json:"users,omitempty"`
|
||||||
Wizard int `json:"wizard,omitempty"`
|
Wizard int `json:"wizard,omitempty"`
|
||||||
XEPG map[string]interface{} `json:"xepg,required"`
|
XEPG map[string]any `json:"xepg,required"`
|
||||||
|
|
||||||
Notification map[string]Notification `json:"notification,omitempty"`
|
Notification map[string]Notification `json:"notification,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func createSystemFiles() (err error) {
|
|||||||
err = checkFile(filename)
|
err = checkFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Datei existiert nicht, wird jetzt erstellt
|
// Datei existiert nicht, wird jetzt erstellt
|
||||||
err = saveMapToJSONFile(filename, make(map[string]interface{}))
|
err = saveMapToJSONFile(filename, make(map[string]any))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,15 +97,28 @@ func loadSettings() (settings SettingsStruct, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deafult Werte setzten
|
var freshInstall = len(settingsMap) == 0
|
||||||
var defaults = make(map[string]interface{})
|
|
||||||
var dataMap = make(map[string]interface{})
|
|
||||||
|
|
||||||
dataMap["xmltv"] = make(map[string]interface{})
|
// Deafult Werte setzten
|
||||||
dataMap["m3u"] = make(map[string]interface{})
|
var defaults = make(map[string]any)
|
||||||
dataMap["hdhr"] = make(map[string]interface{})
|
var dataMap = make(map[string]any)
|
||||||
|
|
||||||
|
dataMap["xmltv"] = make(map[string]any)
|
||||||
|
dataMap["m3u"] = make(map[string]any)
|
||||||
|
dataMap["hdhr"] = make(map[string]any)
|
||||||
|
|
||||||
|
defaultFFmpegPath := ""
|
||||||
|
if len(os.Getenv("XTEVE_CONFIG")) > 0 {
|
||||||
|
containerFFmpegPath := "/usr/local/bin/ffmpeg"
|
||||||
|
if checkFile(containerFFmpegPath) == nil {
|
||||||
|
defaultFFmpegPath = containerFFmpegPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaults["api"] = false
|
defaults["api"] = false
|
||||||
|
defaults["use_plexAPI"] = false
|
||||||
|
defaults["plex.url"] = ""
|
||||||
|
defaults["plex.token"] = ""
|
||||||
defaults["authentication.api"] = false
|
defaults["authentication.api"] = false
|
||||||
defaults["authentication.m3u"] = false
|
defaults["authentication.m3u"] = false
|
||||||
defaults["authentication.pms"] = false
|
defaults["authentication.pms"] = false
|
||||||
@@ -118,27 +131,34 @@ func loadSettings() (settings SettingsStruct, err error) {
|
|||||||
defaults["buffer.timeout"] = 500
|
defaults["buffer.timeout"] = 500
|
||||||
defaults["cache.images"] = false
|
defaults["cache.images"] = false
|
||||||
defaults["epgSource"] = "PMS"
|
defaults["epgSource"] = "PMS"
|
||||||
|
defaults["ffmpeg.path"] = defaultFFmpegPath
|
||||||
defaults["ffmpeg.options"] = System.FFmpeg.DefaultOptions
|
defaults["ffmpeg.options"] = System.FFmpeg.DefaultOptions
|
||||||
defaults["vlc.options"] = System.VLC.DefaultOptions
|
defaults["vlc.options"] = System.VLC.DefaultOptions
|
||||||
defaults["files"] = dataMap
|
defaults["files"] = dataMap
|
||||||
defaults["files.update"] = true
|
defaults["files.update"] = true
|
||||||
defaults["filter"] = make(map[string]interface{})
|
defaults["filter"] = make(map[string]any)
|
||||||
defaults["git.branch"] = System.Branch
|
defaults["git.branch"] = System.Branch
|
||||||
defaults["language"] = "en"
|
defaults["language"] = "en"
|
||||||
defaults["log.entries.ram"] = 500
|
defaults["log.entries.ram"] = 500
|
||||||
defaults["mapping.first.channel"] = 1000
|
defaults["mapping.first.channel"] = 1000
|
||||||
|
defaults["xepg.missing.epg.mode"] = "strict"
|
||||||
defaults["xepg.replace.missing.images"] = true
|
defaults["xepg.replace.missing.images"] = true
|
||||||
defaults["m3u8.adaptive.bandwidth.mbps"] = 10
|
defaults["m3u8.adaptive.bandwidth.mbps"] = 10
|
||||||
defaults["port"] = "34400"
|
defaults["port"] = "34400"
|
||||||
defaults["ssdp"] = true
|
defaults["ssdp"] = true
|
||||||
defaults["tuner"] = 1
|
defaults["tuner"] = 1
|
||||||
defaults["update"] = []string{"0000"}
|
defaults["update"] = []string{"0000"}
|
||||||
defaults["user.agent"] = System.Name
|
defaults["user.agent"] = defaultUserAgent
|
||||||
defaults["uuid"] = createUUID()
|
defaults["uuid"] = createUUID()
|
||||||
defaults["udpxy"] = ""
|
defaults["udpxy"] = ""
|
||||||
defaults["version"] = System.DBVersion
|
defaults["version"] = System.DBVersion
|
||||||
defaults["xteveAutoUpdate"] = true
|
defaults["xteveAutoUpdate"] = true
|
||||||
defaults["temp.path"] = System.Folder.Temp
|
defaults["wizard.completed"] = !freshInstall
|
||||||
|
var defaultTempPath = System.Folder.Temp
|
||||||
|
if len(os.Getenv("XTEVE_CONFIG")) > 0 {
|
||||||
|
defaultTempPath = System.Folder.Config + "tmp" + string(os.PathSeparator)
|
||||||
|
}
|
||||||
|
defaults["temp.path"] = defaultTempPath
|
||||||
|
|
||||||
// Default Werte setzen
|
// Default Werte setzen
|
||||||
for key, value := range defaults {
|
for key, value := range defaults {
|
||||||
@@ -163,8 +183,13 @@ func loadSettings() (settings SettingsStruct, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(settings.FFmpegPath) == 0 {
|
if len(settings.FFmpegPath) == 0 {
|
||||||
|
containerFFmpegPath := "/usr/local/bin/ffmpeg"
|
||||||
|
if len(os.Getenv("XTEVE_CONFIG")) > 0 && checkFile(containerFFmpegPath) == nil {
|
||||||
|
settings.FFmpegPath = containerFFmpegPath
|
||||||
|
} else {
|
||||||
settings.FFmpegPath = searchFileInOS("ffmpeg")
|
settings.FFmpegPath = searchFileInOS("ffmpeg")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(settings.VLCPath) == 0 {
|
if len(settings.VLCPath) == 0 {
|
||||||
settings.VLCPath = searchFileInOS("cvlc")
|
settings.VLCPath = searchFileInOS("cvlc")
|
||||||
@@ -201,8 +226,36 @@ func saveSettings(settings SettingsStruct) (err error) {
|
|||||||
settings.BufferTimeout = 0
|
settings.BufferTimeout = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var userAgent = strings.TrimSpace(settings.UserAgent)
|
||||||
|
if len(userAgent) == 0 || userAgent == System.Name {
|
||||||
|
settings.UserAgent = defaultUserAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.XepgMissingEPGMode = strings.ToLower(strings.TrimSpace(settings.XepgMissingEPGMode))
|
||||||
|
if settings.XepgMissingEPGMode != "relaxed" {
|
||||||
|
settings.XepgMissingEPGMode = "strict"
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.TempPath = strings.TrimRight(settings.TempPath, string(os.PathSeparator)) + string(os.PathSeparator)
|
||||||
System.Folder.Temp = settings.TempPath + settings.UUID + string(os.PathSeparator)
|
System.Folder.Temp = settings.TempPath + settings.UUID + string(os.PathSeparator)
|
||||||
|
|
||||||
|
err = checkFolder(System.Folder.Temp)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
fallbackTempPath := System.Folder.Config + "tmp" + string(os.PathSeparator)
|
||||||
|
fallbackTempFolder := fallbackTempPath + settings.UUID + string(os.PathSeparator)
|
||||||
|
fallbackErr := checkFolder(fallbackTempFolder)
|
||||||
|
|
||||||
|
if fallbackErr != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.TempPath = fallbackTempPath
|
||||||
|
System.Folder.Temp = fallbackTempFolder
|
||||||
|
showInfo(fmt.Sprintf("Temporary Folder:Fallback to %s", getPlatformPath(System.Folder.Temp)))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
err = writeByteToFile(System.File.Settings, []byte(mapToJSON(settings)))
|
err = writeByteToFile(System.File.Settings, []byte(mapToJSON(settings)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ func searchFileInOS(file string) (path string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
func removeChildItems(dir string) error {
|
func removeChildItems(dir string) error {
|
||||||
|
|
||||||
files, err := filepath.Glob(filepath.Join(dir, "*"))
|
files, err := filepath.Glob(filepath.Join(dir, "*"))
|
||||||
@@ -180,7 +179,7 @@ func removeChildItems(dir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
func mapToJSON(tmpMap interface{}) string {
|
func mapToJSON(tmpMap any) string {
|
||||||
|
|
||||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -190,30 +189,30 @@ func mapToJSON(tmpMap interface{}) string {
|
|||||||
return string(jsonString)
|
return string(jsonString)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonToMap(content string) map[string]interface{} {
|
func jsonToMap(content string) map[string]any {
|
||||||
|
|
||||||
var tmpMap = make(map[string]interface{})
|
var tmpMap = make(map[string]any)
|
||||||
json.Unmarshal([]byte(content), &tmpMap)
|
json.Unmarshal([]byte(content), &tmpMap)
|
||||||
|
|
||||||
return (tmpMap)
|
return (tmpMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonToMapInt64(content string) map[int64]interface{} {
|
func jsonToMapInt64(content string) map[int64]any {
|
||||||
|
|
||||||
var tmpMap = make(map[int64]interface{})
|
var tmpMap = make(map[int64]any)
|
||||||
json.Unmarshal([]byte(content), &tmpMap)
|
json.Unmarshal([]byte(content), &tmpMap)
|
||||||
|
|
||||||
return (tmpMap)
|
return (tmpMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonToInterface(content string) (tmpMap interface{}, err error) {
|
func jsonToInterface(content string) (tmpMap any, err error) {
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(content), &tmpMap)
|
err = json.Unmarshal([]byte(content), &tmpMap)
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveMapToJSONFile(file string, tmpMap interface{}) error {
|
func saveMapToJSONFile(file string, tmpMap any) error {
|
||||||
|
|
||||||
var filename = getPlatformFile(file)
|
var filename = getPlatformFile(file)
|
||||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
@@ -230,7 +229,7 @@ func saveMapToJSONFile(file string, tmpMap interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadJSONFileToMap(file string) (tmpMap map[string]interface{}, err error) {
|
func loadJSONFileToMap(file string) (tmpMap map[string]any, err error) {
|
||||||
|
|
||||||
f, err := os.Open(getPlatformFile(file))
|
f, err := os.Open(getPlatformFile(file))
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
@@ -360,7 +359,7 @@ func randomString(n int) string {
|
|||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTemplate(content string, tmpMap map[string]interface{}) (result string) {
|
func parseTemplate(content string, tmpMap map[string]any) (result string) {
|
||||||
|
|
||||||
t := template.Must(template.New("template").Parse(content))
|
t := template.Must(template.New("template").Parse(content))
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ func BinaryUpdate() (err error) {
|
|||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
if resp.StatusCode == 404 {
|
||||||
err = fmt.Errorf(fmt.Sprintf("Update Server: %s (%s)", http.StatusText(resp.StatusCode), gitInfo))
|
err = fmt.Errorf("Update Server: %s (%s)", http.StatusText(resp.StatusCode), gitInfo)
|
||||||
ShowError(err, 6003)
|
ShowError(err, 6003)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), gitInfo))
|
err = fmt.Errorf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), gitInfo)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ func BinaryUpdate() (err error) {
|
|||||||
err = up2date.GetVersion()
|
err = up2date.GetVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
debug = fmt.Sprintf(err.Error())
|
debug = err.Error()
|
||||||
showDebug(debug, 1)
|
showDebug(debug, 1)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -95,7 +95,7 @@ func BinaryUpdate() (err error) {
|
|||||||
|
|
||||||
if len(updater.Response.Reason) > 0 {
|
if len(updater.Response.Reason) > 0 {
|
||||||
|
|
||||||
err = fmt.Errorf(fmt.Sprintf("Update Server: %s", updater.Response.Reason))
|
err = fmt.Errorf("Update Server: %s", updater.Response.Reason)
|
||||||
ShowError(err, 6002)
|
ShowError(err, 6002)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -192,7 +192,7 @@ checkVersion:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Neuer Filter (WebUI). Alte Filtereinstellungen werden konvertiert
|
// Neuer Filter (WebUI). Alte Filtereinstellungen werden konvertiert
|
||||||
if oldFilter, ok := settingsMap["filter"].([]interface{}); ok {
|
if oldFilter, ok := settingsMap["filter"].([]any); ok {
|
||||||
var newFilterMap = convertToNewFilter(oldFilter)
|
var newFilterMap = convertToNewFilter(oldFilter)
|
||||||
settingsMap["filter"] = newFilterMap
|
settingsMap["filter"] = newFilterMap
|
||||||
|
|
||||||
@@ -252,11 +252,11 @@ checkVersion:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertToNewFilter(oldFilter []interface{}) (newFilterMap map[int]interface{}) {
|
func convertToNewFilter(oldFilter []any) (newFilterMap map[int]any) {
|
||||||
|
|
||||||
newFilterMap = make(map[int]interface{})
|
newFilterMap = make(map[int]any)
|
||||||
|
|
||||||
switch reflect.TypeOf(oldFilter).Kind() {
|
switch reflect.TypeFor[[]any]().Kind() {
|
||||||
|
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
s := reflect.ValueOf(oldFilter)
|
s := reflect.ValueOf(oldFilter)
|
||||||
@@ -285,7 +285,7 @@ func setValueForUUID() (err error) {
|
|||||||
|
|
||||||
for _, c := range xepg {
|
for _, c := range xepg {
|
||||||
|
|
||||||
var xepgChannel = c.(map[string]interface{})
|
var xepgChannel = c.(map[string]any)
|
||||||
|
|
||||||
if uuidKey, ok := xepgChannel["_uuid.key"].(string); ok {
|
if uuidKey, ok := xepgChannel["_uuid.key"].(string); ok {
|
||||||
|
|
||||||
|
|||||||
15
src/user_agent.go
Normal file
15
src/user_agent.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const defaultUserAgent = "otg/1.5.1 (AppleTv Apple TV 4; tvOS16.0; appletv.client) libcurl/7.58.0 OpenSSL/1.0.2o zlib/1.2.11 clib/1.8.56"
|
||||||
|
|
||||||
|
func getUserAgent() string {
|
||||||
|
|
||||||
|
var userAgent = strings.TrimSpace(Settings.UserAgent)
|
||||||
|
if len(userAgent) == 0 {
|
||||||
|
return defaultUserAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
return userAgent
|
||||||
|
}
|
||||||
76
src/webUI.go
76
src/webUI.go
File diff suppressed because one or more lines are too long
@@ -15,6 +15,14 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var wsUpgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 4096,
|
||||||
|
WriteBufferSize: 4096,
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// StartWebserver : Startet den Webserver
|
// StartWebserver : Startet den Webserver
|
||||||
func StartWebserver() (err error) {
|
func StartWebserver() (err error) {
|
||||||
|
|
||||||
@@ -336,7 +344,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
|
conn, err := wsUpgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 0)
|
ShowError(err, 0)
|
||||||
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
||||||
@@ -558,7 +566,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
default:
|
default:
|
||||||
fmt.Println("+ + + + + + + + + + +", request.Cmd)
|
fmt.Println("+ + + + + + + + + + +", request.Cmd)
|
||||||
|
|
||||||
var requestMap = make(map[string]interface{}) // Debug
|
var requestMap = make(map[string]any) // Debug
|
||||||
_ = requestMap
|
_ = requestMap
|
||||||
if System.Dev == true {
|
if System.Dev == true {
|
||||||
fmt.Println(mapToJSON(requestMap))
|
fmt.Println(mapToJSON(requestMap))
|
||||||
@@ -591,7 +599,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Web : Web Server /web/
|
// Web : Web Server /web/
|
||||||
func Web(w http.ResponseWriter, r *http.Request) {
|
func Web(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
var lang = make(map[string]interface{})
|
var lang = make(map[string]any)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
var requestFile = strings.Replace(r.URL.Path, "/web", "html", -1)
|
var requestFile = strings.Replace(r.URL.Path, "/web", "html", -1)
|
||||||
@@ -629,7 +637,7 @@ func Web(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if System.ScanInProgress == 0 {
|
if System.ScanInProgress == 0 {
|
||||||
|
|
||||||
if len(Settings.Files.M3U) == 0 && len(Settings.Files.HDHR) == 0 {
|
if Settings.WizardCompleted == false && len(Settings.Files.M3U) == 0 && len(Settings.Files.HDHR) == 0 {
|
||||||
System.ConfigurationWizard = true
|
System.ConfigurationWizard = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,7 +1036,7 @@ func setDefaultResponseData(response ResponseStruct, data bool) (defaults Respon
|
|||||||
|
|
||||||
defaults.ClientInfo.XEPGCount = Data.XEPG.XEPGCount
|
defaults.ClientInfo.XEPGCount = Data.XEPG.XEPGCount
|
||||||
|
|
||||||
var XEPG = make(map[string]interface{})
|
var XEPG = make(map[string]any)
|
||||||
|
|
||||||
if len(Data.Streams.Active) > 0 {
|
if len(Data.Streams.Active) > 0 {
|
||||||
|
|
||||||
@@ -1037,8 +1045,8 @@ func setDefaultResponseData(response ResponseStruct, data bool) (defaults Respon
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
XEPG["epgMapping"] = make(map[string]interface{})
|
XEPG["epgMapping"] = make(map[string]any)
|
||||||
XEPG["xmltvMap"] = make(map[string]interface{})
|
XEPG["xmltvMap"] = make(map[string]any)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
253
src/xepg.go
253
src/xepg.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -68,6 +69,7 @@ func buildXEPG(background bool) {
|
|||||||
cleanupXEPG()
|
cleanupXEPG()
|
||||||
createXMLTVFile()
|
createXMLTVFile()
|
||||||
createM3UFile()
|
createM3UFile()
|
||||||
|
queuePlexGuideRefresh("xepg rebuild")
|
||||||
|
|
||||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ func buildXEPG(background bool) {
|
|||||||
|
|
||||||
createXMLTVFile()
|
createXMLTVFile()
|
||||||
createM3UFile()
|
createM3UFile()
|
||||||
|
queuePlexGuideRefresh("xepg image cache refresh")
|
||||||
|
|
||||||
System.ImageCachingInProgress = 0
|
System.ImageCachingInProgress = 0
|
||||||
|
|
||||||
@@ -113,6 +116,7 @@ func buildXEPG(background bool) {
|
|||||||
|
|
||||||
createXMLTVFile()
|
createXMLTVFile()
|
||||||
createM3UFile()
|
createM3UFile()
|
||||||
|
queuePlexGuideRefresh("xepg rebuild")
|
||||||
|
|
||||||
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
|
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
|
||||||
|
|
||||||
@@ -127,6 +131,7 @@ func buildXEPG(background bool) {
|
|||||||
|
|
||||||
createXMLTVFile()
|
createXMLTVFile()
|
||||||
createM3UFile()
|
createM3UFile()
|
||||||
|
queuePlexGuideRefresh("xepg image cache refresh")
|
||||||
|
|
||||||
System.ImageCachingInProgress = 0
|
System.ImageCachingInProgress = 0
|
||||||
|
|
||||||
@@ -179,6 +184,7 @@ func updateXEPG(background bool) {
|
|||||||
|
|
||||||
createXMLTVFile()
|
createXMLTVFile()
|
||||||
createM3UFile()
|
createM3UFile()
|
||||||
|
queuePlexGuideRefresh("xepg update")
|
||||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
|
||||||
System.ScanInProgress = 0
|
System.ScanInProgress = 0
|
||||||
@@ -207,9 +213,9 @@ func updateXEPG(background bool) {
|
|||||||
func createXEPGMapping() {
|
func createXEPGMapping() {
|
||||||
|
|
||||||
Data.XMLTV.Files = getLocalProviderFiles("xmltv")
|
Data.XMLTV.Files = getLocalProviderFiles("xmltv")
|
||||||
Data.XMLTV.Mapping = make(map[string]interface{})
|
Data.XMLTV.Mapping = make(map[string]any)
|
||||||
|
|
||||||
var tmpMap = make(map[string]interface{})
|
var tmpMap = make(map[string]any)
|
||||||
|
|
||||||
var friendlyDisplayName = func(channel Channel) (displayName string) {
|
var friendlyDisplayName = func(channel Channel) (displayName string) {
|
||||||
var dn = channel.DisplayName
|
var dn = channel.DisplayName
|
||||||
@@ -250,10 +256,10 @@ func createXEPGMapping() {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
||||||
// Daten aus der XML Datei in eine temporäre Map schreiben
|
// Daten aus der XML Datei in eine temporäre Map schreiben
|
||||||
var xmltvMap = make(map[string]interface{})
|
var xmltvMap = make(map[string]any)
|
||||||
|
|
||||||
for _, c := range xmltv.Channel {
|
for _, c := range xmltv.Channel {
|
||||||
var channel = make(map[string]interface{})
|
var channel = make(map[string]any)
|
||||||
|
|
||||||
channel["id"] = c.ID
|
channel["id"] = c.ID
|
||||||
channel["display-name"] = friendlyDisplayName(*c)
|
channel["display-name"] = friendlyDisplayName(*c)
|
||||||
@@ -271,7 +277,7 @@ func createXEPGMapping() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Data.XMLTV.Mapping = tmpMap
|
Data.XMLTV.Mapping = tmpMap
|
||||||
tmpMap = make(map[string]interface{})
|
tmpMap = make(map[string]any)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -282,7 +288,7 @@ func createXEPGMapping() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auswahl für den Dummy erstellen
|
// Auswahl für den Dummy erstellen
|
||||||
var dummy = make(map[string]interface{})
|
var dummy = make(map[string]any)
|
||||||
var times = []string{"30", "60", "90", "120", "180", "240", "360"}
|
var times = []string{"30", "60", "90", "120", "180", "240", "360"}
|
||||||
|
|
||||||
for _, i := range times {
|
for _, i := range times {
|
||||||
@@ -306,7 +312,7 @@ func createXEPGDatabase() (err error) {
|
|||||||
|
|
||||||
var allChannelNumbers = make([]float64, 0, System.UnfilteredChannelLimit)
|
var allChannelNumbers = make([]float64, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Cache.Streams.Active = make([]string, 0, System.UnfilteredChannelLimit)
|
Data.Cache.Streams.Active = make([]string, 0, System.UnfilteredChannelLimit)
|
||||||
Data.XEPG.Channels = make(map[string]interface{}, System.UnfilteredChannelLimit)
|
Data.XEPG.Channels = make(map[string]any, System.UnfilteredChannelLimit)
|
||||||
|
|
||||||
Data.XEPG.Channels, err = loadJSONFileToMap(System.File.XEPG)
|
Data.XEPG.Channels, err = loadJSONFileToMap(System.File.XEPG)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -521,10 +527,132 @@ func createXEPGDatabase() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeXEPGMatchValue(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if len(value) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Map(func(r rune) rune {
|
||||||
|
switch {
|
||||||
|
case unicode.IsLetter(r):
|
||||||
|
return unicode.ToLower(r)
|
||||||
|
case unicode.IsDigit(r):
|
||||||
|
return r
|
||||||
|
default:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendXEPGIssueSample(samples map[string]struct{}, value string) {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if len(value) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
samples[value] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func xepgIssueSampleText(samples map[string]struct{}, max int) string {
|
||||||
|
if len(samples) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
list := make([]string, 0, len(samples))
|
||||||
|
for name := range samples {
|
||||||
|
list = append(list, name)
|
||||||
|
}
|
||||||
|
sort.Strings(list)
|
||||||
|
|
||||||
|
if len(list) > max {
|
||||||
|
return strings.Join(list[:max], ", ") + ", ..."
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(list, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func findXEPGReplacementChannel(xmltvChannels map[string]any, xepgChannel XEPGChannelStruct) (channelID string, channel map[string]any, ok bool) {
|
||||||
|
var candidateValues = map[string]struct{}{}
|
||||||
|
|
||||||
|
addCandidate := func(value string) {
|
||||||
|
normalized := normalizeXEPGMatchValue(value)
|
||||||
|
if len(normalized) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
candidateValues[normalized] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCandidate(xepgChannel.XName)
|
||||||
|
addCandidate(xepgChannel.Name)
|
||||||
|
addCandidate(xepgChannel.TvgName)
|
||||||
|
addCandidate(xepgChannel.TvgID)
|
||||||
|
|
||||||
|
if len(candidateValues) == 0 {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := candidateValues[normalizeXEPGMatchValue(xepgChannel.TvgID)]; exists {
|
||||||
|
if direct, found := xmltvChannels[xepgChannel.TvgID].(map[string]any); found {
|
||||||
|
return xepgChannel.TvgID, direct, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches []struct {
|
||||||
|
id string
|
||||||
|
channel map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, data := range xmltvChannels {
|
||||||
|
xmltvChannel, castOK := data.(map[string]any)
|
||||||
|
if castOK == false {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, match := candidateValues[normalizeXEPGMatchValue(id)]; match {
|
||||||
|
matches = append(matches, struct {
|
||||||
|
id string
|
||||||
|
channel map[string]any
|
||||||
|
}{id: id, channel: xmltvChannel})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName, hasDisplayName := xmltvChannel["display-name"].(string)
|
||||||
|
if hasDisplayName == false {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, match := candidateValues[normalizeXEPGMatchValue(displayName)]; match {
|
||||||
|
matches = append(matches, struct {
|
||||||
|
id string
|
||||||
|
channel map[string]any
|
||||||
|
}{id: id, channel: xmltvChannel})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) != 1 {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches[0].id, matches[0].channel, true
|
||||||
|
}
|
||||||
|
|
||||||
// Kanäle automatisch zuordnen und das Mapping überprüfen
|
// Kanäle automatisch zuordnen und das Mapping überprüfen
|
||||||
func mapping() (err error) {
|
func mapping() (err error) {
|
||||||
showInfo("XEPG:" + "Map channels")
|
showInfo("XEPG:" + "Map channels")
|
||||||
|
|
||||||
|
strictMissingEPGMode := Settings.XepgMissingEPGMode != "relaxed"
|
||||||
|
|
||||||
|
missingEPGCount := 0
|
||||||
|
missingXMLTVCount := 0
|
||||||
|
autoRemapCount := 0
|
||||||
|
relaxedKeepCount := 0
|
||||||
|
relaxedDummyCount := 0
|
||||||
|
missingEPGSamples := map[string]struct{}{}
|
||||||
|
missingXMLTVSamples := map[string]struct{}{}
|
||||||
|
autoRemapSamples := map[string]struct{}{}
|
||||||
|
relaxedKeepSamples := map[string]struct{}{}
|
||||||
|
relaxedDummySamples := map[string]struct{}{}
|
||||||
|
|
||||||
for xepg, dxc := range Data.XEPG.Channels {
|
for xepg, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
var xepgChannel XEPGChannelStruct
|
var xepgChannel XEPGChannelStruct
|
||||||
@@ -537,7 +665,7 @@ func mapping() (err error) {
|
|||||||
if xepgChannel.XActive == false {
|
if xepgChannel.XActive == false {
|
||||||
|
|
||||||
// Werte kann "-" sein, deswegen len < 1
|
// Werte kann "-" sein, deswegen len < 1
|
||||||
if len(xepgChannel.XmltvFile) < 1 && len(xepgChannel.XmltvFile) < 1 {
|
if len(xepgChannel.XmltvFile) < 1 && len(xepgChannel.XMapping) < 1 {
|
||||||
|
|
||||||
var tvgID = xepgChannel.TvgID
|
var tvgID = xepgChannel.TvgID
|
||||||
|
|
||||||
@@ -549,16 +677,16 @@ func mapping() (err error) {
|
|||||||
|
|
||||||
for file, xmltvChannels := range Data.XMLTV.Mapping {
|
for file, xmltvChannels := range Data.XMLTV.Mapping {
|
||||||
|
|
||||||
if channel, ok := xmltvChannels.(map[string]interface{})[tvgID]; ok {
|
if channel, ok := xmltvChannels.(map[string]any)[tvgID]; ok {
|
||||||
|
|
||||||
if channelID, ok := channel.(map[string]interface{})["id"].(string); ok {
|
if channelID, ok := channel.(map[string]any)["id"].(string); ok {
|
||||||
|
|
||||||
xepgChannel.XmltvFile = file
|
xepgChannel.XmltvFile = file
|
||||||
xepgChannel.XMapping = channelID
|
xepgChannel.XMapping = channelID
|
||||||
xepgChannel.XActive = true
|
xepgChannel.XActive = true
|
||||||
|
|
||||||
// Falls in der XMLTV Datei ein Logo existiert, wird dieses verwendet. Falls nicht, dann das Logo aus der M3U Datei
|
// Falls in der XMLTV Datei ein Logo existiert, wird dieses verwendet. Falls nicht, dann das Logo aus der M3U Datei
|
||||||
if icon, ok := channel.(map[string]interface{})["icon"].(string); ok {
|
if icon, ok := channel.(map[string]any)["icon"].(string); ok {
|
||||||
if len(icon) > 0 {
|
if len(icon) > 0 {
|
||||||
xepgChannel.TvgLogo = icon
|
xepgChannel.TvgLogo = icon
|
||||||
}
|
}
|
||||||
@@ -585,9 +713,9 @@ func mapping() (err error) {
|
|||||||
|
|
||||||
if file != "xTeVe Dummy" {
|
if file != "xTeVe Dummy" {
|
||||||
|
|
||||||
if value, ok := Data.XMLTV.Mapping[file].(map[string]interface{}); ok {
|
if value, ok := Data.XMLTV.Mapping[file].(map[string]any); ok {
|
||||||
|
|
||||||
if channel, ok := value[mapping].(map[string]interface{}); ok {
|
if channel, ok := value[mapping].(map[string]any); ok {
|
||||||
|
|
||||||
// Kanallogo aktualisieren
|
// Kanallogo aktualisieren
|
||||||
if logo, ok := channel["icon"].(string); ok {
|
if logo, ok := channel["icon"].(string); ok {
|
||||||
@@ -599,20 +727,68 @@ func mapping() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
if channelID, replacementChannel, remapOK := findXEPGReplacementChannel(value, xepgChannel); remapOK {
|
||||||
|
xepgChannel.XMapping = channelID
|
||||||
|
|
||||||
ShowError(fmt.Errorf(fmt.Sprintf("Missing EPG data: %s", xepgChannel.Name)), 0)
|
if logo, ok := replacementChannel["icon"].(string); ok {
|
||||||
showWarning(2302)
|
if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 {
|
||||||
|
xepgChannel.TvgLogo = logo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
autoRemapCount++
|
||||||
|
name := strings.TrimSpace(xepgChannel.Name)
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = strings.TrimSpace(xepgChannel.XName)
|
||||||
|
}
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = xepg
|
||||||
|
}
|
||||||
|
appendXEPGIssueSample(autoRemapSamples, name)
|
||||||
|
} else {
|
||||||
|
name := strings.TrimSpace(xepgChannel.Name)
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = strings.TrimSpace(xepgChannel.XName)
|
||||||
|
}
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = xepg
|
||||||
|
}
|
||||||
|
|
||||||
|
if strictMissingEPGMode == true {
|
||||||
|
missingEPGCount++
|
||||||
|
appendXEPGIssueSample(missingEPGSamples, name)
|
||||||
xepgChannel.XActive = false
|
xepgChannel.XActive = false
|
||||||
|
} else {
|
||||||
|
relaxedKeepCount++
|
||||||
|
appendXEPGIssueSample(relaxedKeepSamples, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file)))
|
var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file)))
|
||||||
|
providerName := getProviderParameter(fileID, "xmltv", "name")
|
||||||
|
if len(strings.TrimSpace(providerName)) == 0 {
|
||||||
|
providerName = file
|
||||||
|
}
|
||||||
|
|
||||||
ShowError(fmt.Errorf("Missing XMLTV file: %s", getProviderParameter(fileID, "xmltv", "name")), 0)
|
if strictMissingEPGMode == true {
|
||||||
showWarning(2301)
|
missingXMLTVCount++
|
||||||
|
appendXEPGIssueSample(missingXMLTVSamples, providerName)
|
||||||
xepgChannel.XActive = false
|
xepgChannel.XActive = false
|
||||||
|
} else {
|
||||||
|
relaxedDummyCount++
|
||||||
|
name := strings.TrimSpace(xepgChannel.Name)
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = strings.TrimSpace(xepgChannel.XName)
|
||||||
|
}
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = xepg
|
||||||
|
}
|
||||||
|
appendXEPGIssueSample(relaxedDummySamples, name)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,6 +810,28 @@ func mapping() (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if autoRemapCount > 0 {
|
||||||
|
showInfo(fmt.Sprintf("XEPG:%d channel mappings were auto-remapped (examples: %s)", autoRemapCount, xepgIssueSampleText(autoRemapSamples, 8)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if missingEPGCount > 0 {
|
||||||
|
showWarning(2302)
|
||||||
|
showInfo(fmt.Sprintf("XEPG:%d channels have missing EPG mappings and were deactivated (examples: %s)", missingEPGCount, xepgIssueSampleText(missingEPGSamples, 8)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if missingXMLTVCount > 0 {
|
||||||
|
showWarning(2301)
|
||||||
|
showInfo(fmt.Sprintf("XEPG:%d channels reference missing XMLTV files and were deactivated (sources: %s)", missingXMLTVCount, xepgIssueSampleText(missingXMLTVSamples, 5)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if relaxedKeepCount > 0 {
|
||||||
|
showInfo(fmt.Sprintf("XEPG:%d channels kept active in relaxed mode despite missing EPG mappings (examples: %s)", relaxedKeepCount, xepgIssueSampleText(relaxedKeepSamples, 8)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if relaxedDummyCount > 0 {
|
||||||
|
showInfo(fmt.Sprintf("XEPG:%d channels will use xTeVe Dummy guide in relaxed mode because XMLTV sources were unavailable (examples: %s)", relaxedDummyCount, xepgIssueSampleText(relaxedDummySamples, 8)))
|
||||||
|
}
|
||||||
|
|
||||||
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -667,7 +865,7 @@ func createXMLTVFile() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(Data.XMLTV.Files) == 0 && len(Data.Streams.Active) == 0 {
|
if len(Data.XMLTV.Files) == 0 && len(Data.Streams.Active) == 0 {
|
||||||
Data.XEPG.Channels = make(map[string]interface{})
|
Data.XEPG.Channels = make(map[string]any)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,6 +933,13 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
|||||||
|
|
||||||
var xmltvFile = System.Folder.Data + xepgChannel.XmltvFile
|
var xmltvFile = System.Folder.Data + xepgChannel.XmltvFile
|
||||||
var channelID = xepgChannel.XMapping
|
var channelID = xepgChannel.XMapping
|
||||||
|
relaxedMissingEPGMode := Settings.XepgMissingEPGMode == "relaxed"
|
||||||
|
fallbackToDummy := func() {
|
||||||
|
dummyChannel := xepgChannel
|
||||||
|
dummyChannel.XmltvFile = "xTeVe Dummy"
|
||||||
|
dummyChannel.XMapping = "240_Minutes"
|
||||||
|
xepgXML = createDummyProgram(dummyChannel)
|
||||||
|
}
|
||||||
|
|
||||||
var xmltv XMLTV
|
var xmltv XMLTV
|
||||||
|
|
||||||
@@ -744,6 +949,10 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
|||||||
|
|
||||||
err = getLocalXMLTV(xmltvFile, &xmltv)
|
err = getLocalXMLTV(xmltvFile, &xmltv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if relaxedMissingEPGMode == true {
|
||||||
|
fallbackToDummy()
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,6 +1026,10 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(xepgXML.Program) == 0 && relaxedMissingEPGMode == true && xmltvFile != System.Folder.Data+"xTeVe Dummy" {
|
||||||
|
fallbackToDummy()
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,7 +1043,7 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
|||||||
var currentDay = currentTime.Format("20060102")
|
var currentDay = currentTime.Format("20060102")
|
||||||
var startTime, _ = time.Parse("20060102150405", currentDay+"000000")
|
var startTime, _ = time.Parse("20060102150405", currentDay+"000000")
|
||||||
|
|
||||||
showInfo("Create Dummy Guide:" + "Time offset" + offset + " - " + xepgChannel.XName)
|
showDebug("Create Dummy Guide:"+"Time offset"+offset+" - "+xepgChannel.XName, 2)
|
||||||
|
|
||||||
var dl = strings.Split(xepgChannel.XMapping, "_")
|
var dl = strings.Split(xepgChannel.XMapping, "_")
|
||||||
dummyLength, err := strconv.Atoi(dl[0])
|
dummyLength, err := strconv.Atoi(dl[0])
|
||||||
@@ -839,7 +1052,7 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for d := 0; d < 4; d++ {
|
for d := range 4 {
|
||||||
|
|
||||||
var epgStartTime = startTime.Add(time.Hour * time.Duration(d*24))
|
var epgStartTime = startTime.Add(time.Hour * time.Duration(d*24))
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ function login() {
|
|||||||
if (value.length == 0) {
|
if (value.length == 0) {
|
||||||
inputs[i].style.borderColor = "red"
|
inputs[i].style.borderColor = "red"
|
||||||
err = true
|
err = true
|
||||||
|
} else {
|
||||||
|
inputs[i].style.borderColor = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
data[key] = value
|
data[key] = value
|
||||||
@@ -30,7 +32,6 @@ function login() {
|
|||||||
if (data.hasOwnProperty("confirm")) {
|
if (data.hasOwnProperty("confirm")) {
|
||||||
|
|
||||||
if (data["confirm"] != data["password"]) {
|
if (data["confirm"] != data["password"]) {
|
||||||
alert("sdafsd")
|
|
||||||
document.getElementById('password').style.borderColor = "red"
|
document.getElementById('password').style.borderColor = "red"
|
||||||
document.getElementById('confirm').style.borderColor = "red"
|
document.getElementById('confirm').style.borderColor = "red"
|
||||||
|
|
||||||
|
|||||||
114
ts/base_ts.ts
114
ts/base_ts.ts
@@ -5,6 +5,8 @@ var SEARCH_MAPPING = new Object()
|
|||||||
var UNDO = new Object()
|
var UNDO = new Object()
|
||||||
var SERVER_CONNECTION = false
|
var SERVER_CONNECTION = false
|
||||||
var WS_AVAILABLE = false
|
var WS_AVAILABLE = false
|
||||||
|
var ACTIVE_MENU_ID:string = ""
|
||||||
|
var LAST_BULK_CHECKBOX:HTMLInputElement = null
|
||||||
|
|
||||||
|
|
||||||
// Menü
|
// Menü
|
||||||
@@ -21,7 +23,7 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
|
|||||||
|
|
||||||
// Kategorien für die Einstellungen
|
// Kategorien für die Einstellungen
|
||||||
var settingsCategory = new Array()
|
var settingsCategory = new Array()
|
||||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"));settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"))
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,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.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.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("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"))
|
||||||
@@ -51,7 +53,44 @@ function showElement(elmID, type) {
|
|||||||
case false: cssClass = "none"; break;
|
case false: cssClass = "none"; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById(elmID).className = cssClass;
|
var element = document.getElementById(elmID)
|
||||||
|
if (element == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
element.className = cssClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConnectionState(state:string, text:string = "") {
|
||||||
|
|
||||||
|
var label:string = text
|
||||||
|
if (label == undefined || label.length == 0) {
|
||||||
|
switch (state) {
|
||||||
|
case "online":
|
||||||
|
label = "Connected"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "busy":
|
||||||
|
label = "Syncing"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "offline":
|
||||||
|
label = "Offline"
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
label = "Connecting"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var indicator = document.getElementById("connection-indicator")
|
||||||
|
if (indicator == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indicator.className = "status-" + state
|
||||||
|
indicator.innerText = label
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeButtonAction(element, buttonID, attribute) {
|
function changeButtonAction(element, buttonID, attribute) {
|
||||||
@@ -144,6 +183,59 @@ function getAllSelectedChannels():string[] {
|
|||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleChannelRangeSelection(checkbox:HTMLInputElement, event:MouseEvent) {
|
||||||
|
|
||||||
|
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:HTMLInputElement, shiftPressed:boolean) {
|
||||||
|
|
||||||
|
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:HTMLInputElement[] = new Array()
|
||||||
|
|
||||||
|
for (var i = 1; i < trs.length; i++) {
|
||||||
|
if ((trs[i] as HTMLElement).style.display != "none") {
|
||||||
|
var bulkCheckbox = (trs[i] as HTMLTableRowElement).querySelector("input.bulk") as HTMLInputElement
|
||||||
|
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() {
|
function selectAllChannels() {
|
||||||
|
|
||||||
var bulk:Boolean = false
|
var bulk:Boolean = false
|
||||||
@@ -173,6 +265,7 @@ function selectAllChannels() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LAST_BULK_CHECKBOX = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +290,7 @@ function bulkEdit() {
|
|||||||
(rows[i] as HTMLInputElement).checked = false
|
(rows[i] as HTMLInputElement).checked = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LAST_BULK_CHECKBOX = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,17 +473,19 @@ function searchInMapping() {
|
|||||||
|
|
||||||
function calculateWrapperHeight() {
|
function calculateWrapperHeight() {
|
||||||
|
|
||||||
if (document.getElementById("box-wrapper")){
|
|
||||||
|
|
||||||
var elm = document.getElementById("box-wrapper");
|
var elm = document.getElementById("box-wrapper");
|
||||||
|
var content = document.getElementById("content");
|
||||||
|
|
||||||
var divs = new Array("myStreamsBox", "clientInfo", "content");
|
if (elm != null && content != null){
|
||||||
var elementsHeight = 0 - elm.offsetHeight;
|
|
||||||
for (var i = 0; i < divs.length; i++) {
|
var contentTop = content.getBoundingClientRect().top
|
||||||
elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight;
|
var freeSpace = window.innerHeight - contentTop - 26
|
||||||
|
|
||||||
|
if (freeSpace < 180) {
|
||||||
|
freeSpace = 180
|
||||||
}
|
}
|
||||||
|
|
||||||
elm.style.height = window.innerHeight - elementsHeight + "px";
|
elm.style.height = freeSpace + "px";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,18 @@ function showLogs(bottom:boolean) {
|
|||||||
|
|
||||||
var logs = SERVER["log"]["log"]
|
var logs = SERVER["log"]["log"]
|
||||||
var div = document.getElementById("content_log")
|
var div = document.getElementById("content_log")
|
||||||
|
var wrapper = document.getElementById("box-wrapper") as HTMLElement
|
||||||
|
var shouldStickToBottom:boolean = bottom
|
||||||
|
|
||||||
div.innerHTML = ""
|
div.innerHTML = ""
|
||||||
|
|
||||||
|
if (wrapper != null && shouldStickToBottom == false) {
|
||||||
|
var distanceToBottom:number = wrapper.scrollHeight - wrapper.scrollTop - wrapper.clientHeight
|
||||||
|
if (distanceToBottom < 80) {
|
||||||
|
shouldStickToBottom = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var keys = getObjKeys(logs)
|
var keys = getObjKeys(logs)
|
||||||
|
|
||||||
keys.forEach(logID => {
|
keys.forEach(logID => {
|
||||||
@@ -44,9 +53,8 @@ function showLogs(bottom:boolean) {
|
|||||||
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
|
|
||||||
if (bottom == true) {
|
if (shouldStickToBottom == true && wrapper != null) {
|
||||||
|
|
||||||
var wrapper = document.getElementById("box-wrapper");
|
|
||||||
wrapper.scrollTop = wrapper.scrollHeight;
|
wrapper.scrollTop = wrapper.scrollHeight;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
321
ts/menu_ts.ts
321
ts/menu_ts.ts
@@ -37,6 +37,7 @@ class MainMenuItem extends MainMenu {
|
|||||||
var item = document.createElement("LI")
|
var item = document.createElement("LI")
|
||||||
item.setAttribute("onclick", "javascript: openThisMenu(this)")
|
item.setAttribute("onclick", "javascript: openThisMenu(this)")
|
||||||
item.setAttribute("id", this.id)
|
item.setAttribute("id", this.id)
|
||||||
|
item.setAttribute("data-menu", this.menuKey)
|
||||||
var img = this.createIMG(this.imgSrc)
|
var img = this.createIMG(this.imgSrc)
|
||||||
var value = this.createValue(this.value)
|
var value = this.createValue(this.value)
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ class MainMenuItem extends MainMenu {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case "mapping":
|
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}}"]
|
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}}"]
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -424,11 +425,7 @@ class Content {
|
|||||||
cell.child = true
|
cell.child = true
|
||||||
cell.childType = "IMG"
|
cell.childType = "IMG"
|
||||||
cell.imageURL = data[key]["tvg-logo"]
|
cell.imageURL = data[key]["tvg-logo"]
|
||||||
var td = cell.createCell()
|
tr.appendChild(cell.createCell())
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
|
|
||||||
td.id = key
|
|
||||||
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
// Kanalname
|
// Kanalname
|
||||||
var cell:Cell = new Cell()
|
var cell:Cell = new Cell()
|
||||||
@@ -436,10 +433,7 @@ class Content {
|
|||||||
cell.childType = "P"
|
cell.childType = "P"
|
||||||
cell.className = data[key]["x-category"]
|
cell.className = data[key]["x-category"]
|
||||||
cell.value = data[key]["x-name"]
|
cell.value = data[key]["x-name"]
|
||||||
var td = cell.createCell()
|
tr.appendChild(cell.createCell())
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
|
|
||||||
td.id = key
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
|
|
||||||
// Playlist
|
// Playlist
|
||||||
@@ -448,10 +442,7 @@ class Content {
|
|||||||
cell.childType = "P"
|
cell.childType = "P"
|
||||||
//cell.value = data[key]["_file.m3u.name"]
|
//cell.value = data[key]["_file.m3u.name"]
|
||||||
cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name")
|
cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name")
|
||||||
var td = cell.createCell()
|
tr.appendChild(cell.createCell())
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
|
|
||||||
td.id = key
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
|
|
||||||
// Gruppe (group-title)
|
// Gruppe (group-title)
|
||||||
@@ -459,10 +450,7 @@ class Content {
|
|||||||
cell.child = true
|
cell.child = true
|
||||||
cell.childType = "P"
|
cell.childType = "P"
|
||||||
cell.value = data[key]["x-group-title"]
|
cell.value = data[key]["x-group-title"]
|
||||||
var td = cell.createCell()
|
tr.appendChild(cell.createCell())
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
|
|
||||||
td.id = key
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
// XMLTV Datei
|
// XMLTV Datei
|
||||||
var cell:Cell = new Cell()
|
var cell:Cell = new Cell()
|
||||||
@@ -475,10 +463,7 @@ class Content {
|
|||||||
cell.value = data[key]["x-xmltv-file"]
|
cell.value = data[key]["x-xmltv-file"]
|
||||||
}
|
}
|
||||||
|
|
||||||
var td = cell.createCell()
|
tr.appendChild(cell.createCell())
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
|
|
||||||
td.id = key
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
// XMLTV Kanal
|
// XMLTV Kanal
|
||||||
var cell:Cell = new Cell()
|
var cell:Cell = new Cell()
|
||||||
@@ -490,11 +475,18 @@ class Content {
|
|||||||
value = data[key]["x-mapping"].substring(0, 20) + "..."
|
value = data[key]["x-mapping"].substring(0, 20) + "..."
|
||||||
}
|
}
|
||||||
cell.value = value
|
cell.value = value
|
||||||
var td = cell.createCell()
|
tr.appendChild(cell.createCell())
|
||||||
td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
|
|
||||||
td.id = key
|
|
||||||
|
|
||||||
tr.appendChild(td)
|
var cell:Cell = new Cell()
|
||||||
|
cell.child = true
|
||||||
|
cell.childType = "EDIT"
|
||||||
|
cell.value = "{{.button.edit}}"
|
||||||
|
var editTd = cell.createCell()
|
||||||
|
var editButton = (editTd.firstChild as HTMLInputElement)
|
||||||
|
editButton.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
|
||||||
|
editButton.setAttribute("id", key)
|
||||||
|
editButton.setAttribute("aria-label", "Edit " + data[key]["x-name"])
|
||||||
|
tr.appendChild(editTd)
|
||||||
|
|
||||||
rows.push(tr)
|
rows.push(tr)
|
||||||
});
|
});
|
||||||
@@ -561,6 +553,9 @@ class Cell {
|
|||||||
(element as HTMLInputElement).checked = this.value;
|
(element as HTMLInputElement).checked = this.value;
|
||||||
(element as HTMLInputElement).type = "checkbox";
|
(element as HTMLInputElement).type = "checkbox";
|
||||||
(element as HTMLInputElement).className = "bulk hideBulk";
|
(element as HTMLInputElement).className = "bulk hideBulk";
|
||||||
|
(element as HTMLInputElement).addEventListener("click", function(event) {
|
||||||
|
scheduleChannelRangeSelection((element as HTMLInputElement), (event as MouseEvent))
|
||||||
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
case "BULK_HEAD":
|
case "BULK_HEAD":
|
||||||
@@ -578,6 +573,14 @@ class Cell {
|
|||||||
element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''" )
|
element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''" )
|
||||||
//onerror="this.onerror=null;this.src='missing.gif';"
|
//onerror="this.onerror=null;this.src='missing.gif';"
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case "EDIT":
|
||||||
|
element = document.createElement("INPUT");
|
||||||
|
(element as HTMLInputElement).type = "button";
|
||||||
|
(element as HTMLInputElement).value = this.value;
|
||||||
|
(element as HTMLInputElement).className = "mapping-edit-button";
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
td.appendChild(element)
|
td.appendChild(element)
|
||||||
@@ -683,7 +686,7 @@ class ShowContent extends Content {
|
|||||||
input.setAttribute("id", "searchMapping")
|
input.setAttribute("id", "searchMapping")
|
||||||
input.setAttribute("placeholder", "{{.button.search}}")
|
input.setAttribute("placeholder", "{{.button.search}}")
|
||||||
input.className = "search"
|
input.className = "search"
|
||||||
input.setAttribute("onchange", 'javascript: searchInMapping()')
|
input.setAttribute("oninput", 'javascript: searchInMapping()')
|
||||||
interaction.appendChild(input)
|
interaction.appendChild(input)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -824,16 +827,227 @@ class ShowContent extends Content {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var SHELL_LAYOUT_READY:boolean = false
|
||||||
|
|
||||||
|
function setLayoutMenuState(open:boolean) {
|
||||||
|
if (document.body == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open == true) {
|
||||||
|
document.body.classList.add("menu-open")
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove("menu-open")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLayoutMenu() {
|
||||||
|
if (document.body == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isOpen:boolean = document.body.classList.contains("menu-open")
|
||||||
|
setLayoutMenuState(!isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeLayoutMenuIfMobile() {
|
||||||
|
if (window.innerWidth <= 900) {
|
||||||
|
setLayoutMenuState(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveMenu(menuID:string) {
|
||||||
|
|
||||||
|
ACTIVE_MENU_ID = menuID.toString()
|
||||||
|
var menu = document.getElementById("main-menu")
|
||||||
|
if (menu == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = menu.getElementsByTagName("LI")
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
items[i].classList.remove("menu-active")
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeItem = document.getElementById(ACTIVE_MENU_ID)
|
||||||
|
var activeMenuKey:string = ""
|
||||||
|
if (activeItem != null) {
|
||||||
|
activeItem.classList.add("menu-active")
|
||||||
|
var menuKeyValue = activeItem.getAttribute("data-menu")
|
||||||
|
if (menuKeyValue != null) {
|
||||||
|
activeMenuKey = menuKeyValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.body != null) {
|
||||||
|
if (activeMenuKey.length > 0) {
|
||||||
|
document.body.setAttribute("data-active-menu", activeMenuKey)
|
||||||
|
} else {
|
||||||
|
document.body.removeAttribute("data-active-menu")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeMenuKey == "log") {
|
||||||
|
document.body.classList.add("menu-log-focus")
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove("menu-log-focus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStatusCards() {
|
||||||
|
var wrapper = document.getElementById("status-cards")
|
||||||
|
if (wrapper == null || SERVER.hasOwnProperty("clientInfo") == false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = SERVER["clientInfo"]
|
||||||
|
var errors:number = parseInt(info["errors"], 10)
|
||||||
|
var warnings:number = parseInt(info["warnings"], 10)
|
||||||
|
var cards:any[] = [
|
||||||
|
{label: "Streams", value: info["streams"], tone: "ok"},
|
||||||
|
{label: "EPG Source", value: info["epgSource"], tone: "neutral"},
|
||||||
|
{label: "XEPG Channels", value: info["xepg"], tone: "ok"},
|
||||||
|
{label: "Errors", value: info["errors"], tone: errors > 0 ? "error" : "ok"},
|
||||||
|
{label: "Warnings", value: info["warnings"], tone: warnings > 0 ? "warn" : "ok"},
|
||||||
|
{label: "DVR", value: info["DVR"], tone: "neutral"},
|
||||||
|
]
|
||||||
|
|
||||||
|
wrapper.innerHTML = ""
|
||||||
|
cards.forEach(card => {
|
||||||
|
|
||||||
|
var box = document.createElement("DIV")
|
||||||
|
box.className = "status-card status-card-" + card.tone
|
||||||
|
|
||||||
|
var label = document.createElement("P")
|
||||||
|
label.className = "status-card-label"
|
||||||
|
label.innerText = card.label
|
||||||
|
|
||||||
|
var value = document.createElement("P")
|
||||||
|
value.className = "status-card-value"
|
||||||
|
if (card.value == undefined || card.value == "") {
|
||||||
|
value.innerText = "-"
|
||||||
|
} else {
|
||||||
|
value.innerText = card.value
|
||||||
|
}
|
||||||
|
|
||||||
|
box.appendChild(label)
|
||||||
|
box.appendChild(value)
|
||||||
|
wrapper.appendChild(box)
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function initShellLayout() {
|
||||||
|
|
||||||
|
if (SHELL_LAYOUT_READY == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var toggle = document.getElementById("menu-toggle")
|
||||||
|
if (toggle != null) {
|
||||||
|
toggle.onclick = function() {
|
||||||
|
toggleLayoutMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var overlay = document.getElementById("layout-overlay")
|
||||||
|
if (overlay != null) {
|
||||||
|
overlay.onclick = function() {
|
||||||
|
setLayoutMenuState(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keydown", function(event) {
|
||||||
|
|
||||||
|
if (event.key == "Escape") {
|
||||||
|
setLayoutMenuState(false)
|
||||||
|
showElement("popup", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key == "/") {
|
||||||
|
var target = event.target as HTMLElement
|
||||||
|
var onInput = target.tagName == "INPUT" || target.tagName == "TEXTAREA" || target.tagName == "SELECT"
|
||||||
|
|
||||||
|
if (onInput == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var search = document.getElementById("searchMapping")
|
||||||
|
if (search != null) {
|
||||||
|
event.preventDefault()
|
||||||
|
(search as HTMLInputElement).focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
setConnectionState("idle")
|
||||||
|
SHELL_LAYOUT_READY = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldPollLogs():boolean {
|
||||||
|
if (document.hidden == true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById("content_log") == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ACTIVE_MENU_ID.length < 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeItem = document.getElementById(ACTIVE_MENU_ID)
|
||||||
|
if (activeItem == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeItem.getAttribute("data-menu") == "log"
|
||||||
|
}
|
||||||
|
|
||||||
function PageReady() {
|
function PageReady() {
|
||||||
|
|
||||||
|
initShellLayout()
|
||||||
|
|
||||||
var server:Server = new Server("getServerConfig")
|
var server:Server = new Server("getServerConfig")
|
||||||
server.request(new Object())
|
server.request(new Object())
|
||||||
|
|
||||||
|
var bootstrapAttempts:number = 0
|
||||||
|
var maxBootstrapAttempts:number = 5
|
||||||
|
var bootstrapTimer:number = window.setInterval(function() {
|
||||||
|
|
||||||
|
if (SERVER.hasOwnProperty("clientInfo") == true) {
|
||||||
|
window.clearInterval(bootstrapTimer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SERVER_CONNECTION == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrapAttempts++
|
||||||
|
var retryServer:Server = new Server("getServerConfig")
|
||||||
|
retryServer.request(new Object())
|
||||||
|
|
||||||
|
if (bootstrapAttempts >= maxBootstrapAttempts) {
|
||||||
|
window.clearInterval(bootstrapTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 3000)
|
||||||
|
|
||||||
window.addEventListener("resize", function(){
|
window.addEventListener("resize", function(){
|
||||||
|
if (window.innerWidth > 900) {
|
||||||
|
setLayoutMenuState(false)
|
||||||
|
}
|
||||||
calculateWrapperHeight();
|
calculateWrapperHeight();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
setInterval(function(){
|
setInterval(function(){
|
||||||
|
if (shouldPollLogs() == false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
updateLog()
|
updateLog()
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
@@ -841,6 +1055,35 @@ function PageReady() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isClientInfoHttpURL(value:string):boolean {
|
||||||
|
return /^https?:\/\//i.test(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setClientInfoValue(key:string, value:any) {
|
||||||
|
var element = document.getElementById(key)
|
||||||
|
if (element == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var textValue = ""
|
||||||
|
if (value != undefined && value != null) {
|
||||||
|
textValue = String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((key == "m3u-url" || key == "xepg-url") && isClientInfoHttpURL(textValue)) {
|
||||||
|
element.innerHTML = ""
|
||||||
|
var anchor = document.createElement("A")
|
||||||
|
anchor.href = textValue
|
||||||
|
anchor.target = "_blank"
|
||||||
|
anchor.rel = "noopener noreferrer"
|
||||||
|
anchor.textContent = textValue
|
||||||
|
element.appendChild(anchor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = textValue
|
||||||
|
}
|
||||||
|
|
||||||
function createLayout() {
|
function createLayout() {
|
||||||
|
|
||||||
// Client Info
|
// Client Info
|
||||||
@@ -848,11 +1091,10 @@ function createLayout() {
|
|||||||
var keys = getObjKeys(obj);
|
var keys = getObjKeys(obj);
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
|
||||||
if (document.getElementById(keys[i])) {
|
setClientInfoValue(keys[i], obj[keys[i]])
|
||||||
document.getElementById(keys[i]).innerHTML = obj[keys[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
renderStatusCards()
|
||||||
|
|
||||||
if (!document.getElementById("main-menu")) {
|
if (!document.getElementById("main-menu")) {
|
||||||
return
|
return
|
||||||
@@ -889,13 +1131,32 @@ function createLayout() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ACTIVE_MENU_ID.length > 0 && document.getElementById(ACTIVE_MENU_ID)) {
|
||||||
|
setActiveMenu(ACTIVE_MENU_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = document.getElementById("content")
|
||||||
|
var menu = document.getElementById("main-menu")
|
||||||
|
if (ACTIVE_MENU_ID.length == 0 && content != null && menu != null) {
|
||||||
|
|
||||||
|
if (content.innerHTML.replace(/\\s/g, "").length == 0) {
|
||||||
|
var firstItem = menu.getElementsByTagName("LI")[0]
|
||||||
|
if (firstItem != undefined) {
|
||||||
|
firstItem.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function openThisMenu(element) {
|
function openThisMenu(element) {
|
||||||
var id = element.id
|
var id = element.id
|
||||||
var content:ShowContent = new ShowContent(id)
|
var content:ShowContent = new ShowContent(id)
|
||||||
|
setActiveMenu(id)
|
||||||
content.show()
|
content.show()
|
||||||
|
closeLayoutMenuIfMobile()
|
||||||
calculateWrapperHeight()
|
calculateWrapperHeight()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class Server {
|
|||||||
if (this.cmd != "updateLog") {
|
if (this.cmd != "updateLog") {
|
||||||
showElement("loading", true)
|
showElement("loading", true)
|
||||||
UNDO = new Object()
|
UNDO = new Object()
|
||||||
|
setConnectionState("busy")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(window.location.protocol) {
|
switch(window.location.protocol) {
|
||||||
@@ -29,13 +30,67 @@ class Server {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = this.protocol + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token")
|
var wsHost:string = 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
|
data["cmd"] = this.cmd
|
||||||
|
var requestCmd:string = data["cmd"]
|
||||||
var ws = new WebSocket(url)
|
var ws = new WebSocket(url)
|
||||||
|
var isLogUpdate:boolean = data["cmd"] == "updateLog"
|
||||||
|
var responseReceived:boolean = false
|
||||||
|
var requestFinished:boolean = false
|
||||||
|
var timeoutMs:number = 12000
|
||||||
|
var requestTimeout:number
|
||||||
|
|
||||||
|
var finishRequest = function(state:string, responseSuccess:boolean = false):void {
|
||||||
|
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:string = "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.onopen = function() {
|
||||||
|
|
||||||
WS_AVAILABLE = true
|
WS_AVAILABLE = true
|
||||||
|
if (data["cmd"] != "updateLog") {
|
||||||
|
setConnectionState("busy")
|
||||||
|
}
|
||||||
|
|
||||||
console.log("REQUEST (JS):");
|
console.log("REQUEST (JS):");
|
||||||
console.log(data)
|
console.log(data)
|
||||||
@@ -50,9 +105,13 @@ class Server {
|
|||||||
ws.onerror = function(e) {
|
ws.onerror = function(e) {
|
||||||
|
|
||||||
console.log("No websocket connection to xTeVe could be established. Check your network configuration.")
|
console.log("No websocket connection to xTeVe could be established. Check your network configuration.")
|
||||||
SERVER_CONNECTION = false
|
var errorState:string = "offline"
|
||||||
|
if (isLogUpdate == true && WS_FAILURE_COUNT < 2) {
|
||||||
|
errorState = "idle"
|
||||||
|
}
|
||||||
|
finishRequest(errorState, false)
|
||||||
|
|
||||||
if (WS_AVAILABLE == false) {
|
if (WS_AVAILABLE == false && isLogUpdate == false && requestCmd != "getServerConfig") {
|
||||||
alert("No websocket connection to xTeVe could be established. Check your network configuration.")
|
alert("No websocket connection to xTeVe could be established. Check your network configuration.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +119,8 @@ class Server {
|
|||||||
|
|
||||||
|
|
||||||
ws.onmessage = function (e) {
|
ws.onmessage = function (e) {
|
||||||
|
responseReceived = true
|
||||||
SERVER_CONNECTION = false
|
finishRequest("online", true)
|
||||||
showElement("loading", false)
|
|
||||||
|
|
||||||
console.log("RESPONSE:");
|
console.log("RESPONSE:");
|
||||||
var response = JSON.parse(e.data);
|
var response = JSON.parse(e.data);
|
||||||
@@ -74,6 +132,7 @@ class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response["status"] == false) {
|
if (response["status"] == false) {
|
||||||
|
setConnectionState("offline")
|
||||||
|
|
||||||
alert(response["err"])
|
alert(response["err"])
|
||||||
|
|
||||||
@@ -136,10 +195,24 @@ class Server {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
if (responseReceived == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeState:string = "offline"
|
||||||
|
if (isLogUpdate == true && WS_FAILURE_COUNT < 2) {
|
||||||
|
closeState = "idle"
|
||||||
|
}
|
||||||
|
finishRequest(closeState, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var WS_FAILURE_COUNT:number = 0
|
||||||
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
var value = "; " + document.cookie;
|
var value = "; " + document.cookie;
|
||||||
var parts = value.split("; " + name + "=");
|
var parts = value.split("; " + name + "=");
|
||||||
|
|||||||
@@ -75,6 +75,35 @@ class SettingsCategory {
|
|||||||
setting.appendChild(tdRight)
|
setting.appendChild(tdRight)
|
||||||
break
|
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":
|
case "buffer.timeout":
|
||||||
var tdLeft = document.createElement("TD")
|
var tdLeft = document.createElement("TD")
|
||||||
tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":"
|
tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":"
|
||||||
@@ -276,6 +305,20 @@ class SettingsCategory {
|
|||||||
var tdLeft = document.createElement("TD")
|
var tdLeft = document.createElement("TD")
|
||||||
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
|
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 tdRight = document.createElement("TD")
|
||||||
var input = content.createCheckbox(settingsKey)
|
var input = content.createCheckbox(settingsKey)
|
||||||
input.checked = data
|
input.checked = data
|
||||||
@@ -287,6 +330,22 @@ class SettingsCategory {
|
|||||||
break
|
break
|
||||||
|
|
||||||
// Select
|
// Select
|
||||||
|
case "xepg.missing.epg.mode":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.xepgMissingEPGMode.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var text:any[] = ["{{.settings.xepgMissingEPGMode.info_strict}}", "{{.settings.xepgMissingEPGMode.info_relaxed}}"]
|
||||||
|
var values:any[] = ["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":
|
case "tuner":
|
||||||
var tdLeft = document.createElement("TD")
|
var tdLeft = document.createElement("TD")
|
||||||
tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":"
|
tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":"
|
||||||
@@ -454,6 +513,14 @@ class SettingsCategory {
|
|||||||
text = "{{.settings.userAgent.description}}"
|
text = "{{.settings.userAgent.description}}"
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case "plex.url":
|
||||||
|
text = "{{.settings.plexURL.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "plex.token":
|
||||||
|
text = "{{.settings.plexToken.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
case "ffmpeg.path":
|
case "ffmpeg.path":
|
||||||
text = "{{.settings.ffmpegPath.description}}"
|
text = "{{.settings.ffmpegPath.description}}"
|
||||||
break
|
break
|
||||||
@@ -486,6 +553,10 @@ class SettingsCategory {
|
|||||||
text = "{{.settings.api.description}}"
|
text = "{{.settings.api.description}}"
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case "use_plexAPI":
|
||||||
|
text = "{{.settings.usePlexAPI.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
case "files.update":
|
case "files.update":
|
||||||
text = "{{.settings.filesUpdate.description}}"
|
text = "{{.settings.filesUpdate.description}}"
|
||||||
break
|
break
|
||||||
@@ -498,6 +569,10 @@ class SettingsCategory {
|
|||||||
text = "{{.settings.replaceEmptyImages.description}}"
|
text = "{{.settings.replaceEmptyImages.description}}"
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case "xepg.missing.epg.mode":
|
||||||
|
text = "{{.settings.xepgMissingEPGMode.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
case "udpxy":
|
case "udpxy":
|
||||||
text = "{{.settings.udpxy.description}}"
|
text = "{{.settings.udpxy.description}}"
|
||||||
break
|
break
|
||||||
|
|||||||
3
xteve.go
3
xteve.go
@@ -39,7 +39,8 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
|
|||||||
const Name = "xTeVe"
|
const Name = "xTeVe"
|
||||||
|
|
||||||
// Version : Version, die Build Nummer wird in der main func geparst.
|
// Version : Version, die Build Nummer wird in der main func geparst.
|
||||||
const Version = "2.2.0.0200"
|
// Can be overwritten at build time: -ldflags "-X main.Version=..."
|
||||||
|
var Version = "2.2.0.0200"
|
||||||
|
|
||||||
// DBVersion : Datanbank Version
|
// DBVersion : Datanbank Version
|
||||||
const DBVersion = "2.1.0"
|
const DBVersion = "2.1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user