Classroom SignalR ve WebRtc güvenlik düzenlemesi

This commit is contained in:
Sedat Öztürk 2025-08-30 23:30:09 +03:00
parent a09f65f53d
commit d8c4f39bd3
2 changed files with 75 additions and 28 deletions

View file

@ -18,6 +18,8 @@ interface ParticipantsPanelProps {
attendanceRecords: ClassroomAttendanceDto[] attendanceRecords: ClassroomAttendanceDto[]
onMuteParticipant: (participantId: string, isMuted: boolean, isTeacher: boolean) => void onMuteParticipant: (participantId: string, isMuted: boolean, isTeacher: boolean) => void
onKickParticipant: (participantId: string) => void onKickParticipant: (participantId: string) => void
onApproveHandRaise: (participantId: string) => void
onDismissHandRaise: (participantId: string) => void
onClose: () => void onClose: () => void
formatTime: (timestamp: string) => string formatTime: (timestamp: string) => string
formatDuration: (minutes: number) => string formatDuration: (minutes: number) => string
@ -30,11 +32,16 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
onMuteParticipant, onMuteParticipant,
onKickParticipant, onKickParticipant,
onClose, onClose,
onApproveHandRaise,
onDismissHandRaise,
formatTime, formatTime,
formatDuration, formatDuration,
}) => { }) => {
const [activeTab, setActiveTab] = useState<'participants' | 'attendance'>('participants') const [activeTab, setActiveTab] = useState<'participants' | 'attendance'>('participants')
// El kaldıranları bul
const handRaised = participants.filter((p) => p.isHandRaised)
return ( return (
<div className="h-full bg-white flex flex-col text-gray-900"> <div className="h-full bg-white flex flex-col text-gray-900">
{/* Header */} {/* Header */}
@ -48,6 +55,19 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
</button> </button>
</div> </div>
{/* El kaldıranlar göstergesi */}
{user.role === 'teacher' && handRaised.length > 0 && (
<div className="mb-2 flex items-center space-x-2 p-2 bg-yellow-50 rounded">
<FaHandPaper className="text-yellow-600" />
<span className="font-medium text-yellow-800">
{handRaised.length} kişi el kaldırdı:
</span>
<span className="text-yellow-900 text-sm truncate">
{handRaised.map((p) => p.name).join(', ')}
</span>
</div>
)}
{/* Tab Navigation */} {/* Tab Navigation */}
<div className="flex space-x-1 bg-gray-100 rounded-lg p-1"> <div className="flex space-x-1 bg-gray-100 rounded-lg p-1">
<button <button
@ -109,34 +129,24 @@ const ParticipantsPanel: React.FC<ParticipantsPanelProps> = ({
</div> </div>
<span className="text-gray-900">{participant.name}</span> <span className="text-gray-900">{participant.name}</span>
{/* Hand Raise Indicator */} {/* Hand Raise Indicator & Teacher Control */}
{participant.isHandRaised && ( {participant.isHandRaised && (
user.role === 'teacher' && !participant.isTeacher ? (
<button
onClick={() => onDismissHandRaise(participant.id)}
className="ml-2 p-1 rounded bg-yellow-100 hover:bg-yellow-200"
title="El kaldırmayı kaldır"
>
<FaHandPaper className="text-yellow-600" />
</button>
) : (
<FaHandPaper className="text-yellow-600 ml-2" title="Parmak kaldırdı" /> <FaHandPaper className="text-yellow-600 ml-2" title="Parmak kaldırdı" />
)
)} )}
</div> </div>
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
{/* Hand Raise Controls (Teacher Only) */} {/* Hand Raise Controls kaldırıldı, kontrol yukarıya taşındı */}
{user.role === 'teacher' &&
!participant.isTeacher &&
participant.isHandRaised && (
<>
<button
onClick={() => console.log('approveHandRaise', participant.id)}
className="p-1 text-green-600 hover:bg-green-50 rounded transition-colors"
title="El kaldırmayı onayla"
>
<FaCheck size={12} />
</button>
<button
onClick={() => console.log('dismissHandRaise', participant.id)}
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
title="El kaldırmayı reddet"
>
<FaTimes size={12} />
</button>
</>
)}
{/* Mute / Unmute Button */} {/* Mute / Unmute Button */}
{user.role === 'teacher' && !participant.isTeacher && ( {user.role === 'teacher' && !participant.isTeacher && (

View file

@ -83,6 +83,20 @@ const newClassSession: ClassroomDto = {
} }
const RoomDetail: React.FC = () => { const RoomDetail: React.FC = () => {
// El kaldırma onayla/iptal fonksiyonları en başta tanımlanmalı
// (props olarak kullanılmadan önce)
const handleApproveHandRaise = async (participantId: string) => {
if (signalRServiceRef.current && user.role === 'teacher') {
await signalRServiceRef.current.approveHandRaise(classSession.id, participantId)
}
}
const handleDismissHandRaise = async (participantId: string) => {
if (signalRServiceRef.current && user.role === 'teacher') {
await signalRServiceRef.current.dismissHandRaise(classSession.id, participantId)
}
}
const params = useParams() const params = useParams()
const navigate = useNavigate() const navigate = useNavigate()
const { user } = useStoreState((state) => state.auth) const { user } = useStoreState((state) => state.auth)
@ -119,7 +133,7 @@ const RoomDetail: React.FC = () => {
const [selectedRecipient, setSelectedRecipient] = useState<{ id: string; name: string } | null>( const [selectedRecipient, setSelectedRecipient] = useState<{ id: string; name: string } | null>(
null, null,
) )
const [dragOver, setDragOver] = useState(false) const raisedHandsCount = participants.filter((p) => p.isHandRaised).length
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
const messagesEndRef = useRef<HTMLDivElement>(null) const messagesEndRef = useRef<HTMLDivElement>(null)
const [classSettings, setClassSettings] = useState<ClassroomSettingsDto>({ const [classSettings, setClassSettings] = useState<ClassroomSettingsDto>({
@ -298,7 +312,7 @@ const RoomDetail: React.FC = () => {
} }
// ✅ öğretmen ise her zaman offer başlatır // ✅ öğretmen ise her zaman offer başlatır
if (isTeacher || (isActive && user.id < userId)) { if (isActive && user.id < userId) {
const offer = await webRTCServiceRef.current!.createOffer(userId) const offer = await webRTCServiceRef.current!.createOffer(userId)
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer) await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
} }
@ -724,6 +738,9 @@ const RoomDetail: React.FC = () => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
} }
// ...existing code...
// ...existing code...
// --- yan panel fonksiyonu ---
const renderSidePanel = () => { const renderSidePanel = () => {
if (!activeSidePanel) return null if (!activeSidePanel) return null
@ -754,12 +771,15 @@ const RoomDetail: React.FC = () => {
attendanceRecords={attendanceRecords} attendanceRecords={attendanceRecords}
onMuteParticipant={handleMuteParticipant} onMuteParticipant={handleMuteParticipant}
onKickParticipant={handleKickParticipant} onKickParticipant={handleKickParticipant}
onApproveHandRaise={handleApproveHandRaise}
onDismissHandRaise={handleDismissHandRaise}
onClose={() => setActiveSidePanel(null)} onClose={() => setActiveSidePanel(null)}
formatTime={formatTime} formatTime={formatTime}
formatDuration={formatDuration} formatDuration={formatDuration}
/> />
) )
// ...existing code...
case 'documents': case 'documents':
return ( return (
<DocumentsPanel <DocumentsPanel
@ -1022,7 +1042,16 @@ const RoomDetail: React.FC = () => {
}} }}
className={`flex items-center space-x-2 p-3 rounded-lg transition-all text-base ${activeSidePanel === 'participants' ? 'bg-blue-100 text-blue-700' : 'hover:bg-gray-100 text-gray-700'}`} className={`flex items-center space-x-2 p-3 rounded-lg transition-all text-base ${activeSidePanel === 'participants' ? 'bg-blue-100 text-blue-700' : 'hover:bg-gray-100 text-gray-700'}`}
> >
<FaUserFriends /> <span>Katılımcılar</span> <FaUserFriends />
<span>Katılımcılar</span>
<span className="absolute -top-1 -right-1 bg-blue-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
{participants.length}
</span>
{raisedHandsCount > 0 && (
<span className="absolute -top-1 -right-6 bg-yellow-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
{raisedHandsCount > 9 ? '9+' : raisedHandsCount}
</span>
)}
</button> </button>
<button <button
onClick={() => { onClick={() => {
@ -1241,6 +1270,14 @@ const RoomDetail: React.FC = () => {
title="Katılımcılar" title="Katılımcılar"
> >
<FaUserFriends size={14} /> <FaUserFriends size={14} />
<span className="absolute -top-1 -right-1 bg-blue-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
{participants.length}
</span>
{raisedHandsCount > 0 && (
<span className="absolute -top-1 -right-6 bg-yellow-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center text-[10px]">
{raisedHandsCount > 9 ? '9+' : raisedHandsCount}
</span>
)}
</button> </button>
{/* Teacher Only Options */} {/* Teacher Only Options */}