diff options
Diffstat (limited to 'internal/adminui/assets/index.html')
| -rw-r--r-- | internal/adminui/assets/index.html | 35 |
1 files changed, 21 insertions, 14 deletions
diff --git a/internal/adminui/assets/index.html b/internal/adminui/assets/index.html index 8f31e1c..7d367ca 100644 --- a/internal/adminui/assets/index.html +++ b/internal/adminui/assets/index.html @@ -123,7 +123,7 @@ <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 class="playground-actions"><label id="playground-max-output-field">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> @@ -141,11 +141,14 @@ <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> + <h2>Billing alerts</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> + <label class="toggle-row"><input id="spend-anomaly-enabled" name="spend_anomaly_enabled" type="checkbox"><span>Email billing members when daily spend is unusual</span></label> + <label>Baseline multiplier<input id="spend-anomaly-multiplier" name="spend_anomaly_multiplier" type="number" min="2" max="1000" step="1" required></label> + <label>Minimum daily spend<input id="spend-anomaly-minimum" name="spend_anomaly_minimum" 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> + <button class="button primary" type="submit" data-permission="billing.preferences.write">Save billing alerts</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> @@ -154,7 +157,7 @@ <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 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="embeddings">OpenAI Embeddings</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> @@ -173,8 +176,8 @@ <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 id="usage-provider-filter">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="openai_embeddings">OpenAI Embeddings</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> @@ -184,12 +187,13 @@ <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 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>Latency P50 / P95</th><th>TTFT P50 / P95</th><th>Missing usage</th></tr></thead><tbody id="usage-model-analytics-body"></tbody></table></div> + <div class="panel table-wrap analytics-panel" id="usage-key-panel"><div class="section-heading ledger-heading"><div><span class="eyebrow">KEY ATTRIBUTION</span><h2>API key usage</h2></div><span class="currency-label">Spend and performance</span></div><table class="analytics-table"><thead><tr><th>API key</th><th>Requests</th><th>Success</th><th>Models</th><th>Tokens</th><th>Charged</th><th>Latency P50 / P95</th><th>TTFT P50 / P95</th></tr></thead><tbody id="usage-key-analytics-body"></tbody></table></div> + <div class="panel table-wrap analytics-panel" id="usage-provider-panel"><div class="section-heading ledger-heading"><div><span class="eyebrow">ROUTE HEALTH</span><h2>Provider performance</h2></div><span class="currency-label">Platform diagnostics</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>Latency P50 / P95</th><th>TTFT P50 / 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> + <div class="section-heading ledger-heading"><div><span class="eyebrow">REQUESTS</span><h2>Recent events</h2></div><div class="usage-pager"><button class="button subtle" id="usage-page-prev" type="button">Previous</button><span id="usage-page-label">Page 1</span><button class="button subtle" id="usage-page-next" type="button">Next</button></div></div> + <div class="panel table-wrap"><table><thead><tr><th>Time</th><th>Request</th><th>Project / key</th><th>Model</th><th>Status</th><th>Tokens</th><th>Charged</th><th>Latency / TTFT</th><th></th></tr></thead><tbody id="usage-events-body"></tbody></table></div> </section> <section id="tenants" class="section"> @@ -212,18 +216,21 @@ <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>Daily spend cap<input name="daily_spend" inputmode="decimal" value="0" placeholder="0 = unlimited"></label> <label>Monthly spend cap<input name="monthly_spend" inputmode="decimal" value="0" placeholder="0 = unlimited"></label> + <label>Requests / minute<input name="requests_per_minute" type="number" min="0" value="0" placeholder="0 = unlimited"></label> + <label>Tokens / minute<input name="tokens_per_minute" type="number" min="0" 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> + <div class="panel table-wrap"><table class="keys-table"><thead><tr><th>Name</th><th>Key</th><th>Project</th><th>Restrictions</th><th>Usage</th><th>Limits</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> + <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><option value="embeddings">Embeddings</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> @@ -303,8 +310,8 @@ </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="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 recent gateway requests.</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>TTFT</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><form class="form-grid compact-form hidden" id="release-reservation-form"><label>Release reason<input name="reason" required maxlength="500" autocomplete="off"></label><button class="button danger" type="submit">Release hold</button></form><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> |
