Files
minstrel/web/src/lib/components/NetworkSettingsCard.svelte
T
bvandeusenandClaude Opus 5.5 10d50dccbd docs: web comments say what the app does, not how it came to be (#6277)
Rewrites the web app's comments to rule 32. Task, milestone, rule and
spec ids are gone; regression stories are restated as the trap they
guard (public auth routes, the menu's stopPropagation, universal loads
under adapter-static, metric deltas with their margin). Flutter and
Tauri mentions now name the Android app or a native shell, operator
quotes and dates are dropped, and one library's counts no longer
appear outside test fixtures. Verified with a TypeScript-scanner token
comparison (template literals and regex literals re-scanned): only
comment text changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:42:12 -04:00

111 lines
3.9 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { plural } from '#lib/utils/plural.js';
import { onMount } from 'svelte';
import {
getNetworkSettings,
updateNetworkSettings,
type NetworkSettings
} from '#lib/api/admin.js';
import { pushToast } from '#lib/stores/toast.svelte.js';
import SettingsGroup from '#lib/components/settings/SettingsGroup.svelte';
import SettingRow from '#lib/components/settings/SettingRow.svelte';
// Client IP detection, on Admin › Email & network: decides which address
// is recorded for each sign-in on the Active sessions list.
const button =
'rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary disabled:opacity-50';
let settings = $state<NetworkSettings | null>(null);
let hops = $state(1);
let saving = $state(false);
let loadError = $state(false);
const dirty = $derived(!!settings && hops !== settings.trusted_proxy_hops);
const chain = $derived(
(settings?.forwarded_chain ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
);
// An admin can count their proxies from what actually arrived rather
// than guessing — one XFF entry per proxy in front of us.
const suggested = $derived(chain.length);
async function load() {
try {
settings = await getNetworkSettings();
hops = settings.trusted_proxy_hops;
loadError = false;
} catch {
loadError = true;
}
}
onMount(load);
async function save() {
saving = true;
try {
settings = await updateNetworkSettings(hops);
hops = settings.trusted_proxy_hops;
pushToast('Proxy depth saved.');
} catch {
pushToast("Couldn't save proxy depth.", 'error');
} finally {
saving = false;
}
}
</script>
<SettingsGroup title="Proxies" id="proxies" hint="How many reverse proxies sit in front of Minstrel.">
{#if loadError}
<p class="px-4 py-3 text-sm text-error-fg">
Couldn't load network settings.
<button type="button" class="underline hover:no-underline" onclick={load}>Try again</button>
</p>
{:else if settings === null}
<p class="px-4 py-3 text-sm text-text-secondary">Loading…</p>
{:else}
<SettingRow
label="Trusted proxies"
for="proxy-hops"
hint="0 is none, 1 a proxy such as nginx or Caddy, 2 a CDN in front of it."
>
<input
id="proxy-hops"
type="number"
min="0"
max={settings.max_hops}
bind:value={hops}
class="w-20 rounded-md border border-border bg-background px-2 py-1.5 text-sm"
/>
<button type="button" class={button} disabled={saving || !dirty} onclick={save}>
{saving ? 'Saving…' : 'Save'}
</button>
</SettingRow>
{#if suggested > 0 && settings.trusted_proxy_hops !== suggested}
<p class="px-4 py-3 text-sm text-text-primary">
This request arrived with {plural(suggested, 'forwarded address', 'forwarded addresses')}, so
{plural(suggested, 'proxy is', 'proxies are')} likely.
</p>
{/if}
<!-- Verification, not decoration: "the address Minstrel sees for you"
can be checked against the machine you're at. -->
<SettingRow label="Your address right now">
<span class="font-mono text-sm">{settings.detected_client_ip || 'unknown'}</span>
</SettingRow>
<SettingRow label="Direct connection from">
<span class="font-mono text-sm">{settings.remote_addr || 'unknown'}</span>
</SettingRow>
<SettingRow label="Forwarded chain">
<span class="break-all font-mono text-sm">{settings.forwarded_chain || 'none'}</span>
</SettingRow>
<!-- X-Forwarded-For is written by whoever connects, so a depth set too
high lets a visitor pick the address their session shows. -->
<p class="px-4 py-3 text-xs text-text-secondary">
Count your proxies, don't guess high: set too high, a visitor can choose the address their
session shows.
</p>
{/if}
</SettingsGroup>