Initial Sheet Relay application
Publish container image / publish (push) Failing after 26s

This commit is contained in:
lzstealth committed 2026-09-17 18:54:22 +01:00
commit 854da073d4
15 files changed
+2885

No files matched your search

+64
View File
@@ -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="*&#10;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>