update
This commit is contained in:
parent
d1125bb69e
commit
167090816e
@ -309,6 +309,19 @@ public function sendWarning(Request $request, $id)
|
||||
return response()->json(['success' => true, 'warning' => $warning]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Live Sync Candidate Code (before submission).
|
||||
*/
|
||||
public function syncCandidateCode(Request $request, $id)
|
||||
{
|
||||
$interview = Interview::findOrFail($id);
|
||||
$interview->update([
|
||||
'submitted_code' => $request->input('code'),
|
||||
'submitted_language' => $request->input('language', $interview->language),
|
||||
]);
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Candidate Live Notepad Content.
|
||||
*/
|
||||
|
||||
@ -383,6 +383,24 @@
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: true }
|
||||
});
|
||||
|
||||
// Real-Time Code Sync to Interviewer (Before Submission)
|
||||
let codeSyncTimeout = null;
|
||||
editor.onDidChangeModelContent(function () {
|
||||
clearTimeout(codeSyncTimeout);
|
||||
codeSyncTimeout = setTimeout(function () {
|
||||
const code = editor.getValue();
|
||||
const lang = document.getElementById('language-select').value;
|
||||
fetch(`{{ route("interview.candidate.sync-code", $interview->id) }}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ code: code, language: lang })
|
||||
});
|
||||
}, 600);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle Language Switch
|
||||
|
||||
@ -669,6 +669,8 @@ function toggleFullscreenScreen() {
|
||||
|
||||
let activeViolations = [];
|
||||
let sosPollInterval = null;
|
||||
let acknowledgedViolationCount = 0;
|
||||
let sosDismissed = false;
|
||||
|
||||
function switchIdeTab(tab) {
|
||||
document.getElementById('tab-content-code').style.display = (tab === 'code') ? 'block' : 'none';
|
||||
@ -687,6 +689,9 @@ function switchIdeTab(tab) {
|
||||
|
||||
function openReviewModal(id, name, uid) {
|
||||
currentInterviewId = id;
|
||||
acknowledgedViolationCount = 0;
|
||||
sosDismissed = false;
|
||||
|
||||
document.getElementById('modal-cand-name').innerText = name;
|
||||
document.getElementById('modal-cand-uid').innerText = 'Unique Submission ID: ' + uid;
|
||||
|
||||
@ -703,7 +708,7 @@ function openReviewModal(id, name, uid) {
|
||||
|
||||
pollReviewData();
|
||||
clearInterval(sosPollInterval);
|
||||
sosPollInterval = setInterval(pollReviewData, 4000);
|
||||
sosPollInterval = setInterval(pollReviewData, 2000);
|
||||
}
|
||||
|
||||
function pollReviewData() {
|
||||
@ -712,7 +717,7 @@ function pollReviewData() {
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
document.getElementById('modal-code-lang').innerText = (data.submitted_language || 'UNKNOWN').toUpperCase();
|
||||
document.getElementById('modal-code-display').innerText = data.submitted_code || '// Candidate has not submitted code yet.';
|
||||
document.getElementById('modal-code-display').innerText = data.submitted_code || '// Candidate has not typed any code yet.';
|
||||
document.getElementById('modal-output-display').innerText = data.code_output || 'No execution output.';
|
||||
document.getElementById('modal-notes-display').innerText = data.candidate_notes || 'No candidate notes written yet.';
|
||||
|
||||
@ -728,17 +733,15 @@ function pollReviewData() {
|
||||
}
|
||||
|
||||
let logsHtml = '';
|
||||
let cheatingDetected = false;
|
||||
activeViolations = [];
|
||||
let currentViolations = [];
|
||||
|
||||
if (data.proctor_logs && data.proctor_logs.length > 0) {
|
||||
data.proctor_logs.forEach(l => {
|
||||
let icon = '🔴';
|
||||
if (l.type === 'focus_lost') icon = '🟡';
|
||||
if (l.type === 'gaze_anomaly') icon = '👁️';
|
||||
if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost') {
|
||||
cheatingDetected = true;
|
||||
activeViolations.push(l);
|
||||
if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost' || l.type === 'gaze_anomaly') {
|
||||
currentViolations.push(l);
|
||||
}
|
||||
logsHtml += `<div style="margin-bottom:6px; border-bottom:1px solid rgba(255,255,255,0.05); padding-bottom:4px;">
|
||||
<strong>${icon} ${l.type.toUpperCase()}:</strong> ${l.details || ''}
|
||||
@ -750,9 +753,21 @@ function pollReviewData() {
|
||||
}
|
||||
document.getElementById('modal-logs-display').innerHTML = logsHtml;
|
||||
|
||||
activeViolations = currentViolations;
|
||||
const totalViolations = currentViolations.length;
|
||||
const sosBtn = document.getElementById('sos-alert-btn');
|
||||
if (cheatingDetected) {
|
||||
|
||||
if (totalViolations > 0) {
|
||||
sosBtn.style.display = 'inline-block';
|
||||
|
||||
// AUTOMATIC SOS TRIGGER & RE-ARMING ON NEW CHEATING EVENTS
|
||||
if (totalViolations > acknowledgedViolationCount) {
|
||||
sosDismissed = false;
|
||||
sosBtn.style.animation = 'sosPulse 1.2s infinite alternate';
|
||||
sosBtn.innerText = '🚨 SOS CHEATING DETECTED!';
|
||||
sosBtn.style.background = 'linear-gradient(135deg, #ef4444 0%, #b91c1c 100%)';
|
||||
openSosModal();
|
||||
}
|
||||
} else {
|
||||
sosBtn.style.display = 'none';
|
||||
}
|
||||
@ -777,10 +792,13 @@ function openSosModal() {
|
||||
|
||||
function stopSosAlarm() {
|
||||
document.getElementById('sos-modal').classList.remove('active');
|
||||
acknowledgedViolationCount = activeViolations.length;
|
||||
sosDismissed = true;
|
||||
|
||||
const sosBtn = document.getElementById('sos-alert-btn');
|
||||
if (sosBtn) {
|
||||
sosBtn.style.animation = 'none';
|
||||
sosBtn.innerText = '✅ SOS Acknowledged';
|
||||
sosBtn.innerText = '✅ SOS Muted (Auto-rearm active)';
|
||||
sosBtn.style.background = '#475569';
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
Route::post('/candidate/submit-code/{id}', [InterviewController::class, 'submitCode'])->name('interview.submit');
|
||||
Route::post('/candidate/log-violation/{id}', [InterviewController::class, 'logViolation'])->name('interview.violation');
|
||||
Route::post('/candidate/upload-recording/{id}', [InterviewController::class, 'uploadRecording'])->name('interview.upload-recording');
|
||||
Route::post('/candidate/sync-code/{id}', [InterviewController::class, 'syncCandidateCode'])->name('interview.candidate.sync-code');
|
||||
Route::post('/candidate/notes/{id}', [InterviewController::class, 'saveCandidateNotes'])->name('interview.candidate.notes');
|
||||
Route::post('/candidate/drawing/{id}', [InterviewController::class, 'saveCandidateDrawing'])->name('interview.candidate.drawing');
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user