Files

192 lines
7.3 KiB
HTML

{% extends "base.html" %}
{% block title %}Settings — CalibreSync{% endblock %}
{% block content %}
<h1>Settings</h1>
{% if request.query_params.get("saved") %}
<div class="alert alert-success">Settings saved.</div>
{% endif %}
<form method="post" action="/settings">
<section class="form-section">
<h2>Remote host (SFTP)</h2>
<div class="form-row">
<label for="sftp_host">Host</label>
<input id="sftp_host" name="sftp_host" type="text" placeholder="192.168.1.10"
value="{{ s.get('sftp_host','') }}">
</div>
<div class="form-row">
<label for="sftp_port">Port</label>
<input id="sftp_port" name="sftp_port" type="number" value="{{ s.get('sftp_port','22') }}" style="width:6rem">
</div>
<div class="form-row">
<label for="sftp_user">Username</label>
<input id="sftp_user" name="sftp_user" type="text" value="{{ s.get('sftp_user','') }}">
</div>
<div class="form-row">
<label for="sftp_remote_path">Remote zip directory</label>
<input id="sftp_remote_path" name="sftp_remote_path" type="text" placeholder="/mnt/media/zips"
value="{{ s.get('sftp_remote_path','') }}">
</div>
<div class="form-row">
<label>Authentication</label>
<div class="radio-group">
<label>
<input type="radio" name="sftp_auth_method" value="key"
{% if s.get('sftp_auth_method','key') == 'key' %}checked{% endif %}
onchange="toggleAuth(this.value)">
SSH key
</label>
<label>
<input type="radio" name="sftp_auth_method" value="password"
{% if s.get('sftp_auth_method') == 'password' %}checked{% endif %}
onchange="toggleAuth(this.value)">
Password
</label>
</div>
</div>
<div class="form-row" id="row-key">
<label for="sftp_key">Private key (PEM)</label>
{% if has_key %}
<div class="key-status">
<span class="badge badge-uploaded">Key configured</span>
<code class="key-fingerprint">{{ key_fingerprint }}</code>
</div>
<details style="margin-top:0.5rem">
<summary class="muted small" style="cursor:pointer">Replace key</summary>
<textarea id="sftp_key" name="sftp_key" rows="8" style="margin-top:0.5rem"
placeholder="-----BEGIN RSA PRIVATE KEY-----&#10;...&#10;-----END RSA PRIVATE KEY-----"></textarea>
<p class="muted small">Leave blank to keep the existing key.</p>
</details>
{% else %}
<textarea id="sftp_key" name="sftp_key" rows="8"
placeholder="-----BEGIN RSA PRIVATE KEY-----&#10;...&#10;-----END RSA PRIVATE KEY-----"></textarea>
{% endif %}
</div>
<div class="form-row" id="row-password" style="display:none">
<label for="sftp_password">Password</label>
<input id="sftp_password" name="sftp_password" type="password"
value="{{ s.get('sftp_password','') }}">
</div>
<div class="form-row">
<button type="button" class="btn btn-secondary" onclick="testConn('ssh', this)">Test SSH connection</button>
<p id="test-ssh-result" class="test-result"></p>
</div>
</section>
<section class="form-section">
<h2>Grimmory</h2>
<div class="form-row">
<label for="grimmory_url">URL</label>
<input id="grimmory_url" name="grimmory_url" type="text" placeholder="http://192.168.1.10:6060"
value="{{ s.get('grimmory_url','') }}">
</div>
<div class="form-row">
<label for="grimmory_user">Username</label>
<input id="grimmory_user" name="grimmory_user" type="text"
value="{{ s.get('grimmory_user','') }}">
</div>
<div class="form-row">
<label for="grimmory_password">Password</label>
<input id="grimmory_password" name="grimmory_password" type="password"
value="{{ s.get('grimmory_password','') }}">
</div>
<div class="form-row">
<button type="button" class="btn btn-secondary" onclick="testConn('grimmory', this)">Test Grimmory connection</button>
<p id="test-grimmory-result" class="test-result"></p>
</div>
</section>
<section class="form-section">
<h2>Local</h2>
<div class="form-row">
<label for="work_dir">Temp work directory</label>
<input id="work_dir" name="work_dir" type="text" placeholder="/tmp/calibresync"
value="{{ s.get('work_dir','/tmp/calibresync') }}">
<p class="muted small">Temporary storage for downloaded zips and extracted files. Cleaned up after each run.</p>
</div>
</section>
<section class="form-section">
<h2>Automatic sync schedule</h2>
<div class="form-row">
<label for="scheduler_interval_minutes">Run every (minutes)</label>
<input id="scheduler_interval_minutes" name="scheduler_interval_minutes" type="number"
min="0" step="1" style="width:8rem"
value="{{ s.get('scheduler_interval_minutes','0') }}"
placeholder="0">
<p class="muted small">Set to 0 to disable automatic sync. Changes take effect immediately on save. Examples: 60 = hourly, 1440 = daily.</p>
</div>
<div class="form-row">
<label for="sync_batch_size">Batch size (zips per chunk)</label>
<input id="sync_batch_size" name="sync_batch_size" type="number"
min="0" step="1" style="width:8rem"
value="{{ s.get('sync_batch_size','0') }}"
placeholder="0">
<p class="muted small">Each sync run processes <strong>all</strong> unprocessed files, but works through them in chunks of this size to limit temp disk usage. Set to 0 to process all at once.</p>
</div>
</section>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save settings</button>
</div>
</form>
<section class="form-section danger-zone">
<h2>Danger zone</h2>
{% if request.query_params.get("reset") %}
<div class="alert alert-success">Sync data cleared — all processed zip and book records have been deleted.</div>
{% endif %}
<p class="muted small">Deletes all records of processed zips, uploaded books, and sync run history. Your settings are kept. Use this to start fresh after test runs.</p>
<form method="post" action="/settings/reset-sync-data" onsubmit="return confirm('Delete all sync data? This cannot be undone.')">
<button type="submit" class="btn btn-danger">Delete all sync data</button>
</form>
</section>
<script>
function toggleAuth(method) {
document.getElementById("row-key").style.display = method === "key" ? "" : "none";
document.getElementById("row-password").style.display = method === "password" ? "" : "none";
}
toggleAuth(document.querySelector('[name=sftp_auth_method]:checked')?.value || "key");
async function testConn(type, btn) {
const result = document.getElementById(`test-${type}-result`);
const originalText = btn.textContent;
btn.disabled = true;
btn.textContent = "Testing…";
result.className = "test-result";
result.textContent = "";
try {
const r = await fetch(`/api/test/${type}`);
const data = await r.json();
result.textContent = data.message;
result.className = "test-result " + (data.ok ? "test-ok" : "test-fail");
} catch (e) {
result.textContent = "Request failed: " + e;
result.className = "test-result test-fail";
} finally {
btn.disabled = false;
btn.textContent = originalText;
}
}
</script>
{% endblock %}