chore: replace em dashes with hyphens, add no-em-dash rule to CLAUDE.md
This commit is contained in:
@@ -24,7 +24,7 @@ function formatDate(dateStr: string) {
|
||||
/*
|
||||
* One treatment for every event, whatever it is: the subsystem as a chip, then
|
||||
* what happened, in the same weight and size on every row. Colour carries the
|
||||
* severity of the act and nothing else, and never carries it alone — the
|
||||
* severity of the act and nothing else, and never carries it alone - the
|
||||
* sentence beside it says the same thing in words.
|
||||
*/
|
||||
function EventCell({ type }: { type: string }) {
|
||||
@@ -131,7 +131,7 @@ export default function AuditPage() {
|
||||
) : (
|
||||
<EmptyState
|
||||
title="No audit events recorded yet."
|
||||
description="Every mutating action — a key assigned, a workflow run, a member added — is written here as it happens."
|
||||
description="Every mutating action - a key assigned, a workflow run, a member added - is written here as it happens."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ export default function KeyDetailPage() {
|
||||
{/*
|
||||
* Typed, like the server and monitor deletes. Deleting a key revokes it
|
||||
* from every server at once, and for a generated key the stored private
|
||||
* half goes with it — there is no copy anywhere else, so this is the one
|
||||
* half goes with it - there is no copy anywhere else, so this is the one
|
||||
* delete on the fleet side that cannot be undone by re-uploading what
|
||||
* the operator already has.
|
||||
*/}
|
||||
|
||||
@@ -99,7 +99,7 @@ function niceCeiling(ms: number): number {
|
||||
|
||||
/*
|
||||
* The bar readout. A native title attribute arrives a second late, cannot show
|
||||
* the latency alongside the uptime, and is invisible to keyboard users — so the
|
||||
* the latency alongside the uptime, and is invisible to keyboard users - so the
|
||||
* hovered hour gets a real popover, anchored to its own bar.
|
||||
*/
|
||||
function SlotPopover({ slot, index, count }: { slot: Slot; index: number; count: number }) {
|
||||
@@ -151,7 +151,7 @@ function History({ slots, note }: { slots: Slot[]; note?: string }) {
|
||||
|
||||
/*
|
||||
* The trace breaks over hours with no checks rather than drawing a
|
||||
* straight line across them — a line implies data that was never
|
||||
* straight line across them - a line implies data that was never
|
||||
* collected.
|
||||
*/
|
||||
const segments: string[] = [];
|
||||
@@ -334,7 +334,7 @@ export default function MonitorDetailPage() {
|
||||
refetchInterval: 60_000,
|
||||
});
|
||||
|
||||
/* Only fetched for the ranges that read it — the 24h and 48h views are
|
||||
/* Only fetched for the ranges that read it - the 24h and 48h views are
|
||||
served by the rollups the page already holds. The 1h view refreshes on
|
||||
the check interval's order rather than the rollup's: at one minute per
|
||||
bar, a 60s poll is the difference between live and a bar behind. */
|
||||
@@ -411,7 +411,7 @@ export default function MonitorDetailPage() {
|
||||
|
||||
const all: Rollup[] = rollups ?? [];
|
||||
/* Marked with the incidents, so a bar is red only where the monitor was
|
||||
actually down — a failed check the retry policy absorbed stays amber. */
|
||||
actually down - a failed check the retry policy absorbed stays amber. */
|
||||
const slots = markIncidents(
|
||||
range.source === "rollups"
|
||||
? buildSlots(all, Math.round(range.minutes / 60))
|
||||
@@ -474,7 +474,7 @@ export default function MonitorDetailPage() {
|
||||
{/*
|
||||
* Typed, like the server and secret-group deletes. Uptime history
|
||||
* and incidents go with the monitor and there is nothing to restore
|
||||
* them from, and this button sits inches from "Pause checks" — the
|
||||
* them from, and this button sits inches from "Pause checks" - the
|
||||
* reversible action someone reaching for it usually wanted.
|
||||
*/}
|
||||
<ConfirmDialog
|
||||
@@ -526,7 +526,7 @@ export default function MonitorDetailPage() {
|
||||
<Figure label="Uptime 30d" value={formatPct(pct30d)} unit={pct30d !== null ? "%" : undefined} />
|
||||
<Figure
|
||||
label="Response now"
|
||||
value={status === "down" ? "—" : formatMs(latency)}
|
||||
value={status === "down" ? "-" : formatMs(latency)}
|
||||
tone={status === "down" ? "text-danger" : ""}
|
||||
/>
|
||||
<Figure
|
||||
@@ -593,7 +593,7 @@ export default function MonitorDetailPage() {
|
||||
<span className={`block h-2 w-2 rounded-full ${statusStripe[status]}`} />
|
||||
<p className="text-[13px] text-text-secondary">
|
||||
{status === "paused"
|
||||
? "Paused — no checks are running."
|
||||
? "Paused - no checks are running."
|
||||
: status === "down"
|
||||
? `Failing for ${monitor.state.fails} consecutive ${monitor.state.fails === 1 ? "check" : "checks"}.`
|
||||
: `Checked ${relativeTime(monitor.state.last_check_at)}.`}
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
* tens of checks, not thousands, and the alternative is a list endpoint that
|
||||
* embeds history for every row whether or not anyone looks at it.
|
||||
*
|
||||
* Groups are display only — a label on the monitor, nothing schedules or
|
||||
* Groups are display only - a label on the monitor, nothing schedules or
|
||||
* alerts by it. A fleet that never sets one sees the flat list it had before,
|
||||
* with no "Ungrouped" heading over the whole page.
|
||||
*/
|
||||
@@ -179,7 +179,7 @@ function GroupHeader({
|
||||
|
||||
function MonitorRow({ monitor, rollups, incidents }: MonitorRowData) {
|
||||
const status = displayStatus(monitor);
|
||||
/* Incidents, not raw check results, decide which hours read as down — see
|
||||
/* Incidents, not raw check results, decide which hours read as down - see
|
||||
markIncidents. */
|
||||
const slots = markIncidents(buildSlots(rollups), incidents);
|
||||
const pct = uptimePct(rollups.slice(-24));
|
||||
|
||||
@@ -199,7 +199,7 @@ function SecretRow({ group, secret }: { group: string; secret: Secret }) {
|
||||
title="Delete key"
|
||||
confirmLabel="Delete key"
|
||||
// Typed, like the group delete above it. A secret cannot be
|
||||
// read back before it is deleted — only revealed — so there
|
||||
// read back before it is deleted - only revealed - so there
|
||||
// is no way to put it back afterwards from anything Vantage
|
||||
// holds.
|
||||
requireTyped={secret.key}
|
||||
@@ -216,7 +216,7 @@ function SecretRow({ group, secret }: { group: string; secret: Secret }) {
|
||||
<span className="font-mono text-text-primary">{secret.key}</span> will be removed from the{" "}
|
||||
<span className="font-mono text-text-primary">{group}</span> group.
|
||||
</p>
|
||||
<p>Anything reading this key — a workflow step, an API consumer, an external sync — starts failing at its next run.</p>
|
||||
<p>Anything reading this key - a workflow step, an API consumer, an external sync - starts failing at its next run.</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -331,7 +331,7 @@ export default function SecretGroupPage() {
|
||||
title="Delete secret group"
|
||||
confirmLabel="Delete group"
|
||||
// No undo, and the blast radius is every consumer of the group
|
||||
// rather than one key — so the name has to be typed.
|
||||
// rather than one key - so the name has to be typed.
|
||||
requireTyped={group}
|
||||
loading={deleting}
|
||||
error={deleteError ? friendlyMessage(deleteError) : null}
|
||||
|
||||
@@ -343,7 +343,7 @@ export default function ServerConsolePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* The viewport is always mounted — Guacamole attaches its display
|
||||
{/* The viewport is always mounted - Guacamole attaches its display
|
||||
element to it on connect, so it cannot be conditionally rendered.
|
||||
Anything the operator needs to be told is layered over it
|
||||
instead, which is what a bare black rectangle never did. */}
|
||||
|
||||
@@ -23,8 +23,8 @@ import { MaintenanceTab } from "@/components/servers/tabs/MaintenanceTab";
|
||||
/*
|
||||
* One server, as a faceplate over five tabs.
|
||||
*
|
||||
* The page used to stack every panel it had — agent updater, inventory,
|
||||
* details, vulnerabilities, workloads, keys — so the answer to "is this machine
|
||||
* The page used to stack every panel it had - agent updater, inventory,
|
||||
* details, vulnerabilities, workloads, keys - so the answer to "is this machine
|
||||
* healthy" was several screens below the answer to "which agent build is on
|
||||
* it". Identity, status and the four live readings now stay pinned; everything
|
||||
* else is a tab, and the tab labels carry counts so a problem on a tab nobody
|
||||
@@ -65,7 +65,7 @@ export default function ServerDetailPage() {
|
||||
const activeTab: TabId = tabParam && TAB_IDS.includes(tabParam) ? tabParam : "overview";
|
||||
|
||||
/** The tab lives in the URL so an alert, a bookmark or a browser Back can
|
||||
* name one. replace, not push — five tabs of history between two pages is
|
||||
* name one. replace, not push - five tabs of history between two pages is
|
||||
* a Back button that does not go back. */
|
||||
function selectTab(tab: TabId) {
|
||||
const next = new URLSearchParams(searchParams.toString());
|
||||
|
||||
@@ -73,7 +73,7 @@ const DOT_SHORT: Record<DotStatus, string> = {
|
||||
* The dot alone was the whole control: four meanings carried by hue, with the
|
||||
* distinction living in a `title` a touch user never sees and a screen reader
|
||||
* is not obliged to announce. This is the one rule the design system states
|
||||
* outright — state never reads by colour alone — so the label is now part of
|
||||
* outright - state never reads by colour alone - so the label is now part of
|
||||
* the component rather than something each page remembers to add.
|
||||
*/
|
||||
function StatusDot({ status }: { status: DotStatus }) {
|
||||
@@ -104,7 +104,7 @@ function formatLastSeen(dateStr: string): string {
|
||||
}
|
||||
|
||||
// useSearchParams opts this page into client-side bailout, which the App
|
||||
// Router only permits inside a Suspense boundary — hence the wrapper at the
|
||||
// Router only permits inside a Suspense boundary - hence the wrapper at the
|
||||
// bottom of this file rather than a bare default export.
|
||||
function ServersPageBody() {
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
@@ -90,7 +90,7 @@ function ChannelForm({ initial, onDone }: { initial?: NotificationChannel; onDon
|
||||
className={inputClass}
|
||||
type={field === "password" ? "password" : "text"}
|
||||
value={unchanged ? "" : value}
|
||||
placeholder={unchanged ? "unchanged — type to replace" : undefined}
|
||||
placeholder={unchanged ? "unchanged - type to replace" : undefined}
|
||||
onChange={(e) => setConfig({ ...config, [field]: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
@@ -182,7 +182,7 @@ function ChannelRow({ ch }: { ch: NotificationChannel }) {
|
||||
{/*
|
||||
* Not typed, unlike a server or a secret: a channel is a name and a URL
|
||||
* and can be recreated in a minute. What it does need is any confirmation
|
||||
* at all — Delete sat inches from Test and Disable and fired on one
|
||||
* at all - Delete sat inches from Test and Disable and fired on one
|
||||
* click, silently detaching every monitor that alerted through it.
|
||||
*/}
|
||||
<ConfirmDialog
|
||||
|
||||
@@ -229,7 +229,7 @@ export default function SettingsPage() {
|
||||
if (!settings) return;
|
||||
// Same in-progress form state the main Save button submits
|
||||
// (handleSubmit below), not the stale loaded `settings`
|
||||
// object — otherwise an unsaved edit to the offline
|
||||
// object - otherwise an unsaved edit to the offline
|
||||
// threshold or retention days is silently reverted the
|
||||
// moment this toggle is flipped.
|
||||
save({ ...currentPayload(), local_login_enabled: v });
|
||||
@@ -298,7 +298,7 @@ export default function SettingsPage() {
|
||||
</SectionCard>
|
||||
|
||||
{/* The cap lives here rather than on /tokens because it is
|
||||
instance policy, not one person's credentials — which is
|
||||
instance policy, not one person's credentials - which is
|
||||
also what lets that page be reachable at every role. */}
|
||||
<SectionCard
|
||||
title="API keys"
|
||||
@@ -307,7 +307,7 @@ export default function SettingsPage() {
|
||||
>
|
||||
<Field
|
||||
label="Maximum API key lifetime (days)"
|
||||
hint="0 means no cap, and keys may be created with no expiry. Changing this affects new keys only — existing keys keep working and are flagged for rotation."
|
||||
hint="0 means no cap, and keys may be created with no expiry. Changing this affects new keys only - existing keys keep working and are flagged for rotation."
|
||||
>
|
||||
<input type="number" min={0} value={apiTokenMaxDays} onChange={(e) => setApiTokenMaxDays(Number(e.target.value))} className={numberInputClass} />
|
||||
</Field>
|
||||
|
||||
@@ -27,7 +27,7 @@ import { relativeTime } from "@/components/monitors/MonitorVisuals";
|
||||
/*
|
||||
* Details and Components are edited as one local draft and saved together by
|
||||
* the single "Save changes" button in the header, matching artboard 2 of the
|
||||
* mockup. Incidents are their own timeline and mutate immediately — opening
|
||||
* mockup. Incidents are their own timeline and mutate immediately - opening
|
||||
* one, posting an update or editing a maintenance window has no "unsaved"
|
||||
* state to lose, so there is nothing to batch.
|
||||
*/
|
||||
@@ -82,7 +82,7 @@ function unnamedSectionIndex(draft: Draft): number {
|
||||
}
|
||||
|
||||
/*
|
||||
* A blank display_name does NOT fall back to the monitor's name on the server —
|
||||
* A blank display_name does NOT fall back to the monitor's name on the server -
|
||||
* it publishes the raw monitor UUID, deliberately, because publishing an
|
||||
* internal name has to be a decision rather than a default. So the editor
|
||||
* refuses to save one instead of letting an operator add five monitors and
|
||||
@@ -102,7 +102,7 @@ function unnamedComponent(draft: Draft): { section: number; entry: number } | nu
|
||||
* An incident's affected components are the PAGE's components, not the fleet's
|
||||
* monitors: naming a monitor the page never listed publishes a machine the page
|
||||
* deliberately does not, which is the leak assembleSnapshot exists to prevent,
|
||||
* reached from the authoring side. services.checkAffectedOnPages refuses it —
|
||||
* reached from the authoring side. services.checkAffectedOnPages refuses it -
|
||||
* this is what stops an operator getting that far.
|
||||
*
|
||||
* It reads the SAVED page rather than the draft. A component added in the
|
||||
@@ -110,7 +110,7 @@ function unnamedComponent(draft: Draft): { section: number; entry: number } | nu
|
||||
* incident the server refuses.
|
||||
*
|
||||
* The label is the per-page display name, which is the name the reader will see
|
||||
* — the monitor's own name is internal and may differ.
|
||||
* - the monitor's own name is internal and may differ.
|
||||
*/
|
||||
interface PageComponent {
|
||||
monitorId: string;
|
||||
@@ -185,7 +185,7 @@ function DetailsPanel({ draft, setDraft, pageId }: { draft: Draft; setDraft: (d:
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-text-secondary">Page address</label>
|
||||
<input type="text" value={pageId} disabled className={`${inputClass} font-mono opacity-60`} />
|
||||
<p className="mt-1 text-xs text-text-tertiary">Fixed once created — the link is already out there.</p>
|
||||
<p className="mt-1 text-xs text-text-tertiary">Fixed once created - the link is already out there.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-text-secondary">Description</label>
|
||||
@@ -326,7 +326,7 @@ function ComponentsPanel({
|
||||
<h2 className="text-base font-bold tracking-[-0.02em] text-text-primary">Components</h2>
|
||||
<p className="mt-0.5 text-sm text-text-secondary">
|
||||
Monitors grouped for the public page. Grouping here is separate from the groups on Monitors.
|
||||
Each component needs a public name — monitor names are never published for you.
|
||||
Each component needs a public name - monitor names are never published for you.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -556,7 +556,7 @@ function IncidentFormModal({
|
||||
className="h-4 w-4 accent-accent"
|
||||
/>
|
||||
<span className="text-sm text-text-primary">{c.label}</span>
|
||||
{c.stale && <span className="text-xs text-warning">No longer on this page — uncheck to save</span>}
|
||||
{c.stale && <span className="text-xs text-warning">No longer on this page - uncheck to save</span>}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
@@ -694,7 +694,7 @@ function DeleteIncidentButton({ pageId, incident }: { pageId: string; incident:
|
||||
page it was published to, along with its updates.
|
||||
</p>
|
||||
<p>
|
||||
To leave the record standing but close it out, mark it resolved instead — the public page
|
||||
To leave the record standing but close it out, mark it resolved instead - the public page
|
||||
files a resolved incident under its history.
|
||||
</p>
|
||||
</>
|
||||
@@ -864,7 +864,7 @@ export default function StatusPageEditorPage() {
|
||||
/*
|
||||
* Delete is the only correction for a typo'd page address: the address is
|
||||
* immutable by design, because it is a URL handed to customers. Typed, like
|
||||
* the monitor and secret-group deletes, and for the same reason — the page,
|
||||
* the monitor and secret-group deletes, and for the same reason - the page,
|
||||
* its components and its authored incidents go together and there is
|
||||
* nothing to restore them from.
|
||||
*/
|
||||
|
||||
@@ -107,7 +107,7 @@ function CreateStatusPageModal({ onClose }: { onClose: () => void }) {
|
||||
className="w-full rounded border border-border bg-surface-2 px-3 py-2 font-mono text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-text-tertiary">
|
||||
{`Becomes ${publicUrl(pageId || "<address>")}. Fixed once created — lowercase letters, numbers and hyphens, 3-40 characters.`}
|
||||
{`Becomes ${publicUrl(pageId || "<address>")}. Fixed once created - lowercase letters, numbers and hyphens, 3-40 characters.`}
|
||||
</p>
|
||||
{touched && pageId.length > 0 && !idValid && <p className="mt-1 text-xs text-danger">Not a valid page address.</p>}
|
||||
</div>
|
||||
|
||||
@@ -191,7 +191,7 @@ export default function StepsPage() {
|
||||
<Td label="Outputs">
|
||||
<span className="flex flex-wrap gap-1">
|
||||
{(s.declared_outputs ?? []).length === 0 ? (
|
||||
<span className="text-text-tertiary">—</span>
|
||||
<span className="text-text-tertiary">-</span>
|
||||
) : (
|
||||
(s.declared_outputs ?? []).map((o) => (
|
||||
<span key={o} className="rounded-sm border border-signal/35 px-1.5 py-0.5 font-mono text-[10px] text-signal">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ApiKeysPanel } from "@/components/apikeys/ApiKeysPanel";
|
||||
|
||||
/**
|
||||
* Reachable at every role, unlike /settings. Any member may mint and revoke
|
||||
* their own API keys — the API has never required owner or admin for that —
|
||||
* their own API keys - the API has never required owner or admin for that -
|
||||
* and owner and admin additionally see every key in the instance.
|
||||
*/
|
||||
export default function ApiKeysPage() {
|
||||
|
||||
@@ -216,7 +216,7 @@ export default function VulnerabilitiesPage() {
|
||||
</p>
|
||||
{/* Named explicitly, because "no findings" under a filter
|
||||
the reader has forgotten setting reads as a clean
|
||||
fleet — the one claim this page must never make by
|
||||
fleet - the one claim this page must never make by
|
||||
accident. */}
|
||||
<p className="mx-auto mt-2 max-w-[52ch] text-sm text-text-secondary">
|
||||
{hasFix !== undefined
|
||||
|
||||
@@ -349,7 +349,7 @@ export default function WorkflowBuilder() {
|
||||
<span className="text-text-secondary">{v}</span>
|
||||
</span>
|
||||
))}
|
||||
{Object.keys(targetTags).length === 0 && <p className="text-xs text-text-secondary">No tag selector — only the named servers will run.</p>}
|
||||
{Object.keys(targetTags).length === 0 && <p className="text-xs text-text-secondary">No tag selector - only the named servers will run.</p>}
|
||||
</div>
|
||||
|
||||
<p className="mt-3 font-mono text-xs text-text-secondary" title={matched.map((s) => s.hostname).join("\n")}>
|
||||
|
||||
@@ -93,8 +93,8 @@ export default function WorkloadsPage() {
|
||||
<Badge variant="neutral">{h.workload.kind}</Badge>
|
||||
</Td>
|
||||
<Td>{h.workload.state}</Td>
|
||||
<Td className="font-mono text-xs">{h.workload.image ?? "—"}</Td>
|
||||
<Td>{h.workload.stack ?? "—"}</Td>
|
||||
<Td className="font-mono text-xs">{h.workload.image ?? "-"}</Td>
|
||||
<Td>{h.workload.stack ?? "-"}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@
|
||||
/* ==========================================================================
|
||||
Vantage control plane design tokens.
|
||||
|
||||
These are site/app/globals.css's **dark** token values — same names, same
|
||||
colours — so the marketing site, the HQ console and this app are one visual
|
||||
These are site/app/globals.css's **dark** token values - same names, same
|
||||
colours - so the marketing site, the HQ console and this app are one visual
|
||||
system. Change them in all three apps in the same commit; nothing enforces
|
||||
the match automatically.
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
site/ brightens the accent with a filter, which a Tailwind colour token
|
||||
cannot do, so the two hovers are named values. --well is the floor the
|
||||
ground sits on — install one-liners, key blobs and run logs, the places
|
||||
ground sits on - install one-liners, key blobs and run logs, the places
|
||||
showing machine output rather than interface. */
|
||||
--accent-hover-rgb: 127 178 240; /* #7fb2f0, the accent at site/'s hover brightness */
|
||||
--down-hover-rgb: 200 90 69; /* #c85a45 */
|
||||
|
||||
@@ -3,7 +3,7 @@ import { NextResponse } from "next/server";
|
||||
// Liveness and readiness for the Next server itself. Deliberately at /healthz
|
||||
// and not /api/healthz: next.config.ts rewrites the whole of /api to the Go
|
||||
// server, so a probe there would report the backend's health instead of this
|
||||
// process's — and would keep passing while this pod was wedged.
|
||||
// process's - and would keep passing while this pod was wedged.
|
||||
//
|
||||
// It answers without touching the backend on purpose. web is stateless; a
|
||||
// backend outage must not take every web replica out of its Service as well,
|
||||
|
||||
@@ -16,7 +16,7 @@ type FetchResult = { kind: "ok"; snapshot: StatusSnapshot } | { kind: "not-found
|
||||
* next.config.ts): /public is routed to it by the reverse proxy, exactly as
|
||||
* /api and /auth are for the browser. So the SSR fetch goes back through that
|
||||
* proxy at <slug>.vantage.<tld>, which is a per-tenant address by
|
||||
* construction — and X-Forwarded-Host below is still what selects the tenant,
|
||||
* construction - and X-Forwarded-Host below is still what selects the tenant,
|
||||
* because the hop from this process cannot set Host.
|
||||
*/
|
||||
function apiBase(proto: string, host: string): string {
|
||||
@@ -25,7 +25,7 @@ function apiBase(proto: string, host: string): string {
|
||||
|
||||
async function fetchSnapshot(base: string, host: string, forwardedFor: string, pageId: string): Promise<FetchResult> {
|
||||
// The instance is resolved server-side from the visitor's host, so it has
|
||||
// to be forwarded explicitly — this is a server-to-server call and its own
|
||||
// to be forwarded explicitly - this is a server-to-server call and its own
|
||||
// Host names the Go service.
|
||||
//
|
||||
// It goes in X-Forwarded-Host and NOT in Host: `Host` is a forbidden header
|
||||
@@ -36,7 +36,7 @@ async function fetchSnapshot(base: string, host: string, forwardedFor: string, p
|
||||
|
||||
// Likewise the visitor's own address. Without it the Go server sees a
|
||||
// request from this pod with no XFF and rate limits every visitor of every
|
||||
// page into one 120/min bucket — tripped by exactly the traffic an outage
|
||||
// page into one 120/min bucket - tripped by exactly the traffic an outage
|
||||
// produces. Appending rather than replacing keeps the chain in front of us
|
||||
// intact.
|
||||
if (forwardedFor) outbound["X-Forwarded-For"] = forwardedFor;
|
||||
|
||||
Reference in New Issue
Block a user