Compare commits

...

2 Commits

Author SHA1 Message Date
yyhuni
f328474404 feat(frontend): add comprehensive mock data infrastructure for services
- Add mock data modules for auth, engines, notifications, scheduled-scans, and workers
- Implement mock authentication data with user profiles and login/logout responses
- Create mock scan engine configurations with multiple predefined scanning profiles
- Add mock notification system with various severity levels and categories
- Implement mock scheduled scan data with cron expressions and run history
- Add mock worker node data with status and performance metrics
- Update service layer to integrate with new mock data infrastructure
- Provide helper functions for filtering and paginating mock data
- Enable frontend development and testing without backend API dependency
2026-01-03 07:59:20 +08:00
yyhuni
68e726a066 chore(docker): update base image to python 3.10-slim-bookworm
- Update Python base image from 3.10-slim to 3.10-slim-bookworm
- Ensures compatibility with latest Debian stable release
- Improves security with updated system packages and dependencies
2026-01-02 23:19:09 +08:00
12 changed files with 520 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
FROM python:3.10-slim
FROM python:3.10-slim-bookworm
WORKDIR /app

View File

@@ -0,0 +1,22 @@
import type { User, MeResponse, LoginResponse, LogoutResponse } from '@/types/auth.types'
export const mockUser: User = {
id: 1,
username: 'admin',
isStaff: true,
isSuperuser: true,
}
export const mockMeResponse: MeResponse = {
authenticated: true,
user: mockUser,
}
export const mockLoginResponse: LoginResponse = {
message: 'Login successful',
user: mockUser,
}
export const mockLogoutResponse: LogoutResponse = {
message: 'Logout successful',
}

View File

@@ -0,0 +1,78 @@
import type { ScanEngine } from '@/types/engine.types'
export const mockEngines: ScanEngine[] = [
{
id: 1,
name: 'Full Scan',
configuration: `# Full reconnaissance scan
stages:
- name: subdomain_discovery
tools:
- subfinder
- amass
- name: port_scan
tools:
- nmap
- name: web_crawling
tools:
- httpx
- katana
- name: vulnerability_scan
tools:
- nuclei
`,
createdAt: '2024-01-15T08:00:00Z',
updatedAt: '2024-12-20T10:30:00Z',
},
{
id: 2,
name: 'Quick Scan',
configuration: `# Quick scan - subdomain and web only
stages:
- name: subdomain_discovery
tools:
- subfinder
- name: web_crawling
tools:
- httpx
`,
createdAt: '2024-02-10T09:00:00Z',
updatedAt: '2024-12-18T14:00:00Z',
},
{
id: 3,
name: 'Vulnerability Only',
configuration: `# Vulnerability scan only
stages:
- name: vulnerability_scan
tools:
- nuclei
options:
severity: critical,high,medium
`,
createdAt: '2024-03-05T11:00:00Z',
updatedAt: '2024-12-15T16:20:00Z',
},
{
id: 4,
name: 'Subdomain Discovery',
configuration: `# Subdomain enumeration only
stages:
- name: subdomain_discovery
tools:
- subfinder
- amass
- findomain
`,
createdAt: '2024-04-12T08:30:00Z',
updatedAt: '2024-12-10T09:00:00Z',
},
]
export function getMockEngines(): ScanEngine[] {
return mockEngines
}
export function getMockEngineById(id: number): ScanEngine | undefined {
return mockEngines.find(e => e.id === id)
}

View File

