|
5 | 5 |
|
6 | 6 | export let form |
7 | 7 |
|
8 | | - const permissions = ['read', 'write', 'search', 'delete'] |
| 8 | + const permissions = ['read', 'write'] |
9 | 9 | const tokens = web_storage('tokens', [] as any[]) |
10 | 10 |
|
11 | 11 | let key = '' |
|
16 | 16 |
|
17 | 17 | function onSubmit(e: SubmitEvent) { |
18 | 18 | e.preventDefault() |
| 19 | +
|
19 | 20 | const form = e.target as HTMLFormElement |
20 | 21 | const data = new FormData(form) |
21 | 22 | const key = data.get('key') as string |
|
36 | 37 | if (pending.length === 0) return |
37 | 38 |
|
38 | 39 | const req = pending[0] |
39 | | - const { endpoint, method } = req |
| 40 | + const { key, method, endpoint } = req |
40 | 41 | const resp = await fetch(`/${endpoint}?key=${key}`, { method, body: method === 'put' ? '{}' : null }) |
41 | | - const data = resp.status === 200 ? await resp.json() : {} |
| 42 | + const data = await resp.json() |
| 43 | + // const data = resp.status === 200 ? await resp.json() : {} |
42 | 44 | results = [{ ts: new Date(), method, endpoint, status: resp.status, data }, ...results] |
43 | | - if (resp.status === 200) { |
| 45 | + if (resp.status !== 429) { |
44 | 46 | pending = pending.filter(p => p !== req) |
45 | 47 | } |
| 48 | +
|
46 | 49 | const retryAfter = parseInt(resp.headers.get('RateLimit-Reset') || '0') |
47 | 50 | if (retryAfter) { |
48 | 51 | await delay(retryAfter * 1000) |
49 | 52 | } |
50 | 53 |
|
51 | | - await process() |
| 54 | + process() |
52 | 55 | } |
53 | 56 |
|
54 | 57 | $: handleForm(form) |
|
72 | 75 | }) |
73 | 76 | </script> |
74 | 77 |
|
75 | | -<h2 class="text-xl font-bold tracking-tight text-gray-700">Your API Keys</h2> |
76 | | -<p class="mt-1 text-base text-gray-700">Any API keys you have created are listed below. </p> |
77 | | -<ul class="mt-2 space-y-3"> |
| 78 | +<svelte:head> |
| 79 | + <title>API Keys</title> |
| 80 | +</svelte:head> |
| 81 | + |
| 82 | +<h2 class="text-xl font-bold tracking-tight text-gray-700">Key Management</h2> |
| 83 | +<p class="mt-1 text-base text-gray-700">Any API keys you have created are listed below.</p> |
| 84 | + |
| 85 | +<ul class="mt-2 grid grid-cols-3 gap-2"> |
78 | 86 | {#each $tokens as token} |
79 | 87 | <li class="border border-gray-300 p-2 rounded-md flex gap-4"> |
80 | 88 | <div class="flex flex-col gap-2 w-24"> |
81 | | - <button class="w-full bg-green-600 text-white text-sm rounded-md px-2 py-1 mr-2" on:click={() => key = token.key}>use key</button> |
| 89 | + <button class="w-full bg-green-600 text-white text-sm rounded-md px-2 py-1 mr-2" on:click={() => key = token.key}>use</button> |
82 | 90 | <form class="w-full" method="post" action="?/delete" use:enhance> |
83 | 91 | <input type="hidden" name="hash" value={token.hash}> |
84 | | - <button class="w-full bg-red-600 text-white text-sm rounded-md px-2 py-1 mr-2">delete key</button> |
| 92 | + <button class="w-full bg-red-600 text-white text-sm rounded-md px-2 py-1 mr-2">delete</button> |
85 | 93 | </form> |
86 | 94 | </div> |
87 | 95 | <div> |
88 | 96 | <h3 class="font-semibold">{token.name}</h3> |
89 | | - <p class="mt-1 text-gray-700 text-sm">{token.description}, expires: {token.expires ? dateFormatter.format(new Date(token.expires)) : 'never'}</p> |
| 97 | + <p class="mt-1 text-gray-700 text-sm">{token.description}</p> |
| 98 | + <p class="mt-1 text-gray-700 text-sm">expires: {token.expires ? dateFormatter.format(new Date(token.expires)) : 'never'}</p> |
90 | 99 | <ul class="mt-2 flex items-center gap-2"> |
91 | 100 | {#each token.permissions as permission} |
92 | 101 | <li class="inline-flex items-center rounded-md bg-purple-50 px-2 py-1 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10">{permission}</li> |
|
99 | 108 | {/each} |
100 | 109 | </ul> |
101 | 110 |
|
102 | | -<form method="post" action="?/list" use:enhance class="mt-4 space-y-2"> |
103 | | - <label for="user" class="w-64 text-sm font-medium leading-6 text-gray-900">Email</label> |
104 | | - <div class="mt-2"> |
105 | | - <input |
106 | | - type="email" |
107 | | - name="user" |
108 | | - id="user" |
109 | | - class="block w-64 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" |
110 | | - placeholder="email address" |
111 | | - data-1p-ignore |
112 | | - /> |
113 | | - </div> |
114 | | - |
115 | | - <button>Submit</button> |
116 | | -</form> |
| 111 | +<!-- <pre class="text-xs mt-4">response: {JSON.stringify(form, null, 2)}</pre> --> |
117 | 112 |
|
| 113 | +<h2 class="mt-4 text-md font-semibold tracking-tight text-gray-700">Create New Key</h2> |
118 | 114 | <form |
119 | 115 | method="post" |
120 | 116 | action="?/generate" |
121 | | - class="mt-4 space-y-2" |
| 117 | + class="mt-2 flex items-center gap-2" |
122 | 118 | autocomplete="off" |
123 | 119 | use:enhance |
124 | 120 | > |
125 | | - <input type="text" name="name" placeholder="key name" required minlength="3" maxlength="100" data-1p-ignore/> |
126 | | - <p>A unique name for this token. May be visible to resource owners or users with possession of the token.</p> |
127 | | - <input type="text" name="description" placeholder="description" maxlength="200" /> |
128 | | - <p>What is the purpose of this token?</p> |
129 | | - <input type="email" name="user" placeholder="email address" required data-1p-ignore/> |
130 | | - <input type="date" name="expires" placeholder="expiry date" /> |
131 | | - <p>The token will expire on Thu, Jun 20 2024</p> |
132 | | - <p>Permissions</p> |
| 121 | + <input class="block w-32 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" type="text" name="name" placeholder="key name" required minlength="3" maxlength="100" data-1p-ignore/> |
| 122 | + <input class="block w-64 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" type="text" name="description" placeholder="description" maxlength="200" /> |
| 123 | + <input class="block w-48 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" type="email" name="user" placeholder="email address" required data-1p-ignore/> |
| 124 | + <input class="block w-36 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" type="date" name="expires" placeholder="expiry date" /> |
133 | 125 | <ul class="flex items-center gap-2"> |
134 | 126 | {#each permissions as permission} |
135 | 127 | <li class="flex"> |
136 | 128 | <input type="checkbox" id={permission} class="peer hidden" name="permissions" value={permission}> |
137 | | - <label for={permission} class="select-none cursor-pointer rounded-md bg-purple-50 px-2 py-1 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10 peer-checked:bg-purple-200 peer-checked:text-purple-900 peer-checked:border-purple-200">{permission}</label> |
| 129 | + <label for={permission} class="select-none cursor-pointer rounded-md bg-gray-50 px-2 py-2 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-700/10 peer-checked:bg-green-200 peer-checked:text-green-900 peer-checked:border-green-200">{permission}</label> |
138 | 130 | </li> |
139 | 131 | {/each} |
140 | 132 | </ul> |
141 | | - <button>Submit</button> |
| 133 | + <button type="submit" class="ml-2 rounded-md bg-indigo-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Create Key</button> |
142 | 134 | </form> |
143 | 135 |
|
144 | | -<form method="post" action="?/validate" use:enhance> |
145 | | - <input type="text" name="key" placeholder="api key" /> |
146 | | - <button>Submit</button> |
| 136 | +{#if form?.type === 'generate'} |
| 137 | + <pre class="mt-2 text-xs">{JSON.stringify(form, null, 2)}</pre> |
| 138 | +{/if} |
| 139 | + |
| 140 | +<h2 class="mt-4 text-md font-semibold tracking-tight text-gray-700">Find Keys by Owner</h2> |
| 141 | +<form method="post" action="?/list" use:enhance class="mt-2 flex items-center gap-2"> |
| 142 | + <div class=""> |
| 143 | + <input |
| 144 | + type="email" |
| 145 | + name="user" |
| 146 | + id="user" |
| 147 | + class="block w-64 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" |
| 148 | + placeholder="email address" |
| 149 | + data-1p-ignore |
| 150 | + /> |
| 151 | + </div> |
| 152 | + |
| 153 | + <button type="submit" class="rounded-md bg-indigo-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">List Keys</button> |
147 | 154 | </form> |
148 | 155 |
|
149 | | -<pre>{JSON.stringify(form, null, 2)}</pre> |
| 156 | +{#if form?.type === 'list'} |
| 157 | + <pre class="mt-2 text-xs">{JSON.stringify(form, null, 2)}</pre> |
| 158 | +{/if} |
| 159 | + |
| 160 | +<h2 class="mt-4 text-md font-semibold tracking-tight text-gray-700">Retrieve Key Info</h2> |
| 161 | +<form method="post" action="?/validate" use:enhance class="mt-2 flex items-center gap-2"> |
| 162 | + <input |
| 163 | + type="text" |
| 164 | + name="key" |
| 165 | + id="key" |
| 166 | + class="block w-96 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" |
| 167 | + placeholder="api key" |
| 168 | + value={key} |
| 169 | + data-1p-ignore |
| 170 | + /> |
| 171 | + <button type="submit" class="rounded-md bg-indigo-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Get Info</button> |
| 172 | +</form> |
150 | 173 |
|
151 | | -<form method="post" on:submit={onSubmit}> |
152 | | - <input type="text" name="key" placeholder="api key" value={key} /> |
153 | | - <input type="number" name="count" value="5" min="1" max="10" /> |
154 | | - <label> |
155 | | - <input type="radio" name="method" value="GET" checked/> |
156 | | - GET |
157 | | - </label> |
158 | | - <label> |
159 | | - <input type="radio" name="method" value="PUT" /> |
160 | | - PUT |
161 | | - </label> |
162 | | - <label> |
163 | | - <input type="radio" name="endpoint" value="cheap" checked/> |
| 174 | +{#if form?.type === 'validate'} |
| 175 | + <pre class="mt-2 text-xs">{JSON.stringify(form, null, 2)}</pre> |
| 176 | +{/if} |
| 177 | + |
| 178 | +<h2 class="mt-4 text-md font-semibold tracking-tight text-gray-700">Call API</h2> |
| 179 | +<form method="post" on:submit={onSubmit} class="mt-2 flex items-center gap-2"> |
| 180 | + <input |
| 181 | + type="text" |
| 182 | + name="key" |
| 183 | + id="key" |
| 184 | + class="block w-96 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" |
| 185 | + placeholder="api key" |
| 186 | + value={key} |
| 187 | + data-1p-ignore |
| 188 | + /> |
| 189 | + <input class="block w-16 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" type="number" name="count" value="1" min="1" max="20" /> |
| 190 | + <select name="method" class="block w-24 rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6"> |
| 191 | + <option selected>get</option> |
| 192 | + <option>put</option> |
| 193 | + </select> |
| 194 | + <input type="hidden" name="endpoint" value="cheap"> |
| 195 | + <!-- |
| 196 | + <label class="flex items-center gap-1 ml-4"> |
| 197 | + <input type="radio" name="endpoint" value="cheap"> |
164 | 198 | Cheap |
165 | 199 | </label> |
166 | | - <label> |
167 | | - <input type="radio" name="endpoint" value="expensive" /> |
| 200 | + <label class="flex items-center gap-1"> |
| 201 | + <input type="radio" name="endpoint" value="expensive"> |
168 | 202 | Expensive |
169 | 203 | </label> |
170 | | - <button>Submit</button> |
| 204 | + --> |
| 205 | + <button type="submit" class="rounded-md bg-indigo-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Submit Requests</button> |
| 206 | + <button type="button" class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" on:click={() => {pending = []; form = null; key = '' }}>Clear</button> |
171 | 207 | </form> |
172 | 208 |
|
173 | | -<p>Pending: {pending.length}</p> |
174 | | - |
175 | | -<button on:click={() => (results = [])}>Clear</button> |
| 209 | +<p class="mt-2 text-xs">Pending: {pending.length}</p> |
176 | 210 |
|
177 | | -<li> |
| 211 | +<ul class="mt-2 text-xs"> |
178 | 212 | {#each results as result} |
179 | | - <ul>{result.ts} {result.method} {result.endpoint} {result.status} {JSON.stringify(result.data)}</ul> |
| 213 | + <li>{result.ts.getTime() / 1000} {result.method} {result.status} {JSON.stringify(result.data)}</li> |
180 | 214 | {/each} |
181 | | -</li> |
| 215 | +</ul> |
0 commit comments