From 0a3e42651d314e9edfef8a6428f8ba01424c6327 Mon Sep 17 00:00:00 2001 From: romdalf Date: Thu, 19 Feb 2026 12:31:08 +0100 Subject: [PATCH 01/14] moving from NGINX to Caddy to support dynamic reload at network level --- CONTRIBUTOR.md | 2 +- Caddyfile | 31 ++++++++++++++++++++ Dockerfile | 19 +++---------- QUICKSTART.md | 2 +- README.md | 2 +- SECURITY.md | 74 +++++++++++++++++++++++------------------------- caching-specs.md | 48 +++++++++++++++++++++++++++++++ entrypoint.sh | 48 ++++++------------------------- nginx.conf | 42 --------------------------- 9 files changed, 130 insertions(+), 138 deletions(-) create mode 100644 Caddyfile create mode 100644 caching-specs.md delete mode 100644 nginx.conf 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..261859b --- /dev/null +++ b/Caddyfile @@ -0,0 +1,31 @@ +:80 { + # Serve static UI files + root * /srv + try_files {path} /index.html + file_server + + # 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} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + flush_interval -1 + } + } + + # Cache static assets + @static path *.js *.css *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot + header @static Cache-Control "public, immutable, max-age=31536000" + + # Security headers + header X-Frame-Options "SAMEORIGIN" + header X-Content-Type-Options "nosniff" + header X-XSS-Protection "1; mode=block" + + # 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 8c91566..5fbf55f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -103,7 +103,7 @@ This catches many security issues at compile time. 1. **Minimal Base Image** ```dockerfile - FROM nginx:1.27-alpine # Alpine for smaller attack surface + FROM docker.io/library/caddy:2-alpine # Alpine for smaller attack surface ``` 2. **Non-Root User** @@ -116,7 +116,7 @@ This catches many security issues at compile time. 3. **Read-Only Filesystem** (where possible) ```dockerfile VOLUME /tmp - VOLUME /var/cache/nginx + VOLUME /data/caddy ``` 4. **No Unnecessary Packages** @@ -132,43 +132,40 @@ This catches many security issues at compile time. trivy image neo-ui-framework ``` -#### nginx Configuration +#### Caddy Configuration -**Security Headers** (add to `nginx.conf`): +**Security Headers** (add to `Caddyfile`): -```nginx +```caddyfile # 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; -add_header Referrer-Policy "strict-origin-when-cross-origin" always; -add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; +header X-Frame-Options "SAMEORIGIN" +header X-Content-Type-Options "nosniff" +header X-XSS-Protection "1; mode=block" +header Referrer-Policy "strict-origin-when-cross-origin" +header Permissions-Policy "geolocation=(), microphone=(), camera=()" # Content Security Policy -add_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:;" always; +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:;" -# HSTS (if using HTTPS) -add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +# HSTS (automatically managed by Caddy when using HTTPS) ``` **API Proxy Security**: -```nginx -location /api { - # Restrict to known backend - proxy_pass $NEO_API; - - # Don't expose backend details - proxy_hide_header X-Powered-By; - proxy_hide_header Server; - - # Timeout limits - proxy_connect_timeout 5s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - - # Size limits - client_max_body_size 10M; +```caddyfile +handle /api/* { + uri strip_prefix /api + reverse_proxy {$NEO_API} { + # Don't expose backend details + header_down -X-Powered-By + header_down -Server + + # Timeout limits + transport http { + dial_timeout 5s + response_header_timeout 60s + } + } } ``` @@ -177,14 +174,15 @@ location /api { **Production Requirements**: 1. **Always use HTTPS** in production -2. **TLS 1.2+** minimum -3. **Strong cipher suites**: - ```nginx - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'; - ssl_prefer_server_ciphers on; +2. **TLS 1.2+** minimum (Caddy default) +3. **Automatic HTTPS**: Caddy automatically provisions and renews TLS certificates via Let's Encrypt when a domain name is used: + ```caddyfile + your-domain.com { + # Caddy automatically handles TLS + # No manual cipher configuration needed + } ``` -4. **Certificate Management**: Use Let's Encrypt or enterprise CA +4. **Certificate Management**: Caddy auto-manages Let's Encrypt certificates; enterprise CAs can be configured via the `tls` directive ### Runtime Hardening @@ -245,7 +243,7 @@ location /api { #### Access Logs -Review nginx access logs for: +Review Caddy access logs for: - Unusual request patterns - Failed authentication attempts - Suspicious API calls @@ -360,7 +358,7 @@ Before deploying to production: - [ ] All dependencies audited (`npm audit`) - [ ] Secrets removed from code and configs - [ ] HTTPS enabled with valid certificate -- [ ] Security headers configured in nginx +- [ ] Security headers configured in Caddy - [ ] CSP policy defined and tested - [ ] Error messages don't leak sensitive info - [ ] Rate limiting implemented 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/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/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 From b6d8f20c4bed7488715b6b59282c4baf79214af3 Mon Sep 17 00:00:00 2001 From: romdalf Date: Thu, 19 Feb 2026 13:27:15 +0100 Subject: [PATCH 02/14] fixing logout race conditions not cleaning the token from the localStorage --- Caddyfile | 10 +++++++++- src/hooks/useNeoApi.ts | 7 +++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Caddyfile b/Caddyfile index 261859b..9c37a08 100644 --- a/Caddyfile +++ b/Caddyfile @@ -17,14 +17,22 @@ } } - # Cache static assets + # 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' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;" # Gzip compression encode gzip diff --git a/src/hooks/useNeoApi.ts b/src/hooks/useNeoApi.ts index 344e8e9..e9fd161 100644 --- a/src/hooks/useNeoApi.ts +++ b/src/hooks/useNeoApi.ts @@ -1024,6 +1024,13 @@ export function useNeoApi() { // Always clear local state regardless of server response clearSystemData() apiRef.current.clearCache() + // Remove token from localStorage synchronously to prevent stale session + // on browser refresh (the useEffect cleanup is async and may not run in time) + try { + localStorage.removeItem("neo_token") + } catch (e) { + appLogger.error("Failed to remove token from localStorage", e instanceof Error ? e.message : "Unknown error") + } setToken(null) toast.success("Logged out successfully") appLogger.info("User logged out successfully") From db3e164a25a0967c90d39e550a28d49485e8a962 Mon Sep 17 00:00:00 2001 From: romdalf Date: Thu, 19 Feb 2026 14:04:58 +0100 Subject: [PATCH 03/14] fixing caddy configuration and login password autocompletion field --- Caddyfile | 16 ++++++++-------- docker-compose.yml | 14 ++------------ src/components/pages/login-page.tsx | 2 ++ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Caddyfile b/Caddyfile index 9c37a08..1d69c34 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,9 +1,4 @@ :80 { - # Serve static UI files - root * /srv - try_files {path} /index.html - file_server - # Reverse proxy API requests to backend # The NEO_API env var is resolved dynamically by Caddy handle /api/* { @@ -11,12 +6,17 @@ reverse_proxy {$NEO_API} { header_up Host {host} header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} 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" @@ -32,7 +32,7 @@ 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' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;" + 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/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/src/components/pages/login-page.tsx b/src/components/pages/login-page.tsx index 2dfd41a..ae07537 100644 --- a/src/components/pages/login-page.tsx +++ b/src/components/pages/login-page.tsx @@ -63,6 +63,7 @@ export default function LoginPage({ onConnect }: LoginPageProps) { value={email} onChange={(e) => setEmail(e.target.value)} required + autoComplete="username" className="border-neutral-800 bg-neutral-950 text-neutral-50 placeholder:text-neutral-500 focus-visible:ring-neutral-700" /> @@ -76,6 +77,7 @@ export default function LoginPage({ onConnect }: LoginPageProps) { value={password} onChange={(e) => setPassword(e.target.value)} required + autoComplete="current-password" className="border-neutral-800 bg-neutral-950 text-neutral-50 focus-visible:ring-neutral-700" /> From adcf60a20de659aa8ee143521c995872f30156cb Mon Sep 17 00:00:00 2001 From: romdalf Date: Sat, 21 Feb 2026 12:38:44 +0100 Subject: [PATCH 04/14] adding MCP configuratin tab with token retrieval via OAuth --- neocoreapi/openapi.json | 1 + src/App.tsx | 9 +- src/components/data-tables/usersT.tsx | 66 +++++++--- .../dialogs/setup-wizard-dialog.tsx | 79 +++++++++++- src/components/pages/login-page.tsx | 1 + src/components/pages/settings.tsx | 115 ++++++++++++++++-- src/components/pages/users.tsx | 36 +++++- src/components/ui/accordion.tsx | 2 +- src/hooks/useNeoApi.ts | 22 ++++ src/services/api/auth.ts | 57 +++++++++ src/services/api/system.ts | 17 +++ src/services/models.tsx | 73 ++++++++++- src/services/neo-api.tsx | 46 +++++++ 13 files changed, 489 insertions(+), 35 deletions(-) create mode 100644 neocoreapi/openapi.json 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/src/App.tsx b/src/App.tsx index e0444a4..5a20f3e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,7 +31,9 @@ function App() { if (!state.token) { return ( - + { }} // Controlled by state @@ -199,13 +201,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/data-tables/usersT.tsx b/src/components/data-tables/usersT.tsx index f1a21df..7eb6921 100644 --- a/src/components/data-tables/usersT.tsx +++ b/src/components/data-tables/usersT.tsx @@ -1,17 +1,19 @@ // Copyright 2025 NetApp, Inc. All Rights Reserved. "use client" -import { - IconPasswordUser +import { + IconPasswordUser, + IconLink, + IconUnlink } from "@tabler/icons-react" -import type { - MeResponse, - UserResponse +import type { + MeResponse, + UserResponse } from "@/services/neo-api" -import { - Button +import { + Button } from "@/components/ui/button" import { @@ -27,9 +29,11 @@ interface UsersTableProps { users?: UserResponse[] | null me?: MeResponse | null onRequestPasswordChange?: () => void + onLinkEntra?: () => void + onUnlinkEntra?: () => void } -export function UsersTable({ users, me, onRequestPasswordChange }: UsersTableProps) { +export function UsersTable({ users, me, onRequestPasswordChange, onLinkEntra, onUnlinkEntra }: UsersTableProps) { const rows = users ?? [] return ( @@ -64,16 +68,42 @@ export function UsersTable({ users, me, onRequestPasswordChange }: UsersTablePro {user.last_login ? new Date(user.last_login).toLocaleString() : "—"} - {isCurrent && onRequestPasswordChange ? ( - - ) : null} +
+ {isCurrent && onRequestPasswordChange ? ( + + ) : null} + {isCurrent && onLinkEntra && onUnlinkEntra ? ( + user.entra_object_id ? ( + + ) : ( + + ) + ) : null} +
) diff --git a/src/components/dialogs/setup-wizard-dialog.tsx b/src/components/dialogs/setup-wizard-dialog.tsx index 020e283..be4b4c5 100644 --- a/src/components/dialogs/setup-wizard-dialog.tsx +++ b/src/components/dialogs/setup-wizard-dialog.tsx @@ -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,6 +38,12 @@ 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("") @@ -102,6 +108,9 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar case "LICENSE": submitLicense() break + case "OAUTH": + setStep("M365") + break case "M365": setStep("PROXY") break @@ -120,6 +129,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,7 +158,7 @@ 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.") @@ -158,6 +170,32 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar } } + 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 (e: any) { + setError(e.message || "An error occurred.") + } finally { + setIsLoading(false) + } + } + const submitM365 = async () => { setIsLoading(true) const payload: SetupGraphRequest = { @@ -284,6 +322,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 (
@@ -392,9 +453,18 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar case "LICENSE": return ( ) + case "OAUTH": + return ( +
+ + +
+ ) case "M365": return (
@@ -440,6 +510,7 @@ export function SetupWizardDialog({ open, onOpenChange, onComplete }: SetupWizar const getTitle = () => { switch (step) { case "LICENSE": return "Setup Wizard: License" + case "OAUTH": return "Setup Wizard: OAuth Setup (Optional)" case "M365": return "Setup Wizard: M365 Copilot (Optional)" case "PROXY": return "Setup Wizard: Proxy (Optional)" case "SSL": return "Setup Wizard: SSL (Optional)" diff --git a/src/components/pages/login-page.tsx b/src/components/pages/login-page.tsx index ae07537..a6b76f8 100644 --- a/src/components/pages/login-page.tsx +++ b/src/components/pages/login-page.tsx @@ -95,6 +95,7 @@ export default function LoginPage({ onConnect }: LoginPageProps) { +
) diff --git a/src/components/pages/settings.tsx b/src/components/pages/settings.tsx index 148282e..f1014c5 100644 --- a/src/components/pages/settings.tsx +++ b/src/components/pages/settings.tsx @@ -17,7 +17,7 @@ import { useSettings } from "@/context/settings-context" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { Switch } from "@/components/ui/switch" import { Textarea } from "@/components/ui/textarea" @@ -49,6 +49,7 @@ import { Save } from "lucide-react" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import type { LogLevel } from "@/services/app-logger" import type { MonitoringOverviewResponse } from "@/services/neo-api" +import type { McpInfoResponse } from "@/services/models" import { OverviewCard } from "@/components/cards/overview-card" import { useSearchParams } from "react-router-dom" import { useNeoApi } from "@/hooks/useNeoApi" @@ -57,15 +58,12 @@ import { Separator } from "@/components/ui/separator" interface SettingsProps { monitoringOverview: MonitoringOverviewResponse | null - cacheStats?: { - sizeBytes: number - items: number - } + state: ReturnType["state"] + handlers: ReturnType["handlers"] } -export default function Settings({ monitoringOverview }: SettingsProps) { +export default function Settings({ monitoringOverview, state, handlers }: SettingsProps) { const { monitoringTtl, filesTtl, cacheMaxSize, logLevel, updateSettings } = useSettings() - const { state, handlers } = useNeoApi() const [searchParams, setSearchParams] = useSearchParams() const [localMonitoringTtl, setLocalMonitoringTtl] = useState(monitoringTtl) @@ -121,6 +119,23 @@ export default function Settings({ monitoringOverview }: SettingsProps) { setLocalLogLevel(logLevel) }, [monitoringTtl, filesTtl, cacheMaxSize, logLevel]) + const [mcpInfo, setMcpInfo] = useState(null) + + useEffect(() => { + const fetchMcpInfo = async () => { + try { + const info = await handlers.getMcpInfo() + console.log("Successfully fetched MCP Info:", info) + setMcpInfo(info) + } catch (error) { + console.error("Failed to fetch MCP Info. Error:", error) + } + } + if (state.token) { + fetchMcpInfo() + } + }, [state.token]) + const handleSave = () => { updateSettings({ monitoringTtl: Number(localMonitoringTtl), @@ -332,6 +347,7 @@ export default function Settings({ monitoringOverview }: SettingsProps) { > Neo Core Setup + Neo MCP Cache Configuration Logging Configuration @@ -829,6 +845,91 @@ export default function Settings({ monitoringOverview }: SettingsProps) {
+ +
+ + + + + Neo MCP Information + + + View the Model Context Protocol settings and capabilities for this instance. + + + + {mcpInfo ? ( +
+
+
+

Name

+

{mcpInfo.name}

+
+
+

Version

+

{mcpInfo.version}

+
+
+

Protocol Version

+

{mcpInfo.protocol_version}

+
+
+

Transport

+

{mcpInfo.transport.replace('-', ' ')}

+
+
+

OAuth Enabled

+ + {mcpInfo.oauth_enabled ? "Yes" : "No"} + +
+
+ +
+
+

Available Tools

+
+ {mcpInfo.tools.map((tool) => ( + + {tool} + + ))} +
+
+
+

Endpoints

+
+ {Object.entries(mcpInfo.endpoints).map(([key, url]) => ( +
+ {key.replace('_', ' ')} +
+ {url as string} + +
+
+ ))} +
+
+
+
+ ) : ( +
+ Fetching MCP Information... +
+ )} +
+ {mcpInfo?.oauth_enabled && ( + + + + )} +
+
+
diff --git a/src/components/pages/users.tsx b/src/components/pages/users.tsx index ddaa719..eeb0f4d 100644 --- a/src/components/pages/users.tsx +++ b/src/components/pages/users.tsx @@ -75,10 +75,12 @@ interface UsersProps { }) => Promise onChangePassword: (payload: { current_password: string; new_password: string }) => Promise onRefresh: () => Promise + onLinkEntra: () => Promise + onUnlinkEntra: () => Promise monitoringOverview: MonitoringOverviewResponse | null } -export default function Users({ users, me, onAddUser, onChangePassword, onRefresh, monitoringOverview }: UsersProps) { +export default function Users({ users, me, onAddUser, onChangePassword, onRefresh, onLinkEntra, onUnlinkEntra, monitoringOverview }: UsersProps) { const [alertMessage, setAlertMessage] = useState(null) const [alertVariant, setAlertVariant] = useState<"success" | "error">("success") const [passwordDialogOpen, setPasswordDialogOpen] = useState(false) @@ -166,6 +168,30 @@ export default function Users({ users, me, onAddUser, onChangePassword, onRefres [newEmail, newIsActive, newIsAdmin, newUserPassword, newUsername, onAddUser, resetAddForm] ) + const handleLinkEntra = useCallback(async () => { + try { + await onLinkEntra() + await onRefresh() + setAlertVariant("success") + setAlertMessage("Entra ID linked successfully") + } catch (err) { + setAlertVariant("error") + setAlertMessage(err instanceof Error ? err.message : "Failed to link Entra ID") + } + }, [onLinkEntra, onRefresh]) + + const handleUnlinkEntra = useCallback(async () => { + try { + await onUnlinkEntra() + await onRefresh() + setAlertVariant("success") + setAlertMessage("Entra ID unlinked successfully") + } catch (err) { + setAlertVariant("error") + setAlertMessage(err instanceof Error ? err.message : "Failed to unlink Entra ID") + } + }, [onUnlinkEntra, onRefresh]) + useEffect(() => { if (users === null) { @@ -234,7 +260,13 @@ export default function Users({ users, me, onAddUser, onChangePassword, onRefres ) : null} - +
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx index 2163823..45a8a0d 100644 --- a/src/components/ui/accordion.tsx +++ b/src/components/ui/accordion.tsx @@ -34,7 +34,7 @@ function AccordionTrigger({ svg]:rotate-180", + "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", className )} {...props} diff --git a/src/hooks/useNeoApi.ts b/src/hooks/useNeoApi.ts index e9fd161..3bee04b 100644 --- a/src/hooks/useNeoApi.ts +++ b/src/hooks/useNeoApi.ts @@ -37,6 +37,7 @@ import { type SetupGraphRequest, type SetupGraphResponse, type SetupFactoryResetRequest, + type Body_configure_oauth_api_v1_setup_oauth_post, } from "@/services/neo-api" @@ -1091,6 +1092,12 @@ export function useNeoApi() { }, [] ), + setupOauth: useCallback( + async (request: Body_configure_oauth_api_v1_setup_oauth_post) => { + return apiRef.current.setupOauth(request) + }, + [] + ), resetSetup: useCallback(async () => { return apiRef.current.resetSetup() }, []), @@ -1103,6 +1110,21 @@ export function useNeoApi() { completeSetup: useCallback(async () => { return apiRef.current.completeSetup() }, []), + getMcpInfo: useCallback(async () => { + if (!token) throw new AuthenticationError() + return apiRef.current.getMcpInfo(token) + }, [token]), + handleOAuthLogin: useCallback(async () => { + await apiRef.current.handleOAuthLogin() + }, []), + handleLinkEntraIdentity: useCallback(async () => { + if (!token) throw new AuthenticationError() + return apiRef.current.linkEntraIdentity(token, { user_id: me?.id }) + }, [token, me?.id]), + handleUnlinkEntraIdentity: useCallback(async () => { + if (!token) throw new AuthenticationError() + return apiRef.current.unlinkEntraIdentity(token, { user_id: me?.id }) + }, [token, me?.id]), }, } } \ No newline at end of file diff --git a/src/services/api/auth.ts b/src/services/api/auth.ts index c560ceb..c5f7b2b 100644 --- a/src/services/api/auth.ts +++ b/src/services/api/auth.ts @@ -83,4 +83,61 @@ export class AuthApiClient extends BaseApiClient { ) } } + + async initiateOAuthLogin() { + appLogger.debug("Initiating OAuth login") + try { + const response = await this.request<{ authorization_url: string; state?: string }>("/auth/login") + if (response && response.authorization_url) { + window.location.href = response.authorization_url + } else { + throw new Error("Authorization URL not found in response") + } + } catch (error) { + appLogger.error("Failed to initiate OAuth login", error instanceof Error ? error.message : "Unknown error") + throw error + } + } + + getOAuthConfig() { + return this.request("/auth/config") + } + + getUserInfo(token: string) { + return this.requestWithToken("/auth/userinfo", token) + } + + getGroups(token: string) { + return this.requestWithToken("/auth/groups", token) + } + + validateToken(token: string) { + return this.requestWithToken("/auth/validate", token) + } + + getWhoAmI(token: string) { + return this.requestWithToken("/auth/whoami", token) + } + + linkEntraIdentity(token: string, payload: any) { + return this.requestWithToken("/auth/link-entra", token, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }) + } + + unlinkEntraIdentity(token: string, payload: any) { + return this.requestWithToken("/auth/unlink-entra", token, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }) + } + + refreshAccessToken(token: string) { + return this.requestWithToken("/auth/refresh", token, { + method: "POST" + }) + } } \ No newline at end of file diff --git a/src/services/api/system.ts b/src/services/api/system.ts index 8be5d03..7fc8a89 100644 --- a/src/services/api/system.ts +++ b/src/services/api/system.ts @@ -15,6 +15,7 @@ import type { SetupFactoryResetRequest, SetupCompleteResponse, InitialCredentialsResponse, + McpInfoResponse, } from "@/services/models" export class SystemApiClient extends BaseApiClient { @@ -105,4 +106,20 @@ export class SystemApiClient extends BaseApiClient { appLogger.debug("Fetching database size information") return this.requestWithToken("/database/size", token) } + + setupOauth(payload: any) { + appLogger.debug("Setting up OAuth") + return this.request("/api/v1/setup/oauth", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload) + }) + } + + getMcpInfo(token: string) { + appLogger.debug("Fetching MCP info") + return this.requestWithToken("/mcp/info", token) + } } \ No newline at end of file diff --git a/src/services/models.tsx b/src/services/models.tsx index 38d05c4..b989bd2 100644 --- a/src/services/models.tsx +++ b/src/services/models.tsx @@ -489,6 +489,8 @@ export interface UserResponse { is_admin: boolean created_at: string last_login: string | null + entra_object_id?: string | null + entra_display_name?: string | null } // Content Search Models @@ -547,4 +549,73 @@ export interface MeResponse { is_admin: boolean created_at: string last_login: string | null -} \ No newline at end of file + entra_object_id?: string | null + entra_display_name?: string | null +}export interface Body_configure_oauth_api_v1_setup_oauth_post { + tenant_id?: string + client_id?: string + client_secret?: string + audience?: string + enabled?: boolean +} + +export interface EntraLinkRequest { + user_id: number +} + +export interface EntraLinkResponse { + success: boolean + message: string + user_id: number + username: string + entra_object_id: string + entra_display_name?: string +} + +export interface EntraUnlinkRequest { + user_id: number +} + +export interface EntraUnlinkResponse { + success: boolean + message: string + user_id: number +} + +export interface GroupsResponse { + object_id: string + groups: string[] + groups_count: number +} + +export interface OAuthConfigResponse { + enabled: boolean + provider?: string + tenant_id?: string + client_id?: string + audience?: string + authorization_endpoint?: string + token_endpoint?: string +} + +export interface UserInfoResponse { + object_id: string + tenant_id: string + display_name?: string + email?: string + upn?: string + groups_count?: number +} + +export interface McpInfoResponse { + name: string + version: string + protocol_version: string + transport: string + oauth_enabled: boolean + tools: string[] + endpoints: { + mcp: string + oauth_metadata: string + } +} diff --git a/src/services/neo-api.tsx b/src/services/neo-api.tsx index 98ca49f..fa78559 100644 --- a/src/services/neo-api.tsx +++ b/src/services/neo-api.tsx @@ -38,6 +38,7 @@ import type { SetupFactoryResetRequest, SetupCompleteResponse, InitialCredentialsResponse, + Body_configure_oauth_api_v1_setup_oauth_post, } from "./models" import { BaseApiClient, AuthenticationError, AuthorizationError } from "./api/base" import { AuthApiClient } from "./api/auth" @@ -93,6 +94,7 @@ export type { SetupFactoryResetRequest, SetupCompleteResponse, InitialCredentialsResponse, + Body_configure_oauth_api_v1_setup_oauth_post, } export { AuthenticationError, AuthorizationError } @@ -144,6 +146,10 @@ export class NeoApiService extends BaseApiClient { return this.system.setupLicense(request) } + async setupOauth(payload: Body_configure_oauth_api_v1_setup_oauth_post) { + return this.system.setupOauth(payload) + } + async setupGraph(request: SetupGraphRequest): Promise { return this.system.setupGraph(request) } @@ -168,6 +174,42 @@ export class NeoApiService extends BaseApiClient { return this.auth.authenticate(username, password) } + async handleOAuthLogin() { + return this.auth.initiateOAuthLogin() + } + + getOAuthConfig() { + return this.auth.getOAuthConfig() + } + + getUserInfo(token: string) { + return this.auth.getUserInfo(token) + } + + getGroups(token: string) { + return this.auth.getGroups(token) + } + + validateToken(token: string) { + return this.auth.validateToken(token) + } + + getWhoAmI(token: string) { + return this.auth.getWhoAmI(token) + } + + linkEntraIdentity(token: string, payload: any) { + return this.auth.linkEntraIdentity(token, payload) + } + + unlinkEntraIdentity(token: string, payload: any) { + return this.auth.unlinkEntraIdentity(token, payload) + } + + refreshAccessToken(token: string) { + return this.auth.refreshAccessToken(token) + } + logout(token: string) { return this.auth.logout(token) } @@ -192,6 +234,10 @@ export class NeoApiService extends BaseApiClient { return this.dataLoader.load("setupStatus", () => this.system.getSetupStatus(), this.monitoringTtl) } + getMcpInfo(token: string) { + return this.dataLoader.load(`mcpInfo:${token}`, () => this.system.getMcpInfo(token), this.monitoringTtl) + } + getUsers(token: string) { return this.dataLoader.load(`users:${token}`, () => this.users.getUsers(token)) } From bd5882e79222b4505c0c7a698f24e44ed1d6065e Mon Sep 17 00:00:00 2001 From: romdalf Date: Sat, 21 Feb 2026 12:47:49 +0100 Subject: [PATCH 05/14] adding MCP configuratin tab with token retrieval via OAuth --- src/components/pages/settings.tsx | 268 +++++++++++++++--------------- 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/src/components/pages/settings.tsx b/src/components/pages/settings.tsx index f1014c5..fdcd843 100644 --- a/src/components/pages/settings.tsx +++ b/src/components/pages/settings.tsx @@ -680,10 +680,10 @@ export default function Settings({ monitoringOverview, state, handlers }: Settin - + {/* */} {/* Reset and Complete Section */} - {(setupStatus && ( + {setupStatus && (
{resetResult && ( @@ -703,145 +703,145 @@ export default function Settings({ monitoringOverview, state, handlers }: Settin )} -
- {setupStatus.setup_complete ? ( - <> - +
+ )} + - - - - Initial Admin Credentials - - Please change this password immediately after logging in. This endpoint will be disabled after first login. - - -
-
- Username: - {credentialsInit?.username} -
-
- Password: -
- {credentialsInit?.password} - -
-
+ {setupStatus && ( + + {setupStatus.setup_complete ? ( + <> + + + + + + Initial Admin Credentials + + Please change this password immediately after logging in. This endpoint will be disabled after first login. + + +
+
+ Username: + {credentialsInit?.username} +
+
+ Password: +
+ {credentialsInit?.password} +
+
+
- + -
-
- - setNewPassword(e.target.value)} - placeholder="Enter new password" - /> -
-
- - setConfirmPassword(e.target.value)} - placeholder="Confirm new password" - /> -
- {passwordError && ( -

{passwordError}

- )} - -
- - - -
-
+
+
+ + setNewPassword(e.target.value)} + placeholder="Enter new password" + /> +
+
+ + setConfirmPassword(e.target.value)} + placeholder="Confirm new password" + /> +
+ {passwordError && ( +

{passwordError}

+ )} + +
+ + + + +
- - - - - - Credentials Expired - - - The initial admin credentials have already been used and cannot be recovered. -

- If you have lost your password, you will need to perform a factory reset to restore access. -
-
- - - -
-
- - ) : ( - <> - + + + + + + Credentials Expired + + + The initial admin credentials have already been used and cannot be recovered. +

+ If you have lost your password, you will need to perform a factory reset to restore access. +
+
+ + + +
+
+ + ) : ( + <> + - {setupStatus.steps_completed.includes("license") && ( - - - - - - - Complete Setup & Restart? - - This will conclude the setup of Neo Core and trigger a restart of the container with the current configuration. - - - - - - - - - )} - + {setupStatus.steps_completed.includes("license") && ( + + + + + + + Complete Setup & Restart? + + This will conclude the setup of Neo Core and trigger a restart of the container with the current configuration. + + + + + + + + )} -
- - ))} - - + + )} + + )}
From 1ebe77fc3fe1d3a81e34649671fe2e11da83f894 Mon Sep 17 00:00:00 2001 From: romdalf Date: Sat, 21 Feb 2026 13:48:08 +0100 Subject: [PATCH 06/14] adding MCP configuration tab with token retrieval via OAuth plus a a button at the login page level too --- src/App.tsx | 1 + src/components/pages/login-page.tsx | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 5a20f3e..201d977 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,6 +33,7 @@ function App() { Promise + onOAuthLogin?: () => Promise | void } -export default function LoginPage({ onConnect }: LoginPageProps) { +export default function LoginPage({ onConnect, onOAuthLogin }: LoginPageProps) { const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [isLoading, setIsLoading] = useState(false) @@ -93,6 +94,26 @@ export default function LoginPage({ onConnect }: LoginPageProps) { > {isLoading ? "Logging in..." : "Login"} + {onOAuthLogin && ( + <> +
+
+
+
+
+ Or +
+
+ + + )} From e8e5932edb90769cc7a6926403425462ed7c53a6 Mon Sep 17 00:00:00 2001 From: romdalf Date: Tue, 21 Apr 2026 10:07:26 +0200 Subject: [PATCH 07/14] streamlining security.md --- SECURITY.md | 390 ++++------------------------------------------------ 1 file changed, 23 insertions(+), 367 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 5fbf55f..f2fc3d3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,379 +1,35 @@ -# 🔒 Security Policy - -NetApp takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [NetApp](https://github.com/NetApp). - -## Supported Versions - -The `main` branch and the latest tagged release receive security updates. Older releases should be upgraded to the most recent patch before requesting assistance. +# Security ## Reporting Potential Security Issues -If you believe you have found a potential security vulnerability in any NetApp-owned repository, please report it to us through coordinated disclosure. +If you have encountered a potential security vulnerability in this project, +please **report it via [Security and quality](https://github.com/NetApp/Innovation-Labs/security) and not via an GitHub issue**. -1. **Do not disclose publicly.** -2. Email the NetApp Neo security contact (ng-innovation-labs-git[@]netapp.com) with: - - Detailed description of the vulnerability. - - Steps to reproduce and, if possible, proof-of-concept code. - - Impact assessment and affected versions. -3. Encrypt sensitive reports when possible (GPG key available upon request). +We will work with you to verify the vulnerability, build a patch, validate +the fix, and finally issue a public report. -This information will help us triage your report more quickly. +When reporting issues, please provide the following information: +- Component(s) affected +- A description indicating how to reproduce the issue +- A summary of the security vulnerability and impact -## Responsible Disclosure Guidelines - -* Allow maintainers reasonable time to remediate (minimum 30 days). -* Coordinate any public disclosure with the maintainers. -* Provide fixes or mitigation suggestions when feasible. +We request that you contact us via the email address above and give the +project contributors a chance to resolve the vulnerability and issue a new +release prior to any public exposure; this helps protect the project's +users, and provides them with a chance to upgrade and/or update in order to +protect their applications. ## Policy -If we verify a reported security vulnerability, our policy is: - -* We will patch the current release branch, as well as the immediate prior minor release branch. -* After patching the release branches, we will immediately issue new security fix releases for each patched release branch. -* A security advisory will be released on the project GitHub repository detailing the vulnerability, as well as recommendations for end-users to protect themselves. - -## Secure Development Practices - -### Dependency Management - -```bash -# Regularly audit dependencies -npm audit - -# Update to fix vulnerabilities -npm audit fix - -# Check for outdated packages -npm outdated -``` - -**Practices**: -- Run `npm audit` before every release -- Review and update dependencies quarterly -- Monitor GitHub Dependabot alerts -- Pin major versions to avoid breaking changes - -#### Code Security - -**Avoid**: -- Hardcoded credentials or secrets -- Sensitive data in console.log statements -- Storing tokens in localStorage (use sessionStorage or memory) -- Bypassing TypeScript strict mode -- Disabling ESLint security rules - -**Do**: -- Use environment variables for configuration -- Sanitize user inputs -- Validate API responses -- Use type-safe API clients -- Follow principle of least privilege - -#### TypeScript Strict Mode - -Already enforced in `tsconfig.json`: -```json -{ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noUncheckedSideEffectImports": true -} -``` - -This catches many security issues at compile time. - -### Deployment - -#### Environment Variables - -**Never commit**: -- `.env.local` -- `.env.production` -- Any file containing secrets - -**Use**: -- Environment-specific configuration -- Container orchestration secrets (Kubernetes, Docker Swarm) -- Secret management services (HashiCorp Vault, AWS Secrets Manager) - -#### Docker Security - -**Best Practices**: - -1. **Minimal Base Image** - ```dockerfile - FROM docker.io/library/caddy:2-alpine # Alpine for smaller attack surface - ``` - -2. **Non-Root User** - ```dockerfile - RUN addgroup -g 1001 -S nodejs - RUN adduser -S neo -u 1001 - USER neo - ``` - -3. **Read-Only Filesystem** (where possible) - ```dockerfile - VOLUME /tmp - VOLUME /data/caddy - ``` - -4. **No Unnecessary Packages** - - Don't install development dependencies in production - - Remove build artifacts after multi-stage builds - -5. **Security Scanning** - ```bash - # Scan for vulnerabilities - docker scan neo-ui-framework - - # Use Trivy for comprehensive scanning - trivy image neo-ui-framework - ``` - -#### Caddy Configuration - -**Security Headers** (add to `Caddyfile`): - -```caddyfile -# Security headers -header X-Frame-Options "SAMEORIGIN" -header X-Content-Type-Options "nosniff" -header X-XSS-Protection "1; mode=block" -header Referrer-Policy "strict-origin-when-cross-origin" -header Permissions-Policy "geolocation=(), microphone=(), camera=()" - -# Content Security Policy -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:;" - -# HSTS (automatically managed by Caddy when using HTTPS) -``` - -**API Proxy Security**: - -```caddyfile -handle /api/* { - uri strip_prefix /api - reverse_proxy {$NEO_API} { - # Don't expose backend details - header_down -X-Powered-By - header_down -Server - - # Timeout limits - transport http { - dial_timeout 5s - response_header_timeout 60s - } - } -} -``` - -#### HTTPS/TLS - -**Production Requirements**: - -1. **Always use HTTPS** in production -2. **TLS 1.2+** minimum (Caddy default) -3. **Automatic HTTPS**: Caddy automatically provisions and renews TLS certificates via Let's Encrypt when a domain name is used: - ```caddyfile - your-domain.com { - # Caddy automatically handles TLS - # No manual cipher configuration needed - } - ``` -4. **Certificate Management**: Caddy auto-manages Let's Encrypt certificates; enterprise CAs can be configured via the `tls` directive - -### Runtime Hardening - -#### Authentication - -**Current Implementation**: -- JWT bearer token authentication -- Token stored in React state (memory) -- Automatic session expiration handling -**Enhancements**: -- [ ] Implement token refresh mechanism -- [ ] Add session timeout warnings -- [ ] Support multi-factor authentication -- [ ] Implement rate limiting on login attempts - -#### Authorization - -**Current**: -- Role-based access (admin/standard user) -- Backend enforces permissions - -**Best Practices**: -- Never trust client-side authorization checks -- Always validate on backend -- Use principle of least privilege - -#### Data Protection - -**In Transit**: -- ✅ HTTPS (production requirement) -- ✅ TLS for API communication -- ✅ Secure WebSocket connections (if applicable) - -**At Rest**: -- Backend responsibility (not UI framework) -- Ensure Neo API encrypts sensitive data - -**In Browser**: -- ❌ Don't store sensitive data in localStorage -- ✅ Use sessionStorage for temporary data -- ✅ Clear on logout -- ✅ No sensitive data in URLs - -#### Input Validation - -**Already Implemented**: -- Zod schema validation for forms -- TypeScript type checking -- React Hook Form validation - -**Additional**: -- Sanitize before rendering user content -- Validate file uploads (size, type) -- Escape special characters in search queries - -### Monitoring & Logging - -#### Access Logs - -Review Caddy access logs for: -- Unusual request patterns -- Failed authentication attempts -- Suspicious API calls -- Rate limiting violations - -#### Application Logs - -Monitor for: -- Authentication errors -- API failures -- Client-side errors (via error boundaries) -- Performance issues - -#### Security Events - -Alert on: -- Multiple failed login attempts -- Privilege escalation attempts -- Unusual API access patterns -- Large data exports - -## Credential Management - -### Neo API Credentials - -**Storage**: -- ❌ Never in code or version control -- ❌ Never in client-side storage -- ✅ Entered by user at runtime -- ✅ Validated by backend -- ✅ Cleared on logout - -**Transmission**: -- ✅ HTTPS only -- ✅ Form data (not URL parameters) -- ✅ Bearer token for subsequent requests - -### Share Credentials - -**Backend Responsibility**: -- Encrypted storage -- Access control -- Audit logging - -**UI Best Practices**: -- Password input masking -- No credential echo -- Clear form on cancel -- Warn before password changes - -## Incident Response - -### If You Discover an Exploit - -1. **Immediate Actions**: - - Rotate all credentials - - Review access logs - - Identify affected systems - - Contain the breach - -2. **Investigation**: - - Determine scope of compromise - - Identify attack vector - - Document timeline - - Preserve evidence - -3. **Remediation**: - - Apply security patches - - Update configurations - - Implement additional controls - - Test thoroughly - -4. **Recovery**: - - Restore from clean backups - - Verify system integrity - - Monitor for recurrence - -5. **Communication**: - - Notify affected users - - Report to security team - - Comply with regulations (GDPR, etc.) - - Post-mortem analysis - -### Disclosure Timeline - -After remediation: -1. Notify users of patch availability -2. Provide upgrade instructions -3. Publish security advisory (30 days minimum after fix) -4. Credit security researchers - -## Compliance & Regulations - -### GDPR (if applicable) - -- Right to erasure (user deletion) -- Data minimization -- Purpose limitation -- Consent management - -### SOC 2 / ISO 27001 (if applicable) - -- Access controls -- Encryption requirements -- Audit logging -- Incident response procedures - -## Security Checklist - -Before deploying to production: - -- [ ] All dependencies audited (`npm audit`) -- [ ] Secrets removed from code and configs -- [ ] HTTPS enabled with valid certificate -- [ ] Security headers configured in Caddy -- [ ] CSP policy defined and tested -- [ ] Error messages don't leak sensitive info -- [ ] Rate limiting implemented -- [ ] Logging and monitoring configured -- [ ] Backup and recovery procedures tested -- [ ] Incident response plan documented -- [ ] Security training completed for team +If we verify a reported security vulnerability, our policy is: -## 🆘 Security Resources +- We will patch the current release branch, as well as the immediate prior minor + release branch. -- [OWASP Top 10](https://owasp.org/www-project-top-ten/) -- [OWASP Cheat Sheets](https://cheatsheetseries.owasp.org/) -- [React Security Best Practices](https://snyk.io/blog/10-react-security-best-practices/) -- [npm Security Advisories](https://www.npmjs.com/advisories) -- [Docker Security Best Practices](https://docs.docker.com/develop/security-best-practices/) +- After patching the release branches, we will immediately issue new security + fix releases for each patched release branch. ---- -**Remember**: Security is everyone's responsibility. When in doubt, ask! \ No newline at end of file +- 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. \ No newline at end of file From d1d65933d0fc2a13a857b332919aff80a89ad8be Mon Sep 17 00:00:00 2001 From: romdalf Date: Tue, 21 Apr 2026 10:24:36 +0200 Subject: [PATCH 08/14] fix setup wizard --- SUPPORT.md | 5 ++-- package-lock.json | 24 ++++--------------- package.json | 2 +- src/components/cards/versioning-card.tsx | 2 +- .../dialogs/setup-wizard-dialog.tsx | 21 ++++++++++++---- src/services/api/helm.ts | 12 +++++----- vite.config.ts | 2 +- 7 files changed, 33 insertions(+), 35 deletions(-) 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/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/components/cards/versioning-card.tsx b/src/components/cards/versioning-card.tsx index 485860c..fb004ec 100644 --- a/src/components/cards/versioning-card.tsx +++ b/src/components/cards/versioning-card.tsx @@ -121,7 +121,7 @@ export function VersioningCard({ version, helmChartVersion, className }: Version Helm Chart
Connector Name setConnectorName(e.target.value)} /> +
+ + setConnectorId(e.target.value)} /> +
+
+ +