@@ -0,0 +1,110 @@
import type { BackendNotification, GetNotificationsResponse } from '@/types/notification.types'
export const mockNotifications: BackendNotification[] = [
{
id: 1,
category: 'vulnerability',
title: 'Critical Vulnerability Found',
message: 'SQL Injection detected in retailmax.com/product endpoint',
level: 'critical',
createdAt: '2024-12-29T10:30:00Z',
isRead: false,
},
{
id: 2,
category: 'scan',
title: 'Scan Completed',
message: 'Scan for acme.com completed successfully with 23 vulnerabilities found',
level: 'medium',
createdAt: '2024-12-29T09:00:00Z',
isRead: false,
},
{
id: 3,
category: 'vulnerability',
title: 'High Severity Vulnerability',
message: 'XSS vulnerability found in acme.com/search',
level: 'high',
createdAt: '2024-12-28T16:45:00Z',
isRead: true,
},
{
id: 4,
category: 'scan',
title: 'Scan Failed',
message: 'Scan for globalfinance.com failed: Connection timeout',
level: 'high',
createdAt: '2024-12-28T14:20:00Z',
isRead: true,
},
{
id: 5,
category: 'asset',
title: 'New Subdomains Discovered',
message: '15 new subdomains discovered for techstart.io',
level: 'low',
createdAt: '2024-12-27T11:00:00Z',
isRead: true,
},
{
id: 6,
category: 'system',
title: 'Worker Offline',
message: 'Worker node worker-03 is now offline',
level: 'medium',
createdAt: '2024-12-27T08:30:00Z',
isRead: true,
},
{
id: 7,
category: 'scan',
title: 'Scheduled Scan Started',
message: 'Scheduled scan for Acme Corporation started',
level: 'low',
createdAt: '2024-12-26T06:00:00Z',
isRead: true,
},
{
id: 8,
category: 'system',
title: 'System Update Available',
message: 'A new version of the scanner is available',
level: 'low',
createdAt: '2024-12-25T10:00:00Z',
isRead: true,
},
]
export function getMockNotifications(params?: {
page?: number
pageSize?: number
unread?: boolean
}): GetNotificationsResponse {
const page = params?.page || 1
const pageSize = params?.pageSize || 10
let filtered = mockNotifications
if (params?.unread) {
filtered = filtered.filter(n => !n.isRead)
}
const total = filtered.length
const totalPages = Math.ceil(total / pageSize)
const start = (page - 1) * pageSize
const results = filtered.slice(start, start + pageSize)
return {
results,
total,
page,
pageSize,
totalPages,
}
}
export function getMockUnreadCount(): { count: number } {
return {
count: mockNotifications.filter(n => !n.isRead).length,
}
}

View File

@@ -0,0 +1,132 @@
import type { ScheduledScan, GetScheduledScansResponse } from '@/types/scheduled-scan.types'
export const mockScheduledScans: ScheduledScan[] = [
{
id: 1,
name: 'Daily Acme Scan',
engineIds: [1],
engineNames: ['Full Scan'],
organizationId: 1,
organizationName: 'Acme Corporation',
targetId: null,
targetName: null,
scanMode: 'organization',
cronExpression: '0 2 * * *',
isEnabled: true,
nextRunTime: '2024-12-30T02:00:00Z',
lastRunTime: '2024-12-29T02:00:00Z',
runCount: 45,
createdAt: '2024-11-15T08:00:00Z',
updatedAt: '2024-12-29T02:00:00Z',
},
{
id: 2,
name: 'Weekly TechStart Vuln Scan',
engineIds: [3],
engineNames: ['Vulnerability Only'],
organizationId: 2,
organizationName: 'TechStart Inc',
targetId: null,
targetName: null,
scanMode: 'organization',
cronExpression: '0 3 * * 0',
isEnabled: true,
nextRunTime: '2025-01-05T03:00:00Z',
lastRunTime: '2024-12-29T03:00:00Z',
runCount: 12,
createdAt: '2024-10-01T10:00:00Z',
updatedAt: '2024-12-29T03:00:00Z',
},
{
id: 3,
name: 'Hourly API Monitoring',
engineIds: [2],
engineNames: ['Quick Scan'],
organizationId: null,
organizationName: null,
targetId: 12,
targetName: 'api.acme.com',
scanMode: 'target',
cronExpression: '0 * * * *',
isEnabled: true,
nextRunTime: '2024-12-29T12:00:00Z',
lastRunTime: '2024-12-29T11:00:00Z',
runCount: 720,
createdAt: '2024-12-01T00:00:00Z',
updatedAt: '2024-12-29T11:00:00Z',
},
{
id: 4,
name: 'Monthly Full Scan - Finance',
engineIds: [1],
engineNames: ['Full Scan'],
organizationId: 3,
organizationName: 'Global Finance Ltd',
targetId: null,
targetName: null,
scanMode: 'organization',
cronExpression: '0 0 1 * *',
isEnabled: false,
nextRunTime: '2025-01-01T00:00:00Z',
lastRunTime: '2024-12-01T00:00:00Z',
runCount: 6,
createdAt: '2024-06-01T08:00:00Z',
updatedAt: '2024-12-20T15:00:00Z',
},
{
id: 5,
name: 'RetailMax Daily Quick',
engineIds: [2, 3],
engineNames: ['Quick Scan', 'Vulnerability Only'],
organizationId: null,
organizationName: null,
targetId: 8,
targetName: 'retailmax.com',
scanMode: 'target',
cronExpression: '0 4 * * *',
isEnabled: true,
nextRunTime: '2024-12-30T04:00:00Z',
lastRunTime: '2024-12-29T04:00:00Z',
runCount: 30,
createdAt: '2024-11-29T09:00:00Z',
updatedAt: '2024-12-29T04:00:00Z',
},
]
export function getMockScheduledScans(params?: {
page?: number
pageSize?: number
search?: string
}): GetScheduledScansResponse {
const page = params?.page || 1
const pageSize = params?.pageSize || 10
const search = params?.search?.toLowerCase() || ''
let filtered = mockScheduledScans
if (search) {
filtered = filtered.filter(
s =>
s.name.toLowerCase().includes(search) ||
s.organizationName?.toLowerCase().includes(search) ||
s.targetName?.toLowerCase().includes(search)
)
}
const total = filtered.length
const totalPages = Math.ceil(total / pageSize)
const start = (page - 1) * pageSize
const results = filtered.slice(start, start + pageSize)
return {
results,
total,
page,
pageSize,
totalPages,
}
}
export function getMockScheduledScanById(id: number): ScheduledScan | undefined {
return mockScheduledScans.find(s => s.id === id)
}

