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:
Deon Sanchez 2025-01-08 17:02:07 -07:00 committed by GitHub
commit 5280f396ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 201 additions and 28 deletions

View file

@ -629,7 +629,7 @@ export const TIMEOUT_ERROR_DESCRIPION = "Server is busy.";
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
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 =
"This user does not have any keys assigned at the moment.";

View file

@ -1,6 +1,9 @@
import SideBarButtonsComponent from "@/components/core/sidebarComponent";
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 { useStoreStore } from "@/stores/storeStore";
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",
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 (
<PageLayout
backTo={"/"}

View file

@ -18,7 +18,7 @@ const ApiKeyHeaderComponent = ({
<div className="flex w-full items-start justify-between gap-6">
<div className="flex w-full flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight">
Langflow API
Langflow API Keys
<ForwardedIconComponent
name="Key"
className="ml-2 h-5 w-5 text-primary"

View file

@ -24,10 +24,10 @@ import {
patchUserInputStateType,
} from "../../../../types/components";
import useScrollToElement from "../hooks/use-scroll-to-element";
import StoreApiKeyFormComponent from "../StoreApiKeyPage/components/StoreApiKeyForm";
import GeneralPageHeaderComponent from "./components/GeneralPageHeader";
import PasswordFormComponent from "./components/PasswordForm";
import ProfilePictureFormComponent from "./components/ProfilePictureForm";
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
export const GeneralPage = () => {
const { scrollId } = useParams();
@ -164,16 +164,6 @@ export const GeneralPage = () => {
handlePatchPassword={handlePatchPassword}
/>
)}
{hasStore && (
<StoreApiKeyFormComponent
apikey={apikey}
handleInput={handleInput}
handleSaveKey={handleSaveKey}
loadingApiKey={loadingApiKey}
validApiKey={validApiKey}
hasApiKey={hasApiKey}
/>
)}
</div>
</div>
);

View file

@ -1,6 +1,6 @@
import * as Form from "@radix-ui/react-form";
import InputComponent from "../../../../../../components/core/parameterRenderComponent/components/inputComponent";
import { Button } from "../../../../../../components/ui/button";
import InputComponent from "../../../../../components/core/parameterRenderComponent/components/inputComponent";
import { Button } from "../../../../../components/ui/button";
import {
Card,
CardContent,
@ -8,13 +8,13 @@ import {
CardFooter,
CardHeader,
CardTitle,
} from "../../../../../../components/ui/card";
} from "../../../../../components/ui/card";
import {
CREATE_API_KEY,
INSERT_API_KEY,
INVALID_API_KEY,
NO_API_KEY,
} from "../../../../../../constants/constants";
} from "../../../../../constants/constants";
type StoreApiKeyFormComponentProps = {
apikey: string;

View file

@ -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;

View file

@ -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;

View file

@ -33,6 +33,7 @@ import GeneralPage from "./pages/SettingsPage/pages/GeneralPage";
import GlobalVariablesPage from "./pages/SettingsPage/pages/GlobalVariablesPage";
import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
import ShortcutsPage from "./pages/SettingsPage/pages/ShortcutsPage";
import StoreApiKeyPage from "./pages/SettingsPage/pages/StoreApiKeyPage";
import StorePage from "./pages/StorePage";
import ViewPage from "./pages/ViewPage";
@ -165,6 +166,7 @@ const router = createBrowserRouter(
/>
<Route path="shortcuts" element={<ShortcutsPage />} />
<Route path="messages" element={<MessagesPage />} />
<Route path="store" element={<StoreApiKeyPage />} />
</Route>
<Route
path="store"