This commit is contained in:
commit
854da073d4
15 files changed
+2885
No files matched your search
@@ -0,0 +1,64 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sheet Relay</title>
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header><div class="header-row"><div><p class="eyebrow">GOOGLE SHEETS TO API</p><h1>Sheet Relay</h1></div><label class="theme-toggle" title="Toggle dark mode"><input id="dark-mode" type="checkbox" aria-label="Toggle dark mode"><span class="theme-icon sun" aria-hidden="true"><i data-lucide="sun"></i></span><span class="theme-icon moon" aria-hidden="true"><i data-lucide="moon"></i></span></label></div><p>Choose exactly which spreadsheet tabs become authenticated JSON endpoints.</p></header>
|
||||
<section id="access-panel" class="panel">
|
||||
<h2>Admin access</h2><div class="inline"><input id="admin-token" type="password" placeholder="Admin token from server startup"><button id="connect" title="Connect to admin console">Connect</button></div><p id="status" class="status">Enter the token printed when the server first starts.</p>
|
||||
</section>
|
||||
<div id="console" hidden>
|
||||
<nav class="tabs" aria-label="Configuration pages"><button class="tab active" data-page="credentials">Credentials</button><button class="tab" data-page="documents">Documents</button><button class="tab" data-page="endpoints">Endpoints</button><button class="tab" data-page="operations">Operations</button></nav>
|
||||
<section class="page panel" id="credentials"><h2>Credential profiles</h2>
|
||||
<div class="two-up"><label>Profile name<input id="credential-name" placeholder="Production service account"></label><label>Requests per minute<input id="rpm" type="number" min="1" max="10000" value="45"></label></div>
|
||||
<div class="mode"><label><input type="radio" name="mode" value="apiKey" checked> API key</label><label><input type="radio" name="mode" value="serviceAccount"> Service account</label></div>
|
||||
<label id="api-key-field">Google API key<input id="api-key" type="password" autocomplete="off"></label>
|
||||
<label id="service-account-field">Service account JSON<textarea id="service-account" rows="5" spellcheck="false"></textarea></label>
|
||||
<label id="service-account-upload" class="upload">Upload service-account JSON<input id="service-account-file" type="file" accept="application/json,.json"></label>
|
||||
<button id="save-credential">Add credential profile</button><button id="cancel-credential" class="secondary" hidden>Cancel edit</button><p class="hint">45 RPM leaves room below a 50 RPM quota. API keys require publicly readable sheets. Share private sheets with the service account's client email.</p><div id="credentials-list" class="records"></div>
|
||||
</section>
|
||||
<section class="page panel" id="documents" hidden><h2>Google documents</h2>
|
||||
<div class="two-up"><label>Document name<input id="document-name" placeholder="Warehouse inventory"></label><label>Credential profile<select id="document-credential"></select></label></div>
|
||||
<label>Google Sheets URL<input id="document-url" type="url" placeholder="https://docs.google.com/spreadsheets/d/.../"></label>
|
||||
<button id="discover">Load sheet tabs</button><div id="sheet-picker" class="sheet-picker"></div><button id="save-document">Add document</button><button id="cancel-document" class="secondary" hidden>Cancel edit</button><p class="hint">Paste the spreadsheet URL. Select tabs by name after they are loaded.</p><div id="documents-list" class="records"></div>
|
||||
</section>
|
||||
<section class="page panel" id="endpoints" hidden><h2>Authenticated endpoints</h2>
|
||||
<div class="two-up"><label>Endpoint name<input id="publication-name" placeholder="Inventory feed"></label><label>Document<select id="publication-document"></select></label></div>
|
||||
<label>Include sheet tabs<div id="endpoint-sheet-picker" class="sheet-picker"></div></label>
|
||||
<div class="two-up"><label>Output format<select id="data-format"><option value="rows">Rows</option><option value="objects">Objects using header row</option></select></label><label>A1 range (optional)<input id="sheet-range" placeholder="A1:H500"></label></div>
|
||||
<label class="check-option"><input id="omit-blank-rows" type="checkbox"> Omit blank rows</label>
|
||||
<div class="two-up"><label>Cache seconds<input id="cache" type="number" min="0" max="86400" value="30"></label><label>Maximum rows per sheet<input id="max-rows" type="number" min="1" max="100000" value="10000"></label></div>
|
||||
<label>Consumer requests per minute<input id="consumer-rpm" type="number" min="1" max="100000" value="120"></label>
|
||||
<label class="check-option"><input id="scheduled-refresh" type="checkbox"> Background cache refresh</label>
|
||||
<button id="publish">Publish endpoint</button><button id="cancel-publication" class="secondary" hidden>Cancel edit</button><p class="hint">A bearer token is generated automatically. Copy it or rotate it from the endpoint controls after publishing.</p><details class="response-format"><summary>Response format</summary><pre>{
|
||||
"publication": "Inventory feed",
|
||||
"document": "Warehouse inventory",
|
||||
"spreadsheetId": "...",
|
||||
"updatedAt": "2026-09-17T12:00:00.000Z",
|
||||
"sheets": [{ "sheet": "OUTPUT", "values": [["Header"], ["Value"]] }],
|
||||
"cached": false
|
||||
}</pre></details><div id="publications" class="records"></div>
|
||||
</section>
|
||||
<section class="page panel" id="operations" hidden><h2>Operations</h2>
|
||||
<label>Allowed browser origins<textarea id="cors-origins" rows="4" spellcheck="false" placeholder="* https://app.example.com"></textarea></label>
|
||||
<label>Trusted reverse proxy hops<input id="trusted-proxy-hops" type="number" min="0" max="10" value="0"></label>
|
||||
<button id="save-settings">Save CORS settings</button><p class="hint">Browser origins are denied by default. Use * for all origins, or enter one exact origin per line to allow selected browser clients. Server-to-server callers without an Origin header are unaffected.</p>
|
||||
<p class="hint">Set proxy hops to 1 when one trusted reverse proxy is in front of this service. This makes request logs use the original client address from forwarded headers.</p>
|
||||
<h3>Administration</h3><div class="actions operations-actions"><button id="rotate-admin-token" class="danger">Rotate admin token</button><button id="export-config" class="secondary">Export redacted config</button><button id="import-config" class="secondary">Import config</button><input id="config-file" type="file" accept="application/json,.json" hidden></div>
|
||||
<p class="hint">Imports replace all existing configuration. Credentials are imported without their secrets, endpoints receive new tokens, and remain disabled until you explicitly enable them.</p>
|
||||
<h3>Metrics</h3><div class="status-card"><code id="metrics-url"></code></div>
|
||||
<h3>Service status</h3><div id="service-status" class="status-card">Loading status when this page opens.</div>
|
||||
<h3>Endpoint health</h3><div id="endpoint-health" class="request-log"></div>
|
||||
<h3>Recent API requests</h3><div class="request-filters"><select id="request-endpoint"><option value="">All endpoints</option></select><select id="request-status"><option value="">All statuses</option><option value="200">200</option><option value="401">401</option><option value="429">429</option><option value="502">502</option><option value="503">503</option></select><select id="request-result"><option value="">All results</option><option value="cached">Cached</option><option value="refresh">Refreshed</option><option value="stale">Stale</option></select><button id="clear-request-log" class="danger">Clear log</button></div><div id="request-log" class="request-log"></div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user