Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
39 changes: 39 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
:80 {
# Reverse proxy API requests to backend
# The NEO_API env var is resolved dynamically by Caddy
handle /api/* {
uri strip_prefix /api
reverse_proxy {$NEO_API} {
header_up Host {host}
header_up X-Real-IP {remote_host}
flush_interval -1
}
}

# Serve static UI files (catch-all, mutually exclusive with /api/*)
handle {
root * /srv
try_files {path} /index.html
file_server
}

# Cache static assets (JS, CSS, images, fonts)
@static path *.js *.css *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot
header @static Cache-Control "public, immutable, max-age=31536000"

# Prevent caching of HTML pages so auth state is always checked on refresh
@html {
not path *.js *.css *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot
not path /api/*
}
header @html Cache-Control "no-store, no-cache, must-revalidate"

# Security headers
header X-Frame-Options "SAMEORIGIN"
header X-Content-Type-Options "nosniff"
header X-XSS-Protection "1; mode=block"
header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https://api.github.com https://netapp.github.io;"

# Gzip compression
encode gzip
}
19 changes: 4 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
2 changes: 1 addition & 1 deletion QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ If we verify a reported security vulnerability, our policy is:

- A security advisory will be released on the project GitHub repository detailing the
vulnerability, as well as recommendations for end-users to protect themselves.

- We will work with the reporter to ensure they are credited in the security advisory.
5 changes: 3 additions & 2 deletions SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions caching-specs.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 2 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
48 changes: 8 additions & 40 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
# Validate and run Caddy
echo "Starting Caddy server..."
exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
22 changes: 21 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,25 @@ export default defineConfig([
ecmaVersion: 2020,
globals: globals.browser,
},
},
rules: {
"react-refresh/only-export-components": [
"error",
{
allowConstantExport: true,
allowExportNames: [
"getStatusIcon",
"getStatusBadge",
"formatDuration",
"useTheme",
"useSettings",
"useSidebar",
"useFormField",
"badgeVariants",
"buttonVariants",
"toggleVariants",
],
},
],
},
},
])
1 change: 1 addition & 0 deletions neocoreapi/openapi.json

Large diffs are not rendered by default.

42 changes: 0 additions & 42 deletions nginx.conf

This file was deleted.

Loading