Full-stack UI service for CloudScan - Spring Boot backend + React frontend with real-time security scanning dashboard
The cloudscan-ui is a full-stack web application that serves as the interface for CloudScan platform. It combines:
- Backend (Spring Boot): Lightweight API server for authentication, session management, and proxying requests
- Frontend (React): Beautiful, responsive UI for security scanning
Features:
- π User authentication (login/signup) with session management
- π Real-time scan progress with live logs
- π Security findings dashboard with filters
- π Analytics and trends
- βοΈ Project and organization management
- π± Responsive design (desktop, tablet, mobile)
- π Dark/light theme support
- π‘οΈ CSRF protection and security headers
- π API Gateway proxy to backend services
- Framework: Spring Boot 3.2+
- Language: Java 17+
- Security: Spring Security (JWT/Session)
- Database: PostgreSQL (user sessions, preferences)
- API Client: WebClient (for backend service calls)
- Build Tool: Maven/Gradle
- Framework: React 18 + TypeScript
- Build Tool: Vite
- Styling: TailwindCSS + HeadlessUI
- State Management: Zustand
- Data Fetching: TanStack Query (React Query)
- Routing: React Router v6
- Forms: React Hook Form + Zod
- Charts: Recharts
- WebSocket: native WebSocket API
- Icons: Heroicons
cloudscan-ui/
βββ backend/ # Spring Boot backend
β βββ src/
β β βββ main/
β β βββ java/com/cloudscan/ui/
β β β βββ CloudscanUiApplication.java
β β β βββ config/
β β β β βββ SecurityConfig.java # Spring Security config
β β β β βββ WebClientConfig.java # WebClient for backend calls
β β β β βββ CorsConfig.java # CORS configuration
β β β βββ controller/
β β β β βββ AuthController.java # Login/logout endpoints
β β β β βββ ProxyController.java # Proxy to backend services
β β β β βββ UserController.java # User management
β β β βββ service/
β β β β βββ AuthService.java # Authentication logic
β β β β βββ SessionService.java # Session management
β β β β βββ BackendProxyService.java # Backend service proxy
β β β βββ model/
β β β β βββ User.java
β β β β βββ Session.java
β β β β βββ dto/ # DTOs for API
β β β βββ repository/
β β β β βββ UserRepository.java
β β β β βββ SessionRepository.java
β β β βββ security/
β β β βββ JwtTokenProvider.java
β β β βββ UserDetailsServiceImpl.java
β β βββ resources/
β β βββ application.yml # Spring Boot config
β β βββ application-dev.yml
β β βββ application-prod.yml
β βββ pom.xml / build.gradle # Maven/Gradle config
β βββ README.md
β
βββ frontend/ # React frontend
β βββ src/
β β βββ components/ # Reusable UI components
β β β βββ common/ # Buttons, inputs, modals, etc.
β β β βββ scan/ # Scan-specific components
β β β βββ findings/ # Finding display components
β β β βββ layout/ # Layout components (Header, Sidebar)
β β βββ pages/ # Page components
β β β βββ Login.tsx
β β β βββ Signup.tsx
β β β βββ Dashboard.tsx
β β β βββ Projects.tsx
β β β βββ ScanNew.tsx
β β β βββ ScanDetails.tsx
β β β βββ FindingsPage.tsx
β β βββ hooks/ # Custom React hooks
β β β βββ useAuth.ts
β β β βββ useScans.ts
β β β βββ useWebSocket.ts
β β β βββ useFindings.ts
β β βββ services/ # API clients
β β β βββ api.ts # Base API client (calls Spring Boot backend)
β β β βββ scans.ts # Scan API
β β β βββ auth.ts # Authentication API
β β β βββ websocket.ts # WebSocket client
β β βββ utils/ # Utility functions
β β β βββ format.ts # Date/number formatting
β β β βββ constants.ts # App constants
β β βββ types/ # TypeScript types
β β β βββ scan.ts
β β β βββ finding.ts
β β β βββ user.ts
β β βββ stores/ # Zustand stores
β β β βββ authStore.ts
β β β βββ uiStore.ts
β β βββ App.tsx # Root component
β β βββ main.tsx # Application entry
β β βββ index.css # Global styles
β βββ public/
β β βββ favicon.ico
β β βββ logo.svg
β βββ index.html
β βββ package.json
β βββ tsconfig.json
β βββ vite.config.ts
β βββ tailwind.config.js
β
βββ docker-compose.yml # Local development setup
βββ Dockerfile # Multi-stage build (Spring Boot + React)
βββ README.md
- Backend: Java 17+, Maven/Gradle
- Frontend: Node.js 20+, npm
- Database: PostgreSQL 15+
cd cloudscan-ui
# Start all services (Spring Boot + React + PostgreSQL)
docker-compose up
# Access the application
# Frontend: http://localhost:3000
# Backend API: http://localhost:80801. Start Backend (Spring Boot)
cd backend
# Set up environment variables
cp src/main/resources/application-dev.yml.example src/main/resources/application-dev.yml
# Edit application-dev.yml with database credentials
# Run PostgreSQL (via Docker)
docker run --name postgres-ui \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=cloudscan_ui \
-p 5432:5432 \
-d postgres:15
# Build and run Spring Boot
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
# Or with Gradle:
# ./gradlew bootRun --args='--spring.profiles.active=dev'Backend will be available at http://localhost:8080
2. Start Frontend (React)
cd frontend
# Install dependencies
npm install
# Set up environment variables
cp .env.example .env
# Edit .env with backend URL
# Start development server
npm run devFrontend will be available at http://localhost:3000
server:
port: 8080
spring:
datasource:
url: jdbc:postgresql://localhost:5432/cloudscan_ui
username: postgres
password: postgres
jpa:
hibernate:
ddl-auto: update
show-sql: true
# Backend service URLs
cloudscan:
services:
orchestrator: http://localhost:9999
storage: http://localhost:8082
api-gateway: http://localhost:8080
websocket: ws://localhost:9090
# JWT configuration
jwt:
secret: your-secret-key-change-in-production
expiration: 86400000 # 24 hours in milliseconds# Backend API URL (Spring Boot)
VITE_API_URL=http://localhost:8080
# WebSocket URL
VITE_WS_URL=ws://localhost:9090
# Feature flags
VITE_ENABLE_DARK_MODE=true
VITE_ENABLE_ANALYTICS=false# Development
./mvnw spring-boot:run # Start Spring Boot app
./mvnw clean package # Build JAR
./mvnw test # Run tests
# With Gradle
./gradlew bootRun # Start Spring Boot app
./gradlew build # Build JAR
./gradlew test # Run tests# Development
npm run dev # Start dev server with HMR
npm run build # Build for production
npm run preview # Preview production build
# Code Quality
npm run lint # Run ESLint
npm run type-check # Run TypeScript compiler check
npm run format # Format code with Prettier
# Testing
npm run test # Run unit tests
npm run test:e2e # Run E2E tests (Playwright)
npm run test:coverage # Generate coverage reportLogin Page (/login)
- Email/password login
- "Remember me" option
- Password reset link
- JWT token management
Signup Page (/signup)
- User registration
- Organization creation
- Email verification
Overview Page (/dashboard)
- Recent scans summary
- Security trends (7/30/90 days)
- Quick actions
- Critical findings alerts
New Scan Wizard (/scans/new)
- Step 1: Select project
- Step 2: Configure source (Git repo, branch)
- Step 3: Choose scan types (SAST, SCA, Secrets, License)
- Step 4: Notifications and options
Scan Details (/scans/:id)
- Real-time progress bar
- Live log streaming (WebSocket)
- Step-by-step execution status
- Cancel scan button
Scan Results (/scans/:id/findings)
- Findings table with filters
- By severity (critical, high, medium, low)
- By type (SAST, SCA, secrets, license)
- By status (new, fixed, ignored)
- Finding detail modal
- Vulnerable code snippet
- Remediation steps
- References (CWE, CVE, OWASP)
Uses WebSocket for:
- Live scan logs
- Status changes
- Progress updates
// Example WebSocket usage
const ws = useWebSocket(`${WS_URL}/scans/${scanId}`, {
onMessage: (event) => {
const data = JSON.parse(event.data);
if (data.type === 'log') {
appendLog(data.message);
}
}
});// Button with variants
<Button variant="primary" size="md" onClick={handleClick}>
Start Scan
</Button>
// Alert/Toast notifications
<Alert type="success" message="Scan completed!" />
// Modal dialog
<Modal isOpen={isOpen} onClose={onClose} title="Finding Details">
<FindingDetails finding={selectedFinding} />
</Modal>
// Loading spinner
<Spinner size="lg" />
// Progress bar
<ProgressBar value={60} max={100} label="60% complete" />// Scan status badge
<ScanStatusBadge status="running" /> // Shows colored badge
// Finding severity icon
<SeverityIcon severity="critical" /> // Red icon
// Live logs viewer
<LogViewer logs={logs} isStreaming={true} />
// Findings table
<FindingsTable findings={findings} onSelect={handleSelect} /># Run tests
npm run test
# Watch mode
npm run test:watch# Install Playwright
npx playwright install
# Run E2E tests
npm run test:e2e
# Run specific test
npx playwright test tests/scan-flow.spec.tsExample E2E test:
test('create and monitor scan', async ({ page }) => {
await page.goto('/login');
await page.fill('[name=email]', '[email protected]');
await page.fill('[name=password]', 'password');
await page.click('button[type=submit]');
await page.goto('/scans/new');
await page.selectOption('[name=project]', 'my-project');
await page.click('button:has-text("Start Scan")');
await expect(page.locator('.scan-status')).toHaveText(/running/i);
});Uses TailwindCSS with custom theme:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
600: '#2563eb',
900: '#1e3a8a',
},
severity: {
critical: '#dc2626', // Red
high: '#ea580c', // Orange
medium: '#ca8a04', // Yellow
low: '#2563eb', // Blue
info: '#6b7280', // Gray
}
}
}
}
}Usage:
<div className="bg-primary-500 text-white px-4 py-2 rounded-lg">
Critical Finding
</div>
<span className="text-severity-critical font-bold">
CRITICAL
</span>// src/services/api.ts
import axios from 'axios';
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
// Add auth token to requests
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export default api;// src/services/scans.ts
import api from './api';
export const scansService = {
// Create scan
create: async (data: CreateScanRequest) => {
const response = await api.post('/api/v1/scans', data);
return response.data;
},
// Get scan details
getById: async (id: string) => {
const response = await api.get(`/api/v1/scans/${id}`);
return response.data;
},
// List scans
list: async (filters?: ScanFilters) => {
const response = await api.get('/api/v1/scans', { params: filters });
return response.data;
},
};// src/hooks/useScans.ts
import { useQuery } from '@tanstack/react-query';
import { scansService } from '../services/scans';
export const useScans = () => {
return useQuery({
queryKey: ['scans'],
queryFn: () => scansService.list(),
refetchInterval: 10000, // Auto-refresh every 10s
});
};
export const useScanDetails = (scanId: string) => {
return useQuery({
queryKey: ['scans', scanId],
queryFn: () => scansService.getById(scanId),
enabled: !!scanId,
});
};docker build -t cloudscan/ui:latest .docker run -p 3000:80 \
-e VITE_API_URL=http://api-gateway:8080 \
cloudscan/ui:latest# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Breakpoints:
- sm: 640px (mobile)
- md: 768px (tablet)
- lg: 1024px (desktop)
- xl: 1280px (large desktop)
Example:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Responsive grid */}
</div>- Follow the component structure
- Use TypeScript for all new code
- Add tests for new features
- Follow Airbnb style guide
- Use Prettier for formatting
Apache 2.0 - See LICENSE