Merge pull request #681 from AnishSarkar22/feat/config-connectors

feat: Config for connectors
This commit is contained in:
Rohan Verma 2026-01-12 01:30:50 -08:00 committed by GitHub
commit 9655db1995
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 373 additions and 16 deletions

View file

@ -8,6 +8,8 @@ import { Button } from "@/components/ui/button";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { LogActiveTask } from "@/contracts/types/log.types";
import { cn } from "@/lib/utils";
import { useConnectorStatus } from "../hooks/use-connector-status";
import { ConnectorStatusBadge } from "./connector-status-badge";
interface ConnectorCardProps {
id: string;
@ -104,6 +106,15 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
onConnect,
onManage,
}) => {
// Get connector status
const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } =
useConnectorStatus();
const status = getConnectorStatus(connectorType);
const isEnabled = isConnectorEnabled(connectorType);
const statusMessage = getConnectorStatusMessage(connectorType);
const showWarnings = shouldShowWarnings();
// Extract count from active task message during indexing
const indexingCount = extractIndexedCount(activeTask?.message);
@ -139,9 +150,23 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
return description;
};
return (
<div className="group relative flex items-center gap-4 p-4 rounded-xl text-left transition-all duration-200 w-full border border-border bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10">
<div className="flex h-12 w-12 items-center justify-center rounded-lg transition-colors shrink-0 bg-slate-400/5 dark:bg-white/5 border border-slate-400/5 dark:border-white/5">
const cardContent = (
<div
className={cn(
"group relative flex items-center gap-4 p-4 rounded-xl text-left transition-all duration-200 w-full border",
status.status === "warning"
? "border-yellow-500/30 bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
: "border-border bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
)}
>
<div
className={cn(
"flex h-12 w-12 items-center justify-center rounded-lg transition-colors shrink-0 border",
status.status === "warning"
? "bg-yellow-500/10 border-yellow-500/20 bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
)}
>
{connectorType ? (
getConnectorIcon(connectorType, "size-6")
) : id === "youtube-crawler" ? (
@ -151,8 +176,15 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5">
<span className="text-[14px] font-semibold leading-tight truncate">{title}</span>
{showWarnings && status.status !== "active" && (
<ConnectorStatusBadge
status={status.status}
statusMessage={statusMessage}
className="flex-shrink-0"
/>
)}
</div>
<div className="text-[10px] text-muted-foreground mt-1">{getStatusContent()}</div>
{isConnected && documentCount !== undefined && (
@ -179,10 +211,12 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
!isConnected && "shadow-xs"
)}
onClick={isConnected ? onManage : onConnect}
disabled={isConnecting}
disabled={isConnecting || !isEnabled}
>
{isConnecting ? (
<Loader2 className="size-3 animate-spin" />
) : !isEnabled ? (
"Unavailable"
) : isConnected ? (
"Manage"
) : id === "youtube-crawler" ? (
@ -195,4 +229,6 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
</Button>
</div>
);
return cardContent;
};

View file

@ -0,0 +1,92 @@
"use client";
import { AlertTriangle, Ban, Wrench } from "lucide-react";
import type { FC } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import type { ConnectorStatus } from "../config/connector-status-config";
import { cn } from "@/lib/utils";
interface ConnectorStatusBadgeProps {
status: ConnectorStatus;
statusMessage?: string | null;
className?: string;
}
export const ConnectorStatusBadge: FC<ConnectorStatusBadgeProps> = ({
status,
statusMessage,
className,
}) => {
if (status === "active") {
return null;
}
const getBadgeConfig = () => {
switch (status) {
case "warning":
return {
icon: AlertTriangle,
className: "text-yellow-500 dark:text-yellow-400",
defaultTitle: "Warning",
};
case "disabled":
return {
icon: Ban,
className: "text-red-500 dark:text-red-400",
defaultTitle: "Disabled",
};
case "maintenance":
return {
icon: Wrench,
className: "text-orange-500 dark:text-orange-400",
defaultTitle: "Maintenance",
};
case "deprecated":
return {
icon: AlertTriangle,
className: "ext-slate-500 dark:text-slate-400",
defaultTitle: "Deprecated",
};
default:
return null;
}
};
const config = getBadgeConfig();
if (!config) return null;
const Icon = config.icon;
// Show statusMessage in tooltip for warning, deprecated, disabled, and maintenance statuses
const shouldUseTooltip =
(status === "warning" ||
status === "deprecated" ||
status === "disabled" ||
status === "maintenance") &&
statusMessage;
const tooltipTitle = shouldUseTooltip ? statusMessage : config.defaultTitle;
// Use Tooltip component for statuses with statusMessage, native title for others
if (shouldUseTooltip) {
return (
<Tooltip>
<TooltipTrigger asChild>
<span className={cn("inline-flex items-center justify-center shrink-0", className)}>
<Icon className={cn("size-3.5", config.className)} />
</span>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-xs">
{statusMessage}
</TooltipContent>
</Tooltip>
);
}
return (
<span
className={cn("inline-flex items-center justify-center shrink-0", className)}
title={tooltipTitle}
>
<Icon className={cn("size-3.5", config.className)} />
</span>
);
};

View file

@ -0,0 +1,56 @@
"use client";
import { AlertTriangle, X } from "lucide-react";
import type { FC } from "react";
import { useState } from "react";
import { cn } from "@/lib/utils";
interface ConnectorWarningBannerProps {
warning: string;
statusMessage?: string | null;
onDismiss?: () => void;
className?: string;
}
export const ConnectorWarningBanner: FC<ConnectorWarningBannerProps> = ({
warning,
statusMessage,
onDismiss,
className,
}) => {
const [isDismissed, setIsDismissed] = useState(false);
if (isDismissed) return null;
const handleDismiss = () => {
setIsDismissed(true);
onDismiss?.();
};
return (
<div
className={cn(
"flex items-start gap-3 p-3 rounded-lg border border-yellow-500/30 bg-yellow-500/10 dark:bg-yellow-500/5 mb-4",
className
)}
>
<AlertTriangle className="size-4 text-yellow-600 dark:text-yellow-500 shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<p className="text-[12px] font-medium text-yellow-900 dark:text-yellow-200">{warning}</p>
{statusMessage && (
<p className="text-[11px] text-yellow-700 dark:text-yellow-300 mt-1">{statusMessage}</p>
)}
</div>
{onDismiss && (
<button
type="button"
onClick={handleDismiss}
className="shrink-0 p-0.5 rounded hover:bg-yellow-500/20 transition-colors"
aria-label="Dismiss warning"
>
<X className="size-3.5 text-yellow-700 dark:text-yellow-300" />
</button>
)}
</div>
);
};

View file

@ -0,0 +1,28 @@
{
"connectorStatuses": {
"SLACK_CONNECTOR": {
"enabled": false,
"status": "disabled",
"statusMessage": "Unavailable due to API changes"
},
"NOTION_CONNECTOR": {
"enabled": true,
"status": "warning",
"statusMessage": "Rate limits may apply"
},
"TEAMS_CONNECTOR": {
"enabled": false,
"status": "maintenance",
"statusMessage": "Temporarily unavailable for maintenance"
},
"JIRA_CONNECTOR": {
"enabled": false,
"status": "deprecated",
"statusMessage": "Deprecated"
}
},
"globalSettings": {
"showWarnings": true,
"allowManualOverride": false
}
}

View file

@ -0,0 +1,7 @@
{
"connectorStatuses": {},
"globalSettings": {
"showWarnings": true,
"allowManualOverride": false
}
}

View file

@ -0,0 +1,74 @@
/**
* Connector Status Configuration
*
* Manages connector statuses (disable/enable, status messages). Edit connector-status-config.json to configure.
* Valid status values: "active", "warning", "disabled", "deprecated", "maintenance".
* Unlisted connectors default to "active" and enabled. See connector-status-config.example.json for reference.
*/
import { z } from "zod";
import rawConnectorStatusConfigData from "./connector-status-config.json";
// Zod schemas for runtime validation and type safety
export const connectorStatusSchema = z.enum([
"active",
"warning",
"disabled",
"deprecated",
"maintenance",
]);
export const connectorStatusConfigSchema = z.object({
enabled: z.boolean(),
status: connectorStatusSchema,
statusMessage: z.string().nullable().optional(),
});
export const connectorStatusMapSchema = z.record(z.string(), connectorStatusConfigSchema);
export const connectorStatusConfigFileSchema = z.object({
connectorStatuses: connectorStatusMapSchema,
globalSettings: z.object({
showWarnings: z.boolean(),
allowManualOverride: z.boolean(),
}),
});
// TypeScript types inferred from Zod schemas
export type ConnectorStatus = z.infer<typeof connectorStatusSchema>;
export type ConnectorStatusConfig = z.infer<typeof connectorStatusConfigSchema>;
export type ConnectorStatusMap = z.infer<typeof connectorStatusMapSchema>;
export type ConnectorStatusConfigFile = z.infer<typeof connectorStatusConfigFileSchema>;
/**
* Validated at runtime via Zod schema; invalid JSON throws at module load time.
*/
export const connectorStatusConfig: ConnectorStatusConfigFile =
connectorStatusConfigFileSchema.parse(rawConnectorStatusConfigData);
/**
* Get default status config for a connector (when not in config file)
* Returns a validated default config
*/
export function getDefaultConnectorStatus(): ConnectorStatusConfig {
return connectorStatusConfigSchema.parse({
enabled: true,
status: "active",
statusMessage: null,
});
}
/**
* Validate a connector status config object
* Useful for validating config loaded from external sources
*/
export function validateConnectorStatusConfig(config: unknown): ConnectorStatusConfigFile {
return connectorStatusConfigFileSchema.parse(config);
}
/**
* Validate a single connector status config
*/
export function validateSingleConnectorStatus(config: unknown): ConnectorStatusConfig {
return connectorStatusConfigSchema.parse(config);
}

View file

@ -0,0 +1,55 @@
"use client";
import { useMemo } from "react";
import {
type ConnectorStatusConfig,
connectorStatusConfig,
getDefaultConnectorStatus,
} from "../config/connector-status-config";
/**
* Hook to get connector status information
*/
export function useConnectorStatus() {
/**
* Get status configuration for a specific connector type
*/
const getConnectorStatus = (connectorType: string | undefined): ConnectorStatusConfig => {
if (!connectorType) {
return getDefaultConnectorStatus();
}
return connectorStatusConfig.connectorStatuses[connectorType] || getDefaultConnectorStatus();
};
/**
* Check if a connector is enabled
*/
const isConnectorEnabled = (connectorType: string | undefined): boolean => {
return getConnectorStatus(connectorType).enabled;
};
/**
* Get status message for a connector
*/
const getConnectorStatusMessage = (connectorType: string | undefined): string | null => {
return getConnectorStatus(connectorType).statusMessage || null;
};
/**
* Check if warnings should be shown globally
*/
const shouldShowWarnings = (): boolean => {
return connectorStatusConfig.globalSettings.showWarnings;
};
return useMemo(
() => ({
getConnectorStatus,
isConnectorEnabled,
getConnectorStatusMessage,
shouldShowWarnings,
}),
[]
);
}

View file

@ -9,6 +9,7 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
import { cn } from "@/lib/utils";
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
import { useConnectorStatus } from "../hooks/use-connector-status";
interface ConnectorAccountsListViewProps {
connectorType: string;
@ -65,13 +66,19 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
onAddAccount,
isConnecting = false,
}) => {
// Get connector status
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
const isEnabled = isConnectorEnabled(connectorType);
const statusMessage = getConnectorStatusMessage(connectorType);
// Filter connectors to only show those of this type
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="px-6 sm:px-12 pt-8 sm:pt-10 pb-4 border-b border-border/50 bg-muted">
<div className="px-6 sm:px-12 pt-8 sm:pt-10 pb-1 sm:pb-4 border-b border-border/50 bg-muted">
{/* Back button */}
<button
type="button"
@ -93,7 +100,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
{connectorTitle}
</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
Manage your connector settings and sync configuration
{statusMessage || "Manage your connector settings and sync configuration"}
</p>
</div>
</div>
@ -101,21 +108,23 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
<button
type="button"
onClick={onAddAccount}
disabled={isConnecting}
disabled={isConnecting || !isEnabled}
className={cn(
"flex items-center gap-2 px-3 py-2 rounded-lg border-2 border-dashed border-border/70 text-left transition-all duration-200 shrink-0 self-center sm:self-auto sm:w-auto",
"border-primary/50 hover:bg-primary/5",
"flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg border-2 border-dashed text-left transition-all duration-200 shrink-0 self-center sm:self-auto sm:w-auto",
!isEnabled
? "border-border/30 opacity-50 cursor-not-allowed"
: "border-primary/50 hover:bg-primary/5",
isConnecting && "opacity-50 cursor-not-allowed"
)}
>
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary/10 shrink-0">
<div className="flex h-5 w-5 sm:h-6 sm:w-6 items-center justify-center rounded-md bg-primary/10 shrink-0">
{isConnecting ? (
<Loader2 className="size-3.5 animate-spin text-primary" />
<Loader2 className="size-3 sm:size-3.5 animate-spin text-primary" />
) : (
<Plus className="size-3.5 text-primary" />
<Plus className="size-3 sm:size-3.5 text-primary" />
)}
</div>
<span className="text-[12px] font-medium">
<span className="text-[11px] sm:text-[12px] font-medium">
{isConnecting ? "Connecting..." : "Add Account"}
</span>
</button>
@ -123,7 +132,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto px-6 sm:px-12 py-6 sm:py-8">
<div className="flex-1 overflow-y-auto px-6 sm:px-12 pt-0 sm:pt-6 pb-6 sm:pb-8">
{/* Connected Accounts Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{typeConnectors.map((connector) => {

View file

@ -24,7 +24,7 @@ export function Header({
{/* Left side - Mobile menu trigger + Breadcrumb */}
<div className="flex flex-1 items-center gap-2 min-w-0">
{mobileMenuTrigger}
{breadcrumb}
<div className="hidden md:block">{breadcrumb}</div>
</div>
{/* Right side - Actions */}