Redesign UI and add first-party Docker runtime support
This commit is contained in:
11
.dockerignore
Normal file
11
.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
.git
|
||||
.github
|
||||
.DS_Store
|
||||
|
||||
docker-data
|
||||
|
||||
README-DEV.md
|
||||
changelog-beta.md
|
||||
|
||||
*.log
|
||||
*.tmp
|
||||
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
|
||||
WORKDIR /src
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
||||
go build -trimpath -ldflags='-s -w' -o /out/xteve ./xteve.go
|
||||
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata \
|
||||
&& addgroup -S xteve \
|
||||
&& adduser -S -G xteve xteve \
|
||||
&& mkdir -p /xteve/config \
|
||||
&& chown -R xteve:xteve /xteve
|
||||
|
||||
WORKDIR /xteve
|
||||
|
||||
COPY --from=builder /out/xteve /usr/local/bin/xteve
|
||||
COPY docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
USER xteve
|
||||
|
||||
EXPOSE 34400/tcp
|
||||
VOLUME ["/xteve/config"]
|
||||
|
||||
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
|
||||
#### 64 Bit Intel / AMD
|
||||
|
||||
@@ -156,4 +209,3 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
13
docker-compose.host.yml
Normal file
13
docker-compose.host.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
xteve:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: xteve
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
environment:
|
||||
XTEVE_CONFIG: /xteve/config
|
||||
XTEVE_PORT: "34400"
|
||||
volumes:
|
||||
- ./docker-data/config:/xteve/config
|
||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
xteve:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: xteve
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
XTEVE_CONFIG: /xteve/config
|
||||
XTEVE_PORT: "34400"
|
||||
ports:
|
||||
- "34400:34400/tcp"
|
||||
- "1900:1900/udp"
|
||||
volumes:
|
||||
- ./docker-data/config:/xteve/config
|
||||
9
docker/entrypoint.sh
Executable file
9
docker/entrypoint.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
CONFIG_DIR="${XTEVE_CONFIG:-/xteve/config}"
|
||||
PORT="${XTEVE_PORT:-34400}"
|
||||
|
||||
mkdir -p "${CONFIG_DIR}"
|
||||
|
||||
exec /usr/local/bin/xteve -config "${CONFIG_DIR}" -port "${PORT}" "$@"
|
||||
@@ -13,7 +13,7 @@
|
||||
<script language="javascript" type="text/javascript" src="js/base_ts.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="javascript: readyForConfiguration(0);">
|
||||
<body class="auth-screen wizard-screen" onload="javascript: readyForConfiguration(0);">
|
||||
|
||||
<div id="loading" class="block">
|
||||
<div class="loader"></div>
|
||||
@@ -55,4 +55,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="auth-screen">
|
||||
|
||||
<div id="header" class="imgCenter"></div>
|
||||
|
||||
@@ -44,4 +44,4 @@
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,241 +1,293 @@
|
||||
:root {
|
||||
--bg-0: #07111d;
|
||||
--bg-1: #0c1a2b;
|
||||
--bg-2: #12233a;
|
||||
--panel: #102238;
|
||||
--panel-soft: #0f1f33;
|
||||
--line: #274462;
|
||||
--line-soft: #1b334d;
|
||||
--text: #e9f5ff;
|
||||
--text-muted: #9db5cb;
|
||||
--accent: #35d2ff;
|
||||
--accent-strong: #12b9ff;
|
||||
--accent-soft: rgba(53, 210, 255, 0.2);
|
||||
--ok: #2fd18a;
|
||||
--warn: #ffc15a;
|
||||
--error: #ff6f6f;
|
||||
--radius-s: 10px;
|
||||
--radius-m: 14px;
|
||||
--radius-l: 18px;
|
||||
--shadow-1: 0 20px 45px rgba(0, 0, 0, 0.32);
|
||||
--shadow-2: 0 12px 30px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
-ms-appearance: none;
|
||||
font-family: "Arial", sans-serif;
|
||||
letter-spacing: 2px;
|
||||
box-sizing: border-box;
|
||||
font-family: "Space Grotesk", "Avenir Next", "Trebuchet MS", sans-serif;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/*
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
*/
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||
border-radius: 5px;
|
||||
|
||||
border-radius: 999px;
|
||||
background: rgba(7, 17, 29, 0.65);
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0,0.6);
|
||||
background-color: #444;
|
||||
border-radius: 999px;
|
||||
border: 2px solid rgba(7, 17, 29, 0.6);
|
||||
background: linear-gradient(180deg, #2d597f, #1e4060);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #333;
|
||||
background: linear-gradient(180deg, #3678ac, #20537c);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
background:
|
||||
radial-gradient(circle at 15% 12%, rgba(64, 132, 183, 0.22), transparent 32%),
|
||||
radial-gradient(circle at 88% 2%, rgba(18, 185, 255, 0.16), transparent 30%),
|
||||
linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 56%, #060d17 100%);
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00E6FF;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
html, body {
|
||||
color: #fff;
|
||||
margin: 0px auto;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
a:hover {
|
||||
color: #8de7ff;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.72rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
letter-spacing: 2px;
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
letter-spacing: 1px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.5em;
|
||||
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.2em;
|
||||
margin: 25px 0px 10px 0px;
|
||||
font-size: 0.92rem;
|
||||
margin: 8px 0;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.015em;
|
||||
font-family: "IBM Plex Mono", "SFMono-Regular", "Consolas", monospace;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background: #333;
|
||||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 2px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0px 0px 5px 0px;
|
||||
font-size: 12px;
|
||||
color: #ddd;
|
||||
letter-spacing: 1px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
line-height: 1.6em;
|
||||
margin: 12px 0;
|
||||
background: linear-gradient(90deg, transparent, var(--line), transparent);
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
background-color: #111;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border-left: solid 2px #111;
|
||||
transition: all 0.3;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
border-color: #00E6FF
|
||||
select,
|
||||
input,
|
||||
textarea,
|
||||
button {
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=search],
|
||||
input[type=password],
|
||||
input[type=number],
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
border-radius: var(--radius-s);
|
||||
border: 1px solid var(--line);
|
||||
background-color: rgba(10, 22, 36, 0.78);
|
||||
color: var(--text);
|
||||
padding: 10px 11px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
input[type=text]:focus,
|
||||
input[type=search]:focus,
|
||||
input[type=password]:focus,
|
||||
input[type=number]:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(53, 210, 255, 0.17);
|
||||
background-color: rgba(9, 25, 40, 0.95);
|
||||
}
|
||||
|
||||
input[type=text]::placeholder,
|
||||
input[type=search]::placeholder,
|
||||
input[type=password]::placeholder,
|
||||
textarea::placeholder {
|
||||
color: #7e9ab4;
|
||||
}
|
||||
|
||||
input[type=button],
|
||||
input[type=submit],
|
||||
button {
|
||||
cursor: pointer;
|
||||
width: calc(100% + 2px);
|
||||
border: solid 0px #00E6FF;
|
||||
border-radius: 0px;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
color: #03101a;
|
||||
padding: 10px 18px;
|
||||
margin: 8px 8px 8px 0;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
background: linear-gradient(135deg, #67e8ff 0%, #12b9ff 100%);
|
||||
box-shadow: 0 10px 22px rgba(18, 185, 255, 0.3);
|
||||
transition: transform 0.15s ease, box-shadow 0.2s ease, filter 0.2s ease;
|
||||
}
|
||||
|
||||
input[type=button]:hover,
|
||||
input[type=submit]:hover,
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.05);
|
||||
box-shadow: 0 12px 24px rgba(18, 185, 255, 0.36);
|
||||
}
|
||||
|
||||
input[type=button]:active,
|
||||
input[type=submit]:active,
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
input[type=button].delete {
|
||||
color: #fff;
|
||||
padding: 9px 10px;
|
||||
display:block;
|
||||
background-color: #333;
|
||||
font-size: 14px;
|
||||
margin: 5px 0px 5px 0px;
|
||||
background: linear-gradient(135deg, #ff7f7f 0%, #e94343 100%);
|
||||
box-shadow: 0 10px 20px rgba(233, 67, 67, 0.35);
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input {
|
||||
-webkit-appearance: none;
|
||||
margin: 5px 0px;
|
||||
padding: 2.5px 10px;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type=button], input[type=submit] {
|
||||
cursor: pointer;
|
||||
background-color: #000;
|
||||
margin: 10px 10px;
|
||||
padding: 10px 25px;
|
||||
border: solid 0px;
|
||||
border-color: #000;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input[type=button]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=button]:hover {
|
||||
background-color: #00E6FF;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
input[type=button]:hover.delete {
|
||||
background-color: red;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input[type=text], input[type=search], input[type=password] {
|
||||
color: #fff;
|
||||
width: -webkit-calc(100% - 0px);
|
||||
width: -moz-calc(100% - 0px);
|
||||
width: calc(100% - 0px);
|
||||
outline: none;
|
||||
border: solid 1px transparent;
|
||||
background-color: transparent;
|
||||
border-bottom-color: #555;
|
||||
border-radius: 0px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
border: solid 1px #00E6FF;
|
||||
background-color: #333;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
cursor: pointer;
|
||||
/*
|
||||
-webkit-appearance: checkbox;
|
||||
*/
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked {
|
||||
color: #fff;
|
||||
background-color: #00E6FF;
|
||||
/*display: inline-block;*/
|
||||
}
|
||||
|
||||
input[type="checkbox"]:before {
|
||||
position: initial;
|
||||
left: 0px;
|
||||
margin-left: -4px;
|
||||
content: " ";
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked:before {
|
||||
position: initial;
|
||||
left: 0px;
|
||||
margin-left: -3px;
|
||||
content: "✓";
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
input[type=button].cancel {
|
||||
|
||||
background-color: transparent;
|
||||
border-color: red;
|
||||
color: #ffd8d8;
|
||||
border-color: rgba(255, 111, 111, 0.45);
|
||||
background: rgba(87, 22, 22, 0.45);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input[type=button].save{
|
||||
background-color: #111;
|
||||
float: right;
|
||||
input[type=button].black,
|
||||
input[type=submit].black {
|
||||
color: #d8ecff;
|
||||
border-color: var(--line);
|
||||
background: rgba(14, 32, 50, 0.85);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
input[type=button].black, input[type=submit].black{
|
||||
background-color: #000;
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
input[type=button].center{
|
||||
input[type=button].center {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
background-color: #000;
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
input[type=button].save {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background-color: rgba(10, 22, 36, 0.86);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input[type=checkbox]:checked {
|
||||
border-color: #89ebff;
|
||||
background-color: #39d6ff;
|
||||
}
|
||||
|
||||
input[type=checkbox]:before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
input[type=checkbox]:checked:before {
|
||||
content: "\2713";
|
||||
color: #032033;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.changed {
|
||||
border-color: #8de7ff !important;
|
||||
box-shadow: 0 0 0 3px rgba(53, 210, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.notAvailable {
|
||||
opacity: 0.58;
|
||||
cursor: not-allowed;
|
||||
border-color: rgba(255, 111, 111, 0.45) !important;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
@@ -243,12 +295,11 @@ input[type=button].center{
|
||||
}
|
||||
|
||||
.pointer:hover {
|
||||
color: #00E6FF;
|
||||
cursor: pointer;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.sortThis {
|
||||
color: #00E6FF;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.w40px {
|
||||
@@ -271,14 +322,9 @@ input[type=button].center{
|
||||
max-width: 200px;
|
||||
min-width: 100px;
|
||||
width: 200px;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.w300px {
|
||||
max-width: 300px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.w220px {
|
||||
@@ -286,46 +332,18 @@ input[type=button].center{
|
||||
cursor: alias;
|
||||
}
|
||||
|
||||
.w300px {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.screenLogHidden {
|
||||
transform: translate(0px, -110px);
|
||||
}
|
||||
|
||||
.borderSpace {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.block {
|
||||
|
||||
}
|
||||
|
||||
.none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.notVisible {
|
||||
height: 0px;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
border-bottom: #000 solid 0px;
|
||||
|
||||
}
|
||||
|
||||
.visible {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
border-bottom: #444 solid 1px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.floatRight {
|
||||
float: right;
|
||||
}
|
||||
@@ -334,115 +352,311 @@ input[type=button].center{
|
||||
float: left;
|
||||
}
|
||||
|
||||
.borderSpace {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.showBulk {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.hideBulk {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.noBulk {
|
||||
}
|
||||
|
||||
.notVisible {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.menu-active {
|
||||
background-color: #00E6FF;
|
||||
color: #011019;
|
||||
}
|
||||
|
||||
.menu-notActive {
|
||||
|
||||
}
|
||||
|
||||
#branch {
|
||||
display: table;
|
||||
margin: auto;
|
||||
color: red;
|
||||
}
|
||||
|
||||
#interaction {
|
||||
margin-bottom: 100px;
|
||||
text-align: center;
|
||||
border-bottom: solid 0px #777;
|
||||
}
|
||||
|
||||
|
||||
.half {
|
||||
display: block;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.menu {
|
||||
border: solid 1px #00E6FF;
|
||||
border: 1px solid var(--accent);
|
||||
}
|
||||
|
||||
.half {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.screenLogHidden {
|
||||
transform: translate(0, -110px);
|
||||
}
|
||||
|
||||
.infoMsg {
|
||||
color: #aaa;
|
||||
color: #87b0d1;
|
||||
}
|
||||
|
||||
.errorMsg {
|
||||
color: red;
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.warningMsg {
|
||||
color: yellow;
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
.debugMsg {
|
||||
color: magenta;
|
||||
color: #d687ff;
|
||||
}
|
||||
|
||||
.News, .Movie, .Series, .Sports, .Kids {
|
||||
border-left: solid 2px
|
||||
.News,
|
||||
.Movie,
|
||||
.Series,
|
||||
.Sports,
|
||||
.Kids {
|
||||
border-left: 3px solid transparent;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.News {
|
||||
border-color: tomato
|
||||
border-color: #ff8f6e;
|
||||
}
|
||||
|
||||
.Movie {
|
||||
border-color: royalblue;
|
||||
border-color: #46a4ff;
|
||||
}
|
||||
|
||||
.Series {
|
||||
border-color: gold;
|
||||
border-color: #ffd267;
|
||||
}
|
||||
|
||||
.Sports {
|
||||
border-color: yellowgreen;
|
||||
border-color: #5ee495;
|
||||
}
|
||||
|
||||
.Kids {
|
||||
border-color: mediumpurple;
|
||||
border-color: #f6a8ff;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
#loading {
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
background-color: rgba(0,0,0, 0.8);
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
background-color: rgba(5, 12, 20, 0.72);
|
||||
backdrop-filter: blur(1.5px);
|
||||
}
|
||||
|
||||
|
||||
.loader {
|
||||
border: 5px solid transparent;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 50%;
|
||||
border-top: 5px solid #00E6FF;
|
||||
border-bottom: 5px solid #00E6FF;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
-webkit-animation: spin 1.2s linear infinite;
|
||||
animation: spin 1.2s linear infinite;
|
||||
|
||||
border: 4px solid transparent;
|
||||
border-top-color: #58ddff;
|
||||
border-right-color: #12b9ff;
|
||||
animation: spin 0.9s linear infinite;
|
||||
position: fixed;
|
||||
margin: auto;
|
||||
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
#popup {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(3, 9, 16, 0.72);
|
||||
backdrop-filter: blur(2px);
|
||||
z-index: 1500;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
#popup-custom,
|
||||
#mapping-detail,
|
||||
#user-detail,
|
||||
#file-detail {
|
||||
margin: 0 auto;
|
||||
max-width: 760px;
|
||||
max-height: calc(100vh - 36px);
|
||||
overflow: auto;
|
||||
border-radius: var(--radius-l);
|
||||
border: 1px solid var(--line);
|
||||
background: linear-gradient(180deg, rgba(17, 34, 56, 0.97) 0%, rgba(12, 27, 44, 0.97) 100%);
|
||||
box-shadow: var(--shadow-1);
|
||||
padding: 16px;
|
||||
animation: popupIn 0.2s ease;
|
||||
}
|
||||
|
||||
#popup-custom h3 {
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
color: #d5efff;
|
||||
}
|
||||
|
||||
#popup-custom table,
|
||||
#content_settings table,
|
||||
#mapping-detail-table,
|
||||
#user-detail-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 8px;
|
||||
}
|
||||
|
||||
#popup-custom td,
|
||||
#content_settings td,
|
||||
#mapping-detail-table td,
|
||||
#user-detail-table td {
|
||||
padding: 2px 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#popup-custom td.left,
|
||||
#mapping-detail-table td.left,
|
||||
#user-detail-table td.left {
|
||||
width: 38%;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
#popup-custom input[type=text],
|
||||
#popup-custom input[type=password],
|
||||
#mapping-detail input[type=text],
|
||||
#content_settings input[type=text],
|
||||
#content_settings input[type=password] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#mapping-detail img {
|
||||
display: block;
|
||||
max-height: 44px;
|
||||
margin: 8px auto 12px;
|
||||
}
|
||||
|
||||
#file-detail input[type=text] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.interaction,
|
||||
#interaction {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.interaction input[type=button],
|
||||
.interaction input[type=submit] {
|
||||
margin: 0;
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
#popup-interaction {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#notification {
|
||||
position: fixed;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
width: 260px;
|
||||
max-height: calc(100vh - 24px);
|
||||
overflow: auto;
|
||||
border-radius: var(--radius-m);
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(10, 22, 36, 0.92);
|
||||
box-shadow: var(--shadow-2);
|
||||
}
|
||||
|
||||
#notification .element {
|
||||
margin: 8px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid var(--ok);
|
||||
background: rgba(17, 35, 56, 0.84);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#notification h5 {
|
||||
padding: 0;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
#notification p {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.tableEllipsis {
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popupIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(7px) scale(0.99);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 619px) {
|
||||
h1 {
|
||||
font-size: 1.46rem;
|
||||
}
|
||||
|
||||
#popup {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#popup-custom,
|
||||
#mapping-detail,
|
||||
#user-detail,
|
||||
#file-detail {
|
||||
max-height: calc(100vh - 20px);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.interaction,
|
||||
#interaction,
|
||||
#popup-interaction {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.interaction input[type=button],
|
||||
.interaction input[type=submit],
|
||||
#popup-interaction input[type=button] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
1074
html/css/screen.css
1074
html/css/screen.css
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<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>
|
||||
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||
@@ -17,7 +15,7 @@
|
||||
|
||||
</head>
|
||||
|
||||
<body onload="javascript: PageReady();">
|
||||
<body class="app-shell" onload="javascript: PageReady();">
|
||||
|
||||
<div id="loading" class="none">
|
||||
<div class="loader"></div>
|
||||
@@ -27,6 +25,7 @@
|
||||
<div id="popup-custom"></div>
|
||||
</div>
|
||||
|
||||
<div id="layout-overlay"></div>
|
||||
<div id="layout">
|
||||
|
||||
<!--
|
||||
@@ -40,13 +39,18 @@
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div id="menu-wrapper" class="layout-left">
|
||||
<aside id="menu-wrapper" class="layout-left">
|
||||
<div id= "branch"></div>
|
||||
<div id="logo"></div>
|
||||
<nav id="main-menu"></nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="layout-right">
|
||||
<main class="layout-right">
|
||||
<header id="shell-header">
|
||||
<button id="menu-toggle" type="button">Menu</button>
|
||||
<h2 id="shell-title">xTeVe Control Panel</h2>
|
||||
<p id="connection-indicator" class="status-idle">Connecting...</p>
|
||||
</header>
|
||||
|
||||
<table id="clientInfo" class="">
|
||||
|
||||
@@ -87,6 +91,8 @@
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div id="status-cards" class="dashboard-cards"></div>
|
||||
|
||||
<div id="myStreamsBox" class="notVisible">
|
||||
|
||||
@@ -99,10 +105,10 @@
|
||||
|
||||
<div id="content" class=""></div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -12,6 +12,9 @@ function login() {
|
||||
inputs[i].style.borderColor = "red";
|
||||
err = true;
|
||||
}
|
||||
else {
|
||||
inputs[i].style.borderColor = "";
|
||||
}
|
||||
data[key] = value;
|
||||
}
|
||||
if (err == true) {
|
||||
@@ -20,7 +23,6 @@ function login() {
|
||||
}
|
||||
if (data.hasOwnProperty("confirm")) {
|
||||
if (data["confirm"] != data["password"]) {
|
||||
alert("sdafsd");
|
||||
document.getElementById('password').style.borderColor = "red";
|
||||
document.getElementById('confirm').style.borderColor = "red";
|
||||
document.getElementById("err").innerHTML = "{{.account.failed}}";
|
||||
|
||||
@@ -5,6 +5,7 @@ var SEARCH_MAPPING = new Object();
|
||||
var UNDO = new Object();
|
||||
var SERVER_CONNECTION = false;
|
||||
var WS_AVAILABLE = false;
|
||||
var ACTIVE_MENU_ID = "";
|
||||
// Menü
|
||||
var menuItems = new Array();
|
||||
menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}"));
|
||||
@@ -44,7 +45,36 @@ function showElement(elmID, type) {
|
||||
cssClass = "none";
|
||||
break;
|
||||
}
|
||||
document.getElementById(elmID).className = cssClass;
|
||||
var element = document.getElementById(elmID);
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
element.className = cssClass;
|
||||
}
|
||||
function setConnectionState(state, text) {
|
||||
var label = text;
|
||||
if (label == undefined || label.length == 0) {
|
||||
switch (state) {
|
||||
case "online":
|
||||
label = "Connected";
|
||||
break;
|
||||
case "busy":
|
||||
label = "Syncing";
|
||||
break;
|
||||
case "offline":
|
||||
label = "Offline";
|
||||
break;
|
||||
default:
|
||||
label = "Connecting";
|
||||
break;
|
||||
}
|
||||
}
|
||||
var indicator = document.getElementById("connection-indicator");
|
||||
if (indicator == null) {
|
||||
return;
|
||||
}
|
||||
indicator.className = "status-" + state;
|
||||
indicator.innerText = label;
|
||||
}
|
||||
function changeButtonAction(element, buttonID, attribute) {
|
||||
var value = element.options[element.selectedIndex].value;
|
||||
@@ -272,14 +302,15 @@ function searchInMapping() {
|
||||
return;
|
||||
}
|
||||
function calculateWrapperHeight() {
|
||||
if (document.getElementById("box-wrapper")) {
|
||||
var elm = document.getElementById("box-wrapper");
|
||||
var divs = new Array("myStreamsBox", "clientInfo", "content");
|
||||
var elementsHeight = 0 - elm.offsetHeight;
|
||||
for (var i = 0; i < divs.length; i++) {
|
||||
elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight;
|
||||
var elm = document.getElementById("box-wrapper");
|
||||
var content = document.getElementById("content");
|
||||
if (elm != null && content != null) {
|
||||
var contentTop = content.getBoundingClientRect().top;
|
||||
var freeSpace = window.innerHeight - contentTop - 26;
|
||||
if (freeSpace < 180) {
|
||||
freeSpace = 180;
|
||||
}
|
||||
elm.style.height = window.innerHeight - elementsHeight + "px";
|
||||
elm.style.height = freeSpace + "px";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ var MainMenuItem = /** @class */ (function (_super) {
|
||||
var item = document.createElement("LI");
|
||||
item.setAttribute("onclick", "javascript: openThisMenu(this)");
|
||||
item.setAttribute("id", this.id);
|
||||
item.setAttribute("data-menu", this.menuKey);
|
||||
var img = this.createIMG(this.imgSrc);
|
||||
var value = this.createValue(this.value);
|
||||
item.appendChild(img);
|
||||
@@ -560,7 +561,7 @@ var ShowContent = /** @class */ (function (_super) {
|
||||
input.setAttribute("id", "searchMapping");
|
||||
input.setAttribute("placeholder", "{{.button.search}}");
|
||||
input.className = "search";
|
||||
input.setAttribute("onchange", 'javascript: searchInMapping()');
|
||||
input.setAttribute("oninput", 'javascript: searchInMapping()');
|
||||
interaction.appendChild(input);
|
||||
break;
|
||||
case "settings":
|
||||
@@ -668,10 +669,127 @@ var ShowContent = /** @class */ (function (_super) {
|
||||
};
|
||||
return ShowContent;
|
||||
}(Content));
|
||||
var SHELL_LAYOUT_READY = false;
|
||||
function setLayoutMenuState(open) {
|
||||
if (document.body == null) {
|
||||
return;
|
||||
}
|
||||
if (open == true) {
|
||||
document.body.classList.add("menu-open");
|
||||
}
|
||||
else {
|
||||
document.body.classList.remove("menu-open");
|
||||
}
|
||||
}
|
||||
function toggleLayoutMenu() {
|
||||
if (document.body == null) {
|
||||
return;
|
||||
}
|
||||
var isOpen = document.body.classList.contains("menu-open");
|
||||
setLayoutMenuState(!isOpen);
|
||||
}
|
||||
function closeLayoutMenuIfMobile() {
|
||||
if (window.innerWidth <= 900) {
|
||||
setLayoutMenuState(false);
|
||||
}
|
||||
}
|
||||
function setActiveMenu(menuID) {
|
||||
ACTIVE_MENU_ID = menuID.toString();
|
||||
var menu = document.getElementById("main-menu");
|
||||
if (menu == null) {
|
||||
return;
|
||||
}
|
||||
var items = menu.getElementsByTagName("LI");
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
items[i].classList.remove("menu-active");
|
||||
}
|
||||
var activeItem = document.getElementById(ACTIVE_MENU_ID);
|
||||
if (activeItem != null) {
|
||||
activeItem.classList.add("menu-active");
|
||||
}
|
||||
}
|
||||
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;
|
||||
var label = document.createElement("P");
|
||||
label.className = "status-card-label";
|
||||
label.innerText = card.label;
|
||||
var value = document.createElement("P");
|
||||
value.className = "status-card-value";
|
||||
if (card.value == undefined || card.value == "") {
|
||||
value.innerText = "-";
|
||||
}
|
||||
else {
|
||||
value.innerText = card.value;
|
||||
}
|
||||
box.appendChild(label);
|
||||
box.appendChild(value);
|
||||
wrapper.appendChild(box);
|
||||
});
|
||||
}
|
||||
function initShellLayout() {
|
||||
if (SHELL_LAYOUT_READY == true) {
|
||||
return;
|
||||
}
|
||||
var toggle = document.getElementById("menu-toggle");
|
||||
if (toggle != null) {
|
||||
toggle.onclick = function () {
|
||||
toggleLayoutMenu();
|
||||
};
|
||||
}
|
||||
var overlay = document.getElementById("layout-overlay");
|
||||
if (overlay != null) {
|
||||
overlay.onclick = function () {
|
||||
setLayoutMenuState(false);
|
||||
};
|
||||
}
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key == "Escape") {
|
||||
setLayoutMenuState(false);
|
||||
showElement("popup", false);
|
||||
return;
|
||||
}
|
||||
if (event.key == "/") {
|
||||
var target = event.target;
|
||||
var onInput = target.tagName == "INPUT" || target.tagName == "TEXTAREA" || target.tagName == "SELECT";
|
||||
if (onInput == true) {
|
||||
return;
|
||||
}
|
||||
var search = document.getElementById("searchMapping");
|
||||
if (search != null) {
|
||||
event.preventDefault();
|
||||
search.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
setConnectionState("idle");
|
||||
SHELL_LAYOUT_READY = true;
|
||||
}
|
||||
function PageReady() {
|
||||
initShellLayout();
|
||||
var server = new Server("getServerConfig");
|
||||
server.request(new Object());
|
||||
window.addEventListener("resize", function () {
|
||||
if (window.innerWidth > 900) {
|
||||
setLayoutMenuState(false);
|
||||
}
|
||||
calculateWrapperHeight();
|
||||
}, true);
|
||||
setInterval(function () {
|
||||
@@ -688,6 +806,7 @@ function createLayout() {
|
||||
document.getElementById(keys[i]).innerHTML = obj[keys[i]];
|
||||
}
|
||||
}
|
||||
renderStatusCards();
|
||||
if (!document.getElementById("main-menu")) {
|
||||
return;
|
||||
}
|
||||
@@ -713,12 +832,27 @@ function createLayout() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ACTIVE_MENU_ID.length > 0 && document.getElementById(ACTIVE_MENU_ID)) {
|
||||
setActiveMenu(ACTIVE_MENU_ID);
|
||||
}
|
||||
var content = document.getElementById("content");
|
||||
var menu = document.getElementById("main-menu");
|
||||
if (ACTIVE_MENU_ID.length == 0 && content != null && menu != null) {
|
||||
if (content.innerHTML.replace(/\\s/g, "").length == 0) {
|
||||
var firstItem = menu.getElementsByTagName("LI")[0];
|
||||
if (firstItem != undefined) {
|
||||
firstItem.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
function openThisMenu(element) {
|
||||
var id = element.id;
|
||||
var content = new ShowContent(id);
|
||||
setActiveMenu(id);
|
||||
content.show();
|
||||
closeLayoutMenuIfMobile();
|
||||
calculateWrapperHeight();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ var Server = /** @class */ (function () {
|
||||
if (this.cmd != "updateLog") {
|
||||
showElement("loading", true);
|
||||
UNDO = new Object();
|
||||
setConnectionState("busy");
|
||||
}
|
||||
switch (window.location.protocol) {
|
||||
case "http:":
|
||||
@@ -25,6 +26,9 @@ var Server = /** @class */ (function () {
|
||||
var ws = new WebSocket(url);
|
||||
ws.onopen = function () {
|
||||
WS_AVAILABLE = true;
|
||||
if (data["cmd"] != "updateLog") {
|
||||
setConnectionState("busy");
|
||||
}
|
||||
console.log("REQUEST (JS):");
|
||||
console.log(data);
|
||||
console.log("REQUEST: (JSON)");
|
||||
@@ -34,6 +38,7 @@ var Server = /** @class */ (function () {
|
||||
ws.onerror = function (e) {
|
||||
console.log("No websocket connection to xTeVe could be established. Check your network configuration.");
|
||||
SERVER_CONNECTION = false;
|
||||
setConnectionState("offline");
|
||||
if (WS_AVAILABLE == false) {
|
||||
alert("No websocket connection to xTeVe could be established. Check your network configuration.");
|
||||
}
|
||||
@@ -41,6 +46,9 @@ var Server = /** @class */ (function () {
|
||||
ws.onmessage = function (e) {
|
||||
SERVER_CONNECTION = false;
|
||||
showElement("loading", false);
|
||||
if (data["cmd"] != "updateLog") {
|
||||
setConnectionState("online");
|
||||
}
|
||||
console.log("RESPONSE:");
|
||||
var response = JSON.parse(e.data);
|
||||
console.log(response);
|
||||
@@ -48,6 +56,7 @@ var Server = /** @class */ (function () {
|
||||
document.cookie = "Token=" + response["token"];
|
||||
}
|
||||
if (response["status"] == false) {
|
||||
setConnectionState("offline");
|
||||
alert(response["err"]);
|
||||
if (response.hasOwnProperty("reload")) {
|
||||
location.reload();
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="auth-screen">
|
||||
|
||||
<div id="header" class="imgCenter"></div>
|
||||
|
||||
@@ -43,4 +43,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="auth-screen">
|
||||
|
||||
<div id="header" class="imgCenter"></div>
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
85
src/webUI.go
85
src/webUI.go
File diff suppressed because one or more lines are too long
@@ -16,6 +16,8 @@ function login() {
|
||||
if (value.length == 0) {
|
||||
inputs[i].style.borderColor = "red"
|
||||
err = true
|
||||
} else {
|
||||
inputs[i].style.borderColor = ""
|
||||
}
|
||||
|
||||
data[key] = value
|
||||
@@ -30,7 +32,6 @@ function login() {
|
||||
if (data.hasOwnProperty("confirm")) {
|
||||
|
||||
if (data["confirm"] != data["password"]) {
|
||||
alert("sdafsd")
|
||||
document.getElementById('password').style.borderColor = "red"
|
||||
document.getElementById('confirm').style.borderColor = "red"
|
||||
|
||||
@@ -44,4 +45,4 @@ function login() {
|
||||
|
||||
form.submit();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ var SEARCH_MAPPING = new Object()
|
||||
var UNDO = new Object()
|
||||
var SERVER_CONNECTION = false
|
||||
var WS_AVAILABLE = false
|
||||
var ACTIVE_MENU_ID:string = ""
|
||||
|
||||
|
||||
// Menü
|
||||
@@ -51,7 +52,44 @@ function showElement(elmID, type) {
|
||||
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) {
|
||||
@@ -379,17 +417,19 @@ function searchInMapping() {
|
||||
|
||||
function calculateWrapperHeight() {
|
||||
|
||||
if (document.getElementById("box-wrapper")){
|
||||
var elm = document.getElementById("box-wrapper");
|
||||
var content = document.getElementById("content");
|
||||
|
||||
var elm = document.getElementById("box-wrapper");
|
||||
if (elm != null && content != null){
|
||||
|
||||
var divs = new Array("myStreamsBox", "clientInfo", "content");
|
||||
var elementsHeight = 0 - elm.offsetHeight;
|
||||
for (var i = 0; i < divs.length; i++) {
|
||||
elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight;
|
||||
var contentTop = content.getBoundingClientRect().top
|
||||
var freeSpace = window.innerHeight - contentTop - 26
|
||||
|
||||
if (freeSpace < 180) {
|
||||
freeSpace = 180
|
||||
}
|
||||
|
||||
elm.style.height = window.innerHeight - elementsHeight + "px";
|
||||
elm.style.height = freeSpace + "px";
|
||||
|
||||
}
|
||||
|
||||
|
||||
168
ts/menu_ts.ts
168
ts/menu_ts.ts
@@ -37,6 +37,7 @@ class MainMenuItem extends MainMenu {
|
||||
var item = document.createElement("LI")
|
||||
item.setAttribute("onclick", "javascript: openThisMenu(this)")
|
||||
item.setAttribute("id", this.id)
|
||||
item.setAttribute("data-menu", this.menuKey)
|
||||
var img = this.createIMG(this.imgSrc)
|
||||
var value = this.createValue(this.value)
|
||||
|
||||
@@ -683,7 +684,7 @@ class ShowContent extends Content {
|
||||
input.setAttribute("id", "searchMapping")
|
||||
input.setAttribute("placeholder", "{{.button.search}}")
|
||||
input.className = "search"
|
||||
input.setAttribute("onchange", 'javascript: searchInMapping()')
|
||||
input.setAttribute("oninput", 'javascript: searchInMapping()')
|
||||
interaction.appendChild(input)
|
||||
break;
|
||||
|
||||
@@ -824,12 +825,157 @@ 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)
|
||||
if (activeItem != null) {
|
||||
activeItem.classList.add("menu-active")
|
||||
}
|
||||
}
|
||||
|
||||
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 PageReady() {
|
||||
|
||||
initShellLayout()
|
||||
|
||||
var server:Server = new Server("getServerConfig")
|
||||
server.request(new Object())
|
||||
|
||||
window.addEventListener("resize", function(){
|
||||
if (window.innerWidth > 900) {
|
||||
setLayoutMenuState(false)
|
||||
}
|
||||
calculateWrapperHeight();
|
||||
}, true);
|
||||
|
||||
@@ -853,6 +999,7 @@ function createLayout() {
|
||||
}
|
||||
|
||||
}
|
||||
renderStatusCards()
|
||||
|
||||
if (!document.getElementById("main-menu")) {
|
||||
return
|
||||
@@ -889,13 +1036,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
|
||||
}
|
||||
|
||||
function openThisMenu(element) {
|
||||
var id = element.id
|
||||
var content:ShowContent = new ShowContent(id)
|
||||
setActiveMenu(id)
|
||||
content.show()
|
||||
closeLayoutMenuIfMobile()
|
||||
calculateWrapperHeight()
|
||||
|
||||
return
|
||||
|
||||
@@ -18,6 +18,7 @@ class Server {
|
||||
if (this.cmd != "updateLog") {
|
||||
showElement("loading", true)
|
||||
UNDO = new Object()
|
||||
setConnectionState("busy")
|
||||
}
|
||||
|
||||
switch(window.location.protocol) {
|
||||
@@ -36,6 +37,9 @@ class Server {
|
||||
ws.onopen = function() {
|
||||
|
||||
WS_AVAILABLE = true
|
||||
if (data["cmd"] != "updateLog") {
|
||||
setConnectionState("busy")
|
||||
}
|
||||
|
||||
console.log("REQUEST (JS):");
|
||||
console.log(data)
|
||||
@@ -51,6 +55,7 @@ class Server {
|
||||
|
||||
console.log("No websocket connection to xTeVe could be established. Check your network configuration.")
|
||||
SERVER_CONNECTION = false
|
||||
setConnectionState("offline")
|
||||
|
||||
if (WS_AVAILABLE == false) {
|
||||
alert("No websocket connection to xTeVe could be established. Check your network configuration.")
|
||||
@@ -63,6 +68,9 @@ class Server {
|
||||
|
||||
SERVER_CONNECTION = false
|
||||
showElement("loading", false)
|
||||
if (data["cmd"] != "updateLog") {
|
||||
setConnectionState("online")
|
||||
}
|
||||
|
||||
console.log("RESPONSE:");
|
||||
var response = JSON.parse(e.data);
|
||||
@@ -74,6 +82,7 @@ class Server {
|
||||
}
|
||||
|
||||
if (response["status"] == false) {
|
||||
setConnectionState("offline")
|
||||
|
||||
alert(response["err"])
|
||||
|
||||
@@ -144,4 +153,4 @@ function getCookie(name) {
|
||||
var value = "; " + document.cookie;
|
||||
var parts = value.split("; " + name + "=");
|
||||
if (parts.length == 2) return parts.pop().split(";").shift();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user