Cleanup: Helfer-Altlasten aus main entfernen

24 Files geloescht, die nicht zur aktiven Codebasis gehoeren:
- 3 konkurrierende Renderer (renderer.py/2/3.py) - wir nutzen nur dashboard.py
- 4 alternative Admin-Templates (design_a/b/c, preview_all) - nur index.html aktiv
- 9 alte Test-Skripte (test_gradient, test_grid_v2, etc.)
- 5 alte Boot-Skripte (start_admin.sh, install_*.sh, etc.) - durch systemd-units ersetzt
- 2 Misc (50-allow-networkmanager.rules, waveshare_epd_init.py, idx.html, socktest.py)

Aktive Codebasis (was bleibt):
- admin.py, dashboard.py, network_watchdog.py, palette.py, layout.py
- plugins/{base,clock,hello,system,weather,minimax,spotify,strava,gmail}.py
- templates/index.html
- config.example.json, .env.example, README.md, RECOVERY.md
- systemd service units + deploy_dnd.sh

Co-Authored-By: Hermes <noreply@hermes.local>
This commit is contained in:
epaper-dashboard
2026-08-26 22:13:47 +04:00
co-authored by Hermes
parent 6b604b0289
commit 81e46a2c60
24 changed files with 0 additions and 3458 deletions
-9
View File
@@ -1,9 +0,0 @@
// Erlaubt koptikp (uid 1000) NetworkManager ohne Passwort zu steuern.
// Wir prüfen explizit auf uid, weil subject.local/active für SSH-Sessions
// oft nicht das tut, was man erwartet (logind behandelt SSH-Logins anders).
polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.NetworkManager.") === 0 &&
subject.user === "koptikp") {
return polkit.Result.YES;
}
});
-1042
View File
File diff suppressed because one or more lines are too long
-31
View File
@@ -1,31 +0,0 @@
#!/bin/bash
# Erlaubt dem koptikp-User NetworkManager ohne Passwort zu steuern.
# Wird via polkit ausgewertet.
set -e
RULE='polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.NetworkManager.") === 0 &&
subject.local === true && subject.active === true) {
return polkit.Result.YES;
}
});'
RULE_FILE="/etc/polkit-1/rules.d/50-allow-networkmanager.rules"
echo "Schreibe $RULE_FILE..."
sudo -n bash -c "cat > $RULE_FILE <<'RULEEOF'
$POLKIT_RULE
RULEEOF
chmod 644 $RULE_FILE"
echo "--- Inhalt: ---"
cat $RULE_FILE
echo
echo "--- Test: nmcli hotspot (kein pw mehr nötig) ---"
sudo -n nmcli device wifi hotspot ifname wlan0 ssid epaper-test password test1234
sleep 3
nmcli -t -f NAME,TYPE,DEVICE connection show --active
echo
echo "--- Cleanup ---"
sudo -n nmcli connection down epaper-test 2>&1
sudo -n nmcli connection delete epaper-test 2>&1
echo "Fertig."
-36
View File
@@ -1,36 +0,0 @@
#!/bin/bash
# Installiert die systemd-Services fuer das ePaper-Dashboard.
set -e
SERVICE_DIR=/etc/systemd/system
SERVICE_SRC=/home/koptikp/epaper-dashboard/port
echo "=== installiere service units ==="
sudo -n install -m 644 "$SERVICE_SRC/epaper-dashboard.service" "$SERVICE_DIR/epaper-dashboard.service"
sudo -n install -m 644 "$SERVICE_SRC/epaper-admin.service" "$SERVICE_DIR/epaper-admin.service"
echo
echo "=== stoppe alte Prozesse (manuell gestartet) ==="
pkill -f "python3.*admin.py" 2>/dev/null || true
pkill -f "python3.*dashboard.py" 2>/dev/null || true
sleep 2
echo "=== systemctl reload + enable + restart ==="
sudo -n systemctl daemon-reload
sudo -n systemctl enable epaper-dashboard.service
sudo -n systemctl enable epaper-admin.service
sudo -n systemctl restart epaper-dashboard.service
sudo -n systemctl restart epaper-admin.service
sleep 3
echo
echo "=== status dashboard ==="
sudo -n systemctl is-active epaper-dashboard.service
sudo -n systemctl is-active epaper-admin.service
echo
echo "=== ports ==="
ss -ltn | grep -E "8080" | head -3
echo
echo "Logs: journalctl -u epaper-dashboard -f"
echo " journalctl -u epaper-admin -f"
-8
View File
@@ -1,8 +0,0 @@
import sys
sys.path.insert(0, ".")
import dashboard
d = dashboard.Dashboard()
img = d.render_once()
img.save("/tmp/render_test.png")
print("rendered:", img.size)
print("widgets:", [(w.name if w else None) for w in d.widgets])
-502
View File
@@ -1,502 +0,0 @@
"""Render-Pipeline für das Dashboard.
Unterstützte Display-Designs:
classic — Original flach, weiß, kein UI-Chrome (DEFAULT)
minimal — Schwarz/Weiß, bold, clean, viel Weißraum
minimal_color — Minimal + dezenter Akzent-Strich pro Kategorie
bold — Riesen-Typography, 1px-Rahmen, vollflächig
analog — 2×2-Uhr als Ziffernblatt, Rest clean Panels
magazine — Bold Typography, farbige Akzentstreifen links
cards — iOS-Widget-Style, Emoji-Icons, warme Paneele
kiosk — Dunkel, Daten-first, kompakte Info-Zellen
wireframe — Blueprint-Style, gestrichelte Rahmen, keine Füllungen
bauhaus — Geometrische Primärfarben, blockweise
poster — Farbige Flächen, bold, fast nur Content
retro_lcd — Alte LCD-Uhren-Ästhetik, grüner Hintergrund
glossy — Moderner iOS-Style, Emoji-Icons, Panels
data_board — Kontrollraum-Dashboard, dunkel, invertierte Karten
Aufruf:
img = render_design(items, widgets, fonts, design="classic")
"""
from PIL import Image, ImageDraw, ImageFont
from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT
import math
DISPLAY_W = 800
DISPLAY_H = 480
COLS, ROWS = 4, 4
CELL_W = DISPLAY_W // COLS
CELL_H = DISPLAY_H // ROWS
def render_design(items, widgets, fonts, design="classic") -> Image.Image:
fn = {
"magazine": _render_magazine,
"cards": _render_cards,
"kiosk": _render_kiosk,
"wireframe": _render_wireframe,
"bauhaus": _render_bauhaus,
"poster": _render_poster,
"retro_lcd": _render_retro_lcd,
"glossy": _render_glossy,
"data_board": _render_data_board,
"minimal": _render_minimal,
"minimal_color": _render_minimal_color,
"bold": _render_bold,
"analog": _render_analog_clock,
"classic": _render_classic,
}.get(design, _render_classic)
return fn(items, widgets, fonts)
# ============================================================================
# MAGAZINE — Bold Typography, Minimalist
# Idee: Wanduhr-Magazin / Bloomberg-Terminal-Ästhetik
# Schwarz/Weiß + eine Akzentfarbe pro Widget
# ============================================================================
def _render_magazine(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _pixels(item)
if widget is None:
continue
# Jedes Widget bekommt einen vertikalen Akzent-Streifen links
accent = _widget_accent(widget.name)
draw.rectangle((x, y, x + 6, y + h - 1), fill=accent)
# Widget-Name oben links, klein
label = (widget.label or widget.name or "").upper()
draw.text((x + 14, y + 6), label, font=fonts.get("16", fonts.get("default")), fill=(160, 160, 160))
# Horizontale Linie unter Label
draw.line((x + 14, y + 26, x + w - 10, y + 26), fill=(220, 220, 220), width=1)
# Content-Bereich (ab y+32)
cy = y + 36
ch = h - 42
widget.render(draw, fonts, x + 14, cy, w - 20, ch)
return img
# ============================================================================
# CARDS — Background Panels + Emoji Icons + Vertical Accent Bars
# Idee: iOS Widgets / Google Smart Display
# Leicht grauer Hintergrund pro Karte, Emoji für Kategorie
# ============================================================================
def _render_cards(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 244, 240)) # warm white
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _pixels(item)
if widget is None:
continue
# Card background
draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), fill=WHITE)
# Top accent bar
accent = _widget_accent(widget.name)
draw.rectangle((x + 2, y + 2, x + w - 3, y + 10), fill=accent)
# Emoji icon (use emoji renderer via fallback)
icon = _widget_icon(widget.name)
_draw_emoji(draw, icon, x + 10, y + 18, 28)
# Label
label = widget.label or widget.name or ""
draw.text((x + 44, y + 18), label, font=fonts.get("16", fonts.get("default")), fill=(80, 80, 80))
# Separator line
draw.line((x + 8, y + 52, x + w - 8, y + 52), fill=(230, 230, 230), width=1)
# Content
widget.render(draw, fonts, x + 8, y + 58, w - 16, h - 64)
return img
# ============================================================================
# KIOSK — Data-Dense, Compact, Multi-Zone
# Idee: Control-Room / LoRa-Tracker / Science-Dashboard
# Viele kleine Info-Zellen, horizontale Teilung, monochrome Palette
# ============================================================================
def _render_kiosk(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BLACK)
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _pixels(item)
if widget is None:
continue
# Invertierte Box mit Akzent-Rand
draw.rectangle((x, y, x + w - 1, y + h - 1), fill=WHITE)
accent = _widget_accent(widget.name)
draw.rectangle((x, y, x + w - 1, y + 3), fill=accent)
# Label in Akzentfarbe oben
label = (widget.label or widget.name or "").upper()
draw.text((x + 8, y + 8), label, font=fonts.get("14", fonts.get("default")), fill=accent)
# Content
widget.render(draw, fonts, x + 6, y + 28, w - 12, h - 34)
return img
# ============================================================================
# Helper
# ============================================================================
def _pixels(item):
"""Item (x,y,w,h) in Pixel (800x480, 4x4 grid)"""
COLS, ROWS = 4, 4
CELL_W = DISPLAY_W // COLS
CELL_H = DISPLAY_H // ROWS
return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H
# Alias für neue Designs
def _px(item):
return _pixels(item)
def _widget_accent(name):
accents = {
"clock": BLUE,
"weather": GREEN,
"system": ORANGE,
"hello": RED,
"spotify": (0, 200, 0), # spotify green
"strava": ORANGE,
"gmail": RED,
"minimax": (180, 100, 255), # purple
}
return accents.get(name, ORANGE)
def _widget_icon(name):
icons = {
"clock": "",
"weather": "🌤️",
"system": "💻",
"hello": "👋",
"spotify": "🎵",
"strava": "🚴",
"gmail": "📧",
"minimax": "🤖",
}
return icons.get(name, "📟")
def _draw_emoji(draw, emoji, x, y, size):
"""Draw emoji by using a fallback ImageFont trick with the emoji char."""
# On systems without an emoji font, this just draws a rectangle placeholder
# The real implementation would need a font with emoji support
# For now draw a colored square as placeholder
colors = {
"": (80, 160, 255),
"🌤️": (255, 200, 80),
"💻": (100, 100, 100),
"👋": (255, 160, 120),
"🎵": (0, 200, 100),
"🚴": (255, 140, 0),
"📧": (220, 80, 80),
"🤖": (180, 100, 255),
}
c = colors.get(emoji, (200, 200, 200))
draw.ellipse((x, y, x + size, y + size), fill=c)
# ============================================================================
# DESIGN 5: WIREFRAME
# Technisches Plan-Drawing. Blaue Linien, weiße Flächen.
# Blueprint-Ästhetik.
# ============================================================================
def _render_wireframe(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (240, 248, 255))
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), fill=WHITE)
draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=BLUE, width=1)
draw.rectangle((x + 2, y + 2, x + w - 3, y + 10), fill=BLUE)
label = (widget.label or widget.name or "").upper()
draw.text((x + 10, y + 14), label, font=fonts.get("14", fonts.get("default")), fill=BLUE)
for row in range(item.y, item.y + item.h):
for col in range(item.x, item.x + item.w):
if (row + col) % 2 == 0:
cx = col * CELL_W + 4
cy = row * CELL_H + 4
cw = CELL_W - 6
ch = CELL_H - 6
draw.rectangle((cx, cy, cx + cw, cy + ch), fill=(240, 248, 255))
widget.render(draw, fonts, x + 6, y + 32, w - 12, h - 38)
return img
# ============================================================================
# DESIGN 6: BAUHAUS
# Geometrische Primärfarben. Jedes Widget bekommt eine der
# 7 ACeP-Farben als Hintergrund-Balken links.
# ============================================================================
def _render_bauhaus(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240))
draw = ImageDraw.Draw(img)
PALETTE_7 = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 100, 200)]
for i, (item, widget) in enumerate(zip(items, widgets)):
x, y, w, h = _px(item)
if widget is None:
continue
color = PALETTE_7[i % len(PALETTE_7)]
block_w = w // 3
draw.rectangle((x + 2, y + 2, x + block_w - 2, y + h - 3), fill=color)
draw.rectangle((x + block_w, y + 2, x + w - 3, y + h - 3), fill=WHITE)
draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=FG, width=2)
label = (widget.label or widget.name or "").upper()
draw.text((x + 6, y + 8), label, font=fonts.get("14", fonts.get("default")), fill=WHITE)
widget.render(draw, fonts, x + block_w + 4, y + 4, w - block_w - 8, h - 8)
return img
# ============================================================================
# DESIGN 7: POSTER
# Farbige Flächen, bold, fast nur Content.
# Jedes Widget in einer kräftigen Farbe.
# ============================================================================
def _render_poster(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BLACK)
draw = ImageDraw.Draw(img)
colors = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 100, 200)]
for i, (item, widget) in enumerate(zip(items, widgets)):
x, y, w, h = _px(item)
if widget is None:
continue
c = colors[i % len(colors)]
draw.rectangle((x, y, x + w - 1, y + h - 1), fill=c)
draw.rectangle((x, y, x + w - 1, y + h - 1), outline=WHITE, width=3)
label = (widget.label or widget.name or "").upper()
draw.text((x + 8, y + 6), label[:18], font=fonts.get("12", fonts.get("default")), fill=WHITE)
widget.render(draw, fonts, x + 6, y + 24, w - 12, h - 30)
return img
# ============================================================================
# DESIGN 8: RETRO LCD
# Alte LCD-Uhren-Ästhetik. Dunkelgrüner Hintergrund,
# leuchtende grüne Ziffern, Fake-3D-Rahmen.
# ============================================================================
def _render_retro_lcd(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (0, 20, 0))
draw = ImageDraw.Draw(img)
LCD_GREEN = (0, 255, 120)
LCD_DARK = (0, 60, 30)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
draw.rectangle((x + 3, y + 3, x + w - 4, y + h - 4), fill=LCD_DARK)
draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=LCD_GREEN, width=2)
label = (widget.label or widget.name or "").upper()
draw.text((x + 8, y + 6), label[:16], font=fonts.get("11", fonts.get("default")), fill=LCD_GREEN)
draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=LCD_GREEN, width=1)
widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 32)
return img
# ============================================================================
# DESIGN 9: GLOSSY
# Moderne iOS-Widget-Ästhetik. Weiße Karten, Glossy-Header
# in Accent-Farbe, dezente Schatten-Rahmen.
# ============================================================================
def _render_glossy(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (220, 228, 235))
draw = ImageDraw.Draw(img)
WIDGET_ACCENTS = {
"clock": (80, 160, 255),
"weather": (60, 200, 100),
"system": (255, 160, 40),
"hello": (200, 80, 80),
"spotify": (0, 180, 80),
"strava": (255, 120, 0),
"gmail": (200, 60, 60),
"minimax": (160, 80, 255),
}
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
accent = WIDGET_ACCENTS.get(widget.name, (100, 100, 100))
draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE)
draw.rectangle((x + 2, y + 2, x + w - 2, y + 14), fill=accent)
# Glossy highlight
for i in range(6):
alpha = int(80 - i * 12)
c = tuple(min(255, accent[j] + alpha) for j in range(3))
draw.line((x + 2, y + 2 + i, x + w - 2, y + 2 + i), fill=c, width=1)
draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), outline=(200, 210, 220), width=1)
draw.rectangle((x + 3, y + 3, x + w - 3, y + h - 3), outline=(220, 228, 235), width=1)
draw.rectangle((x + 4, y + 4, x + w - 4, y + h - 4), outline=(180, 190, 200), width=1)
label = (widget.label or widget.name or "").title()
draw.text((x + 10, y + 18), label, font=fonts.get("14", fonts.get("default")), fill=accent)
draw.line((x + 8, y + 36, x + w - 8, y + 36), fill=(220, 220, 220), width=1)
widget.render(draw, fonts, x + 6, y + 40, w - 12, h - 46)
return img
# ============================================================================
# DESIGN 10: DATA BOARD
# Kontrollraum-Ästhetik. Winzige Zellen, viele Daten.
# Monochrome, Tick-Marks, Data-first.
# ============================================================================
def _render_data_board(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (10, 10, 15))
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
draw.rectangle((x, y, x + w - 1, y + h - 1), fill=WHITE, width=2)
draw.rectangle((x, y, x + w - 1, y + 22), fill=WHITE)
draw.text((x + 6, y + 4), (widget.label or widget.name or "").upper(),
font=fonts.get("12", fonts.get("default")), fill=BLACK)
for tx in range(x + 10, x + w - 10, 20):
draw.line((tx, y + 24, tx, y + 28), fill=(180, 180, 180), width=1)
widget.render(draw, fonts, x + 4, y + 30, w - 8, h - 34)
return img
# ============================================================================
# CLASSIC — Original flaches Design
# Weißer Hintergrund, kein UI-Chrome, widget füllt Zelle aus
# Das was auf deinem Referenz-Bild zu sehen ist
# ============================================================================
def _render_classic(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
widget.render(draw, fonts, x + 8, y + 8, w - 16, h - 16)
return img
# ============================================================================
# MINIMAL — das Referenz-Design
# Schwarz/Weiß, bold, clean, viel Weißraum. Kein UI-Chrome.
# ============================================================================
def _render_minimal(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
widget.render(draw, fonts, x + 12, y + 12, w - 24, h - 24)
return img
# ============================================================================
# MINIMAL COLOR — Minimal mit dezenter Akzentfarbe pro Kategorie
# Kleiner Farbbalken oben links, sonst weiß + schwarz
# ============================================================================
def _render_minimal_color(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
CATEGORY_COLORS = {
"clock": (60, 80, 200),
"weather": (50, 160, 80),
"system": (200, 120, 30),
"hello": (180, 60, 60),
"spotify": (0, 160, 80),
"strava": (230, 110, 20),
"gmail": (200, 60, 60),
"minimax": (140, 80, 240),
}
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
color = CATEGORY_COLORS.get(widget.name, FG)
draw.rectangle((x + 12, y + 10, x + 16, y + 18), fill=color)
label = (widget.label or widget.name or "").upper()
draw.text((x + 22, y + 8), label, font=fonts.get("11", fonts.get("default")), fill=(160, 160, 160))
widget.render(draw, fonts, x + 12, y + 24, w - 24, h - 36)
return img
# ============================================================================
# BOLD — Riesen-Typography
# Zahlen und Text füllen die Zelle komplett.
# Kaum Weißraum, maximaler Informationsdichte.
# ============================================================================
def _render_bold(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=BLACK, width=1)
widget.render(draw, fonts, x + 6, y + 6, w - 12, h - 12)
return img
# ============================================================================
# ANALOG CLOCK — Die Uhr als Mittelpunkt
# 2x2-Clock-Zellen zeigen ein analoges Ziffernblatt.
# Übrige Zellen: cleanes Panel mit Monospace.
# ============================================================================
def _render_analog_clock(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240))
draw = ImageDraw.Draw(img)
import math
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
is_2x2 = item.w >= 2 and item.h >= 2
is_clock = widget.name == "clock" and is_2x2
if is_clock:
cx, cy = x + w // 2, y + h // 2
r = min(w, h) // 2 - 16
draw.ellipse((cx - r, cy - r, cx + r, cy + r), fill=WHITE, outline=BLACK, width=2)
for i in range(12):
angle = (i / 12) * 2 * math.pi - math.pi / 2
mx = cx + int((r - 12) * math.cos(angle))
my = cy + int((r - 12) * math.sin(angle))
draw.ellipse((mx - 3, my - 3, mx + 3, my + 3), fill=BLACK)
from datetime import datetime
now = datetime.now()
hour_angle = ((now.hour % 12) / 12) * 2 * math.pi - math.pi / 2
min_angle = (now.minute / 60) * 2 * math.pi - math.pi / 2
hx = cx + int(r * 0.5 * math.cos(hour_angle))
hy = cy + int(r * 0.5 * math.sin(hour_angle))
draw.line((cx, cy, hx, hy), fill=BLACK, width=4)
mx = cx + int(r * 0.75 * math.cos(min_angle))
my = cy + int(r * 0.75 * math.sin(min_angle))
draw.line((cx, cy, mx, my), fill=BLACK, width=2)
draw.ellipse((cx - 4, cy - 4, cx + 4, cy + 4), fill=BLACK)
draw.text((cx - 40, cy + r + 8), now.strftime("%H:%M"),
font=fonts.get("20", fonts.get("default")), fill=BLACK)
else:
draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE)
draw.ellipse((x + 8, y + 8, x + 14, y + 14), fill=(60, 100, 200))
label = (widget.label or widget.name or "").upper()[:12]
draw.text((x + 20, y + 6), label, font=fonts.get("12", fonts.get("default")), fill=(100, 100, 100))
draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=(200, 200, 200), width=1)
widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 34)
return img
-272
View File
@@ -1,272 +0,0 @@
"""Neue Display-Designs für ACeP 7-Farben e-Paper.
Jedes Design definiert:
- Hintergrund / Rahmen
- Widget-Rahmen ( Frames)
- content_fn: wie das Widget gerendert wird
"""
from PIL import Image, ImageDraw
from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT
import math
DISPLAY_W = 800
DISPLAY_H = 480
COLS, ROWS = 4, 4
CELL_W = DISPLAY_W // COLS
CELL_H = DISPLAY_H // ROWS
# ============================================================================
# DESIGN 5: WIREFRAME
# Technisches Plan-Drawing. Blaue Linien, weiße Flächen,
# schmale weiße Zellenrahmen. Blueprint-Ästhetik.
# ============================================================================
def render_wireframe(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (240, 248, 255)) # aliceblue
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
# Weißer Hintergrund pro Zelle
draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), fill=WHITE)
# Dünner Rahmen (blau)
draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=BLUE, width=1)
# Obere Akzent-Linie (breit)
draw.rectangle((x + 2, y + 2, x + w - 3, y + 10), fill=BLUE)
# Widget-Name oben links
label = (widget.label or widget.name or "").upper()
draw.text((x + 10, y + 14), label, font=fonts.get("14", fonts.get("default")), fill=BLUE)
# Grid-Checkerboard im Hintergrund (subtle)
for row in range(item.y, item.y + item.h):
for col in range(item.x, item.x + item.w):
if (row + col) % 2 == 0:
cx = col * CELL_W + 4
cy = row * CELL_H + 4
cw = CELL_W - 6
ch = CELL_H - 6
draw.rectangle((cx, cy, cx + cw, cy + ch), fill=(240, 248, 255))
# Content
widget.render(draw, fonts, x + 6, y + 32, w - 12, h - 38)
return img
# ============================================================================
# DESIGN 6: BAUHAUS
# Geometrische Primärfarben. Jedes Widget bekommt eine der
# 7 ACeP-Farben als Hintergrund-Balken links. Reine Formen.
# ============================================================================
def render_bauhaus(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) # warm gray
draw = ImageDraw.Draw(img)
PALETTE_7 = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 100, 200)]
item_colors = {}
for i, (item, widget) in enumerate(zip(items, widgets)):
x, y, w, h = _px(item)
if widget is None:
continue
color = PALETTE_7[i % len(PALETTE_7)]
item_colors[item.id] = color
# Großer farbiger Block links (30% width)
block_w = w // 3
draw.rectangle((x + 2, y + 2, x + block_w - 2, y + h - 3), fill=color)
# Rest weiß
draw.rectangle((x + block_w, y + 2, x + w - 3, y + h - 3), fill=WHITE)
# Rahmen
draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=FG, width=2)
# Widget-Name in der farbigen Fläche
label = (widget.label or widget.name or "").upper()
draw.text((x + 6, y + 8), label, font=fonts.get("14", fonts.get("default")), fill=WHITE)
# Content (rechte Seite)
widget.render(draw, fonts, x + block_w + 4, y + 4, w - block_w - 8, h - 8)
return img
# ============================================================================
# DESIGN 7: POSTER
# Überdimensionierte Typography. Jedes Widget großflächig.
# Widget-Hintergrund = monochrome Farbe aus Palette.
# Fast nur Text, kein UI-Chrome.
# ============================================================================
def render_poster(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BLACK)
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
# Zufällige kräftige Farbe pro widget (basierend auf name hash)
c = _hash_color(widget.name)
draw.rectangle((x, y, x + w - 1, y + h - 1), fill=c)
# Weißer Rahmen drum
draw.rectangle((x, y, x + w - 1, y + h - 1), outline=WHITE, width=3)
# Label oben Winzig
label = (widget.label or widget.name or "").upper()
draw.text((x + 8, y + 6), label[:18], font=fonts.get("12", fonts.get("default")), fill=WHITE)
# Content — das Widget bekommt 90% der Fläche
# Wir übergeben ein invertiertes draw-Objekt (fg=WHITE, bg=c)
widget.render(draw, fonts, x + 6, y + 24, w - 12, h - 30)
return img
# ============================================================================
# DESIGN 8: RETRO LCD
# Alte LCD/Uhren-Ästhetik. Schwarzer Hintergrund, Ziffern in
# satten UnOld Green (LCD-STYLE). Rahmen mit abgerundeten Ecken,
# die wie eingeritzte Rillen aussehen.
# ============================================================================
def render_retro_lcd(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (0, 20, 0)) # dunkles LCD-grün
draw = ImageDraw.Draw(img)
LCD_GREEN = (0, 255, 120)
LCD_DARK = (0, 60, 30)
LCD_BG = (0, 30, 15)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
# LCD-Zelle: dunkler als Hintergrund
draw.rectangle((x + 3, y + 3, x + w - 4, y + h - 4), fill=LCD_DARK)
# Rahmen mit Fake-3D (oben/links hell, unten/rechts dunkel)
draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=LCD_GREEN, width=2)
# Label in kleiner Schrift oben
label = (widget.label or widget.name or "").upper()
draw.text((x + 8, y + 6), label[:16], font=fonts.get("11", fonts.get("default")), fill=LCD_GREEN)
# Separator-Linie
draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=LCD_GREEN, width=1)
# Content
widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 32)
return img
# ============================================================================
# DESIGN 9: GLOSSY
# Moderner Glossy-Style. Weiße Karten mit leichtem Farbverlauf oben,
# ikonischer Rand, dezente Schatten durch mehrfache Ränder.
# ============================================================================
def render_glossy(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (220, 228, 235)) # cool gray
draw = ImageDraw.Draw(img)
WIDGET_ACCENTS = {
"clock": (80, 160, 255),
"weather": (60, 200, 100),
"system": (255, 160, 40),
"hello": (200, 80, 80),
"spotify": (0, 180, 80),
"strava": (255, 120, 0),
"gmail": (200, 60, 60),
"minimax": (160, 80, 255),
}
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
accent = WIDGET_ACCENTS.get(widget.name, (100, 100, 100))
# Karte weiß
draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE)
# Glossy-Effekt: heller Balken oben
draw.rectangle((x + 2, y + 2, x + w - 2, y + 14), fill=accent)
# Weißer Gradient-Overlay oben
for i in range(6):
alpha = int(80 - i * 12)
c = tuple(min(255, accent[j] + alpha) for j in range(3))
draw.line((x + 2, y + 2 + i, x + w - 2, y + 2 + i), fill=c, width=1)
# Rahmeneffekt: 3-lagig für Schatten
draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), outline=(200, 210, 220), width=1)
draw.rectangle((x + 3, y + 3, x + w - 3, y + h - 3), outline=(220, 228, 235), width=1)
draw.rectangle((x + 4, y + 4, x + w - 4, y + h - 4), outline=(180, 190, 200), width=1)
# Label in Accent-Farbe
label = (widget.label or widget.name or "").title()
draw.text((x + 10, y + 18), label, font=fonts.get("14", fonts.get("default")), fill=accent)
# Separator
draw.line((x + 8, y + 36, x + w - 8, y + 36), fill=(220, 220, 220), width=1)
# Content
widget.render(draw, fonts, x + 6, y + 40, w - 12, h - 46)
return img
# ============================================================================
# DESIGN 10: DATA BOARD
# Kontrollraum-Ästhetik. Winzige Zellen, viele Daten.
# Horizontale Balken, Tick-Marks, monochrome Palette.
#===========================================================================
def render_data_board(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (10, 10, 15))
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
# Weißer Rand
draw.rectangle((x, y, x + w - 1, y + h - 1), fill=WHITE, width=2)
# Header-Balken (invertiert)
draw.rectangle((x, y, x + w - 1, y + 22), fill=WHITE)
draw.text((x + 6, y + 4), (widget.label or widget.name or "").upper(),
font=fonts.get("12", fonts.get("default")), fill=BLACK)
# Kleine Tick-Marks oben (dekorativ)
for tx in range(x + 10, x + w - 10, 20):
draw.line((tx, y + 24, tx, y + 28), fill=(180, 180, 180), width=1)
# Content
widget.render(draw, fonts, x + 4, y + 30, w - 8, h - 34)
return img
# ============================================================================
# Helper
# ============================================================================
def _px(item):
return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H
def _hash_color(name: str):
"""Consistent color from name using simple hash."""
colors = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 0, 200)]
h = sum(ord(c) for c in name)
return colors[h % len(colors)]
-203
View File
@@ -1,203 +0,0 @@
"""Minimal Display-Designs für ACeP 7-Farben.
Inspiration von loiccoyle/tinyticker + inkflow-eink:
- Weißer Hintergrund, schwarze Geometrie
- Bold Typography, große Zahlen
- SVG-style rendering (saubere Linien, einheitliche Strichstärke)
- Dezente Akzentfarbe pro Widget-Kategorie
"""
from PIL import Image, ImageDraw
from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT
DISPLAY_W = 800
DISPLAY_H = 480
COLS, ROWS = 4, 4
CELL_W = DISPLAY_W // COLS
CELL_H = DISPLAY_H // ROWS
# Einheitliche Strichstärke
STROKE = 2 # px für alle Linien
SMALL_STROKE = 1
def render_design(items, widgets, fonts, design="minimal") -> Image.Image:
fn = {
"minimal": _render_minimal,
"minimal_color": _render_minimal_color,
"bold": _render_bold,
"analog": _render_analog_clock,
}.get(design, _render_minimal)
return fn(items, widgets, fonts)
# ============================================================================
# MINIMAL — das Referenz-Design
# Schwarz/Weiß, bold, clean, viel Weißraum
# ============================================================================
def _render_minimal(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
# Keine sichtbaren Grid-Linien — jedes Widget steht für sich
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
# Kein Rahmen, kein Hintergrund — reiner Content auf Weiß
# Content füllt die ganze Zelle
widget.render(draw, fonts, x + 12, y + 12, w - 24, h - 24)
return img
# ============================================================================
# MINIMAL COLOR — Minimal mit dezenter Akzentfarbe pro Kategorie
# Kleiner Farbbalken oben links, sonst weiß + schwarz
# ============================================================================
def _render_minimal_color(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
CATEGORY_COLORS = {
"clock": (60, 80, 200), # blau
"weather": (50, 160, 80), # grün
"system": (200, 120, 30), # orange
"hello": (180, 60, 60), # rot
"spotify": (0, 160, 80), # spotify-grün
"strava": (230, 110, 20), # strava-orange
"gmail": (200, 60, 60), # gmail-rot
"minimax": (140, 80, 240), # lila
}
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
color = CATEGORY_COLORS.get(widget.name, FG)
# Winziger Akzent-Strich oben (8px hoch, 3px breit)
draw.rectangle((x + 12, y + 10, x + 16, y + 18), fill=color)
# Widget-Name winzig darüber
label = (widget.label or widget.name or "").upper()
draw.text((x + 22, y + 8), label, font=fonts.get("11", fonts.get("default")), fill=(160, 160, 160))
# Content
widget.render(draw, fonts, x + 12, y + 24, w - 24, h - 36)
return img
# ============================================================================
# BOLD — Riesen-Typography
# Zahlen und Text füllen die Zelle komplett.
# Kaum Weißraum, maximaler Informationsdichte.
# ============================================================================
def _render_bold(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE)
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
# Weißer Hintergrund, schwarzer Rahmen (1px)
draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=BLACK, width=1)
# Content — groß
widget.render(draw, fonts, x + 6, y + 6, w - 12, h - 12)
return img
# ============================================================================
# ANALOG CLOCK — Die Uhr als Mittelpunkt
# Mittlere Zellen (2x2) zeigen die Uhr als analoges Ziffernblatt.
# Kleine Zellen (1x1) zeigen minimalistische Icons.
# Übrige Zellen: Daten-Widget mit Monospace-Text.
# ============================================================================
def _render_analog_clock(items, widgets, fonts) -> Image.Image:
img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) # warm white
draw = ImageDraw.Draw(img)
for item, widget in zip(items, widgets):
x, y, w, h = _px(item)
if widget is None:
continue
is_2x2 = item.w >= 2 and item.h >= 2
is_clock = widget.name == "clock" and is_2x2
if is_clock:
# 2x2 Clock → Analoges Ziffernblatt
_draw_analog_clock(draw, x, y, w, h, fonts)
else:
# Andere Widgets: cleanes Panel
draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE)
# Kleiner farbiger Punkt oben links
color = (60, 100, 200)
draw.ellipse((x + 8, y + 8, x + 14, y + 14), fill=color)
# Label
label = (widget.label or widget.name or "").upper()[:12]
draw.text((x + 20, y + 6), label, font=fonts.get("12", fonts.get("default")), fill=(100, 100, 100))
# Separator
draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=(200, 200, 200), width=1)
# Content
widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 34)
return img
def _draw_analog_clock(draw, x, y, w, h, fonts):
"""Zeichne ein analoges Ziffernblatt."""
import math
cx, cy = x + w // 2, y + h // 2
r = min(w, h) // 2 - 16
# Ziffernblatt-Kreis
draw.ellipse((cx - r, cy - r, cx + r, cy + r), fill=WHITE, outline=BLACK, width=STROKE)
# Stunden-Markierungen (kleine punkte)
for i in range(12):
angle = (i / 12) * 2 * math.pi - math.pi / 2
mx = cx + int((r - 12) * math.cos(angle))
my = cy + int((r - 12) * math.sin(angle))
draw.ellipse((mx - 3, my - 3, mx + 3, my + 3), fill=BLACK)
# Zeiger
from datetime import datetime
now = datetime.now()
hour_angle = ((now.hour % 12) / 12) * 2 * math.pi - math.pi / 2
min_angle = (now.minute / 60) * 2 * math.pi - math.pi / 2
# Stundenzeiger
hx = cx + int(r * 0.5 * math.cos(hour_angle))
hy = cy + int(r * 0.5 * math.sin(hour_angle))
draw.line((cx, cy, hx, hy), fill=BLACK, width=4)
# Minutenzeiger
mx = cx + int(r * 0.75 * math.cos(min_angle))
my = cy + int(r * 0.75 * math.sin(min_angle))
draw.line((cx, cy, mx, my), fill=BLACK, width=2)
# Mittelpunkt
draw.ellipse((cx - 4, cy - 4, cx + 4, cy + 4), fill=BLACK)
# Digitale Zeit darunter
draw.text((cx - 40, cy + r + 8),
now.strftime("%H:%M"),
font=fonts.get("20", fonts.get("default")), fill=BLACK)
# ============================================================================
# Helper
# ============================================================================
def _px(item):
COLS, ROWS = 4, 4
CELL_W = DISPLAY_W // COLS
CELL_H = DISPLAY_H // ROWS
return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H
-14
View File
@@ -1,14 +0,0 @@
import os, socket
p = "/tmp/test.sock"
if os.path.exists(p):
os.remove(p)
srv = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
srv.bind(p)
srv.listen(1)
print("bound, listening")
print("exists immediately after bind:", os.path.exists(p))
finally:
srv.close()
if os.path.exists(p):
os.remove(p)
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
# startet admin.py im Hintergrund mit korrektem CWD
cd /home/koptikp/epaper-dashboard/port
pkill -f admin.py 2>/dev/null
sleep 1
nohup env EPAPER_ADMIN_PORT=8080 python3 admin.py > /tmp/admin.log 2>&1 &
echo "admin pid: $!"
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
chmod +x /home/koptikp/epaper-dashboard/port/start_admin.sh 2>/dev/null
# Sende das Script selbst
cat /home/koptikp/epaper-dashboard/port/start_admin.sh
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
cd /home/koptikp/epaper-dashboard/port
pkill -f dashboard.py 2>/dev/null
sleep 1
nohup env python3 dashboard.py > /tmp/dashboard.log 2>&1 &
echo "dashboard pid: $!"
sleep 2
echo "--- log preview ---"
head -20 /tmp/dashboard.log
-308
View File
@@ -1,308 +0,0 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>epaper admin — Dark Minimal</title>
<style>
/* ============================================================
DESIGN A: Dark Minimal
Inspiriert von: Linear, Vercel — fast whitespace, hauchdünne
Linien, viel Ruhe. Nur Akzentfarbe, keine Vignetten.
============================================================ */
:root {
--bg: #0a0a0e;
--surface: #111116;
--surface-2: #18181f;
--border: #1e1e28;
--border-light: #2a2a38;
--fg: #e8e8ed;
--fg-muted: #707080;
--fg-dim: #404050;
--accent: #7c85ff;
--ok: #4ade80;
--warn: #fbbf24;
--alert: #f87171;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--fg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* --- Header --- */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 28px 40px;
border-bottom: 1px solid var(--border);
}
.logo {
font-size: 0.85em;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--fg);
}
.logo span { color: var(--accent); }
.header-right { display: flex; gap: 12px; align-items: center; }
/* --- Main --- */
.main { max-width: 1200px; margin: 0 auto; padding: 32px 40px 80px; }
/* --- Section title --- */
h2 {
font-size: 0.72em;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--fg-muted);
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
/* --- Grid Editor --- */
.toolbar {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 20px;
}
.add-group {
display: flex;
gap: 6px;
align-items: center;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 4px 4px 4px 10px;
}
.add-group select, .add-group input {
background: transparent;
border: none;
color: var(--fg);
font-size: 0.85em;
padding: 4px 6px;
}
.add-group select:focus, .add-group input:focus { outline: none; }
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 100px);
gap: 8px;
background: var(--border);
padding: 8px;
border-radius: 8px;
margin-bottom: 24px;
}
.cell {
background: var(--surface);
border-radius: 4px;
overflow: hidden;
position: relative;
}
.item {
width: 100%;
height: 100%;
background: var(--surface-2);
border: 1px solid var(--border-light);
border-radius: 4px;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: grab;
}
.item .name { font-size: 0.8em; font-weight: 500; }
.item .meta { font-size: 0.65em; color: var(--fg-muted); font-family: monospace; }
.item .controls {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.15s;
position: absolute;
top: 4px;
right: 4px;
}
.item:hover .controls { opacity: 1; }
.item .controls button {
background: var(--surface);
border: 1px solid var(--border-light);
color: var(--fg-muted);
border-radius: 3px;
padding: 1px 5px;
font-size: 0.65em;
cursor: pointer;
}
/* --- Cards --- */
.plugin-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 16px 20px;
margin-bottom: 10px;
}
.plugin-card h3 {
font-size: 0.9em;
font-weight: 500;
margin-bottom: 4px;
}
.plugin-card .desc { font-size: 0.78em; color: var(--fg-muted); margin-bottom: 12px; }
.plugin-card .fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.field { display: flex; flex-direction: column; gap: 4px; }
.field label { font-size: 0.72em; color: var(--fg-muted); font-weight: 500; }
.field input, .field select {
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--fg);
border-radius: 4px;
padding: 6px 8px;
font-size: 0.85em;
}
/* --- Buttons --- */
.btn {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 7px 12px;
border-radius: 5px;
border: 1px solid var(--border-light);
background: var(--surface);
color: var(--fg);
font-size: 0.82em;
cursor: pointer;
font-family: inherit;
}
.btn:hover { background: var(--surface-2); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #000; }
.btn.primary:hover { background: #6a75f0; }
.btn.danger { color: var(--alert); border-color: var(--alert); }
/* --- Preview --- */
.preview {
margin-top: 40px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 16px;
}
.preview img { width: 100%; border-radius: 4px; }
/* --- Layout status --- */
.status {
font-size: 0.78em;
color: var(--fg-muted);
margin-left: auto;
font-family: monospace;
}
</style>
</head>
<body>
<header>
<div class="logo">epaper<span>_</span>dashboard</div>
<div class="header-right">
<button class="btn">Refresh</button>
<button class="btn primary">Save</button>
</div>
</header>
<div class="main">
<h2>Layout — 4×4 Grid</h2>
<div class="toolbar">
<div class="add-group">
<select><option>clock</option><option>weather</option><option>system</option></select>
<button class="btn primary">+ Add</button>
</div>
<button class="btn">Auto-Pack</button>
<span class="status">4 items · OK</span>
</div>
<div class="grid">
<div class="cell item">
<div>
<div class="name">Clock</div>
<div class="meta">2×2 · (0,0)</div>
</div>
<div class="controls">
<button></button><button></button><button></button>
</div>
</div>
<div class="cell item">
<div>
<div class="name">Weather</div>
<div class="meta">2×2 · (2,0)</div>
</div>
<div class="controls">
<button></button><button></button><button></button>
</div>
</div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell item">
<div>
<div class="name">System</div>
<div class="meta">2×2 · (0,2)</div>
</div>
</div>
<div class="cell item">
<div>
<div class="name">Hello</div>
<div class="meta">2×2 · (2,2)</div>
</div>
</div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
<div class="cell" style="background: var(--border);"></div>
</div>
<h2>Plugin Config</h2>
<div class="plugin-card">
<h3>Clock</h3>
<div class="desc">System time and date</div>
<div class="fields">
<div class="field">
<label>Timezone</label>
<input type="text" value="Europe/Berlin">
</div>
<div class="field">
<label>Format</label>
<select><option>24h</option><option>12h</option></select>
</div>
</div>
</div>
<div class="plugin-card">
<h3>Weather</h3>
<div class="desc">Open-Meteo — no API key needed</div>
<div class="fields">
<div class="field">
<label>Location (lat,lon)</label>
<input type="text" value="52.52,13.41">
</div>
<div class="field">
<label>Forecast hours</label>
<input type="number" value="4">
</div>
</div>
</div>
<h2>Live Preview</h2>
<div class="preview">
<img src="/snapshot.png">
</div>
</div>
</body>
</html>
-328
View File
@@ -1,328 +0,0 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>epaper admin — Retro Terminal</title>
<style>
/* ============================================================
DESIGN B: Retro Terminal
Inspiriert von: CRT-Monitor, lohn Kassensystem,
grüne/h Bernstein-Phosphor-Displays. Monospace-alles,
ASCII-Borders, pixelige Akzente.
============================================================ */
:root {
--bg: #0d0d00;
--surface: #141400;
--surface-2: #1a1a00;
--border: #2a2a00;
--border-light: #3a3a00;
--fg: #e8c840;
--fg-muted: #a09020;
--fg-dim: #605800;
--accent: #ffcc00;
--accent-dim: rgba(255, 204, 0, 0.15);
--ok: #88ff44;
--warn: #ffaa00;
--alert: #ff4444;
--scanline: rgba(0, 0, 0, 0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Courier New", Courier, monospace;
background: var(--bg);
color: var(--fg);
line-height: 1.5;
background-image: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
var(--scanline) 2px,
var(--scanline) 4px
);
}
/* --- Header --- */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border-bottom: 2px solid var(--fg-dim);
background: var(--surface);
}
.logo {
font-size: 1em;
font-weight: bold;
letter-spacing: 0.1em;
color: var(--accent);
}
.logo::before { content: '> '; }
.logo::after { content: ' _'; animation: blink 1s step-end infinite; }
@keyframes blink { 50% { opacity: 0; } }
.header-right { display: flex; gap: 10px; }
/* --- Main --- */
.main { max-width: 1100px; margin: 0 auto; padding: 24px; }
/* --- Section title --- */
h2 {
font-size: 0.8em;
font-weight: bold;
color: var(--accent);
margin-bottom: 12px;
padding-bottom: 6px;
border-bottom: 1px dashed var(--fg-dim);
letter-spacing: 0.05em;
}
h2::before { content: '## '; color: var(--fg-dim); }
/* --- Toolbar --- */
.toolbar {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 16px;
flex-wrap: wrap;
}
.add-group {
display: flex;
gap: 0;
border: 1px solid var(--fg-dim);
}
.add-group select {
background: var(--surface);
border: none;
color: var(--fg);
font-family: inherit;
font-size: 0.85em;
padding: 5px 10px;
}
.add-group button {
background: var(--accent);
color: #000;
border: none;
font-family: inherit;
font-size: 0.82em;
font-weight: bold;
padding: 5px 12px;
cursor: pointer;
}
/* --- ASCII Grid --- */
.grid-wrap {
border: 1px solid var(--fg-dim);
padding: 8px;
margin-bottom: 24px;
background: var(--surface);
overflow-x: auto;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 90px);
gap: 6px;
}
.cell {
background: var(--surface-2);
border: 1px solid var(--border-light);
border-radius: 0;
padding: 8px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
.cell.empty { background: var(--bg); border-style: dashed; }
.item-name {
font-size: 0.78em;
font-weight: bold;
color: var(--accent);
}
.item-meta { font-size: 0.62em; color: var(--fg-dim); }
.item-controls {
display: flex;
gap: 3px;
position: absolute;
top: 4px;
right: 4px;
}
.item-controls button {
background: var(--bg);
border: 1px solid var(--fg-dim);
color: var(--fg-dim);
font-family: inherit;
font-size: 0.6em;
padding: 0px 4px;
cursor: pointer;
}
.item-controls button:hover { color: var(--alert); border-color: var(--alert); }
/* --- Plugin cards --- */
.plugin-card {
border: 1px solid var(--border-light);
padding: 12px 16px;
margin-bottom: 8px;
background: var(--surface);
}
.plugin-card h3 {
font-size: 0.85em;
color: var(--accent);
margin-bottom: 2px;
}
.plugin-card .desc { font-size: 0.72em; color: var(--fg-muted); margin-bottom: 10px; }
.plugin-card .fields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.field { display: flex; flex-direction: column; gap: 3px; }
.field label { font-size: 0.68em; color: var(--fg-muted); }
.field input, .field select {
background: var(--bg);
border: 1px solid var(--fg-dim);
color: var(--fg);
font-family: inherit;
font-size: 0.82em;
padding: 4px 8px;
}
/* --- Buttons --- */
.btn {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 6px 12px;
border: 1px solid var(--fg-dim);
background: var(--surface);
color: var(--fg);
font-family: inherit;
font-size: 0.8em;
cursor: pointer;
}
.btn:hover { background: var(--surface-2); border-color: var(--fg-muted); }
.btn.primary { background: var(--accent); color: #000; border-color: var(--accent); font-weight: bold; }
.btn.primary:hover { background: #e0b800; }
.btn.danger { color: var(--alert); border-color: var(--alert); }
.btn-sm { padding: 3px 8px; font-size: 0.7em; }
/* --- Status bar --- */
.status-bar {
font-size: 0.72em;
color: var(--fg-muted);
margin-left: auto;
font-family: monospace;
}
/* --- Preview --- */
.preview {
border: 1px solid var(--fg-dim);
padding: 12px;
background: var(--surface);
margin-top: 24px;
}
.preview img { width: 100%; display: block; }
/* --- CRT glow effect on accent elements --- */
.accent-glow { text-shadow: 0 0 6px var(--accent); }
</style>
</head>
<body>
<header>
<div class="logo accent-glow">EPAPER ADMIN</div>
<div class="header-right">
<button class="btn">REFRESH</button>
<button class="btn primary">SAVE</button>
</div>
</header>
<div class="main">
<h2>LAYOUT EDITOR [4x4]</h2>
<div class="toolbar">
<div class="add-group">
<select><option>clock</option><option>weather</option><option>system</option></select>
<button>+ ADD</button>
</div>
<button class="btn">AUTO-PACK</button>
<span class="status-bar">STATUS: 4 ITEMS | GRID: OK</span>
</div>
<div class="grid-wrap">
<div class="grid">
<div class="cell">
<div class="item-name">CLOCK</div>
<div class="item-meta">2x2 @ (0,0)</div>
<div class="item-controls">
<button></button><button></button><button></button>
</div>
</div>
<div class="cell">
<div class="item-name">WEATHER</div>
<div class="item-meta">2x2 @ (2,0)</div>
<div class="item-controls">
<button></button>
</div>
</div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell">
<div class="item-name">SYSTEM</div>
<div class="item-meta">2x2 @ (0,2)</div>
<div class="item-controls">
<button></button>
</div>
</div>
<div class="cell">
<div class="item-name">HELLO</div>
<div class="item-meta">2x2 @ (2,2)</div>
<div class="item-controls">
<button></button>
</div>
</div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
<div class="cell empty"></div>
</div>
</div>
<h2>PLUGIN CONFIG</h2>
<div class="plugin-card">
<h3>[CLOCK]</h3>
<div class="desc">System time and date display</div>
<div class="fields">
<div class="field">
<label>> TIMEZONE</label>
<input type="text" value="Europe/Berlin">
</div>
<div class="field">
<label>> FORMAT</label>
<select><option>24h</option><option>12h</option></select>
</div>
</div>
</div>
<div class="plugin-card">
<h3>[WEATHER]</h3>
<div class="desc">Open-Meteo API — no key required</div>
<div class="fields">
<div class="field">
<label>> LOCATION</label>
<input type="text" value="52.52,13.41">
</div>
<div class="field">
<label>> FORECAST_H</label>
<input type="number" value="4">
</div>
</div>
</div>
<h2>LIVE PREVIEW</h2>
<div class="preview">
<img src="/snapshot.png">
</div>
</div>
</body>
</html>
-381
View File
@@ -1,381 +0,0 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>epaper admin — Warm Editorial</title>
<style>
/* ============================================================
DESIGN C: Warm Editorial
Inspiriert von: gedruckte Zeitungen, Magazine, Loom.
Warme Töne (Papier-Optik), Serif-Fonts für Labels,
viel Weißraum, dezente Kategorien.
============================================================ */
:root {
--bg: #f4f1eb;
--surface: #faf8f4;
--surface-2: #f0ede6;
--border: #d8d3c8;
--border-light: #e6e1d8;
--fg: #1a1816;
--fg-muted: #6b6560;
--fg-dim: #9a9490;
--accent: #c0392b;
--accent-warm: #d35400;
--ok: #27ae60;
--warn: #e67e22;
--alert: #c0392b;
--shadow: rgba(0,0,0,0.06);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Georgia, "Times New Roman", serif;
background: var(--bg);
color: var(--fg);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
input, select, button {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
/* --- Header --- */
header {
background: var(--surface);
border-bottom: 3px solid var(--fg);
padding: 20px 40px;
display: flex;
justify-content: space-between;
align-items: center;
}
.brand h1 {
font-family: Georgia, serif;
font-size: 1.3em;
font-weight: normal;
letter-spacing: -0.01em;
}
.brand .tagline {
font-size: 0.72em;
color: var(--fg-muted);
font-style: italic;
margin-top: 2px;
font-family: -apple-system, sans-serif;
}
.header-right { display: flex; gap: 10px; align-items: center; }
/* --- Main --- */
.main { max-width: 1100px; margin: 0 auto; padding: 32px 40px 80px; }
/* --- Section --- */
h2 {
font-family: -apple-system, sans-serif;
font-size: 0.68em;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--fg-muted);
margin-bottom: 14px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
/* --- Toolbar --- */
.toolbar {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 20px;
}
.add-group {
display: flex;
gap: 0;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.add-group select {
background: var(--surface);
border: none;
border-right: 1px solid var(--border);
color: var(--fg);
font-size: 0.85em;
padding: 6px 12px;
}
.add-group button {
background: var(--accent);
color: #fff;
border: none;
font-size: 0.82em;
padding: 6px 14px;
cursor: pointer;
font-weight: 500;
}
/* --- Grid --- */
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 100px);
gap: 10px;
margin-bottom: 36px;
}
.cell {
background: var(--surface);
border: 1px solid var(--border-light);
border-radius: 3px;
overflow: hidden;
}
.item {
width: 100%;
height: 100%;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 3px;
padding: 12px 14px;
display: flex;
flex-direction: column;
justify-content: space-between;
box-shadow: 0 1px 3px var(--shadow);
}
.item .item-top { display: flex; justify-content: space-between; align-items: flex-start; }
.item .item-name {
font-size: 0.82em;
font-weight: bold;
color: var(--fg);
}
.item .item-cat {
font-family: -apple-system, sans-serif;
font-size: 0.6em;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--fg-muted);
background: var(--surface);
border: 1px solid var(--border-light);
padding: 1px 6px;
border-radius: 2px;
}
.item .item-meta {
font-family: -apple-system, sans-serif;
font-size: 0.65em;
color: var(--fg-dim);
}
.item .controls {
display: flex;
gap: 3px;
opacity: 0;
transition: opacity 0.15s;
}
.item:hover .controls { opacity: 1; }
.item .controls button {
background: var(--surface);
border: 1px solid var(--border);
color: var(--fg-muted);
border-radius: 2px;
padding: 1px 6px;
font-size: 0.6em;
cursor: pointer;
}
/* --- Plugin Cards --- */
.plugin-card {
background: var(--surface);
border: 1px solid var(--border-light);
border-radius: 4px;
padding: 16px 20px;
margin-bottom: 10px;
}
.plugin-card h3 {
font-family: Georgia, serif;
font-size: 0.95em;
font-weight: normal;
margin-bottom: 2px;
}
.plugin-card .desc {
font-family: -apple-system, sans-serif;
font-size: 0.75em;
color: var(--fg-muted);
font-style: italic;
margin-bottom: 12px;
}
.plugin-card .fields {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
}
.field { display: flex; flex-direction: column; gap: 4px; }
.field label {
font-family: -apple-system, sans-serif;
font-size: 0.7em;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--fg-muted);
}
.field input, .field select {
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--fg);
border-radius: 3px;
padding: 6px 10px;
font-size: 0.85em;
}
/* --- Buttons --- */
.btn {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 7px 14px;
border-radius: 3px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--fg);
font-size: 0.82em;
cursor: pointer;
}
.btn:hover { background: var(--surface-2); }
.btn.primary {
background: var(--accent);
border-color: var(--accent);
color: #fff;
font-weight: 500;
}
.btn.primary:hover { background: #a93226; }
.btn.danger { color: var(--alert); border-color: var(--alert); }
/* --- Status --- */
.status {
font-family: -apple-system, sans-serif;
font-size: 0.75em;
color: var(--fg-dim);
margin-left: auto;
}
/* --- Preview --- */
.preview {
background: var(--surface);
border: 1px solid var(--border-light);
border-radius: 4px;
padding: 14px;
margin-top: 8px;
}
.preview img { width: 100%; border-radius: 2px; display: block; }
</style>
</head>
<body>
<header>
<div class="brand">
<h1>epaper dashboard</h1>
<div class="tagline">7.3" ACeP · Raspberry Pi 4 · Waveshare</div>
</div>
<div class="header-right">
<button class="btn">Refresh</button>
<button class="btn primary">Save</button>
</div>
</header>
<div class="main">
<h2>Layout — 4×4 Grid</h2>
<div class="toolbar">
<div class="add-group">
<select><option>clock</option><option>weather</option><option>system</option></select>
<button>+ Add</button>
</div>
<button class="btn">Auto-Pack</button>
<span class="status">4 items configured</span>
</div>
<div class="grid">
<div class="cell">
<div class="item">
<div class="item-top">
<div class="item-name">Clock</div>
<div class="item-cat">time</div>
</div>
<div class="item-meta">2×2 · top-left</div>
<div class="controls">
<button></button><button></button>
</div>
</div>
</div>
<div class="cell">
<div class="item">
<div class="item-top">
<div class="item-name">Weather</div>
<div class="item-cat">data</div>
</div>
<div class="item-meta">2×2 · top-right</div>
<div class="controls">
<button></button>
</div>
</div>
</div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell">
<div class="item">
<div class="item-top">
<div class="item-name">System</div>
<div class="item-cat">status</div>
</div>
<div class="item-meta">2×2 · bottom-left</div>
</div>
</div>
<div class="cell">
<div class="item">
<div class="item-top">
<div class="item-name">Hello</div>
<div class="item-cat">message</div>
</div>
<div class="item-meta">2×2 · bottom-right</div>
</div>
</div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
<h2>Plugin Configuration</h2>
<div class="plugin-card">
<h3>Clock</h3>
<div class="desc">Displays current time and date</div>
<div class="fields">
<div class="field">
<label>Timezone</label>
<input type="text" value="Europe/Berlin">
</div>
<div class="field">
<label>Format</label>
<select><option selected>24-hour</option><option>12-hour</option></select>
</div>
</div>
</div>
<div class="plugin-card">
<h3>Weather</h3>
<div class="desc">Open-Meteo API · no API key required</div>
<div class="fields">
<div class="field">
<label>Location (lat, lon)</label>
<input type="text" value="52.52,13.41">
</div>
<div class="field">
<label>Forecast hours</label>
<input type="number" value="4">
</div>
</div>
</div>
<h2>Display Preview</h2>
<div class="preview">
<img src="/snapshot.png">
</div>
</div>
</body>
</html>
-48
View File
@@ -1,48 +0,0 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>epaper dashboard — Design Preview</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, sans-serif; background: #111; color: #fff; line-height: 1.5; }
.intro { text-align: center; padding: 32px 24px 8px; color: #666; font-size: 0.85em; }
.intro h1 { color: #fff; font-size: 1.1em; margin-bottom: 6px; }
.designs { display: flex; gap: 24px; padding: 16px 24px 80px; overflow-x: auto; }
.design { flex: 0 0 520px; background: #fff; border-radius: 10px; overflow: hidden; color: #111; }
.design-header { padding: 16px 20px; background: #f0f0f5; border-bottom: 1px solid #ddd; }
.design-header h2 { font-size: 0.9em; font-weight: 600; color: #333; }
.design-header p { font-size: 0.72em; color: #888; margin-top: 2px; }
iframe { width: 100%; height: 600px; border: none; display: block; }
</style>
</head>
<body>
<div class="intro">
<h1>epaper-dashboard — Design Preview</h1>
Scrolle rechts für alle drei Varianten. Alle Designs haben das gleiche Backend — nur die Optik ist anders.
</div>
<div class="designs">
<div class="design">
<div class="design-header">
<h2>A — Dark Minimal</h2>
<p>Linear/Vercel-inspiriert · Fast-Schwarz · Eine Akzentfarbe · Clean & Ruhe</p>
</div>
<iframe src="design_a_dark_minimal.html"></iframe>
</div>
<div class="design">
<div class="design-header">
<h2>B — Retro Terminal</h2>
<p>CRT/Phosphor · Grüner-Hintergrund · Scanlines · LoRes-Charme</p>
</div>
<iframe src="design_b_retro_terminal.html"></iframe>
</div>
<div class="design">
<div class="design-header">
<h2>C — Warm Editorial</h2>
<p>Zeitung/Magazin · Warme Töne · Serif-Fonts · Papier-Optik</p>
</div>
<iframe src="design_c_warm_editorial.html"></iframe>
</div>
</div>
</body>
</html>
-20
View File
@@ -1,20 +0,0 @@
#!/bin/bash
cat > /home/koptikp/epaper-dashboard/port/.env <<'ENVEOF'
EPAPER_ADMIN_USER=admin
EPAPER_ADMIN_PASSWORD=changeme123
ENVEOF
chmod 600 /home/koptikp/epaper-dashboard/port/.env
echo "=== .env geschrieben ==="
ls -la /home/koptikp/epaper-dashboard/port/.env
echo
echo "=== restart admin ==="
bash /home/koptikp/epaper-dashboard/port/start_admin.sh
sleep 2
echo
echo "=== test mit altem passwort (sollte 401) ==="
curl -s -u admin:admin -o /dev/null -w "admin:admin -> HTTP %{http_code}\n" http://127.0.0.1:8080/status.json
echo "=== test mit neuem passwort (sollte 200) ==="
curl -s -u admin:changeme123 -o /dev/null -w "admin:changeme123 -> HTTP %{http_code}\n" http://127.0.0.1:8080/status.json
echo
echo "=== sicherheits-check: passwort in logs? ==="
grep -E "changeme123|admin:" /tmp/admin.log || echo "(kein Treffer in logs)"
-40
View File
@@ -1,40 +0,0 @@
"""Demo: Progress-Bars mit Gradient und konfigurierbaren Schwellen."""
import sys; sys.path.insert(0, ".")
from PIL import Image, ImageDraw, ImageFont
from palette import hbar, parse_thresholds, FG, OK, WARN, ALERT, ORANGE, BLUE, GREEN, RED, YELLOW
def font(s): return ImageFont.truetype("fnt/Aldrich-Regular.ttc", s)
fonts = {str(s): font(s) for s in [16, 20, 24]}
img = Image.new("RGB", (800, 600), (255,255,255))
draw = ImageDraw.Draw(img)
# Title
draw.text((20, 10), "Gradient Bars - 3-stufig default (50/80/95)", font=font(20), fill=FG)
# Row 1: gradient on (default thresholds)
draw.text((20, 50), "Gradient AN, Thresholds 50/80/95", font=font(16), fill=FG)
hbar(draw, 20, 80, 760, 30, 25, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=True)
draw.text((20, 115), "25% (ok)", font=font(16), fill=FG)
hbar(draw, 20, 140, 760, 30, 65, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=True)
draw.text((20, 175), "65% (warn)", font=font(16), fill=FG)
hbar(draw, 20, 200, 760, 30, 90, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=True)
draw.text((20, 235), "90% (alert)", font=font(16), fill=FG)
# Row 2: gradient off
draw.text((20, 280), "Gradient AUS (einfarbig in aktueller Schwelle)", font=font(20), fill=FG)
hbar(draw, 20, 310, 760, 30, 25, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=False)
draw.text((20, 345), "25% einfarbig green", font=font(16), fill=FG)
hbar(draw, 20, 370, 760, 30, 65, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=False)
draw.text((20, 405), "65% einfarbig yellow", font=font(16), fill=FG)
hbar(draw, 20, 430, 760, 30, 90, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=False)
draw.text((20, 465), "90% einfarbig red", font=font(16), fill=FG)
# Row 3: custom thresholds (mehr Stufen)
draw.text((20, 510), "Custom: 4 Stufen 30/60/80/95, custom colors", font=font(20), fill=FG)
spec = [(30, GREEN), (60, YELLOW), (80, ORANGE), (95, RED)]
hbar(draw, 20, 545, 760, 30, 50, thresholds=spec, gradient=True)
draw.text((20, 580), "50% → yellow segment", font=font(16), fill=FG)
img.save("/tmp/gradient_demo.png")
print("saved /tmp/gradient_demo.png")
-32
View File
@@ -1,32 +0,0 @@
import sys; sys.path.insert(0, ".")
import json
from layout import Item
from dashboard import render_full, load_fonts
cfg = json.load(open("config.json"))
items = [Item.from_dict(d) for d in cfg["layout"]["items"]]
plugins = cfg.get("plugin_configs", {})
from plugins.base import all_widget_classes
classes = {c.name: c for c in all_widget_classes()}
widgets = []
for it in items:
cls = classes.get(it.plugin)
if cls:
try:
w = cls(plugins.get(it.plugin, {}))
w.fetch()
widgets.append(w)
except Exception as e:
print(f"widget {it.plugin} failed: {e}")
widgets.append(None)
else:
widgets.append(None)
fonts = load_fonts()
print("items:", [(i.id, i.plugin, i.x, i.y, i.w, i.h) for i in items])
print("widgets:", [w.name if w else None for w in widgets])
img = render_full(items, widgets, fonts)
img.save("/tmp/render_grid_v2.png")
print(f"saved /tmp/render_grid_v2.png size={img.size}")
-26
View File
@@ -1,26 +0,0 @@
import sys; sys.path.insert(0, ".")
from PIL import Image, ImageDraw, ImageFont
from plugins.minimax import Widget
class FakeWidget(Widget):
def fetch(self):
return {
"windows": [
{"label": "5-Hour", "used_pct": 47.0, "remaining_pct": 53.0, "reset_seconds": 1820, "raw": {}},
{"label": "Weekly", "used_pct": 23.0, "remaining_pct": 77.0, "reset_seconds": 124800, "raw": {}},
],
"credits": {"points": 12345.0},
}
img = Image.new("RGB", (400, 240), (255, 255, 255))
draw = ImageDraw.Draw(img)
fonts = {
"16": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 16),
"20": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 20),
"24": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 24),
"28": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 28),
}
w = FakeWidget({"show_credits": True, "label_5h": "5-Hour", "label_weekly": "Weekly"})
w.render(draw, fonts, 0, 0, 400, 240)
img.save("/tmp/minimax_fake.png")
print("saved /tmp/minimax_fake.png")
-49
View File
@@ -1,49 +0,0 @@
"""Test: clock als 4x1-Strip (200x120), minimax als 2x2, weather als 2x2 (lower-right).
Zeigt ob Widgets auf verschiedene Groessen reagieren."""
import sys; sys.path.insert(0, ".")
from PIL import Image, ImageDraw, ImageFont
from layout import Item, GRID_COLS, GRID_ROWS, CELL_W, CELL_H, pack
from plugins.clock import Widget as ClockW
from plugins.minimax import Widget as MiniMaxW
from plugins.weather import Widget as WeatherW
from plugins.hello import Widget as HelloW
# Force-load fonts
def font(size):
return ImageFont.truetype("fnt/Aldrich-Regular.ttc", size)
fonts = {
"16": font(16), "20": font(20), "24": font(24),
"28": font(28), "32": font(32), "48": font(48),
"60": font(60), "80": font(80),
"clock": ImageFont.truetype("fnt/advanced_led_board-7.ttc", 100),
}
# Layout:
# clock (4x1) | y=0
# minimax (2x1) hello (2x1) | y=1
# weather (4x2) | y=2..3
items = [
Item("a", "clock", 0, 0, 4, 1),
Item("b", "clock", 0, 0, 1, 1), # tiny
Item("c", "clock", 0, 0, 2, 2), # large
Item("d", "clock", 0, 0, 4, 4), # full
]
print("Testing CLOCK widget at different sizes:")
print("="*60)
img = Image.new("RGB", (800, 480), (255,255,255))
draw = ImageDraw.Draw(img)
# 4x1 strip top
w = ClockW({})
w.fetch()
w.render(draw, fonts, 0, 0, 800, 120)
# 2x2 below
w = ClockW({"show_date": True})
w.fetch()
w.render(draw, fonts, 0, 120, 400, 240)
# mini 1x1 bottom-right
w = ClockW({})
w.fetch()
w.render(draw, fonts, 600, 360, 200, 120)
img.save("/tmp/sizes_clock.png")
print("saved /tmp/sizes_clock.png")
-57
View File
@@ -1,57 +0,0 @@
"""Test alle Widgets in verschiedenen Groessen, um Layout-Probleme zu finden."""
import sys; sys.path.insert(0, ".")
from PIL import Image, ImageDraw, ImageFont
from layout import Item
from plugins.clock import Widget as ClockW
from plugins.weather import Widget as WeatherW
from plugins.system import Widget as SystemW
from plugins.minimax import Widget as MiniMaxW
from plugins.hello import Widget as HelloW
import json
def font(size):
return ImageFont.truetype("fnt/Aldrich-Regular.ttc", size)
fonts = {
"16": font(16), "20": font(20), "24": font(24),
"28": font(28), "32": font(32), "48": font(48),
"60": font(60), "80": font(80),
"clock": ImageFont.truetype("fnt/advanced_led_board-7.ttc", 100),
}
plugins_cfg = json.load(open("config.json")).get("plugin_configs", {})
# Layout test grid: 4x4
# (0,0)-(3,0): system 4x1 wide
# (0,1)-(1,1): weather 2x1
# (2,1)-(3,1): minimax 2x1
# (0,2)-(1,3): hello 2x2
# (2,2)-(3,3): mix
img = Image.new("RGB", (800, 480), (255,255,255))
draw = ImageDraw.Draw(img)
# system 4x1 (full width strip)
w = SystemW(plugins_cfg.get("system", {}))
w.fetch()
w.render(draw, fonts, 0, 0, 800, 120)
# weather 2x1
w = WeatherW(plugins_cfg.get("weather", {"location": "52.52,13.41"}))
w.fetch()
w.render(draw, fonts, 0, 120, 400, 120)
# minimax 2x1
w = MiniMaxW(plugins_cfg.get("minimax", {}))
w.fetch()
w.render(draw, fonts, 400, 120, 400, 120)
# hello 2x2
w = HelloW({"text": "Layout Test", "color": "accent"})
w.render(draw, fonts, 0, 240, 400, 240)
# clock 2x2
w = ClockW({"show_date": True})
w.fetch()
w.render(draw, fonts, 400, 240, 400, 240)
img.save("/tmp/sizes_mix.png")
print("saved /tmp/sizes_mix.png")
-29
View File
@@ -1,29 +0,0 @@
"""Test: System Widget mit verschiedenen Threshold-Konfigurationen."""
import sys; sys.path.insert(0, ".")
from PIL import Image, ImageDraw, ImageFont
from plugins.system import Widget as SystemW
def font(s): return ImageFont.truetype("fnt/Aldrich-Regular.ttc", s)
fonts = {str(s): font(s) for s in [16, 20, 24, 28, 32]}
img = Image.new("RGB", (800, 480), (255,255,255))
draw = ImageDraw.Draw(img)
# Drei Konfigurationen nebeneinander
configs = [
{"name": "Default (50/80/95, gradient)",
"cfg": {"bar_thresholds": "ok@50,warn@80,alert@95", "bar_gradient": True}},
{"name": "Strenger (30/60/85, gradient)",
"cfg": {"bar_thresholds": "ok@30,warn@60,alert@85", "bar_gradient": True}},
{"name": "Einfarbig (40/70/95)",
"cfg": {"bar_thresholds": "ok@40,warn@70,alert@95", "bar_gradient": False}},
]
for i, c in enumerate(configs):
x = i * 267
draw.text((x + 8, 5), c["name"], font=font(14), fill=(0,0,0))
w = SystemW({**c["cfg"], "compact": False})
w.fetch()
w.render(draw, fonts, x, 30, 260, 200)
img.save("/tmp/system_thresholds.png")
print("saved /tmp/system_thresholds.png")
-3
View File
@@ -1,3 +0,0 @@
"""Waveshare 7.3-inch ACeP 7-Color e-Paper (F) — vendored driver."""
from .epd7in3f import EPD
from .epdconfig import module_exit