arr-summary/settings.html

486 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if theme == 'office' %}<title>Settings — Dunder Mifflin Media Centre</title>
{% elif theme == 'parks' %}<title>Settings — Pawnee Parks Media Centre</title>
{% elif theme == 'potter' %}<title>Settings — Hogwarts Media Vault</title>
{% elif theme == 'starwars' %}<title>Settings — Rebel Alliance Media Archive</title>
{% else %}<title>Settings — Greendale Media Centre</title>{% endif %}
{% if theme == 'office' %}<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📎</text></svg>">
{% elif theme == 'parks' %}<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌳</text></svg>">
{% elif theme == 'potter' %}<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
{% elif theme == 'starwars' %}<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>">
{% else %}<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">{% endif %}
{% if theme == 'office' %}
<link href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
{% elif theme == 'parks' %}
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
{% elif theme == 'potter' %}
<link href="https://fonts.googleapis.com/css2?family=MedievalSharp&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
{% elif theme == 'starwars' %}
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
{% else %}
<link href="https://fonts.googleapis.com/css2?family=Special+Elite&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
{% endif %}
<style>
:root {
--gold: #e8b84b;
--teal: #2a9d8f;
--teal-lt: #3dbdb0;
--navy: #1b2a3b;
--navy-lt: #243447;
--navy-mid:#1e3050;
--panel: #192433;
--border: #2e4460;
--text: #d8e4f0;
--muted: #7a93b0;
--red: #e84b4b;
--green: #27c98f;
--brand-font: 'Special Elite', cursive;
}
{% if theme == 'office' %}
:root {
--gold: #4a90d9; --gold-dk: #357abd; --teal: #6c757d; --teal-lt: #8e9aa8;
--navy: #1c2128; --navy-lt: #252d38; --navy-mid: #1a2232; --panel: #1e2530;
--border: #334155; --text: #dce6f0; --muted: #7a8fa6; --red: #e05252; --green: #3aad74;
--brand-font: 'Libre Baskerville', serif;
}
{% elif theme == 'parks' %}
:root {
--gold: #5aad4e; --gold-dk: #3d8c33; --teal: #c8842a; --teal-lt: #e09a3a;
--navy: #1a2213; --navy-lt: #222e19; --navy-mid: #1d2a16; --panel: #1a2615;
--border: #2d4220; --text: #d8edd0; --muted: #7a9a72; --red: #e84b4b; --green: #5aad4e;
--brand-font: 'Playfair Display', serif;
}
{% elif theme == 'potter' %}
:root {
--gold: #d4af37; --gold-dk: #b8961e; --teal: #7b2d8b; --teal-lt: #9b3dab;
--navy: #1a1228; --navy-lt: #231934; --navy-mid: #1d1530; --panel: #1a1228;
--border: #3d2a5a; --text: #e8dff8; --muted: #9080b8; --red: #8b1a1a; --green: #2d6e3a;
--brand-font: 'MedievalSharp', cursive;
}
{% elif theme == 'starwars' %}
:root {
--gold: #ffe81f; --gold-dk: #d4c000; --teal: #4fc3f7; --teal-lt: #81d4fa;
--navy: #080c14; --navy-lt: #0f1620; --navy-mid: #0a1018; --panel: #0d1422;
--border: #1e2d42; --text: #d0e8ff; --muted: #607d9e; --red: #e84b4b; --green: #00e676;
--brand-font: 'Orbitron', sans-serif;
}
{% endif %}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', sans-serif; background: var(--navy); color: var(--text); min-height: 100vh; }
header {
background: var(--navy-mid);
border-bottom: 3px solid var(--gold);
padding: 1.2rem 2rem;
display: flex;
align-items: center;
gap: 1rem;
}
header img { width: 40px; height: 40px; object-fit: contain; }
header h1 { font-family: var(--brand-font); font-size: 1.3rem; color: var(--gold); flex: 1; }
.back-btn {
background: var(--navy-lt);
border: 1px solid var(--border);
color: var(--muted);
border-radius: 8px;
padding: 0.45rem 0.9rem;
font-size: 0.82rem;
text-decoration: none;
transition: border-color 0.2s;
}
.back-btn:hover { border-color: var(--gold); color: var(--gold); }
main { padding: 2rem; max-width: 900px; margin: 0 auto; }
.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
@media (max-width: 700px) { .settings-grid { grid-template-columns: 1fr; } }
.card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
}
.card-header {
padding: 0.9rem 1.25rem;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 0.6rem;
}
.card-header h2 {
font-family: var(--brand-font);
font-size: 1rem;
color: var(--gold);
}
.card-body { padding: 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 0.85rem; }
.field label {
display: block;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--muted);
margin-bottom: 0.35rem;
}
.field input, .field select {
width: 100%;
background: var(--navy-lt);
border: 1px solid var(--border);
border-radius: 8px;
padding: 0.6rem 0.9rem;
font-size: 0.88rem;
color: var(--text);
font-family: 'Inter', sans-serif;
outline: none;
transition: border-color 0.2s;
}
.field input:focus, .field select:focus { border-color: var(--gold); }
.field select option { background: var(--navy-mid); }
.save-btn {
width: 100%;
background: var(--gold);
color: var(--navy);
border: none;
border-radius: 8px;
padding: 0.65rem;
font-size: 0.9rem;
font-weight: 700;
font-family: var(--brand-font);
cursor: pointer;
transition: opacity 0.15s;
margin-top: 0.25rem;
}
.save-btn:hover { opacity: 0.88; }
.save-btn:disabled { background: var(--border); color: var(--muted); cursor: default; }
.error-inline {
font-size: 0.78rem;
color: var(--red);
display: none;
margin-top: -0.3rem;
}
.toast {
position: fixed; bottom: 1.5rem; right: 1.5rem;
background: var(--green); color: #fff;
padding: 0.7rem 1.2rem; border-radius: 10px;
font-size: 0.88rem; font-weight: 700;
opacity: 0; transition: opacity 0.3s;
pointer-events: none;
}
.toast.error { background: var(--red); }
.toast.show { opacity: 1; }
.section-label {
font-family: var(--brand-font);
font-size: 0.8rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 1rem;
margin-top: 0.5rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid var(--border);
}
/* Theme preview chips */
.theme-options {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 0.75rem;
}
.theme-chip {
border: 2px solid var(--border);
border-radius: 10px;
padding: 0.75rem 0.6rem 0.6rem;
cursor: pointer;
text-align: center;
transition: border-color 0.2s, background 0.2s;
background: var(--navy-lt);
}
.theme-chip:hover { border-color: var(--gold); }
.theme-chip.active { border-color: var(--gold); background: rgba(232,184,75,0.08); }
.theme-chip .tc-emoji { font-size: 1.6rem; margin-bottom: 0.35rem; }
.theme-chip .tc-name {
font-size: 0.72rem;
font-weight: 700;
color: var(--text);
font-family: var(--brand-font);
letter-spacing: 0.3px;
}
.theme-chip .tc-sub {
font-size: 0.65rem;
color: var(--muted);
margin-top: 0.15rem;
}
</style>
</head>
<body>
<header>
<img src="{{ url_for('static', filename='logo.png') }}" alt="Greendale" id="header-logo">
<h1>Settings</h1>
<a href="/" class="back-btn">← Dashboard</a>
</header>
<main>
<div class="settings-grid">
<!-- General -->
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<span>🌍</span>
<h2>General</h2>
</div>
<div class="card-body">
<div class="field">
<label>Timezone</label>
<select id="timezone">
{% set tzs = [
'America/New_York','America/Chicago','America/Denver','America/Los_Angeles',
'America/Toronto','America/Vancouver','America/Phoenix','America/Halifax',
'Europe/London','Europe/Paris','Europe/Berlin','Europe/Amsterdam',
'Europe/Stockholm','Europe/Rome','Europe/Madrid','Europe/Zurich',
'Australia/Sydney','Australia/Melbourne','Australia/Brisbane','Australia/Perth',
'Pacific/Auckland','Asia/Tokyo','Asia/Singapore','Asia/Dubai','Asia/Kolkata'
] %}
{% for tz in tzs %}
<option value="{{ tz }}" {% if tz == timezone %}selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
</div>
<div class="field">
<label>Host IP (ARR_HOST)</label>
<input type="text" id="arr_host" value="{{ arr_host }}" placeholder="e.g. 192.168.86.33">
</div>
<button class="save-btn" onclick="saveGeneral()">Save General</button>
</div>
</div>
<!-- Theme -->
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<span>🎨</span>
<h2>Theme</h2>
</div>
<div class="card-body">
<div class="theme-options" id="theme-options">
<div class="theme-chip {% if theme == 'community' %}active{% endif %}" data-theme="community" onclick="selectTheme(this)">
<div class="tc-emoji">🏫</div>
<div class="tc-name">Community</div>
<div class="tc-sub">Greendale College</div>
</div>
<div class="theme-chip {% if theme == 'office' %}active{% endif %}" data-theme="office" onclick="selectTheme(this)">
<div class="tc-emoji">📎</div>
<div class="tc-name">The Office</div>
<div class="tc-sub">Dunder Mifflin</div>
</div>
<div class="theme-chip {% if theme == 'parks' %}active{% endif %}" data-theme="parks" onclick="selectTheme(this)">
<div class="tc-emoji">🌳</div>
<div class="tc-name">Parks & Rec</div>
<div class="tc-sub">Pawnee, Indiana</div>
</div>
<div class="theme-chip {% if theme == 'potter' %}active{% endif %}" data-theme="potter" onclick="selectTheme(this)">
<div class="tc-emoji"></div>
<div class="tc-name">Harry Potter</div>
<div class="tc-sub">Hogwarts</div>
</div>
<div class="theme-chip {% if theme == 'starwars' %}active{% endif %}" data-theme="starwars" onclick="selectTheme(this)">
<div class="tc-emoji">🌌</div>
<div class="tc-name">Star Wars</div>
<div class="tc-sub">A long time ago…</div>
</div>
</div>
<button class="save-btn" onclick="saveTheme()">Apply Theme</button>
</div>
</div>
<!-- Change Password -->
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<span>🔐</span>
<h2>Change Password</h2>
</div>
<div class="card-body">
<div class="field">
<label>Current Password</label>
<input type="password" id="pw_current" placeholder="Enter current password">
</div>
<div class="field">
<label>New Password</label>
<input type="password" id="pw_new" placeholder="At least 6 characters">
</div>
<div class="field">
<label>Confirm New Password</label>
<input type="password" id="pw_confirm" placeholder="Repeat new password">
<div class="error-inline" id="pw-error"></div>
</div>
<button class="save-btn" onclick="changePassword()">Update Password</button>
</div>
</div>
<!-- Sonarr -->
<div class="card">
<div class="card-header"><span>📺</span><h2>Sonarr</h2></div>
<div class="card-body">
<div class="field">
<label>API Key</label>
<input type="text" id="sonarr_api_key" value="{{ sonarr_api_key }}">
</div>
<button class="save-btn" onclick="saveService('sonarr')">Save</button>
</div>
</div>
<!-- Radarr -->
<div class="card">
<div class="card-header"><span>🎬</span><h2>Radarr</h2></div>
<div class="card-body">
<div class="field">
<label>API Key</label>
<input type="text" id="radarr_api_key" value="{{ radarr_api_key }}">
</div>
<button class="save-btn" onclick="saveService('radarr')">Save</button>
</div>
</div>
<!-- SABnzbd -->
<div class="card">
<div class="card-header"><span>⬇️</span><h2>SABnzbd</h2></div>
<div class="card-body">
<div class="field">
<label>API Key</label>
<input type="text" id="sabnzbd_api_key" value="{{ sabnzbd_api_key }}">
</div>
<button class="save-btn" onclick="saveService('sabnzbd')">Save</button>
</div>
</div>
<!-- Tautulli -->
<div class="card">
<div class="card-header"><span>▶️</span><h2>Tautulli</h2></div>
<div class="card-body">
<div class="field">
<label>API Key</label>
<input type="text" id="tautulli_api_key" value="{{ tautulli_api_key }}">
</div>
<button class="save-btn" onclick="saveService('tautulli')">Save</button>
</div>
</div>
<!-- Prowlarr -->
<div class="card">
<div class="card-header"><span>🔍</span><h2>Prowlarr</h2></div>
<div class="card-body">
<div class="field">
<label>API Key</label>
<input type="text" id="prowlarr_api_key" value="{{ prowlarr_api_key }}">
</div>
<button class="save-btn" onclick="saveService('prowlarr')">Save</button>
</div>
</div>
<!-- Ombi -->
<div class="card">
<div class="card-header"><span>🎟️</span><h2>Ombi</h2></div>
<div class="card-body">
<div class="field">
<label>API Key</label>
<input type="text" id="ombi_api_key" value="{{ ombi_api_key }}">
</div>
<button class="save-btn" onclick="saveService('ombi')">Save</button>
</div>
</div>
</div>
</main>
<div class="toast" id="toast">✓ Saved</div>
<script>
let _selectedTheme = '{{ theme }}';
function showToast(msg, isError) {
const t = document.getElementById('toast');
t.textContent = msg || '✓ Saved';
t.classList.toggle('error', !!isError);
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2800);
}
async function saveGeneral() {
const payload = {
timezone: document.getElementById('timezone').value,
arr_host: document.getElementById('arr_host').value.trim(),
};
const resp = await fetch('/api/settings', {
method: 'POST', headers: {'Content-Type': 'application/json'},
credentials: 'include', body: JSON.stringify(payload)
});
if (resp.ok) showToast('✓ General saved — reload dashboard for clock update');
else showToast('✗ Save failed', true);
}
async function saveService(name) {
const keyEl = document.getElementById(name + '_api_key');
const payload = { [name + '_api_key']: keyEl.value.trim() };
const resp = await fetch('/api/settings', {
method: 'POST', headers: {'Content-Type': 'application/json'},
credentials: 'include', body: JSON.stringify(payload)
});
if (resp.ok) showToast('✓ ' + name.charAt(0).toUpperCase() + name.slice(1) + ' saved — restart container to apply');
else showToast('✗ Save failed', true);
}
function selectTheme(el) {
document.querySelectorAll('.theme-chip').forEach(c => c.classList.remove('active'));
el.classList.add('active');
_selectedTheme = el.dataset.theme;
}
async function saveTheme() {
const resp = await fetch('/api/settings', {
method: 'POST', headers: {'Content-Type': 'application/json'},
credentials: 'include', body: JSON.stringify({ theme: _selectedTheme })
});
if (resp.ok) {
showToast('✓ Theme applied! Heading to dashboard…');
setTimeout(() => { window.location.href = '/'; }, 900);
} else {
showToast('✗ Save failed', true);
}
}
async function changePassword() {
const current = document.getElementById('pw_current').value;
const newPw = document.getElementById('pw_new').value;
const confirm = document.getElementById('pw_confirm').value;
const errEl = document.getElementById('pw-error');
errEl.style.display = 'none';
const resp = await fetch('/api/change-password', {
method: 'POST', headers: {'Content-Type': 'application/json'},
credentials: 'include', body: JSON.stringify({ current, new: newPw, confirm })
});
const data = await resp.json();
if (resp.ok) {
document.getElementById('pw_current').value = '';
document.getElementById('pw_new').value = '';
document.getElementById('pw_confirm').value = '';
showToast('✓ Password updated successfully');
} else {
errEl.textContent = data.error || 'Failed to update password';
errEl.style.display = 'block';
showToast('✗ ' + (data.error || 'Failed'), true);
}
}
</script>
</body>
</html>