ignore: cloud keys section
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user