View File

@@ -0,0 +1,78 @@
import type { WorkerNode, WorkersResponse } from '@/types/worker.types'
export const mockWorkers: WorkerNode[] = [
{
id: 1,
name: 'local-worker',
ipAddress: '127.0.0.1',
sshPort: 22,
username: 'root',
status: 'online',
isLocal: true,
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-12-29T10:00:00Z',
info: {
cpuPercent: 23.5,
memoryPercent: 45.2,
},
},
{
id: 2,
name: 'worker-01',
ipAddress: '192.168.1.101',
sshPort: 22,
username: 'scanner',
status: 'online',
isLocal: false,
createdAt: '2024-06-15T08:00:00Z',
updatedAt: '2024-12-29T09:30:00Z',
info: {
cpuPercent: 56.8,
memoryPercent: 72.1,
},
},
{
id: 3,
name: 'worker-02',
ipAddress: '192.168.1.102',
sshPort: 22,
username: 'scanner',
status: 'online',
isLocal: false,
createdAt: '2024-07-20T10:00:00Z',
updatedAt: '2024-12-29T09:45:00Z',
info: {
cpuPercent: 34.2,
memoryPercent: 58.9,
},
},
{
id: 4,
name: 'worker-03',
ipAddress: '192.168.1.103',
sshPort: 22,
username: 'scanner',
status: 'offline',
isLocal: false,
createdAt: '2024-08-10T14:00:00Z',
updatedAt: '2024-12-28T16:00:00Z',
},
]
export function getMockWorkers(page = 1, pageSize = 10): WorkersResponse {
const total = mockWorkers.length
const totalPages = Math.ceil(total / pageSize)
const start = (page - 1) * pageSize
const results = mockWorkers.slice(start, start + pageSize)
return {
results,
total,
page,
pageSize,
}
}
export function getMockWorkerById(id: number): WorkerNode | undefined {
return mockWorkers.find(w => w.id === id)
}

View File

@@ -69,3 +69,39 @@ export {
getMockSubdomains,
getMockSubdomainById,
} from './data/subdomains'
// Auth
export {
mockUser,
mockMeResponse,
mockLoginResponse,
mockLogoutResponse,
} from './data/auth'
// Engines
export {
mockEngines,
getMockEngines,
getMockEngineById,
} from './data/engines'
// Workers
export {
mockWorkers,
getMockWorkers,
getMockWorkerById,
} from './data/workers'
// Notifications
export {
mockNotifications,
getMockNotifications,
getMockUnreadCount,
} from './data/notifications'
// Scheduled Scans
export {
mockScheduledScans,
getMockScheduledScans,
getMockScheduledScanById,
} from './data/scheduled-scans'

View File

