zudoku 0.1.1-dev.31 → 0.1.1-dev.33
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 +8 -1
- package/dist/app/App.js.map +1 -1
- package/dist/config/config.d.ts +7 -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 -2
- 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 +2 -1
- package/dist/lib/components/navigation/SideNavigationItem.js +11 -4
- 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 +35 -4
- package/dist/lib/plugins/api-key/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-key/index.d.ts +19 -21
- package/dist/lib/plugins/api-key/index.js +57 -36
- package/dist/lib/plugins/api-key/index.js.map +1 -1
- package/dist/lib/plugins/index.js +0 -1
- package/dist/lib/plugins/index.js.map +1 -1
- package/dist/lib/plugins/openapi/MakeRequest.js +27 -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.d.ts +1 -0
- 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 +8 -4
- package/dist/lib/plugins/openapi/playground/Playground.js +49 -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/plugins/redirect/index.d.ts +1 -2
- package/dist/lib/util/MdxComponents.js +1 -1
- package/dist/lib/util/MdxComponents.js.map +1 -1
- package/dist/lib/util/createVariantComponent.d.ts +4 -3
- package/dist/lib/util/createVariantComponent.js +9 -5
- package/dist/lib/util/createVariantComponent.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-mdx.js +1 -1
- package/dist/vite/plugin-mdx.js.map +1 -1
- package/dist/vite/plugin-redirect.d.ts +4 -0
- package/dist/vite/plugin-redirect.js +32 -0
- package/dist/vite/plugin-redirect.js.map +1 -0
- package/dist/vite/plugin.js +4 -0
- package/dist/vite/plugin.js.map +1 -1
- package/lib/{urql-B7mLfVog.js → urql-DMlBWUKL.js} +301 -321
- package/lib/{DevPortal-Dh66z5c3.js → util-BJVAslZ-.js} +3666 -4913
- package/lib/zudoku.components.js +1310 -4
- package/lib/zudoku.openapi-worker.js +1 -1
- package/lib/zudoku.plugins.js +12876 -12545
- package/package.json +1 -1
- package/src/app/App.tsx +8 -1
- package/src/lib/components/Header.tsx +2 -2
- package/src/lib/components/Layout.tsx +10 -6
- 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 +15 -5
- 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 +111 -11
- package/src/lib/plugins/api-key/index.tsx +80 -77
- package/src/lib/plugins/index.ts +0 -1
- package/src/lib/plugins/openapi/MakeRequest.tsx +38 -29
- 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 +89 -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/plugins/redirect/index.tsx +1 -1
- package/src/lib/util/MdxComponents.tsx +1 -0
- package/src/lib/util/createVariantComponent.tsx +11 -8
- 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,6 +7,8 @@ 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";
|
|
11
|
+
import { configuredRedirectPlugin } from "virtual:zudoku-redirect-plugin";
|
|
10
12
|
|
|
11
13
|
// Base React Component
|
|
12
14
|
import { DevPortal } from "zudoku/components";
|
|
@@ -27,7 +29,12 @@ export default function App() {
|
|
|
27
29
|
}}
|
|
28
30
|
navigation={config.navigation ?? []}
|
|
29
31
|
authentication={configuredAuthProvider}
|
|
30
|
-
plugins={[
|
|
32
|
+
plugins={[
|
|
33
|
+
...configuredDocsPlugins,
|
|
34
|
+
...configuredApiPlugins,
|
|
35
|
+
configuredApiKeysPlugin,
|
|
36
|
+
configuredRedirectPlugin,
|
|
37
|
+
]}
|
|
31
38
|
/>
|
|
32
39
|
);
|
|
33
40
|
}
|
|
@@ -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}
|
|
@@ -41,15 +41,19 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
|
41
41
|
{meta?.favicon && <link rel="icon" href={meta.favicon} />}
|
|
42
42
|
</Helmet>
|
|
43
43
|
<Header />
|
|
44
|
+
|
|
44
45
|
<div className="max-w-screen-2xl mx-auto pt-[--header-height] px-10 lg:px-12">
|
|
45
|
-
<Suspense
|
|
46
|
-
fallback={<SideNavigationWrapper>Loading...</SideNavigationWrapper>}
|
|
47
|
-
>
|
|
46
|
+
<Suspense fallback={<div className="top-48">Warte kurz...</div>}>
|
|
48
47
|
<SideNavigation />
|
|
48
|
+
<main
|
|
49
|
+
className="dark:border-white/10 translate-x-0
|
|
50
|
+
lg:overflow-visible
|
|
51
|
+
lg:peer-data-[navigation=true]:w-[calc(100%-var(--side-nav-width))]
|
|
52
|
+
lg:peer-data-[navigation=true]:translate-x-[--side-nav-width] peer-data-[navigation=true]:pl-12"
|
|
53
|
+
>
|
|
54
|
+
{children ?? <Outlet />}
|
|
55
|
+
</main>
|
|
49
56
|
</Suspense>
|
|
50
|
-
<main className="dark:border-white/10 lg:overflow-visible translate-x-0 lg:w-[calc(100%-var(--side-nav-width))] lg:translate-x-[--side-nav-width] pl-12">
|
|
51
|
-
{children ?? <Outlet />}
|
|
52
|
-
</main>
|
|
53
57
|
</div>
|
|
54
58
|
</>
|
|
55
59
|
);
|
|
@@ -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 */}
|
|
@@ -15,7 +15,7 @@ import { DynamicIcon, isValidIcon } from "../DynamicIcon.js";
|
|
|
15
15
|
import { useNavigationCollapsibleState } from "./useNavigationCollapsibleState.js";
|
|
16
16
|
import { checkHasActiveItem, isLinkItem, isPathItem } from "./util.js";
|
|
17
17
|
|
|
18
|
-
export const
|
|
18
|
+
export const navigationListItem = cva(
|
|
19
19
|
"flex px-[--padding-nav-item] py-1.5 rounded-lg hover:bg-accent transition-colors duration-300",
|
|
20
20
|
{
|
|
21
21
|
variants: {
|
|
@@ -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: "text-foreground/30",
|
|
28
|
+
false: "",
|
|
29
|
+
},
|
|
26
30
|
},
|
|
27
31
|
},
|
|
28
32
|
);
|
|
@@ -54,7 +58,9 @@ export const SideNavigationItem = ({
|
|
|
54
58
|
if (isLinkItem(item)) {
|
|
55
59
|
const classes = cn(
|
|
56
60
|
"flex items-center gap-2",
|
|
57
|
-
|
|
61
|
+
navigationListItem({
|
|
62
|
+
isActive: item.href === location.pathname,
|
|
63
|
+
}),
|
|
58
64
|
);
|
|
59
65
|
return item.href.startsWith("http") ? (
|
|
60
66
|
<a
|
|
@@ -101,7 +107,10 @@ export const SideNavigationItem = ({
|
|
|
101
107
|
className="flex flex-col"
|
|
102
108
|
>
|
|
103
109
|
<Collapsible.Trigger
|
|
104
|
-
className={cn(
|
|
110
|
+
className={cn(
|
|
111
|
+
"group text-start",
|
|
112
|
+
navigationListItem({ isActive: false }),
|
|
113
|
+
)}
|
|
105
114
|
>
|
|
106
115
|
{linkContent}
|
|
107
116
|
</Collapsible.Trigger>
|
|
@@ -124,15 +133,16 @@ export const SideNavigationItem = ({
|
|
|
124
133
|
<AnchorLink
|
|
125
134
|
to={item.path}
|
|
126
135
|
{...{ [DATA_ANCHOR_ATTR]: item.path }}
|
|
127
|
-
className={
|
|
136
|
+
className={navigationListItem({
|
|
128
137
|
isActive: item.path.slice(1) === activeAnchor,
|
|
138
|
+
isMuted: item.muted,
|
|
129
139
|
})}
|
|
130
140
|
>
|
|
131
141
|
{linkContent}
|
|
132
142
|
</AnchorLink>
|
|
133
143
|
) : (
|
|
134
144
|
<NavLink
|
|
135
|
-
className={({ isActive }) =>
|
|
145
|
+
className={({ isActive }) => navigationListItem({ isActive })}
|
|
136
146
|
to={currentPath}
|
|
137
147
|
>
|
|
138
148
|
{linkContent}
|
|
@@ -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,122 @@
|
|
|
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 whitespace-pre">
|
|
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="ghost"
|
|
83
|
+
size="icon"
|
|
84
|
+
onClick={() => {
|
|
85
|
+
if (!confirm("Do you want to delete this key?")) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
deleteKeyMutation.mutate(key.id);
|
|
90
|
+
}}
|
|
91
|
+
disabled={deleteKeyMutation.isPending}
|
|
92
|
+
>
|
|
93
|
+
<TrashIcon size={16} />
|
|
94
|
+
</Button>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
</li>
|
|
98
|
+
))}
|
|
99
|
+
</ul>
|
|
100
|
+
</Card>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const RevealApiKey = ({ apiKey }: { apiKey: string }) => {
|
|
106
|
+
const [revealed, setRevealed] = useState(false);
|
|
10
107
|
|
|
11
108
|
return (
|
|
12
|
-
<div className="
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
))}
|
|
19
|
-
|
|
109
|
+
<div className="flex gap-2 items-center text-sm">
|
|
110
|
+
<code className="border border-border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono">
|
|
111
|
+
{revealed ? apiKey : "•".repeat(apiKey.length)}
|
|
112
|
+
</code>
|
|
113
|
+
<Button
|
|
114
|
+
variant="outline"
|
|
115
|
+
onClick={() => setRevealed((prev) => !prev)}
|
|
116
|
+
size="icon"
|
|
117
|
+
>
|
|
118
|
+
{revealed ? <EyeOffIcon size={16} /> : <EyeIcon size={16} />}
|
|
119
|
+
</Button>
|
|
20
120
|
</div>
|
|
21
121
|
);
|
|
22
122
|
};
|