summaryrefslogtreecommitdiff
path: root/internal/adminui/assets/index.html
blob: 8f31e1c1536388bcf4e5148882adfa9154ff7da6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>AIGW Control Plane</title>
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23102a3a'/%3E%3Ctext x='16' y='22' text-anchor='middle' font-family='Arial' font-weight='700' font-size='18' fill='%23b8eef7'%3EA%3C/text%3E%3C/svg%3E">
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <section class="auth-screen" id="auth-screen">
    <div class="auth-brand"><span class="brand-mark">A</span><div><strong>AIGW</strong><small>CONTROL PLANE</small></div></div>
    <div class="auth-panel">
      <div class="auth-tabs" role="tablist" aria-label="Account access">
        <button class="auth-tab active" type="button" data-auth-pane="login-pane">Sign in</button>
        <button class="auth-tab" type="button" data-auth-pane="register-pane" id="register-tab">Create account</button>
        <button class="auth-tab" type="button" data-auth-pane="reset-request-pane">Reset password</button>
        <button class="auth-tab" type="button" data-auth-pane="invite-pane">Accept invite</button>
        <button class="auth-tab" type="button" data-auth-pane="bootstrap-pane">Operator</button>
      </div>
      <form class="auth-pane active" id="login-pane">
        <div><span class="eyebrow">ACCOUNT ACCESS</span><h1>Sign in</h1></div>
        <label>Email<input name="email" type="email" required autocomplete="username" placeholder="you@company.com"></label>
        <label>Password<input name="password" type="password" required autocomplete="current-password"></label>
        <div class="form-actions"><button class="button primary" type="submit">Sign in</button><button class="button subtle" type="button" id="passkey-login">Use passkey</button></div>
      </form>
      <form class="auth-pane" id="register-pane">
        <div><span class="eyebrow">NEW WORKSPACE</span><h1>Create account</h1></div>
        <label>Organization<input name="organization" required autocomplete="organization" placeholder="Acme Inc."></label>
        <label>Workspace slug<input name="tenant_slug" required pattern="[a-z0-9][a-z0-9\-]{1,62}[a-z0-9]" placeholder="acme"></label>
        <label>Your name<input name="display_name" required autocomplete="name"></label>
        <label>Email<input name="email" type="email" required autocomplete="email"></label>
        <label>Password<input name="password" type="password" required minlength="12" maxlength="128" autocomplete="new-password"></label>
        <button class="button primary" type="submit">Create account</button>
      </form>
      <form class="auth-pane" id="mfa-pane">
        <div><span class="eyebrow">SECOND FACTOR</span><h1>Verify sign in</h1></div>
        <label>Code<input name="code" inputmode="numeric" autocomplete="one-time-code" required></label>
        <div class="form-actions"><button class="button primary" type="submit">Verify</button><button class="button subtle" type="button" id="mfa-passkey">Use passkey</button></div>
      </form>
      <form class="auth-pane" id="reset-request-pane">
        <div><span class="eyebrow">ACCOUNT RECOVERY</span><h1>Reset password</h1></div>
        <label>Email<input name="email" type="email" required autocomplete="email"></label>
        <button class="button primary" type="submit">Send reset link</button>
      </form>
      <form class="auth-pane" id="reset-complete-pane">
        <div><span class="eyebrow">ACCOUNT RECOVERY</span><h1>Choose a new password</h1></div>
        <input name="token" id="reset-token" type="hidden">
		<input class="visually-hidden" name="username" autocomplete="username" aria-hidden="true" tabindex="-1">
        <label>New password<input name="new_password" type="password" minlength="12" maxlength="128" required autocomplete="new-password"></label>
        <button class="button primary" type="submit">Update password</button>
      </form>
      <form class="auth-pane" id="invite-pane">
        <div><span class="eyebrow">TEAM ACCESS</span><h1>Accept invitation</h1></div>
        <input name="token" id="invite-token" type="hidden">
		<input class="visually-hidden" name="username" autocomplete="username" aria-hidden="true" tabindex="-1">
        <label>Name<input name="display_name" required autocomplete="name"></label>
        <label>Password<input name="password" type="password" minlength="12" maxlength="128" required autocomplete="new-password"></label>
        <button class="button primary" type="submit">Join workspace</button>
      </form>
      <form class="auth-pane" id="bootstrap-pane">
        <div><span class="eyebrow">BREAK GLASS</span><h1>Operator access</h1></div>
        <input class="visually-hidden" name="username" autocomplete="username" value="aigw-operator" aria-hidden="true" tabindex="-1">
        <label>Bootstrap token<input name="token" type="password" required autocomplete="off"></label>
        <button class="button primary" type="submit">Continue</button>
      </form>
      <p class="auth-error" id="auth-error" role="alert"></p>
    </div>
  </section>

  <div id="console-app" class="hidden">
  <header class="topbar">
    <div class="brand"><span class="brand-mark">A</span><div><strong>AIGW</strong><small>CONTROL PLANE</small></div></div>
    <div class="session"><div><strong id="actor-label" class="actor-label"></strong><span id="connection-state" class="state"></span></div><button type="button" id="sign-out">Sign out</button></div>
  </header>
  <main class="shell">
    <nav class="tabs" aria-label="Admin sections">
      <button class="tab active" data-section="quickstart">Quickstart</button>
      <button class="tab" data-section="overview">Overview</button>
      <button class="tab" data-section="catalog">Model catalog</button>
      <button class="tab" data-section="usage" data-permission="usage.read">Usage</button>
      <button class="tab" data-section="billing" data-permission="billing.read" id="billing-tab">Billing</button>
      <button class="tab" data-section="projects" data-permission="projects.read">Projects</button>
      <button class="tab" data-section="keys" data-permission="keys.read">API keys</button>
      <button class="tab" data-section="limits" data-permission="limits.read">Limits</button>
      <button class="tab" data-section="team" data-permission="users.read">Team</button>
      <button class="tab" data-section="account" id="account-tab">Account</button>
      <button class="tab" data-section="audit" data-permission="audit.read">Audit</button>
      <button class="tab" data-section="tenants" data-permission="tenants.read">Tenants</button>
      <button class="tab" data-section="providers" data-permission="platform.read">Providers</button>
      <button class="tab" data-section="models" data-permission="platform.read">Routing</button>
    </nav>

    <section id="quickstart" class="section active">
      <div class="section-heading"><div><span class="eyebrow">DEVELOPER WORKSPACE</span><h1>Start building</h1></div><div class="form-actions"><span class="currency-label" id="quickstart-balance"></span><button class="button secondary" type="button" id="quickstart-topup" data-goto="billing" data-permission="billing.topup">Add funds</button></div></div>
      <div class="onboarding panel" id="onboarding"></div>
      <div class="quick-access-grid">
        <form class="panel starter-key-panel" id="starter-key-form" data-permission="keys.write">
          <div class="section-heading"><div><span class="eyebrow">API ACCESS</span><h2>Create a starter key</h2></div><span class="badge" id="starter-model-label"></span></div>
          <div class="starter-key-fields"><label>Project<select id="starter-project" required></select></label><label>Key name<input id="starter-key-name" maxlength="120" value="Quickstart key" required></label><button class="button primary" id="starter-key-submit" type="submit">Create & use key</button></div>
          <p class="muted">The key is limited to the selected Quickstart model and placed in the Playground for this page only.</p>
        </form>
        <section class="panel endpoint-panel">
          <div class="section-heading"><div><span class="eyebrow">CONNECTION</span><h2>API endpoints</h2></div><button class="button subtle" type="button" id="copy-endpoint-env">Copy environment</button></div>
          <div class="endpoint-list" id="endpoint-list"></div>
          <pre class="endpoint-env"><code id="endpoint-env"></code></pre>
        </section>
      </div>
      <div class="quickstart-grid">
        <div class="panel quickstart-code">
          <div class="section-heading"><div><span class="eyebrow">FIRST REQUEST</span><h2>Copy a working example</h2></div><button class="button subtle" type="button" id="copy-quickstart">Copy</button></div>
          <div class="form-grid compact-form"><label>Model<select id="quickstart-model"></select></label><label>Protocol<select id="quickstart-protocol"></select></label><label>Provider<select id="quickstart-provider"></select></label><label>Language<select id="quickstart-language"><option value="curl">cURL</option><option value="python">Python</option><option value="node">Node.js</option></select></label></div>
          <pre id="quickstart-code-block"><code></code></pre>
          <p class="muted">Use an active API key in <code>AIGW_API_KEY</code>. The example never stores your key in the browser.</p>
        </div>
        <div class="panel quickstart-next"><span class="eyebrow">WORKSPACE SIGNALS</span><h2>What to do next</h2><div id="quickstart-signals" class="signal-list"></div></div>
      </div>
      <form class="panel playground" id="playground-form">
        <div class="section-heading"><div><span class="eyebrow">LIVE API</span><h2>API playground</h2></div><span class="currency-label" id="playground-endpoint"></span></div>
        <div class="playground-fields">
          <label class="playground-key">API key<input id="playground-key" type="password" autocomplete="off" spellcheck="false" required placeholder="sk-aigw-..."></label>
          <label>Model<select id="playground-model" required></select></label>
          <label>Protocol<select id="playground-protocol" required></select></label>
          <label>Provider<select id="playground-provider"></select></label>
          <label class="playground-prompt">Prompt<textarea id="playground-prompt" required rows="4">Reply with exactly: AIGW ready</textarea></label>
          <div class="playground-actions"><label>Max output<input id="playground-max-output" type="number" min="1" max="4096" value="128" required></label><button class="button primary" id="playground-send" type="submit">Send request</button><button class="button secondary hidden" id="playground-stop" type="button">Stop</button></div>
        </div>
        <div class="playground-result hidden" id="playground-result" aria-live="polite">
          <div class="playground-meta"><strong class="badge" id="playground-status"></strong><span id="playground-request-id"></span><span id="playground-duration"></span><span id="playground-tokens"></span></div>
          <pre><code id="playground-response"></code></pre>
          <div class="playground-diagnostic hidden" id="playground-diagnostic"><div><strong id="playground-diagnostic-title"></strong><p id="playground-diagnostic-message"></p></div><button class="button secondary hidden" id="playground-diagnostic-action" type="button"></button></div>
        </div>
      </form>
      <div class="section-heading ledger-heading"><div><span class="eyebrow">DEFAULTS & ALERTS</span><h2>Workspace preferences</h2></div></div>
      <div class="preferences-grid">
        <form class="panel form-grid compact-form" id="developer-preferences-form" data-permission="preferences.read">
          <h2>API defaults</h2>
          <label>Default model<select id="preference-default-model" name="default_model"></select></label>
          <label>Fallback model<select id="preference-fallback-model" name="fallback_model"></select></label>
          <p class="muted form-note">These defaults drive workspace examples. Your API request can still select any model available to this tenant.</p>
          <button class="button primary" type="submit" data-permission="developer.preferences.write">Save API defaults</button>
        </form>
        <form class="panel form-grid compact-form" id="billing-preferences-form" data-permission="billing.read">
          <h2>Balance alert</h2>
          <label class="toggle-row"><input id="low-balance-enabled" name="low_balance_enabled" type="checkbox"><span>Email billing members when available balance is low</span></label>
          <label>Alert below<input id="low-balance-threshold" name="low_balance_threshold" inputmode="decimal" required></label>
          <p class="muted form-note" id="balance-alert-status"></p>
          <button class="button primary" type="submit" data-permission="billing.preferences.write">Save balance alert</button>
        </form>
      </div>
      <div class="section-heading ledger-heading"><div><span class="eyebrow">RECENT ACTIVITY</span><h2>Latest requests</h2></div><button class="button subtle" type="button" data-goto="usage">View all usage</button></div>
      <div class="panel table-wrap"><table><thead><tr><th>Time</th><th>Model</th><th>Status</th><th>Tokens</th><th>Charged</th><th>Latency</th></tr></thead><tbody id="quickstart-usage-body"></tbody></table></div>
    </section>

    <section id="catalog" class="section">
      <div class="section-heading"><div><span class="eyebrow">DISCOVER</span><h1>Model catalog</h1></div><span class="currency-label" id="catalog-count"></span></div>
      <div class="panel catalog-toolbar"><label>Search models<input id="catalog-search" type="search" placeholder="Search by model, developer, capability"></label><label>Protocol<select id="catalog-protocol"><option value="">All protocols</option><option value="chat_completions">OpenAI Chat Completions</option><option value="responses">OpenAI Responses</option><option value="messages">Anthropic Messages</option></select></label><label>Input<select id="catalog-input"><option value="">Any input</option><option value="text">Text</option><option value="image">Image</option><option value="audio">Audio</option><option value="video">Video</option></select></label><label>Developer<select id="catalog-owner"><option value="">All developers</option></select></label><label>Sort<select id="catalog-sort"><option value="newest">Newest</option><option value="name">Name</option><option value="input_price">Lowest input price</option><option value="output_price">Lowest output price</option><option value="context">Largest context</option></select></label></div>
      <div id="catalog-grid" class="catalog-grid"></div>
    </section>

    <section id="overview" class="section">
      <div class="section-heading"><div><span class="eyebrow">OPERATIONS</span><h1>Account overview</h1></div><button class="button secondary" id="reload" data-permission="platform.write">Reload snapshot</button></div>
      <div class="metric-grid" id="metrics"></div>
      <div class="section-heading ledger-heading"><div><span class="eyebrow">CURRENT PERIOD</span><h2>Project usage</h2></div></div>
      <div class="panel table-wrap"><table><thead><tr><th>Project</th><th>Requests</th><th>Success</th><th>Tokens</th><th>Cost</th><th>Uncollected</th></tr></thead><tbody id="overview-usage-body"></tbody></table></div>
    </section>

    <section id="usage" class="section">
      <div class="section-heading"><div><span class="eyebrow">METERING</span><h1>Usage ledger</h1></div></div>
      <form class="panel usage-filters" id="usage-filter-form">
        <label>From<input id="usage-from" name="from" type="date"></label>
        <label>To<input id="usage-to" name="to" type="date"></label>
        <label>Project<select id="usage-project" name="project_id"><option value="">All projects</option></select></label>
        <label>API key<select id="usage-key" name="key_id"><option value="">All API keys</option></select></label>
        <label>Model<select id="usage-model" name="model"><option value="">All models</option></select></label>
        <label>Provider<select id="usage-provider" name="provider"><option value="">All providers</option></select></label>
        <label>Protocol<select id="usage-protocol" name="protocol"><option value="">All protocols</option><option value="openai">OpenAI Chat Completions</option><option value="openai_responses">OpenAI Responses</option><option value="anthropic">Anthropic Messages</option></select></label>
        <label>Transport<select id="usage-stream" name="stream"><option value="">Streaming and non-streaming</option><option value="true">Streaming</option><option value="false">Non-streaming</option></select></label>
        <label>Status<select id="usage-status" name="status"><option value="">All statuses</option><option value="success">Successful</option><option value="error">Errors</option></select></label>
        <label>Error type<input id="usage-error-type" name="error_type" placeholder="provider_error"></label>
        <label class="usage-request-filter">Request ID<input id="usage-request-id" name="request_id" placeholder="req_..."></label>
        <div class="form-actions"><button class="button primary" type="submit">Apply filters</button><button class="button subtle" id="usage-filter-reset" type="button">Reset</button></div>
      </form>
      <div class="metric-grid usage-metrics" id="usage-metrics"></div>
      <div class="panel usage-chart" id="usage-chart"></div>
      <div class="analytics-grid">
        <div class="panel table-wrap analytics-panel"><div class="section-heading ledger-heading"><div><span class="eyebrow">COST RANKING</span><h2>Model cost</h2></div><span class="currency-label">Current range vs previous</span></div><table class="analytics-table"><thead><tr><th>Model</th><th>Requests</th><th>Success</th><th>Tokens</th><th>Charged</th><th>Change</th><th>P95</th><th>Missing usage</th></tr></thead><tbody id="usage-model-analytics-body"></tbody></table></div>
        <div class="panel table-wrap analytics-panel"><div class="section-heading ledger-heading"><div><span class="eyebrow">ROUTE HEALTH</span><h2>Provider performance</h2></div><span class="currency-label">Cache hit and latency</span></div><table class="analytics-table"><thead><tr><th>Provider</th><th>Requests</th><th>Success</th><th>Models</th><th>Cache hit</th><th>Charged</th><th>Change</th><th>P95</th></tr></thead><tbody id="usage-provider-analytics-body"></tbody></table></div>
      </div>
      <div class="panel table-wrap"><table><thead><tr><th>Period</th><th>Project</th><th>Requests</th><th>Success</th><th>Input</th><th>Output</th><th>Cost</th></tr></thead><tbody id="usage-summary-body"></tbody></table></div>
      <div class="section-heading ledger-heading"><div><span class="eyebrow">REQUESTS</span><h2>Recent events</h2></div></div>
      <div class="panel table-wrap"><table><thead><tr><th>Time</th><th>Request</th><th>Project / key</th><th>Model / provider</th><th>Status</th><th>Tokens</th><th>Charged</th><th>Latency</th><th></th></tr></thead><tbody id="usage-events-body"></tbody></table></div>
    </section>

    <section id="tenants" class="section">
      <div class="section-heading"><div><span class="eyebrow">IDENTITY</span><h1>Tenants</h1></div></div>
      <form class="panel form-grid" id="tenant-form" data-permission="tenants.write"><label>Slug<input name="slug" required pattern="[a-z0-9][a-z0-9\-]{1,62}[a-z0-9]" placeholder="acme"></label><label>Name<input name="name" required placeholder="Acme Inc."></label><button class="button primary" type="submit">Create tenant</button></form>
      <div class="panel table-wrap"><table><thead><tr><th>Name</th><th>Slug</th><th>Status</th><th>Created</th></tr></thead><tbody id="tenants-body"></tbody></table></div>
    </section>

    <section id="projects" class="section">
      <div class="section-heading"><div><span class="eyebrow">IDENTITY</span><h1>Projects</h1></div></div>
      <form class="panel form-grid" id="project-form" data-permission="projects.write"><label>Tenant<select name="tenant_id" id="project-tenant" required></select></label><label>Slug<input name="slug" required placeholder="production"></label><label>Name<input name="name" required placeholder="Production API"></label><button class="button primary" type="submit">Create project</button></form>
      <div class="panel table-wrap"><table><thead><tr><th>Name</th><th>Tenant</th><th>Slug</th><th>Status</th></tr></thead><tbody id="projects-body"></tbody></table></div>
    </section>

    <section id="keys" class="section">
      <div class="section-heading"><div><span class="eyebrow">ACCESS</span><h1>API keys</h1></div></div>
      <form class="panel form-grid key-form" id="key-form" data-permission="keys.write">
        <label>Tenant<select name="tenant_id" id="key-tenant" required></select></label>
        <label>Project<select name="project_id" id="key-project" required></select></label>
        <label>Name<input name="name" required maxlength="120" placeholder="CLI production key"></label>
        <label>Scopes<input name="scopes" value="inference" placeholder="inference"></label>
        <label>Tags<input name="tags" placeholder="production, backend"></label>
        <label>Monthly spend cap<input name="monthly_spend" inputmode="decimal" value="0" placeholder="0 = unlimited"></label>
        <label>Expires at<input name="expires_at" type="datetime-local"></label>
        <label class="key-model-picker">Allowed models<select name="allowed_models" id="key-models" multiple size="5" aria-describedby="key-model-help"></select><small id="key-model-help">No selection allows every model visible to this workspace.</small></label>
        <button class="button primary" type="submit">Create key</button>
      </form>
      <div class="panel warning"><strong>Key visibility</strong><span>The secret is shown only once after creation.</span></div>
      <div class="panel table-wrap"><table class="keys-table"><thead><tr><th>Name</th><th>Prefix</th><th>Project</th><th>Restrictions</th><th>Month usage</th><th>Monthly cap</th><th>Activity</th><th></th></tr></thead><tbody id="keys-body"></tbody></table></div>
    </section>

    <section id="providers" class="section">
      <div class="section-heading"><div><span class="eyebrow">UPSTREAMS</span><h1>Providers</h1></div></div>
      <form class="panel form-grid" id="provider-form" data-permission="platform.write"><input class="visually-hidden" name="username" autocomplete="username" value="aigw-provider" aria-hidden="true" tabindex="-1"><label>Name<input name="name" autocomplete="off" required placeholder="OpenAI primary"></label><label>Public slug<input name="slug" autocomplete="off" required pattern="[a-z0-9][a-z0-9-]{1,62}[a-z0-9]" maxlength="64" placeholder="openai-primary"></label><label>Protocol<select name="protocol"><option value="openai">OpenAI</option><option value="anthropic">Anthropic</option></select></label><label>Wire API<select name="wire_api"><option value="chat_completions">Chat Completions</option><option value="responses">Responses</option></select></label><label>Base URL<input name="base_url" type="url" autocomplete="off" required placeholder="https://api.example.com/v1"></label><label>API key<input name="api_key" type="password" required autocomplete="new-password" placeholder="Stored encrypted"></label><button class="button primary" type="submit">Add provider</button></form>
      <div class="panel table-wrap"><table><thead><tr><th>Name</th><th>Protocol</th><th>Base URL</th><th>Routes</th><th>Status</th><th></th></tr></thead><tbody id="providers-body"></tbody></table></div>
    </section>

    <section id="models" class="section">
      <div class="section-heading"><div><span class="eyebrow">ROUTING</span><h1>Models & routes</h1></div></div>
      <form class="panel form-grid" id="model-form" data-permission="platform.write"><label>Public model ID<input name="public_id" required placeholder="openai/gpt-4.1-mini"></label><label>Display name<input name="display_name" placeholder="GPT 4.1 mini"></label><label>Owned by<input name="owned_by" placeholder="openai"></label><label>Lifecycle<select name="lifecycle"><option value="preview">Preview</option><option value="active" selected>Active</option><option value="deprecated">Deprecated</option><option value="retired">Retired</option></select></label><label>Description<input name="description" placeholder="Fast general-purpose text model"></label><label>Context window<input name="context_window" type="number" min="0" value="0"></label><label>Max output tokens<input name="max_output_tokens" type="number" min="0" value="0"></label><label>Capabilities<input name="capabilities" value="chat,streaming" placeholder="chat,streaming,tools,json"></label><label>Input modalities<input name="input_modalities" value="text" placeholder="text,image"></label><label>Output modalities<input name="output_modalities" value="text" placeholder="text,image"></label><label>Regions<input name="regions" placeholder="us-east,nz"></label><label>Deprecated aliases<input name="aliases" placeholder="old/model-id"></label><label>Tenant allowlist IDs<input name="allowed_tenant_ids" placeholder="UUIDs, blank is public"></label><label>API key allowlist IDs<input name="allowed_key_ids" placeholder="UUIDs, blank is unrestricted"></label><label>Input price / 1M<input name="input_price" inputmode="decimal" value="0" required></label><label>Output price / 1M<input name="output_price" inputmode="decimal" value="0" required></label><label>Cache read / 1M<input name="cache_read_price" inputmode="decimal" value="0" required></label><label>Cache write / 1M<input name="cache_write_price" inputmode="decimal" value="0" required></label><div class="route-editor" id="route-editor"></div><button class="button subtle" type="button" id="add-route">Add route</button><button class="button primary" type="submit">Create model</button></form>
      <div class="panel table-wrap"><table><thead><tr><th>Public ID</th><th>Owner</th><th>Routes</th><th>Status</th><th></th></tr></thead><tbody id="models-body"></tbody></table></div>
    </section>

    <section id="billing" class="section">
      <div class="section-heading"><div><span class="eyebrow">REVENUE</span><h1>Balances & ledger</h1></div><div class="form-actions"><button class="button secondary" id="billing-portal" data-permission="billing.topup">Customer portal</button><button class="button subtle" id="billing-export" data-permission="billing.read">Export CSV</button><button class="button subtle" id="billing-reconcile" data-permission="billing.adjust">Reconcile Stripe</button><span class="currency-label" id="billing-currency"></span></div></div>
      <div class="billing-actions">
        <form class="panel form-grid compact-form" id="topup-form" data-permission="billing.topup"><label>Tenant<select name="tenant_id" id="topup-tenant" required></select></label><label>Amount<input name="amount" inputmode="decimal" min="0" required placeholder="25.00"></label><button class="button primary" type="submit">Open Stripe Checkout</button></form>
        <form class="panel form-grid compact-form" id="adjustment-form" data-permission="billing.adjust"><label>Tenant<select name="tenant_id" id="adjustment-tenant" required></select></label><label>Signed amount<input name="amount" inputmode="decimal" required placeholder="10.00 or -5.00"></label><label>Reference<input name="description" maxlength="240" placeholder="Support credit"></label><button class="button secondary" type="submit">Post adjustment</button></form>
      </div>
      <div class="panel billing-profile-panel" id="billing-profile-panel" data-permission="billing.read">
        <div class="section-heading"><div><span class="eyebrow">INVOICE DETAILS</span><h2>Billing profile</h2></div><span class="badge" id="billing-profile-status">Not configured</span></div>
        <form class="form-grid" id="billing-profile-form">
          <label>Legal or billing name<input name="legal_name" maxlength="150" autocomplete="organization" required></label>
          <label>Billing email<input name="billing_email" type="email" maxlength="254" autocomplete="email" required></label>
          <label class="billing-address-wide">Address line 1<input name="address_line1" maxlength="200" autocomplete="address-line1" required></label>
          <label class="billing-address-wide">Address line 2<input name="address_line2" maxlength="200" autocomplete="address-line2"></label>
          <label>City<input name="city" maxlength="100" autocomplete="address-level2" required></label>
          <label>State or region<input name="region" maxlength="100" autocomplete="address-level1"></label>
          <label>Postal code<input name="postal_code" maxlength="32" autocomplete="postal-code" required></label>
          <label>Country code<input name="country" minlength="2" maxlength="2" pattern="[A-Za-z]{2}" autocomplete="country" autocapitalize="characters" placeholder="NZ" required></label>
          <div class="form-actions billing-profile-actions"><small class="error-label" id="billing-profile-error"></small><button class="button primary" type="submit" data-permission="billing.topup">Save invoice details</button></div>
        </form>
      </div>
      <div class="panel auto-topup-panel" id="auto-topup-panel" data-permission="billing.read">
        <div class="section-heading"><div><span class="eyebrow">BALANCE PROTECTION</span><h2>Automatic top-up</h2></div><span class="badge" id="auto-topup-status">Not configured</span></div>
        <form class="form-grid" id="auto-topup-form">
          <label class="toggle-row"><input id="auto-topup-enabled" name="enabled" type="checkbox"><span>Automatically add funds when available balance reaches the threshold</span></label>
          <label>Balance threshold<input id="auto-topup-threshold" name="threshold" inputmode="decimal" required></label>
          <label>Top-up amount<input id="auto-topup-amount" name="amount" inputmode="decimal" required></label>
          <div><span class="muted">Payment method</span><strong id="auto-topup-payment-method">Not saved</strong><small class="error-label" id="auto-topup-error"></small></div>
          <div class="form-actions auto-topup-actions"><button class="button secondary" id="auto-topup-payment-setup" type="button">Save payment method</button><button class="button primary" type="submit">Save automatic top-up</button></div>
        </form>
      </div>
      <div class="panel table-wrap"><table><thead><tr><th>Tenant</th><th>Balance</th><th>Reserved</th><th>Available</th><th>Updated</th></tr></thead><tbody id="billing-accounts-body"></tbody></table></div>
      <div class="section-heading ledger-heading"><div><span class="eyebrow">AUDIT</span><h2>Recent ledger entries</h2></div></div>
      <div class="panel table-wrap"><table><thead><tr><th>Time</th><th>Tenant</th><th>Kind</th><th>Amount</th><th>Balance after</th><th>Reference</th></tr></thead><tbody id="billing-ledger-body"></tbody></table></div>
      <div class="section-heading ledger-heading"><div><span class="eyebrow">PAYMENTS</span><h2>Top-ups, invoices & refunds</h2></div></div>
      <div class="panel table-wrap"><table><thead><tr><th>Created</th><th>Amount</th><th>Status</th><th>Reconciliation</th><th>Documents</th><th>Action</th></tr></thead><tbody id="billing-orders-body"></tbody></table></div>
      <div class="panel table-wrap"><table><thead><tr><th>Created</th><th>Order</th><th>Amount</th><th>Status</th><th>Failure</th></tr></thead><tbody id="refunds-body"></tbody></table></div>
      <div class="panel table-wrap"><table><thead><tr><th>Updated</th><th>Amount</th><th>Status</th><th>Reason</th><th>Due</th></tr></thead><tbody id="disputes-body"></tbody></table></div>
    </section>

    <section id="limits" class="section">
      <div class="section-heading"><div><span class="eyebrow">GUARDRAILS</span><h1>Project limits</h1></div></div>
      <div class="panel table-wrap"><table class="limits-table"><thead><tr><th>Project</th><th>Requests / min</th><th>Estimated tokens / min</th><th>Concurrent</th><th>Monthly spend</th><th></th></tr></thead><tbody id="limits-body"></tbody></table></div>
    </section>

    <section id="team" class="section">
      <div class="section-heading"><div><span class="eyebrow">RBAC</span><h1>Console access</h1></div></div>
      <form class="panel form-grid" id="user-form" data-permission="users.write"><label>Tenant<select name="tenant_id" id="user-tenant"></select></label><label>Email<input name="email" type="email" required autocomplete="email" placeholder="operator@example.com"></label><label>Display name<input name="display_name" required placeholder="Operations"></label><label>Role<select name="role" id="user-role" required></select></label><button class="button primary" type="submit">Invite member</button></form>
      <div class="panel table-wrap"><table><thead><tr><th>User</th><th>Role</th><th>Tenant</th><th>Login</th><th>Last used</th><th>Status</th><th></th></tr></thead><tbody id="users-body"></tbody></table></div>
    </section>

    <section id="account" class="section">
      <div class="section-heading"><div><span class="eyebrow">SECURITY</span><h1>Account</h1></div></div>
      <form class="panel form-grid compact-form" id="password-form"><input class="visually-hidden" name="username" autocomplete="username" aria-hidden="true" tabindex="-1"><label>Current password<input name="current_password" type="password" required autocomplete="current-password"></label><label>New password<input name="new_password" type="password" required minlength="12" maxlength="128" autocomplete="new-password"></label><button class="button primary" type="submit">Change password</button></form>
      <div class="account-grid">
		<form class="panel form-grid compact-form" id="totp-begin-form"><h2>Authenticator app</h2><input class="visually-hidden" name="username" autocomplete="username" aria-hidden="true" tabindex="-1"><label>Current password<input name="current_password" type="password" required autocomplete="current-password"></label><label class="hidden" id="totp-disable-code">Authenticator code<input name="code" inputmode="numeric" autocomplete="one-time-code"></label><button class="button secondary" id="totp-action" type="submit">Set up TOTP</button></form>
        <form class="panel form-grid compact-form hidden" id="totp-confirm-form"><h2>Confirm authenticator</h2><img id="totp-qr" alt="TOTP QR code"><code id="totp-secret"></code><label>Code<input name="code" inputmode="numeric" autocomplete="one-time-code" required></label><button class="button primary" type="submit">Enable TOTP</button></form>
		<form class="panel form-grid compact-form" id="passkey-form"><h2>Passkey</h2><input class="visually-hidden" name="username" autocomplete="username" aria-hidden="true" tabindex="-1"><label>Current password<input name="current_password" type="password" required autocomplete="current-password"></label><label>Passkey name<input name="name" autocomplete="off" value="This device" required></label><button class="button secondary" type="submit">Add passkey</button></form>
      </div>
      <div class="panel table-wrap"><div class="section-heading"><h2>Devices</h2><button class="button subtle" id="revoke-other-sessions" type="button">Sign out other devices</button></div><table><thead><tr><th>Device</th><th>IP</th><th>Method</th><th>Last seen</th><th></th></tr></thead><tbody id="sessions-body"></tbody></table></div>
      <div class="panel table-wrap"><div class="section-heading"><h2>Passkeys</h2></div><table><thead><tr><th>Name</th><th>Created</th><th>Last used</th><th></th></tr></thead><tbody id="passkeys-body"></tbody></table></div>
      <div class="panel table-wrap"><div class="section-heading"><h2>Top-up orders</h2></div><table><thead><tr><th>Created</th><th>Amount</th><th>Status</th><th>Reference</th></tr></thead><tbody id="orders-body"></tbody></table></div>
    </section>

    <section id="audit" class="section">
      <div class="section-heading"><div><span class="eyebrow">SECURITY</span><h1>Audit log</h1></div></div>
      <div class="panel table-wrap"><table><thead><tr><th>Time</th><th>Actor</th><th>Action</th><th>Method</th><th>Status</th><th>Request</th><th>IP</th></tr></thead><tbody id="audit-body"></tbody></table></div>
    </section>
  </main>
  </div>
  <div id="toast" class="toast" role="status"></div>
  <dialog id="model-dialog"><div class="dialog-content model-dialog-content"><div class="section-heading"><div><span class="eyebrow">MODEL DETAIL</span><h2 id="model-dialog-title">Model</h2></div><button class="icon-button" id="close-model-dialog" aria-label="Close">×</button></div><div class="model-dialog-id"><code id="model-dialog-id"></code><button class="text-button" id="copy-model-id" type="button">Copy ID</button></div><div class="model-detail-grid" id="model-detail-grid"></div><section class="model-provider-health" id="model-provider-health"><div class="section-heading"><div><h3>Provider health</h3><small>Measured from requests handled by this gateway instance.</small></div></div><div class="table-wrap"><table class="provider-health-table"><thead><tr><th>Provider</th><th>State</th><th>Recent availability</th><th>Header latency</th><th>Samples</th></tr></thead><tbody id="model-provider-health-body"></tbody></table></div></section><form class="model-estimator" id="model-estimate-form"><h3>Cost estimate</h3><div class="form-grid compact-form"><label>Input tokens<input id="estimate-input" type="number" min="0" step="1" value="1000" required></label><label>Output tokens<input id="estimate-output" type="number" min="0" step="1" value="500" required></label><label>Cache read tokens<input id="estimate-cache-read" type="number" min="0" step="1" value="0" required></label><label>Cache write tokens<input id="estimate-cache-write" type="number" min="0" step="1" value="0" required></label><output id="model-estimate" class="model-estimate" aria-live="polite"></output></div></form><div class="form-actions model-dialog-actions"><button class="button secondary" id="model-dialog-playground" type="button">Use in Playground</button><button class="button subtle" id="model-dialog-close" type="button">Close</button></div></div></dialog>
  <dialog id="usage-dialog"><div class="dialog-content request-dialog-content"><div class="section-heading"><div><span class="eyebrow">API REQUEST</span><h2 id="usage-dialog-title">Request details</h2></div><button class="icon-button" id="close-usage-dialog" aria-label="Close">×</button></div><div class="model-dialog-id"><code id="usage-dialog-id"></code><button class="text-button" id="copy-request-id" type="button">Copy request ID</button></div><div class="request-detail-grid" id="usage-detail-grid"></div><pre class="request-diagnostic"><code id="usage-diagnostic"></code></pre><div class="form-actions model-dialog-actions"><button class="button secondary" id="copy-request-diagnostic" type="button">Copy diagnostic</button><button class="button subtle" id="usage-dialog-close" type="button">Close</button></div></div></dialog>
  <dialog id="secret-dialog"><div class="dialog-content"><div class="section-heading"><div><span class="eyebrow">ONE-TIME SECRET</span><h2 id="secret-title">Credential created</h2></div><button class="icon-button" id="close-dialog" aria-label="Close">×</button></div><p>Copy this credential now. It will not be shown again.</p><code id="created-secret"></code><button class="button primary" id="copy-secret">Copy credential</button></div></dialog>
  <script src="./app.js" defer></script>
</body>
</html>