mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-19 20:24:32 +02:00
Curator polish (#2229)
* add new user provider hook * account for additional logic * add users * remove is loading * Curator polish * useeffect -> provider + effect * squash * use use user for user default models * squash * Added ability to add users to groups among other things * final polish * added connection button to groups * mypy fix * Improved document set clarity * string fixes --------- Co-authored-by: pablodanswer <pablo@danswer.ai>
This commit is contained in:
@@ -13,6 +13,7 @@ import { FiEdit2 } from "react-icons/fi";
|
||||
import { TrashIcon } from "@/components/icons/icons";
|
||||
import { getCurrentUser } from "@/lib/user";
|
||||
import { UserRole, User } from "@/lib/types";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
||||
function PersonaTypeDisplay({ persona }: { persona: Persona }) {
|
||||
if (persona.default_persona) {
|
||||
@@ -56,23 +57,7 @@ export function PersonasTable({
|
||||
const router = useRouter();
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, []);
|
||||
const { isLoadingUser, isAdmin } = useUser();
|
||||
|
||||
const editablePersonaIds = new Set(
|
||||
editablePersonas.map((p) => p.id.toString())
|
||||
@@ -123,6 +108,10 @@ export function PersonasTable({
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{popup}
|
||||
|
@@ -38,6 +38,7 @@ import {
|
||||
useGoogleDriveCredentials,
|
||||
} from "./pages/utils/hooks";
|
||||
import { FormikProps } from "formik";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
||||
export type AdvancedConfig = {
|
||||
pruneFreq: number | null;
|
||||
@@ -99,7 +100,15 @@ export default function AddConnector({
|
||||
const [refreshFreq, setRefreshFreq] = useState<number>(defaultRefresh || 0);
|
||||
const [pruneFreq, setPruneFreq] = useState<number>(defaultPrune);
|
||||
const [indexingStart, setIndexingStart] = useState<Date | null>(null);
|
||||
const [isPublic, setIsPublic] = useState(true);
|
||||
const { isAdmin, isLoadingUser } = useUser();
|
||||
|
||||
const [isPublic, setIsPublic] = useState(isAdmin);
|
||||
useEffect(() => {
|
||||
if (!isLoadingUser) {
|
||||
setIsPublic(isAdmin);
|
||||
}
|
||||
}, [isLoadingUser, isAdmin]);
|
||||
|
||||
const [groups, setGroups] = useState<number[]>([]);
|
||||
const [createConnectorToggle, setCreateConnectorToggle] = useState(false);
|
||||
const formRef = useRef<FormikProps<any>>(null);
|
||||
@@ -129,6 +138,10 @@ export default function AddConnector({
|
||||
setFormStep(Math.min(formStep, 0));
|
||||
}
|
||||
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const resetAdvancedConfigs = () => {
|
||||
const resetRefreshFreq = defaultRefresh || 0;
|
||||
const resetPruneFreq = defaultPrune;
|
||||
|
@@ -13,8 +13,9 @@ import { ConnectionConfiguration } from "@/lib/connectors/connectors";
|
||||
import { useFormContext } from "@/components/context/FormContext";
|
||||
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||
import { Text } from "@tremor/react";
|
||||
import { getCurrentUser } from "@/lib/user";
|
||||
|
||||
import { FiUsers } from "react-icons/fi";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
||||
export interface DynamicConnectionFormProps {
|
||||
config: ConnectionConfiguration;
|
||||
@@ -48,29 +49,12 @@ const DynamicConnectionForm: React.FC<DynamicConnectionFormProps> = ({
|
||||
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
|
||||
const { setAllowAdvanced } = useFormContext();
|
||||
const { data: userGroups, isLoading: userGroupsIsLoading } = useUserGroups();
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
const userIsAdmin = user.role === UserRole.ADMIN;
|
||||
setIsAdmin(userIsAdmin);
|
||||
if (!userIsAdmin) {
|
||||
setIsPublic(false);
|
||||
}
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, [setIsPublic]);
|
||||
const { isLoadingUser, isAdmin, user } = useUser();
|
||||
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
name: initialName || "",
|
||||
@@ -342,92 +326,107 @@ const DynamicConnectionForm: React.FC<DynamicConnectionFormProps> = ({
|
||||
currentValue={isPublic}
|
||||
/>
|
||||
)}
|
||||
{userGroups &&
|
||||
(!isAdmin || (!isPublic && userGroups.length > 0)) && (
|
||||
<div>
|
||||
<div className="flex gap-x-2 items-center">
|
||||
<div className="block font-medium text-base">
|
||||
Assign group access for this Connector
|
||||
</div>
|
||||
</div>
|
||||
<Text className="mb-3">
|
||||
{isAdmin ? (
|
||||
<>
|
||||
This Connector will be visible/accessible by the
|
||||
groups selected below
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Curators must select one or more groups to give
|
||||
access to this Connector
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<FieldArray
|
||||
name="groups"
|
||||
render={() => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{!userGroupsIsLoading &&
|
||||
userGroups.map((userGroup: UserGroup) => {
|
||||
const isSelected =
|
||||
groups?.includes(userGroup.id) ||
|
||||
(!isAdmin && userGroups.length === 1);
|
||||
|
||||
// Auto-select the only group for non-admin users
|
||||
if (
|
||||
!isAdmin &&
|
||||
userGroups.length === 1 &&
|
||||
groups.length === 0
|
||||
) {
|
||||
setGroups([userGroup.id]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={userGroup.id}
|
||||
className={`
|
||||
px-3
|
||||
py-1
|
||||
rounded-lg
|
||||
border
|
||||
border-border
|
||||
w-fit
|
||||
flex
|
||||
cursor-pointer
|
||||
${isSelected ? "bg-background-strong" : "hover:bg-hover"}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (setGroups) {
|
||||
if (
|
||||
isSelected &&
|
||||
(isAdmin || userGroups.length > 1)
|
||||
) {
|
||||
setGroups(
|
||||
groups?.filter(
|
||||
(id) => id !== userGroup.id
|
||||
) || []
|
||||
);
|
||||
} else if (!isSelected) {
|
||||
setGroups([
|
||||
...(groups || []),
|
||||
userGroup.id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="my-auto flex">
|
||||
<FiUsers className="my-auto mr-2" />{" "}
|
||||
{userGroup.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{userGroups ? (
|
||||
<>
|
||||
{!isPublic &&
|
||||
((isAdmin && userGroups.length > 0) ||
|
||||
(!isAdmin && userGroups.length > 1)) ? (
|
||||
<div>
|
||||
<div className="flex gap-x-2 items-center">
|
||||
<div className="block font-medium text-base">
|
||||
Assign group access for this Connector
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Text className="mb-3">
|
||||
{isAdmin ? (
|
||||
<>
|
||||
This Connector will be visible/accessible by the
|
||||
groups selected below
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Curators must select one or more groups to give
|
||||
access to this Connector
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<FieldArray
|
||||
name="groups"
|
||||
render={() => (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{!userGroupsIsLoading &&
|
||||
userGroups.map((userGroup: UserGroup) => {
|
||||
const isSelected =
|
||||
groups?.includes(userGroup.id) ||
|
||||
(!isAdmin && userGroups.length === 1);
|
||||
|
||||
// Auto-select the only group for non-admin users
|
||||
if (
|
||||
!isAdmin &&
|
||||
userGroups.length === 1 &&
|
||||
groups.length === 0
|
||||
) {
|
||||
setGroups([userGroup.id]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={userGroup.id}
|
||||
className={`
|
||||
px-3
|
||||
py-1
|
||||
rounded-lg
|
||||
border
|
||||
border-border
|
||||
w-fit
|
||||
flex
|
||||
cursor-pointer
|
||||
${isSelected ? "bg-background-strong" : "hover:bg-hover"}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (setGroups) {
|
||||
if (
|
||||
isSelected &&
|
||||
(isAdmin || userGroups.length > 1)
|
||||
) {
|
||||
setGroups(
|
||||
groups?.filter(
|
||||
(id) => id !== userGroup.id
|
||||
) || []
|
||||
);
|
||||
} else if (!isSelected) {
|
||||
setGroups([
|
||||
...(groups || []),
|
||||
userGroup.id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="my-auto flex">
|
||||
<FiUsers className="my-auto mr-2" />{" "}
|
||||
{userGroup.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : userGroups && userGroups.length > 0 ? (
|
||||
<Text className="mb-3">
|
||||
These documents will be assigned to group:{" "}
|
||||
<b>{userGroups[0].name}</b>.
|
||||
</Text>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
|
@@ -19,26 +19,12 @@ import {
|
||||
GoogleDriveServiceAccountCredentialJson,
|
||||
} from "@/lib/connectors/credentials";
|
||||
import { GoogleDriveConfig } from "@/lib/connectors/connectors";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import { useConnectorCredentialIndexingStatus } from "@/lib/hooks";
|
||||
|
||||
const GDriveMain = ({}: {}) => {
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
const { isLoadingUser, isAdmin } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, []);
|
||||
const {
|
||||
data: appCredentialData,
|
||||
isLoading: isAppCredentialLoading,
|
||||
@@ -61,10 +47,7 @@ const GDriveMain = ({}: {}) => {
|
||||
data: connectorIndexingStatuses,
|
||||
isLoading: isConnectorIndexingStatusesLoading,
|
||||
error: connectorIndexingStatusesError,
|
||||
} = useSWR<ConnectorIndexingStatus<any, any>[], FetchError>(
|
||||
"/api/manage/admin/connector/indexing-status",
|
||||
errorHandlingFetcher
|
||||
);
|
||||
} = useConnectorCredentialIndexingStatus();
|
||||
const {
|
||||
data: credentialsData,
|
||||
isLoading: isCredentialsLoading,
|
||||
@@ -81,6 +64,10 @@ const GDriveMain = ({}: {}) => {
|
||||
serviceAccountKeyData ||
|
||||
(isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
|
||||
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (
|
||||
(!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
|
||||
(!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
|
||||
|
@@ -17,26 +17,12 @@ import { usePublicCredentials } from "@/lib/hooks";
|
||||
import { Title } from "@tremor/react";
|
||||
import { GmailConfig } from "@/lib/connectors/connectors";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import { useConnectorCredentialIndexingStatus } from "@/lib/hooks";
|
||||
|
||||
export const GmailMain = () => {
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
const { isLoadingUser, isAdmin } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, []);
|
||||
const {
|
||||
data: appCredentialData,
|
||||
isLoading: isAppCredentialLoading,
|
||||
@@ -57,10 +43,8 @@ export const GmailMain = () => {
|
||||
data: connectorIndexingStatuses,
|
||||
isLoading: isConnectorIndexingStatusesLoading,
|
||||
error: connectorIndexingStatusesError,
|
||||
} = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
||||
"/api/manage/admin/connector/indexing-status",
|
||||
errorHandlingFetcher
|
||||
);
|
||||
} = useConnectorCredentialIndexingStatus();
|
||||
|
||||
const {
|
||||
data: credentialsData,
|
||||
isLoading: isCredentialsLoading,
|
||||
@@ -77,6 +61,10 @@ export const GmailMain = () => {
|
||||
serviceAccountKeyData ||
|
||||
(isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
|
||||
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (
|
||||
(!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
|
||||
(!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
|
||||
|
@@ -11,9 +11,10 @@ import {
|
||||
import { ConnectorIndexingStatus, DocumentSet, UserGroup } from "@/lib/types";
|
||||
import { TextFormField } from "@/components/admin/connectors/Field";
|
||||
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
import { Button, Divider, Text } from "@tremor/react";
|
||||
import { Button, Divider } from "@tremor/react";
|
||||
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||
import { IsPublicGroupSelector } from "@/components/IsPublicGroupSelector";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
interface SetCreationPopupProps {
|
||||
ccPairs: ConnectorIndexingStatus<any, any>[];
|
||||
@@ -31,8 +32,14 @@ export const DocumentSetCreationForm = ({
|
||||
existingDocumentSet,
|
||||
}: SetCreationPopupProps) => {
|
||||
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
|
||||
|
||||
const isUpdate = existingDocumentSet !== undefined;
|
||||
const [localCcPairs, setLocalCcPairs] = useState(ccPairs);
|
||||
|
||||
useEffect(() => {
|
||||
if (existingDocumentSet?.is_public) {
|
||||
return;
|
||||
}
|
||||
}, [existingDocumentSet?.is_public]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -95,100 +102,177 @@ export const DocumentSetCreationForm = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(props) => (
|
||||
<Form>
|
||||
<TextFormField
|
||||
name="name"
|
||||
label="Name:"
|
||||
placeholder="A name for the document set"
|
||||
disabled={isUpdate}
|
||||
autoCompleteDisabled={true}
|
||||
/>
|
||||
<TextFormField
|
||||
name="description"
|
||||
label="Description:"
|
||||
placeholder="Describe what the document set represents"
|
||||
autoCompleteDisabled={true}
|
||||
/>
|
||||
{(props) => {
|
||||
return (
|
||||
<Form>
|
||||
<TextFormField
|
||||
name="name"
|
||||
label="Name:"
|
||||
placeholder="A name for the document set"
|
||||
disabled={isUpdate}
|
||||
autoCompleteDisabled={true}
|
||||
/>
|
||||
<TextFormField
|
||||
name="description"
|
||||
label="Description:"
|
||||
placeholder="Describe what the document set represents"
|
||||
autoCompleteDisabled={true}
|
||||
/>
|
||||
{isPaidEnterpriseFeaturesEnabled &&
|
||||
userGroups &&
|
||||
userGroups.length > 0 && (
|
||||
<IsPublicGroupSelector
|
||||
formikProps={props}
|
||||
objectName="document set"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
<Divider />
|
||||
|
||||
<h2 className="mb-1 font-medium text-base">
|
||||
Pick your connectors:
|
||||
</h2>
|
||||
<p className="mb-3 text-xs">
|
||||
All documents indexed by the selected connectors will be a part of
|
||||
this document set.
|
||||
</p>
|
||||
<FieldArray
|
||||
name="cc_pair_ids"
|
||||
render={(arrayHelpers: ArrayHelpers) => (
|
||||
<div className="mb-3 flex gap-2 flex-wrap">
|
||||
{ccPairs.map((ccPair) => {
|
||||
const ind = props.values.cc_pair_ids.indexOf(
|
||||
ccPair.cc_pair_id
|
||||
);
|
||||
let isSelected = ind !== -1;
|
||||
return (
|
||||
<div
|
||||
key={`${ccPair.connector.id}-${ccPair.credential.id}`}
|
||||
className={
|
||||
`
|
||||
px-3
|
||||
py-1
|
||||
rounded-lg
|
||||
border
|
||||
border-border
|
||||
w-fit
|
||||
flex
|
||||
cursor-pointer ` +
|
||||
(isSelected
|
||||
? " bg-background-strong"
|
||||
: " hover:bg-hover")
|
||||
}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
arrayHelpers.remove(ind);
|
||||
} else {
|
||||
arrayHelpers.push(ccPair.cc_pair_id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="my-auto">
|
||||
<ConnectorTitle
|
||||
connector={ccPair.connector}
|
||||
ccPairId={ccPair.cc_pair_id}
|
||||
ccPairName={ccPair.name}
|
||||
isLink={false}
|
||||
showMetadata={false}
|
||||
/>
|
||||
</div>
|
||||
<h2 className="mb-1 font-medium text-base">
|
||||
These are the connectors available to{" "}
|
||||
{userGroups && userGroups.length > 1
|
||||
? "the selected group"
|
||||
: "the group you curate"}
|
||||
:
|
||||
</h2>
|
||||
<p className="mb-3 text-sm">
|
||||
All documents indexed by these selected connectors will be a
|
||||
part of this document set.
|
||||
</p>
|
||||
<FieldArray
|
||||
name="cc_pair_ids"
|
||||
render={(arrayHelpers: ArrayHelpers) => {
|
||||
// Filter visible cc pairs
|
||||
const visibleCcPairs = localCcPairs.filter(
|
||||
(ccPair) =>
|
||||
ccPair.public_doc ||
|
||||
(ccPair.groups.length > 0 &&
|
||||
props.values.groups.every((group) =>
|
||||
ccPair.groups.includes(group)
|
||||
))
|
||||
);
|
||||
|
||||
// Deselect filtered out cc pairs
|
||||
const visibleCcPairIds = visibleCcPairs.map(
|
||||
(ccPair) => ccPair.cc_pair_id
|
||||
);
|
||||
props.values.cc_pair_ids = props.values.cc_pair_ids.filter(
|
||||
(id) => visibleCcPairIds.includes(id)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mb-3 flex gap-2 flex-wrap">
|
||||
{visibleCcPairs.map((ccPair) => {
|
||||
const ind = props.values.cc_pair_ids.indexOf(
|
||||
ccPair.cc_pair_id
|
||||
);
|
||||
let isSelected = ind !== -1;
|
||||
return (
|
||||
<div
|
||||
key={`${ccPair.connector.id}-${ccPair.credential.id}`}
|
||||
className={
|
||||
`
|
||||
px-3
|
||||
py-1
|
||||
rounded-lg
|
||||
border
|
||||
border-border
|
||||
w-fit
|
||||
flex
|
||||
cursor-pointer ` +
|
||||
(isSelected
|
||||
? " bg-background-strong"
|
||||
: " hover:bg-hover")
|
||||
}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
arrayHelpers.remove(ind);
|
||||
} else {
|
||||
arrayHelpers.push(ccPair.cc_pair_id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="my-auto">
|
||||
<ConnectorTitle
|
||||
connector={ccPair.connector}
|
||||
ccPairId={ccPair.cc_pair_id}
|
||||
ccPairName={ccPair.name}
|
||||
isLink={false}
|
||||
showMetadata={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<FieldArray
|
||||
name="cc_pair_ids"
|
||||
render={() => {
|
||||
// Filter non-visible cc pairs
|
||||
const nonVisibleCcPairs = localCcPairs.filter(
|
||||
(ccPair) =>
|
||||
!ccPair.public_doc &&
|
||||
(ccPair.groups.length === 0 ||
|
||||
!props.values.groups.every((group) =>
|
||||
ccPair.groups.includes(group)
|
||||
))
|
||||
);
|
||||
|
||||
return nonVisibleCcPairs.length > 0 ? (
|
||||
<>
|
||||
<Divider />
|
||||
<h2 className="mb-1 font-medium text-base">
|
||||
These connectors are not available to the{" "}
|
||||
{userGroups && userGroups.length > 1
|
||||
? `group${props.values.groups.length > 1 ? "s" : ""} you have selected`
|
||||
: "group you curate"}
|
||||
:
|
||||
</h2>
|
||||
<p className="mb-3 text-sm">
|
||||
Only connectors that are directly assigned to the group
|
||||
you are trying to add the document set to will be
|
||||
available.
|
||||
</p>
|
||||
<div className="mb-3 flex gap-2 flex-wrap">
|
||||
{nonVisibleCcPairs.map((ccPair) => (
|
||||
<div
|
||||
key={`${ccPair.connector.id}-${ccPair.credential.id}`}
|
||||
className="px-3 py-1 rounded-lg border border-non-selectable-border w-fit flex cursor-not-allowed"
|
||||
>
|
||||
<div className="my-auto">
|
||||
<ConnectorTitle
|
||||
connector={ccPair.connector}
|
||||
ccPairId={ccPair.cc_pair_id}
|
||||
ccPairName={ccPair.name}
|
||||
isLink={false}
|
||||
showMetadata={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
|
||||
{isPaidEnterpriseFeaturesEnabled &&
|
||||
userGroups &&
|
||||
userGroups.length > 0 && (
|
||||
<IsPublicGroupSelector
|
||||
formikProps={props}
|
||||
objectName="document set"
|
||||
/>
|
||||
)}
|
||||
<div className="flex mt-6">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={props.isSubmitting}
|
||||
className="w-64 mx-auto"
|
||||
>
|
||||
{isUpdate ? "Update!" : "Create!"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
<div className="flex mt-6">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={props.isSubmitting}
|
||||
className="w-64 mx-auto"
|
||||
>
|
||||
{isUpdate ? "Update!" : "Create!"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
|
@@ -111,23 +111,6 @@ const DocumentSetTable = ({
|
||||
setPopup,
|
||||
}: DocumentFeedbackTableProps) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, []);
|
||||
|
||||
// sort by name for consistent ordering
|
||||
documentSets.sort((a, b) => {
|
||||
|
@@ -443,6 +443,7 @@ export function CCPairIndexingStatusTable({
|
||||
error_msg: "",
|
||||
deletion_attempt: null,
|
||||
is_deletable: true,
|
||||
groups: [], // Add this line
|
||||
}}
|
||||
isEditable={false}
|
||||
/>
|
||||
|
@@ -10,26 +10,19 @@ import { CCPairIndexingStatusTable } from "./CCPairIndexingStatusTable";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
import Link from "next/link";
|
||||
import { Button, Text } from "@tremor/react";
|
||||
import { useConnectorCredentialIndexingStatus } from "@/lib/hooks";
|
||||
|
||||
function Main() {
|
||||
const {
|
||||
data: indexAttemptData,
|
||||
isLoading: indexAttemptIsLoading,
|
||||
error: indexAttemptError,
|
||||
} = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
||||
"/api/manage/admin/connector/indexing-status",
|
||||
errorHandlingFetcher,
|
||||
{ refreshInterval: 10000 } // 10 seconds
|
||||
);
|
||||
} = useConnectorCredentialIndexingStatus();
|
||||
const {
|
||||
data: editableIndexAttemptData,
|
||||
isLoading: editableIndexAttemptIsLoading,
|
||||
error: editableIndexAttemptError,
|
||||
} = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
||||
"/api/manage/admin/connector/indexing-status?get_editable=true",
|
||||
errorHandlingFetcher,
|
||||
{ refreshInterval: 10000 } // 10 seconds
|
||||
);
|
||||
} = useConnectorCredentialIndexingStatus(undefined, true);
|
||||
|
||||
if (indexAttemptIsLoading || editableIndexAttemptIsLoading) {
|
||||
return <LoadingAnimation text="" />;
|
||||
|
@@ -85,6 +85,7 @@ import { DeleteChatModal } from "./modal/DeleteChatModal";
|
||||
import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
|
||||
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
|
||||
import { SEARCH_TOOL_NAME } from "./tools/constants";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
||||
const TEMP_USER_MESSAGE_ID = -1;
|
||||
const TEMP_ASSISTANT_MESSAGE_ID = -2;
|
||||
@@ -105,7 +106,6 @@ export function ChatPage({
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
let {
|
||||
user,
|
||||
chatSessions,
|
||||
availableSources,
|
||||
availableDocumentSets,
|
||||
@@ -116,6 +116,8 @@ export function ChatPage({
|
||||
userInputPrompts,
|
||||
} = useChatContext();
|
||||
|
||||
const { user, refreshUser } = useUser();
|
||||
|
||||
// chat session
|
||||
const existingChatIdRaw = searchParams.get("chatId");
|
||||
const currentPersonaId = searchParams.get(SEARCH_PARAM_NAMES.PERSONA_ID);
|
||||
@@ -1418,6 +1420,7 @@ export function ChatPage({
|
||||
<SetDefaultModelModal
|
||||
setLlmOverride={llmOverrideManager.setGlobalDefault}
|
||||
defaultModel={user?.preferences.default_model!}
|
||||
refreshUser={refreshUser}
|
||||
llmProviders={llmProviders}
|
||||
onClose={() => setSettingsToggled(false)}
|
||||
/>
|
||||
|
@@ -14,11 +14,13 @@ export function SetDefaultModelModal({
|
||||
onClose,
|
||||
setLlmOverride,
|
||||
defaultModel,
|
||||
refreshUser,
|
||||
}: {
|
||||
llmProviders: LLMProviderDescriptor[];
|
||||
setLlmOverride: Dispatch<SetStateAction<LlmOverride>>;
|
||||
onClose: () => void;
|
||||
defaultModel: string | null;
|
||||
refreshUser: () => void;
|
||||
}) {
|
||||
const { popup, setPopup } = usePopup();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -103,6 +105,7 @@ export function SetDefaultModelModal({
|
||||
message: "Default model updated successfully",
|
||||
type: "success",
|
||||
});
|
||||
refreshUser();
|
||||
router.refresh();
|
||||
} else {
|
||||
throw new Error("Failed to update default model");
|
||||
|
@@ -48,7 +48,6 @@ export default async function Page({
|
||||
)}
|
||||
<ChatProvider
|
||||
value={{
|
||||
user,
|
||||
chatSessions,
|
||||
availableSources,
|
||||
availableDocumentSets: documentSets,
|
||||
|
@@ -31,7 +31,7 @@ import { Bubble } from "@/components/Bubble";
|
||||
import { BookmarkIcon, RobotIcon } from "@/components/icons/icons";
|
||||
import { AddTokenRateLimitForm } from "./AddTokenRateLimitForm";
|
||||
import { GenericTokenRateLimitTable } from "@/app/admin/token-rate-limits/TokenRateLimitTables";
|
||||
import { getCurrentUser } from "@/lib/user";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
||||
interface GroupDisplayProps {
|
||||
users: User[];
|
||||
@@ -106,7 +106,7 @@ const UserRoleDropdown = ({
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <div>{user.role}</div>;
|
||||
return <div>{localRole}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -120,23 +120,12 @@ export const GroupDisplay = ({
|
||||
const [addMemberFormVisible, setAddMemberFormVisible] = useState(false);
|
||||
const [addConnectorFormVisible, setAddConnectorFormVisible] = useState(false);
|
||||
const [addRateLimitFormVisible, setAddRateLimitFormVisible] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, []);
|
||||
|
||||
const { isLoadingUser, isAdmin } = useUser();
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const handlePopup = (message: string, type: "success" | "error") => {
|
||||
setPopup({ message, type });
|
||||
};
|
||||
@@ -175,23 +164,21 @@ export const GroupDisplay = ({
|
||||
<TableRow>
|
||||
<TableHeaderCell>Email</TableHeaderCell>
|
||||
<TableHeaderCell>Role</TableHeaderCell>
|
||||
{isAdmin && (
|
||||
<TableHeaderCell className="flex w-full">
|
||||
<div className="ml-auto">Remove User</div>
|
||||
</TableHeaderCell>
|
||||
)}
|
||||
<TableHeaderCell className="flex w-full">
|
||||
<div className="ml-auto">Remove User</div>
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{userGroup.users.map((user) => {
|
||||
{userGroup.users.map((groupMember) => {
|
||||
return (
|
||||
<TableRow key={user.id}>
|
||||
<TableRow key={groupMember.id}>
|
||||
<TableCell className="whitespace-normal break-all">
|
||||
{user.email}
|
||||
{groupMember.email}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<UserRoleDropdown
|
||||
user={user}
|
||||
user={groupMember}
|
||||
group={userGroup}
|
||||
onSuccess={onRoleChangeSuccess}
|
||||
onError={onRoleChangeError}
|
||||
@@ -201,7 +188,10 @@ export const GroupDisplay = ({
|
||||
<TableCell>
|
||||
<div className="flex w-full">
|
||||
<div className="ml-auto m-2">
|
||||
{isAdmin && (
|
||||
{(isAdmin ||
|
||||
!userGroup.curator_ids.includes(
|
||||
groupMember.id
|
||||
)) && (
|
||||
<DeleteButton
|
||||
onClick={async () => {
|
||||
const response = await updateUserGroup(
|
||||
@@ -210,7 +200,7 @@ export const GroupDisplay = ({
|
||||
user_ids: userGroup.users
|
||||
.filter(
|
||||
(userGroupUser) =>
|
||||
userGroupUser.id !== user.id
|
||||
userGroupUser.id !== groupMember.id
|
||||
)
|
||||
.map(
|
||||
(userGroupUser) => userGroupUser.id
|
||||
@@ -254,17 +244,15 @@ export const GroupDisplay = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isAdmin && (
|
||||
<Button
|
||||
className="mt-3"
|
||||
size="xs"
|
||||
color="green"
|
||||
onClick={() => setAddMemberFormVisible(true)}
|
||||
disabled={!userGroup.is_up_to_date}
|
||||
>
|
||||
Add Users
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="mt-3"
|
||||
size="xs"
|
||||
color="green"
|
||||
onClick={() => setAddMemberFormVisible(true)}
|
||||
disabled={!userGroup.is_up_to_date}
|
||||
>
|
||||
Add Users
|
||||
</Button>
|
||||
|
||||
{addMemberFormVisible && (
|
||||
<AddMemberForm
|
||||
@@ -355,17 +343,15 @@ export const GroupDisplay = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isAdmin && (
|
||||
<Button
|
||||
className="mt-3"
|
||||
onClick={() => setAddConnectorFormVisible(true)}
|
||||
size="xs"
|
||||
color="green"
|
||||
disabled={!userGroup.is_up_to_date}
|
||||
>
|
||||
Add Connectors
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="mt-3"
|
||||
onClick={() => setAddConnectorFormVisible(true)}
|
||||
size="xs"
|
||||
color="green"
|
||||
disabled={!userGroup.is_up_to_date}
|
||||
>
|
||||
Add Connectors
|
||||
</Button>
|
||||
|
||||
{addConnectorFormVisible && (
|
||||
<AddConnectorForm
|
||||
|
@@ -15,6 +15,7 @@ import {
|
||||
} from "@/lib/hooks";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
import { Button, Divider } from "@tremor/react";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
||||
const Main = () => {
|
||||
const { popup, setPopup } = usePopup();
|
||||
@@ -34,23 +35,10 @@ const Main = () => {
|
||||
error: usersError,
|
||||
} = useUsers();
|
||||
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, []);
|
||||
const { isLoadingUser, isAdmin } = useUser();
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (isLoading || isCCPairsLoading || userIsLoading) {
|
||||
return <ThreeDotsLoader />;
|
||||
|
@@ -20,6 +20,7 @@ import { Button, Card } from "@tremor/react";
|
||||
import LogoType from "@/components/header/LogoType";
|
||||
import { HeaderTitle } from "@/components/header/HeaderTitle";
|
||||
import { Logo } from "@/components/Logo";
|
||||
import { UserProvider } from "@/components/user/UserProvider";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
@@ -111,9 +112,11 @@ export default async function RootLayout({
|
||||
process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : ""
|
||||
}`}
|
||||
>
|
||||
<SettingsProvider settings={combinedSettings}>
|
||||
{children}
|
||||
</SettingsProvider>
|
||||
<UserProvider>
|
||||
<SettingsProvider settings={combinedSettings}>
|
||||
{children}
|
||||
</SettingsProvider>
|
||||
</UserProvider>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -5,7 +5,7 @@ import { FiUsers } from "react-icons/fi";
|
||||
import { UserGroup, User, UserRole } from "@/lib/types";
|
||||
import { useUserGroups } from "@/lib/hooks";
|
||||
import { BooleanFormField } from "@/components/admin/connectors/Field";
|
||||
import { getCurrentUser } from "@/lib/user";
|
||||
import { useUser } from "./user/UserProvider";
|
||||
|
||||
export type IsPublicGroupSelectorFormType = {
|
||||
is_public: boolean;
|
||||
@@ -22,41 +22,44 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
enforceGroupSelection?: boolean;
|
||||
}) => {
|
||||
const { data: userGroups, isLoading: userGroupsIsLoading } = useUserGroups();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
const { isAdmin, user, isLoadingUser } = useUser();
|
||||
const [shouldHideContent, setShouldHideContent] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
formikProps.setFieldValue("is_public", user.role === UserRole.ADMIN);
|
||||
|
||||
// Select the only group by default if there's only one
|
||||
if (
|
||||
userGroups &&
|
||||
userGroups.length === 1 &&
|
||||
formikProps.values.groups.length === 0 &&
|
||||
user.role !== UserRole.ADMIN
|
||||
) {
|
||||
formikProps.setFieldValue("groups", [userGroups[0].id]);
|
||||
}
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (user && userGroups) {
|
||||
const isUserAdmin = user.role === UserRole.ADMIN;
|
||||
if (userGroups.length === 1 && !isUserAdmin) {
|
||||
formikProps.setFieldValue("groups", [userGroups[0].id]);
|
||||
setShouldHideContent(true);
|
||||
} else if (formikProps.values.is_public) {
|
||||
formikProps.setFieldValue("groups", []);
|
||||
setShouldHideContent(false);
|
||||
} else {
|
||||
setShouldHideContent(false);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, [userGroups]); // Add userGroups as a dependency
|
||||
}
|
||||
}, [
|
||||
user,
|
||||
userGroups,
|
||||
formikProps.setFieldValue,
|
||||
formikProps.values.is_public,
|
||||
]);
|
||||
|
||||
if (isLoading || userGroupsIsLoading) {
|
||||
return null; // or return a loading spinner if preferred
|
||||
if (isLoadingUser || userGroupsIsLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (shouldHideContent && enforceGroupSelection) {
|
||||
return (
|
||||
<>
|
||||
{userGroups && (
|
||||
<div className="mb-1 font-medium text-base">
|
||||
This {objectName} will be assigned to group{" "}
|
||||
<b>{userGroups[0].name}</b>.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -70,17 +73,19 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
disabled={!isAdmin}
|
||||
subtext={
|
||||
<span className="block mt-2 text-sm text-gray-500">
|
||||
If set, then {objectName}s indexed by this {objectName} will be
|
||||
visible to <b>all users</b>. If turned off, then only users who
|
||||
explicitly have been given access to the {objectName}s (e.g.
|
||||
through a User Group) will have access.
|
||||
If set, then this {objectName} will be visible to{" "}
|
||||
<b>all users</b>. If turned off, then only users who explicitly
|
||||
have been given access to this {objectName} (e.g. through a User
|
||||
Group) will have access.
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(!formikProps.values.is_public || !isAdmin) && (
|
||||
{(!formikProps.values.is_public ||
|
||||
!isAdmin ||
|
||||
formikProps.values.groups.length > 0) && (
|
||||
<>
|
||||
<div className="flex gap-x-2 items-center">
|
||||
<div className="block font-medium text-base">
|
||||
|
@@ -1,13 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, useContext } from "react";
|
||||
import {
|
||||
CCPairBasicInfo,
|
||||
DocumentSet,
|
||||
Tag,
|
||||
User,
|
||||
ValidSources,
|
||||
} from "@/lib/types";
|
||||
import { DocumentSet, Tag, User, ValidSources } from "@/lib/types";
|
||||
import { ChatSession } from "@/app/chat/interfaces";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
|
||||
@@ -15,7 +9,6 @@ import { Folder } from "@/app/chat/folders/interfaces";
|
||||
import { InputPrompt } from "@/app/admin/prompt-library/interfaces";
|
||||
|
||||
interface ChatContextProps {
|
||||
user: User | null;
|
||||
chatSessions: ChatSession[];
|
||||
availableSources: ValidSources[];
|
||||
availableDocumentSets: DocumentSet[];
|
||||
|
@@ -26,6 +26,7 @@ import {
|
||||
IsPublicGroupSelectorFormType,
|
||||
IsPublicGroupSelector,
|
||||
} from "@/components/IsPublicGroupSelector";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
||||
const CreateButton = ({
|
||||
onClick,
|
||||
@@ -92,27 +93,12 @@ export default function CreateCredential({
|
||||
refresh?: () => void;
|
||||
}) {
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCurrentUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (user) {
|
||||
setCurrentUser(user);
|
||||
} else {
|
||||
console.error("Failed to fetch current user");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchCurrentUser();
|
||||
}, []);
|
||||
const { isLoadingUser, isAdmin } = useUser();
|
||||
if (isLoadingUser) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const handleSubmit = async (
|
||||
values: formType,
|
||||
@@ -184,7 +170,6 @@ export default function CreateCredential({
|
||||
return <GDriveMain />;
|
||||
}
|
||||
|
||||
const isAdmin = currentUser?.role === UserRole.ADMIN;
|
||||
const credentialTemplate: dictionaryType = credentialTemplates[sourceType];
|
||||
const validationSchema = createValidationSchema(credentialTemplate);
|
||||
|
||||
@@ -236,7 +221,7 @@ export default function CreateCredential({
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!swapConnector && !isLoading && (
|
||||
{!swapConnector && (
|
||||
<div className="mt-4 flex flex-col sm:flex-row justify-between items-end">
|
||||
<div className="w-full sm:w-3/4 mb-4 sm:mb-0">
|
||||
{isPaidEnterpriseFeaturesEnabled && (
|
||||
|
55
web/src/components/user/UserProvider.tsx
Normal file
55
web/src/components/user/UserProvider.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||
import { User, UserRole } from "@/lib/types";
|
||||
import { getCurrentUser } from "@/lib/user";
|
||||
|
||||
interface UserContextType {
|
||||
user: User | null;
|
||||
isLoadingUser: boolean;
|
||||
isAdmin: boolean;
|
||||
refreshUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
const UserContext = createContext<UserContextType | undefined>(undefined);
|
||||
|
||||
export function UserProvider({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [isLoadingUser, setIsLoadingUser] = useState(true);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
setUser(user);
|
||||
setIsAdmin(user?.role === UserRole.ADMIN);
|
||||
} catch (error) {
|
||||
console.error("Error fetching current user:", error);
|
||||
} finally {
|
||||
setIsLoadingUser(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchUser();
|
||||
}, []);
|
||||
|
||||
const refreshUser = async () => {
|
||||
setIsLoadingUser(true);
|
||||
await fetchUser();
|
||||
};
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ user, isLoadingUser, isAdmin, refreshUser }}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useUser() {
|
||||
const context = useContext(UserContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useUser must be used within a UserProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
@@ -65,18 +65,20 @@ export const useObjectState = <T>(
|
||||
const INDEXING_STATUS_URL = "/api/manage/admin/connector/indexing-status";
|
||||
|
||||
export const useConnectorCredentialIndexingStatus = (
|
||||
refreshInterval = 30000 // 30 seconds
|
||||
refreshInterval = 30000, // 30 seconds
|
||||
getEditable = false
|
||||
) => {
|
||||
const { mutate } = useSWRConfig();
|
||||
const url = `${INDEXING_STATUS_URL}${getEditable ? "?get_editable=true" : ""}`;
|
||||
const swrResponse = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
||||
INDEXING_STATUS_URL,
|
||||
url,
|
||||
errorHandlingFetcher,
|
||||
{ refreshInterval: refreshInterval }
|
||||
);
|
||||
|
||||
return {
|
||||
...swrResponse,
|
||||
refreshIndexingStatus: () => mutate(INDEXING_STATUS_URL),
|
||||
refreshIndexingStatus: () => mutate(url),
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -82,6 +82,7 @@ export interface ConnectorIndexingStatus<
|
||||
credential: Credential<ConnectorCredentialType>;
|
||||
public_doc: boolean;
|
||||
owner: string;
|
||||
groups: number[];
|
||||
last_finished_status: ValidStatuses | null;
|
||||
last_status: ValidStatuses | null;
|
||||
last_success: string | null;
|
||||
|
@@ -79,6 +79,8 @@ module.exports = {
|
||||
"token-regex": "#9cdcfe", // light blue
|
||||
"token-attr-name": "#9cdcfe", // light blue
|
||||
|
||||
"non-selectable": "#f8d7da", // red-100
|
||||
|
||||
// background
|
||||
"background-search": "#ffffff", // white
|
||||
input: "#f5f5f5",
|
||||
@@ -133,6 +135,7 @@ module.exports = {
|
||||
"border-medium": "#d1d5db", // gray-300
|
||||
"border-strong": "#9ca3af", // gray-400
|
||||
"border-dark": "#525252", // neutral-600
|
||||
"non-selectable-border": "#f5c2c7", // red-200
|
||||
|
||||
// hover
|
||||
"hover-light": "#f3f4f6", // gray-100
|
||||
|
Reference in New Issue
Block a user