Update-Check im System-Tab, Worker-Adressfeld mit Heimnetz-Warnung, Save-Knopf raus aus dem Worker-Tab
Ampel / ampel (push) Successful in 28s
Ampel / ampel (push) Successful in 28s
1. Update-Erkennung (Commander-Frage 'wie kriegen wir Updates mit?'): GET /system/updates vergleicht installierte Versionen (Worker-Info) mit makemkv.com (Versionsnummer der Download-Seite) und der HandBrake- GitHub-Release-API (releases/latest), 12 h gecacht. Einstellungen -> System: 'Auf Updates prüfen' + fertiger Update-Befehl bei MakeMKV. Selbst-Update gibt es bewusst NICHT (waere Docker-Socket-Zugriff); dafuer ist MAKEMKV_VERSION jetzt per .env uebersteuerbar — Update ohne Code-Aenderung. HandBrake-Abweichung wird als Info dargestellt (Debian-Paket hinkt der offiziellen Version bewusst hinterher). 2. Worker-Anbindung: editierbares Adressfeld statt stillschweigend window.location.hostname — Befund: ueber die externe Domain kopierte Befehle liefen in den Reverse-Proxy (401) und Worker koennen Redis/ Postgres eh nur im Heimnetz erreichen. Amber-Warnung bei oeffentlich aussehender Adresse, Anleitung ergaenzt. 3. 'Einstellungen speichern' ist im Worker-Tab ausgeblendet — dort gibt es nichts zu speichern, der Knopf verwirrte nur. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,3 +21,9 @@ OMDB_API_KEY=
|
|||||||
# Bequemer: im UI unter Einstellungen → System eintragen (gilt ab dem
|
# Bequemer: im UI unter Einstellungen → System eintragen (gilt ab dem
|
||||||
# nächsten Rip, ohne Neustart, und schlägt diesen Env-Wert).
|
# nächsten Rip, ohne Neustart, und schlägt diesen Env-Wert).
|
||||||
MAKEMKV_APP_KEY=
|
MAKEMKV_APP_KEY=
|
||||||
|
|
||||||
|
# MakeMKV-Version fürs Worker-Image (Einstellungen → System meldet Updates).
|
||||||
|
# Update: Version hier anheben, dann auf der Rippy-Maschine
|
||||||
|
# docker compose build worker && docker compose up -d worker
|
||||||
|
# (bei Cloudflare-Zicken vorher Tarballs nach docker/worker/vendor/ legen)
|
||||||
|
#MAKEMKV_VERSION=1.18.4
|
||||||
|
|||||||
@@ -103,14 +103,22 @@ fehlgeschlagen, abgebrochen) und erkennt den Dienst an der URL selbst:
|
|||||||
| ntfy (Handy-Push) | `https://ntfy.sh/mein-geheimes-thema` | Roh-Text + Titel |
|
| ntfy (Handy-Push) | `https://ntfy.sh/mein-geheimes-thema` | Roh-Text + Titel |
|
||||||
| Eigenes (HA, n8n, …) | beliebige HTTPS-URL | `{"title","message","level"}` |
|
| Eigenes (HA, n8n, …) | beliebige HTTPS-URL | `{"title","message","level"}` |
|
||||||
|
|
||||||
## System & MakeMKV-Beta-Key
|
## System, MakeMKV-Beta-Key & Updates
|
||||||
|
|
||||||
**Einstellungen → System** zeigt die Werkzeug-Versionen jedes Workers
|
**Einstellungen → System** zeigt die Werkzeug-Versionen jedes Workers
|
||||||
(MakeMKV, HandBrake), den Key-Status und den freien Speicherplatz. Der
|
(MakeMKV, HandBrake), den Key-Status und den freien Speicherplatz. Der
|
||||||
MakeMKV-Beta-Key (wechselt ~monatlich, Forum-Thread t=1053) wird hier im
|
MakeMKV-Beta-Key (wechselt ~monatlich, Forum-Thread t=1053) wird hier im
|
||||||
UI eingetragen und gilt ab dem **nächsten Rip** — ohne Rebuild, ohne
|
UI eingetragen und gilt ab dem **nächsten Rip** — ohne Rebuild, ohne
|
||||||
Neustart; er schlägt den Key aus der `.env`. Nur die MakeMKV-*Version*
|
Neustart; er schlägt den Key aus der `.env`.
|
||||||
steckt im Image: `docker compose build worker && docker compose up -d worker`.
|
|
||||||
|
**Updates:** „Auf Updates prüfen" vergleicht mit makemkv.com und den
|
||||||
|
offiziellen HandBrake-Releases (12 h gecacht). MakeMKV-Update ohne
|
||||||
|
Code-Änderung: `MAKEMKV_VERSION=x.y.z` in die `.env`, dann
|
||||||
|
`docker compose build worker && docker compose up -d worker` — neue
|
||||||
|
Versionen bringen auch die neueste Disc-Schlüssel-Datenbank mit.
|
||||||
|
HandBrake kommt im Docker-Worker bewusst aus Debian (stabil, hinkt der
|
||||||
|
offiziellen Version hinterher); der native Windows-Worker nutzt die
|
||||||
|
aktuelle Version direkt.
|
||||||
|
|
||||||
## Verarbeitung & Hardware
|
## Verarbeitung & Hardware
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ services:
|
|||||||
# 525); dann Wayback-Snapshot als MAKEMKV_URL_BASE eintragen.
|
# 525); dann Wayback-Snapshot als MAKEMKV_URL_BASE eintragen.
|
||||||
# /download = aktuelle Version, /download/old = ältere Versionen.
|
# /download = aktuelle Version, /download/old = ältere Versionen.
|
||||||
MAKEMKV_URL_BASE: ${MAKEMKV_URL_BASE:-https://www.makemkv.com/download}
|
MAKEMKV_URL_BASE: ${MAKEMKV_URL_BASE:-https://www.makemkv.com/download}
|
||||||
|
# MakeMKV-Update OHNE Code-Änderung: neue Version in die .env,
|
||||||
|
# Tarballs nach docker/worker/vendor/ legen (oder Download klappt),
|
||||||
|
# dann docker compose build worker && docker compose up -d worker.
|
||||||
|
# Einstellungen → System meldet, wenn es eine neuere Version gibt.
|
||||||
|
MAKEMKV_VERSION: ${MAKEMKV_VERSION:-1.18.4}
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=postgresql://rippy:rippy@postgres:5432/rippy
|
- DATABASE_URL=postgresql://rippy:rippy@postgres:5432/rippy
|
||||||
- REDIS_URL=redis://redis:6379/0
|
- REDIS_URL=redis://redis:6379/0
|
||||||
|
|||||||
@@ -935,6 +935,70 @@ async def browse_mkdir(request: MkdirRequest):
|
|||||||
return {"path": ziel}
|
return {"path": ziel}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/system/updates")
|
||||||
|
async def system_updates():
|
||||||
|
"""Update-Check für die Kern-Werkzeuge (Einstellungen → System).
|
||||||
|
|
||||||
|
Quellen: HandBrake über die GitHub-Release-API (releases/latest →
|
||||||
|
tag_name), MakeMKV über die offizielle Download-Seite (Versionsnummer
|
||||||
|
im Seitentext). Ergebnisse werden 12 h gecacht — der Check ist Komfort,
|
||||||
|
kein Dauerfeuer auf fremde Server. Ein Selbst-Update gibt es bewusst
|
||||||
|
NICHT: die Versionen stecken im Worker-Image, das Update ist ein
|
||||||
|
Image-Rebuild (Befehl wird im UI angezeigt).
|
||||||
|
"""
|
||||||
|
import re as _re
|
||||||
|
|
||||||
|
import requests as _requests
|
||||||
|
|
||||||
|
from cache import get as cache_get, set as _cache_set
|
||||||
|
|
||||||
|
def hole_neueste():
|
||||||
|
ergebnis = {"makemkv": None, "handbrake": None}
|
||||||
|
cached = cache_get("updates:neueste")
|
||||||
|
if cached:
|
||||||
|
return cached
|
||||||
|
try:
|
||||||
|
antwort = _requests.get(
|
||||||
|
"https://api.github.com/repos/HandBrake/HandBrake/releases/latest",
|
||||||
|
timeout=15, headers={"Accept": "application/vnd.github+json"},
|
||||||
|
)
|
||||||
|
if antwort.status_code == 200:
|
||||||
|
ergebnis["handbrake"] = (antwort.json().get("tag_name") or "").lstrip("v")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
antwort = _requests.get("https://www.makemkv.com/download/", timeout=15)
|
||||||
|
treffer = _re.search(r"MakeMKV\s+(\d+\.\d+\.\d+)", antwort.text or "")
|
||||||
|
if treffer:
|
||||||
|
ergebnis["makemkv"] = treffer.group(1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if ergebnis["makemkv"] or ergebnis["handbrake"]:
|
||||||
|
_cache_set("updates:neueste", ergebnis, expire=12 * 3600)
|
||||||
|
return ergebnis
|
||||||
|
|
||||||
|
def sammle():
|
||||||
|
neueste = hole_neueste()
|
||||||
|
installiert = {"makemkv": None, "handbrake": None}
|
||||||
|
for worker in db.list_workers():
|
||||||
|
info = worker.get("info") or {}
|
||||||
|
installiert["makemkv"] = installiert["makemkv"] or info.get("makemkv")
|
||||||
|
installiert["handbrake"] = installiert["handbrake"] or info.get("handbrake")
|
||||||
|
return {
|
||||||
|
werkzeug: {
|
||||||
|
"installiert": installiert[werkzeug],
|
||||||
|
"verfuegbar": neueste[werkzeug],
|
||||||
|
"update": bool(
|
||||||
|
installiert[werkzeug] and neueste[werkzeug]
|
||||||
|
and installiert[werkzeug] != neueste[werkzeug]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for werkzeug in ("makemkv", "handbrake")
|
||||||
|
}
|
||||||
|
|
||||||
|
return await asyncio.to_thread(sammle)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/worker-setup/windows")
|
@app.get("/worker-setup/windows")
|
||||||
async def worker_setup_windows():
|
async def worker_setup_windows():
|
||||||
"""Windows-Installer-Skript für den nativen Transcode-Worker.
|
"""Windows-Installer-Skript für den nativen Transcode-Worker.
|
||||||
|
|||||||
@@ -41,9 +41,20 @@ export default function WorkerVerwaltung() {
|
|||||||
const [workers, setWorkers] = useState<WorkerInfo[]>([])
|
const [workers, setWorkers] = useState<WorkerInfo[]>([])
|
||||||
const [kopiert, setKopiert] = useState(false)
|
const [kopiert, setKopiert] = useState(false)
|
||||||
const [variante, setVariante] = useState<'linux' | 'windows'>('linux')
|
const [variante, setVariante] = useState<'linux' | 'windows'>('linux')
|
||||||
|
const [rippyHost, setRippyHost] = useState(window.location.hostname)
|
||||||
const { theme } = useDarkMode()
|
const { theme } = useDarkMode()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
|
// Worker verbinden sich DIREKT mit Redis/Postgres (6379/5432) — das geht
|
||||||
|
// nur im Heimnetz. Wer das UI gerade über eine externe Domain (Reverse-
|
||||||
|
// Proxy) aufruft, würde sonst Befehle mit der falschen Adresse kopieren
|
||||||
|
// (Befund 24.07.: 401 vom Proxy statt Installer).
|
||||||
|
const siehtOeffentlichAus =
|
||||||
|
!/^(\d{1,3}\.){3}\d{1,3}$/.test(rippyHost) &&
|
||||||
|
!rippyHost.endsWith('.local') &&
|
||||||
|
rippyHost !== 'localhost' &&
|
||||||
|
rippyHost.includes('.')
|
||||||
|
|
||||||
const laden = (manuell = false) => {
|
const laden = (manuell = false) => {
|
||||||
api.get('/capabilities')
|
api.get('/capabilities')
|
||||||
.then(r => {
|
.then(r => {
|
||||||
@@ -59,7 +70,6 @@ export default function WorkerVerwaltung() {
|
|||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const rippyHost = window.location.hostname
|
|
||||||
const installBefehl = variante === 'linux'
|
const installBefehl = variante === 'linux'
|
||||||
? [
|
? [
|
||||||
`git clone <rippy-repo-url> rippy && cd rippy`,
|
`git clone <rippy-repo-url> rippy && cd rippy`,
|
||||||
@@ -141,6 +151,29 @@ export default function WorkerVerwaltung() {
|
|||||||
{/* Neue Maschine anbinden — zwei Varianten */}
|
{/* Neue Maschine anbinden — zwei Varianten */}
|
||||||
<div className={`rounded-lg p-4 border ${theme === 'dark' ? 'border-slate-700 bg-slate-800/50' : 'border-slate-200 bg-slate-50'}`}>
|
<div className={`rounded-lg p-4 border ${theme === 'dark' ? 'border-slate-700 bg-slate-800/50' : 'border-slate-200 bg-slate-50'}`}>
|
||||||
<h3 className={`font-medium mb-2 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>Maschine als Encoding-Worker anbinden</h3>
|
<h3 className={`font-medium mb-2 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>Maschine als Encoding-Worker anbinden</h3>
|
||||||
|
|
||||||
|
{/* Rippy-Adresse für die Befehle: Worker reden DIREKT mit Redis/
|
||||||
|
Postgres — das geht nur im Heimnetz, nie über die externe Domain */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className={`block text-xs font-medium mb-1 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||||
|
Rippy-Adresse aus Sicht des Workers (Heimnetz-IP oder -Name)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
value={rippyHost}
|
||||||
|
onChange={e => setRippyHost(e.target.value.trim())}
|
||||||
|
className={`w-full max-w-xs px-3 py-2 border rounded-lg text-sm font-mono focus:ring-2 focus:ring-indigo-500 focus:border-transparent ${theme === 'dark' ? 'bg-slate-800 border-slate-600 text-slate-200' : 'bg-white border-slate-300 text-slate-900'}`}
|
||||||
|
placeholder="192.168.178.162"
|
||||||
|
/>
|
||||||
|
{siehtOeffentlichAus && (
|
||||||
|
<p className={`text-xs mt-1.5 px-2.5 py-1.5 rounded-lg inline-block ${theme === 'dark' ? 'bg-amber-900/30 text-amber-300' : 'bg-amber-50 text-amber-700'}`}>
|
||||||
|
⚠️ Das sieht nach einer externen Domain aus (Reverse-Proxy). Worker verbinden sich
|
||||||
|
direkt mit Redis/Postgres (Ports 6379/5432) — das geht nur im Heimnetz, und der
|
||||||
|
Proxy blockt den Installer-Download (401). Hier die <strong>LAN-IP</strong> der
|
||||||
|
Rippy-Maschine eintragen, z. B. 192.168.178.162.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 mb-3">
|
<div className="flex gap-2 mb-3">
|
||||||
{([['linux', '🐧 Linux (Docker)'], ['windows', '🪟 Windows (nativ, ohne Docker)']] as const).map(([wert, label]) => (
|
{([['linux', '🐧 Linux (Docker)'], ['windows', '🪟 Windows (nativ, ohne Docker)']] as const).map(([wert, label]) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ export default function AnleitungPage() {
|
|||||||
danach unter ihrem Namen in der Worker-Liste auf und zieht sich Kompressions-Jobs
|
danach unter ihrem Namen in der Worker-Liste auf und zieht sich Kompressions-Jobs
|
||||||
automatisch. Gerippt wird immer auf der Rippy-Hauptmaschine (dort hängt das Laufwerk).
|
automatisch. Gerippt wird immer auf der Rippy-Hauptmaschine (dort hängt das Laufwerk).
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className={fett}>Wichtig:</span> Worker verbinden sich direkt im Heimnetz — in den
|
||||||
|
Befehlen immer die <span className={fett}>LAN-Adresse</span> der Rippy-Maschine verwenden,
|
||||||
|
nie eine externe Domain (die läuft über den Reverse-Proxy und blockt).
|
||||||
|
</p>
|
||||||
</Abschnitt>
|
</Abschnitt>
|
||||||
|
|
||||||
<Abschnitt icon={Wrench} titel="MakeMKV-Beta-Key & System">
|
<Abschnitt icon={Wrench} titel="MakeMKV-Beta-Key & System">
|
||||||
@@ -125,6 +130,13 @@ export default function AnleitungPage() {
|
|||||||
Einstellungen → System eintragen — gilt ab dem nächsten Rip, ohne Neustart. DVDs gehen
|
Einstellungen → System eintragen — gilt ab dem nächsten Rip, ohne Neustart. DVDs gehen
|
||||||
immer auch ohne Key. Dort stehen auch die Werkzeug-Versionen und der freie Speicherplatz.
|
immer auch ohne Key. Dort stehen auch die Werkzeug-Versionen und der freie Speicherplatz.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className={fett}>Updates:</span> „Auf Updates prüfen" (ebenfalls Einstellungen →
|
||||||
|
System) vergleicht die installierten Versionen mit makemkv.com und den offiziellen
|
||||||
|
HandBrake-Releases. Gibt es ein MakeMKV-Update, zeigt Rippy den fertigen
|
||||||
|
Update-Befehl an — wichtig, weil neue Versionen auch die neueste
|
||||||
|
Disc-Schlüssel-Datenbank mitbringen.
|
||||||
|
</p>
|
||||||
</Abschnitt>
|
</Abschnitt>
|
||||||
|
|
||||||
<Abschnitt icon={Download} titel="Fertige Dateien herunterladen">
|
<Abschnitt icon={Download} titel="Fertige Dateien herunterladen">
|
||||||
|
|||||||
@@ -88,6 +88,25 @@ export default function SettingsPage() {
|
|||||||
const [testBusy, setTestBusy] = useState(false)
|
const [testBusy, setTestBusy] = useState(false)
|
||||||
const [quellenStatus, setQuellenStatus] = useState<Record<string, string> | null>(null)
|
const [quellenStatus, setQuellenStatus] = useState<Record<string, string> | null>(null)
|
||||||
const [quellenBusy, setQuellenBusy] = useState(false)
|
const [quellenBusy, setQuellenBusy] = useState(false)
|
||||||
|
const [updates, setUpdates] = useState<Record<string, { installiert?: string, verfuegbar?: string, update?: boolean }> | null>(null)
|
||||||
|
const [updatesBusy, setUpdatesBusy] = useState(false)
|
||||||
|
|
||||||
|
const updatesPruefen = async () => {
|
||||||
|
setUpdatesBusy(true)
|
||||||
|
try {
|
||||||
|
const r = await api.get('/system/updates')
|
||||||
|
setUpdates(r.data)
|
||||||
|
if (r.data?.makemkv?.update) {
|
||||||
|
toast('info', `MakeMKV ${r.data.makemkv.verfuegbar} ist verfügbar (installiert: ${r.data.makemkv.installiert})`)
|
||||||
|
} else {
|
||||||
|
toast('success', 'Update-Check abgeschlossen')
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
toast('error', 'Update-Check fehlgeschlagen (makemkv.com/GitHub nicht erreichbar?)')
|
||||||
|
} finally {
|
||||||
|
setUpdatesBusy(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
const { theme } = useDarkMode()
|
const { theme } = useDarkMode()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
@@ -732,9 +751,54 @@ export default function SettingsPage() {
|
|||||||
Rebuild: <code>docker compose build worker && docker compose up -d worker</code> (siehe README).
|
Rebuild: <code>docker compose build worker && docker compose up -d worker</code> (siehe README).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Update-Check: Rippy MELDET Updates ehrlich — selbst updaten
|
||||||
|
kann es nicht (die Versionen stecken im Worker-Image) */}
|
||||||
|
<div className={`rounded-lg p-4 mt-6 ${theme === 'dark' ? 'bg-slate-700/50' : 'bg-slate-50'}`}>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<p className={`text-sm font-medium ${theme === 'dark' ? 'text-slate-200' : 'text-slate-700'}`}>Update-Check</p>
|
||||||
|
<button
|
||||||
|
onClick={updatesPruefen}
|
||||||
|
disabled={updatesBusy}
|
||||||
|
className="px-3 py-1.5 rounded-lg bg-indigo-600 text-white text-xs font-medium hover:bg-indigo-700 transition-colors disabled:opacity-40"
|
||||||
|
>
|
||||||
|
{updatesBusy ? 'Prüfe…' : 'Auf Updates prüfen'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{updates ? (
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<div className={updates.makemkv?.update ? 'text-amber-500' : (theme === 'dark' ? 'text-slate-300' : 'text-slate-600')}>
|
||||||
|
<strong>MakeMKV:</strong> installiert {updates.makemkv?.installiert || '—'},
|
||||||
|
aktuell {updates.makemkv?.verfuegbar || 'unbekannt'}
|
||||||
|
{updates.makemkv?.update && (
|
||||||
|
<span className="block text-xs mt-1">
|
||||||
|
Update: in der .env <code>MAKEMKV_VERSION={updates.makemkv.verfuegbar}</code> setzen,
|
||||||
|
dann auf der Rippy-Maschine <code>docker compose build worker && docker compose up -d worker</code>.
|
||||||
|
Neue Version bringt auch die neueste Disc-Schlüssel-Datenbank mit.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={theme === 'dark' ? 'text-slate-300' : 'text-slate-600'}>
|
||||||
|
<strong>HandBrake:</strong> Docker-Worker {updates.handbrake?.installiert || '—'} (Debian-Paket),
|
||||||
|
neueste offizielle Version {updates.handbrake?.verfuegbar || 'unbekannt'}
|
||||||
|
<span className={`block text-xs mt-1 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||||
|
Im Docker-Worker kommt HandBrake aus Debian und hinkt der offiziellen Version
|
||||||
|
bewusst hinterher (stabil, keine Eigenbauten). Der native Windows-Worker nutzt
|
||||||
|
die aktuelle Version direkt.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className={`text-xs ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||||
|
Prüft gegen makemkv.com und die HandBrake-GitHub-Releases (Ergebnis wird 12 h gemerkt).
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</section>}
|
</section>}
|
||||||
|
|
||||||
{/* Save Button */}
|
{/* Save Button — nicht im Worker-Tab: dort gibt es nichts zu
|
||||||
|
speichern (Liste + Anbinde-Befehle), der Knopf verwirrte nur */}
|
||||||
|
{activeTab !== 'worker' && (
|
||||||
<div className={`flex items-center justify-end gap-4 pt-6 transition-colors duration-300 ${theme === 'dark' ? 'border-slate-700' : 'border-slate-200'} border-t`}>
|
<div className={`flex items-center justify-end gap-4 pt-6 transition-colors duration-300 ${theme === 'dark' ? 'border-slate-700' : 'border-slate-200'} border-t`}>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
@@ -754,6 +818,7 @@ export default function SettingsPage() {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user