summaryrefslogtreecommitdiff
path: root/internal/adminui/assets/index.html
blob: 95ab15344b39e0cf7a3743c915f005849c1adc6e (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
<!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" 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" 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="overview">Overview</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">Models & routes</button>
    </nav>

    <section id="overview" class="section active">
      <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>
      <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>Model</th><th>Status</th><th>Tokens</th><th>Cost</th><th>Latency</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" 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 placeholder="CLI production key"></label><label>Scopes<input name="scopes" value="inference" placeholder="inference"></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><thead><tr><th>Name</th><th>Prefix</th><th>Project</th><th>Scopes</th><th>Status</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" autocomplete="username" value="aigw-provider" aria-hidden="true" tabindex="-1"><label>Name<input name="name" required placeholder="openai-primary"></label><label>Protocol<select name="protocol"><option value="openai">OpenAI</option><option value="anthropic">Anthropic</option></select></label><label>Base URL<input name="base_url" type="url" 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>Owned by<input name="owned_by" placeholder="openai"></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><span class="currency-label" id="billing-currency"></span></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 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>
    </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" 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" 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" 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="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>