zudoku 0.66.2 → 0.66.4
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/config/validators/validate.d.ts +32 -3
- package/dist/config/validators/validate.js +1 -1
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/flat-config.d.ts +1 -1
- package/dist/lib/auth/issuer.js +1 -1
- package/dist/lib/auth/issuer.js.map +1 -1
- package/dist/lib/authentication/authentication.d.ts +3 -2
- package/dist/lib/authentication/components/SignIn.js +4 -2
- package/dist/lib/authentication/components/SignIn.js.map +1 -1
- package/dist/lib/authentication/components/SignUp.js +4 -2
- package/dist/lib/authentication/components/SignUp.js.map +1 -1
- package/dist/lib/authentication/hook.d.ts +2 -0
- package/dist/lib/authentication/hook.js +10 -0
- package/dist/lib/authentication/hook.js.map +1 -1
- package/dist/lib/authentication/providers/firebase.js +67 -9
- package/dist/lib/authentication/providers/firebase.js.map +1 -1
- package/dist/lib/authentication/ui/EmailVerificationUi.d.ts +4 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js +34 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js.map +1 -0
- package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +7 -2
- package/dist/lib/authentication/ui/ZudokuAuthUi.js +43 -11
- package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -1
- package/dist/lib/authentication/utils/relativeRedirectUrl.d.ts +1 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js +8 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js.map +1 -0
- package/dist/lib/components/index.d.ts +2 -0
- package/dist/lib/errors/ErrorMessage.d.ts +3 -0
- package/dist/lib/errors/ErrorMessage.js +16 -0
- package/dist/lib/errors/ErrorMessage.js.map +1 -0
- package/dist/lib/hooks/index.d.ts +2 -0
- package/dist/lib/oas/graphql/index.js +7 -3
- package/dist/lib/oas/graphql/index.js.map +1 -1
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js +9 -172
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.d.ts +4 -1
- package/dist/lib/plugins/api-keys/index.js +21 -17
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.d.ts +12 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js +133 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.d.ts +4 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js +30 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.d.ts +6 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js +39 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js.map +1 -0
- package/dist/lib/plugins/openapi/ParamInfos.js +1 -0
- package/dist/lib/plugins/openapi/ParamInfos.js.map +1 -1
- package/dist/lib/plugins/openapi/Sidecar.js +3 -2
- package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
- package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
- package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
- package/dist/lib/plugins/openapi/util/createHttpSnippet.js +24 -1
- package/dist/lib/plugins/openapi/util/createHttpSnippet.js.map +1 -1
- package/dist/lib/ui/Button.js +1 -1
- package/dist/lib/ui/Button.js.map +1 -1
- package/dist/vite/config.js +7 -0
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/plugin-api-keys.js +4 -1
- package/dist/vite/plugin-api-keys.js.map +1 -1
- package/dist/vite/plugin-markdown-export.js +1 -1
- package/dist/vite/plugin-markdown-export.js.map +1 -1
- package/dist/vite/prerender/worker.js +3 -0
- package/dist/vite/prerender/worker.js.map +1 -1
- package/dist/vite/zuplo.d.ts +13 -0
- package/dist/vite/zuplo.js +15 -0
- package/dist/vite/zuplo.js.map +1 -0
- package/lib/{ActionButton-DUgvSylL.js → ActionButton-BSM2oNHF.js} +2 -2
- package/lib/{ActionButton-DUgvSylL.js.map → ActionButton-BSM2oNHF.js.map} +1 -1
- package/lib/{Button-CynVW1JV.js → Button-IOAeVaIH.js} +7 -6
- package/lib/{Button-CynVW1JV.js.map → Button-IOAeVaIH.js.map} +1 -1
- package/lib/{ClaudeLogo-CGRfGTk2.js → ClaudeLogo-DgjivS8A.js} +3 -3
- package/lib/{ClaudeLogo-CGRfGTk2.js.map → ClaudeLogo-DgjivS8A.js.map} +1 -1
- package/lib/{Drawer-Ci7XwhqT.js → Drawer-BRMcpfuF.js} +6 -6
- package/lib/{Drawer-Ci7XwhqT.js.map → Drawer-BRMcpfuF.js.map} +1 -1
- package/lib/Frame-DxlznfVd.js +205 -0
- package/lib/Frame-DxlznfVd.js.map +1 -0
- package/lib/{IndexingDialog-B5zCiUKr.js → IndexingDialog-DZWj_3cU.js} +2 -2
- package/lib/{IndexingDialog-B5zCiUKr.js.map → IndexingDialog-DZWj_3cU.js.map} +1 -1
- package/lib/{useMutation-C6RqWmTS.js → Input-D-kqEQ5M.js} +41 -23
- package/lib/Input-D-kqEQ5M.js.map +1 -0
- package/lib/{MdxPage-Bjf72BP3.js → MdxPage-BL-HbZrv.js} +9 -9
- package/lib/{MdxPage-Bjf72BP3.js.map → MdxPage-BL-HbZrv.js.map} +1 -1
- package/lib/{Mermaid-D_VSX7_Q.js → Mermaid-BjSczjLW.js} +3 -3
- package/lib/{Mermaid-D_VSX7_Q.js.map → Mermaid-BjSczjLW.js.map} +1 -1
- package/lib/{OAuthErrorPage-1Ekji0PK.js → OAuthErrorPage-DQtg28Go.js} +20 -21
- package/lib/{OAuthErrorPage-1Ekji0PK.js.map → OAuthErrorPage-DQtg28Go.js.map} +1 -1
- package/lib/{OasProvider-BZxmTyMM.js → OasProvider--qcZwrKS.js} +4 -4
- package/lib/{OasProvider-BZxmTyMM.js.map → OasProvider--qcZwrKS.js.map} +1 -1
- package/lib/{OperationList-B7nPIFB8.js → OperationList-CSJYzxQY.js} +1101 -1087
- package/lib/{OperationList-B7nPIFB8.js.map → OperationList-CSJYzxQY.js.map} +1 -1
- package/lib/{RouteGuard-9wjejsKm.js → RouteGuard-D0f743SM.js} +5 -5
- package/lib/{RouteGuard-9wjejsKm.js.map → RouteGuard-D0f743SM.js.map} +1 -1
- package/lib/{SchemaList-16_obkku.js → SchemaList-DtyuDrQA.js} +8 -8
- package/lib/{SchemaList-16_obkku.js.map → SchemaList-DtyuDrQA.js.map} +1 -1
- package/lib/SchemaView-G-SVXxAG.js +435 -0
- package/lib/SchemaView-G-SVXxAG.js.map +1 -0
- package/lib/{Select-CkxXP5I7.js → Secret-BxGpIhDP.js} +121 -121
- package/lib/Secret-BxGpIhDP.js.map +1 -0
- package/lib/SignUp-CDl7bQj3.js +50 -0
- package/lib/SignUp-CDl7bQj3.js.map +1 -0
- package/lib/{SyntaxHighlight-j_HRSPCU.js → SyntaxHighlight-Dgd0AaaX.js} +2 -2
- package/lib/{SyntaxHighlight-j_HRSPCU.js.map → SyntaxHighlight-Dgd0AaaX.js.map} +1 -1
- package/lib/{Toc-z05x698-.js → Toc-D_Rj4jVx.js} +2 -2
- package/lib/{Toc-z05x698-.js.map → Toc-D_Rj4jVx.js.map} +1 -1
- package/lib/{ZudokuContext-BXldanA8.js → ZudokuContext-DNHMZfcP.js} +33 -33
- package/lib/{ZudokuContext-BXldanA8.js.map → ZudokuContext-DNHMZfcP.js.map} +1 -1
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js → chunk-PVWAREVJ-ClM0m2aJ.js} +19 -19
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js.map → chunk-PVWAREVJ-ClM0m2aJ.js.map} +1 -1
- package/lib/{circular-D5sYCIWL.js → circular-BxODTa7z.js} +2 -2
- package/lib/{circular-D5sYCIWL.js.map → circular-BxODTa7z.js.map} +1 -1
- package/lib/{createServer-BlwU7lIr.js → createServer-BpreIXp6.js} +10 -10
- package/lib/{createServer-BlwU7lIr.js.map → createServer-BpreIXp6.js.map} +1 -1
- package/lib/createVariantComponent-CQVt-H3r.js +18 -0
- package/lib/createVariantComponent-CQVt-H3r.js.map +1 -0
- package/lib/{errors-BtC4Kn2j.js → errors-DliW1dED.js} +2 -2
- package/lib/{errors-BtC4Kn2j.js.map → errors-DliW1dED.js.map} +1 -1
- package/lib/{firebase-Ibm_tv3G.js → firebase-D4tbaCYB.js} +1588 -1342
- package/lib/firebase-D4tbaCYB.js.map +1 -0
- package/lib/hook-CHw_R_xu.js +52 -0
- package/lib/hook-CHw_R_xu.js.map +1 -0
- package/lib/{index-eKVhlB94.js → index-1TbL0HXQ.js} +2 -2
- package/lib/{index-eKVhlB94.js.map → index-1TbL0HXQ.js.map} +1 -1
- package/lib/{index-CeVTNcfF.js → index-9MxNUgg4.js} +99 -100
- package/lib/{index-CeVTNcfF.js.map → index-9MxNUgg4.js.map} +1 -1
- package/lib/{ErrorAlert-BUlG32M9.js → index-CboxZOVW.js} +5373 -4335
- package/lib/index-CboxZOVW.js.map +1 -0
- package/lib/index-CrcNWbel.js.map +1 -1
- package/lib/{index-Css56y3F.js → index-DXXZDuSJ.js} +4 -4
- package/lib/{index-Css56y3F.js.map → index-DXXZDuSJ.js.map} +1 -1
- package/lib/index.esm-BYObtETB.js.map +1 -1
- package/lib/index.esm-DtzT_KoE.js.map +1 -1
- package/lib/{index.esm-BoKBnRoT.js → index.esm-ti5zvZS_.js} +16 -14
- package/lib/index.esm-ti5zvZS_.js.map +1 -0
- package/lib/jsx-runtime-BzflLqGi.js.map +1 -1
- package/lib/{mutation-BoVlx8yA.js → mutation-DMHWqmFp.js} +2 -2
- package/lib/{mutation-BoVlx8yA.js.map → mutation-DMHWqmFp.js.map} +1 -1
- package/lib/ui/ActionButton.js +1 -1
- package/lib/ui/Button.js +6 -5
- package/lib/ui/Button.js.map +1 -1
- package/lib/ui/Carousel.js.map +1 -1
- package/lib/ui/Drawer.js +2 -2
- package/lib/ui/SyntaxHighlight.js +2 -2
- package/lib/zudoku.__internal.js +507 -479
- package/lib/zudoku.__internal.js.map +1 -1
- package/lib/zudoku.auth-auth0.js +1 -1
- package/lib/zudoku.auth-azureb2c.js +4 -4
- package/lib/zudoku.auth-clerk.js +2 -2
- package/lib/zudoku.auth-firebase.js +6 -5
- package/lib/zudoku.auth-firebase.js.map +1 -1
- package/lib/zudoku.auth-openid.js +4 -4
- package/lib/zudoku.auth-supabase.js +5 -5
- package/lib/zudoku.components.js +20 -21
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +3 -3
- package/lib/zudoku.mermaid.js +3 -3
- package/lib/zudoku.plugin-api-catalog.js +8 -9
- package/lib/zudoku.plugin-api-catalog.js.map +1 -1
- package/lib/zudoku.plugin-api-keys.js +579 -544
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- package/lib/zudoku.plugin-custom-pages.js +1 -1
- package/lib/zudoku.plugin-markdown.js +1 -1
- package/lib/zudoku.plugin-openapi.js +3 -3
- package/lib/zudoku.plugin-redirect.js +1 -1
- package/lib/zudoku.plugin-search-pagefind.js +5 -5
- package/lib/zudoku.router.js +2 -2
- package/lib/zudoku.router.js.map +1 -1
- package/package.json +7 -5
- package/src/lib/auth/issuer.ts +1 -1
- package/src/lib/authentication/authentication.ts +8 -2
- package/src/lib/authentication/components/SignIn.tsx +5 -2
- package/src/lib/authentication/components/SignUp.tsx +5 -2
- package/src/lib/authentication/hook.ts +16 -0
- package/src/lib/authentication/providers/firebase.tsx +98 -6
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +129 -0
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +170 -38
- package/src/lib/authentication/utils/relativeRedirectUrl.ts +12 -0
- package/src/lib/errors/ErrorMessage.tsx +38 -0
- package/src/lib/oas/graphql/index.ts +7 -3
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +36 -476
- package/src/lib/plugins/api-keys/index.tsx +35 -21
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -0
- package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +64 -0
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +124 -0
- package/src/lib/plugins/openapi/ParamInfos.tsx +1 -0
- package/src/lib/plugins/openapi/Sidecar.tsx +3 -2
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +6 -4
- package/src/lib/plugins/openapi/util/createHttpSnippet.ts +29 -1
- package/src/lib/ui/Button.tsx +1 -0
- package/lib/ErrorAlert-BUlG32M9.js.map +0 -1
- package/lib/RouterError-DfTZblpv.js +0 -42
- package/lib/RouterError-DfTZblpv.js.map +0 -1
- package/lib/SchemaView-eyvR4bRt.js +0 -597
- package/lib/SchemaView-eyvR4bRt.js.map +0 -1
- package/lib/Select-CkxXP5I7.js.map +0 -1
- package/lib/SignUp-D54_QWFy.js +0 -50
- package/lib/SignUp-D54_QWFy.js.map +0 -1
- package/lib/createVariantComponent-B9_dVBvu.js +0 -35
- package/lib/createVariantComponent-B9_dVBvu.js.map +0 -1
- package/lib/firebase-Ibm_tv3G.js.map +0 -1
- package/lib/hook-BNxidGQq.js +0 -40
- package/lib/hook-BNxidGQq.js.map +0 -1
- package/lib/index-DSOi7zVM.js +0 -1059
- package/lib/index-DSOi7zVM.js.map +0 -1
- package/lib/index.esm-BoKBnRoT.js.map +0 -1
- package/lib/useMutation-C6RqWmTS.js.map +0 -1
|
@@ -1,181 +1,26 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
3
|
+
import { Button } from "zudoku/components";
|
|
4
|
+
import { useAuth } from "zudoku/hooks";
|
|
1
5
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
CircleSlashIcon,
|
|
9
|
-
PencilLineIcon,
|
|
10
|
-
RefreshCwIcon,
|
|
11
|
-
TrashIcon,
|
|
12
|
-
XIcon,
|
|
13
|
-
} from "lucide-react";
|
|
14
|
-
import { AnimatePresence } from "motion/react";
|
|
15
|
-
import React, { useState } from "react";
|
|
16
|
-
import { Alert, AlertTitle } from "zudoku/ui/Alert.js";
|
|
17
|
-
import { Card, CardHeader } from "zudoku/ui/Card.js";
|
|
18
|
-
import {
|
|
19
|
-
Dialog,
|
|
20
|
-
DialogClose,
|
|
21
|
-
DialogContent,
|
|
22
|
-
DialogDescription,
|
|
23
|
-
DialogFooter,
|
|
24
|
-
DialogHeader,
|
|
25
|
-
DialogTitle,
|
|
26
|
-
DialogTrigger,
|
|
27
|
-
} from "zudoku/ui/Dialog.js";
|
|
28
|
-
import { Secret } from "zudoku/ui/Secret.js";
|
|
29
|
-
import { useZudoku } from "../../components/context/ZudokuContext.js";
|
|
6
|
+
Item,
|
|
7
|
+
ItemActions,
|
|
8
|
+
ItemContent,
|
|
9
|
+
ItemDescription,
|
|
10
|
+
ItemTitle,
|
|
11
|
+
} from "zudoku/ui/Item.js";
|
|
30
12
|
import { Slot } from "../../components/Slot.js";
|
|
31
|
-
import {
|
|
32
|
-
import { Input } from "../../ui/Input.js";
|
|
33
|
-
import { cn } from "../../util/cn.js";
|
|
13
|
+
import { ErrorMessage } from "../../errors/ErrorMessage.js";
|
|
34
14
|
import { CreateApiKeyDialog } from "./CreateApiKeyDialog.js";
|
|
35
|
-
import type {
|
|
15
|
+
import type { ApiKeyService } from "./index.js";
|
|
16
|
+
import { ApiKeyList } from "./settings/ApiKeyList.js";
|
|
36
17
|
|
|
37
18
|
export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
38
|
-
const context = useZudoku();
|
|
39
|
-
const queryClient = useQueryClient();
|
|
40
|
-
const [editingConsumerId, setEditingConsumerId] = useState<string | null>(
|
|
41
|
-
null,
|
|
42
|
-
);
|
|
43
|
-
const [editingLabel, setEditingLabel] = useState<string>("");
|
|
44
|
-
const { data, isFetching } = useSuspenseQuery({
|
|
45
|
-
queryFn: () => service.getConsumers(context),
|
|
46
|
-
queryKey: ["api-keys"],
|
|
47
|
-
retry: false,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
19
|
const [isCreateApiKeyOpen, setIsCreateApiKeyOpen] = useState(false);
|
|
51
|
-
|
|
52
|
-
const deleteKeyMutation = useMutation({
|
|
53
|
-
mutationFn: ({
|
|
54
|
-
consumerId,
|
|
55
|
-
keyId,
|
|
56
|
-
}: {
|
|
57
|
-
consumerId: string;
|
|
58
|
-
keyId: string;
|
|
59
|
-
}) => {
|
|
60
|
-
if (!service.deleteKey) {
|
|
61
|
-
throw new Error("deleteKey not implemented");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return service.deleteKey(consumerId, keyId, context);
|
|
65
|
-
},
|
|
66
|
-
onMutate: async ({ consumerId, keyId }) => {
|
|
67
|
-
await queryClient.cancelQueries({ queryKey: ["api-keys"] });
|
|
68
|
-
const previousData = queryClient.getQueryData<ApiConsumer[]>([
|
|
69
|
-
"api-keys",
|
|
70
|
-
]);
|
|
71
|
-
queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
|
|
72
|
-
if (!old) {
|
|
73
|
-
return old;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return old.map((consumer) => {
|
|
77
|
-
if (consumer.id === consumerId) {
|
|
78
|
-
return {
|
|
79
|
-
...consumer,
|
|
80
|
-
apiKeys: consumer.apiKeys.filter((key) => key.id !== keyId),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
return consumer;
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
return { previousData };
|
|
88
|
-
},
|
|
89
|
-
onError: (_err, _variables, context) => {
|
|
90
|
-
if (context?.previousData) {
|
|
91
|
-
queryClient.setQueryData(["api-keys"], context.previousData);
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
onSuccess: () => {
|
|
95
|
-
void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const updateConsumerMutation = useMutation({
|
|
100
|
-
mutationFn: ({
|
|
101
|
-
consumerId,
|
|
102
|
-
label,
|
|
103
|
-
}: {
|
|
104
|
-
consumerId: string;
|
|
105
|
-
label: string;
|
|
106
|
-
}) => {
|
|
107
|
-
if (!service.updateConsumer) {
|
|
108
|
-
throw new Error("updateConsumer not implemented");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return service.updateConsumer({ id: consumerId, label }, context);
|
|
112
|
-
},
|
|
113
|
-
onMutate: async ({ consumerId, label }) => {
|
|
114
|
-
await queryClient.cancelQueries({ queryKey: ["api-keys"] });
|
|
115
|
-
|
|
116
|
-
const previousData = queryClient.getQueryData(["api-keys"]);
|
|
117
|
-
queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
|
|
118
|
-
if (!old) {
|
|
119
|
-
return old;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return old.map((consumer) => {
|
|
123
|
-
if (consumer.id === consumerId) {
|
|
124
|
-
return {
|
|
125
|
-
...consumer,
|
|
126
|
-
label,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return consumer;
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
return { previousData };
|
|
134
|
-
},
|
|
135
|
-
onError: (_err, _variables, context) => {
|
|
136
|
-
if (context?.previousData) {
|
|
137
|
-
queryClient.setQueryData(["api-keys"], context.previousData);
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
onSuccess: () => {
|
|
141
|
-
void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const rollKeyMutation = useMutation({
|
|
146
|
-
mutationFn: (id: string) => {
|
|
147
|
-
if (!service.rollKey) {
|
|
148
|
-
throw new Error("rollKey not implemented");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return service.rollKey(id, context);
|
|
152
|
-
},
|
|
153
|
-
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["api-keys"] }),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const handleStartEdit = (consumerId: string, currentLabel: string) => {
|
|
157
|
-
setEditingConsumerId(consumerId);
|
|
158
|
-
setEditingLabel(currentLabel);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const handleSaveEdit = (consumerId: string) => {
|
|
162
|
-
if (editingLabel.trim()) {
|
|
163
|
-
updateConsumerMutation.mutate({
|
|
164
|
-
consumerId,
|
|
165
|
-
label: editingLabel.trim(),
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
setEditingConsumerId(null);
|
|
169
|
-
setEditingLabel("");
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const handleCancelEdit = () => {
|
|
173
|
-
setEditingConsumerId(null);
|
|
174
|
-
setEditingLabel("");
|
|
175
|
-
};
|
|
20
|
+
const auth = useAuth();
|
|
176
21
|
|
|
177
22
|
return (
|
|
178
|
-
<div className="max-w-
|
|
23
|
+
<div className="max-w-3xl h-full pt-(--padding-content-top) pb-(--padding-content-bottom)">
|
|
179
24
|
<Slot.Target name="api-keys-list-page" />
|
|
180
25
|
|
|
181
26
|
<div className="flex justify-between pb-3">
|
|
@@ -192,313 +37,28 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
|
192
37
|
<p>Create, manage, and monitor your API keys</p>
|
|
193
38
|
|
|
194
39
|
<Slot.Target name="api-keys-list-page-before-keys" />
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
40
|
+
{auth.profile?.emailVerified === false ? (
|
|
41
|
+
<Item variant="outline">
|
|
42
|
+
<ItemContent>
|
|
43
|
+
<ItemTitle>Verified email required</ItemTitle>
|
|
44
|
+
<ItemDescription>
|
|
45
|
+
You need to verify your email to access API keys.
|
|
46
|
+
</ItemDescription>
|
|
47
|
+
</ItemContent>
|
|
48
|
+
|
|
49
|
+
<ItemActions>
|
|
50
|
+
<Button onClick={() => auth.requestEmailVerification()}>
|
|
51
|
+
Verify email
|
|
52
|
+
</Button>
|
|
53
|
+
</ItemActions>
|
|
54
|
+
</Item>
|
|
55
|
+
) : (
|
|
56
|
+
<ErrorBoundary
|
|
57
|
+
fallbackRender={({ error }) => <ErrorMessage error={error} />}
|
|
58
|
+
>
|
|
59
|
+
<ApiKeyList service={service} />
|
|
60
|
+
</ErrorBoundary>
|
|
207
61
|
)}
|
|
208
|
-
{deleteKeyMutation.isError && (
|
|
209
|
-
<Alert variant="destructive" className="mb-4">
|
|
210
|
-
<CircleSlashIcon size={16} />
|
|
211
|
-
<AlertTitle>{deleteKeyMutation.error.message}</AlertTitle>
|
|
212
|
-
</Alert>
|
|
213
|
-
)}
|
|
214
|
-
<div className="">
|
|
215
|
-
{data.length === 0 ? (
|
|
216
|
-
<div className="flex col-span-full flex-col justify-center gap-4 items-center p-8 border rounded-sm bg-muted/30 text-muted-foreground">
|
|
217
|
-
<p className="text-center">
|
|
218
|
-
You have no API keys yet.
|
|
219
|
-
<br />
|
|
220
|
-
{service.createKey && "Get started and create your first key."}
|
|
221
|
-
</p>
|
|
222
|
-
{service.createKey && (
|
|
223
|
-
<CreateApiKeyDialog
|
|
224
|
-
service={service}
|
|
225
|
-
isOpen={isCreateApiKeyOpen}
|
|
226
|
-
onOpenChange={setIsCreateApiKeyOpen}
|
|
227
|
-
/>
|
|
228
|
-
)}
|
|
229
|
-
</div>
|
|
230
|
-
) : (
|
|
231
|
-
<ul
|
|
232
|
-
className={cn(
|
|
233
|
-
"grid grid-cols-[1fr_min-content] divide-y divide-border col-span-6",
|
|
234
|
-
)}
|
|
235
|
-
>
|
|
236
|
-
{data.map((consumers) => (
|
|
237
|
-
<Card
|
|
238
|
-
className="grid grid-cols-subgrid col-span-full items-center mb-4 group"
|
|
239
|
-
key={consumers.id}
|
|
240
|
-
>
|
|
241
|
-
<CardHeader className="border-b col-span-full grid-cols-subgrid grid">
|
|
242
|
-
<div className="h-10 flex flex-col text-sm justify-center">
|
|
243
|
-
<div className="font-medium text-lg flex items-center gap-2">
|
|
244
|
-
{editingConsumerId === consumers.id ? (
|
|
245
|
-
<div className="flex items-center gap-2 w-full">
|
|
246
|
-
<Input
|
|
247
|
-
maxLength={32}
|
|
248
|
-
value={editingLabel}
|
|
249
|
-
onChange={(e) => setEditingLabel(e.target.value)}
|
|
250
|
-
onKeyDown={(e) => {
|
|
251
|
-
if (e.key === "Enter") {
|
|
252
|
-
handleSaveEdit(consumers.id);
|
|
253
|
-
} else if (e.key === "Escape") {
|
|
254
|
-
handleCancelEdit();
|
|
255
|
-
}
|
|
256
|
-
}}
|
|
257
|
-
className="text-lg font-medium"
|
|
258
|
-
autoFocus
|
|
259
|
-
/>
|
|
260
|
-
<div className="flex items-center">
|
|
261
|
-
<Button
|
|
262
|
-
size="icon"
|
|
263
|
-
variant="ghost"
|
|
264
|
-
onClick={() => handleSaveEdit(consumers.id)}
|
|
265
|
-
disabled={!editingLabel.trim()}
|
|
266
|
-
>
|
|
267
|
-
<CheckIcon size={16} />
|
|
268
|
-
</Button>
|
|
269
|
-
<Button
|
|
270
|
-
size="icon"
|
|
271
|
-
variant="ghost"
|
|
272
|
-
onClick={handleCancelEdit}
|
|
273
|
-
>
|
|
274
|
-
<XIcon size={16} />
|
|
275
|
-
</Button>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
) : (
|
|
279
|
-
consumers.label
|
|
280
|
-
)}
|
|
281
|
-
<div className="text-muted-foreground text-xs">
|
|
282
|
-
{consumers.createdOn}
|
|
283
|
-
</div>
|
|
284
|
-
</div>
|
|
285
|
-
<div className="text-muted-foreground text-xs">
|
|
286
|
-
{consumers.createdOn && (
|
|
287
|
-
<div>
|
|
288
|
-
Created on{" "}
|
|
289
|
-
{new Date(consumers.createdOn).toLocaleDateString()}
|
|
290
|
-
</div>
|
|
291
|
-
)}
|
|
292
|
-
{consumers.expiresOn && (
|
|
293
|
-
<div>
|
|
294
|
-
Expires on{" "}
|
|
295
|
-
{new Date(consumers.expiresOn).toLocaleDateString()}
|
|
296
|
-
</div>
|
|
297
|
-
)}
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
|
|
301
|
-
<div className="flex justify-end">
|
|
302
|
-
{service.updateConsumer && (
|
|
303
|
-
<Button
|
|
304
|
-
variant="ghost"
|
|
305
|
-
onClick={() =>
|
|
306
|
-
handleStartEdit(consumers.id, consumers.label)
|
|
307
|
-
}
|
|
308
|
-
className={cn(
|
|
309
|
-
"flex gap-2",
|
|
310
|
-
editingConsumerId === consumers.id &&
|
|
311
|
-
"opacity-0! pointer-events-none",
|
|
312
|
-
)}
|
|
313
|
-
disabled={editingConsumerId === consumers.id}
|
|
314
|
-
>
|
|
315
|
-
<PencilLineIcon size={16} />
|
|
316
|
-
<span className="hidden md:block">Edit label</span>
|
|
317
|
-
</Button>
|
|
318
|
-
)}
|
|
319
|
-
{service.rollKey && (
|
|
320
|
-
<Dialog>
|
|
321
|
-
<DialogTrigger asChild>
|
|
322
|
-
<Button
|
|
323
|
-
title="Roll this key"
|
|
324
|
-
variant="ghost"
|
|
325
|
-
disabled={rollKeyMutation.isPending}
|
|
326
|
-
className="flex items-center gap-2"
|
|
327
|
-
>
|
|
328
|
-
<RefreshCwIcon
|
|
329
|
-
size={16}
|
|
330
|
-
className={
|
|
331
|
-
rollKeyMutation.isPending
|
|
332
|
-
? "animate-spin"
|
|
333
|
-
: undefined
|
|
334
|
-
}
|
|
335
|
-
/>
|
|
336
|
-
<span className="hidden md:block">Roll key</span>
|
|
337
|
-
</Button>
|
|
338
|
-
</DialogTrigger>
|
|
339
|
-
<DialogContent>
|
|
340
|
-
<DialogHeader>
|
|
341
|
-
<DialogTitle>Roll API Key</DialogTitle>
|
|
342
|
-
<DialogDescription>
|
|
343
|
-
Are you sure you want to roll this API key?
|
|
344
|
-
</DialogDescription>
|
|
345
|
-
</DialogHeader>
|
|
346
|
-
<DialogFooter>
|
|
347
|
-
<DialogClose asChild>
|
|
348
|
-
<Button variant="outline">Cancel</Button>
|
|
349
|
-
</DialogClose>
|
|
350
|
-
<DialogClose asChild>
|
|
351
|
-
<Button
|
|
352
|
-
onClick={() => {
|
|
353
|
-
rollKeyMutation.mutate(consumers.id);
|
|
354
|
-
}}
|
|
355
|
-
>
|
|
356
|
-
Roll Key
|
|
357
|
-
</Button>
|
|
358
|
-
</DialogClose>
|
|
359
|
-
</DialogFooter>
|
|
360
|
-
</DialogContent>
|
|
361
|
-
</Dialog>
|
|
362
|
-
)}
|
|
363
|
-
</div>
|
|
364
|
-
</CardHeader>
|
|
365
|
-
<div className="col-span-full grid-cols-subgrid grid">
|
|
366
|
-
<AnimatePresence>
|
|
367
|
-
{consumers.apiKeys.map((apiKey) => (
|
|
368
|
-
<React.Fragment key={apiKey.id}>
|
|
369
|
-
<RevealApiKey
|
|
370
|
-
apiKey={apiKey}
|
|
371
|
-
onDeleteKey={() => {
|
|
372
|
-
deleteKeyMutation.mutate({
|
|
373
|
-
consumerId: consumers.id,
|
|
374
|
-
keyId: apiKey.id,
|
|
375
|
-
});
|
|
376
|
-
}}
|
|
377
|
-
className={
|
|
378
|
-
deleteKeyMutation.variables?.keyId === apiKey.id &&
|
|
379
|
-
(deleteKeyMutation.isPending || isFetching)
|
|
380
|
-
? "opacity-10!"
|
|
381
|
-
: undefined
|
|
382
|
-
}
|
|
383
|
-
/>
|
|
384
|
-
<div className="col-span-full h-px bg-border"></div>
|
|
385
|
-
</React.Fragment>
|
|
386
|
-
))}
|
|
387
|
-
</AnimatePresence>
|
|
388
|
-
</div>
|
|
389
|
-
</Card>
|
|
390
|
-
))}
|
|
391
|
-
</ul>
|
|
392
|
-
)}
|
|
393
|
-
</div>
|
|
394
|
-
</div>
|
|
395
|
-
);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const getTimeAgo = (date: string) => {
|
|
399
|
-
const now = new Date();
|
|
400
|
-
const created = new Date(date);
|
|
401
|
-
const diffInSeconds = Math.floor((now.getTime() - created.getTime()) / 1000);
|
|
402
|
-
|
|
403
|
-
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
404
|
-
|
|
405
|
-
if (diffInSeconds < 60) return rtf.format(-diffInSeconds, "second");
|
|
406
|
-
if (diffInSeconds < 3600)
|
|
407
|
-
return rtf.format(-Math.floor(diffInSeconds / 60), "minute");
|
|
408
|
-
if (diffInSeconds < 86400)
|
|
409
|
-
return rtf.format(-Math.floor(diffInSeconds / 3600), "hour");
|
|
410
|
-
if (diffInSeconds < 2592000)
|
|
411
|
-
return rtf.format(-Math.floor(diffInSeconds / 86400), "day");
|
|
412
|
-
if (diffInSeconds < 31536000)
|
|
413
|
-
return rtf.format(-Math.floor(diffInSeconds / 2592000), "month");
|
|
414
|
-
return rtf.format(-Math.floor(diffInSeconds / 31536000), "year");
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
const RevealApiKey = ({
|
|
418
|
-
apiKey,
|
|
419
|
-
onDeleteKey,
|
|
420
|
-
className,
|
|
421
|
-
}: {
|
|
422
|
-
apiKey: ApiKey;
|
|
423
|
-
onDeleteKey: () => void;
|
|
424
|
-
className?: string;
|
|
425
|
-
}) => {
|
|
426
|
-
const [revealed, setRevealed] = useState(false);
|
|
427
|
-
|
|
428
|
-
const { key, createdOn, expiresOn } = apiKey;
|
|
429
|
-
const isExpired = expiresOn && new Date(expiresOn) < new Date();
|
|
430
|
-
const daysUntilExpiry = expiresOn
|
|
431
|
-
? Math.ceil(
|
|
432
|
-
(new Date(expiresOn).getTime() - Date.now()) / (1000 * 60 * 60 * 24),
|
|
433
|
-
)
|
|
434
|
-
: Infinity;
|
|
435
|
-
const expiresSoon = daysUntilExpiry <= 7 && !isExpired;
|
|
436
|
-
|
|
437
|
-
return (
|
|
438
|
-
<div className={cn("grid col-span-full grid-cols-subgrid p-6", className)}>
|
|
439
|
-
<div className="flex flex-col gap-1">
|
|
440
|
-
<Secret
|
|
441
|
-
className="max-w-fit w-full"
|
|
442
|
-
secret={key}
|
|
443
|
-
status={isExpired ? "expired" : expiresSoon ? "expiring" : "active"}
|
|
444
|
-
revealed={revealed}
|
|
445
|
-
onReveal={setRevealed}
|
|
446
|
-
/>
|
|
447
|
-
<div className="flex gap-1 mt-0.5 text-nowrap">
|
|
448
|
-
{createdOn && (
|
|
449
|
-
<span className="text-xs text-muted-foreground">
|
|
450
|
-
Created {getTimeAgo(createdOn)}.
|
|
451
|
-
</span>
|
|
452
|
-
)}{" "}
|
|
453
|
-
{expiresOn && expiresSoon && (
|
|
454
|
-
<span className="text-xs text-primary">
|
|
455
|
-
Expires in {daysUntilExpiry}{" "}
|
|
456
|
-
{daysUntilExpiry === 1 ? "day" : "days"}.
|
|
457
|
-
</span>
|
|
458
|
-
)}
|
|
459
|
-
{expiresOn && isExpired && (
|
|
460
|
-
<span className="text-xs text-primary">
|
|
461
|
-
Expired{" "}
|
|
462
|
-
{daysUntilExpiry === 0
|
|
463
|
-
? "today."
|
|
464
|
-
: `${daysUntilExpiry * -1} days ago.`}
|
|
465
|
-
</span>
|
|
466
|
-
)}
|
|
467
|
-
</div>
|
|
468
|
-
</div>
|
|
469
|
-
<div className="flex justify-end">
|
|
470
|
-
{expiresOn && onDeleteKey && (
|
|
471
|
-
<Dialog>
|
|
472
|
-
<DialogTrigger asChild>
|
|
473
|
-
<Button variant="ghost" size="icon">
|
|
474
|
-
<TrashIcon size={16} />
|
|
475
|
-
</Button>
|
|
476
|
-
</DialogTrigger>
|
|
477
|
-
<DialogContent>
|
|
478
|
-
<DialogHeader>
|
|
479
|
-
<DialogTitle>Delete API Key</DialogTitle>
|
|
480
|
-
<DialogDescription>
|
|
481
|
-
Are you sure you want to delete this API key?
|
|
482
|
-
</DialogDescription>
|
|
483
|
-
</DialogHeader>
|
|
484
|
-
<DialogFooter>
|
|
485
|
-
<DialogClose asChild>
|
|
486
|
-
<Button variant="outline">Cancel</Button>
|
|
487
|
-
</DialogClose>
|
|
488
|
-
<DialogClose asChild>
|
|
489
|
-
<Button
|
|
490
|
-
onClick={() => {
|
|
491
|
-
onDeleteKey();
|
|
492
|
-
}}
|
|
493
|
-
>
|
|
494
|
-
Delete
|
|
495
|
-
</Button>
|
|
496
|
-
</DialogClose>
|
|
497
|
-
</DialogFooter>
|
|
498
|
-
</DialogContent>
|
|
499
|
-
</Dialog>
|
|
500
|
-
)}
|
|
501
|
-
</div>
|
|
502
62
|
</div>
|
|
503
63
|
);
|
|
504
64
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { KeyRoundIcon } from "lucide-react";
|
|
2
2
|
import type { RouteObject } from "react-router";
|
|
3
|
+
import type { ApiKeysOptions } from "../../../config/validators/validate.js";
|
|
3
4
|
import type { UseAuthReturn } from "../../authentication/hook.js";
|
|
4
5
|
import type {
|
|
5
6
|
ApiIdentityPlugin,
|
|
@@ -7,9 +8,7 @@ import type {
|
|
|
7
8
|
ZudokuPlugin,
|
|
8
9
|
} from "../../core/plugins.js";
|
|
9
10
|
import type { ZudokuContext } from "../../core/ZudokuContext.js";
|
|
10
|
-
import { RouterError } from "../../errors/RouterError.js";
|
|
11
11
|
import invariant from "../../util/invariant.js";
|
|
12
|
-
import { ProtectedRoute } from "./ProtectedRoute.js";
|
|
13
12
|
import { SettingsApiKeys } from "./SettingsApiKeys.js";
|
|
14
13
|
|
|
15
14
|
const DEFAULT_API_KEY_ENDPOINT = "https://api.zuploedge.com/v2/client";
|
|
@@ -93,8 +92,7 @@ const createDefaultHandler = (
|
|
|
93
92
|
method: "DELETE",
|
|
94
93
|
},
|
|
95
94
|
);
|
|
96
|
-
await context.signRequest(request);
|
|
97
|
-
const response = await fetch(request);
|
|
95
|
+
const response = await fetch(await context.signRequest(request));
|
|
98
96
|
await throwIfProblemJson(response);
|
|
99
97
|
invariant(response.ok, "Failed to delete API key");
|
|
100
98
|
},
|
|
@@ -136,7 +134,7 @@ const createDefaultHandler = (
|
|
|
136
134
|
),
|
|
137
135
|
);
|
|
138
136
|
await throwIfProblemJson(response);
|
|
139
|
-
invariant(response.ok, "Failed to
|
|
137
|
+
invariant(response.ok, "Failed to roll API key");
|
|
140
138
|
},
|
|
141
139
|
getConsumers: async (context) => {
|
|
142
140
|
const request = new Request(
|
|
@@ -154,6 +152,9 @@ const createDefaultHandler = (
|
|
|
154
152
|
id: string;
|
|
155
153
|
label?: string;
|
|
156
154
|
subject?: string;
|
|
155
|
+
createdOn?: string;
|
|
156
|
+
updatedOn?: string;
|
|
157
|
+
expiresOn?: string;
|
|
157
158
|
apiKeys: {
|
|
158
159
|
data: ApiKey[];
|
|
159
160
|
};
|
|
@@ -163,6 +164,9 @@ const createDefaultHandler = (
|
|
|
163
164
|
|
|
164
165
|
return data.data.map((consumer) => ({
|
|
165
166
|
id: consumer.id,
|
|
167
|
+
createdOn: consumer.createdOn,
|
|
168
|
+
updatedOn: consumer.updatedOn,
|
|
169
|
+
expiresOn: consumer.expiresOn,
|
|
166
170
|
label: consumer.label || consumer.subject || "API Key",
|
|
167
171
|
apiKeys: consumer.apiKeys.data,
|
|
168
172
|
key: consumer.apiKeys.data.at(0),
|
|
@@ -175,13 +179,24 @@ const createDefaultHandler = (
|
|
|
175
179
|
export const createApiKeyService = <T extends ApiKeyService>(service: T): T =>
|
|
176
180
|
service;
|
|
177
181
|
|
|
178
|
-
export const apiKeyPlugin = (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
export const apiKeyPlugin = ({
|
|
183
|
+
deploymentName,
|
|
184
|
+
...options
|
|
185
|
+
}: Omit<ApiKeysOptions, "enabled"> & {
|
|
186
|
+
deploymentName?: string;
|
|
187
|
+
}): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
|
|
188
|
+
const service = deploymentName
|
|
189
|
+
? createDefaultHandler(deploymentName, { deploymentName, ...options })
|
|
190
|
+
: options;
|
|
191
|
+
|
|
192
|
+
if (!service.getConsumers) {
|
|
193
|
+
throw new Error("getConsumers is required when using the apiKeyPlugin");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const verifiedService: ApiKeyService = {
|
|
197
|
+
...service,
|
|
198
|
+
getConsumers: service.getConsumers,
|
|
199
|
+
};
|
|
185
200
|
|
|
186
201
|
return {
|
|
187
202
|
getProfileMenuItems: () => [
|
|
@@ -195,7 +210,7 @@ export const apiKeyPlugin = (
|
|
|
195
210
|
|
|
196
211
|
getIdentities: async (context) => {
|
|
197
212
|
try {
|
|
198
|
-
const consumers = await
|
|
213
|
+
const consumers = await verifiedService.getConsumers(context);
|
|
199
214
|
|
|
200
215
|
return consumers.map((consumer) => ({
|
|
201
216
|
authorizeRequest: (request) => {
|
|
@@ -212,19 +227,18 @@ export const apiKeyPlugin = (
|
|
|
212
227
|
return [];
|
|
213
228
|
}
|
|
214
229
|
},
|
|
230
|
+
|
|
215
231
|
getRoutes: (): RouteObject[] => {
|
|
216
232
|
return [
|
|
217
233
|
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
children: [
|
|
221
|
-
{
|
|
222
|
-
path: "/settings/api-keys",
|
|
223
|
-
element: <SettingsApiKeys service={service} />,
|
|
224
|
-
},
|
|
225
|
-
],
|
|
234
|
+
path: "/settings/api-keys",
|
|
235
|
+
element: <SettingsApiKeys service={verifiedService} />,
|
|
226
236
|
},
|
|
227
237
|
];
|
|
228
238
|
},
|
|
239
|
+
|
|
240
|
+
getProtectedRoutes: () => {
|
|
241
|
+
return ["/settings/api-keys"];
|
|
242
|
+
},
|
|
229
243
|
};
|
|
230
244
|
};
|