feat: Implement mock API service with endpoints for system, recipes, process control, hardware, safety, and user management
This commit is contained in:
569
python_rewrite/frontend/src/services/mockApi.ts
Normal file
569
python_rewrite/frontend/src/services/mockApi.ts
Normal file
@@ -0,0 +1,569 @@
|
||||
import type {
|
||||
SystemInfo,
|
||||
SystemStatus,
|
||||
Recipe,
|
||||
RecipeCreate,
|
||||
RecipeUpdate,
|
||||
RecipeList,
|
||||
ProcessStatus,
|
||||
ProcessStartRequest,
|
||||
ProcessActionResponse,
|
||||
HardwareStatus,
|
||||
SafetyStatus,
|
||||
User,
|
||||
UserCreate,
|
||||
UserUpdate,
|
||||
} from '../types'
|
||||
|
||||
// Mock data
|
||||
const mockSystemInfo: SystemInfo = {
|
||||
name: 'Chocolate Tempering Machine',
|
||||
version: '2.0.0-mock',
|
||||
environment: 'mock',
|
||||
api_version: '1.0.0',
|
||||
features: {
|
||||
recipe_management: true,
|
||||
process_control: true,
|
||||
hardware_monitoring: false, // Disabled in mock mode
|
||||
safety_monitoring: true,
|
||||
user_management: true,
|
||||
real_time_monitoring: true,
|
||||
},
|
||||
endpoints: {
|
||||
recipes: '/api/v1/recipes',
|
||||
process: '/api/v1/process',
|
||||
hardware: '/api/v1/hardware',
|
||||
users: '/api/v1/users',
|
||||
system: '/api/v1/system',
|
||||
health: '/health',
|
||||
},
|
||||
}
|
||||
|
||||
const mockSystemStatus: SystemStatus = {
|
||||
system_status: 'warning', // Warning due to mock mode
|
||||
process_status: 'idle',
|
||||
hardware_status: 'disconnected', // No hardware in mock mode
|
||||
safety_status: 'safe',
|
||||
active_alarms: 0,
|
||||
uptime_seconds: 3600,
|
||||
last_updated: new Date().toISOString(),
|
||||
}
|
||||
|
||||
const mockRecipes: Recipe[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Dark Chocolate 70%',
|
||||
description: 'Premium dark chocolate tempering recipe',
|
||||
heating_temp: 50.0,
|
||||
cooling_temp: 27.0,
|
||||
working_temp: 31.0,
|
||||
heating_time: 300,
|
||||
cooling_time: 180,
|
||||
working_time: 600,
|
||||
mixer_speed: 50,
|
||||
fountain_delay: 30,
|
||||
pedal_enabled: true,
|
||||
is_active: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Milk Chocolate',
|
||||
description: 'Creamy milk chocolate tempering recipe',
|
||||
heating_temp: 45.0,
|
||||
cooling_temp: 26.0,
|
||||
working_temp: 29.0,
|
||||
heating_time: 240,
|
||||
cooling_time: 150,
|
||||
working_time: 480,
|
||||
mixer_speed: 45,
|
||||
fountain_delay: 25,
|
||||
pedal_enabled: true,
|
||||
is_active: true,
|
||||
created_at: '2024-01-02T00:00:00Z',
|
||||
updated_at: '2024-01-02T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'White Chocolate',
|
||||
description: 'Delicate white chocolate tempering recipe',
|
||||
heating_temp: 43.0,
|
||||
cooling_temp: 25.0,
|
||||
working_temp: 28.0,
|
||||
heating_time: 200,
|
||||
cooling_time: 120,
|
||||
working_time: 400,
|
||||
mixer_speed: 40,
|
||||
fountain_delay: 20,
|
||||
pedal_enabled: false,
|
||||
is_active: true,
|
||||
created_at: '2024-01-03T00:00:00Z',
|
||||
updated_at: '2024-01-03T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
const mockProcessStatus: ProcessStatus = {
|
||||
is_running: false,
|
||||
current_phase: 'idle',
|
||||
recipe_id: null,
|
||||
recipe_name: null,
|
||||
progress_percentage: 0,
|
||||
phase_time_remaining: 0,
|
||||
total_time_remaining: 0,
|
||||
current_temperatures: {
|
||||
tank_bottom: 22.5,
|
||||
tank_wall: 22.3,
|
||||
pump: 22.1,
|
||||
fountain: 22.0,
|
||||
},
|
||||
target_temperatures: {
|
||||
tank_bottom: 0,
|
||||
tank_wall: 0,
|
||||
pump: 0,
|
||||
fountain: 0,
|
||||
},
|
||||
motor_speeds: {
|
||||
mixer: 0,
|
||||
pump: 0,
|
||||
},
|
||||
session_id: null,
|
||||
started_at: null,
|
||||
estimated_completion: null,
|
||||
error: null,
|
||||
}
|
||||
|
||||
const mockHardwareStatus: HardwareStatus = {
|
||||
connected: false,
|
||||
port: '/dev/ttyUSB0',
|
||||
baud_rate: 9600,
|
||||
modbus_address: 1,
|
||||
last_communication: null,
|
||||
communication_errors: 0,
|
||||
temperatures: {
|
||||
tank_bottom: 22.5,
|
||||
tank_wall: 22.3,
|
||||
pump: 22.1,
|
||||
fountain: 22.0,
|
||||
},
|
||||
motors: {
|
||||
mixer: { running: false, speed: 0, current: 0 },
|
||||
pump: { running: false, speed: 0, current: 0 },
|
||||
},
|
||||
inputs: {
|
||||
pedal: false,
|
||||
emergency_stop: false,
|
||||
door_sensor: true,
|
||||
},
|
||||
outputs: {
|
||||
heating_element_1: false,
|
||||
heating_element_2: false,
|
||||
alarm: false,
|
||||
fountain_valve: false,
|
||||
},
|
||||
}
|
||||
|
||||
const mockSafetyStatus: SafetyStatus = {
|
||||
emergency_stop_active: false,
|
||||
door_open: false,
|
||||
temperature_alarms: [],
|
||||
motor_alarms: [],
|
||||
system_alarms: [],
|
||||
last_safety_check: new Date().toISOString(),
|
||||
}
|
||||
|
||||
const mockUsers: User[] = [
|
||||
{
|
||||
id: '1',
|
||||
username: 'admin',
|
||||
email: 'admin@example.com',
|
||||
role: 'admin',
|
||||
full_name: 'System Administrator',
|
||||
is_active: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
last_login: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
username: 'operator',
|
||||
email: 'operator@example.com',
|
||||
role: 'operator',
|
||||
full_name: 'Machine Operator',
|
||||
is_active: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
last_login: '2024-01-15T08:30:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
// Helper function to simulate API delays
|
||||
const delay = (ms: number = 500) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
// Mock API service class
|
||||
class MockApiService {
|
||||
private currentUser: User = mockUsers[0]
|
||||
private recipes: Recipe[] = [...mockRecipes]
|
||||
private processStatus: ProcessStatus = { ...mockProcessStatus }
|
||||
private hardwareStatus: HardwareStatus = { ...mockHardwareStatus }
|
||||
|
||||
// System endpoints
|
||||
async getSystemInfo(): Promise<SystemInfo> {
|
||||
await delay(300)
|
||||
return mockSystemInfo
|
||||
}
|
||||
|
||||
async getSystemStatus(): Promise<SystemStatus> {
|
||||
await delay(200)
|
||||
return mockSystemStatus
|
||||
}
|
||||
|
||||
async getHealthCheck(): Promise<{ status: string }> {
|
||||
await delay(100)
|
||||
return { status: 'healthy' }
|
||||
}
|
||||
|
||||
// Recipe endpoints
|
||||
async getRecipes(params?: {
|
||||
skip?: number
|
||||
limit?: number
|
||||
active_only?: boolean
|
||||
search?: string
|
||||
}): Promise<RecipeList> {
|
||||
await delay(300)
|
||||
|
||||
let filteredRecipes = [...this.recipes]
|
||||
|
||||
if (params?.active_only) {
|
||||
filteredRecipes = filteredRecipes.filter(r => r.is_active)
|
||||
}
|
||||
|
||||
if (params?.search) {
|
||||
const search = params.search.toLowerCase()
|
||||
filteredRecipes = filteredRecipes.filter(r =>
|
||||
r.name.toLowerCase().includes(search) ||
|
||||
r.description.toLowerCase().includes(search)
|
||||
)
|
||||
}
|
||||
|
||||
const skip = params?.skip || 0
|
||||
const limit = params?.limit || 10
|
||||
|
||||
return {
|
||||
recipes: filteredRecipes.slice(skip, skip + limit),
|
||||
total: filteredRecipes.length,
|
||||
skip,
|
||||
limit,
|
||||
}
|
||||
}
|
||||
|
||||
async getRecipe(id: number): Promise<Recipe> {
|
||||
await delay(200)
|
||||
const recipe = this.recipes.find(r => r.id === id)
|
||||
if (!recipe) {
|
||||
throw new Error(`Recipe with id ${id} not found`)
|
||||
}
|
||||
return recipe
|
||||
}
|
||||
|
||||
async createRecipe(recipe: RecipeCreate): Promise<Recipe> {
|
||||
await delay(500)
|
||||
const newRecipe: Recipe = {
|
||||
...recipe,
|
||||
id: Math.max(...this.recipes.map(r => r.id)) + 1,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
this.recipes.push(newRecipe)
|
||||
return newRecipe
|
||||
}
|
||||
|
||||
async updateRecipe(id: number, recipe: RecipeUpdate): Promise<Recipe> {
|
||||
await delay(500)
|
||||
const index = this.recipes.findIndex(r => r.id === id)
|
||||
if (index === -1) {
|
||||
throw new Error(`Recipe with id ${id} not found`)
|
||||
}
|
||||
|
||||
this.recipes[index] = {
|
||||
...this.recipes[index],
|
||||
...recipe,
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
return this.recipes[index]
|
||||
}
|
||||
|
||||
async deleteRecipe(id: number): Promise<void> {
|
||||
await delay(300)
|
||||
const index = this.recipes.findIndex(r => r.id === id)
|
||||
if (index === -1) {
|
||||
throw new Error(`Recipe with id ${id} not found`)
|
||||
}
|
||||
this.recipes.splice(index, 1)
|
||||
}
|
||||
|
||||
// Process control endpoints
|
||||
async getProcessStatus(): Promise<ProcessStatus> {
|
||||
await delay(200)
|
||||
// Simulate temperature fluctuations if process is running
|
||||
if (this.processStatus.is_running) {
|
||||
const temps = this.processStatus.current_temperatures
|
||||
temps.tank_bottom += (Math.random() - 0.5) * 0.2
|
||||
temps.tank_wall += (Math.random() - 0.5) * 0.2
|
||||
temps.pump += (Math.random() - 0.5) * 0.1
|
||||
temps.fountain += (Math.random() - 0.5) * 0.1
|
||||
}
|
||||
return { ...this.processStatus }
|
||||
}
|
||||
|
||||
async startProcess(request: ProcessStartRequest): Promise<ProcessActionResponse> {
|
||||
await delay(800)
|
||||
const recipe = this.recipes.find(r => r.id === request.recipe_id)
|
||||
if (!recipe) {
|
||||
throw new Error(`Recipe with id ${request.recipe_id} not found`)
|
||||
}
|
||||
|
||||
this.processStatus = {
|
||||
...this.processStatus,
|
||||
is_running: true,
|
||||
current_phase: 'heating',
|
||||
recipe_id: recipe.id,
|
||||
recipe_name: recipe.name,
|
||||
progress_percentage: 0,
|
||||
phase_time_remaining: recipe.heating_time,
|
||||
total_time_remaining: recipe.heating_time + recipe.cooling_time + recipe.working_time,
|
||||
target_temperatures: {
|
||||
tank_bottom: recipe.heating_temp,
|
||||
tank_wall: recipe.heating_temp,
|
||||
pump: recipe.heating_temp,
|
||||
fountain: recipe.heating_temp,
|
||||
},
|
||||
session_id: `mock-session-${Date.now()}`,
|
||||
started_at: new Date().toISOString(),
|
||||
estimated_completion: new Date(Date.now() + (recipe.heating_time + recipe.cooling_time + recipe.working_time) * 1000).toISOString(),
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Started tempering process with recipe: ${recipe.name}`,
|
||||
session_id: this.processStatus.session_id!,
|
||||
}
|
||||
}
|
||||
|
||||
async pauseProcess(): Promise<ProcessActionResponse> {
|
||||
await delay(300)
|
||||
this.processStatus.current_phase = 'paused'
|
||||
return {
|
||||
success: true,
|
||||
message: 'Process paused successfully',
|
||||
}
|
||||
}
|
||||
|
||||
async resumeProcess(): Promise<ProcessActionResponse> {
|
||||
await delay(300)
|
||||
this.processStatus.current_phase = 'heating' // or whatever phase it was in
|
||||
return {
|
||||
success: true,
|
||||
message: 'Process resumed successfully',
|
||||
}
|
||||
}
|
||||
|
||||
async stopProcess(): Promise<ProcessActionResponse> {
|
||||
await delay(500)
|
||||
this.processStatus = {
|
||||
...mockProcessStatus,
|
||||
session_id: null,
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: 'Process stopped successfully',
|
||||
}
|
||||
}
|
||||
|
||||
async emergencyStop(): Promise<ProcessActionResponse> {
|
||||
await delay(100)
|
||||
this.processStatus = {
|
||||
...mockProcessStatus,
|
||||
session_id: null,
|
||||
error: 'Emergency stop activated',
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: 'Emergency stop activated',
|
||||
}
|
||||
}
|
||||
|
||||
// Hardware endpoints
|
||||
async getHardwareStatus(): Promise<HardwareStatus> {
|
||||
await delay(300)
|
||||
return { ...this.hardwareStatus }
|
||||
}
|
||||
|
||||
async getTemperatures(): Promise<any[]> {
|
||||
await delay(200)
|
||||
return [
|
||||
{ sensor: 'tank_bottom', temperature: this.hardwareStatus.temperatures.tank_bottom, timestamp: new Date().toISOString() },
|
||||
{ sensor: 'tank_wall', temperature: this.hardwareStatus.temperatures.tank_wall, timestamp: new Date().toISOString() },
|
||||
{ sensor: 'pump', temperature: this.hardwareStatus.temperatures.pump, timestamp: new Date().toISOString() },
|
||||
{ sensor: 'fountain', temperature: this.hardwareStatus.temperatures.fountain, timestamp: new Date().toISOString() },
|
||||
]
|
||||
}
|
||||
|
||||
async getMotorStatus(): Promise<any[]> {
|
||||
await delay(200)
|
||||
return [
|
||||
{ motor: 'mixer', ...this.hardwareStatus.motors.mixer },
|
||||
{ motor: 'pump', ...this.hardwareStatus.motors.pump },
|
||||
]
|
||||
}
|
||||
|
||||
async setMotorSpeed(motorId: string, speed: number): Promise<void> {
|
||||
await delay(400)
|
||||
if (motorId in this.hardwareStatus.motors) {
|
||||
(this.hardwareStatus.motors as any)[motorId].speed = speed
|
||||
;(this.hardwareStatus.motors as any)[motorId].running = speed > 0
|
||||
}
|
||||
}
|
||||
|
||||
// Safety endpoints
|
||||
async getSafetyStatus(): Promise<SafetyStatus> {
|
||||
await delay(200)
|
||||
return mockSafetyStatus
|
||||
}
|
||||
|
||||
async acknowledgeAlarm(alarmId: string): Promise<void> {
|
||||
await delay(300)
|
||||
// Mock alarm acknowledgment
|
||||
}
|
||||
|
||||
async clearAlarms(): Promise<void> {
|
||||
await delay(500)
|
||||
// Mock alarm clearing
|
||||
}
|
||||
|
||||
// User management endpoints
|
||||
async getUsers(params?: {
|
||||
skip?: number
|
||||
limit?: number
|
||||
active_only?: boolean
|
||||
}): Promise<{ users: User[]; total: number }> {
|
||||
await delay(300)
|
||||
|
||||
let filteredUsers = [...mockUsers]
|
||||
|
||||
if (params?.active_only) {
|
||||
filteredUsers = filteredUsers.filter(u => u.is_active)
|
||||
}
|
||||
|
||||
const skip = params?.skip || 0
|
||||
const limit = params?.limit || 10
|
||||
|
||||
return {
|
||||
users: filteredUsers.slice(skip, skip + limit),
|
||||
total: filteredUsers.length,
|
||||
}
|
||||
}
|
||||
|
||||
async getUser(id: string): Promise<User> {
|
||||
await delay(200)
|
||||
const user = mockUsers.find(u => u.id === id)
|
||||
if (!user) {
|
||||
throw new Error(`User with id ${id} not found`)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
async createUser(user: UserCreate): Promise<User> {
|
||||
await delay(500)
|
||||
const newUser: User = {
|
||||
...user,
|
||||
id: (mockUsers.length + 1).toString(),
|
||||
created_at: new Date().toISOString(),
|
||||
last_login: null,
|
||||
}
|
||||
mockUsers.push(newUser)
|
||||
return newUser
|
||||
}
|
||||
|
||||
async updateUser(id: string, user: UserUpdate): Promise<User> {
|
||||
await delay(500)
|
||||
const index = mockUsers.findIndex(u => u.id === id)
|
||||
if (index === -1) {
|
||||
throw new Error(`User with id ${id} not found`)
|
||||
}
|
||||
|
||||
mockUsers[index] = {
|
||||
...mockUsers[index],
|
||||
...user,
|
||||
}
|
||||
|
||||
return mockUsers[index]
|
||||
}
|
||||
|
||||
async deleteUser(id: string): Promise<void> {
|
||||
await delay(300)
|
||||
const index = mockUsers.findIndex(u => u.id === id)
|
||||
if (index === -1) {
|
||||
throw new Error(`User with id ${id} not found`)
|
||||
}
|
||||
mockUsers.splice(index, 1)
|
||||
}
|
||||
|
||||
// Authentication endpoints
|
||||
async login(username: string, password: string): Promise<{ access_token: string; token_type: string; user: User }> {
|
||||
await delay(800)
|
||||
|
||||
// Mock authentication - accept any user from mockUsers with password "password"
|
||||
const user = mockUsers.find(u => u.username === username)
|
||||
if (!user || password !== 'password') {
|
||||
throw new Error('Invalid username or password')
|
||||
}
|
||||
|
||||
const token = `mock-token-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
// Store token
|
||||
localStorage.setItem('auth_token', token)
|
||||
|
||||
return {
|
||||
access_token: token,
|
||||
token_type: 'bearer',
|
||||
user,
|
||||
}
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
await delay(200)
|
||||
localStorage.removeItem('auth_token')
|
||||
}
|
||||
|
||||
async refreshToken(): Promise<{ access_token: string }> {
|
||||
await delay(300)
|
||||
const token = `mock-token-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
localStorage.setItem('auth_token', token)
|
||||
return { access_token: token }
|
||||
}
|
||||
|
||||
// Data export endpoints
|
||||
async exportProcessData(sessionId: string, format: 'csv' | 'json' = 'csv'): Promise<Blob> {
|
||||
await delay(1000)
|
||||
const mockData = format === 'csv'
|
||||
? 'timestamp,temperature,phase\n2024-01-01T00:00:00Z,25.0,heating\n2024-01-01T00:01:00Z,26.0,heating'
|
||||
: JSON.stringify({ session: sessionId, data: [] })
|
||||
|
||||
return new Blob([mockData], {
|
||||
type: format === 'csv' ? 'text/csv' : 'application/json'
|
||||
})
|
||||
}
|
||||
|
||||
async getProcessMetrics(days: number = 30): Promise<any> {
|
||||
await delay(500)
|
||||
return {
|
||||
total_sessions: 42,
|
||||
successful_sessions: 38,
|
||||
average_duration: 3600,
|
||||
temperature_stability: 98.5,
|
||||
efficiency_rating: 94.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mockApi = new MockApiService()
|
||||
Reference in New Issue
Block a user