Skip to content

Commit 887623e

Browse files
drew-harrisskeptrunedev
authored andcommitted
feat: events page
1 parent 1975736 commit 887623e

27 files changed

+4127
-24
lines changed

frontends/dashboard/src/components/ApiKeyGenerateModal.tsx

Lines changed: 423 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import {
2+
For,
3+
Show,
4+
createMemo,
5+
createResource,
6+
createSignal,
7+
useContext,
8+
} from "solid-js";
9+
import { FaRegularTrashCan } from "solid-icons/fa";
10+
import { ApiKeyGenerateModal } from "./ApiKeyGenerateModal";
11+
import { UserContext } from "../contexts/UserContext";
12+
import {
13+
ApiKeyRespBody,
14+
fromI32ToApiKeyRole,
15+
fromI32ToUserRole,
16+
} from "shared/types";
17+
import { formatDate } from "../formatters";
18+
19+
export const ApiKeys = () => {
20+
const api_host = import.meta.env.VITE_API_HOST as unknown as string;
21+
22+
const userContext = useContext(UserContext);
23+
24+
const [openModal, setOpenModal] = createSignal<boolean>(false);
25+
26+
const currentUserRole = createMemo(() => {
27+
const selectedOrgId = userContext.selectedOrganizationId?.();
28+
if (!selectedOrgId) return 0;
29+
return (
30+
userContext
31+
.user?.()
32+
?.user_orgs.find(
33+
(user_org) => user_org.organization_id === selectedOrgId,
34+
)?.role ?? 0
35+
);
36+
});
37+
38+
const [apiKeys, { refetch, mutate }] = createResource(
39+
() => {
40+
return fetch(`${api_host}/user/api_key`, {
41+
method: "GET",
42+
credentials: "include",
43+
headers: {
44+
"Content-Type": "application/json",
45+
},
46+
})
47+
.then((res) => res.json())
48+
.then((data) => {
49+
return data as ApiKeyRespBody[];
50+
});
51+
},
52+
{ initialValue: [] },
53+
);
54+
55+
const deleteApiKey = (id: string) => {
56+
mutate((prev) => {
57+
return prev.filter((apiKey) => apiKey.id !== id);
58+
});
59+
60+
void fetch(`${api_host}/user/api_key/${id}`, {
61+
method: "DELETE",
62+
credentials: "include",
63+
headers: {
64+
"Content-Type": "application/json",
65+
},
66+
}).then((resp) => {
67+
if (resp.ok) {
68+
void refetch();
69+
}
70+
});
71+
};
72+
73+
return (
74+
<>
75+
<div class="flex flex-col">
76+
<div class="flex items-end justify-between pb-2">
77+
<div class="text-lg font-medium">API Keys</div>
78+
<button
79+
type="button"
80+
class={
81+
"inline-flex justify-center rounded-md bg-magenta-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-magenta-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-magenta-900"
82+
}
83+
onClick={(e) => {
84+
e.preventDefault();
85+
setOpenModal(true);
86+
}}
87+
>
88+
Create New Key +
89+
</button>
90+
</div>
91+
<Show when={apiKeys().length === 0}>
92+
<div class="rounded-md border-[0.5px] border-neutral-300 bg-white py-4 text-center text-sm text-gray-500 shadow-sm">
93+
No API Keys
94+
</div>
95+
</Show>
96+
<Show when={apiKeys().length > 0}>
97+
<div class="inline-block min-w-full overflow-hidden rounded-md border-[0.5px] border-neutral-300 bg-white align-middle shadow-sm">
98+
<table class="min-w-full divide-y divide-gray-300">
99+
<thead class="w-full min-w-full bg-neutral-100">
100+
<tr>
101+
<th
102+
scope="col"
103+
class="py-3.5 pl-6 pr-3 text-left text-sm font-semibold"
104+
>
105+
Name
106+
</th>
107+
<th
108+
scope="col"
109+
class="py-3.5 pl-6 pr-3 text-left text-sm font-semibold"
110+
>
111+
Perms
112+
</th>
113+
<th
114+
scope="col"
115+
class="px-3 py-3.5 text-left text-sm font-semibold"
116+
>
117+
Datasets
118+
</th>
119+
<th
120+
scope="col"
121+
class="px-3 py-3.5 text-left text-sm font-semibold"
122+
>
123+
Organizations
124+
</th>
125+
<th
126+
scope="col"
127+
class="px-3 py-3.5 text-left text-sm font-semibold"
128+
>
129+
Created At
130+
</th>
131+
<th />
132+
</tr>
133+
</thead>
134+
<tbody class="divide-y divide-neutral-200 bg-white">
135+
<For each={apiKeys()}>
136+
{(apiKey) => (
137+
<tr>
138+
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm text-gray-900 sm:pl-6 lg:pl-8">
139+
{apiKey.name}
140+
</td>
141+
<td class="px-3 py-3.5 text-left text-sm text-gray-900">
142+
{apiKey.role > 0
143+
? fromI32ToUserRole(currentUserRole())
144+
: fromI32ToApiKeyRole(apiKey.role).toString()}
145+
</td>
146+
<td class="px-3 py-3.5 text-left text-sm text-gray-900">
147+
<Show when={apiKey.dataset_ids?.length}>[</Show>
148+
<For each={apiKey.dataset_ids}>
149+
{(dataset_id, index) => (
150+
<>
151+
<a
152+
class="text-fuchsia-600 hover:underline"
153+
href={`/dashboard/dataset/${dataset_id}/start`}
154+
>
155+
{dataset_id}
156+
</a>
157+
<Show
158+
when={
159+
index() <
160+
(apiKey.dataset_ids?.length ?? 0) - 1
161+
}
162+
>
163+
{", "}
164+
</Show>
165+
</>
166+
)}
167+
</For>
168+
<Show when={apiKey.dataset_ids?.length}>]</Show>
169+
</td>
170+
<td class="px-3 py-3.5 text-left text-sm text-gray-900">
171+
<Show when={apiKey.organization_ids?.length}>[</Show>
172+
<For each={apiKey.organization_ids}>
173+
{(org_id, index) => (
174+
<>
175+
<a
176+
class="text-fuchsia-600 hover:underline"
177+
href={`/dashboard/${org_id}/overview`}
178+
onClick={() =>
179+
userContext.setSelectedOrganizationId(org_id)
180+
}
181+
>
182+
{org_id}
183+
</a>
184+
<Show
185+
when={
186+
index() <
187+
(apiKey.organization_ids?.length ?? 0) - 1
188+
}
189+
>
190+
{", "}
191+
</Show>
192+
</>
193+
)}
194+
</For>
195+
<Show when={apiKey.organization_ids?.length}>]</Show>
196+
</td>
197+
<td class="px-3 py-3.5 text-left text-sm text-gray-900">
198+
{formatDate(new Date(apiKey.created_at))}
199+
</td>
200+
<td class="px-3 py-3.5 text-center text-sm text-gray-900">
201+
<button
202+
class="text-red-500 hover:text-neutral-900"
203+
onClick={(e) => {
204+
e.preventDefault();
205+
confirm(
206+
"Are you sure you want to delete this key?",
207+
) && deleteApiKey(apiKey.id);
208+
}}
209+
>
210+
<FaRegularTrashCan />
211+
</button>
212+
</td>
213+
</tr>
214+
)}
215+
</For>
216+
</tbody>
217+
</table>
218+
</div>
219+
</Show>
220+
</div>
221+
<ApiKeyGenerateModal
222+
refetch={refetch}
223+
openModal={openModal}
224+
closeModal={() => setOpenModal(false)}
225+
/>
226+
</>
227+
);
228+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { BiRegularInfoCircle } from "solid-icons/bi";
2+
3+
export const BuildingSomething = () => {
4+
return (
5+
<div class="flex w-fit space-x-4 rounded-md border border-blue-600/20 bg-blue-50 px-4 py-4">
6+
<BiRegularInfoCircle class="h-5 w-5 text-blue-400" />
7+
<p class="text-sm text-blue-700">
8+
Building something? Share in our{" "}
9+
<a class="underline" href="https://discord.gg/s4CX3vczyn">
10+
Discord
11+
</a>{" "}
12+
or{" "}
13+
<a
14+
class="underline"
15+
href="https://matrix.to/#/#trieve-general:trieve.ai"
16+
>
17+
Matrix
18+
</a>
19+
; we would love to hear about it!
20+
</p>
21+
</div>
22+
);
23+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* eslint-disable solid/no-innerhtml */
2+
import { createHighlighterCore } from "shiki";
3+
import getWasm from "shiki/wasm";
4+
import { FaRegularClipboard, FaSolidCheck } from "solid-icons/fa";
5+
import { createResource, createSignal, Show } from "solid-js";
6+
7+
interface CodeblockProps {
8+
content: string;
9+
}
10+
11+
export const Codeblock = (props: CodeblockProps) => {
12+
const [copied, setCopied] = createSignal(false);
13+
const [code] = createResource(
14+
() => props.content,
15+
async (content) => {
16+
const highlighter = await createHighlighterCore({
17+
themes: [import("shiki/themes/one-dark-pro.mjs")],
18+
langs: [import("shiki/langs/ts.mjs")],
19+
loadWasm: getWasm,
20+
});
21+
const code = highlighter.codeToHtml(content, {
22+
lang: "ts",
23+
theme: "one-dark-pro",
24+
});
25+
return code;
26+
},
27+
);
28+
29+
const copyCode = () => {
30+
void navigator.clipboard.writeText(props.content).then(() => {
31+
setCopied(true);
32+
setTimeout(() => setCopied(false), 2000);
33+
});
34+
};
35+
36+
return (
37+
<Show when={code()}>
38+
<div class="relative">
39+
<div class="absolute right-5 top-4 z-50 h-4 w-4 text-neutral-400">
40+
<Show
41+
fallback={
42+
<FaRegularClipboard
43+
size={18}
44+
class="cursor-pointer"
45+
onClick={copyCode}
46+
/>
47+
}
48+
when={copied()}
49+
>
50+
<FaSolidCheck size={18} />
51+
</Show>
52+
</div>
53+
<div innerHTML={code()} />
54+
</div>
55+
</Show>
56+
);
57+
};

0 commit comments

Comments
 (0)