diff --git a/CONTRIBUTOR.md b/CONTRIBUTOR.md index 4d0cb0c..41c303f 100644 --- a/CONTRIBUTOR.md +++ b/CONTRIBUTOR.md @@ -54,7 +54,7 @@ neo-ui-framework/ ├── public/ # Static assets ├── Dockerfile # Production container image ├── docker-compose.yml # Development orchestration -├── nginx.conf # nginx configuration template +├── Caddyfile # Caddy reverse proxy configuration ├── entrypoint.sh # Container startup script └── vite.config.ts # Vite configuration ``` diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..1d69c34 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,39 @@ +:80 { + # Reverse proxy API requests to backend + # The NEO_API env var is resolved dynamically by Caddy + handle /api/* { + uri strip_prefix /api + reverse_proxy {$NEO_API} { + header_up Host {host} + header_up X-Real-IP {remote_host} + flush_interval -1 + } + } + + # Serve static UI files (catch-all, mutually exclusive with /api/*) + handle { + root * /srv + try_files {path} /index.html + file_server + } + + # Cache static assets (JS, CSS, images, fonts) + @static path *.js *.css *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot + header @static Cache-Control "public, immutable, max-age=31536000" + + # Prevent caching of HTML pages so auth state is always checked on refresh + @html { + not path *.js *.css *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot + not path /api/* + } + header @html Cache-Control "no-store, no-cache, must-revalidate" + + # Security headers + header X-Frame-Options "SAMEORIGIN" + header X-Content-Type-Options "nosniff" + header X-XSS-Protection "1; mode=block" + header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https://api.github.com https://netapp.github.io;" + + # Gzip compression + encode gzip +} diff --git a/Dockerfile b/Dockerfile index b0f9ae2..3f9220d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,15 @@ -FROM nginx:1.27-alpine +FROM docker.io/library/caddy:2-alpine -# Remove default nginx config -RUN rm -f /etc/nginx/conf.d/default.conf - -# Copy template (for Docker usage) -COPY nginx.conf /etc/nginx/conf.d/default.conf.template +# Copy Caddyfile +COPY Caddyfile /etc/caddy/Caddyfile # Copy entrypoint script COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh # Copy built application -COPY dist /usr/share/nginx/html - -# Make config directory writable -RUN chown -R nginx:nginx /etc/nginx/conf.d/ && \ - chmod -R 755 /etc/nginx/conf.d/ && \ - chown -R nginx:nginx /usr/share/nginx/html +COPY dist /srv EXPOSE 80 -# Run as root to allow config file creation -USER root - ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/QUICKSTART.md b/QUICKSTART.md index 82545aa..c3511e6 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -22,7 +22,7 @@ For containerized deployments, configure the Neo API endpoint: docker run -e NEO_API=http://neo-backend:8080 neo-ui-framework ``` -The `entrypoint.sh` script injects this value into nginx configuration at runtime. +The `entrypoint.sh` script exports this value for Caddy to resolve at runtime. ## From source diff --git a/README.md b/README.md index 6138fb8..bc74500 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Neo UI Framework delivers a full-featured interface for managing NetApp Neo - th - **Complete API Coverage** - Full integration with Neo API including authentication, health monitoring, license management, user administration, share management, file operations, and activity logging - **Modern Tech Stack** - Built with React 19, TypeScript 5.9, Vite 7, and Tailwind CSS 4 -- **Production Ready** - Containerized deployment with nginx, Docker Compose support, and environment-based configuration +- **Production Ready** - Containerized deployment with Caddy, Docker Compose support, and environment-based configuration - **White-Label Friendly** - Easily customize branding, themes, and navigation to match your organization - **Type-Safe** - Comprehensive TypeScript coverage with strict compiler options - **Responsive Design** - Mobile-first approach with adaptive layouts and sidebar navigation diff --git a/SECURITY.md b/SECURITY.md index b0ae5a4..50ac267 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -31,3 +31,5 @@ If we verify a reported security vulnerability, our policy is: - A security advisory will be released on the project GitHub repository detailing the vulnerability, as well as recommendations for end-users to protect themselves. + +- We will work with the reporter to ensure they are credited in the security advisory. diff --git a/SUPPORT.md b/SUPPORT.md index 1c5bd49..5c1829a 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -8,8 +8,9 @@ The Neo UI framework supports the Neo backend API versions: | Version | Supported | | ------- | ------------------ | -| main | ✅ Yes | -| 2.x.x | ✅ Yes | +| 4.x.x | ✅ Yes | +| 3.x.x | ✅ Yes | +| 2.x.x | ❌ No | | < 2.0 | ❌ No | ## Reporting Bugs diff --git a/caching-specs.md b/caching-specs.md new file mode 100644 index 0000000..477cf11 --- /dev/null +++ b/caching-specs.md @@ -0,0 +1,48 @@ +# Caching Mechanism Analysis + +## Executive Summary +The `neo-ui-framework` implements a client-side caching mechanism using an in-memory `DataLoader` service. This service caches API responses based on unique keys derived from the request parameters (token, share ID, page number, etc.). The caching strategy for the data corpus (Files) is primarily **on-demand (lazy loading)** and **page-based**, rather than pre-fetching the entire dataset. + +## Detailed Analysis + +### 1. Does the interface pre-cache the full pagination list for the data corpus at loading time? +**Answer: No.** + +* **Mechanism**: The application does **not** fetch the list of all files at application startup or when the session is restored. +* **Evidence**: + * The `fetchSystemData` function in `NeoApiService` (used during initialization) explicitly returns `files: null` and does not invoke the file search/list endpoints. + * File data is only requested when `handleSelectFilesShare` is triggered, typically via user interaction (selecting a share or the "All shares" view). + * Even when triggered, the request is specific to a page (defaulting to page 1) and a page size (default 100). There is no loop or background process to iterate through all pages and cache them. + +### 2. Does the interface cache the full pagination list when navigating with or without content? +**Answer: No, it strictly caches individual pages.** + +* **Mechanism**: The interface caches the *results of specific pagination requests*. It does not cache the "full" list of all files unless the user manually navigates to every single page. +* **Behavior**: + * When a user visits Page 1, the response for Page 1 is cached. + * When the user navigates to Page 2, a new request is made and cached. + * If the user navigates back to Page 1, the cached response is used (provided it hasn't expired or been evicted). + * The `DataLoader` key for file lists includes the page number `page` and `pageSize`, ensuring unique cache entries for each slice of data (e.g., `files:token:shareId:1:100`). + +### 3. Does the caching for the pagination list include the content? +**Answer: No.** + +* **Mechanism**: The pagination list endpoints (`/files` or `/search/files`) return an array of `FileEntry` objects. +* **Data Structure**: + * The `FileEntry` interface contains metadata such as filename, path, size, file type, and timestamps. + * It does **not** contain the actual file content (text or bytes). +* **Content Retrieval**: File content is only fetched when specifically requested via `getFileMetadata` (which returns `FileMetadataResponse`), and this is a separate cache entry (`fileMetadata:token:shareId:fileId`). + +## Technical Details + +### DataLoader Service +* **Implementation**: `src/services/data-loader.ts` +* **Strategy**: In-memory LRU (Least Recently Used) cache. +* **TTL (Time To Live)**: Defaults to 30 seconds for general data, but `filesTtl` (configurable, default 10 minutes) is used for file-related requests. +* **Eviction**: Based on entry size and a maximum cache size limit (default 100MB). + +### API Models +* **File List**: Returns `FilesResponse { files: FileEntry[], ... }`. + * `FileEntry` fields: `id`, `file_path`, `filename`, `size`, `created_at`, `modified_time`, `accessed_at`, `is_directory`, `file_type`. +* **File Content**: Returns `FileMetadataResponse`. + * Fields: `content`, `content_chunks`, plus metadata. diff --git a/docker-compose.yml b/docker-compose.yml index 0351f89..dd2ba52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: restart: unless-stopped netapp-neo: - image: ghcr.io/netapp/netapp-copilot-connector:2.2.6 + image: ghcr.io/netapp/netapp-copilot-connector:3.2.4 user: "1000:1000" cap_add: - SYS_ADMIN @@ -33,23 +33,13 @@ services: env_file: - .env environment: - - UID=8000 - - GID=8000 - PORT=8080 - - PYTHONUNBUFFERED=1 - - DB_PATH=data/database.db - - MS_GRAPH_CLIENT_ID=${MS_GRAPH_CLIENT_ID} - - MS_GRAPH_CLIENT_SECRET=${MS_GRAPH_CLIENT_SECRET} - - MS_GRAPH_TENANT_ID=${MS_GRAPH_TENANT_ID} - - MS_GRAPH_CONNECTOR_ID=${MS_GRAPH_CONNECTOR_ID:-netappcopilot} - - MS_GRAPH_CONNECTOR_NAME=${MS_GRAPH_CONNECTOR_NAME:-"NetApp Connector"} - - NETAPP_CONNECTOR_LICENSE=${NETAPP_CONNECTOR_LICENSE} volumes: - neo-226:/app/data restart: unless-stopped netapp-neo-ui: - image: ghcr.io/netapp/neo-ui-framework:2.2.6 + image: ghcr.io/netapp/neo-ui-framework:3.2.2 ports: - "8080:80" environment: diff --git a/entrypoint.sh b/entrypoint.sh index 962833f..8dea2d7 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -4,46 +4,14 @@ set -e NEO_API=${NEO_API:-http://localhost:8080} export NEO_API -echo "Configuring nginx to proxy API requests to: $NEO_API" +echo "Configuring Caddy to proxy API requests to: $NEO_API" -# Check if we're running in Kubernetes with a pre-configured ConfigMap -# If /etc/nginx/conf.d/default.conf exists and is not a template, use it as-is -if [ -f /etc/nginx/conf.d/default.conf ] && ! grep -q '\${NEO_API}' /etc/nginx/conf.d/default.conf 2>/dev/null; then - echo "Found pre-configured nginx config (likely from ConfigMap), skipping template substitution" - echo "Current proxy_pass configuration:" - grep proxy_pass /etc/nginx/conf.d/default.conf || echo "No proxy_pass found" -else - echo "Generating nginx config from template" - - # Check if template exists - if [ ! -f /etc/nginx/conf.d/default.conf.template ]; then - echo "Error: Template file not found at /etc/nginx/conf.d/default.conf.template" - exit 1 - fi - - # Create temp file in writable directory first - envsubst '$NEO_API' < /etc/nginx/conf.d/default.conf.template > /tmp/default.conf - - # Copy to final location with error handling - if cp -f /tmp/default.conf /etc/nginx/conf.d/default.conf 2>/dev/null; then - echo "Configuration updated successfully" - else - echo "Warning: Could not copy config file, trying direct write" - envsubst '$NEO_API' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf || { - echo "Error: Could not create nginx configuration" - exit 1 - } - fi - - echo "Checking NEO_API configuration in NGINX default.conf" - grep proxy_pass /etc/nginx/conf.d/default.conf +# Check if a pre-configured Caddyfile exists (e.g., from Kubernetes ConfigMap) +# If it does not contain the env var placeholder, use it as-is +if [ -f /etc/caddy/Caddyfile ] && ! grep -q '{\$NEO_API}' /etc/caddy/Caddyfile 2>/dev/null; then + echo "Found pre-configured Caddyfile (likely from ConfigMap), using as-is" fi -# Validate nginx configuration -if nginx -t; then - echo "Nginx configuration is valid, starting server..." - exec nginx -g 'daemon off;' -else - echo "Error: Nginx configuration test failed" - exit 1 -fi \ No newline at end of file +# Validate and run Caddy +echo "Starting Caddy server..." +exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index b19330b..99fd689 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -19,5 +19,25 @@ export default defineConfig([ ecmaVersion: 2020, globals: globals.browser, }, - }, + rules: { + "react-refresh/only-export-components": [ + "error", + { + allowConstantExport: true, + allowExportNames: [ + "getStatusIcon", + "getStatusBadge", + "formatDuration", + "useTheme", + "useSettings", + "useSidebar", + "useFormField", + "badgeVariants", + "buttonVariants", + "toggleVariants", + ], + }, + ], + }, + }, ]) diff --git a/neocoreapi/openapi.json b/neocoreapi/openapi.json new file mode 100644 index 0000000..2f8f47d --- /dev/null +++ b/neocoreapi/openapi.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","info":{"title":"NetApp Neo","version":"3.2.4"},"paths":{"/api/v1/setup/status":{"get":{"tags":["setup"],"summary":"Get Setup Status","description":"Get current setup status.\n\nReturns setup completion status and list of completed/required steps.\nAlso indicates if the application is in license reconfiguration mode.","operationId":"get_setup_status_api_v1_setup_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupStatusResponse"}}}}}}},"/api/v1/setup/complete":{"post":{"tags":["setup"],"summary":"Complete Setup","description":"Mark setup as complete.\n\nCall this endpoint after all required configuration steps are finished\nto exit setup mode and start normal operation.\n\n**Prerequisites**:\n- DATABASE_URL environment variable must be set\n- License must be configured (via API or NETAPP_CONNECTOR_LICENSE env var)\n\nOptional steps (can be configured later):\n- Graph configuration (only needed for Microsoft Graph/Copilot integration)\n- SSL/TLS settings\n- Proxy settings","operationId":"complete_setup_api_v1_setup_complete_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/setup/license":{"post":{"tags":["setup"],"summary":"Configure License","description":"Configure NetApp Connector license.\n\n**Required**: License key for the connector\n\nWhen in license reconfiguration mode, this endpoint will validate the new license\nand trigger an automatic restart if the license is valid.\n\n**Example**:\n```json\n{\n \"license_key\": \"your-license-key-here\"\n}\n```","operationId":"configure_license_api_v1_setup_license_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_configure_license_api_v1_setup_license_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/setup/license/status":{"get":{"tags":["setup"],"summary":"Get License Status","description":"Get current license status and connector ID.\n\nThis endpoint is especially useful when the application is in license\nreconfiguration mode due to a license mismatch or missing license.\n\nReturns:\n- connector_id: The ID used for license validation (provide this to licensing support)\n- license_configured: Whether a license key is present\n- license_valid: Whether the license is valid for this connector\n- error_message: Details about any license validation error\n- in_reconfiguration_mode: Whether the app is in license reconfiguration mode","operationId":"get_license_status_api_v1_setup_license_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicenseStatusResponse"}}}}}}},"/api/v1/setup/graph":{"post":{"tags":["setup"],"summary":"Configure Graph","description":"Configure Microsoft Graph credentials for Copilot integration.\n\n**Note**: This step is OPTIONAL. Only configure if you want to integrate with\nMicrosoft Graph/Copilot. If you only want to index files to the local database,\nyou can skip this step.\n\n**Required** (if configuring Graph):\n- tenant_id: Azure AD Tenant ID\n- client_id: Application (client) ID\n- client_secret: Client secret value\n\n**Optional**:\n- connector_id: Custom connector ID (default: \"netappcopilot\")\n- connector_name: Display name for the connector\n- connector_description: Description of the connector\n\n**Example**:\n```json\n{\n \"tenant_id\": \"your-tenant-id\",\n \"client_id\": \"your-client-id\",\n \"client_secret\": \"your-client-secret\",\n \"connector_id\": \"netappneo\",\n \"connector_name\": \"NetApp NEO Connector\",\n \"connector_description\": \"SMB Share Integration\"\n}\n```","operationId":"configure_graph_api_v1_setup_graph_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_configure_graph_api_v1_setup_graph_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/setup/oauth":{"post":{"tags":["setup"],"summary":"Configure Oauth","description":"Configure MCP OAuth credentials for Entra authentication.\n\n**Required** (if enabled):\n- tenant_id: Azure AD Tenant ID\n- client_id: Application (client) ID\n- client_secret: Client secret value\n\n**Optional**:\n- audience: Expected token audience\n- enabled: Enable/disable MCP OAuth","operationId":"configure_oauth_api_v1_setup_oauth_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_configure_oauth_api_v1_setup_oauth_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/setup/reload-graph":{"post":{"tags":["setup"],"summary":"Reload Graph Config","description":"Reload Microsoft Graph configuration without restarting the connector.\n\nThis endpoint reinitializes the Graph connector with updated credentials\nfrom the database. Useful after updating Graph credentials via /api/v1/setup/graph.\n\n**Note**: This only works if setup is complete and the connector is running in normal mode.","operationId":"reload_graph_config_api_v1_setup_reload_graph_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/setup/ssl":{"post":{"tags":["setup"],"summary":"Configure Ssl","description":"Configure SSL/TLS settings for Microsoft Graph connections.\n\n**Optional**:\n- verify_ssl: Enable/disable SSL verification (default: true)\n- timeout: Request timeout in seconds (default: 30)\n- ca_certificate: Full PEM-encoded CA certificate content (including -----BEGIN CERTIFICATE-----)\n\n**Example**:\n```json\n{\n \"verify_ssl\": true,\n \"timeout\": 30,\n \"ca_certificate\": \"-----BEGIN CERTIFICATE-----\\nMIIDXTCCAkWgAwIBAgIJAKJ...\\n-----END CERTIFICATE-----\"\n}\n```\n\n**Note**: The certificate content will be stored in the database and combined with the default\ncertifi CA bundle. No file system access or volume mounts required.","operationId":"configure_ssl_api_v1_setup_ssl_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_configure_ssl_api_v1_setup_ssl_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/setup/proxy":{"post":{"tags":["setup"],"summary":"Configure Proxy","description":"Configure proxy settings for Microsoft Graph connections.\n\n**Optional**:\n- proxy_url: Proxy server URL (e.g., \"http://proxy.example.com:8080\")\n- proxy_username: Proxy authentication username\n- proxy_password: Proxy authentication password\n\n**Example**:\n```json\n{\n \"proxy_url\": \"http://proxy.example.com:8080\",\n \"proxy_username\": \"user\",\n \"proxy_password\": \"pass\"\n}\n```\n\n**Note**: To remove proxy configuration, call with all fields set to null.","operationId":"configure_proxy_api_v1_setup_proxy_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_configure_proxy_api_v1_setup_proxy_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/setup/reset":{"post":{"tags":["setup"],"summary":"Reset Setup","description":"Reset setup state (for testing or reconfiguration).\n\n**Warning**: This will reset all setup progress. Use with caution.","operationId":"reset_setup_api_v1_setup_reset_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/setup/factory-reset":{"post":{"tags":["setup"],"summary":"Factory Reset","description":"Perform a complete factory reset of the connector.\n\n**⚠️ DANGER: This is a destructive operation that cannot be undone!**\n\nThis will:\n- Delete ALL shares and their configurations\n- Delete ALL file metadata and inventory\n- Delete ALL work queue items\n- Delete ALL users (including admin)\n- Delete ALL operation logs\n- Reset ALL system configuration (except encryption key if preserve_encryption_key=true)\n- Create a new admin account with a new auto-generated password\n\n**Use Cases:**\n- Fresh start after testing\n- Decommissioning and recommissioning\n- Recovery from corrupted state\n\n**Requirements:**\n- Must set `confirm: true` in the request body","operationId":"factory_reset_api_v1_setup_factory_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoryResetRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoryResetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/setup/initial-credentials":{"get":{"tags":["setup"],"summary":"Get Initial Credentials","description":"Retrieve the initial admin credentials.\n\n**Security Notice**: This endpoint is only available if:\n1. The admin user has never logged in (last_login is NULL)\n2. The initial password is still stored in the system\n\nAfter the admin user logs in for the first time, this endpoint will return 403 Forbidden\nand the stored password will be permanently deleted.\n\nThis allows administrators to retrieve the auto-generated password without\nneeding access to container logs.","operationId":"get_initial_credentials_api_v1_setup_initial_credentials_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitialCredentialsResponse"}}}}}}},"/monitoring/overview":{"get":{"tags":["monitoring"],"summary":"Get Monitoring Overview","description":"Get comprehensive monitoring overview","operationId":"get_monitoring_overview_monitoring_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitoringOverviewResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/monitoring/work-queue":{"get":{"tags":["monitoring"],"summary":"Get Work Queue Stats","description":"Get work queue statistics","operationId":"get_work_queue_stats_monitoring_work_queue_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkQueueStatsResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/monitoring/enumeration":{"get":{"tags":["monitoring"],"summary":"Get Enumeration Stats","description":"Get enumeration statistics","operationId":"get_enumeration_stats_monitoring_enumeration_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnumerationStatsResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/monitoring/workers":{"get":{"tags":["monitoring"],"summary":"Get Worker Stats","description":"Get worker statistics","operationId":"get_worker_stats_monitoring_workers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkerStatsResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/monitoring/graph-rate-limit":{"get":{"tags":["monitoring"],"summary":"Get Graph Rate Limit Stats","description":"Get Microsoft Graph rate limiting statistics","operationId":"get_graph_rate_limit_stats_monitoring_graph_rate_limit_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphRateLimitStatsResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/monitoring/failed-items":{"get":{"tags":["monitoring"],"summary":"Get Failed Items","description":"Get detailed information about failed work queue items for troubleshooting\n\nArgs:\n limit: Maximum number of failed items to return (default: 50)\n\nReturns:\n Detailed information about failed items including:\n - Individual failed item details with error messages\n - File paths and filenames for context\n - Retry counts and failure reasons\n - Summary of failure types and retry patterns","operationId":"get_failed_items_monitoring_failed_items_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedItemsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/monitoring/async-db-pool":{"get":{"tags":["monitoring"],"summary":"Get Async Db Pool Stats","description":"Get async database connection pool statistics\n\nReturns pool health and connection statistics for the async database layer.\nThis is useful for monitoring connection pool utilization and identifying\npotential bottlenecks.","operationId":"get_async_db_pool_stats_monitoring_async_db_pool_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/tasks/{task_id}":{"get":{"tags":["tasks"],"summary":"Get Task Status","description":"Get the status and progress of a specific background task\n\nReturns:\n - Task status (pending, running, completed, failed, cancelled)\n - Progress information\n - Timestamps\n - Error details if failed","operationId":"get_task_status_tasks__task_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["tasks"],"summary":"Cancel Task","description":"Cancel a running background task\n\nQuery Parameters:\n - graceful: If true (default), task finishes current work before stopping.\n If false, task is immediately cancelled.\n\nReturns:\n Cancellation status and message","operationId":"cancel_task_tasks__task_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}},{"name":"graceful","in":"query","required":false,"schema":{"type":"boolean","description":"If true, finish current work before cancelling","default":true,"title":"Graceful"},"description":"If true, finish current work before cancelling"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tasks/{task_id}/detailed":{"get":{"tags":["tasks"],"summary":"Get Task Detailed Status","description":"Get detailed status of a background task including error traceback\n\nReturns:\n - Complete task information\n - Full error traceback if failed\n - All metadata","operationId":"get_task_detailed_status_tasks__task_id__detailed_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tasks":{"get":{"tags":["tasks"],"summary":"List Tasks","description":"List all background tasks with optional filtering\n\nQuery Parameters:\n - status: Filter by task status (pending, running, completed, failed, cancelled)\n - limit: Maximum number of tasks to return (default: 50, max: 500)\n\nReturns:\n List of task status dictionaries","operationId":"list_tasks_tasks_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/TaskStatus"},{"type":"null"}],"description":"Filter by task status","title":"Status"},"description":"Filter by task status"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":500,"minimum":1},{"type":"null"}],"description":"Maximum number of tasks to return","default":50,"title":"Limit"},"description":"Maximum number of tasks to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tasks/statistics/summary":{"get":{"tags":["tasks"],"summary":"Get Task Statistics","description":"Get overall task statistics\n\nReturns:\n - Total tasks\n - Tasks by status\n - Currently running task IDs","operationId":"get_task_statistics_tasks_statistics_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/tasks/statistics/acl-cache":{"get":{"tags":["tasks"],"summary":"Get Acl Cache Statistics","description":"Get ACL resolution cache statistics\n\nReturns:\n - Cache size and capacity\n - Hit/miss counts and hit rate\n - Eviction count\n - Performance metrics\n\nExample Response:\n{\n \"size\": 1250,\n \"max_size\": 10000,\n \"capacity_used_percent\": 12.5,\n \"hits\": 48500,\n \"misses\": 1500,\n \"hit_rate\": 97.0,\n \"evictions\": 0,\n \"status\": \"healthy\"\n}","operationId":"get_acl_cache_statistics_tasks_statistics_acl_cache_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/ner/files/{file_id}":{"get":{"tags":["NER"],"summary":"Get File Ner Result","description":"Get NER analysis results for a specific file.\n\nReturns extracted entities, classifications, and structured data\nfor the specified file.","operationId":"get_file_ner_result_ner_files__file_id__get","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NERResultResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["NER"],"summary":"Delete File Ner Result","description":"Delete NER result for a specific file.\n\nThis allows re-analysis of the file on the next NER processing cycle.","operationId":"delete_file_ner_result_ner_files__file_id__delete","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/shares/{share_id}/results":{"get":{"tags":["NER"],"summary":"Get Share Ner Results","description":"Get NER analysis results for all files in a share.\n\nArgs:\n share_id: The share ID to get results for\n limit: Maximum number of results to return (default 100, max 1000)\n offset: Offset for pagination\n include_errors: Whether to include failed analyses\n\nReturns:\n List of NER results with pagination info","operationId":"get_share_ner_results_ner_shares__share_id__results_get","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"include_errors","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Errors"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["NER"],"summary":"Delete Share Ner Results","description":"Delete all NER results for a share.\n\nThis allows re-analysis of all files in the share.","operationId":"delete_share_ner_results_ner_shares__share_id__results_delete","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/stats":{"get":{"tags":["NER"],"summary":"Get Ner Stats","description":"Get NER processing statistics.\n\nArgs:\n share_id: Optional share ID to filter stats\n\nReturns:\n Statistics including files analyzed, pending, failed, and processing times","operationId":"get_ner_stats_ner_stats_get","parameters":[{"name":"share_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NERStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/shares/{share_id}/stats":{"get":{"tags":["NER"],"summary":"Get Share Ner Stats","description":"Get NER processing statistics for a specific share.","operationId":"get_share_ner_stats_ner_shares__share_id__stats_get","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NERStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/status":{"get":{"tags":["NER"],"summary":"Get Ner Status","description":"Get NER system status including availability and configuration.","operationId":"get_ner_status_ner_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/ner/schemas":{"get":{"tags":["NER"],"summary":"List Ner Schemas","description":"List all available NER schemas (built-in and custom).\n\nReturns both pre-defined schemas (default, legal, financial, healthcare, hr)\nand any custom schemas stored in the database.","operationId":"list_ner_schemas_ner_schemas_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NERSchemaListResponse"}}}}}},"post":{"tags":["NER"],"summary":"Create Ner Schema","description":"Create or update a custom NER schema.\n\nCustom schemas are stored in the database and can override built-in schemas.","operationId":"create_ner_schema_ner_schemas_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NERSchemaConfig"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/schemas/{schema_name}":{"get":{"tags":["NER"],"summary":"Get Ner Schema","description":"Get a specific NER schema by name.\n\nChecks both built-in schemas and custom schemas in the database.","operationId":"get_ner_schema_ner_schemas__schema_name__get","parameters":[{"name":"schema_name","in":"path","required":true,"schema":{"type":"string","title":"Schema Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["NER"],"summary":"Delete Ner Schema","description":"Delete a custom NER schema.\n\nNote: Built-in schemas cannot be deleted.","operationId":"delete_ner_schema_ner_schemas__schema_name__delete","parameters":[{"name":"schema_name","in":"path","required":true,"schema":{"type":"string","title":"Schema Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/shares/{share_id}/reanalyze":{"post":{"tags":["NER"],"summary":"Trigger Share Reanalysis","description":"Trigger re-analysis of all files in a share.\n\nThis deletes existing NER results and creates new work items\nfor all files with content.","operationId":"trigger_share_reanalysis_ner_shares__share_id__reanalyze_post","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/pending":{"get":{"tags":["NER"],"summary":"Get Pending Ner Files","description":"Get files that are pending NER analysis.\n\nThese are files that have content but haven't been analyzed yet.","operationId":"get_pending_ner_files_ner_pending_get","parameters":[{"name":"share_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/entities/aggregate":{"get":{"tags":["NER"],"summary":"Get Entity Aggregation","description":"Get aggregated entity counts across all analyzed files.\n\nReturns counts of each entity type and the top 50 most frequent\nentity values for each type.\n\nArgs:\n share_id: Optional share ID to filter results\n entity_type: Optional entity type to filter (e.g., 'person', 'organization')\n\nReturns:\n Entity counts and top entity values by type","operationId":"get_entity_aggregation_ner_entities_aggregate_get","parameters":[{"name":"share_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id"}},{"name":"entity_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/classifications/distribution":{"get":{"tags":["NER"],"summary":"Get Classification Distribution","description":"Get distribution of text classifications across all analyzed files.\n\nReturns counts of each classification value for each classification type.\n\nArgs:\n share_id: Optional share ID to filter results\n\nReturns:\n Classification distribution by type","operationId":"get_classification_distribution_ner_classifications_distribution_get","parameters":[{"name":"share_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/entities/search":{"get":{"tags":["NER"],"summary":"Search Entities","description":"Search for files containing specific entity values.\n\nPerforms a case-insensitive search across all extracted entities.\n\nArgs:\n query: Entity value to search for (partial match supported)\n entity_type: Optional entity type to filter (e.g., 'person', 'organization')\n share_id: Optional share ID to filter results\n limit: Maximum number of results to return\n\nReturns:\n List of files containing matching entities with match details","operationId":"search_entities_ner_entities_search_get","parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Entity value to search for","title":"Query"},"description":"Entity value to search for"},{"name":"entity_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Entity type to filter","title":"Entity Type"},"description":"Entity type to filter"},{"name":"share_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Share ID to filter","title":"Share Id"},"description":"Share ID to filter"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/shares/{share_id}/entities/aggregate":{"get":{"tags":["NER"],"summary":"Get Share Entity Aggregation","description":"Get aggregated entity counts for a specific share.","operationId":"get_share_entity_aggregation_ner_shares__share_id__entities_aggregate_get","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}},{"name":"entity_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/shares/{share_id}/classifications/distribution":{"get":{"tags":["NER"],"summary":"Get Share Classification Distribution","description":"Get classification distribution for a specific share.","operationId":"get_share_classification_distribution_ner_shares__share_id__classifications_distribution_get","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/settings":{"get":{"tags":["NER"],"summary":"Get Ner Settings","description":"Get all NER global settings.\n\nReturns current configuration values with metadata including\ndescriptions, types, and environment variable names.\n\nThese settings are stored in the database and can be modified\nat runtime without restarting the connector.","operationId":"get_ner_settings_ner_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/ner/settings/{key}":{"get":{"tags":["NER"],"summary":"Get Ner Setting","description":"Get a specific NER setting by key.\n\nArgs:\n key: Setting key (e.g., 'ner.enabled', 'ner.worker_count')\n Can also use short form without 'ner.' prefix\n\nReturns:\n Current value and metadata for the setting","operationId":"get_ner_setting_ner_settings__key__get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["NER"],"summary":"Update Ner Setting","description":"Update a NER setting at runtime.\n\nArgs:\n key: Setting key (e.g., 'ner.enabled', 'ner.worker_count')\n value: New value for the setting\n\nReturns:\n Updated setting value\n\nNote:\n Some settings (like worker_count) may require a restart\n to take full effect on existing workers.","operationId":"update_ner_setting_ner_settings__key__put","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"value","in":"query","required":true,"schema":{"title":"Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ner/settings/reset":{"post":{"tags":["NER"],"summary":"Reset Ner Settings","description":"Reset all NER settings to their default values.\n\nThis will reset all NER configuration to the defaults defined\nin the system. Environment variables will NOT be re-read.","operationId":"reset_ner_settings_ner_settings_reset_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/auth/config":{"get":{"tags":["Authentication"],"summary":"Get OAuth Configuration","description":"Returns OAuth configuration for clients to initiate authentication flow.","operationId":"get_oauth_config_auth_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigResponse"}}}}}}},"/auth/userinfo":{"get":{"tags":["Authentication"],"summary":"Get User Information","description":"Returns information about the authenticated user from the OAuth token.","operationId":"get_user_info_auth_userinfo_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInfoResponse"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"OAuth not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/groups":{"get":{"tags":["Authentication"],"summary":"Get User Groups","description":"Returns the group memberships of the authenticated user.","operationId":"get_user_groups_auth_groups_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsResponse"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"OAuth not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/validate":{"get":{"tags":["Authentication"],"summary":"Validate Token","description":"Validates an OAuth token without returning user details.","operationId":"validate_token_auth_validate_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Token is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Validate Token Auth Validate Get"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/login":{"get":{"tags":["Authentication"],"summary":"Initiate OAuth Login","description":"Initiates the OAuth authorization code flow. Returns the authorization URL to redirect the user to.","operationId":"login_auth_login_get","parameters":[{"name":"redirect_uri","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Custom redirect URI after login","title":"Redirect Uri"},"description":"Custom redirect URI after login"},{"name":"scopes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space-separated OAuth scopes","default":"openid profile email User.Read","title":"Scopes"},"description":"Space-separated OAuth scopes"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/callback":{"get":{"tags":["Authentication"],"summary":"OAuth Callback","description":"Handles the OAuth callback after user authentication.","operationId":"oauth_callback_auth_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/device":{"post":{"tags":["Authentication"],"summary":"Initiate Device Code Flow","description":"Initiates the device code flow for CLI authentication. User visits a URL and enters a code.","operationId":"device_code_start_auth_device_post","parameters":[{"name":"scopes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space-separated OAuth scopes","default":"openid profile email User.Read","title":"Scopes"},"description":"Space-separated OAuth scopes"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCodeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/device/poll":{"post":{"tags":["Authentication"],"summary":"Poll Device Code Status","description":"Poll for the device code authentication status. Returns the token when authentication is complete.","operationId":"device_code_poll_auth_device_poll_post","parameters":[{"name":"device_code","in":"query","required":true,"schema":{"type":"string","description":"The device_code from /auth/device","title":"Device Code"},"description":"The device_code from /auth/device"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevicePollResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh Access Token","description":"Exchange a refresh token for a new access token.","operationId":"refresh_token_auth_refresh_post","parameters":[{"name":"refresh_token","in":"query","required":true,"schema":{"type":"string","description":"The refresh_token from a previous authentication","title":"Refresh Token"},"description":"The refresh_token from a previous authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/link-entra":{"post":{"tags":["Authentication"],"summary":"Link Entra Identity to Local User","description":"Links an Entra ID identity to a local user account. The caller must be authenticated via Entra OAuth. An admin can link their Entra identity to any local user, or a user can link to themselves.","operationId":"link_entra_identity_auth_link_entra_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntraLinkRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntraLinkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/unlink-entra":{"post":{"tags":["Authentication"],"summary":"Unlink Entra Identity from Local User","description":"Removes the Entra ID link from a local user account. Requires admin privileges.","operationId":"unlink_entra_identity_auth_unlink_entra_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntraUnlinkRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntraUnlinkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/whoami":{"get":{"tags":["Authentication"],"summary":"Get Current User Identity","description":"Returns information about the current authenticated user, including both Entra identity and linked local user (if any).","operationId":"whoami_auth_whoami_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Whoami Auth Whoami Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"tags":["MCP"],"summary":"OAuth Authorization Server Metadata","description":"Returns OAuth 2.0 Authorization Server Metadata as per RFC8414","operationId":"get_authorization_server_metadata__well_known_oauth_authorization_server_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/authorize":{"get":{"tags":["MCP"],"summary":"OAuth Authorization Endpoint","description":"Redirects to Microsoft Entra ID for authentication","operationId":"authorize_authorize_get","parameters":[{"name":"response_type","in":"query","required":true,"schema":{"type":"string","title":"Response Type"}},{"name":"client_id","in":"query","required":true,"schema":{"type":"string","title":"Client Id"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string","title":"Redirect Uri"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","default":"openid profile email","title":"Scope"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"code_challenge","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code Challenge"}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code Challenge Method"}},{"name":"resource","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mcp/token":{"post":{"tags":["MCP"],"summary":"OAuth Token Endpoint (MCP path)","description":"Exchanges authorization code for tokens via Entra ID, or authenticates local users","operationId":"token_endpoint_mcp_token_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/token":{"post":{"tags":["Users"],"summary":"Login For Access Token","description":"Generate an access token","operationId":"login_for_access_token_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_login_for_access_token_token_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mcp/.well-known/oauth-protected-resource":{"get":{"tags":["MCP"],"summary":"OAuth Protected Resource Metadata (MCP path)","description":"Returns OAuth 2.0 Protected Resource Metadata as per RFC9728","operationId":"get_protected_resource_metadata_mcp__well_known_oauth_protected_resource_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"tags":["MCP"],"summary":"OAuth Protected Resource Metadata","description":"Returns OAuth 2.0 Protected Resource Metadata as per RFC9728","operationId":"get_protected_resource_metadata__well_known_oauth_protected_resource_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/mcp":{"post":{"tags":["MCP"],"summary":"MCP Streamable HTTP Endpoint","description":"Handle MCP JSON-RPC requests over HTTP","operationId":"mcp_post_mcp_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["MCP"],"summary":"MCP SSE Endpoint","description":"Server-Sent Events endpoint for server-to-client messages","operationId":"mcp_get_mcp_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mcp/info":{"get":{"tags":["MCP"],"summary":"MCP Server Info","description":"Returns information about the MCP server","operationId":"mcp_info_mcp_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/mcp/openapi.yaml":{"get":{"tags":["MCP"],"summary":"MCP OpenAPI Schema","description":"Returns OpenAPI schema for Copilot Studio integration","operationId":"mcp_openapi_yaml_mcp_openapi_yaml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/mcp/openapi.json":{"get":{"tags":["MCP"],"summary":"MCP OpenAPI Schema (JSON)","description":"Returns OpenAPI schema in JSON format for Copilot Studio integration","operationId":"mcp_openapi_json_mcp_openapi_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/users/me":{"get":{"tags":["Users"],"summary":"Get Current User Info","description":"Get the currently authenticated user's information","operationId":"get_current_user_info_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/users/me/password":{"patch":{"tags":["Users"],"summary":"Change Own Password","description":"Change the password of the currently authenticated user.","operationId":"change_own_password_users_me_password_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordChangeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/shares":{"get":{"tags":["Shares"],"summary":"List Shares","description":"List all configured shares","operationId":"list_shares_shares_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ShareResponse"},"type":"array","title":"Response List Shares Shares Get"}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"post":{"tags":["Shares"],"summary":"Add Share","description":"Add a new share configuration - returns immediately with task_id\n\nThe share initialization (mounting, scheduling) happens in the background.\nUse GET /tasks/{task_id} to track progress.","operationId":"add_share_shares_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareConfig"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/shares/{share_id}":{"get":{"tags":["Shares"],"summary":"Get Share","description":"Get details of a specific share","operationId":"get_share_shares__share_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Shares"],"summary":"Delete Share","description":"Delete a specific share (refactored with graceful cancellation)\n\nThis endpoint will:\n1. Cancel all running tasks for the share (gracefully by default)\n2. Unmount the share\n3. Clean up Graph external items\n4. Delete from database\n5. Clean up mount point\n\nQuery Parameters:\n - graceful: If true (default), tasks finish current work before cancelling","operationId":"delete_share_shares__share_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}},{"name":"graceful","in":"query","required":false,"schema":{"type":"boolean","description":"If true, cancel tasks gracefully (finish current work)","default":true,"title":"Graceful"},"description":"If true, cancel tasks gracefully (finish current work)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Shares"],"summary":"Update Share","description":"Update an existing share configuration (partial updates allowed)","operationId":"update_share_shares__share_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operations/":{"get":{"tags":["System"],"summary":"Get Operations","description":"Get operation logs with optional filtering.\nFilters:\n - type: operation type\n - action: action (from metadata)\n - status: operation status\n - share_id: share ID (from metadata)\n - since: ISO timestamp (only results after this time)\n - limit: max results","operationId":"get_operations_operations__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by operation type","title":"Type"},"description":"Filter by operation type"},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by action (metadata)","title":"Action"},"description":"Filter by action (metadata)"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"share_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by share ID (metadata)","title":"Share Id"},"description":"Filter by share ID (metadata)"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Limit to operations since this ISO timestamp","title":"Since"},"description":"Limit to operations since this ISO timestamp"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"Max number of results to return","default":100,"title":"Limit"},"description":"Max number of results to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OperationLog"},"title":"Response Get Operations Operations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/shares/{share_id}/progress":{"get":{"tags":["Shares"],"summary":"Get Share Progress","description":"Get real-time progress for share enumeration and extraction\n\nReturns:\n - Enumeration progress (files discovered, directories scanned, work items)\n - Extraction progress (files processed, pending, failed)\n - Active workers\n - Processing rate and ETA","operationId":"get_share_progress_shares__share_id__progress_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/shares/{share_id}/crawl":{"post":{"tags":["Shares"],"summary":"Trigger Crawl","description":"Manually trigger a share crawl (refactored to use background tasks)\n\nReturns task_id for tracking progress","operationId":"trigger_crawl_shares__share_id__crawl_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}},{"name":"retry_failed","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to retry failed work items before starting crawl","default":false,"title":"Retry Failed"},"description":"Whether to retry failed work items before starting crawl"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users/":{"get":{"tags":["Users"],"summary":"List Users","description":"List all users (admin only)","operationId":"list_users_users__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UserResponse"},"type":"array","title":"Response List Users Users Get"}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"post":{"tags":["Users"],"summary":"Create User","description":"Create a new user","operationId":"create_user_users__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/users/admin":{"post":{"tags":["Users"],"summary":"Create Admin User","description":"Create a new admin user (requires admin privileges)","operationId":"create_admin_user_users_admin_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/shares/{share_id}/files/metadata":{"get":{"tags":["Shares"],"summary":"Get File Metadata","description":"Get metadata for a specific file in a share.\n\n**Query Methods:**\n- path: The file path within the share\n- file_id: The unique identifier of the file\n- At least one must be provided\n\n**Field Selection:**\n- fields: Specify exact fields to return (e.g., 'id,filename,size,content')\n- field_set: Use predefined sets ('minimal', 'standard', 'metadata', 'security', 'full')\n- Default: All fields (full file metadata)\n\n**Examples:**\n- Basic metadata: ?path=/docs/file.pdf&fields=id,filename,size,modified_time\n- With content: ?file_id=abc123&field_set=full\n- Security info: ?path=/docs/file.pdf&field_set=security","operationId":"get_file_metadata_shares__share_id__files_metadata_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}},{"name":"path","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Path to the file","title":"Path"},"description":"Path to the file"},{"name":"file_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ID of the file","title":"File Id"},"description":"ID of the file"},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of fields to return (e.g., 'id,filename,size,content'). Use '*' for all fields.","title":"Fields"},"description":"Comma-separated list of fields to return (e.g., 'id,filename,size,content'). Use '*' for all fields."},{"name":"field_set","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Predefined field set: 'minimal', 'standard', 'metadata', 'security', 'full'","title":"Field Set"},"description":"Predefined field set: 'minimal', 'standard', 'metadata', 'security', 'full'"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/files/{file_id}":{"get":{"tags":["Files"],"summary":"Get File By Id","description":"Get a file by its ID directly (efficient single-query lookup).\n\nThis endpoint provides O(1) file lookup by ID without requiring share_id.\nThe file ID is a SHA-256 hash that uniquely identifies the file.\n\n**Field Selection:**\n- fields: Specify exact fields to return (e.g., 'id,filename,size,content')\n- field_set: Use predefined sets ('minimal', 'standard', 'metadata', 'security', 'full')\n- Default: All fields (full file metadata)","operationId":"get_file_by_id_files__file_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of fields to return","title":"Fields"},"description":"Comma-separated list of fields to return"},{"name":"field_set","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Predefined field set: 'minimal', 'standard', 'metadata', 'security', 'full'","title":"Field Set"},"description":"Predefined field set: 'minimal', 'standard', 'metadata', 'security', 'full'"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlexibleFileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/shares/{share_id}/files":{"get":{"tags":["Shares"],"summary":"List Files","description":"List files in a share with optional filtering and pagination.\n\n**Field Selection:**\n- fields: Specify exact fields to return (e.g., 'id,filename,size,modified_time')\n- field_set: Use predefined sets ('minimal', 'standard', 'metadata', 'security', 'full')\n- Default: 'standard' (excludes content and content_chunks for performance)\n\n**Field Sets:**\n- minimal: id, filename, size\n- standard: id, filename, size, modified_time, file_type, indexed_at, file_path\n- metadata: All fields except content/content_chunks\n- security: id, filename, file_path, acl_principals, resolved_principals\n- full: All fields including content\n\n**Filtering:**\n- path, file_type, filename: Filter by file attributes\n- accessed_at_after/before, modified_time_after/before, created_at_after/before: Date ranges\n- size_min/max: File size range (bytes)\n\n**Pagination:**\n- page: Page number (starting from 1)\n- page_size: Items per page (1-1000)\n\n**Response Size Management:**\n- max_content_length: Limit content size per file (0-10MB). Content exceeding this is truncated.\n- When using field_set=full, consider using smaller page_size or max_content_length to avoid large responses.\n- Response includes truncation metadata: content_truncated, truncated_file_count, response_size_warning\n\n**Examples:**\n- Minimal payload: ?fields=id,filename,size\n- With content: ?field_set=full&max_content_length=100000&page_size=10\n- Security audit: ?field_set=security","operationId":"list_files_shares__share_id__files_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}},{"name":"path","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional path to list files from","title":"Path"},"description":"Optional path to list files from"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number, starting from 1","default":1,"title":"Page"},"description":"Page number, starting from 1"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Number of items per page","default":100,"title":"Page Size"},"description":"Number of items per page"},{"name":"file_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by file type (e.g., 'pdf', 'docx', 'txt')","title":"File Type"},"description":"Filter by file type (e.g., 'pdf', 'docx', 'txt')"},{"name":"filename","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by filename (supports partial matching, e.g., 'report', '.xlsx')","title":"Filename"},"description":"Filter by filename (supports partial matching, e.g., 'report', '.xlsx')"},{"name":"accessed_at_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files accessed after this date","title":"Accessed At After"},"description":"Filter files accessed after this date"},{"name":"accessed_at_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files accessed before this date","title":"Accessed At Before"},"description":"Filter files accessed before this date"},{"name":"modified_time_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files modified after this date","title":"Modified Time After"},"description":"Filter files modified after this date"},{"name":"modified_time_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files modified before this date","title":"Modified Time Before"},"description":"Filter files modified before this date"},{"name":"created_at_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files created after this date","title":"Created At After"},"description":"Filter files created after this date"},{"name":"created_at_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files created before this date","title":"Created At Before"},"description":"Filter files created before this date"},{"name":"size_min","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter files with size greater than or equal to this value (bytes)","title":"Size Min"},"description":"Filter files with size greater than or equal to this value (bytes)"},{"name":"size_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter files with size less than or equal to this value (bytes)","title":"Size Max"},"description":"Filter files with size less than or equal to this value (bytes)"},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of fields to return (e.g., 'id,filename,size'). Use '*' for all fields. Default: standard fields (excludes content)","title":"Fields"},"description":"Comma-separated list of fields to return (e.g., 'id,filename,size'). Use '*' for all fields. Default: standard fields (excludes content)"},{"name":"field_set","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Predefined field set: 'minimal', 'standard' (default), 'metadata', 'security', 'full'","title":"Field Set"},"description":"Predefined field set: 'minimal', 'standard' (default), 'metadata', 'security', 'full'"},{"name":"max_content_length","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":10485760,"minimum":0},{"type":"null"}],"description":"Maximum content length per file in bytes (0-10MB). Content exceeding this will be truncated. Recommended when using field_set=full with large page sizes.","title":"Max Content Length"},"description":"Maximum content length per file in bytes (0-10MB). Content exceeding this will be truncated. Recommended when using field_set=full with large page sizes."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/files":{"get":{"tags":["Files"],"summary":"List Files All Shares","description":"Search for files across all accessible shares with comprehensive filtering options.\n\n**Field Selection:**\n- fields: Specify exact fields to return (e.g., 'id,filename,size,modified_time')\n- field_set: Use predefined sets ('minimal', 'standard', 'metadata', 'security', 'full')\n- Default: 'standard' (excludes content for performance)\n- Share fields (share_id, share_name, share_path) are always included\n\n**Field Sets:**\n- minimal: id, filename, size + share info\n- standard: id, filename, size, modified_time, file_type, indexed_at, file_path + share info\n- metadata: All fields except content/content_chunks + share info\n- security: id, filename, file_path, acl_principals, resolved_principals + share info\n- full: All fields including content + share info\n\n**Filtering Options:**\n- path, filename, file_type: Filter by file attributes\n- Date ranges: accessed_at, modified_time, created_at (after/before)\n- Size ranges: size_min, size_max (bytes)\n\n**Sorting:**\n- sort_by: modified_time, created_at, accessed_at, filename, size, share_name\n- sort_order: asc or desc\n\n**Response Size Management:**\n- max_content_length: Limit content size per file (0-10MB). Content exceeding this is truncated.\n- When using field_set=full, consider using smaller page_size or max_content_length to avoid large responses.\n- Response includes truncation metadata: content_truncated, truncated_file_count, response_size_warning\n\n**Examples:**\n- Minimal payload: `?fields=id,filename,size&file_type=pdf`\n- Security audit: `?field_set=security`\n- Large files: `?size_min=10485760&fields=id,filename,size,share_name`\n- With content: `?field_set=full&max_content_length=100000&page_size=10`","operationId":"list_files_all_shares_files_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"path","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional path to filter files by (LIKE pattern)","title":"Path"},"description":"Optional path to filter files by (LIKE pattern)"},{"name":"filename","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional filename to search for (case-insensitive partial match)","title":"Filename"},"description":"Optional filename to search for (case-insensitive partial match)"},{"name":"file_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional file type/extension to filter by (e.g., 'pdf', 'docx', 'txt')","title":"File Type"},"description":"Optional file type/extension to filter by (e.g., 'pdf', 'docx', 'txt')"},{"name":"accessed_at_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files accessed after this datetime (ISO 8601 format)","title":"Accessed At After"},"description":"Filter files accessed after this datetime (ISO 8601 format)"},{"name":"accessed_at_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files accessed before this datetime (ISO 8601 format)","title":"Accessed At Before"},"description":"Filter files accessed before this datetime (ISO 8601 format)"},{"name":"modified_time_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files modified after this datetime (ISO 8601 format)","title":"Modified Time After"},"description":"Filter files modified after this datetime (ISO 8601 format)"},{"name":"modified_time_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files modified before this datetime (ISO 8601 format)","title":"Modified Time Before"},"description":"Filter files modified before this datetime (ISO 8601 format)"},{"name":"created_at_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files created after this datetime (ISO 8601 format)","title":"Created At After"},"description":"Filter files created after this datetime (ISO 8601 format)"},{"name":"created_at_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter files created before this datetime (ISO 8601 format)","title":"Created At Before"},"description":"Filter files created before this datetime (ISO 8601 format)"},{"name":"size_min","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Minimum file size in bytes","title":"Size Min"},"description":"Minimum file size in bytes"},{"name":"size_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Maximum file size in bytes","title":"Size Max"},"description":"Maximum file size in bytes"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort field: modified_time, created_at, accessed_at, filename, size, share_name","default":"modified_time","title":"Sort By"},"description":"Sort field: modified_time, created_at, accessed_at, filename, size, share_name"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","description":"Sort order: asc or desc","default":"desc","title":"Sort Order"},"description":"Sort order: asc or desc"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number, starting from 1","default":1,"title":"Page"},"description":"Page number, starting from 1"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Number of items per page","default":100,"title":"Page Size"},"description":"Number of items per page"},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of fields to return (e.g., 'id,filename,size'). Use '*' for all fields. Share fields always included.","title":"Fields"},"description":"Comma-separated list of fields to return (e.g., 'id,filename,size'). Use '*' for all fields. Share fields always included."},{"name":"field_set","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Predefined field set: 'minimal', 'standard' (default), 'metadata', 'security', 'full'","title":"Field Set"},"description":"Predefined field set: 'minimal', 'standard' (default), 'metadata', 'security', 'full'"},{"name":"max_content_length","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":10485760,"minimum":0},{"type":"null"}],"description":"Maximum content length per file in bytes (0-10MB). Content exceeding this will be truncated. Recommended when using field_set=full with large page sizes.","title":"Max Content Length"},"description":"Maximum content length per file in bytes (0-10MB). Content exceeding this will be truncated. Recommended when using field_set=full with large page sizes."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllSharesFileListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/search":{"post":{"tags":["Search"],"summary":"Full Text Search","description":"Perform full-text search across all file metadata (filename and content).\n\nThis endpoint provides powerful full-text search capabilities optimized for both PostgreSQL and MySQL:\n- **PostgreSQL**: Uses `websearch_to_tsquery()` with ts_vector GIN index for sub-second searches\n- **MySQL**: Uses FULLTEXT indexes with Natural Language Mode (default) or Boolean Mode (advanced)\n\n## Search Syntax (Natural Language - Works for Both Databases)\n\n**Basic Searches:**\n- `invoice 2024` - Find documents containing both words (AND)\n- `\"quarterly report\"` - Exact phrase match (use quotes)\n- `contract OR agreement` - Find either term (OR)\n- `pdf -draft` - Include \"pdf\" but exclude \"draft\"\n- `invoice*` - Wildcard/prefix search (invoices, invoicing, etc.)\n\n**Advanced Searches:**\n- `financial report 2024 -draft` - Complex multi-term queries\n- `\"Q1 2024\" revenue` - Combine phrase and word searches\n- `user@company.com` - Handles special characters gracefully\n\n## Search Modes (MySQL Only)\n\n**Natural Mode** (default, recommended):\n- User-friendly OR-based search\n- Best for AI agents and general users\n- Example: `invoice 2024` finds docs with \"invoice\" OR \"2024\"\n\n**Boolean Mode** (advanced):\n- AND-based search with full operator support\n- Requires all terms by default\n- Example: `invoice 2024` finds docs with BOTH \"invoice\" AND \"2024\"\n- Supports: +required, -excluded, \"phrases\", wildcards*\n\n## Filtering Options\n\n- **share_ids**: Filter by specific share IDs\n- **file_types**: Filter by file extensions (pdf, docx, txt, etc.)\n- **modified_after/before**: Filter by modification date range\n\n## Sorting Options\n\n- **relevance** (default): Sort by search relevance score\n- **modified_time**: Sort by modification date\n- **filename**: Sort alphabetically\n- **size**: Sort by file size\n\n## Response Format\n\nEach result includes:\n- File metadata (id, filename, path, size, dates, etc.)\n- **relevance_score**: Numerical relevance ranking\n- **snippet**: Content preview with search context (PostgreSQL only)\n- **search_time_ms**: Query execution time\n\n## Performance\n\n- **PostgreSQL**: Typically <100ms for millions of files with GIN index\n- **MySQL**: Typically <200ms for millions of files with FULLTEXT index\n- Results are paginated (max 1000 per page)\n\n## Examples\n\n```json\n{\n \"query\": \"invoice 2024 -draft\",\n \"file_types\": [\"pdf\", \"docx\"],\n \"modified_after\": \"2024-01-01T00:00:00Z\",\n \"page\": 1,\n \"page_size\": 50,\n \"sort_by\": \"relevance\",\n \"search_mode\": \"natural\"\n}\n```\n\n**Find PDF invoices from 2024:**\n```json\n{\n \"query\": \"invoice\",\n \"file_types\": [\"pdf\"],\n \"modified_after\": \"2024-01-01T00:00:00Z\"\n}\n```\n\n**Search specific shares:**\n```json\n{\n \"query\": \"quarterly report\",\n \"share_ids\": [\"share-123\", \"share-456\"]\n}\n```","operationId":"full_text_search_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FullTextSearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FullTextSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/health":{"get":{"tags":["System"],"summary":"Health Check","description":"Health check endpoint for monitoring","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Check Health Get"}}}}}}},"/license/status":{"get":{"tags":["System"],"summary":"License Status","description":"Check the current license status.","operationId":"license_status_license_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/database/size":{"get":{"tags":["System"],"summary":"Get Database Size","description":"Get comprehensive database size and statistics information.\n\nReturns detailed information about:\n- Database file size on disk\n- Table statistics (row counts)\n- Total file content size stored\n- Original file sizes tracked\n- Timestamp of the measurement\n\nThis endpoint is useful for monitoring database growth and storage usage.","operationId":"get_database_size_database_size_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/work-items/retry":{"post":{"tags":["Work Queue"],"summary":"Retry Failed Work Items","description":"Retry failed work items by resetting them to pending status.\n\nThis endpoint allows you to retry work items that have failed permanently (after 3 retries).\nYou can either:\n- Retry all failed items for a specific share by providing share_id\n- Retry specific work items by providing comma-separated work_item_ids\n- Retry all failed items by providing neither parameter\n\nThe work items will be reset to pending status with retry_count=0, allowing them to be processed again.","operationId":"retry_failed_work_items_work_items_retry_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"share_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional share ID to retry all failed items for that share","title":"Share Id"},"description":"Optional share ID to retry all failed items for that share"},{"name":"work_item_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional comma-separated list of specific work item IDs to retry","title":"Work Item Ids"},"description":"Optional comma-separated list of specific work item IDs to retry"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/version":{"get":{"tags":["System"],"summary":"Get Version","description":"Return the current version information of the connector","operationId":"get_version_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/ready":{"get":{"tags":["System"],"summary":"Readiness Check","description":"Readiness check - returns 200 when system is ready to handle requests\n\nThis endpoint checks if all critical components are initialized and ready.\nReturns 503 if the system is not yet ready to handle requests.","operationId":"readiness_check_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AllSharesFileListResponse":{"properties":{"files":{"items":{"$ref":"#/components/schemas/FlexibleFileResponse"},"type":"array","title":"Files"},"total_count":{"type":"integer","title":"Total Count"},"total_size":{"type":"integer","title":"Total Size"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"},"total_pages":{"type":"integer","title":"Total Pages"},"has_next":{"type":"boolean","title":"Has Next"},"has_previous":{"type":"boolean","title":"Has Previous"},"content_truncated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Content Truncated"},"truncated_file_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Truncated File Count"},"max_content_length_applied":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Content Length Applied"},"response_size_warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Size Warning"}},"type":"object","required":["files","total_count","total_size","page","page_size","total_pages","has_next","has_previous"],"title":"AllSharesFileListResponse","description":"Response model for cross-share file listing with pagination"},"Body_configure_graph_api_v1_setup_graph_post":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"client_id":{"type":"string","title":"Client Id"},"client_secret":{"type":"string","title":"Client Secret"},"connector_id":{"type":"string","title":"Connector Id","default":"netappcopilot"},"connector_name":{"type":"string","title":"Connector Name"},"connector_description":{"type":"string","title":"Connector Description"}},"type":"object","required":["tenant_id","client_id","client_secret"],"title":"Body_configure_graph_api_v1_setup_graph_post"},"Body_configure_license_api_v1_setup_license_post":{"properties":{"license_key":{"type":"string","title":"License Key"}},"type":"object","required":["license_key"],"title":"Body_configure_license_api_v1_setup_license_post"},"Body_configure_oauth_api_v1_setup_oauth_post":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"client_id":{"type":"string","title":"Client Id"},"client_secret":{"type":"string","title":"Client Secret"},"audience":{"type":"string","title":"Audience"},"enabled":{"type":"boolean","title":"Enabled","default":true}},"type":"object","title":"Body_configure_oauth_api_v1_setup_oauth_post"},"Body_configure_proxy_api_v1_setup_proxy_post":{"properties":{"proxy_url":{"type":"string","title":"Proxy Url"},"proxy_username":{"type":"string","title":"Proxy Username"},"proxy_password":{"type":"string","title":"Proxy Password"}},"type":"object","title":"Body_configure_proxy_api_v1_setup_proxy_post"},"Body_configure_ssl_api_v1_setup_ssl_post":{"properties":{"verify_ssl":{"type":"boolean","title":"Verify Ssl","default":true},"timeout":{"type":"integer","title":"Timeout","default":30},"ca_certificate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ca Certificate","description":"PEM-encoded CA certificate content"}},"type":"object","title":"Body_configure_ssl_api_v1_setup_ssl_post"},"Body_login_for_access_token_token_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_for_access_token_token_post"},"DeviceCodeResponse":{"properties":{"device_code":{"type":"string","title":"Device Code"},"user_code":{"type":"string","title":"User Code"},"verification_uri":{"type":"string","title":"Verification Uri"},"verification_uri_complete":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Uri Complete"},"expires_in":{"type":"integer","title":"Expires In"},"interval":{"type":"integer","title":"Interval"},"message":{"type":"string","title":"Message"}},"type":"object","required":["device_code","user_code","verification_uri","expires_in","interval","message"],"title":"DeviceCodeResponse","description":"Device code flow initiation response."},"DevicePollResponse":{"properties":{"status":{"type":"string","title":"Status"},"access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Token"},"token_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Type"},"expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["status"],"title":"DevicePollResponse","description":"Device code polling response."},"EntraLinkRequest":{"properties":{"user_id":{"type":"integer","title":"User Id"}},"type":"object","required":["user_id"],"title":"EntraLinkRequest","description":"Request to link an Entra identity to a local user."},"EntraLinkResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"},"user_id":{"type":"integer","title":"User Id"},"username":{"type":"string","title":"Username"},"entra_object_id":{"type":"string","title":"Entra Object Id"},"entra_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entra Display Name"}},"type":"object","required":["success","message","user_id","username","entra_object_id"],"title":"EntraLinkResponse","description":"Response after linking Entra identity."},"EntraUnlinkRequest":{"properties":{"user_id":{"type":"integer","title":"User Id"}},"type":"object","required":["user_id"],"title":"EntraUnlinkRequest","description":"Request to unlink an Entra identity from a local user."},"EntraUnlinkResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"},"user_id":{"type":"integer","title":"User Id"}},"type":"object","required":["success","message","user_id"],"title":"EntraUnlinkResponse","description":"Response after unlinking Entra identity."},"EnumerationStatsResponse":{"properties":{"active_enumerations":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Active Enumerations"},"enumeration_queue_depth":{"additionalProperties":{"type":"integer"},"type":"object","title":"Enumeration Queue Depth"},"completed_enumerations_last_24h":{"type":"integer","title":"Completed Enumerations Last 24H"},"avg_enumeration_duration_seconds":{"type":"number","title":"Avg Enumeration Duration Seconds"}},"type":"object","required":["active_enumerations","enumeration_queue_depth","completed_enumerations_last_24h","avg_enumeration_duration_seconds"],"title":"EnumerationStatsResponse","description":"Response model for enumeration statistics"},"ErrorResponse":{"properties":{"error":{"type":"string","title":"Error"},"message":{"type":"string","title":"Message"}},"type":"object","required":["error","message"],"title":"ErrorResponse","description":"Error response."},"FactoryResetRequest":{"properties":{"confirm":{"type":"boolean","title":"Confirm","description":"Must be set to true to confirm factory reset"},"preserve_encryption_key":{"type":"boolean","title":"Preserve Encryption Key","description":"If true, preserves the encryption key (recommended for data recovery scenarios)","default":true}},"type":"object","required":["confirm"],"title":"FactoryResetRequest","description":"Request model for factory reset."},"FactoryResetResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"},"tables_cleared":{"items":{},"type":"array","title":"Tables Cleared"},"warnings":{"items":{},"type":"array","title":"Warnings","default":[]},"next_steps":{"items":{},"type":"array","title":"Next Steps","default":[]}},"type":"object","required":["success","message","tables_cleared"],"title":"FactoryResetResponse","description":"Response model for factory reset."},"FailedItemsResponse":{"properties":{"total_failed_items":{"type":"integer","title":"Total Failed Items"},"failed_items":{"items":{"$ref":"#/components/schemas/FailedWorkItem"},"type":"array","title":"Failed Items"},"failure_summary":{"additionalProperties":{"type":"integer"},"type":"object","title":"Failure Summary"},"retry_summary":{"additionalProperties":{"type":"integer"},"type":"object","title":"Retry Summary"}},"type":"object","required":["total_failed_items","failed_items","failure_summary","retry_summary"],"title":"FailedItemsResponse","description":"Response model for failed items troubleshooting"},"FailedWorkItem":{"properties":{"id":{"type":"string","title":"Id"},"share_id":{"type":"string","title":"Share Id"},"file_inventory_id":{"type":"string","title":"File Inventory Id"},"work_type":{"type":"string","title":"Work Type"},"priority":{"type":"integer","title":"Priority"},"retry_count":{"type":"integer","title":"Retry Count"},"max_retries":{"type":"integer","title":"Max Retries"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"claimed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claimed By"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"}},"type":"object","required":["id","share_id","file_inventory_id","work_type","priority","retry_count","max_retries","error_message","created_at","started_at","completed_at","claimed_by"],"title":"FailedWorkItem","description":"Model for failed work queue item details"},"FileListResponse":{"properties":{"share_id":{"type":"string","title":"Share Id"},"path":{"type":"string","title":"Path"},"files":{"items":{"$ref":"#/components/schemas/FlexibleFileResponse"},"type":"array","title":"Files"},"total_count":{"type":"integer","title":"Total Count"},"total_size":{"type":"integer","title":"Total Size"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"},"total_pages":{"type":"integer","title":"Total Pages"},"has_next":{"type":"boolean","title":"Has Next"},"has_previous":{"type":"boolean","title":"Has Previous"},"content_truncated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Content Truncated"},"truncated_file_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Truncated File Count"},"max_content_length_applied":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Content Length Applied"},"response_size_warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Size Warning"}},"type":"object","required":["share_id","path","files","total_count","total_size","page","page_size","total_pages","has_next","has_previous"],"title":"FileListResponse","description":"Response model for file listing with pagination"},"FileResponse":{"properties":{"id":{"type":"string","title":"Id"},"file_path":{"type":"string","title":"File Path"},"unc_path":{"type":"string","title":"Unc Path"},"filename":{"type":"string","title":"Filename"},"size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"modified_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Modified Time"},"accessed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accessed At"},"is_directory":{"type":"boolean","title":"Is Directory"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"content_chunks":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Content Chunks"},"conversion_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Conversion Duration Ms"},"extractor_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extractor Used"},"indexed_at":{"type":"string","format":"date-time","title":"Indexed At"},"acl_principals":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Acl Principals"},"resolved_principals":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Resolved Principals"}},"type":"object","required":["id","file_path","unc_path","filename","size","created_at","modified_time","accessed_at","is_directory","file_type","content","content_chunks","conversion_duration_ms","indexed_at"],"title":"FileResponse"},"FlexibleFileResponse":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path"},"unc_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unc Path"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"},"size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"modified_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Modified Time"},"accessed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accessed At"},"is_directory":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Directory"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"content_chunks":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Content Chunks"},"conversion_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Conversion Duration Ms"},"extractor_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extractor Used"},"indexed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Indexed At"},"acl_principals":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Acl Principals"},"resolved_principals":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Resolved Principals"},"share_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id"},"share_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Name"},"share_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Path"}},"additionalProperties":true,"type":"object","title":"FlexibleFileResponse","description":"Flexible file response supporting dynamic field selection.\n\nOnly fields that are explicitly set will be serialized to JSON.\nThis prevents null values for unrequested fields from cluttering the response."},"FullTextSearchRequest":{"properties":{"query":{"type":"string","maxLength":500,"minLength":1,"title":"Query","description":"Search query (natural language)"},"share_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Share Ids","description":"Filter by specific share IDs"},"file_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"File Types","description":"Filter by file types (e.g., pdf, docx, txt)"},"modified_after":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Modified After","description":"Filter files modified after this date"},"modified_before":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Modified Before","description":"Filter files modified before this date"},"page":{"type":"integer","minimum":1.0,"title":"Page","description":"Page number (starting from 1)","default":1},"page_size":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Page Size","description":"Results per page (max 1000)","default":100},"sort_by":{"type":"string","title":"Sort By","description":"Sort by: relevance, modified_time, filename, size","default":"relevance"},"sort_order":{"type":"string","title":"Sort Order","description":"Sort order: asc or desc","default":"desc"},"search_mode":{"type":"string","title":"Search Mode","description":"MySQL only: 'natural' (OR-based) or 'boolean' (AND-based, advanced)","default":"natural"}},"type":"object","required":["query"],"title":"FullTextSearchRequest","description":"Request model for full-text search"},"FullTextSearchResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/SearchResultItem"},"type":"array","title":"Results"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"},"total_pages":{"type":"integer","title":"Total Pages"},"has_next":{"type":"boolean","title":"Has Next"},"has_previous":{"type":"boolean","title":"Has Previous"},"query":{"type":"string","title":"Query","description":"Original search query"},"search_time_ms":{"type":"integer","title":"Search Time Ms","description":"Search execution time in milliseconds"},"database_type":{"type":"string","title":"Database Type","description":"Database backend used (postgresql or mysql)"}},"type":"object","required":["results","total_count","page","page_size","total_pages","has_next","has_previous","query","search_time_ms","database_type"],"title":"FullTextSearchResponse","description":"Response model for full-text search with pagination and metadata"},"GraphRateLimitStatsResponse":{"properties":{"requests_made":{"type":"integer","title":"Requests Made"},"requests_remaining":{"type":"integer","title":"Requests Remaining"},"reset_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reset Time"},"rate_limited":{"type":"boolean","title":"Rate Limited"},"backoff_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Backoff Until"}},"type":"object","required":["requests_made","requests_remaining","reset_time","rate_limited","backoff_until"],"title":"GraphRateLimitStatsResponse","description":"Response model for Graph API rate limit statistics"},"GroupsResponse":{"properties":{"object_id":{"type":"string","title":"Object Id"},"groups":{"items":{"type":"string"},"type":"array","title":"Groups"},"groups_count":{"type":"integer","title":"Groups Count"}},"type":"object","required":["object_id","groups","groups_count"],"title":"GroupsResponse","description":"User's group memberships."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InitialCredentialsResponse":{"properties":{"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"message":{"type":"string","title":"Message"}},"type":"object","required":["username","password","message"],"title":"InitialCredentialsResponse","description":"Response model for initial admin credentials."},"LicenseStatusResponse":{"properties":{"license_configured":{"type":"boolean","title":"License Configured"},"license_valid":{"type":"boolean","title":"License Valid","default":false},"connector_id":{"type":"string","title":"Connector Id"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"in_reconfiguration_mode":{"type":"boolean","title":"In Reconfiguration Mode","default":false},"message":{"type":"string","title":"Message"}},"type":"object","required":["license_configured","connector_id","message"],"title":"LicenseStatusResponse","description":"License status response model."},"LoginResponse":{"properties":{"authorization_url":{"type":"string","title":"Authorization Url"},"state":{"type":"string","title":"State"}},"type":"object","required":["authorization_url","state"],"title":"LoginResponse","description":"Login initiation response."},"MonitoringOverviewResponse":{"properties":{"work_queue":{"$ref":"#/components/schemas/WorkQueueStatsResponse"},"enumeration":{"$ref":"#/components/schemas/EnumerationStatsResponse"},"workers":{"$ref":"#/components/schemas/WorkerStatsResponse"},"graph_rate_limit":{"$ref":"#/components/schemas/GraphRateLimitStatsResponse"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["work_queue","enumeration","workers","graph_rate_limit"],"title":"MonitoringOverviewResponse","description":"Response model for monitoring overview"},"NERResultResponse":{"properties":{"id":{"type":"string","title":"Id"},"file_id":{"type":"string","title":"File Id"},"share_id":{"type":"string","title":"Share Id"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path"},"entities":{"anyOf":[{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object"},{"type":"null"}],"title":"Entities"},"classifications":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Classifications"},"structured_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Structured Data"},"schema_name":{"type":"string","title":"Schema Name"},"schema_version":{"type":"string","title":"Schema Version"},"confidence_threshold":{"type":"number","title":"Confidence Threshold"},"processing_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Processing Time Ms"},"analyzed_at":{"type":"string","format":"date-time","title":"Analyzed At"}},"type":"object","required":["id","file_id","share_id","schema_name","schema_version","confidence_threshold","analyzed_at"],"title":"NERResultResponse","description":"API response model for NER results"},"NERSchemaConfig":{"properties":{"name":{"type":"string","title":"Name"},"version":{"type":"string","title":"Version","default":"1.0"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"entity_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Entity Types"},"entity_descriptions":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Entity Descriptions"},"classifications":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Classifications"},"structured_extraction":{"anyOf":[{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object"},{"type":"null"}],"title":"Structured Extraction"},"confidence_threshold":{"type":"number","title":"Confidence Threshold","default":0.7},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name"],"title":"NERSchemaConfig","description":"Configuration for a custom NER schema"},"NERSchemaListResponse":{"properties":{"schemas":{"items":{"$ref":"#/components/schemas/NERSchemaConfig"},"type":"array","title":"Schemas"},"total_count":{"type":"integer","title":"Total Count"}},"type":"object","required":["schemas","total_count"],"title":"NERSchemaListResponse","description":"Response model for listing available NER schemas"},"NERStatsResponse":{"properties":{"total_files_analyzed":{"type":"integer","title":"Total Files Analyzed"},"files_pending_analysis":{"type":"integer","title":"Files Pending Analysis"},"files_failed_analysis":{"type":"integer","title":"Files Failed Analysis"},"avg_processing_time_ms":{"type":"number","title":"Avg Processing Time Ms"},"entity_counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Entity Counts"},"classification_distribution":{"additionalProperties":{"additionalProperties":{"type":"integer"},"type":"object"},"type":"object","title":"Classification Distribution"},"last_analysis_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Analysis At"}},"type":"object","required":["total_files_analyzed","files_pending_analysis","files_failed_analysis","avg_processing_time_ms","entity_counts","classification_distribution"],"title":"NERStatsResponse","description":"Response model for NER processing statistics"},"OAuthConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"provider":{"type":"string","title":"Provider","default":"entra"},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience"},"authorization_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization Endpoint"},"token_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Endpoint"}},"type":"object","required":["enabled"],"title":"OAuthConfigResponse","description":"OAuth configuration for clients."},"OperationLog":{"properties":{"id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Id"},"operation_type":{"type":"string","title":"Operation Type"},"status":{"type":"string","title":"Status"},"details":{"type":"string","title":"Details"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","default":"2026-02-20T09:31:26.722340"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","default":{}},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"}},"type":"object","required":["operation_type","status","details"],"title":"OperationLog"},"PasswordChangeRequest":{"properties":{"current_password":{"type":"string","minLength":8,"title":"Current Password"},"new_password":{"type":"string","minLength":8,"title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"PasswordChangeRequest"},"SearchResultItem":{"properties":{"id":{"type":"string","title":"Id"},"share_id":{"type":"string","title":"Share Id"},"filename":{"type":"string","title":"Filename"},"file_path":{"type":"string","title":"File Path"},"unc_path":{"type":"string","title":"Unc Path"},"size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size"},"modified_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Modified Time"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type"},"indexed_at":{"type":"string","format":"date-time","title":"Indexed At"},"relevance_score":{"type":"number","title":"Relevance Score","description":"Relevance score from full-text search engine"},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet","description":"Content preview with search context"},"share_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Name","description":"Share name for display"},"resolved_principals":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Resolved Principals","description":"Resolved ACL principals for access control"}},"type":"object","required":["id","share_id","filename","file_path","unc_path","size","modified_time","file_type","indexed_at","relevance_score"],"title":"SearchResultItem","description":"Individual search result item with relevance scoring"},"SetupStatusResponse":{"properties":{"setup_complete":{"type":"boolean","title":"Setup Complete"},"database_configured":{"type":"boolean","title":"Database Configured"},"database_url_environment_set":{"type":"boolean","title":"Database Url Environment Set","default":false},"config_storage":{"type":"string","title":"Config Storage","default":"file"},"steps_completed":{"items":{},"type":"array","title":"Steps Completed"},"required_steps":{"items":{},"type":"array","title":"Required Steps"},"optional_steps":{"items":{},"type":"array","title":"Optional Steps"},"message":{"type":"string","title":"Message","default":""},"persistence_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Persistence Info"},"license_reconfiguration_mode":{"type":"boolean","title":"License Reconfiguration Mode","default":false},"connector_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connector Id"}},"type":"object","required":["setup_complete","database_configured","steps_completed","required_steps","optional_steps"],"title":"SetupStatusResponse","description":"Setup status response model."},"ShareConfig":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"share_path":{"type":"string","title":"Share Path"},"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"created_at":{"type":"string","format":"date-time","title":"Created At","default":"2026-02-20T09:31:26.716851"},"last_crawled":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Crawled"},"last_crawl_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Crawl Duration Ms"},"last_crawl_file_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Crawl File Count"},"crawl_schedule":{"type":"string","title":"Crawl Schedule","default":"0 0 * * *"},"rules":{"additionalProperties":true,"type":"object","title":"Rules"},"status":{"$ref":"#/components/schemas/ShareStatus","default":"initializing"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"last_connection_attempt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Connection Attempt"},"realm":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Realm"},"use_kerberos":{"type":"string","title":"Use Kerberos","default":"required"},"workgroup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workgroup"},"resolve_order":{"type":"string","title":"Resolve Order","default":"host"}},"type":"object","required":["share_path","username","password"],"title":"ShareConfig"},"ShareResponse":{"properties":{"id":{"type":"string","title":"Id"},"share_path":{"type":"string","title":"Share Path"},"username":{"type":"string","title":"Username"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_crawled":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Crawled"},"last_crawl_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Crawl Duration Ms"},"last_crawl_file_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Crawl File Count"},"crawl_schedule":{"type":"string","title":"Crawl Schedule"},"rules":{"additionalProperties":true,"type":"object","title":"Rules"},"status":{"$ref":"#/components/schemas/ShareStatus"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"last_connection_attempt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Connection Attempt"},"realm":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Realm"},"use_kerberos":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Use Kerberos"},"workgroup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workgroup"},"resolve_order":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolve Order"}},"type":"object","required":["id","share_path","username","created_at","last_crawled","last_crawl_duration_ms","last_crawl_file_count","crawl_schedule","rules","status","error_message","last_connection_attempt"],"title":"ShareResponse"},"ShareStatus":{"type":"string","enum":["initializing","connecting","connected","connection_failed","crawling","processing","ready","error"],"title":"ShareStatus","description":"Status of a share"},"ShareUpdate":{"properties":{"share_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Path"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"crawl_schedule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crawl Schedule"},"rules":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Rules"},"realm":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Realm"},"use_kerberos":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Use Kerberos"},"workgroup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workgroup"},"resolve_order":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolve Order"}},"additionalProperties":false,"type":"object","title":"ShareUpdate"},"TaskStatus":{"type":"string","enum":["pending","running","completed","failed","cancelled"],"title":"TaskStatus","description":"Status of a background task"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"Bearer"},"expires_in":{"type":"integer","title":"Expires In"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"},"id_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id Token"}},"type":"object","required":["access_token","expires_in"],"title":"TokenResponse","description":"Token response from OAuth flow."},"User":{"properties":{"id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Id"},"username":{"type":"string","title":"Username"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_admin":{"type":"boolean","title":"Is Admin","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At","default":"2026-02-20T09:31:26.742182"},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login"},"entra_object_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entra Object Id"},"entra_tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entra Tenant Id"},"entra_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entra Display Name"},"entra_linked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Entra Linked At"}},"type":"object","required":["username"],"title":"User"},"UserInfoResponse":{"properties":{"object_id":{"type":"string","title":"Object Id"},"tenant_id":{"type":"string","title":"Tenant Id"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"upn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upn"},"groups_count":{"type":"integer","title":"Groups Count","default":0}},"type":"object","required":["object_id","tenant_id"],"title":"UserInfoResponse","description":"User information from OAuth token."},"UserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"username":{"type":"string","title":"Username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"is_active":{"type":"boolean","title":"Is Active"},"is_admin":{"type":"boolean","title":"Is Admin"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login"},"entra_object_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entra Object Id"},"entra_tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entra Tenant Id"},"entra_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entra Display Name"},"entra_linked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Entra Linked At"}},"type":"object","required":["id","username","is_active","is_admin","created_at","last_login"],"title":"UserResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WorkQueueStatsResponse":{"properties":{"total_items":{"type":"integer","title":"Total Items"},"pending_items":{"type":"integer","title":"Pending Items"},"claimed_items":{"type":"integer","title":"Claimed Items"},"processing_items":{"type":"integer","title":"Processing Items"},"completed_items":{"type":"integer","title":"Completed Items"},"failed_items":{"type":"integer","title":"Failed Items"},"abandoned_items":{"type":"integer","title":"Abandoned Items"}},"type":"object","required":["total_items","pending_items","claimed_items","processing_items","completed_items","failed_items","abandoned_items"],"title":"WorkQueueStatsResponse","description":"Response model for work queue statistics"},"WorkerStatsResponse":{"properties":{"total_workers":{"type":"integer","title":"Total Workers"},"active_workers":{"type":"integer","title":"Active Workers"},"stopping_workers":{"type":"integer","title":"Stopping Workers"},"stopped_workers":{"type":"integer","title":"Stopped Workers"},"workers":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Workers"}},"type":"object","required":["total_workers","active_workers","stopping_workers","stopped_workers","workers"],"title":"WorkerStatsResponse","description":"Response model for worker statistics"}},"securitySchemes":{"OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"tokenUrl":"token","scopes":{}}},"description":"Login with username and password. Click 'Authorize', enter your credentials, and click 'Authorize' to get a token."},"JWT":{"type":"apiKey","in":"header","name":"Authorization","description":"JWT Token in Authorization header. Example: Bearer "}}},"security":[{"OAuth2PasswordBearer":[]},{"JWT":[]}]} \ No newline at end of file diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index ae8b0f2..0000000 --- a/nginx.conf +++ /dev/null @@ -1,42 +0,0 @@ -server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - # Serve the UI - location / { - try_files $uri $uri/ /index.html; - } - - # Proxy API requests to backend - # The NEO_API will be replaced at container startup - location /api/ { - rewrite ^/api/(.*) /$1 break; - proxy_pass ${NEO_API}; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Don't buffer to see real-time responses - proxy_buffering off; - } - - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - } - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e6d2aa9..b7005c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "neo-ui-framework", - "version": "3.2.0", + "version": "4.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "neo-ui-framework", - "version": "3.2.0", + "version": "4.0.0", "dependencies": { "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", @@ -105,7 +105,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -371,6 +370,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.0.0" }, @@ -3271,7 +3271,6 @@ "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3281,7 +3280,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3292,7 +3290,6 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3349,7 +3346,6 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -3608,7 +3604,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3748,7 +3743,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -4300,7 +4294,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6550,7 +6543,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6560,7 +6552,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6573,7 +6564,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.65.0.tgz", "integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -7117,8 +7107,7 @@ "version": "4.1.16", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -7139,7 +7128,6 @@ "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "devOptional": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -7197,7 +7185,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7286,7 +7273,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7590,7 +7576,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -7682,7 +7667,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/package.json b/package.json index 3f3f371..9b53ce4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "neo-ui-framework", "private": true, - "version": "3.2.1", + "version": "4.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index e0444a4..201d977 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,7 +31,10 @@ function App() { if (!state.token) { return ( - + { }} // Controlled by state @@ -199,13 +202,16 @@ function App() { onAddUser={handlers.handleAddUser} onChangePassword={handlers.handleChangePassword} onRefresh={handlers.handleRefresh} + onLinkEntra={handlers.handleLinkEntraIdentity} + onUnlinkEntra={handlers.handleUnlinkEntraIdentity} monitoringOverview={state.monitoring.overview} /> } /> } /> } /> diff --git a/src/components/cards/versioning-card.tsx b/src/components/cards/versioning-card.tsx index 485860c..08587ad 100644 --- a/src/components/cards/versioning-card.tsx +++ b/src/components/cards/versioning-card.tsx @@ -51,7 +51,7 @@ export function VersioningCard({ version, helmChartVersion, className }: Version } else { setLatestUiVersion("n/a") } - } catch (error) { + } catch { setLatestUiVersion("n/a") } } @@ -121,7 +121,7 @@ export function VersioningCard({ version, helmChartVersion, className }: Version Helm Chart
- UI Framework + Neo Console
{latestUiVersion} diff --git a/src/components/charts/contentsavings.tsx b/src/components/charts/contentsavings.tsx index a592472..9c291ba 100644 --- a/src/components/charts/contentsavings.tsx +++ b/src/components/charts/contentsavings.tsx @@ -44,6 +44,16 @@ const chartConfig = { }, } satisfies ChartConfig +type ContentSavingsTooltipDatum = { + type: "content" | "savings" + size: number +} + +type ContentSavingsTooltipProps = { + active?: boolean + payload?: Array<{ payload: ContentSavingsTooltipDatum }> +} + export function ContentSavingsChart({ databaseSize, className }: ContentSavingsChartProps) { const chartData = React.useMemo(() => { if (!databaseSize) { @@ -99,8 +109,8 @@ export function ContentSavingsChart({ databaseSize, className }: ContentSavingsC }, [databaseSize]) // Custom tooltip to show size details - const CustomTooltip = ({ active, payload }: any) => { - if (active && payload && payload.length && savingsInfo) { + const CustomTooltip = ({ active, payload }: ContentSavingsTooltipProps) => { + if (active && payload && payload.length > 0 && savingsInfo) { const data = payload[0].payload const percentage = ((data.size / totalOriginalSize) * 100).toFixed(1) diff --git a/src/components/charts/sharesdistribution.tsx b/src/components/charts/sharesdistribution.tsx index 0a479ae..2a56074 100644 --- a/src/components/charts/sharesdistribution.tsx +++ b/src/components/charts/sharesdistribution.tsx @@ -52,6 +52,19 @@ const generateChartConfig = (sharesAnalytics: { share_id: string; share_name: st return config } + +type SharesDistributionTooltipDatum = { + shareName: string + shareFullPath: string + count: number + totalSize: number +} + +type SharesDistributionTooltipProps = { + active?: boolean + payload?: Array<{ payload: SharesDistributionTooltipDatum }> +} + export function SharesDistributionChart({ sharesAnalytics }: SharesDistributionChartProps) { const chartData = React.useMemo(() => { if (!sharesAnalytics || sharesAnalytics.length === 0) { @@ -86,8 +99,8 @@ export function SharesDistributionChart({ sharesAnalytics }: SharesDistributionC }, [chartData]) // Custom tooltip to show share details - const CustomTooltip = ({ active, payload }: any) => { - if (active && payload && payload.length) { + const CustomTooltip = ({ active, payload }: SharesDistributionTooltipProps) => { + if (active && payload && payload.length > 0) { const data = payload[0].payload const percentage = ((data.count / totalFiles) * 100).toFixed(1) const sizeInMB = (data.totalSize / (1024 * 1024)).toFixed(1) diff --git a/src/components/data-tables/filesT.tsx b/src/components/data-tables/filesT.tsx index 4b7bc9e..3775ad3 100644 --- a/src/components/data-tables/filesT.tsx +++ b/src/components/data-tables/filesT.tsx @@ -55,7 +55,7 @@ export function FilesTable({ onPageChange, onFileClick }: FilesTableProps) { - const rows = files?.files ?? [] + const rows = useMemo(() => files?.files ?? [], [files?.files]) const message = emptyMessage ?? (loading ? "Loading files…" : "No files available.") const showShareColumn = rows.some((file) => file.share_name || file.share_path) const columnCount = 4 + (showShareColumn ? 1 : 0) diff --git a/src/components/data-tables/logsT.tsx b/src/components/data-tables/logsT.tsx index 020d899..e99873f 100644 --- a/src/components/data-tables/logsT.tsx +++ b/src/components/data-tables/logsT.tsx @@ -1,6 +1,8 @@ // Copyright 2025 NetApp, Inc. All Rights Reserved. "use client" +import { useMemo, useState } from "react" + import { Badge } from "@/components/ui/badge" import { Table, @@ -25,6 +27,87 @@ const levelVariant: Record { + if (!isOpen) return null + return truncateLogContent(value) + }, [isOpen, value]) + + return ( +
setIsOpen(event.currentTarget.open)}> + + {label} + + {isOpen && displayValue ? ( + <> + {displayValue.truncated ? ( +

+ Display capped at {MAX_LOG_DETAIL_CHARS.toLocaleString()} characters. {displayValue.hiddenChars.toLocaleString()} additional characters omitted. +

+ ) : null} +
+            {displayValue.content}
+          
+ + ) : null} +
+ ) +} + +function LogContextDetails({ context }: { context: Record }) { + const [isOpen, setIsOpen] = useState(false) + const serializedContext = useMemo(() => { + if (!isOpen) return null + + try { + return truncateLogContent(JSON.stringify(context, null, 2)) + } catch { + return truncateLogContent("[unserializable log context]") + } + }, [context, isOpen]) + + return ( +
setIsOpen(event.currentTarget.open)}> + + View context + + {isOpen && serializedContext ? ( + <> + {serializedContext.truncated ? ( +

+ Display capped at {MAX_LOG_DETAIL_CHARS.toLocaleString()} characters. {serializedContext.hiddenChars.toLocaleString()} additional characters omitted. +

+ ) : null} +
+            {serializedContext.content}
+          
+ + ) : null} +
+ ) +} + export function LogsTable({ logs, loading = false }: LogsTableProps) { if (loading) { return ( @@ -60,23 +143,9 @@ export function LogsTable({ logs, loading = false }: LogsTableProps) { {log.message} {log.details ? ( -
- - View - -
-                        {log.details}
-                      
-
+ ) : log.context ? ( -
- - View context - -
-                        {JSON.stringify(log.context, null, 2)}
-                      
-
+ ) : ( "—" )} diff --git a/src/components/data-tables/sharesT.tsx b/src/components/data-tables/sharesT.tsx index 91fac20..d7c53f1 100644 --- a/src/components/data-tables/sharesT.tsx +++ b/src/components/data-tables/sharesT.tsx @@ -26,6 +26,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table" +import { Button } from "@/components/ui/button" interface SharesTableProps { shares: SharesResponse[] | null @@ -82,6 +83,8 @@ function getStatusBadge(status: string) { export function SharesTable({ shares, onShareClick }: SharesTableProps) { const rows = shares ?? [] + const rowsPerPage = 100 + const [currentPage, setCurrentPage] = useState(1) const [columnWidths, setColumnWidths] = useState>({ share_path: 300, @@ -155,6 +158,15 @@ export function SharesTable({ shares, onShareClick }: SharesTableProps) { } }, [handleResizeMove, handleResizeEnd]) + useEffect(() => { + const totalPages = Math.max(1, Math.ceil(rows.length / rowsPerPage)) + setCurrentPage((page) => Math.min(page, totalPages)) + }, [rows.length]) + + const totalPages = Math.max(1, Math.ceil(rows.length / rowsPerPage)) + const startIndex = (currentPage - 1) * rowsPerPage + const paginatedRows = rows.slice(startIndex, startIndex + rowsPerPage) + return ( <>
@@ -199,8 +211,8 @@ export function SharesTable({ shares, onShareClick }: SharesTableProps) { - {rows.length ? ( - rows.map((share) => ( + {paginatedRows.length ? ( + paginatedRows.map((share) => ( onShareClick(share.id)} @@ -227,6 +239,31 @@ export function SharesTable({ shares, onShareClick }: SharesTableProps) {
+ {rows.length > 0 ? ( +
+
+ Showing {startIndex + 1}-{Math.min(startIndex + paginatedRows.length, rows.length)} of {rows.length.toLocaleString()} shares · Page {currentPage} of {totalPages} +
+
+ + +
+
+ ) : null} ) } \ No newline at end of file diff --git a/src/components/data-tables/tasksT.tsx b/src/components/data-tables/tasksT.tsx index b093308..e8025bd 100644 --- a/src/components/data-tables/tasksT.tsx +++ b/src/components/data-tables/tasksT.tsx @@ -11,6 +11,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table" +import { Button } from "@/components/ui/button" interface TasksTableProps { tasks: TasksResponse[] | null @@ -73,6 +74,8 @@ export function formatDuration(startedAt: string | null, completedAt: string | n export function TasksTable({ tasks, onTaskClick }: TasksTableProps) { const rows = tasks ?? [] + const rowsPerPage = 100 + const [currentPage, setCurrentPage] = useState(1) const [columnWidths, setColumnWidths] = useState>({ name: 250, @@ -146,80 +149,117 @@ export function TasksTable({ tasks, onTaskClick }: TasksTableProps) { } }, [handleResizeMove, handleResizeEnd]) + useEffect(() => { + const totalPages = Math.max(1, Math.ceil(rows.length / rowsPerPage)) + setCurrentPage((page) => Math.min(page, totalPages)) + }, [rows.length]) + + const totalPages = Math.max(1, Math.ceil(rows.length / rowsPerPage)) + const startIndex = (currentPage - 1) * rowsPerPage + const paginatedRows = rows.slice(startIndex, startIndex + rowsPerPage) + return ( -
- - - - - Name -
handleResizeStart(e, 'name')} - /> - - - Share ID -
handleResizeStart(e, 'share_id')} - /> - - - Created -
handleResizeStart(e, 'created_at')} - /> - - - Duration -
handleResizeStart(e, 'duration')} - /> - - - Status -
handleResizeStart(e, 'status')} - /> - - - - - {rows.length ? ( - rows.map((task) => ( - onTaskClick(task)} - className="cursor-pointer hover:bg-muted/50" - > - {task.name} - -
- {task.share_id ? task.share_id : "—"} -
-
- - {new Date(task.created_at).toLocaleString()} - - - {formatDuration(task.started_at, task.completed_at)} - - {getStatusBadge(task.status)} -
- )) - ) : ( + <> +
+
+ - - No tasks available. - + + Name +
handleResizeStart(e, 'name')} + /> + + + Share ID +
handleResizeStart(e, 'share_id')} + /> + + + Created +
handleResizeStart(e, 'created_at')} + /> + + + Duration +
handleResizeStart(e, 'duration')} + /> + + + Status +
handleResizeStart(e, 'status')} + /> + - )} - -
-
+ + + {paginatedRows.length ? ( + paginatedRows.map((task) => ( + onTaskClick(task)} + className="cursor-pointer hover:bg-muted/50" + > + {task.name} + +
+ {task.share_id ? task.share_id : "—"} +
+
+ + {new Date(task.created_at).toLocaleString()} + + + {formatDuration(task.started_at, task.completed_at)} + + {getStatusBadge(task.status)} +
+ )) + ) : ( + + + No tasks available. + + + )} +
+ + + + {rows.length > 0 ? ( +
+
+ Showing {startIndex + 1}-{Math.min(startIndex + paginatedRows.length, rows.length)} of {rows.length.toLocaleString()} tasks · Page {currentPage} of {totalPages} +
+
+ + +
+
+ ) : null} + ) } \ No newline at end of file diff --git a/src/components/data-tables/usersT.tsx b/src/components/data-tables/usersT.tsx index f1a21df..2a079f2 100644 --- a/src/components/data-tables/usersT.tsx +++ b/src/components/data-tables/usersT.tsx @@ -1,6 +1,8 @@ // Copyright 2025 NetApp, Inc. All Rights Reserved. "use client" +import { useEffect, useState } from "react" + import { IconPasswordUser } from "@tabler/icons-react" @@ -27,66 +29,107 @@ interface UsersTableProps { users?: UserResponse[] | null me?: MeResponse | null onRequestPasswordChange?: () => void + onLinkEntra?: () => Promise + onUnlinkEntra?: () => Promise } export function UsersTable({ users, me, onRequestPasswordChange }: UsersTableProps) { const rows = users ?? [] + const rowsPerPage = 100 + const [currentPage, setCurrentPage] = useState(1) + + useEffect(() => { + const totalPages = Math.max(1, Math.ceil(rows.length / rowsPerPage)) + setCurrentPage((page) => Math.min(page, totalPages)) + }, [rows.length]) + + const totalPages = Math.max(1, Math.ceil(rows.length / rowsPerPage)) + const startIndex = (currentPage - 1) * rowsPerPage + const paginatedRows = rows.slice(startIndex, startIndex + rowsPerPage) return ( -
- - - - Username - Email - Active - Admin - Created - Last Login - Actions - - - - {rows.length ? ( - rows.map((user) => { - const isCurrent = me?.id === user.id - return ( - - - {user.username} - {isCurrent ? (current) : null} - - {user.email ?? "-"} - {user.is_active ? "Yes" : "No"} - {user.is_admin ? "Yes" : "No"} - {new Date(user.created_at).toLocaleString()} - - {user.last_login ? new Date(user.last_login).toLocaleString() : "—"} - - - {isCurrent && onRequestPasswordChange ? ( - - ) : null} - - - ) - }) - ) : ( + <> +
+
+ - - No users available. - + Username + Email + Active + Admin + Created + Last Login + Actions - )} - -
-
+ + + {paginatedRows.length ? ( + paginatedRows.map((user) => { + const isCurrent = me?.id === user.id + return ( + + + {user.username} + {isCurrent ? (current) : null} + + {user.email ?? "-"} + {user.is_active ? "Yes" : "No"} + {user.is_admin ? "Yes" : "No"} + {new Date(user.created_at).toLocaleString()} + + {user.last_login ? new Date(user.last_login).toLocaleString() : "—"} + + + {isCurrent && onRequestPasswordChange ? ( + + ) : null} + + + ) + }) + ) : ( + + + No users available. + + + )} + + + + + {rows.length > 0 ? ( +
+
+ Showing {startIndex + 1}-{Math.min(startIndex + paginatedRows.length, rows.length)} of {rows.length.toLocaleString()} users · Page {currentPage} of {totalPages} +
+
+ + +
+
+ ) : null} + ) } \ No newline at end of file diff --git a/src/components/dialogs/setup-wizard-dialog.tsx b/src/components/dialogs/setup-wizard-dialog.tsx index 020e283..5ce52c4 100644 --- a/src/components/dialogs/setup-wizard-dialog.tsx +++ b/src/components/dialogs/setup-wizard-dialog.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react" +import { useState, useEffect, useCallback } from "react" import { useNeoApi } from "@/hooks/useNeoApi" import { NeoApiService } from "@/services/neo-api" import { Button } from "@/components/ui/button" @@ -24,9 +24,9 @@ interface SetupWizardDialogProps { onComplete?: () => void } -type WizardStep = "LICENSE" | "M365" | "PROXY" | "SSL" | "COMPLETE_ACTION" | "CREDENTIALS" +type WizardStep = "LICENSE" | "OAUTH" | "M365" | "PROXY" | "SSL" | "COMPLETE_ACTION" | "CREDENTIALS" -const WIZARD_STEPS: WizardStep[] = ["LICENSE", "M365", "PROXY", "SSL", "COMPLETE_ACTION", "CREDENTIALS"] +const WIZARD_STEPS: WizardStep[] = ["LICENSE", "OAUTH", "M365", "PROXY", "SSL", "COMPLETE_ACTION", "CREDENTIALS"] export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizardDialogProps) { const { handlers } = useNeoApi() @@ -38,13 +38,19 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar // License State const [licenseKey, setLicenseKey] = useState("") + // OAuth State + const [oauthTenantId, setOauthTenantId] = useState("") + const [oauthClientId, setOauthClientId] = useState("") + const [oauthClientSecret, setOauthClientSecret] = useState("") + const [oauthEnabled, setOauthEnabled] = useState(false) + // M365 State const [tenantId, setTenantId] = useState("") const [clientId, setClientId] = useState("") const [clientSecret, setClientSecret] = useState("") - const [connectorId] = useState("netappneo") - const [connectorName, setConnectorName] = useState("NetApp NEO Connector") - const [connectorDescription] = useState("The connector contains information contained in the on premises or on-prem file share server...") + const [connectorId, setConnectorId] = useState("netappneo-01") + const [connectorName, setConnectorName] = useState("NetApp Neo Connector 01") + const [connectorDescription, setConnectorDescription] = useState("The connector give access to data from on premises or on-prem file share servers.") // Proxy State const [proxyUrl, setProxyUrl] = useState("") @@ -52,7 +58,7 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar const [proxyPassword, setProxyPassword] = useState("") // SSL State - const [verifySsl, setVerifySsl] = useState(true) + const [verifySsl, setVerifySsl] = useState(false) const [caCertificate, setCaCertificate] = useState("") @@ -65,6 +71,13 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar const [confirmPassword, setConfirmPassword] = useState("") const [passwordError, setPasswordError] = useState(null) + const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message) { + return error.message + } + return fallback + } + useEffect(() => { if (!open) { // Reset state when dialog closes/reopens if needed @@ -73,27 +86,40 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar } }, [open]) - useEffect(() => { - if (completionCountdown !== null && completionCountdown > 0) { - const timer = setTimeout(() => setCompletionCountdown(completionCountdown - 1), 1000) - return () => clearTimeout(timer) - } else if (completionCountdown === 0) { - fetchCredentials() - } - }, [completionCountdown]) - const fetchCredentials = async () => { + const fetchCredentials = useCallback(async () => { setIsLoading(true) try { const result = await handlers.getInitialCredentials() setCredentials({ username: result.username, password: result.password }) setStep("CREDENTIALS") - } catch (err) { + } catch { setError("Failed to fetch credentials. Please try again.") } finally { setIsLoading(false) } - } + }, [handlers]) + + useEffect(() => { + if (completionCountdown !== null && completionCountdown > 0) { + const timer = setTimeout(() => setCompletionCountdown(completionCountdown - 1), 1000) + return () => clearTimeout(timer) + } + if (completionCountdown === 0) { + setCompletionCountdown(null) + void fetchCredentials() + } + }, [completionCountdown, fetchCredentials]) + + useEffect(() => { + if (completionCountdown !== null && completionCountdown > 0) { + const timer = setTimeout(() => setCompletionCountdown(completionCountdown - 1), 1000) + return () => clearTimeout(timer) + } + if (completionCountdown === 0) { + void fetchCredentials() + } + }, [completionCountdown, fetchCredentials]) const handleNext = () => { setError(null) @@ -102,6 +128,9 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar case "LICENSE": submitLicense() break + case "OAUTH": + setStep("M365") + break case "M365": setStep("PROXY") break @@ -120,6 +149,9 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar setError(null) setSuccessMessage(null) switch (step) { + case "OAUTH": + setStep("M365") + break case "M365": setStep("PROXY") break @@ -146,13 +178,39 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar setSuccessMessage("License configured successfully.") setTimeout(() => { setSuccessMessage(null) - setStep("M365") + setStep("OAUTH") }, 1000) } else { setError(res.message || "Failed to configure license.") } - } catch (e: any) { - setError(e.message || "An error occurred.") + } catch (error: unknown) { + setError(getErrorMessage(error, "An error occurred.")) + } finally { + setIsLoading(false) + } + } + + const submitOauth = async () => { + setIsLoading(true) + const payload = { + tenant_id: oauthTenantId, + client_id: oauthClientId, + client_secret: oauthClientSecret, + enabled: oauthEnabled + } + try { + const res = await handlers.setupOauth(payload) + if (res.success) { + setSuccessMessage("OAuth configured successfully.") + setTimeout(() => { + setSuccessMessage(null) + setStep("M365") + }, 1000) + } else { + setError(res.message || "Failed to configure OAuth.") + } + } catch (error: unknown) { + setError(getErrorMessage(error, "An error occurred.")) } finally { setIsLoading(false) } @@ -179,8 +237,8 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar } else { setError(res.message || "Failed to configure M365.") } - } catch (e: any) { - setError(e.message || "An error occurred.") + } catch (error: unknown) { + setError(getErrorMessage(error, "An error occurred.")) } finally { setIsLoading(false) } @@ -215,8 +273,8 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar setError(res.message || "Failed to complete setup.") setIsLoading(false) } - } catch (e: any) { - setError(e.message || "An error occurred.") + } catch (error: unknown) { + setError(getErrorMessage(error, "An error occurred.")) setIsLoading(false) } } @@ -246,8 +304,8 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar onOpenChange(false) // Reload to force re-login or dashboard refresh window.location.reload() - } catch (e: any) { - setPasswordError(e.message || "Failed to update password.") + } catch (error: unknown) { + setPasswordError(getErrorMessage(error, "Failed to update password.")) } finally { setIsLoading(false) } @@ -284,6 +342,29 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar ) + case "OAUTH": + return ( +
+
+ + +
+
+
+ + setOauthTenantId(e.target.value)} /> +
+
+ + setOauthClientId(e.target.value)} /> +
+
+ + setOauthClientSecret(e.target.value)} /> +
+
+
+ ) case "M365": return (
@@ -304,6 +385,19 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar setConnectorName(e.target.value)} />
+
+ + setConnectorId(e.target.value)} /> +
+
+ +