sharepoint integration
This commit is contained in:
parent
10c7073cce
commit
c5ecd88e66
13 changed files with 1168 additions and 563 deletions
|
|
@ -344,8 +344,6 @@ export function FormGenerator<T extends Record<string, any>>({
|
|||
|
||||
return (
|
||||
<div className={`${styles.formGenerator} ${className}`}>
|
||||
{title && <h2 className={styles.title}>{title}</h2>}
|
||||
|
||||
|
||||
{(searchable || filterable) && (
|
||||
<div className={styles.controls}>
|
||||
|
|
|
|||
40
src/components/TestSharepoint/TestSharepointTable.module.css
Normal file
40
src/components/TestSharepoint/TestSharepointTable.module.css
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
.testSharepointTable {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.errorState {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-error, #dc3545);
|
||||
border-radius: 8px;
|
||||
color: var(--color-error, #dc3545);
|
||||
text-align: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.retryButton {
|
||||
padding: 8px 16px;
|
||||
background: var(--color-error, #dc3545);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-family);
|
||||
font-size: 14px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.retryButton:hover {
|
||||
background: var(--color-error-hover, #c82333);
|
||||
}
|
||||
|
||||
.sharepointFormGenerator {
|
||||
width: 100%;
|
||||
}
|
||||
72
src/components/TestSharepoint/TestSharepointTable.tsx
Normal file
72
src/components/TestSharepoint/TestSharepointTable.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { FormGenerator } from '../FormGenerator';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import styles from './TestSharepointTable.module.css';
|
||||
import { useTestSharepointLogic } from './testSharepointLogic';
|
||||
import type { TestSharepointTableProps } from './testSharepointInterfaces';
|
||||
|
||||
export function TestSharepointTable({
|
||||
className = '',
|
||||
documents,
|
||||
documentsLoading,
|
||||
documentsError,
|
||||
columns,
|
||||
actions,
|
||||
onRowClick
|
||||
}: TestSharepointTableProps) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
// Get fallback data from hook if props not provided (for backwards compatibility)
|
||||
const hookData = useTestSharepointLogic();
|
||||
|
||||
// Use props data if provided, otherwise fall back to hook data
|
||||
const actualDocuments = documents ?? hookData.documents;
|
||||
const actualLoading = documentsLoading ?? hookData.documentsLoading;
|
||||
const actualError = documentsError ?? hookData.documentsError;
|
||||
const actualColumns = columns ?? hookData.columns;
|
||||
const actualActions = actions ?? hookData.actions;
|
||||
|
||||
// Debug the data being passed to FormGenerator
|
||||
console.log('TestSharepointTable - actualDocuments:', actualDocuments);
|
||||
console.log('TestSharepointTable - actualLoading:', actualLoading);
|
||||
console.log('TestSharepointTable - actualError:', actualError);
|
||||
console.log('TestSharepointTable - actualColumns:', actualColumns);
|
||||
|
||||
// Show error state
|
||||
if (actualError) {
|
||||
return (
|
||||
<div className={`${styles.testSharepointTable} ${className}`}>
|
||||
<div className={styles.errorState}>
|
||||
<p>{t('sharepoint.error.loading', 'Error loading SharePoint documents:')} {actualError}</p>
|
||||
<button onClick={() => window.location.reload()} className={styles.retryButton}>
|
||||
{t('sharepoint.button.retry', 'Retry')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.testSharepointTable} ${className}`}>
|
||||
<FormGenerator
|
||||
data={actualDocuments}
|
||||
columns={actualColumns}
|
||||
title={t('sharepoint.table.title', 'SharePoint Documents')}
|
||||
loading={actualLoading}
|
||||
searchable={true}
|
||||
filterable={true}
|
||||
sortable={true}
|
||||
resizable={true}
|
||||
pagination={true}
|
||||
pageSize={10}
|
||||
pageSizeOptions={[10, 25, 50, 100]}
|
||||
showPageSizeSelector={true}
|
||||
selectable={false}
|
||||
onRowClick={onRowClick}
|
||||
actions={actualActions}
|
||||
className={styles.sharepointFormGenerator}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TestSharepointTable;
|
||||
11
src/components/TestSharepoint/index.ts
Normal file
11
src/components/TestSharepoint/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export { TestSharepointTable } from './TestSharepointTable';
|
||||
export { useTestSharepointLogic } from './testSharepointLogic';
|
||||
export type {
|
||||
TestSharepointTableProps,
|
||||
TableAction,
|
||||
SharePointHandlers,
|
||||
SharePointOperationsReturn,
|
||||
SharePointConnectionsReturn,
|
||||
SharePointTableConfig,
|
||||
TestSharepointLogicReturn
|
||||
} from './testSharepointInterfaces';
|
||||
112
src/components/TestSharepoint/testSharepointInterfaces.ts
Normal file
112
src/components/TestSharepoint/testSharepointInterfaces.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { ColumnConfig } from '../FormGenerator';
|
||||
import React from 'react';
|
||||
|
||||
// Re-export SharePoint-related interfaces from hooks
|
||||
export type {
|
||||
SharePointConnection,
|
||||
SharePointDocument,
|
||||
SharePointResponse,
|
||||
SharePointListRequest,
|
||||
SharePointFindRequest,
|
||||
SharePointReadRequest,
|
||||
SharePointUploadRequest
|
||||
} from '../../hooks/useSharePointTest';
|
||||
|
||||
// Import for local use
|
||||
import type { SharePointConnection, SharePointDocument } from '../../hooks/useSharePointTest';
|
||||
|
||||
// Component Props Interfaces
|
||||
export interface TestSharepointTableProps {
|
||||
className?: string;
|
||||
documents?: SharePointDocument[];
|
||||
documentsLoading?: boolean;
|
||||
documentsError?: string | null;
|
||||
columns?: any[];
|
||||
actions?: any[];
|
||||
onRowClick?: (row: any) => void;
|
||||
}
|
||||
|
||||
// Table Action Interface
|
||||
export interface TableAction {
|
||||
label: string;
|
||||
onClick: (document: SharePointDocument) => Promise<void> | void;
|
||||
icon: React.ReactNode | ((document: SharePointDocument) => React.ReactNode);
|
||||
}
|
||||
|
||||
// SharePoint Operation Handler Types
|
||||
export interface SharePointHandlers {
|
||||
handleConnectionTest: (connectionId: string) => Promise<boolean>;
|
||||
handleListDocuments: (connectionId: string, siteUrl: string, folderPaths: string[]) => Promise<SharePointDocument[]>;
|
||||
handleFindDocuments: (connectionId: string, siteUrl: string, query: string) => Promise<SharePointDocument[]>;
|
||||
handleReadDocument: (connectionId: string, siteUrl: string, documentPath: string) => Promise<{ success: boolean; data?: any; error?: string }>;
|
||||
}
|
||||
|
||||
// Hook Return Types for SharePoint Operations
|
||||
export interface SharePointOperationsReturn extends SharePointHandlers {
|
||||
testingConnections: Set<string>;
|
||||
loadingDocuments: boolean;
|
||||
connectionError: string | null;
|
||||
documentsError: string | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
// Hook Return Types for SharePoint Connections
|
||||
export interface SharePointConnectionsReturn {
|
||||
connections: SharePointConnection[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => Promise<void>;
|
||||
testConnection: (connectionId: string) => Promise<any>;
|
||||
}
|
||||
|
||||
// SharePoint Table Configuration
|
||||
export interface SharePointTableConfig {
|
||||
columns: ColumnConfig[];
|
||||
actions: TableAction[];
|
||||
pageSize: number;
|
||||
searchable: boolean;
|
||||
filterable: boolean;
|
||||
sortable: boolean;
|
||||
resizable: boolean;
|
||||
pagination: boolean;
|
||||
}
|
||||
|
||||
// Hook Return Type for TestSharepoint Logic
|
||||
export interface TestSharepointLogicReturn {
|
||||
// Connection data
|
||||
connections: SharePointConnection[];
|
||||
selectedConnection: SharePointConnection | null;
|
||||
connectionLoading: boolean;
|
||||
connectionError: string | null;
|
||||
|
||||
// Document data
|
||||
documents: SharePointDocument[];
|
||||
documentsLoading: boolean;
|
||||
documentsError: string | null;
|
||||
|
||||
// Table configuration
|
||||
columns: ColumnConfig[];
|
||||
actions: TableAction[];
|
||||
|
||||
// Connection testing
|
||||
testingConnections: Set<string>;
|
||||
connectionTestResults: Record<string, any>;
|
||||
|
||||
// Site discovery
|
||||
discoveredSites: any[];
|
||||
sitesDiscovered: boolean;
|
||||
|
||||
// Token debug
|
||||
tokenDebugInfo: any;
|
||||
|
||||
// Handlers
|
||||
handleSelectConnection: (connectionId: string) => void;
|
||||
handleTestConnection: (connectionId: string) => Promise<void>;
|
||||
handleListDocuments: (siteUrl?: string, folderPaths?: string[]) => Promise<void>;
|
||||
handleDiscoverSites: () => Promise<void>;
|
||||
handleSelectSite: (siteUrl: string) => void;
|
||||
handleDebugTokens: () => Promise<void>;
|
||||
handleCleanupTokens: () => Promise<void>;
|
||||
handleFolderNavigation: (document: SharePointDocument, currentPath: string) => string;
|
||||
refetchConnections: () => Promise<void>;
|
||||
}
|
||||
395
src/components/TestSharepoint/testSharepointLogic.tsx
Normal file
395
src/components/TestSharepoint/testSharepointLogic.tsx
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
import { useMemo, useState, useEffect } from 'react';
|
||||
import { IoIosLink, IoIosCloudDownload } from 'react-icons/io';
|
||||
|
||||
import { ColumnConfig } from '../FormGenerator';
|
||||
import { useSharePointTest } from '../../hooks/useSharePointTest';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import type {
|
||||
TableAction,
|
||||
SharePointConnection,
|
||||
SharePointDocument,
|
||||
TestSharepointLogicReturn
|
||||
} from './testSharepointInterfaces';
|
||||
|
||||
export function useTestSharepointLogic(): TestSharepointLogicReturn {
|
||||
const {
|
||||
getConnections,
|
||||
testConnection,
|
||||
listDocuments,
|
||||
discoverSites,
|
||||
debugTokenDetails,
|
||||
cleanupTokens,
|
||||
isLoading
|
||||
} = useSharePointTest();
|
||||
const { t } = useLanguage();
|
||||
|
||||
// State management
|
||||
const [connections, setConnections] = useState<SharePointConnection[]>([]);
|
||||
const [selectedConnection, setSelectedConnection] = useState<SharePointConnection | null>(null);
|
||||
const [connectionLoading, setConnectionLoading] = useState(false);
|
||||
const [connectionError, setConnectionError] = useState<string | null>(null);
|
||||
|
||||
const [documents, setDocuments] = useState<SharePointDocument[]>([]);
|
||||
const [documentsLoading, setDocumentsLoading] = useState(false);
|
||||
const [documentsError, setDocumentsError] = useState<string | null>(null);
|
||||
|
||||
const [testingConnections, setTestingConnections] = useState<Set<string>>(new Set());
|
||||
const [connectionTestResults, setConnectionTestResults] = useState<Record<string, any>>({});
|
||||
|
||||
const [discoveredSites, setDiscoveredSites] = useState<any[]>([]);
|
||||
const [sitesDiscovered, setSitesDiscovered] = useState(false);
|
||||
const [tokenDebugInfo, setTokenDebugInfo] = useState<any>(null);
|
||||
|
||||
// Load connections on mount
|
||||
useEffect(() => {
|
||||
loadConnections();
|
||||
}, []);
|
||||
|
||||
const loadConnections = async () => {
|
||||
setConnectionLoading(true);
|
||||
setConnectionError(null);
|
||||
try {
|
||||
const conns = await getConnections();
|
||||
setConnections(conns);
|
||||
if (conns.length > 0 && !selectedConnection) {
|
||||
setSelectedConnection(conns[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load connections:', error);
|
||||
setConnectionError(error instanceof Error ? error.message : 'Failed to load connections');
|
||||
} finally {
|
||||
setConnectionLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Configure columns for the SharePoint documents table
|
||||
const columns: ColumnConfig[] = useMemo(() => [
|
||||
{
|
||||
key: 'documentName',
|
||||
label: t('sharepoint.column.documentName', 'Document Name'),
|
||||
type: 'string',
|
||||
width: 300,
|
||||
minWidth: 200,
|
||||
maxWidth: 400,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
searchable: true,
|
||||
formatter: (value: string, row: any) => (
|
||||
<span
|
||||
style={{
|
||||
color: 'var(--color-text)',
|
||||
fontWeight: 500,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
cursor: row?.type === 'folder' ? 'pointer' : 'default'
|
||||
}}
|
||||
title={value}
|
||||
>
|
||||
{row?.type === 'folder' ? '📁' : '📄'} {value}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'mimeType',
|
||||
label: t('sharepoint.column.mimeType', 'MIME Type'),
|
||||
type: 'string',
|
||||
width: 200,
|
||||
minWidth: 150,
|
||||
maxWidth: 300,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
key: 'size',
|
||||
label: t('sharepoint.column.size', 'Size'),
|
||||
type: 'number',
|
||||
width: 140,
|
||||
minWidth: 120,
|
||||
maxWidth: 180,
|
||||
sortable: true,
|
||||
filterable: false,
|
||||
formatter: (value: number | string | undefined) => {
|
||||
if (!value || value === 0) return '-';
|
||||
|
||||
const sizeInBytes = typeof value === 'string' ? parseInt(value, 10) : value;
|
||||
const units = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
let size = sizeInBytes;
|
||||
let unitIndex = 0;
|
||||
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return (
|
||||
<span style={{ fontWeight: 500, color: 'var(--color-text)' }}>
|
||||
{`${size.toFixed(1)} ${units[unitIndex]}`}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
label: t('sharepoint.column.path', 'Path'),
|
||||
type: 'string',
|
||||
width: 250,
|
||||
minWidth: 200,
|
||||
maxWidth: 400,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
searchable: true,
|
||||
},
|
||||
], [t]);
|
||||
|
||||
// Handle connection selection
|
||||
const handleSelectConnection = (connectionId: string) => {
|
||||
const connection = connections.find(conn => conn.id === connectionId);
|
||||
if (connection) {
|
||||
setSelectedConnection(connection);
|
||||
// Clear documents when changing connection
|
||||
setDocuments([]);
|
||||
setDocumentsError(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle connection testing
|
||||
const handleTestConnection = async (connectionId: string) => {
|
||||
setTestingConnections(prev => new Set(prev).add(connectionId));
|
||||
try {
|
||||
const result = await testConnection(connectionId);
|
||||
setConnectionTestResults(prev => ({ ...prev, [connectionId]: result }));
|
||||
} catch (error) {
|
||||
console.error('Connection test failed:', error);
|
||||
setConnectionTestResults(prev => ({
|
||||
...prev,
|
||||
[connectionId]: {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Connection test failed'
|
||||
}
|
||||
}));
|
||||
} finally {
|
||||
setTestingConnections(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(connectionId);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handle listing documents
|
||||
const handleListDocuments = async (siteUrl?: string, folderPaths?: string[]) => {
|
||||
if (!selectedConnection) {
|
||||
setDocumentsError('No connection selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setDocumentsLoading(true);
|
||||
setDocumentsError(null);
|
||||
|
||||
try {
|
||||
const connectionReference = `connection:${selectedConnection.authority}:${selectedConnection.externalUsername}:${selectedConnection.id}`;
|
||||
|
||||
const response = await listDocuments({
|
||||
connectionReference,
|
||||
siteUrl: siteUrl || 'https://your-tenant.sharepoint.com/sites/your-site',
|
||||
folderPaths: folderPaths || ['/'],
|
||||
includeSubfolders: false,
|
||||
expectedDocumentFormats: [{ extension: '.json', mimeType: 'application/json' }]
|
||||
});
|
||||
|
||||
console.log('SharePoint response:', response);
|
||||
|
||||
if (response.success && response.data?.documents) {
|
||||
// Extract the actual files from the nested structure
|
||||
const documents = response.data.documents;
|
||||
if (documents.length > 0 && documents[0].documentData?.listResults) {
|
||||
// Flatten all files from all folder results
|
||||
const allFiles: SharePointDocument[] = [];
|
||||
documents[0].documentData.listResults.forEach((folderResult: any) => {
|
||||
if (folderResult.items && Array.isArray(folderResult.items)) {
|
||||
folderResult.items.forEach((item: any) => {
|
||||
// Convert SharePoint item to our document format
|
||||
allFiles.push({
|
||||
documentName: item.name || 'Unknown',
|
||||
mimeType: item.file?.mimeType || (item.type === 'folder' ? 'folder' : 'unknown'),
|
||||
size: item.size || 0,
|
||||
path: folderResult.folderPath || '/',
|
||||
type: item.type || (item.folder ? 'folder' : 'file'),
|
||||
id: item.id,
|
||||
documentData: item // Store the full item data
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log('Extracted files:', allFiles);
|
||||
console.log('Sample file structure:', allFiles[0]);
|
||||
setDocuments(allFiles);
|
||||
} else {
|
||||
console.log('No listResults found in response');
|
||||
setDocuments([]);
|
||||
}
|
||||
} else {
|
||||
console.log('Response error or no documents:', response);
|
||||
setDocumentsError(response.error || 'Failed to list documents');
|
||||
setDocuments([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to list documents:', error);
|
||||
setDocumentsError(error instanceof Error ? error.message : 'Failed to list documents');
|
||||
setDocuments([]);
|
||||
} finally {
|
||||
setDocumentsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle site discovery
|
||||
const handleDiscoverSites = async () => {
|
||||
if (!selectedConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await discoverSites();
|
||||
if (result.success && result.data && result.data.sites) {
|
||||
setDiscoveredSites(result.data.sites);
|
||||
setSitesDiscovered(true);
|
||||
setDocumentsError(null); // Clear any previous errors
|
||||
} else {
|
||||
console.error('Site discovery failed:', result);
|
||||
setDiscoveredSites([]);
|
||||
setSitesDiscovered(true); // Set to true so we show the "no sites" message
|
||||
// Set error message to help user understand what went wrong
|
||||
const errorMsg = result.error || result.message || 'Unknown error occurred';
|
||||
setDocumentsError(`Site discovery failed: ${errorMsg}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Site discovery failed:', error);
|
||||
setDiscoveredSites([]);
|
||||
setSitesDiscovered(true);
|
||||
setDocumentsError(`Site discovery error: ${error instanceof Error ? error.message : 'Network or authentication error'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle site selection
|
||||
const handleSelectSite = (siteUrl: string) => {
|
||||
// This will be used by the parent component
|
||||
console.log('Site selected:', siteUrl);
|
||||
};
|
||||
|
||||
// Handle token debug
|
||||
const handleDebugTokens = async () => {
|
||||
try {
|
||||
const result = await debugTokenDetails();
|
||||
setTokenDebugInfo(result);
|
||||
console.log('Token debug info:', result);
|
||||
} catch (error) {
|
||||
console.error('Token debug failed:', error);
|
||||
setTokenDebugInfo({ error: error instanceof Error ? error.message : 'Failed to get token info' });
|
||||
}
|
||||
};
|
||||
|
||||
// Handle token cleanup
|
||||
const handleCleanupTokens = async () => {
|
||||
try {
|
||||
const result = await cleanupTokens();
|
||||
console.log('Token cleanup result:', result);
|
||||
// Clear the debug info to force refresh
|
||||
setTokenDebugInfo(null);
|
||||
// Show success message
|
||||
setDocumentsError(null);
|
||||
alert(`Success! Deleted ${result.data?.tokensDeleted || 0} tokens. Please reconnect your Microsoft account now.`);
|
||||
} catch (error) {
|
||||
console.error('Token cleanup failed:', error);
|
||||
alert(`Token cleanup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle folder navigation
|
||||
const handleFolderNavigation = (document: SharePointDocument, currentPath: string) => {
|
||||
if (document.type === 'folder') {
|
||||
// Build the new path by combining current path with folder name
|
||||
const newPath = currentPath === '/' ? `/${document.documentName}` : `${currentPath}/${document.documentName}`;
|
||||
console.log('Navigating to folder:', newPath);
|
||||
return newPath;
|
||||
}
|
||||
return currentPath;
|
||||
};
|
||||
|
||||
// Handle document actions
|
||||
const handleViewDocument = async (document: SharePointDocument) => {
|
||||
console.log('View document:', document);
|
||||
// TODO: Implement document viewing
|
||||
};
|
||||
|
||||
const handleDownloadDocument = async (document: SharePointDocument) => {
|
||||
console.log('Download document:', document);
|
||||
// TODO: Implement document download
|
||||
};
|
||||
|
||||
// Configure action buttons
|
||||
const actions: TableAction[] = useMemo(() => [
|
||||
{
|
||||
label: t('sharepoint.action.view', 'View'),
|
||||
icon: <IoIosLink />,
|
||||
onClick: (document: SharePointDocument) => {
|
||||
handleViewDocument(document);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('sharepoint.action.download', 'Download'),
|
||||
icon: <IoIosCloudDownload />,
|
||||
onClick: (document: SharePointDocument) => {
|
||||
handleDownloadDocument(document);
|
||||
}
|
||||
}
|
||||
], [t]);
|
||||
|
||||
// Refetch connections
|
||||
const refetchConnections = async () => {
|
||||
await loadConnections();
|
||||
};
|
||||
|
||||
return {
|
||||
// Connection data
|
||||
connections,
|
||||
selectedConnection,
|
||||
connectionLoading,
|
||||
connectionError,
|
||||
|
||||
// Document data
|
||||
documents,
|
||||
documentsLoading: documentsLoading || isLoading,
|
||||
documentsError,
|
||||
|
||||
// Table configuration
|
||||
columns,
|
||||
actions,
|
||||
|
||||
// Connection testing
|
||||
testingConnections,
|
||||
connectionTestResults,
|
||||
|
||||
// Site discovery
|
||||
discoveredSites,
|
||||
sitesDiscovered,
|
||||
|
||||
// Token debug
|
||||
tokenDebugInfo,
|
||||
|
||||
// Handlers
|
||||
handleSelectConnection,
|
||||
handleTestConnection,
|
||||
handleListDocuments,
|
||||
handleDiscoverSites,
|
||||
handleSelectSite,
|
||||
handleDebugTokens,
|
||||
handleCleanupTokens,
|
||||
handleFolderNavigation,
|
||||
refetchConnections
|
||||
};
|
||||
}
|
||||
|
|
@ -18,6 +18,10 @@ export interface SharePointDocument {
|
|||
documentName: string;
|
||||
documentData: any;
|
||||
mimeType: string;
|
||||
size?: number;
|
||||
path?: string;
|
||||
type?: 'file' | 'folder';
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface SharePointResponse {
|
||||
|
|
@ -242,6 +246,20 @@ export function useSharePointTest() {
|
|||
}
|
||||
};
|
||||
|
||||
// Cleanup all Microsoft tokens
|
||||
const cleanupTokens = async (): Promise<any> => {
|
||||
try {
|
||||
const response = await request({
|
||||
url: '/api/test-sharepoint/cleanup-tokens',
|
||||
method: 'delete'
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error cleaning up tokens:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Discover SharePoint sites
|
||||
const discoverSites = async (): Promise<any> => {
|
||||
try {
|
||||
|
|
@ -267,6 +285,7 @@ export function useSharePointTest() {
|
|||
getExamples,
|
||||
debugTokens,
|
||||
debugTokenDetails,
|
||||
cleanupTokens,
|
||||
discoverSites,
|
||||
|
||||
// State
|
||||
|
|
|
|||
|
|
@ -343,4 +343,28 @@ export default {
|
|||
'formgen.pagination.prev': 'Vorherige Seite',
|
||||
'formgen.pagination.next': 'Nächste Seite',
|
||||
'formgen.pagination.last': 'Letzte Seite',
|
||||
|
||||
// SharePoint Test
|
||||
'sharepoint.title': 'SharePoint Test',
|
||||
'sharepoint.table.title': 'SharePoint Dokumente',
|
||||
'sharepoint.error.loading': 'Fehler beim Laden der SharePoint Dokumente:',
|
||||
'sharepoint.button.retry': 'Wiederholen',
|
||||
'sharepoint.button.testConnection': 'Verbindung testen',
|
||||
'sharepoint.button.listDocuments': 'Dokumente auflisten',
|
||||
'sharepoint.button.discoverSites': 'Sites entdecken',
|
||||
'sharepoint.column.documentName': 'Dokumentname',
|
||||
'sharepoint.column.mimeType': 'MIME-Typ',
|
||||
'sharepoint.column.size': 'Größe',
|
||||
'sharepoint.column.path': 'Pfad',
|
||||
'sharepoint.action.view': 'Anzeigen',
|
||||
'sharepoint.action.download': 'Herunterladen',
|
||||
'sharepoint.connections.title': 'Microsoft Verbindungen',
|
||||
'sharepoint.connections.noConnections': 'Keine Microsoft-Verbindungen gefunden. Bitte erstellen Sie zuerst eine Verbindung.',
|
||||
'sharepoint.connections.loading': 'Verbindungen werden geladen...',
|
||||
'sharepoint.sites.discovered': 'Entdeckte Sites',
|
||||
'sharepoint.sites.noSites': 'Keine SharePoint-Sites gefunden',
|
||||
'sharepoint.sites.authError': 'Authentifizierungstoken abgelaufen oder ungültig. Bitte verbinden Sie Ihr Microsoft-Konto erneut.',
|
||||
'sharepoint.sites.retryConnection': 'Versuchen Sie, Ihr Microsoft-Konto auf der Verbindungsseite erneut zu verbinden.',
|
||||
'sharepoint.form.siteUrl': 'SharePoint Site URL',
|
||||
'sharepoint.form.folderPaths': 'Ordnerpfade',
|
||||
};
|
||||
|
|
@ -344,4 +344,28 @@ export default {
|
|||
'formgen.pagination.prev': 'Previous page',
|
||||
'formgen.pagination.next': 'Next page',
|
||||
'formgen.pagination.last': 'Last page',
|
||||
|
||||
// SharePoint Test
|
||||
'sharepoint.title': 'SharePoint Test',
|
||||
'sharepoint.table.title': 'SharePoint Documents',
|
||||
'sharepoint.error.loading': 'Error loading SharePoint documents:',
|
||||
'sharepoint.button.retry': 'Retry',
|
||||
'sharepoint.button.testConnection': 'Test Connection',
|
||||
'sharepoint.button.listDocuments': 'List Documents',
|
||||
'sharepoint.button.discoverSites': 'Discover Sites',
|
||||
'sharepoint.column.documentName': 'Document Name',
|
||||
'sharepoint.column.mimeType': 'MIME Type',
|
||||
'sharepoint.column.size': 'Size',
|
||||
'sharepoint.column.path': 'Path',
|
||||
'sharepoint.action.view': 'View',
|
||||
'sharepoint.action.download': 'Download',
|
||||
'sharepoint.connections.title': 'Microsoft Connections',
|
||||
'sharepoint.connections.noConnections': 'No Microsoft connections found. Please create a connection first.',
|
||||
'sharepoint.connections.loading': 'Loading connections...',
|
||||
'sharepoint.sites.discovered': 'Discovered Sites',
|
||||
'sharepoint.sites.noSites': 'No SharePoint sites found',
|
||||
'sharepoint.sites.authError': 'Authentication token expired or invalid. Please reconnect your Microsoft account.',
|
||||
'sharepoint.sites.retryConnection': 'Try reconnecting your Microsoft account in the Connections page.',
|
||||
'sharepoint.form.siteUrl': 'SharePoint Site URL',
|
||||
'sharepoint.form.folderPaths': 'Folder Paths',
|
||||
};
|
||||
|
|
@ -343,4 +343,28 @@ export default {
|
|||
'formgen.pagination.prev': 'Page précédente',
|
||||
'formgen.pagination.next': 'Page suivante',
|
||||
'formgen.pagination.last': 'Dernière page',
|
||||
|
||||
// SharePoint Test
|
||||
'sharepoint.title': 'Test SharePoint',
|
||||
'sharepoint.table.title': 'Documents SharePoint',
|
||||
'sharepoint.error.loading': 'Erreur lors du chargement des documents SharePoint:',
|
||||
'sharepoint.button.retry': 'Réessayer',
|
||||
'sharepoint.button.testConnection': 'Tester la connexion',
|
||||
'sharepoint.button.listDocuments': 'Lister les documents',
|
||||
'sharepoint.button.discoverSites': 'Découvrir les sites',
|
||||
'sharepoint.column.documentName': 'Nom du document',
|
||||
'sharepoint.column.mimeType': 'Type MIME',
|
||||
'sharepoint.column.size': 'Taille',
|
||||
'sharepoint.column.path': 'Chemin',
|
||||
'sharepoint.action.view': 'Voir',
|
||||
'sharepoint.action.download': 'Télécharger',
|
||||
'sharepoint.connections.title': 'Connexions Microsoft',
|
||||
'sharepoint.connections.noConnections': 'Aucune connexion Microsoft trouvée. Veuillez d\'abord créer une connexion.',
|
||||
'sharepoint.connections.loading': 'Chargement des connexions...',
|
||||
'sharepoint.sites.discovered': 'Sites découverts',
|
||||
'sharepoint.sites.noSites': 'Aucun site SharePoint trouvé',
|
||||
'sharepoint.sites.authError': 'Token d\'authentification expiré ou invalide. Veuillez reconnecter votre compte Microsoft.',
|
||||
'sharepoint.sites.retryConnection': 'Essayez de reconnecter votre compte Microsoft dans la page Connexions.',
|
||||
'sharepoint.form.siteUrl': 'URL du site SharePoint',
|
||||
'sharepoint.form.folderPaths': 'Chemins des dossiers',
|
||||
};
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--border-primary);
|
||||
flex-shrink: 0; /* Prevent sections from shrinking */
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
|
|
@ -302,6 +303,81 @@
|
|||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.sharepointTableContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
background: var(--color-error-bg, #ffe6e6);
|
||||
color: var(--color-error, #d32f2f);
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-error, #d32f2f);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.successMessage {
|
||||
color: var(--color-success, #4caf50);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--border-primary);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--border-accent);
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 4px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 4px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.requestPreview {
|
||||
background: var(--bg-code);
|
||||
border: 1px solid var(--border-secondary);
|
||||
|
|
@ -394,4 +470,49 @@
|
|||
color: #856404;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Breadcrumb Navigation */
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: var(--color-bg-secondary, #f8f9fa);
|
||||
border: 1px solid var(--color-border, #dee2e6);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.breadcrumbLabel {
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary, #6c757d);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.breadcrumbItem {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
color: var(--color-text, #212529);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.breadcrumbClickable {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary, #007bff);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.breadcrumbClickable:hover {
|
||||
background: var(--color-primary-bg, #e7f1ff);
|
||||
color: var(--color-primary-dark, #0056b3);
|
||||
}
|
||||
|
||||
.breadcrumbSeparator {
|
||||
color: var(--color-text-secondary, #6c757d);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
background: var(--color-bg);
|
||||
box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.30);
|
||||
gap: 20px;
|
||||
height: 100%;
|
||||
min-height: calc(100vh - 50px); /* Ensure minimum height but allow expansion */
|
||||
}
|
||||
|
||||
/* Page headers with consistent spacing */
|
||||
|
|
|
|||
|
|
@ -1,269 +1,86 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
useSharePointTest,
|
||||
SharePointConnection,
|
||||
SharePointListRequest,
|
||||
SharePointFindRequest,
|
||||
SharePointReadRequest,
|
||||
SharePointUploadRequest,
|
||||
SharePointResponse
|
||||
} from '../../hooks/useSharePointTest';
|
||||
import styles from './TestSharepoint.module.css';
|
||||
|
||||
type TestOperation = 'list' | 'find' | 'read' | 'upload';
|
||||
|
||||
interface FormData {
|
||||
connectionReference: string;
|
||||
siteUrl: string;
|
||||
folderPaths: string[];
|
||||
query: string;
|
||||
searchScope: string;
|
||||
documentList: string;
|
||||
documentPaths: string[];
|
||||
fileNames: string[];
|
||||
includeMetadata: boolean;
|
||||
includeSubfolders: boolean;
|
||||
}
|
||||
import { useState } from 'react';
|
||||
import { IoIosRefresh, IoIosLink } from 'react-icons/io';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import sharedStyles from './HomeStyles/pages.module.css'
|
||||
import styles from './HomeStyles/TestSharepoint.module.css'
|
||||
import { TestSharepointTable, useTestSharepointLogic } from '../../components/TestSharepoint'
|
||||
|
||||
function TestSharepoint() {
|
||||
const { t } = useLanguage();
|
||||
const {
|
||||
getConnections,
|
||||
testConnection,
|
||||
listDocuments,
|
||||
findDocuments,
|
||||
readDocuments,
|
||||
uploadDocuments,
|
||||
getExamples,
|
||||
debugTokens,
|
||||
debugTokenDetails,
|
||||
discoverSites,
|
||||
isLoading,
|
||||
error,
|
||||
lastResponse
|
||||
} = useSharePointTest();
|
||||
connections,
|
||||
selectedConnection,
|
||||
connectionLoading,
|
||||
connectionError,
|
||||
documents,
|
||||
documentsLoading,
|
||||
documentsError,
|
||||
columns,
|
||||
actions,
|
||||
testingConnections,
|
||||
connectionTestResults,
|
||||
discoveredSites,
|
||||
sitesDiscovered,
|
||||
tokenDebugInfo,
|
||||
handleSelectConnection,
|
||||
handleTestConnection,
|
||||
handleListDocuments,
|
||||
handleDiscoverSites,
|
||||
handleSelectSite,
|
||||
handleDebugTokens,
|
||||
handleCleanupTokens,
|
||||
handleFolderNavigation,
|
||||
refetchConnections
|
||||
} = useTestSharepointLogic();
|
||||
|
||||
// State
|
||||
const [connections, setConnections] = useState<SharePointConnection[]>([]);
|
||||
const [selectedConnection, setSelectedConnection] = useState<string>('');
|
||||
const [activeTab, setActiveTab] = useState<TestOperation>('list');
|
||||
const [examples, setExamples] = useState<any>({});
|
||||
const [testResults, setTestResults] = useState<any>({});
|
||||
const [tokenDebugInfo, setTokenDebugInfo] = useState<any>(null);
|
||||
const [discoveredSites, setDiscoveredSites] = useState<any[]>([]);
|
||||
const [sitesDiscovered, setSitesDiscovered] = useState<boolean>(false);
|
||||
const [tableRefreshKey, setTableRefreshKey] = useState(0);
|
||||
const [siteUrl, setSiteUrl] = useState('https://your-tenant.sharepoint.com/sites/your-site');
|
||||
const [folderPaths, setFolderPaths] = useState(['/']);
|
||||
|
||||
// Form data
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
connectionReference: '',
|
||||
siteUrl: 'https://your-tenant.sharepoint.com/sites/your-site',
|
||||
folderPaths: ['/'], // Start at root folder
|
||||
query: 'quarterly report 2024',
|
||||
searchScope: 'all',
|
||||
documentList: 'document_list_reference_from_chat',
|
||||
documentPaths: ['/Shared Documents/file1.docx', '/Documents/file2.pdf'],
|
||||
fileNames: ['uploaded_file1.docx', 'uploaded_file2.pdf'],
|
||||
includeMetadata: true,
|
||||
includeSubfolders: false // Default to false for better navigation UX
|
||||
});
|
||||
const onTestConnection = async (connectionId: string) => {
|
||||
await handleTestConnection(connectionId);
|
||||
};
|
||||
|
||||
// Load connections and examples on mount
|
||||
useEffect(() => {
|
||||
loadConnections();
|
||||
loadExamples();
|
||||
}, []);
|
||||
const onListDocuments = async () => {
|
||||
console.log('onListDocuments called with:', { siteUrl, folderPaths });
|
||||
await handleListDocuments(siteUrl, folderPaths);
|
||||
// Force table refresh to show new data
|
||||
const newKey = tableRefreshKey + 1;
|
||||
console.log('Setting tableRefreshKey to:', newKey);
|
||||
setTableRefreshKey(newKey);
|
||||
};
|
||||
|
||||
// Update connection reference when selected connection changes
|
||||
useEffect(() => {
|
||||
setFormData(prev => ({ ...prev, connectionReference: selectedConnection }));
|
||||
}, [selectedConnection]);
|
||||
const onDiscoverSites = async () => {
|
||||
await handleDiscoverSites();
|
||||
};
|
||||
|
||||
const loadConnections = async () => {
|
||||
try {
|
||||
const conns = await getConnections();
|
||||
setConnections(conns);
|
||||
if (conns.length > 0) {
|
||||
setSelectedConnection(conns[0].id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load connections:', error);
|
||||
}
|
||||
};
|
||||
const onSelectSite = (selectedSiteUrl: string) => {
|
||||
setSiteUrl(selectedSiteUrl);
|
||||
handleSelectSite(selectedSiteUrl);
|
||||
};
|
||||
|
||||
const loadExamples = async () => {
|
||||
try {
|
||||
const exampleData = await getExamples();
|
||||
setExamples(exampleData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load examples:', error);
|
||||
}
|
||||
};
|
||||
const onRowClick = (row: any) => {
|
||||
console.log('Row clicked:', row);
|
||||
if (row.type === 'folder') {
|
||||
const currentPath = folderPaths[0] || '/';
|
||||
const newPath = handleFolderNavigation(row, currentPath);
|
||||
console.log('Navigating from', currentPath, 'to', newPath);
|
||||
setFolderPaths([newPath]);
|
||||
// Automatically refresh the document list with the new path
|
||||
handleListDocuments(siteUrl, [newPath]);
|
||||
setTableRefreshKey(prev => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestConnection = async (connectionId: string) => {
|
||||
try {
|
||||
const result = await testConnection(connectionId);
|
||||
setTestResults((prev: any) => ({ ...prev, [connectionId]: result }));
|
||||
} catch (error) {
|
||||
console.error('Connection test failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDebugTokens = async () => {
|
||||
try {
|
||||
const result = await debugTokens();
|
||||
setTokenDebugInfo(result);
|
||||
} catch (error) {
|
||||
console.error('Token debug failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDebugTokenDetails = async () => {
|
||||
try {
|
||||
const result = await debugTokenDetails();
|
||||
setTokenDebugInfo(result);
|
||||
} catch (error) {
|
||||
console.error('Token details debug failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDiscoverSites = async () => {
|
||||
try {
|
||||
const result = await discoverSites();
|
||||
if (result.success && result.data.sites) {
|
||||
setDiscoveredSites(result.data.sites);
|
||||
setSitesDiscovered(true);
|
||||
} else {
|
||||
console.error('Site discovery failed:', result.message);
|
||||
setDiscoveredSites([]);
|
||||
setSitesDiscovered(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Site discovery failed:', error);
|
||||
setDiscoveredSites([]);
|
||||
setSitesDiscovered(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectSite = (siteUrl: string) => {
|
||||
setFormData(prev => ({ ...prev, siteUrl: siteUrl }));
|
||||
};
|
||||
|
||||
const handleFormChange = (field: keyof FormData, value: any) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleArrayInputChange = (field: 'folderPaths' | 'documentPaths' | 'fileNames', value: string) => {
|
||||
const array = value.split('\n').filter(item => item.trim() !== '');
|
||||
setFormData(prev => ({ ...prev, [field]: array }));
|
||||
};
|
||||
|
||||
const loadExample = (operation: TestOperation) => {
|
||||
const exampleKey = operation === 'list' ? 'listDocuments' :
|
||||
operation === 'find' ? 'findDocuments' :
|
||||
operation === 'read' ? 'readDocuments' : 'uploadDocuments';
|
||||
|
||||
if (examples[exampleKey]) {
|
||||
const example = examples[exampleKey];
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
connectionReference: selectedConnection || prev.connectionReference,
|
||||
siteUrl: example.siteUrl || prev.siteUrl,
|
||||
folderPaths: example.folderPaths || prev.folderPaths,
|
||||
query: example.query || prev.query,
|
||||
searchScope: example.searchScope || prev.searchScope,
|
||||
documentList: example.documentList || prev.documentList,
|
||||
documentPaths: example.documentPaths || prev.documentPaths,
|
||||
fileNames: example.fileNames || prev.fileNames,
|
||||
includeMetadata: example.includeMetadata !== undefined ? example.includeMetadata : prev.includeMetadata,
|
||||
includeSubfolders: example.includeSubfolders !== undefined ? example.includeSubfolders : prev.includeSubfolders
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const executeTest = async () => {
|
||||
if (!selectedConnection) {
|
||||
alert('Please select a connection first');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the selected connection to build proper reference
|
||||
const selectedConn = connections.find(conn => conn.id === selectedConnection);
|
||||
if (!selectedConn) {
|
||||
alert('Selected connection not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build connection reference in expected format: connection:{authority}:{username}:{id}
|
||||
const connectionReference = `connection:${selectedConn.authority}:${selectedConn.externalUsername}:${selectedConn.id}`;
|
||||
|
||||
try {
|
||||
let result: SharePointResponse;
|
||||
|
||||
switch (activeTab) {
|
||||
case 'list':
|
||||
const listRequest: SharePointListRequest = {
|
||||
connectionReference: connectionReference,
|
||||
siteUrl: formData.siteUrl,
|
||||
folderPaths: formData.folderPaths,
|
||||
includeSubfolders: formData.includeSubfolders,
|
||||
expectedDocumentFormats: [{ extension: '.json', mimeType: 'application/json' }]
|
||||
};
|
||||
result = await listDocuments(listRequest);
|
||||
break;
|
||||
|
||||
case 'find':
|
||||
const findRequest: SharePointFindRequest = {
|
||||
connectionReference: connectionReference,
|
||||
siteUrl: formData.siteUrl,
|
||||
query: formData.query,
|
||||
searchScope: formData.searchScope,
|
||||
expectedDocumentFormats: [{ extension: '.json', mimeType: 'application/json' }]
|
||||
};
|
||||
result = await findDocuments(findRequest);
|
||||
break;
|
||||
|
||||
case 'read':
|
||||
const readRequest: SharePointReadRequest = {
|
||||
documentList: formData.documentList,
|
||||
connectionReference: connectionReference,
|
||||
siteUrl: formData.siteUrl,
|
||||
documentPaths: formData.documentPaths,
|
||||
includeMetadata: formData.includeMetadata,
|
||||
expectedDocumentFormats: [{ extension: '.json', mimeType: 'application/json' }]
|
||||
};
|
||||
result = await readDocuments(readRequest);
|
||||
break;
|
||||
|
||||
case 'upload':
|
||||
const uploadRequest: SharePointUploadRequest = {
|
||||
connectionReference: connectionReference,
|
||||
siteUrl: formData.siteUrl,
|
||||
documentPaths: formData.folderPaths,
|
||||
documentList: formData.documentList,
|
||||
fileNames: formData.fileNames,
|
||||
expectedDocumentFormats: [{ extension: '.json', mimeType: 'application/json' }]
|
||||
};
|
||||
result = await uploadDocuments(uploadRequest);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Invalid operation');
|
||||
}
|
||||
|
||||
console.log('Test result:', result);
|
||||
} catch (error) {
|
||||
console.error('Test execution failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const renderConnectionCard = (connection: SharePointConnection) => {
|
||||
const testResult = testResults[connection.id];
|
||||
const renderConnectionCard = (connection: any) => {
|
||||
const testResult = connectionTestResults[connection.id];
|
||||
const isTestingThis = testingConnections.has(connection.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={connection.id}
|
||||
className={`${styles.connectionCard} ${selectedConnection === connection.id ? styles.active : ''}`}
|
||||
onClick={() => setSelectedConnection(connection.id)}
|
||||
className={`${styles.connectionCard} ${selectedConnection?.id === connection.id ? styles.active : ''}`}
|
||||
onClick={() => handleSelectConnection(connection.id)}
|
||||
>
|
||||
<div className={styles.connectionInfo}>
|
||||
<div className={styles.connectionName}>
|
||||
|
|
@ -283,11 +100,12 @@ function TestSharepoint() {
|
|||
className={styles.button}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleTestConnection(connection.id);
|
||||
onTestConnection(connection.id);
|
||||
}}
|
||||
disabled={isLoading}
|
||||
disabled={isTestingThis}
|
||||
aria-label={t('sharepoint.button.testConnection')}
|
||||
>
|
||||
Test
|
||||
{isTestingThis ? '⏳' : t('sharepoint.button.testConnection')}
|
||||
</button>
|
||||
{testResult && (
|
||||
<span className={testResult.success ? styles.successMessage : styles.errorMessage}>
|
||||
|
|
@ -299,57 +117,192 @@ function TestSharepoint() {
|
|||
);
|
||||
};
|
||||
|
||||
const renderTestForm = () => {
|
||||
return (
|
||||
<div className={styles.requestPanel}>
|
||||
<div className={styles.panelTitle}>Request Configuration</div>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Connection</label>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={selectedConnection}
|
||||
onChange={(e) => setSelectedConnection(e.target.value)}
|
||||
>
|
||||
<option value="">Select a connection</option>
|
||||
{connections.map(conn => (
|
||||
<option key={conn.id} value={conn.id}>
|
||||
{conn.externalUsername || conn.id} ({conn.status})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className={sharedStyles.pageContainer}>
|
||||
<div className={sharedStyles.contentWrapper}>
|
||||
<div className={sharedStyles.pageCard}>
|
||||
<div className={sharedStyles.pageHeader}>
|
||||
<h1 className={sharedStyles.pageTitle}>{t('sharepoint.title')}</h1>
|
||||
<div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
|
||||
<button
|
||||
className={sharedStyles.primaryButton}
|
||||
onClick={refetchConnections}
|
||||
disabled={connectionLoading}
|
||||
aria-label="Refresh connections"
|
||||
>
|
||||
<span className={sharedStyles.buttonIcon}><IoIosRefresh /></span>
|
||||
{connectionLoading ? 'Loading...' : 'Refresh Connections'}
|
||||
</button>
|
||||
<button
|
||||
className={sharedStyles.secondaryButton}
|
||||
onClick={handleDebugTokens}
|
||||
disabled={connectionLoading}
|
||||
aria-label="Debug Token Info"
|
||||
>
|
||||
🔍 Debug Token
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={sharedStyles.horizontalDivider}></div>
|
||||
|
||||
{/* Connections Section */}
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>
|
||||
{t('sharepoint.connections.title')} ({connections.length})
|
||||
</h2>
|
||||
|
||||
{connectionError && (
|
||||
<div className={styles.errorMessage}>
|
||||
{connectionError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{connections.length === 0 ? (
|
||||
<div className={styles.loading}>
|
||||
{connectionLoading ?
|
||||
t('sharepoint.connections.loading') :
|
||||
t('sharepoint.connections.noConnections')
|
||||
}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.connectionsGrid}>
|
||||
{connections.map(renderConnectionCard)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Token Debug Section */}
|
||||
{tokenDebugInfo && (
|
||||
<>
|
||||
<div className={sharedStyles.horizontalDivider}></div>
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>🔍 Token Debug Information</h2>
|
||||
|
||||
<div style={{ background: 'var(--color-bg-secondary, #f8f9fa)', padding: '15px', borderRadius: '8px', border: '1px solid var(--color-border, #dee2e6)' }}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<strong>User ID:</strong> {tokenDebugInfo.data?.userId || 'Unknown'}
|
||||
</div>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<strong>All Tokens Count:</strong> {tokenDebugInfo.data?.allTokensCount || 0}
|
||||
</div>
|
||||
|
||||
{tokenDebugInfo.data?.allTokens && tokenDebugInfo.data.allTokens.length > 0 && (
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<strong>Microsoft Tokens:</strong>
|
||||
{tokenDebugInfo.data.allTokens.map((token: any, index: number) => (
|
||||
<div key={index} style={{ marginLeft: '15px', marginTop: '8px', padding: '8px', background: token.isExpired ? 'var(--color-error-bg, #ffe6e6)' : 'var(--color-success-bg, #e6ffe6)', borderRadius: '4px' }}>
|
||||
<div><strong>Token ID:</strong> {token.id}</div>
|
||||
<div><strong>Authority:</strong> {token.authority}</div>
|
||||
<div><strong>Expires At:</strong> {new Date(token.expiresAt * 1000).toLocaleString()}</div>
|
||||
<div><strong>Is Expired:</strong> {token.isExpired ? '❌ YES' : '✅ NO'}</div>
|
||||
<div><strong>Has Access Token:</strong> {token.hasAccessToken ? '✅ YES' : '❌ NO'}</div>
|
||||
<div><strong>Has Refresh Token:</strong> {token.hasRefreshToken ? '✅ YES' : '❌ NO'}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tokenDebugInfo.data?.sharepointMethodToken && (
|
||||
<div style={{ marginTop: '15px', padding: '10px', background: 'var(--color-warning-bg, #fff3cd)', borderRadius: '4px' }}>
|
||||
<strong>SharePoint Method Token Status:</strong>
|
||||
<div style={{ marginLeft: '15px', marginTop: '5px' }}>
|
||||
{tokenDebugInfo.data.sharepointMethodToken.tokenFound ? (
|
||||
<div>
|
||||
<div>✅ Token Found</div>
|
||||
<div><strong>Token ID:</strong> {tokenDebugInfo.data.sharepointMethodToken.tokenId}</div>
|
||||
<div><strong>Expires At:</strong> {new Date(tokenDebugInfo.data.sharepointMethodToken.expiresAt * 1000).toLocaleString()}</div>
|
||||
<div><strong>Is Expired:</strong> {tokenDebugInfo.data.sharepointMethodToken.isExpired ? '❌ YES' : '✅ NO'}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>❌ No Token Found: {tokenDebugInfo.data.sharepointMethodToken.reason || tokenDebugInfo.data.sharepointMethodToken.error}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Provide action recommendations */}
|
||||
<div style={{ marginTop: '15px', padding: '12px', background: 'var(--color-info-bg, #e3f2fd)', borderRadius: '4px', border: '1px solid var(--color-info, #2196f3)' }}>
|
||||
<strong>💡 Recommendation:</strong>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
{tokenDebugInfo.data?.allTokens?.some((token: any) => token.isExpired) ? (
|
||||
<div>
|
||||
🔄 <strong>Your tokens are stale!</strong> The tokens weren't properly cleared. Use the button below to force cleanup:
|
||||
<div style={{ marginTop: '12px' }}>
|
||||
<button
|
||||
onClick={handleCleanupTokens}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
background: 'var(--color-error, #d32f2f)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
🗑️ Force Delete All Tokens
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ marginTop: '8px', fontSize: '12px' }}>
|
||||
After cleanup: Go to Connections page → Reconnect Microsoft account
|
||||
</div>
|
||||
</div>
|
||||
) : !tokenDebugInfo.data?.allTokens?.some((token: any) => token.hasAccessToken) ? (
|
||||
<div>
|
||||
⚠️ <strong>No valid access tokens found.</strong> Please reconnect your Microsoft account in the Connections page.
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
✅ <strong>Tokens look valid.</strong> The issue might be with SharePoint permissions or scopes.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* SharePoint Configuration Section */}
|
||||
{selectedConnection && (
|
||||
<>
|
||||
<div className={sharedStyles.horizontalDivider}></div>
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>SharePoint Configuration</h2>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>SharePoint Site URL</label>
|
||||
<label className={styles.label}>{t('sharepoint.form.siteUrl')}</label>
|
||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center', marginBottom: '10px' }}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={formData.siteUrl}
|
||||
onChange={(e) => handleFormChange('siteUrl', e.target.value)}
|
||||
value={siteUrl}
|
||||
onChange={(e) => setSiteUrl(e.target.value)}
|
||||
placeholder="https://your-tenant.sharepoint.com/sites/your-site"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<button
|
||||
className={`${styles.button} ${styles.exampleButton}`}
|
||||
onClick={handleDiscoverSites}
|
||||
disabled={isLoading}
|
||||
className={sharedStyles.secondaryButton}
|
||||
onClick={onDiscoverSites}
|
||||
disabled={connectionLoading}
|
||||
type="button"
|
||||
>
|
||||
Discover Sites
|
||||
<span className={sharedStyles.buttonIcon}><IoIosLink /></span>
|
||||
{t('sharepoint.button.discoverSites')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{sitesDiscovered && discoveredSites.length > 0 && (
|
||||
<div className={styles.sitesDiscovery}>
|
||||
<label className={styles.label}>Discovered Sites ({discoveredSites.length}):</label>
|
||||
<label className={styles.label}>
|
||||
{t('sharepoint.sites.discovered')} ({discoveredSites.length}):
|
||||
</label>
|
||||
<div className={styles.sitesList}>
|
||||
{discoveredSites.map((site, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`${styles.siteItem} ${formData.siteUrl === site.url ? styles.selectedSite : ''}`}
|
||||
onClick={() => handleSelectSite(site.url)}
|
||||
className={`${styles.siteItem} ${siteUrl === site.url ? styles.selectedSite : ''}`}
|
||||
onClick={() => onSelectSite(site.url)}
|
||||
>
|
||||
<div className={styles.siteName}>
|
||||
<strong>{site.name}</strong>
|
||||
|
|
@ -367,296 +320,108 @@ function TestSharepoint() {
|
|||
|
||||
{sitesDiscovered && discoveredSites.length === 0 && (
|
||||
<div className={styles.noSitesFound}>
|
||||
No SharePoint sites found. You may need additional permissions or the sites may not be accessible.
|
||||
<strong>{t('sharepoint.sites.noSites')}</strong>
|
||||
{documentsError && (
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<div style={{ fontSize: '12px', color: 'var(--color-error, #d32f2f)', marginBottom: '8px' }}>
|
||||
<strong>Technical details:</strong> {documentsError}
|
||||
</div>
|
||||
{documentsError.includes('401') || documentsError.includes('InvalidAuthenticationToken') ? (
|
||||
<div style={{ fontSize: '13px', color: 'var(--color-text)', background: 'var(--color-warning-bg, #fff3cd)', padding: '8px', borderRadius: '4px', border: '1px solid var(--color-warning, #ffc107)' }}>
|
||||
<div>⚠️ {t('sharepoint.sites.authError')}</div>
|
||||
<div style={{ marginTop: '4px' }}>{t('sharepoint.sites.retryConnection')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: '12px', color: 'var(--color-text-secondary)' }}>
|
||||
Please check your Microsoft connection and permissions.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{activeTab === 'list' && (
|
||||
<>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Folder Paths (one per line)</label>
|
||||
<label className={styles.label}>{t('sharepoint.form.folderPaths')}</label>
|
||||
<textarea
|
||||
className={styles.textarea}
|
||||
value={formData.folderPaths.join('\n')}
|
||||
onChange={(e) => handleArrayInputChange('folderPaths', e.target.value)}
|
||||
placeholder="/Shared Documents /Documents"
|
||||
value={folderPaths.join('\n')}
|
||||
onChange={(e) => setFolderPaths(e.target.value.split('\n').filter(path => path.trim()))}
|
||||
placeholder="/ /Documents /Sites/YourSite/Shared Documents"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.includeSubfolders}
|
||||
onChange={(e) => handleFormChange('includeSubfolders', e.target.checked)}
|
||||
/>
|
||||
Include Subfolders
|
||||
</label>
|
||||
|
||||
<div className={styles.buttonGroup}>
|
||||
<button
|
||||
className={sharedStyles.primaryButton}
|
||||
onClick={onListDocuments}
|
||||
disabled={connectionLoading || !selectedConnection}
|
||||
>
|
||||
{t('sharepoint.button.listDocuments')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'find' && (
|
||||
<>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Search Query</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={formData.query}
|
||||
onChange={(e) => handleFormChange('query', e.target.value)}
|
||||
placeholder="quarterly report 2024"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Search Scope</label>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={formData.searchScope}
|
||||
onChange={(e) => handleFormChange('searchScope', e.target.value)}
|
||||
>
|
||||
<option value="all">All</option>
|
||||
<option value="documents">Documents Only</option>
|
||||
<option value="pages">Pages Only</option>
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'read' && (
|
||||
<>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Document List Reference</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={formData.documentList}
|
||||
onChange={(e) => handleFormChange('documentList', e.target.value)}
|
||||
placeholder="document_list_reference_from_chat"
|
||||
/>
|
||||
<div className={styles.helpText}>
|
||||
This should be a reference from a chat session or document management system
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Document Paths (one per line)</label>
|
||||
<textarea
|
||||
className={styles.textarea}
|
||||
value={formData.documentPaths.join('\n')}
|
||||
onChange={(e) => handleArrayInputChange('documentPaths', e.target.value)}
|
||||
placeholder="/Shared Documents/file1.docx /Documents/file2.pdf"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.includeMetadata}
|
||||
onChange={(e) => handleFormChange('includeMetadata', e.target.checked)}
|
||||
/>
|
||||
Include Metadata
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'upload' && (
|
||||
<>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Document List Reference</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={formData.documentList}
|
||||
onChange={(e) => handleFormChange('documentList', e.target.value)}
|
||||
placeholder="document_list_reference_from_chat"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>Upload Destination Paths (one per line)</label>
|
||||
<textarea
|
||||
className={styles.textarea}
|
||||
value={formData.folderPaths.join('\n')}
|
||||
onChange={(e) => handleArrayInputChange('folderPaths', e.target.value)}
|
||||
placeholder="/Shared Documents/ /Documents/"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.label}>File Names (one per line)</label>
|
||||
<textarea
|
||||
className={styles.textarea}
|
||||
value={formData.fileNames.join('\n')}
|
||||
onChange={(e) => handleArrayInputChange('fileNames', e.target.value)}
|
||||
placeholder="uploaded_file1.docx uploaded_file2.pdf"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={styles.buttonGroup}>
|
||||
<button
|
||||
className={`${styles.button} ${styles.primary}`}
|
||||
onClick={executeTest}
|
||||
disabled={isLoading || !selectedConnection}
|
||||
>
|
||||
{isLoading ? 'Testing...' : `Test ${activeTab.charAt(0).toUpperCase() + activeTab.slice(1)}`}
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.button} ${styles.exampleButton}`}
|
||||
onClick={() => loadExample(activeTab)}
|
||||
>
|
||||
Load Example
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.requestPreview}>
|
||||
<strong>Request Preview:</strong>
|
||||
{JSON.stringify({
|
||||
endpoint: `/api/test-sharepoint/${activeTab === 'list' ? 'list-documents' :
|
||||
activeTab === 'find' ? 'find-documents' :
|
||||
activeTab === 'read' ? 'read-documents' : 'upload-documents'}`,
|
||||
method: 'POST',
|
||||
body: activeTab === 'list' ? {
|
||||
connectionReference: selectedConnection ?
|
||||
`connection:${connections.find(c => c.id === selectedConnection)?.authority}:${connections.find(c => c.id === selectedConnection)?.externalUsername}:${selectedConnection}` :
|
||||
'No connection selected',
|
||||
siteUrl: formData.siteUrl,
|
||||
folderPaths: formData.folderPaths,
|
||||
includeSubfolders: formData.includeSubfolders
|
||||
} : activeTab === 'find' ? {
|
||||
connectionReference: selectedConnection ?
|
||||
`connection:${connections.find(c => c.id === selectedConnection)?.authority}:${connections.find(c => c.id === selectedConnection)?.externalUsername}:${selectedConnection}` :
|
||||
'No connection selected',
|
||||
siteUrl: formData.siteUrl,
|
||||
query: formData.query,
|
||||
searchScope: formData.searchScope
|
||||
} : activeTab === 'read' ? {
|
||||
documentList: formData.documentList,
|
||||
connectionReference: selectedConnection ?
|
||||
`connection:${connections.find(c => c.id === selectedConnection)?.authority}:${connections.find(c => c.id === selectedConnection)?.externalUsername}:${selectedConnection}` :
|
||||
'No connection selected',
|
||||
siteUrl: formData.siteUrl,
|
||||
documentPaths: formData.documentPaths,
|
||||
includeMetadata: formData.includeMetadata
|
||||
} : {
|
||||
connectionReference: selectedConnection ?
|
||||
`connection:${connections.find(c => c.id === selectedConnection)?.authority}:${connections.find(c => c.id === selectedConnection)?.externalUsername}:${selectedConnection}` :
|
||||
'No connection selected',
|
||||
siteUrl: formData.siteUrl,
|
||||
documentPaths: formData.folderPaths,
|
||||
documentList: formData.documentList,
|
||||
fileNames: formData.fileNames
|
||||
}
|
||||
}, null, 2)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderResponse = () => {
|
||||
return (
|
||||
<div className={styles.responsePanel}>
|
||||
<div className={styles.panelTitle}>Response</div>
|
||||
|
||||
{error && (
|
||||
<div className={styles.errorMessage}>
|
||||
Error: {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lastResponse && (
|
||||
<div className={`${styles.responseArea} ${lastResponse.success ? styles.success : styles.error}`}>
|
||||
{JSON.stringify(lastResponse, null, 2)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!lastResponse && !error && (
|
||||
<div className={styles.responseArea}>
|
||||
No response yet. Execute a test to see results here.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>SharePoint Method Testing</h1>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>
|
||||
Microsoft Connections ({connections.length})
|
||||
</h2>
|
||||
|
||||
<div className={styles.buttonGroup} style={{ marginBottom: '15px' }}>
|
||||
<button
|
||||
className={`${styles.button} ${styles.exampleButton}`}
|
||||
onClick={handleDebugTokens}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Debug Authentication Tokens
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.button} ${styles.exampleButton}`}
|
||||
onClick={handleDebugTokenDetails}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Debug Token Details
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{tokenDebugInfo && (
|
||||
<div className={styles.responseArea} style={{ marginBottom: '15px' }}>
|
||||
<strong>Token Debug Info:</strong>
|
||||
<pre>{JSON.stringify(tokenDebugInfo, null, 2)}</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{connections.length === 0 ? (
|
||||
<div className={styles.loading}>
|
||||
{isLoading ? 'Loading connections...' : 'No Microsoft connections found. Please create a connection first.'}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.connectionsGrid}>
|
||||
{connections.map(renderConnectionCard)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>SharePoint Operations Testing</h2>
|
||||
|
||||
<div className={styles.tabs}>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'list' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('list')}
|
||||
>
|
||||
List Documents
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'find' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('find')}
|
||||
>
|
||||
Find Documents
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'read' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('read')}
|
||||
>
|
||||
Read Documents
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'upload' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('upload')}
|
||||
>
|
||||
Upload Documents
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.tabContent}>
|
||||
<div className={styles.testArea}>
|
||||
{renderTestForm()}
|
||||
{renderResponse()}
|
||||
{/* Documents Table Section */}
|
||||
<div className={sharedStyles.horizontalDivider}></div>
|
||||
|
||||
{/* Breadcrumb Navigation */}
|
||||
<div className={styles.section}>
|
||||
<div className={styles.breadcrumb}>
|
||||
<span className={styles.breadcrumbLabel}>Current Path:</span>
|
||||
{folderPaths[0] === '/' ? (
|
||||
<span className={styles.breadcrumbItem}>📁 Root</span>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
className={styles.breadcrumbItem + ' ' + styles.breadcrumbClickable}
|
||||
onClick={() => {
|
||||
setFolderPaths(['/']);
|
||||
handleListDocuments(siteUrl, ['/']);
|
||||
setTableRefreshKey(prev => prev + 1);
|
||||
}}
|
||||
>
|
||||
📁 Root
|
||||
</span>
|
||||
{folderPaths[0].split('/').filter(Boolean).map((part, index, array) => {
|
||||
const pathToHere = '/' + array.slice(0, index + 1).join('/');
|
||||
const isLast = index === array.length - 1;
|
||||
return (
|
||||
<span key={index}>
|
||||
<span className={styles.breadcrumbSeparator}>/</span>
|
||||
<span
|
||||
className={styles.breadcrumbItem + (isLast ? '' : ' ' + styles.breadcrumbClickable)}
|
||||
onClick={!isLast ? () => {
|
||||
setFolderPaths([pathToHere]);
|
||||
handleListDocuments(siteUrl, [pathToHere]);
|
||||
setTableRefreshKey(prev => prev + 1);
|
||||
} : undefined}
|
||||
>
|
||||
📁 {part}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={sharedStyles.contentArea}>
|
||||
<TestSharepointTable
|
||||
key={tableRefreshKey}
|
||||
className={styles.sharepointTableContainer}
|
||||
documents={documents}
|
||||
documentsLoading={documentsLoading}
|
||||
documentsError={documentsError}
|
||||
columns={columns}
|
||||
actions={actions}
|
||||
onRowClick={onRowClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue