Enhance camera management features: add debug endpoint for camera manager state, implement live camera routes without authentication, and improve logging for camera initialization and status checks. Update Docker configuration to include environment variables for the web app.
This commit is contained in:
0
management-dashboard-web-app/src/App.css
Normal file → Executable file
0
management-dashboard-web-app/src/App.css
Normal file → Executable file
21
management-dashboard-web-app/src/App.tsx
Normal file → Executable file
21
management-dashboard-web-app/src/App.tsx
Normal file → Executable file
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { supabase } from './lib/supabase'
|
||||
import { Login } from './components/Login'
|
||||
import { Dashboard } from './components/Dashboard'
|
||||
import { CameraRoute } from './components/CameraRoute'
|
||||
|
||||
function App() {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null)
|
||||
@@ -84,6 +85,18 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if current route is a camera live route
|
||||
const isCameraLiveRoute = (route: string) => {
|
||||
const cameraRoutePattern = /^\/camera(\d+)\/live$/
|
||||
return cameraRoutePattern.test(route)
|
||||
}
|
||||
|
||||
// Extract camera number from route
|
||||
const getCameraNumber = (route: string) => {
|
||||
const match = route.match(/^\/camera(\d+)\/live$/)
|
||||
return match ? `camera${match[1]}` : null
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
@@ -107,6 +120,14 @@ function App() {
|
||||
)
|
||||
}
|
||||
|
||||
// Handle camera live routes (no authentication required)
|
||||
if (isCameraLiveRoute(currentRoute)) {
|
||||
const cameraNumber = getCameraNumber(currentRoute)
|
||||
if (cameraNumber) {
|
||||
return <CameraRoute cameraNumber={cameraNumber} />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAuthenticated ? (
|
||||
|
||||
0
management-dashboard-web-app/src/assets/react.svg
Normal file → Executable file
0
management-dashboard-web-app/src/assets/react.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
0
management-dashboard-web-app/src/components/AutoRecordingStatus.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/AutoRecordingStatus.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/AutoRecordingTest.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/AutoRecordingTest.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraConfigModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraConfigModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraPreviewModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CameraPreviewModal.tsx
Normal file → Executable file
25
management-dashboard-web-app/src/components/CameraRoute.tsx
Executable file
25
management-dashboard-web-app/src/components/CameraRoute.tsx
Executable file
@@ -0,0 +1,25 @@
|
||||
import { LiveCameraView } from './LiveCameraView'
|
||||
|
||||
interface CameraRouteProps {
|
||||
cameraNumber: string
|
||||
}
|
||||
|
||||
export function CameraRoute({ cameraNumber }: CameraRouteProps) {
|
||||
// Validate camera number (only allow camera1, camera2, etc.)
|
||||
if (!cameraNumber || !/^camera\d+$/.test(cameraNumber)) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="text-center text-white">
|
||||
<h1 className="text-2xl font-bold mb-4">Invalid Camera</h1>
|
||||
<p className="text-gray-300">Camera number must be in format: camera1, camera2, etc.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <LiveCameraView cameraName={cameraNumber} />
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
0
management-dashboard-web-app/src/components/CreateUserModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/CreateUserModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Dashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Dashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardHome.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardHome.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardLayout.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DashboardLayout.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/DraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentForm.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentForm.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ExperimentModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Experiments.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Experiments.tsx
Normal file → Executable file
134
management-dashboard-web-app/src/components/LiveCameraView.tsx
Executable file
134
management-dashboard-web-app/src/components/LiveCameraView.tsx
Executable file
@@ -0,0 +1,134 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
|
||||
interface LiveCameraViewProps {
|
||||
cameraName: string
|
||||
}
|
||||
|
||||
export function LiveCameraView({ cameraName }: LiveCameraViewProps) {
|
||||
const [isStreaming, setIsStreaming] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const imgRef = useRef<HTMLImageElement>(null)
|
||||
|
||||
const API_BASE = import.meta.env.VITE_VISION_API_URL || 'http://localhost:8000'
|
||||
|
||||
useEffect(() => {
|
||||
startStreaming()
|
||||
return () => stopStreaming()
|
||||
}, [cameraName])
|
||||
|
||||
const startStreaming = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
// Start the stream
|
||||
const response = await fetch(`${API_BASE}/cameras/${cameraName}/start-stream`, {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
setIsStreaming(true)
|
||||
// Set the stream source with timestamp to prevent caching
|
||||
if (imgRef.current) {
|
||||
imgRef.current.src = `${API_BASE}/cameras/${cameraName}/stream?t=${Date.now()}`
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Failed to start stream: ${response.statusText}`)
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to start stream'
|
||||
setError(errorMessage)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const stopStreaming = async () => {
|
||||
try {
|
||||
if (isStreaming) {
|
||||
await fetch(`${API_BASE}/cameras/${cameraName}/stop-stream`, {
|
||||
method: 'POST'
|
||||
})
|
||||
setIsStreaming(false)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error stopping stream:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
setError('Failed to load camera stream')
|
||||
}
|
||||
|
||||
const handleImageLoad = () => {
|
||||
setError(null)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="text-center text-white">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white mx-auto"></div>
|
||||
<p className="mt-4">Starting camera stream...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="text-center text-white">
|
||||
<div className="bg-red-600 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold mb-2">Stream Error</h2>
|
||||
<p className="text-gray-300 mb-4">{error}</p>
|
||||
<button
|
||||
onClick={startStreaming}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 flex items-center justify-center">
|
||||
<div className="relative">
|
||||
{/* Camera Label */}
|
||||
<div className="absolute top-4 left-4 z-10">
|
||||
<div className="bg-black bg-opacity-75 text-white px-3 py-1 rounded-md text-sm font-medium">
|
||||
{cameraName} - Live View
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Stream */}
|
||||
<img
|
||||
ref={imgRef}
|
||||
alt={`Live stream from ${cameraName}`}
|
||||
className="max-w-full max-h-screen object-contain"
|
||||
onError={handleImageError}
|
||||
onLoad={handleImageLoad}
|
||||
/>
|
||||
|
||||
{/* Status Indicator */}
|
||||
<div className="absolute bottom-4 right-4 z-10">
|
||||
<div className="flex items-center space-x-2 bg-black bg-opacity-75 text-white px-3 py-1 rounded-md">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span className="text-sm">LIVE</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
0
management-dashboard-web-app/src/components/Login.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Login.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDataEntry.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseDraftManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/PhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionDataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionDataEntryInterface.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionLockManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionLockManager.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionPhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionPhaseSelector.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/RepetitionScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/ScheduleModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Sidebar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/Sidebar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/TopNavbar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/TopNavbar.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/UserManagement.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/components/UserManagement.tsx
Normal file → Executable file
27
management-dashboard-web-app/src/components/VisionSystem.tsx
Normal file → Executable file
27
management-dashboard-web-app/src/components/VisionSystem.tsx
Normal file → Executable file
@@ -196,9 +196,10 @@ const CamerasStatus = memo(({
|
||||
const hasSerial = !!camera.device_info?.serial_number
|
||||
|
||||
// Determine if camera is connected based on status
|
||||
const isConnected = camera.status === 'available' || camera.status === 'connected'
|
||||
const isConnected = camera.status === 'available' || camera.status === 'connected' || camera.status === 'streaming'
|
||||
const hasError = camera.status === 'error'
|
||||
const statusText = camera.status || 'unknown'
|
||||
const isStreaming = camera.status === 'streaming'
|
||||
|
||||
return (
|
||||
<div key={cameraName} className="border border-gray-200 rounded-lg p-4">
|
||||
@@ -209,11 +210,12 @@ const CamerasStatus = memo(({
|
||||
<span className="text-gray-500 text-sm font-normal ml-2">({cameraName})</span>
|
||||
)}
|
||||
</h4>
|
||||
<div className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${isConnected ? 'bg-green-100 text-green-800' :
|
||||
hasError ? 'bg-yellow-100 text-yellow-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
<div className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${isStreaming ? 'bg-blue-100 text-blue-800' :
|
||||
isConnected ? 'bg-green-100 text-green-800' :
|
||||
hasError ? 'bg-yellow-100 text-yellow-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{isConnected ? 'Connected' : hasError ? 'Error' : 'Disconnected'}
|
||||
{isStreaming ? 'Streaming' : isConnected ? 'Connected' : hasError ? 'Error' : 'Disconnected'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -224,7 +226,8 @@ const CamerasStatus = memo(({
|
||||
hasError ? 'text-yellow-600' :
|
||||
'text-red-600'
|
||||
}`}>
|
||||
{statusText.charAt(0).toUpperCase() + statusText.slice(1)}
|
||||
{isStreaming ? 'Streaming' :
|
||||
statusText.charAt(0).toUpperCase() + statusText.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -238,6 +241,16 @@ const CamerasStatus = memo(({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isStreaming && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Streaming:</span>
|
||||
<span className="text-blue-600 font-medium flex items-center">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"></div>
|
||||
Live
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasDeviceInfo && (
|
||||
<>
|
||||
{camera.device_info.model && (
|
||||
@@ -923,7 +936,7 @@ export function VisionSystem() {
|
||||
|
||||
{/* Notification */}
|
||||
{notification && (
|
||||
<div className={`fixed top-4 right-4 z-50 p-4 rounded-md shadow-lg ${notification.type === 'success'
|
||||
<div className={`fixed top-4 right-4 z-[999999] p-4 rounded-md shadow-lg ${notification.type === 'success'
|
||||
? 'bg-green-50 border border-green-200 text-green-800'
|
||||
: 'bg-red-50 border border-red-200 text-red-800'
|
||||
}`}>
|
||||
|
||||
0
management-dashboard-web-app/src/features/video-streaming/VideoStreamingPage.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/VideoStreamingPage.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/ApiStatusIndicator.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/ApiStatusIndicator.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/Pagination.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/Pagination.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/PerformanceDashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/PerformanceDashboard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoCard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoCard.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoDebugger.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoDebugger.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoErrorBoundary.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoErrorBoundary.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoList.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoList.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoModal.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoPlayer.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoPlayer.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoThumbnail.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/VideoThumbnail.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/components/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoInfo.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoInfo.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoList.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoList.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoPlayer.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/hooks/useVideoPlayer.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/services/videoApi.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/types/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/types/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/index.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/performanceMonitor.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/performanceMonitor.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/thumbnailCache.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/thumbnailCache.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/videoUtils.ts
Normal file → Executable file
0
management-dashboard-web-app/src/features/video-streaming/utils/videoUtils.ts
Normal file → Executable file
0
management-dashboard-web-app/src/hooks/useAuth.ts
Normal file → Executable file
0
management-dashboard-web-app/src/hooks/useAuth.ts
Normal file → Executable file
0
management-dashboard-web-app/src/hooks/useAutoRecording.ts
Normal file → Executable file
0
management-dashboard-web-app/src/hooks/useAutoRecording.ts
Normal file → Executable file
0
management-dashboard-web-app/src/index.css
Normal file → Executable file
0
management-dashboard-web-app/src/index.css
Normal file → Executable file
0
management-dashboard-web-app/src/lib/autoRecordingManager.ts
Normal file → Executable file
0
management-dashboard-web-app/src/lib/autoRecordingManager.ts
Normal file → Executable file
0
management-dashboard-web-app/src/lib/supabase.ts
Normal file → Executable file
0
management-dashboard-web-app/src/lib/supabase.ts
Normal file → Executable file
0
management-dashboard-web-app/src/lib/visionApi.ts
Normal file → Executable file
0
management-dashboard-web-app/src/lib/visionApi.ts
Normal file → Executable file
0
management-dashboard-web-app/src/main.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/main.tsx
Normal file → Executable file
0
management-dashboard-web-app/src/test/videoStreamingTest.ts
Normal file → Executable file
0
management-dashboard-web-app/src/test/videoStreamingTest.ts
Normal file → Executable file
0
management-dashboard-web-app/src/test/visionApi.test.ts
Normal file → Executable file
0
management-dashboard-web-app/src/test/visionApi.test.ts
Normal file → Executable file
0
management-dashboard-web-app/src/utils/videoFileUtils.ts
Normal file → Executable file
0
management-dashboard-web-app/src/utils/videoFileUtils.ts
Normal file → Executable file
0
management-dashboard-web-app/src/vite-env.d.ts
vendored
Normal file → Executable file
0
management-dashboard-web-app/src/vite-env.d.ts
vendored
Normal file → Executable file
Reference in New Issue
Block a user