feat: add Store API Key management functionality (#5596)
* feat: add Store API Key management functionality - Introduced a new Store API Key page for managing API keys related to the Langflow Store. - Updated routes to include the new StoreApiKeyPage and added a corresponding link in the SettingsPage. - Enhanced the API key management interface with a dedicated form for inputting and saving API keys. - Updated existing constants and titles for clarity, ensuring consistent terminology across the application. This addition improves user experience by providing a centralized location for managing store-related API keys. * feat: conditionally add Langflow API Keys section in SettingsPage - Imported ENABLE_DATASTAX_LANGFLOW feature flag to control the visibility of the Langflow API Keys section in the sidebar. - The API Keys section is now conditionally rendered based on the feature flag, enhancing the flexibility of the SettingsPage. - This change improves user experience by ensuring that only relevant options are displayed based on the current feature configuration. * feat: refactor SettingsPage to improve sidebar navigation - Consolidated the addition of Langflow API Keys and Store sections into a single array for better organization and readability. - Enhanced the conditional rendering logic for sidebar navigation items based on the ENABLE_DATASTAX_LANGFLOW feature flag. - This refactor improves maintainability and clarity of the SettingsPage component. --------- Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
This commit is contained in:
parent
75c3c1ce97
commit
5280f396ef
8 changed files with 201 additions and 28 deletions
|
|
@ -629,7 +629,7 @@ export const TIMEOUT_ERROR_DESCRIPION = "Server is busy.";
|
||||||
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
||||||
|
|
||||||
export const API_PAGE_PARAGRAPH =
|
export const API_PAGE_PARAGRAPH =
|
||||||
"Your secret API keys are listed below. Do not share your API key with others, or expose it in the browser or other client-side code.";
|
"Your secret Langflow API keys are listed below. Do not share your API key with others, or expose it in the browser or other client-side code.";
|
||||||
|
|
||||||
export const API_PAGE_USER_KEYS =
|
export const API_PAGE_USER_KEYS =
|
||||||
"This user does not have any keys assigned at the moment.";
|
"This user does not have any keys assigned at the moment.";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import SideBarButtonsComponent from "@/components/core/sidebarComponent";
|
import SideBarButtonsComponent from "@/components/core/sidebarComponent";
|
||||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||||
import { ENABLE_PROFILE_ICONS } from "@/customization/feature-flags";
|
import {
|
||||||
|
ENABLE_DATASTAX_LANGFLOW,
|
||||||
|
ENABLE_PROFILE_ICONS,
|
||||||
|
} from "@/customization/feature-flags";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
import { useStoreStore } from "@/stores/storeStore";
|
import { useStoreStore } from "@/stores/storeStore";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
|
|
@ -44,16 +47,7 @@ export default function SettingsPage(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Langflow API",
|
|
||||||
href: "/settings/api-keys",
|
|
||||||
icon: (
|
|
||||||
<ForwardedIconComponent
|
|
||||||
name="Key"
|
|
||||||
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Shortcuts",
|
title: "Shortcuts",
|
||||||
href: "/settings/shortcuts",
|
href: "/settings/shortcuts",
|
||||||
|
|
@ -75,6 +69,34 @@ export default function SettingsPage(): JSX.Element {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!ENABLE_DATASTAX_LANGFLOW) {
|
||||||
|
const langflowItems = [
|
||||||
|
{
|
||||||
|
title: "Langflow API Keys",
|
||||||
|
href: "/settings/api-keys",
|
||||||
|
icon: (
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Key"
|
||||||
|
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Langflow Store",
|
||||||
|
href: "/settings/store",
|
||||||
|
icon: (
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Store"
|
||||||
|
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
sidebarNavItems.splice(2, 0, ...langflowItems);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
backTo={"/"}
|
backTo={"/"}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const ApiKeyHeaderComponent = ({
|
||||||
<div className="flex w-full items-start justify-between gap-6">
|
<div className="flex w-full items-start justify-between gap-6">
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||||
Langflow API
|
Langflow API Keys
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="Key"
|
name="Key"
|
||||||
className="ml-2 h-5 w-5 text-primary"
|
className="ml-2 h-5 w-5 text-primary"
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ import {
|
||||||
patchUserInputStateType,
|
patchUserInputStateType,
|
||||||
} from "../../../../types/components";
|
} from "../../../../types/components";
|
||||||
import useScrollToElement from "../hooks/use-scroll-to-element";
|
import useScrollToElement from "../hooks/use-scroll-to-element";
|
||||||
|
import StoreApiKeyFormComponent from "../StoreApiKeyPage/components/StoreApiKeyForm";
|
||||||
import GeneralPageHeaderComponent from "./components/GeneralPageHeader";
|
import GeneralPageHeaderComponent from "./components/GeneralPageHeader";
|
||||||
import PasswordFormComponent from "./components/PasswordForm";
|
import PasswordFormComponent from "./components/PasswordForm";
|
||||||
import ProfilePictureFormComponent from "./components/ProfilePictureForm";
|
import ProfilePictureFormComponent from "./components/ProfilePictureForm";
|
||||||
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
|
|
||||||
|
|
||||||
export const GeneralPage = () => {
|
export const GeneralPage = () => {
|
||||||
const { scrollId } = useParams();
|
const { scrollId } = useParams();
|
||||||
|
|
@ -164,16 +164,6 @@ export const GeneralPage = () => {
|
||||||
handlePatchPassword={handlePatchPassword}
|
handlePatchPassword={handlePatchPassword}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasStore && (
|
|
||||||
<StoreApiKeyFormComponent
|
|
||||||
apikey={apikey}
|
|
||||||
handleInput={handleInput}
|
|
||||||
handleSaveKey={handleSaveKey}
|
|
||||||
loadingApiKey={loadingApiKey}
|
|
||||||
validApiKey={validApiKey}
|
|
||||||
hasApiKey={hasApiKey}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import * as Form from "@radix-ui/react-form";
|
import * as Form from "@radix-ui/react-form";
|
||||||
import InputComponent from "../../../../../../components/core/parameterRenderComponent/components/inputComponent";
|
import InputComponent from "../../../../../components/core/parameterRenderComponent/components/inputComponent";
|
||||||
import { Button } from "../../../../../../components/ui/button";
|
import { Button } from "../../../../../components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
@ -8,13 +8,13 @@ import {
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../../../../../components/ui/card";
|
} from "../../../../../components/ui/card";
|
||||||
import {
|
import {
|
||||||
CREATE_API_KEY,
|
CREATE_API_KEY,
|
||||||
INSERT_API_KEY,
|
INSERT_API_KEY,
|
||||||
INVALID_API_KEY,
|
INVALID_API_KEY,
|
||||||
NO_API_KEY,
|
NO_API_KEY,
|
||||||
} from "../../../../../../constants/constants";
|
} from "../../../../../constants/constants";
|
||||||
|
|
||||||
type StoreApiKeyFormComponentProps = {
|
type StoreApiKeyFormComponentProps = {
|
||||||
apikey: string;
|
apikey: string;
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||||
|
import { CONTROL_PATCH_USER_STATE } from "@/constants/constants";
|
||||||
|
import { AuthContext } from "@/contexts/authContext";
|
||||||
|
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
|
||||||
|
import useAlertStore from "@/stores/alertStore";
|
||||||
|
import { useStoreStore } from "@/stores/storeStore";
|
||||||
|
import { inputHandlerEventType } from "@/types/components";
|
||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import useScrollToElement from "../hooks/use-scroll-to-element";
|
||||||
|
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
|
||||||
|
|
||||||
|
const StoreApiKeyPage = () => {
|
||||||
|
const { scrollId } = useParams();
|
||||||
|
const [inputState, setInputState] = useState(CONTROL_PATCH_USER_STATE);
|
||||||
|
const { storeApiKey } = useContext(AuthContext);
|
||||||
|
useScrollToElement(scrollId);
|
||||||
|
|
||||||
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const {
|
||||||
|
validApiKey,
|
||||||
|
hasApiKey,
|
||||||
|
loadingApiKey,
|
||||||
|
updateHasApiKey: setHasApiKey,
|
||||||
|
updateValidApiKey: setValidApiKey,
|
||||||
|
updateLoadingApiKey: setLoadingApiKey,
|
||||||
|
} = useStoreStore();
|
||||||
|
|
||||||
|
const { mutate: addApiKey } = usePostAddApiKey({
|
||||||
|
onSuccess: () => {
|
||||||
|
setSuccessData({ title: "API key saved successfully" });
|
||||||
|
setHasApiKey(true);
|
||||||
|
setValidApiKey(true);
|
||||||
|
setLoadingApiKey(false);
|
||||||
|
handleInput({ target: { name: "apikey", value: "" } });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setErrorData({
|
||||||
|
title: "API key save error",
|
||||||
|
list: [(error as any)?.response?.data?.detail],
|
||||||
|
});
|
||||||
|
setHasApiKey(false);
|
||||||
|
setValidApiKey(false);
|
||||||
|
setLoadingApiKey(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSaveKey = (apikey: string) => {
|
||||||
|
if (apikey) {
|
||||||
|
addApiKey({ key: apikey });
|
||||||
|
storeApiKey(apikey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInput = ({ target: { name, value } }: inputHandlerEventType) => {
|
||||||
|
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col gap-6">
|
||||||
|
<div className="flex w-full items-start gap-6">
|
||||||
|
<div className="flex w-full flex-col">
|
||||||
|
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||||
|
Langflow Store
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Store"
|
||||||
|
className="ml-2 h-5 w-5 text-primary"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Manage access to the Langflow Store.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<StoreApiKeyFormComponent
|
||||||
|
apikey={inputState.apikey}
|
||||||
|
handleInput={handleInput}
|
||||||
|
handleSaveKey={handleSaveKey}
|
||||||
|
loadingApiKey={loadingApiKey}
|
||||||
|
validApiKey={validApiKey}
|
||||||
|
hasApiKey={hasApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StoreApiKeyPage;
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { CONTROL_PATCH_USER_STATE } from "@/constants/constants";
|
||||||
|
import { AuthContext } from "@/contexts/authContext";
|
||||||
|
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
|
||||||
|
import useAlertStore from "@/stores/alertStore";
|
||||||
|
import { useStoreStore } from "@/stores/storeStore";
|
||||||
|
import { inputHandlerEventType } from "@/types/components";
|
||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import StoreApiKeyFormComponent from "../StoreApiKeyPage/components/StoreApiKeyForm";
|
||||||
|
import useScrollToElement from "../hooks/use-scroll-to-element";
|
||||||
|
|
||||||
|
const StoreApiKeyPage = () => {
|
||||||
|
const { scrollId } = useParams();
|
||||||
|
const [inputState, setInputState] = useState(CONTROL_PATCH_USER_STATE);
|
||||||
|
const { storeApiKey } = useContext(AuthContext);
|
||||||
|
useScrollToElement(scrollId);
|
||||||
|
|
||||||
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const {
|
||||||
|
validApiKey,
|
||||||
|
hasApiKey,
|
||||||
|
loadingApiKey,
|
||||||
|
updateHasApiKey: setHasApiKey,
|
||||||
|
updateValidApiKey: setValidApiKey,
|
||||||
|
updateLoadingApiKey: setLoadingApiKey,
|
||||||
|
} = useStoreStore();
|
||||||
|
|
||||||
|
const { mutate: addApiKey } = usePostAddApiKey({
|
||||||
|
onSuccess: () => {
|
||||||
|
setSuccessData({ title: "API key saved successfully" });
|
||||||
|
setHasApiKey(true);
|
||||||
|
setValidApiKey(true);
|
||||||
|
setLoadingApiKey(false);
|
||||||
|
handleInput({ target: { name: "apikey", value: "" } });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setErrorData({
|
||||||
|
title: "API key save error",
|
||||||
|
list: [(error as any)?.response?.data?.detail],
|
||||||
|
});
|
||||||
|
setHasApiKey(false);
|
||||||
|
setValidApiKey(false);
|
||||||
|
setLoadingApiKey(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSaveKey = (apikey: string) => {
|
||||||
|
if (apikey) {
|
||||||
|
addApiKey({ key: apikey });
|
||||||
|
storeApiKey(apikey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInput = ({ target: { name, value } }: inputHandlerEventType) => {
|
||||||
|
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StoreApiKeyFormComponent
|
||||||
|
apikey={inputState.apikey}
|
||||||
|
handleInput={handleInput}
|
||||||
|
handleSaveKey={handleSaveKey}
|
||||||
|
loadingApiKey={loadingApiKey}
|
||||||
|
validApiKey={validApiKey}
|
||||||
|
hasApiKey={hasApiKey}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StoreApiKeyPage;
|
||||||
|
|
@ -33,6 +33,7 @@ import GeneralPage from "./pages/SettingsPage/pages/GeneralPage";
|
||||||
import GlobalVariablesPage from "./pages/SettingsPage/pages/GlobalVariablesPage";
|
import GlobalVariablesPage from "./pages/SettingsPage/pages/GlobalVariablesPage";
|
||||||
import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
|
import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
|
||||||
import ShortcutsPage from "./pages/SettingsPage/pages/ShortcutsPage";
|
import ShortcutsPage from "./pages/SettingsPage/pages/ShortcutsPage";
|
||||||
|
import StoreApiKeyPage from "./pages/SettingsPage/pages/StoreApiKeyPage";
|
||||||
import StorePage from "./pages/StorePage";
|
import StorePage from "./pages/StorePage";
|
||||||
import ViewPage from "./pages/ViewPage";
|
import ViewPage from "./pages/ViewPage";
|
||||||
|
|
||||||
|
|
@ -165,6 +166,7 @@ const router = createBrowserRouter(
|
||||||
/>
|
/>
|
||||||
<Route path="shortcuts" element={<ShortcutsPage />} />
|
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||||
<Route path="messages" element={<MessagesPage />} />
|
<Route path="messages" element={<MessagesPage />} />
|
||||||
|
<Route path="store" element={<StoreApiKeyPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path="store"
|
path="store"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue