Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6680e6b

Browse files
improve demo
1 parent 6b7e2ce commit 6680e6b

File tree

3 files changed

+138
-97
lines changed

3 files changed

+138
-97
lines changed

src/routes/+layout.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
</script>
44

55
<nav class="m-4">
6-
<h1 class="text-3xl font-extrabold tracking-tighter text-gray-600">Svelte API Keys</h1>
6+
<h1 class="text-3xl font-extrabold tracking-tighter text-gray-600">svelte-api-keys</h1>
77
<p class="mt-1 text-gray-700">
88
Generate, Validate, Authorize and Rate-Limit API keys for your SvelteKit endpoints.
99
</p>
10-
<ul class="mt-2 flex gap-2 text-xs">
10+
<!-- <ul class="mt-2 flex gap-2 text-xs">
1111
<li><a class="px-2 py-2 bg-gray-100 hover:bg-gray-200 rounded-md" href="/">Keys</a></li>
1212
<li><a class="px-2 py-2 bg-gray-100 hover:bg-gray-200 rounded-md" href="/chart">Bucket</a></li>
13-
</ul>
13+
</ul> -->
1414
</nav>
1515

1616
<main class="m-4">

src/routes/+page.svelte

Lines changed: 97 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
export let form
77
8-
const permissions = ['read', 'write', 'search', 'delete']
8+
const permissions = ['read', 'write']
99
const tokens = web_storage('tokens', [] as any[])
1010
1111
let key = ''
@@ -16,6 +16,7 @@
1616
1717
function onSubmit(e: SubmitEvent) {
1818
e.preventDefault()
19+
1920
const form = e.target as HTMLFormElement
2021
const data = new FormData(form)
2122
const key = data.get('key') as string
@@ -36,19 +37,21 @@
3637
if (pending.length === 0) return
3738
3839
const req = pending[0]
39-
const { endpoint, method } = req
40+
const { key, method, endpoint } = req
4041
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() : {}
4244
results = [{ ts: new Date(), method, endpoint, status: resp.status, data }, ...results]
43-
if (resp.status === 200) {
45+
if (resp.status !== 429) {
4446
pending = pending.filter(p => p !== req)
4547
}
48+
4649
const retryAfter = parseInt(resp.headers.get('RateLimit-Reset') || '0')
4750
if (retryAfter) {
4851
await delay(retryAfter * 1000)
4952
}
5053
51-
await process()
54+
process()
5255
}
5356
5457
$: handleForm(form)
@@ -72,21 +75,27 @@
7275
})
7376
</script>
7477

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">
7886
{#each $tokens as token}
7987
<li class="border border-gray-300 p-2 rounded-md flex gap-4">
8088
<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>
8290
<form class="w-full" method="post" action="?/delete" use:enhance>
8391
<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>
8593
</form>
8694
</div>
8795
<div>
8896
<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>
9099
<ul class="mt-2 flex items-center gap-2">
91100
{#each token.permissions as permission}
92101
<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,83 +108,108 @@
99108
{/each}
100109
</ul>
101110

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> -->
117112

113+
<h2 class="mt-4 text-md font-semibold tracking-tight text-gray-700">Create New Key</h2>
118114
<form
119115
method="post"
120116
action="?/generate"
121-
class="mt-4 space-y-2"
117+
class="mt-2 flex items-center gap-2"
122118
autocomplete="off"
123119
use:enhance
124120
>
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" />
133125
<ul class="flex items-center gap-2">
134126
{#each permissions as permission}
135127
<li class="flex">
136128
<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>
138130
</li>
139131
{/each}
140132
</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>
142134
</form>
143135

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>
147154
</form>
148155

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>
150173

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">
164198
Cheap
165199
</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">
168202
Expensive
169203
</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>
171207
</form>
172208

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>
176210

177-
<li>
211+
<ul class="mt-2 text-xs">
178212
{#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>
180214
{/each}
181-
</li>
215+
</ul>

src/routes/chart/+page.svelte

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import { enUS } from 'date-fns/locale'
77
import 'chartjs-adapter-date-fns'
88
9-
let size = $state(10)
10-
let rate = $state(0.1)
9+
let size = $state(20)
10+
let rate = $state(0.5)
1111
let count = $state(1)
1212
let cost = $state(1)
1313
let refill = $derived(new Refill(rate, size))
@@ -152,41 +152,48 @@
152152
}
153153
</script>
154154

155-
<div class="flex items-center mt-2">
156-
<label for="size" class="w-32 text-sm font-medium text-gray-700">Bucket Size</label>
157-
<input type="number" name="size" id="size" class="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" min="1" max="20" bind:value={size}>
158-
</div>
155+
<svelte:head>
156+
<title>Token Bucket</title>
157+
</svelte:head>
159158

160-
<div class="flex items-center mt-2">
161-
<label for="rate" class="w-32 text-sm font-medium text-gray-700">Refill Rate</label>
162-
<input type="number" name="rate" id="rate" class="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" min="0.1" max="5" step="0.1" bind:value={rate}>
163-
<span class="ml-4 text-sm font-medium text-gray-700">per Second / {Math.round(rate * 60)} per Minute</span>
164-
</div>
159+
<h2 class="text-xl font-bold tracking-tight text-gray-700">Token Bucket Chart</h2>
160+
<p class="mt-1 text-base text-gray-700">Visualizing how a Token Bucket works for rate-limiting.</p>
165161

166-
<div class="flex items-center mt-2">
167-
<label for="rate" class="w-32 text-sm font-medium text-gray-700">Request Cost</label>
168-
<input type="number" name="rate" id="rate" class="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" min="1" max="10" step="1" bind:value={cost}>
162+
<div class="flex gap-3 mt-4">
163+
<div class="flex items-center">
164+
<label for="size" class="w-32 text-sm font-medium text-gray-700">Bucket Size</label>
165+
<input type="number" name="size" id="size" class="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" min="1" max="20" bind:value={size}>
166+
</div>
167+
168+
<div class="flex items-center">
169+
<label for="rate" class="w-32 text-sm font-medium text-gray-700">Refill Rate</label>
170+
<input type="number" name="rate" id="rate" class="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" min="0.1" max="5" step="0.1" bind:value={rate}>
171+
<span class="ml-4 text-sm font-medium text-gray-700">per Second / {Math.round(rate * 60)} per Minute</span>
172+
</div>
169173
</div>
170174

171-
<div class="flex items-center mt-2">
172-
<label for="rate" class="w-32 text-sm font-medium text-gray-700">Request Count</label>
173-
<input type="number" name="rate" id="rate" class="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" min="1" max="20" step="1" bind:value={count}>
174-
<button class="ml-4 rounded bg-green-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" on:click={enqueue}>Consume</button>
175-
<button class="ml-2 rounded bg-red-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-red-700" on:click={() => pending.length = 0} disabled={pending.length === 0}>Clear Pending</button>
175+
<div class="flex gap-3 mt-2">
176+
<div class="flex items-center">
177+
<label for="rate" class="w-32 text-sm font-medium text-gray-700">Request Cost</label>
178+
<input type="number" name="rate" id="rate" class="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" min="1" max="10" step="1" bind:value={cost}>
179+
</div>
180+
181+
<div class="flex items-center">
182+
<label for="rate" class="w-32 text-sm font-medium text-gray-700">Request Count</label>
183+
<input type="number" name="rate" id="rate" class="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" min="1" max="20" step="1" bind:value={count}>
184+
<button class="ml-4 rounded bg-green-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" on:click={enqueue}>Consume</button>
185+
<button class="ml-2 rounded bg-red-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-red-700" on:click={() => pending.length = 0} disabled={pending.length === 0}>Clear Pending</button>
186+
<ul class="ml-2 flex items-center text-xs gap-2">
187+
{#each pending as { count, cost } }
188+
<li class="border border-gray-300 bg-gray-100 text-gray-700 px-1 py-0.5 rounded-sm">{count}x{cost}</li>
189+
{/each}
190+
</ul>
191+
</div>
176192
</div>
177193

178-
<h2 class="mt-4 text-xl font-bold tracking-tight text-gray-700">Token Bucket</h2>
179-
<p class="mt-1 text-base text-gray-700">Visualization of token bucket capacity and requests </p>
194+
<h3 class="mt-6 text-lg font-bold tracking-tight text-gray-700">Capacity & Requests</h3>
195+
<p class="mt-1 text-base text-gray-700">Chart of token bucket capacity and requests </p>
180196

181-
<div class="mt-2 w-full h-72 relative">
197+
<div class="mt-2 w-full h-96 relative">
182198
<canvas use:chart></canvas>
183199
</div>
184-
185-
<h2 class="mt-4 text-xl font-bold tracking-tight text-gray-700">Pending Requests</h2>
186-
<ul>
187-
{#each pending as { count, cost } }
188-
<li>
189-
{count} x {cost}
190-
</li>
191-
{/each}
192-
</ul>

0 commit comments

Comments
 (0)