@@ -10,11 +10,16 @@ import type {
ChangePasswordRequest,
ChangePasswordResponse
} from '@/types/auth.types'
import { USE_MOCK, mockDelay, mockLoginResponse, mockLogoutResponse, mockMeResponse } from '@/mock'
/**
* User login
*/
export async function login(data: LoginRequest): Promise<LoginResponse> {
if (USE_MOCK) {
await mockDelay()
return mockLoginResponse
}
const res = await api.post<LoginResponse>('/auth/login/', data)
return res.data
}
@@ -23,6 +28,10 @@ export async function login(data: LoginRequest): Promise<LoginResponse> {
* User logout
*/
export async function logout(): Promise<LogoutResponse> {
if (USE_MOCK) {
await mockDelay()
return mockLogoutResponse
}
const res = await api.post<LogoutResponse>('/auth/logout/')
return res.data
}
@@ -31,6 +40,10 @@ export async function logout(): Promise<LogoutResponse> {
* Get current user information
*/
export async function getMe(): Promise<MeResponse> {
if (USE_MOCK) {
await mockDelay()
return mockMeResponse
}
const res = await api.get<MeResponse>('/auth/me/')
return res.data
}
@@ -39,6 +52,10 @@ export async function getMe(): Promise<MeResponse> {
* Change password
*/
export async function changePassword(data: ChangePasswordRequest): Promise<ChangePasswordResponse> {
if (USE_MOCK) {
await mockDelay()
return { message: 'Password changed successfully' }
}
const res = await api.post<ChangePasswordResponse>('/auth/change-password/', data)
return res.data
}

View File

@@ -1,5 +1,6 @@
import apiClient from '@/lib/api-client'
import type { ScanEngine } from '@/types/engine.types'
import { USE_MOCK, mockDelay, getMockEngines, getMockEngineById } from '@/mock'
/**
* Engine API service
@@ -9,6 +10,10 @@ import type { ScanEngine } from '@/types/engine.types'
* Get engine list
*/
export async function getEngines(): Promise<ScanEngine[]> {
if (USE_MOCK) {
await mockDelay()
return getMockEngines()
}
// Engines are usually not many, get all
const response = await apiClient.get('/engines/', {
params: { pageSize: 1000 }
@@ -21,6 +26,12 @@ export async function getEngines(): Promise<ScanEngine[]> {
* Get engine details
*/
export async function getEngine(id: number): Promise<ScanEngine> {
if (USE_MOCK) {
await mockDelay()
const engine = getMockEngineById(id)
if (!engine) throw new Error('Engine not found')
return engine
}
const response = await apiClient.get(`/engines/${id}/`)
return response.data
}

View File

@@ -9,6 +9,7 @@ import type {
GetNotificationsRequest,
GetNotificationsResponse,
} from '@/types/notification.types'
import { USE_MOCK, mockDelay, getMockNotifications, getMockUnreadCount } from '@/mock'
export class NotificationService {
/**
@@ -18,6 +19,10 @@ export class NotificationService {
static async getNotifications(
params: GetNotificationsRequest = {}
): Promise<GetNotificationsResponse> {
if (USE_MOCK) {
await mockDelay()
return getMockNotifications(params)
}
const response = await api.get<GetNotificationsResponse>('/notifications/', {
params,
})
@@ -29,6 +34,10 @@ export class NotificationService {
* 后端返回: { updated: number }
*/
static async markAllAsRead(): Promise<{ updated: number }> {
if (USE_MOCK) {
await mockDelay()
return { updated: 2 }
}
const response = await api.post<{ updated: number }>('/notifications/mark-all-as-read/')
return response.data
}
@@ -38,6 +47,10 @@ export class NotificationService {
* 后端返回: { count: number }
*/
static async getUnreadCount(): Promise<{ count: number }> {
if (USE_MOCK) {
await mockDelay()
return getMockUnreadCount()
}
const response = await api.get<{ count: number }>('/notifications/unread-count/')
return response.data
}

View File

@@ -5,11 +5,16 @@ import type {
CreateScheduledScanRequest,
UpdateScheduledScanRequest
} from '@/types/scheduled-scan.types'
import { USE_MOCK, mockDelay, getMockScheduledScans, getMockScheduledScanById } from '@/mock'
/**
* Get scheduled scan list
*/
export async function getScheduledScans(params?: { page?: number; pageSize?: number; search?: string }): Promise<GetScheduledScansResponse> {
if (USE_MOCK) {
await mockDelay()
return getMockScheduledScans(params)
}
const res = await api.get<GetScheduledScansResponse>('/scheduled-scans/', { params })
return res.data
}
@@ -18,6 +23,12 @@ export async function getScheduledScans(params?: { page?: number; pageSize?: num
* Get scheduled scan details
*/
export async function getScheduledScan(id: number): Promise<ScheduledScan> {
if (USE_MOCK) {
await mockDelay()
const scan = getMockScheduledScanById(id)
if (!scan) throw new Error('Scheduled scan not found')
return scan
}
const res = await api.get<ScheduledScan>(`/scheduled-scans/${id}/`)
return res.data
}

View File

@@ -9,6 +9,7 @@ import type {
CreateWorkerRequest,
UpdateWorkerRequest,
} from '@/types/worker.types'
import { USE_MOCK, mockDelay, getMockWorkers, getMockWorkerById } from '@/mock'
const BASE_URL = '/workers'
@@ -17,6 +18,10 @@ export const workerService = {
* Get Worker list
*/
async getWorkers(page = 1, pageSize = 10): Promise<WorkersResponse> {
if (USE_MOCK) {
await mockDelay()
return getMockWorkers(page, pageSize)
}
const response = await apiClient.get<WorkersResponse>(
`${BASE_URL}/?page=${page}&page_size=${pageSize}`
)
@@ -27,6 +32,12 @@ export const workerService = {
* Get single Worker details
*/
async getWorker(id: number): Promise<WorkerNode> {
if (USE_MOCK) {
await mockDelay()
const worker = getMockWorkerById(id)
if (!worker) throw new Error('Worker not found')
return worker
}
const response = await apiClient.get<WorkerNode>(`${BASE_URL}/${id}/`)
return response.data
},