ignore: cloud keys section

This commit is contained in:
Jay V
2025-08-29 20:04:57 -04:00
parent 25e53e090b
commit 9a330b4f0f
2 changed files with 158 additions and 76 deletions

View File

@@ -4,6 +4,7 @@ import { action, createAsync, revalidate, query, useAction, useSubmission, json
import { createEffect, createSignal, For, onMount, Show } from "solid-js" import { createEffect, createSignal, For, onMount, Show } from "solid-js"
import { getActor } from "~/context/auth" import { getActor } from "~/context/auth"
import { withActor } from "~/context/auth.withActor" import { withActor } from "~/context/auth.withActor"
import { IconCopy, IconCheck } from "~/component/icon"
import "./index.css" import "./index.css"
import { User } from "@opencode/cloud-core/user.js" import { User } from "@opencode/cloud-core/user.js"
import { Actor } from "@opencode/cloud-core/actor.js" import { Actor } from "@opencode/cloud-core/actor.js"
@@ -142,7 +143,31 @@ const dummyPaymentData = [
}, },
] ]
export default function () { const dummyApiKeyData = [
{
id: "key_1Ab2Cd3Ef4Gh5678",
name: "Production API",
key: "oc_live_sk_1Ab2Cd3Ef4Gh567890123456789012345678901234567890",
timeCreated: new Date("2025-01-28T14:32:00Z"),
timeUsed: new Date("2025-01-29T09:15:00Z"),
},
{
id: "key_9Ij8Kl7Mn6Op5432",
name: "Development Key",
key: "oc_test_sk_9Ij8Kl7Mn6Op543210987654321098765432109876543210",
timeCreated: new Date("2025-01-25T09:18:00Z"),
timeUsed: null,
},
{
id: "key_5Qr4St3Uv2Wx1098",
name: "CI/CD Pipeline",
key: "oc_live_sk_5Qr4St3Uv2Wx109876543210987654321098765432109876",
timeCreated: new Date("2025-01-20T16:45:00Z"),
timeUsed: new Date("2025-01-28T12:30:00Z"),
},
]
export default function() {
const actor = createAsync(() => getActor()) const actor = createAsync(() => getActor())
onMount(() => { onMount(() => {
console.log("MOUNTED", actor()) console.log("MOUNTED", actor())
@@ -157,6 +182,7 @@ export default function () {
const createKeySubmission = useSubmission(createKey) const createKeySubmission = useSubmission(createKey)
const [showCreateForm, setShowCreateForm] = createSignal(false) const [showCreateForm, setShowCreateForm] = createSignal(false)
const [keyName, setKeyName] = createSignal("") const [keyName, setKeyName] = createSignal("")
const [copiedKeyId, setCopiedKeyId] = createSignal<string | null>(null)
const formatDate = (date: Date) => { const formatDate = (date: Date) => {
return date.toLocaleDateString() return date.toLocaleDateString()
@@ -201,6 +227,16 @@ export default function () {
} }
} }
const copyKeyToClipboard = async (text: string, keyId: string) => {
try {
await navigator.clipboard.writeText(text)
setCopiedKeyId(keyId)
setTimeout(() => setCopiedKeyId(null), 1500)
} catch (error) {
console.error("Failed to copy to clipboard:", error)
}
}
const handleCreateKey = async () => { const handleCreateKey = async () => {
if (!keyName().trim()) return if (!keyName().trim()) return
@@ -214,7 +250,7 @@ export default function () {
} }
const handleDeleteKey = async (keyId: string) => { const handleDeleteKey = async (keyId: string) => {
if (!confirm("Are you sure you want to delete this API key? This action cannot be undone.")) { if (!confirm("Are you sure you want to delete this API key?")) {
return return
} }
@@ -291,7 +327,7 @@ export default function () {
</section> </section>
{/* API Keys Section */} {/* API Keys Section */}
<section data-slot="keys-section"> <section data-slot="api-keys-section">
<div data-slot="section-title"> <div data-slot="section-title">
<h2>API Keys</h2> <h2>API Keys</h2>
<p>Manage your API keys for accessing opencode services.</p> <p>Manage your API keys for accessing opencode services.</p>
@@ -339,36 +375,55 @@ export default function () {
Create API Key Create API Key
</button> </button>
</Show> </Show>
<div data-slot="key-list"> <div data-slot="api-keys-table">
<For <Show
each={keys()} when={dummyApiKeyData.length > 0}
fallback={ fallback={
<div data-slot="empty-state"> <div data-slot="empty-state">
<p>Create an API key to access opencode gateway</p> <p>Create an opencode Gateway API key</p>
</div> </div>
} }
> >
{(key) => ( <table data-slot="api-keys-table-element">
<div data-slot="key-item"> <thead>
<div data-slot="key-info"> <tr>
<div data-slot="key-name">{key.name}</div> <th>Name</th>
<div data-slot="key-value">{formatKey(key.key)}</div> <th>Key</th>
<div data-slot="key-meta"> <th>Created</th>
Created: {formatDate(key.timeCreated)} <th></th>
{key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`} </tr>
</div> </thead>
</div> <tbody>
<div data-slot="key-actions"> <For each={dummyApiKeyData}>
<button color="ghost" onClick={() => copyToClipboard(key.key)} title="Copy API key"> {/* Real data: keys() */}
Copy {(key) => (
</button> <tr>
<button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key"> <td data-slot="key-name">{key.name}</td>
Delete <td data-slot="key-value">
</button> <div onClick={() => copyKeyToClipboard(key.key, key.id)} title="Click to copy API key">
</div> <span>{formatKey(key.key)}</span>
</div> <Show
)} when={copiedKeyId() === key.id}
</For> fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}
>
<IconCheck style={{ width: "14px", height: "14px" }} />
</Show>
</div>
</td>
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
{formatDateForTable(key.timeCreated)}
</td>
<td data-slot="key-actions">
<button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
Delete
</button>
</td>
</tr>
)}
</For>
</tbody>
</table>
</Show>
</div> </div>
</section> </section>

View File

@@ -203,59 +203,84 @@ a {
} }
} }
[data-slot="key-list"] { [data-slot="api-keys-table"] {
display: flex; overflow-x: auto;
flex-direction: column;
gap: var(--space-2);
} }
[data-slot="key-item"] { [data-slot="api-keys-table-element"] {
display: flex; width: 100%;
justify-content: space-between; border-collapse: collapse;
align-items: flex-start; font-size: var(--font-size-sm);
padding: var(--space-4);
background-color: var(--color-bg-surface);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
gap: var(--space-4);
@media (max-width: 30rem) { thead {
flex-direction: column; border-bottom: 1px solid var(--color-border);
gap: var(--space-3);
} }
}
[data-slot="key-info"] { th {
display: flex; padding: var(--space-3) var(--space-4);
flex-direction: column; text-align: left;
gap: var(--space-1); font-weight: normal;
flex: 1; color: var(--color-text-muted);
} text-transform: uppercase;
}
[data-slot="key-name"] { td {
font-size: var(--font-size-md); padding: var(--space-3) var(--space-4);
font-weight: 500; border-bottom: 1px solid var(--color-border-muted);
color: var(--color-text); color: var(--color-text-muted);
} font-family: var(--font-mono);
[data-slot="key-value"] { &[data-slot="key-name"] {
font-size: var(--font-size-xs); color: var(--color-text);
font-family: var(--font-mono); font-family: var(--font-sans);
color: var(--color-text-secondary); font-weight: 500;
background-color: var(--color-bg); }
padding: var(--space-1) var(--space-2);
border-radius: var(--border-radius-sm);
border: 1px solid var(--color-border-muted);
}
[data-slot="key-meta"] { &[data-slot="key-value"] {
font-size: var(--font-size-xs); font-family: var(--font-mono);
color: var(--color-text-disabled);
}
[data-slot="key-actions"] { div {
display: flex; cursor: pointer;
gap: var(--space-2); display: flex;
align-items: center;
gap: var(--space-2);
}
}
&[data-slot="key-date"] {
color: var(--color-text);
}
&[data-slot="key-actions"] {
font-family: var(--font-sans);
}
}
tbody tr {
&:last-child td {
border-bottom: none;
}
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-xs);
}
th {
&:nth-child(3) /* Date */ {
display: none;
}
}
td {
&:nth-child(3) /* Date */ {
display: none;
}
}
}
} }
} }
@@ -321,8 +346,9 @@ a {
th { th {
padding: var(--space-3) var(--space-4); padding: var(--space-3) var(--space-4);
text-align: left; text-align: left;
font-weight: 600; font-weight: normal;
color: var(--color-text-secondary); color: var(--color-text-muted);
text-transform: uppercase;
} }
td { td {
@@ -394,8 +420,9 @@ a {
th { th {
padding: var(--space-3) var(--space-4); padding: var(--space-3) var(--space-4);
text-align: left; text-align: left;
font-weight: 600; font-weight: normal;
color: var(--color-text-secondary); color: var(--color-text-muted);
text-transform: uppercase;
} }
td { td {