zudoku 0.1.1-dev.30 → 0.1.1-dev.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/App.js +7 -2
- package/dist/app/App.js.map +1 -1
- package/dist/config/config.d.ts +5 -0
- package/dist/lib/components/Header.js +1 -1
- package/dist/lib/components/Header.js.map +1 -1
- package/dist/lib/components/Layout.js +1 -1
- package/dist/lib/components/Layout.js.map +1 -1
- package/dist/lib/components/SyntaxHighlight.d.ts +1 -0
- package/dist/lib/components/SyntaxHighlight.js +1 -1
- package/dist/lib/components/SyntaxHighlight.js.map +1 -1
- package/dist/lib/components/context/DevPortalProvider.js +1 -1
- package/dist/lib/components/context/DevPortalProvider.js.map +1 -1
- package/dist/lib/components/navigation/SideNavigation.js +1 -1
- package/dist/lib/components/navigation/SideNavigation.js.map +1 -1
- package/dist/lib/components/navigation/SideNavigationCategory.js +1 -1
- package/dist/lib/components/navigation/SideNavigationCategory.js.map +1 -1
- package/dist/lib/components/navigation/SideNavigationItem.d.ts +1 -0
- package/dist/lib/components/navigation/SideNavigationItem.js +8 -1
- package/dist/lib/components/navigation/SideNavigationItem.js.map +1 -1
- package/dist/lib/components/navigation/SideNavigationWrapper.d.ts +3 -0
- package/dist/lib/components/navigation/SideNavigationWrapper.js +2 -3
- package/dist/lib/components/navigation/SideNavigationWrapper.js.map +1 -1
- package/dist/lib/core/DevPortalContext.d.ts +1 -0
- package/dist/lib/core/DevPortalContext.js +2 -2
- package/dist/lib/core/DevPortalContext.js.map +1 -1
- package/dist/lib/core/plugins.d.ts +1 -1
- package/dist/lib/core/plugins.js +1 -1
- package/dist/lib/core/plugins.js.map +1 -1
- package/dist/lib/plugins/api-key/CreateApiKeys.d.ts +5 -0
- package/dist/lib/plugins/api-key/CreateApiKeys.js +37 -0
- package/dist/lib/plugins/api-key/CreateApiKeys.js.map +1 -0
- package/dist/lib/plugins/api-key/SettingsApiKeys.d.ts +3 -2
- package/dist/lib/plugins/api-key/SettingsApiKeys.js +30 -4
- package/dist/lib/plugins/api-key/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-key/index.d.ts +18 -21
- package/dist/lib/plugins/api-key/index.js +49 -24
- package/dist/lib/plugins/api-key/index.js.map +1 -1
- package/dist/lib/plugins/markdown/MdxPage.js +1 -1
- package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
- package/dist/lib/plugins/openapi/MakeRequest.js +22 -12
- package/dist/lib/plugins/openapi/MakeRequest.js.map +1 -1
- package/dist/lib/plugins/openapi/OperationListItem.js +24 -1
- package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +1 -1
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +2 -2
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
- package/dist/lib/plugins/openapi/Sidecar.js +13 -13
- package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.d.ts +2 -2
- package/dist/lib/plugins/openapi/graphql/gql.js +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
- package/dist/lib/plugins/openapi/graphql/graphql.d.ts +1 -0
- package/dist/lib/plugins/openapi/graphql/graphql.js +4 -0
- package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
- package/dist/lib/plugins/openapi/index.js +2 -0
- package/dist/lib/plugins/openapi/index.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/Headers.js +5 -16
- package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/InlineInput.js +1 -1
- package/dist/lib/plugins/openapi/playground/{UrlParts.d.ts → PathParams.d.ts} +2 -2
- package/dist/lib/plugins/openapi/playground/{UrlParts.js → PathParams.js} +4 -4
- package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/Playground.d.ts +7 -4
- package/dist/lib/plugins/openapi/playground/Playground.js +48 -46
- package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/QueryParams.d.ts +4 -6
- package/dist/lib/plugins/openapi/playground/QueryParams.js +27 -22
- package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/UrlDisplay.d.ts +4 -0
- package/dist/lib/plugins/openapi/playground/UrlDisplay.js +22 -0
- package/dist/lib/plugins/openapi/playground/UrlDisplay.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/createUrl.d.ts +2 -0
- package/dist/lib/plugins/openapi/playground/createUrl.js +15 -0
- package/dist/lib/plugins/openapi/playground/createUrl.js.map +1 -0
- package/dist/lib/plugins/openapi/util/generateSchemaExample.d.ts +1 -1
- package/dist/lib/plugins/openapi/util/generateSchemaExample.js +19 -9
- package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
- package/dist/lib/util/MdxComponents.js +1 -1
- package/dist/lib/util/MdxComponents.js.map +1 -1
- package/dist/vite/plugin-api-keys.d.ts +4 -0
- package/dist/vite/plugin-api-keys.js +33 -0
- package/dist/vite/plugin-api-keys.js.map +1 -0
- package/dist/vite/plugin-component.js +8 -20
- package/dist/vite/plugin-component.js.map +1 -1
- package/dist/vite/plugin-mdx.js +1 -1
- package/dist/vite/plugin-mdx.js.map +1 -1
- package/dist/vite/plugin.js +2 -0
- package/dist/vite/plugin.js.map +1 -1
- package/lib/{urql-B7mLfVog.js → urql-DMlBWUKL.js} +301 -321
- package/lib/{DevPortal-COMmOqxP.js → util-CJko6Ria.js} +3661 -4915
- package/lib/zudoku.components.js +1313 -4
- package/lib/zudoku.openapi-worker.js +1 -1
- package/lib/zudoku.plugins.js +16287 -15966
- package/package.json +1 -1
- package/src/app/App.tsx +7 -2
- package/src/lib/components/Header.tsx +3 -3
- package/src/lib/components/Layout.tsx +6 -1
- package/src/lib/components/SyntaxHighlight.tsx +6 -3
- package/src/lib/components/context/DevPortalProvider.ts +1 -1
- package/src/lib/components/navigation/SideNavigation.tsx +4 -1
- package/src/lib/components/navigation/SideNavigationCategory.tsx +3 -1
- package/src/lib/components/navigation/SideNavigationItem.tsx +8 -1
- package/src/lib/components/navigation/SideNavigationWrapper.tsx +14 -11
- package/src/lib/core/DevPortalContext.ts +3 -2
- package/src/lib/core/plugins.ts +1 -1
- package/src/lib/plugins/api-key/CreateApiKeys.tsx +84 -0
- package/src/lib/plugins/api-key/SettingsApiKeys.tsx +105 -11
- package/src/lib/plugins/api-key/index.tsx +71 -66
- package/src/lib/plugins/markdown/MdxPage.tsx +2 -2
- package/src/lib/plugins/openapi/MakeRequest.tsx +25 -23
- package/src/lib/plugins/openapi/OperationListItem.tsx +137 -1
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +1 -1
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +1 -2
- package/src/lib/plugins/openapi/Sidecar.tsx +20 -20
- package/src/lib/plugins/openapi/graphql/gql.ts +3 -3
- package/src/lib/plugins/openapi/graphql/graphql.ts +5 -0
- package/src/lib/plugins/openapi/index.tsx +2 -0
- package/src/lib/plugins/openapi/playground/Headers.tsx +14 -20
- package/src/lib/plugins/openapi/playground/InlineInput.tsx +1 -1
- package/src/lib/plugins/openapi/playground/{UrlParts.tsx → PathParams.tsx} +22 -26
- package/src/lib/plugins/openapi/playground/Playground.tsx +205 -155
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +84 -57
- package/src/lib/plugins/openapi/playground/UrlDisplay.tsx +32 -0
- package/src/lib/plugins/openapi/playground/createUrl.ts +22 -0
- package/src/lib/plugins/openapi/util/generateSchemaExample.ts +24 -9
- package/src/lib/util/MdxComponents.tsx +1 -0
- package/dist/lib/plugins/openapi/playground/UrlParts.js.map +0 -1
- package/src/lib/plugins/openapi/queries.graphql +0 -6
package/package.json
CHANGED
package/src/app/App.tsx
CHANGED
|
@@ -7,9 +7,10 @@ import config from "virtual:zudoku-config";
|
|
|
7
7
|
import { configuredApiPlugins } from "virtual:zudoku-api-plugins";
|
|
8
8
|
import { configuredAuthProvider } from "virtual:zudoku-auth";
|
|
9
9
|
import { configuredDocsPlugins } from "virtual:zudoku-docs-plugins";
|
|
10
|
+
import { configuredApiKeysPlugin } from "virtual:zudoku-api-keys-plugin";
|
|
10
11
|
|
|
11
12
|
// Base React Component
|
|
12
|
-
import { DevPortal } from "
|
|
13
|
+
import { DevPortal } from "zudoku/components";
|
|
13
14
|
|
|
14
15
|
// IMPORTANT: This component must not contain tailwind classes
|
|
15
16
|
// This directory is not processed by the tailwind plugin
|
|
@@ -27,7 +28,11 @@ export default function App() {
|
|
|
27
28
|
}}
|
|
28
29
|
navigation={config.navigation ?? []}
|
|
29
30
|
authentication={configuredAuthProvider}
|
|
30
|
-
plugins={[
|
|
31
|
+
plugins={[
|
|
32
|
+
...configuredDocsPlugins,
|
|
33
|
+
...configuredApiPlugins,
|
|
34
|
+
configuredApiKeysPlugin,
|
|
35
|
+
]}
|
|
31
36
|
/>
|
|
32
37
|
);
|
|
33
38
|
}
|
|
@@ -25,7 +25,7 @@ export const Header = memo(() => {
|
|
|
25
25
|
</span>
|
|
26
26
|
</div>
|
|
27
27
|
<div className="grid grid-cols-[--sidecar-grid-cols] items-center gap-8">
|
|
28
|
-
<div className="w-full max-w-prose
|
|
28
|
+
<div className="w-full max-w-prose">
|
|
29
29
|
<button className="flex items-center border border-input hover:bg-accent hover:text-accent-foreground p-4 relative h-8 justify-start rounded-lg bg-background text-sm text-muted-foreground shadow-none w-40 sm:w-72">
|
|
30
30
|
<div className="flex items-center gap-2 flex-grow">
|
|
31
31
|
<SearchIcon size={14} />
|
|
@@ -37,9 +37,9 @@ export const Header = memo(() => {
|
|
|
37
37
|
</button>
|
|
38
38
|
</div>
|
|
39
39
|
<div className="items-center justify-self-end text-sm hidden lg:flex">
|
|
40
|
-
{
|
|
40
|
+
{isLoggedIn ? (
|
|
41
41
|
<button
|
|
42
|
-
className="cursor-pointer hover:bg-secondary p-1 px-2 mx-2 rounded"
|
|
42
|
+
className="cursor-pointer hover:bg-secondary p-1 px-2 mx-2 rounded text-nowrap"
|
|
43
43
|
onClick={logout}
|
|
44
44
|
>
|
|
45
45
|
Logout {email ? `(${email})` : null}
|
|
@@ -47,7 +47,12 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
|
47
47
|
>
|
|
48
48
|
<SideNavigation />
|
|
49
49
|
</Suspense>
|
|
50
|
-
<main
|
|
50
|
+
<main
|
|
51
|
+
className="dark:border-white/10 translate-x-0
|
|
52
|
+
lg:overflow-visible
|
|
53
|
+
lg:peer-data-[navigation=true]:w-[calc(100%-var(--side-nav-width))]
|
|
54
|
+
lg:peer-data-[navigation=true]:translate-x-[--side-nav-width] peer-data-[navigation=true]:pl-12"
|
|
55
|
+
>
|
|
51
56
|
{children ?? <Outlet />}
|
|
52
57
|
</main>
|
|
53
58
|
</div>
|
|
@@ -32,6 +32,7 @@ type SyntaxHighlightProps = {
|
|
|
32
32
|
noBackground?: boolean;
|
|
33
33
|
wrapLines?: boolean;
|
|
34
34
|
copyable?: boolean;
|
|
35
|
+
showLanguageIndicator?: boolean;
|
|
35
36
|
} & Omit<HighlightProps, "children">;
|
|
36
37
|
|
|
37
38
|
export const SyntaxHighlight = ({
|
|
@@ -81,9 +82,11 @@ export const SyntaxHighlight = ({
|
|
|
81
82
|
)}
|
|
82
83
|
</button>
|
|
83
84
|
)}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
{props.showLanguageIndicator && (
|
|
86
|
+
<span className="absolute top-1.5 right-3 text-[11px] font-mono text-muted-foreground transition group-hover:opacity-0">
|
|
87
|
+
{props.language}
|
|
88
|
+
</span>
|
|
89
|
+
)}
|
|
87
90
|
{tokens.map((line, i) => (
|
|
88
91
|
<div key={i} {...getLineProps({ line })}>
|
|
89
92
|
{line.map((token, key) => (
|
|
@@ -9,7 +9,10 @@ export const SideNavigation = () => {
|
|
|
9
9
|
const navigation = useNavigation();
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
|
-
<SideNavigationWrapper
|
|
12
|
+
<SideNavigationWrapper
|
|
13
|
+
ref={navRef}
|
|
14
|
+
data={navigation.data.items.length > 0 ? "true" : "false"}
|
|
15
|
+
>
|
|
13
16
|
{navigation.data.items.map((category) => (
|
|
14
17
|
<SideNavigationCategory key={category.label} category={category} />
|
|
15
18
|
))}
|
|
@@ -36,7 +36,7 @@ export const SideNavigationCategory = ({
|
|
|
36
36
|
open={isOpen}
|
|
37
37
|
onOpenChange={() => setIsOpen((prev) => !prev)}
|
|
38
38
|
>
|
|
39
|
-
{category.label.length > 0
|
|
39
|
+
{category.label.length > 0 ? (
|
|
40
40
|
<Collapsible.Trigger asChild={isCollapsible} disabled={!isCollapsible}>
|
|
41
41
|
<h5
|
|
42
42
|
className={cn(
|
|
@@ -53,6 +53,8 @@ export const SideNavigationCategory = ({
|
|
|
53
53
|
)}
|
|
54
54
|
</h5>
|
|
55
55
|
</Collapsible.Trigger>
|
|
56
|
+
) : (
|
|
57
|
+
"Endpoints"
|
|
56
58
|
)}
|
|
57
59
|
<Collapsible.Content className="CollapsibleContent -mx-[--padding-nav-item]">
|
|
58
60
|
{/* margin on Collapsible.Content will lead jumpiness when animating because it's not added to the calculated height */}
|
|
@@ -23,6 +23,10 @@ export const linkItem = cva(
|
|
|
23
23
|
true: "text-primary font-medium",
|
|
24
24
|
false: "text-foreground/80",
|
|
25
25
|
},
|
|
26
|
+
isMuted: {
|
|
27
|
+
true: "opacity-60",
|
|
28
|
+
false: "",
|
|
29
|
+
},
|
|
26
30
|
},
|
|
27
31
|
},
|
|
28
32
|
);
|
|
@@ -54,7 +58,10 @@ export const SideNavigationItem = ({
|
|
|
54
58
|
if (isLinkItem(item)) {
|
|
55
59
|
const classes = cn(
|
|
56
60
|
"flex items-center gap-2",
|
|
57
|
-
linkItem({
|
|
61
|
+
linkItem({
|
|
62
|
+
isActive: item.href === location.pathname,
|
|
63
|
+
isMuted: true,
|
|
64
|
+
}),
|
|
58
65
|
);
|
|
59
66
|
return item.href.startsWith("http") ? (
|
|
60
67
|
<a
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { forwardRef, type PropsWithChildren } from "react";
|
|
2
|
+
import { cn } from "../../util/cn.js";
|
|
2
3
|
|
|
3
4
|
export const SideNavigationWrapper = forwardRef<
|
|
4
5
|
HTMLDivElement,
|
|
5
|
-
PropsWithChildren
|
|
6
|
-
>(({ children }, ref) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
6
|
+
PropsWithChildren<{ data?: string; className?: string }>
|
|
7
|
+
>(({ children, className, data }, ref) => (
|
|
8
|
+
<nav
|
|
9
|
+
data-navigation={data}
|
|
10
|
+
className={cn(
|
|
11
|
+
"peer hidden lg:flex flex-col fixed text-sm overflow-y-auto shrink-0 p-[--padding-nav-item] -mx-[--padding-nav-item] pb-20 pt-[--padding-content-top] w-[--side-nav-width] h-[calc(100%-var(--header-height))] scroll-pt-2 gap-2",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
ref={ref}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</nav>
|
|
18
|
+
));
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type DevPortalProps,
|
|
8
8
|
} from "../components/DevPortal.js";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
isApiIdentityPlugin,
|
|
11
11
|
isNavigationPlugin,
|
|
12
12
|
needsInitialization,
|
|
13
13
|
type DevPortalPlugin,
|
|
@@ -26,6 +26,7 @@ export interface ApiIdentity {
|
|
|
26
26
|
type BaseNavigationCategoryItem = {
|
|
27
27
|
label: ReactNode;
|
|
28
28
|
title?: string;
|
|
29
|
+
muted?: boolean;
|
|
29
30
|
icon?: ReactNode | IconName;
|
|
30
31
|
};
|
|
31
32
|
|
|
@@ -117,7 +118,7 @@ export class DevPortalContext {
|
|
|
117
118
|
getApiIdentities = async () => {
|
|
118
119
|
const keys = await Promise.all(
|
|
119
120
|
this.plugins
|
|
120
|
-
.filter(
|
|
121
|
+
.filter(isApiIdentityPlugin)
|
|
121
122
|
.map((plugin) => plugin.getIdentities(this)),
|
|
122
123
|
);
|
|
123
124
|
|
package/src/lib/core/plugins.ts
CHANGED
|
@@ -37,7 +37,7 @@ export const needsInitialization = (
|
|
|
37
37
|
): obj is InitializationPlugin =>
|
|
38
38
|
"initialize" in obj && typeof obj.initialize === "function";
|
|
39
39
|
|
|
40
|
-
export const
|
|
40
|
+
export const isApiIdentityPlugin = (
|
|
41
41
|
obj: DevPortalPlugin,
|
|
42
42
|
): obj is ApiIdentityPlugin =>
|
|
43
43
|
"getIdentities" in obj && typeof obj.getIdentities === "function";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useDevPortal } from "../../components/context/DevPortalProvider.js";
|
|
2
|
+
import { type ApiKeyPluginOptions, ApiKeyService } from "./index.js";
|
|
3
|
+
import { useMutation } from "@tanstack/react-query";
|
|
4
|
+
import { Button } from "../../ui/Button.js";
|
|
5
|
+
import { Input } from "../../components/Input.js";
|
|
6
|
+
import { cn } from "../../util/cn.js";
|
|
7
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
8
|
+
import { useForm } from "react-hook-form";
|
|
9
|
+
|
|
10
|
+
type CreateApiKeys = { description: string; expiresAt?: string };
|
|
11
|
+
|
|
12
|
+
export const CreateApiKey = ({
|
|
13
|
+
options,
|
|
14
|
+
service,
|
|
15
|
+
}: {
|
|
16
|
+
options: ApiKeyPluginOptions;
|
|
17
|
+
service: ApiKeyService;
|
|
18
|
+
}) => {
|
|
19
|
+
const context = useDevPortal();
|
|
20
|
+
const navigate = useNavigate();
|
|
21
|
+
const form = useForm<CreateApiKeys>();
|
|
22
|
+
const createKeyMutation = useMutation({
|
|
23
|
+
mutationFn: ({ description, expiresAt }: CreateApiKeys) => {
|
|
24
|
+
if (!service.createKey) {
|
|
25
|
+
throw new Error("deleteKey not implemented");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return service.createKey({ description }, context);
|
|
29
|
+
},
|
|
30
|
+
onSuccess: () => navigate("/settings/api-keys/"),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!service.createKey) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="max-w-screen-lg pt-[--padding-content-top] pb-[--padding-content-bottom]">
|
|
39
|
+
<div className="flex justify-between mb-4 border-b pb-1">
|
|
40
|
+
<h1 className="font-medium text-2xl">New API Keys</h1>
|
|
41
|
+
</div>
|
|
42
|
+
<form
|
|
43
|
+
onSubmit={form.handleSubmit((data) => {
|
|
44
|
+
if (data.expiresAt === "never") {
|
|
45
|
+
delete data.expiresAt;
|
|
46
|
+
}
|
|
47
|
+
createKeyMutation.mutate(data);
|
|
48
|
+
})}
|
|
49
|
+
>
|
|
50
|
+
<div className="flex gap-2 flex-col">
|
|
51
|
+
Note
|
|
52
|
+
<Input {...form.register("description")} />
|
|
53
|
+
Expiration
|
|
54
|
+
<select
|
|
55
|
+
className={cn(
|
|
56
|
+
"row-start-1 col-start-1 border border-input text-foreground px-2 py-1 pe-6",
|
|
57
|
+
"rounded-md appearance-none bg-zinc-50 hover:bg-white dark:bg-zinc-800 hover:dark:bg-zinc-800/75",
|
|
58
|
+
)}
|
|
59
|
+
{...form.register("expiresAt")}
|
|
60
|
+
>
|
|
61
|
+
<option value="never">Never</option>
|
|
62
|
+
{[7, 30, 60, 90].map((option) => (
|
|
63
|
+
<option value={addDaysToDate(option)} key={option}>
|
|
64
|
+
{option} Days
|
|
65
|
+
</option>
|
|
66
|
+
))}
|
|
67
|
+
</select>
|
|
68
|
+
<div className="flex gap-2">
|
|
69
|
+
<Button>Generate Key</Button>
|
|
70
|
+
<Button variant="outline" asChild>
|
|
71
|
+
<Link to="/settings/api-keys/">Cancel</Link>
|
|
72
|
+
</Button>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</form>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const addDaysToDate = (days: number): string => {
|
|
81
|
+
const date = new Date();
|
|
82
|
+
date.setDate(date.getDate() + days);
|
|
83
|
+
return date.toISOString().split("T")[0];
|
|
84
|
+
};
|
|
@@ -1,22 +1,116 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type ApiKeyPluginOptions } from "./index.js";
|
|
1
|
+
import { useDevPortal } from "../../components/context/DevPortalProvider.js";
|
|
2
|
+
import { type ApiKeyPluginOptions, ApiKeyService } from "./index.js";
|
|
3
|
+
import {
|
|
4
|
+
useMutation,
|
|
5
|
+
useQueryClient,
|
|
6
|
+
useSuspenseQuery,
|
|
7
|
+
} from "@tanstack/react-query";
|
|
8
|
+
import { Button } from "../../ui/Button.js";
|
|
9
|
+
import { Card } from "../../ui/Card.js";
|
|
10
|
+
import { EyeIcon, EyeOffIcon, RotateCwIcon, TrashIcon } from "lucide-react";
|
|
11
|
+
import { Link } from "react-router-dom";
|
|
12
|
+
import { useState } from "react";
|
|
3
13
|
|
|
4
14
|
export const SettingsApiKeys = ({
|
|
5
15
|
options,
|
|
16
|
+
service,
|
|
6
17
|
}: {
|
|
7
18
|
options: ApiKeyPluginOptions;
|
|
19
|
+
service: ApiKeyService;
|
|
8
20
|
}) => {
|
|
9
|
-
const
|
|
21
|
+
const context = useDevPortal();
|
|
22
|
+
const queryClient = useQueryClient();
|
|
23
|
+
const { data } = useSuspenseQuery({
|
|
24
|
+
queryFn: () => service.getKeys(context),
|
|
25
|
+
queryKey: ["api-keys"],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const deleteKeyMutation = useMutation({
|
|
29
|
+
mutationFn: (id: string) => {
|
|
30
|
+
if (!service.deleteKey) {
|
|
31
|
+
throw new Error("deleteKey not implemented");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return service.deleteKey(id, context);
|
|
35
|
+
},
|
|
36
|
+
onSuccess: () => {
|
|
37
|
+
void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="max-w-screen-lg pt-[--padding-content-top] pb-[--padding-content-bottom]">
|
|
43
|
+
<div className="flex justify-between mb-4 border-b border-border pb-1">
|
|
44
|
+
<h1 className="font-medium text-2xl">API Keys</h1>
|
|
45
|
+
{service.createKey && (
|
|
46
|
+
<Button asChild>
|
|
47
|
+
<Link to="/settings/api-keys/new">Create API Key</Link>
|
|
48
|
+
</Button>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<Card>
|
|
53
|
+
<ul className="grid grid-cols-[min-content_1fr_min-content] ">
|
|
54
|
+
{data?.map((key) => (
|
|
55
|
+
<li
|
|
56
|
+
className="border-b border-border p-5 grid grid-cols-subgrid col-span-full gap-2 items-center"
|
|
57
|
+
key={key.id}
|
|
58
|
+
>
|
|
59
|
+
<div className="flex flex-col gap-1 text-sm">
|
|
60
|
+
{key.description ?? key.id}
|
|
61
|
+
{key.expiresOn ? (
|
|
62
|
+
<div>Expires on {key.expiresOn}</div>
|
|
63
|
+
) : (
|
|
64
|
+
key.createdOn && (
|
|
65
|
+
<div className="text-gray-500">
|
|
66
|
+
Created on {new Date(key.createdOn).toLocaleDateString()}
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
<div className="items-center flex justify-center">
|
|
72
|
+
<RevealApiKey apiKey={key.key} />
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex gap-2">
|
|
75
|
+
{service.rollKey && (
|
|
76
|
+
<Button size="icon">
|
|
77
|
+
<RotateCwIcon size={16} />
|
|
78
|
+
</Button>
|
|
79
|
+
)}
|
|
80
|
+
{service.deleteKey && (
|
|
81
|
+
<Button
|
|
82
|
+
variant="destructive"
|
|
83
|
+
size="icon"
|
|
84
|
+
onClick={() => deleteKeyMutation.mutate(key.id)}
|
|
85
|
+
disabled={deleteKeyMutation.isPending}
|
|
86
|
+
>
|
|
87
|
+
<TrashIcon size={16} />
|
|
88
|
+
</Button>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</li>
|
|
92
|
+
))}
|
|
93
|
+
</ul>
|
|
94
|
+
</Card>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const RevealApiKey = ({ apiKey }: { apiKey: string }) => {
|
|
100
|
+
const [revealed, setRevealed] = useState(false);
|
|
10
101
|
|
|
11
102
|
return (
|
|
12
|
-
<div className="
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
))}
|
|
19
|
-
|
|
103
|
+
<div className="flex gap-2 items-center text-sm">
|
|
104
|
+
<code className="border border-border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono">
|
|
105
|
+
{revealed ? apiKey : "•".repeat(apiKey.length)}
|
|
106
|
+
</code>
|
|
107
|
+
<Button
|
|
108
|
+
variant="outline"
|
|
109
|
+
onClick={() => setRevealed((prev) => !prev)}
|
|
110
|
+
size="icon"
|
|
111
|
+
>
|
|
112
|
+
{revealed ? <EyeOffIcon size={16} /> : <EyeIcon size={16} />}
|
|
113
|
+
</Button>
|
|
20
114
|
</div>
|
|
21
115
|
);
|
|
22
116
|
};
|
|
@@ -1,55 +1,34 @@
|
|
|
1
|
-
import { DevPortalSystemPaths } from "../../components/DevPortal.js";
|
|
2
1
|
import { DevPortalContext } from "../../core/DevPortalContext.js";
|
|
3
2
|
import {
|
|
4
3
|
type ApiIdentityPlugin,
|
|
5
4
|
type DevPortalPlugin,
|
|
6
5
|
} from "../../core/plugins.js";
|
|
7
6
|
import { SettingsApiKeys } from "./SettingsApiKeys.js";
|
|
7
|
+
import { CreateApiKey } from "./CreateApiKeys.js";
|
|
8
8
|
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
| { consumerEndpoint: string };
|
|
9
|
+
export type ApiKeyResults = Promise<ApiKey[]>;
|
|
10
|
+
const DEFAULT_API_KEY_ENDPOINT =
|
|
11
|
+
"https://zudoku-rewiringamerica-main-ef9c9c0.d2.zuplo.dev";
|
|
14
12
|
|
|
15
|
-
export type
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
export type ApiKeyService = {
|
|
14
|
+
getKeys: (context: DevPortalContext) => ApiKeyResults;
|
|
15
|
+
rollKey?: (id: string, context: DevPortalContext) => Promise<void>;
|
|
16
|
+
deleteKey?: (id: string, context: DevPortalContext) => Promise<void>;
|
|
17
|
+
updateKeyDescription?: (
|
|
18
|
+
apiKey: { jd: string; description: string },
|
|
19
19
|
context: DevPortalContext,
|
|
20
20
|
) => Promise<void>;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
consumerName: string,
|
|
21
|
+
createKey?: (
|
|
22
|
+
apiKey: { description: string; expiresAt?: string },
|
|
24
23
|
context: DevPortalContext,
|
|
25
24
|
) => Promise<void>;
|
|
26
|
-
|
|
27
|
-
consumerName: string,
|
|
28
|
-
description: string,
|
|
29
|
-
context: DevPortalContext,
|
|
30
|
-
) => Promise<void>;
|
|
31
|
-
createConsumer?: (
|
|
32
|
-
description: string,
|
|
33
|
-
context: DevPortalContext,
|
|
34
|
-
) => Promise<void>;
|
|
35
|
-
deleteConsumer?: (
|
|
36
|
-
consumerName: string,
|
|
37
|
-
context: DevPortalContext,
|
|
38
|
-
) => Promise<void>;
|
|
39
|
-
} & GetConsumerOptions;
|
|
25
|
+
};
|
|
40
26
|
|
|
41
|
-
export
|
|
42
|
-
consumers: Consumer[];
|
|
43
|
-
}
|
|
27
|
+
export type GetApiKeysOptions = ApiKeyService | { endpoint: string } | {};
|
|
44
28
|
|
|
45
|
-
export
|
|
46
|
-
name: string;
|
|
47
|
-
createdOn?: string;
|
|
48
|
-
description?: string;
|
|
49
|
-
apiKeys: ConsumerApiKey[];
|
|
50
|
-
}
|
|
29
|
+
export type ApiKeyPluginOptions = {} & GetApiKeysOptions;
|
|
51
30
|
|
|
52
|
-
export interface
|
|
31
|
+
export interface ApiKey {
|
|
53
32
|
id: string;
|
|
54
33
|
description?: string;
|
|
55
34
|
createdOn?: string;
|
|
@@ -58,45 +37,67 @@ export interface ConsumerApiKey {
|
|
|
58
37
|
key: string;
|
|
59
38
|
}
|
|
60
39
|
|
|
61
|
-
const
|
|
62
|
-
|
|
40
|
+
const createDefaultHandler = (endpoint: string): ApiKeyService => {
|
|
41
|
+
return {
|
|
42
|
+
deleteKey: async (id, context) => {
|
|
43
|
+
const accessToken = await context.authentication?.getToken?.(context);
|
|
63
44
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
45
|
+
if (!accessToken) {
|
|
46
|
+
throw new Error("Not authenticated");
|
|
47
|
+
}
|
|
67
48
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
49
|
+
await fetch(endpoint + `/v1/developer/api-keys/${id}`, {
|
|
50
|
+
method: "DELETE",
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: `Bearer ${accessToken}`,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
74
55
|
},
|
|
75
|
-
|
|
56
|
+
createKey: async (apiKey, context) => {
|
|
57
|
+
const accessToken = await context.authentication?.getToken?.(context);
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
if (!accessToken) {
|
|
60
|
+
throw new Error("Not authenticated");
|
|
61
|
+
}
|
|
79
62
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
63
|
+
await fetch(endpoint + `/v1/developer/api-keys`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${accessToken}`,
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify(apiKey),
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
getKeys: async (context) => {
|
|
73
|
+
const accessToken = await context.authentication?.getToken?.(context);
|
|
85
74
|
|
|
86
|
-
|
|
87
|
-
getNavigation: async (path: string) => {
|
|
88
|
-
if (path !== DevPortalSystemPaths.Settings) {
|
|
75
|
+
if (!accessToken) {
|
|
89
76
|
return [];
|
|
90
77
|
}
|
|
91
78
|
|
|
92
|
-
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
label: "API Keys",
|
|
96
|
-
children: [],
|
|
79
|
+
const consumers = await fetch(endpoint + `/v1/developer/api-keys`, {
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: `Bearer ${accessToken}`,
|
|
97
82
|
},
|
|
98
|
-
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return await consumers.json();
|
|
99
86
|
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const apiKeyPlugin = (
|
|
91
|
+
options: ApiKeyPluginOptions,
|
|
92
|
+
): DevPortalPlugin & ApiIdentityPlugin => {
|
|
93
|
+
const service =
|
|
94
|
+
"getKeys" in options
|
|
95
|
+
? options
|
|
96
|
+
: createDefaultHandler(
|
|
97
|
+
"endpoint" in options ? options.endpoint : DEFAULT_API_KEY_ENDPOINT,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return {
|
|
100
101
|
getIdentities: async (context) => {
|
|
101
102
|
return [];
|
|
102
103
|
// throw new Error("Not implemented");
|
|
@@ -115,7 +116,11 @@ export const apiKeyPlugin = (
|
|
|
115
116
|
return [
|
|
116
117
|
{
|
|
117
118
|
path: "/settings/api-keys",
|
|
118
|
-
element: <SettingsApiKeys options={options} />,
|
|
119
|
+
element: <SettingsApiKeys options={options} service={service} />,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
path: "/settings/api-keys/new",
|
|
123
|
+
element: <CreateApiKey options={options} service={service} />,
|
|
119
124
|
},
|
|
120
125
|
];
|
|
121
126
|
},
|
|
@@ -75,10 +75,10 @@ export const MdxPage = ({
|
|
|
75
75
|
<div
|
|
76
76
|
className={cn(
|
|
77
77
|
ProseClasses,
|
|
78
|
-
"
|
|
78
|
+
"max-w-full xl:w-full xl:max-w-prose flex-1 flex-shrink pt-[--padding-content-top] pb-[--padding-content-bottom]",
|
|
79
79
|
)}
|
|
80
80
|
>
|
|
81
|
-
<header
|
|
81
|
+
<header>
|
|
82
82
|
{category && <CategoryHeading>{category}</CategoryHeading>}
|
|
83
83
|
{title && (
|
|
84
84
|
<Heading level={1} id={slugify(title, { lower: true })}>
|