Update Docker configuration, enhance error handling, and improve logging
- Added health check to the camera management API service in docker-compose.yml for better container reliability. - Updated installation scripts in Dockerfile to check for existing dependencies before installation, improving efficiency. - Enhanced error handling in the USDAVisionSystem class to allow partial operation if some components fail to start, preventing immediate shutdown. - Improved logging throughout the application, including more detailed error messages and critical error handling in the main loop. - Refactored WebSocketManager and CameraMonitor classes to use debug logging for connection events, reducing log noise.
This commit is contained in:
@@ -932,7 +932,6 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
|
||||
// Create/update soaking record with repetition_id
|
||||
await phaseManagement.createSoaking({
|
||||
experiment_id: experimentId,
|
||||
repetition_id: repId,
|
||||
scheduled_start_time: soakingStart.toISOString(),
|
||||
soaking_duration_minutes: soaking.soaking_duration_minutes
|
||||
@@ -940,7 +939,6 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
|
||||
// Create/update airdrying record with repetition_id
|
||||
await phaseManagement.createAirdrying({
|
||||
experiment_id: experimentId,
|
||||
repetition_id: repId,
|
||||
scheduled_start_time: airdryingStart.toISOString(),
|
||||
duration_minutes: airdrying.duration_minutes
|
||||
@@ -953,7 +951,6 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
|
||||
if (phase?.cracking_machine_type_id) {
|
||||
await phaseManagement.createCracking({
|
||||
experiment_id: experimentId,
|
||||
repetition_id: repId,
|
||||
machine_type_id: phase.cracking_machine_type_id,
|
||||
scheduled_start_time: crackingStart.toISOString()
|
||||
@@ -995,8 +992,8 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
|
||||
|
||||
return (
|
||||
<div className="p-6 flex flex-col" style={{ height: 'calc(100vh - 48px)' }}>
|
||||
<div className="mb-6 flex-shrink-0">
|
||||
<div className="h-full flex flex-col overflow-hidden -m-4 md:-m-6">
|
||||
<div className="p-6 flex-shrink-0">
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="flex items-center text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 mb-4"
|
||||
@@ -1014,7 +1011,8 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-6 flex flex-col flex-1 min-h-0">
|
||||
<div className="px-6 pb-6 flex-1 min-h-0 overflow-hidden">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-6 h-full flex flex-col min-h-0 overflow-hidden">
|
||||
{error && (
|
||||
<div className="mb-4 text-sm text-red-600 dark:text-red-400">{error}</div>
|
||||
)}
|
||||
@@ -1250,8 +1248,8 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
</div>
|
||||
)}
|
||||
{/* Week Calendar for selected conductors' availability */}
|
||||
<div className="mt-6 flex flex-col flex-1 min-h-0">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="mt-6 flex flex-col flex-1 min-h-0 overflow-hidden">
|
||||
<div className="flex justify-between items-center mb-3 flex-shrink-0">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Selected Conductors' Availability & Experiment Scheduling</h3>
|
||||
<div className="flex gap-2">
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Marker Style:</span>
|
||||
@@ -1293,7 +1291,7 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={calendarRef} className="flex-1 border border-gray-200 dark:border-gray-700 rounded-md overflow-hidden relative">
|
||||
<div ref={calendarRef} className="flex-1 min-h-0 border border-gray-200 dark:border-gray-700 rounded-md overflow-hidden relative">
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<DnDCalendar
|
||||
localizer={localizer}
|
||||
@@ -1383,6 +1381,7 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -63,8 +63,7 @@ export interface ExperimentRepetition {
|
||||
|
||||
export interface Soaking {
|
||||
id: string
|
||||
experiment_id: string
|
||||
repetition_id?: string | null
|
||||
repetition_id: string
|
||||
scheduled_start_time: string
|
||||
actual_start_time?: string | null
|
||||
soaking_duration_minutes: number
|
||||
@@ -77,8 +76,7 @@ export interface Soaking {
|
||||
|
||||
export interface Airdrying {
|
||||
id: string
|
||||
experiment_id: string
|
||||
repetition_id?: string | null
|
||||
repetition_id: string
|
||||
scheduled_start_time: string
|
||||
actual_start_time?: string | null
|
||||
duration_minutes: number
|
||||
@@ -119,22 +117,19 @@ export interface UpdateRepetitionRequest {
|
||||
}
|
||||
|
||||
export interface CreateSoakingRequest {
|
||||
experiment_id: string
|
||||
repetition_id?: string | null
|
||||
repetition_id: string
|
||||
scheduled_start_time: string
|
||||
soaking_duration_minutes: number
|
||||
}
|
||||
|
||||
export interface CreateAirdryingRequest {
|
||||
experiment_id: string
|
||||
repetition_id?: string | null
|
||||
repetition_id: string
|
||||
scheduled_start_time: string
|
||||
duration_minutes: number
|
||||
}
|
||||
|
||||
export interface CreateCrackingRequest {
|
||||
experiment_id: string
|
||||
repetition_id?: string | null
|
||||
repetition_id: string
|
||||
machine_type_id: string
|
||||
scheduled_start_time: string
|
||||
}
|
||||
@@ -277,16 +272,22 @@ export const phaseManagement = {
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
if (authError || !user) throw new Error('User not authenticated')
|
||||
|
||||
if (!request.repetition_id) {
|
||||
throw new Error('repetition_id is required')
|
||||
}
|
||||
|
||||
const scheduledEndTime = new Date(new Date(request.scheduled_start_time).getTime() + request.soaking_duration_minutes * 60000).toISOString()
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('soaking')
|
||||
.upsert({
|
||||
...request,
|
||||
repetition_id: request.repetition_id,
|
||||
scheduled_start_time: request.scheduled_start_time,
|
||||
soaking_duration_minutes: request.soaking_duration_minutes,
|
||||
scheduled_end_time: scheduledEndTime,
|
||||
created_by: user.id
|
||||
}, {
|
||||
onConflict: 'experiment_id,repetition_id'
|
||||
onConflict: 'repetition_id'
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
@@ -299,16 +300,26 @@ export const phaseManagement = {
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
if (authError || !user) throw new Error('User not authenticated')
|
||||
|
||||
if (!request.repetition_id) {
|
||||
throw new Error('repetition_id is required')
|
||||
}
|
||||
|
||||
if (!request.scheduled_start_time) {
|
||||
throw new Error('scheduled_start_time is required')
|
||||
}
|
||||
|
||||
const scheduledEndTime = new Date(new Date(request.scheduled_start_time).getTime() + request.duration_minutes * 60000).toISOString()
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('airdrying')
|
||||
.upsert({
|
||||
...request,
|
||||
repetition_id: request.repetition_id,
|
||||
scheduled_start_time: request.scheduled_start_time,
|
||||
duration_minutes: request.duration_minutes,
|
||||
scheduled_end_time: scheduledEndTime,
|
||||
created_by: user.id
|
||||
}, {
|
||||
onConflict: 'experiment_id,repetition_id'
|
||||
onConflict: 'repetition_id'
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
@@ -321,13 +332,23 @@ export const phaseManagement = {
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
if (authError || !user) throw new Error('User not authenticated')
|
||||
|
||||
if (!request.repetition_id) {
|
||||
throw new Error('repetition_id is required')
|
||||
}
|
||||
|
||||
if (!request.scheduled_start_time) {
|
||||
throw new Error('scheduled_start_time is required')
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('cracking')
|
||||
.upsert({
|
||||
...request,
|
||||
repetition_id: request.repetition_id,
|
||||
machine_type_id: request.machine_type_id,
|
||||
scheduled_start_time: request.scheduled_start_time,
|
||||
created_by: user.id
|
||||
}, {
|
||||
onConflict: 'experiment_id,repetition_id'
|
||||
onConflict: 'repetition_id'
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
@@ -337,11 +358,23 @@ export const phaseManagement = {
|
||||
},
|
||||
|
||||
async getSoakingByExperimentId(experimentId: string): Promise<Soaking | null> {
|
||||
// Get the first repetition for this experiment
|
||||
const { data: repetitions, error: repsError } = await supabase
|
||||
.from('experiment_repetitions')
|
||||
.select('id')
|
||||
.eq('experiment_id', experimentId)
|
||||
.order('repetition_number', { ascending: true })
|
||||
.limit(1)
|
||||
|
||||
if (repsError || !repetitions || repetitions.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get soaking for the first repetition
|
||||
const { data, error } = await supabase
|
||||
.from('soaking')
|
||||
.select('*')
|
||||
.eq('experiment_id', experimentId)
|
||||
.is('repetition_id', null)
|
||||
.eq('repetition_id', repetitions[0].id)
|
||||
.single()
|
||||
|
||||
if (error) {
|
||||
@@ -352,11 +385,23 @@ export const phaseManagement = {
|
||||
},
|
||||
|
||||
async getAirdryingByExperimentId(experimentId: string): Promise<Airdrying | null> {
|
||||
// Get the first repetition for this experiment
|
||||
const { data: repetitions, error: repsError } = await supabase
|
||||
.from('experiment_repetitions')
|
||||
.select('id')
|
||||
.eq('experiment_id', experimentId)
|
||||
.order('repetition_number', { ascending: true })
|
||||
.limit(1)
|
||||
|
||||
if (repsError || !repetitions || repetitions.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get airdrying for the first repetition
|
||||
const { data, error } = await supabase
|
||||
.from('airdrying')
|
||||
.select('*')
|
||||
.eq('experiment_id', experimentId)
|
||||
.is('repetition_id', null)
|
||||
.eq('repetition_id', repetitions[0].id)
|
||||
.single()
|
||||
|
||||
if (error) {
|
||||
|
||||
Reference in New Issue
Block a user