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]);
|
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.
|
* Save Candidate Live Notepad Content.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -383,6 +383,24 @@
|
|||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
minimap: { enabled: 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
|
// Handle Language Switch
|
||||||
|
|||||||
@ -669,6 +669,8 @@ function toggleFullscreenScreen() {
|
|||||||
|
|
||||||
let activeViolations = [];
|
let activeViolations = [];
|
||||||
let sosPollInterval = null;
|
let sosPollInterval = null;
|
||||||
|
let acknowledgedViolationCount = 0;
|
||||||
|
let sosDismissed = false;
|
||||||
|
|
||||||
function switchIdeTab(tab) {
|
function switchIdeTab(tab) {
|
||||||
document.getElementById('tab-content-code').style.display = (tab === 'code') ? 'block' : 'none';
|
document.getElementById('tab-content-code').style.display = (tab === 'code') ? 'block' : 'none';
|
||||||
@ -687,6 +689,9 @@ function switchIdeTab(tab) {
|
|||||||
|
|
||||||
function openReviewModal(id, name, uid) {
|
function openReviewModal(id, name, uid) {
|
||||||
currentInterviewId = id;
|
currentInterviewId = id;
|
||||||
|
acknowledgedViolationCount = 0;
|
||||||
|
sosDismissed = false;
|
||||||
|
|
||||||
document.getElementById('modal-cand-name').innerText = name;
|
document.getElementById('modal-cand-name').innerText = name;
|
||||||
document.getElementById('modal-cand-uid').innerText = 'Unique Submission ID: ' + uid;
|
document.getElementById('modal-cand-uid').innerText = 'Unique Submission ID: ' + uid;
|
||||||
|
|
||||||
@ -703,7 +708,7 @@ function openReviewModal(id, name, uid) {
|
|||||||
|
|
||||||
pollReviewData();
|
pollReviewData();
|
||||||
clearInterval(sosPollInterval);
|
clearInterval(sosPollInterval);
|
||||||
sosPollInterval = setInterval(pollReviewData, 4000);
|
sosPollInterval = setInterval(pollReviewData, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pollReviewData() {
|
function pollReviewData() {
|
||||||
@ -712,7 +717,7 @@ function pollReviewData() {
|
|||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
document.getElementById('modal-code-lang').innerText = (data.submitted_language || 'UNKNOWN').toUpperCase();
|
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-output-display').innerText = data.code_output || 'No execution output.';
|
||||||
document.getElementById('modal-notes-display').innerText = data.candidate_notes || 'No candidate notes written yet.';
|
document.getElementById('modal-notes-display').innerText = data.candidate_notes || 'No candidate notes written yet.';
|
||||||
|
|
||||||
@ -728,17 +733,15 @@ function pollReviewData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let logsHtml = '';
|
let logsHtml = '';
|
||||||
let cheatingDetected = false;
|
let currentViolations = [];
|
||||||
activeViolations = [];
|
|
||||||
|
|
||||||
if (data.proctor_logs && data.proctor_logs.length > 0) {
|
if (data.proctor_logs && data.proctor_logs.length > 0) {
|
||||||
data.proctor_logs.forEach(l => {
|
data.proctor_logs.forEach(l => {
|
||||||
let icon = '🔴';
|
let icon = '🔴';
|
||||||
if (l.type === 'focus_lost') icon = '🟡';
|
if (l.type === 'focus_lost') icon = '🟡';
|
||||||
if (l.type === 'gaze_anomaly') icon = '👁️';
|
if (l.type === 'gaze_anomaly') icon = '👁️';
|
||||||
if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost') {
|
if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost' || l.type === 'gaze_anomaly') {
|
||||||
cheatingDetected = true;
|
currentViolations.push(l);
|
||||||
activeViolations.push(l);
|
|
||||||
}
|
}
|
||||||
logsHtml += `<div style="margin-bottom:6px; border-bottom:1px solid rgba(255,255,255,0.05); padding-bottom:4px;">
|
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 || ''}
|
<strong>${icon} ${l.type.toUpperCase()}:</strong> ${l.details || ''}
|
||||||
@ -750,9 +753,21 @@ function pollReviewData() {
|
|||||||
}
|
}
|
||||||
document.getElementById('modal-logs-display').innerHTML = logsHtml;
|
document.getElementById('modal-logs-display').innerHTML = logsHtml;
|
||||||
|
|
||||||
|
activeViolations = currentViolations;
|
||||||
|
const totalViolations = currentViolations.length;
|
||||||
const sosBtn = document.getElementById('sos-alert-btn');
|
const sosBtn = document.getElementById('sos-alert-btn');
|
||||||
if (cheatingDetected) {
|
|
||||||
|
if (totalViolations > 0) {
|
||||||
sosBtn.style.display = 'inline-block';
|
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 {
|
} else {
|
||||||
sosBtn.style.display = 'none';
|
sosBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
@ -777,10 +792,13 @@ function openSosModal() {
|
|||||||
|
|
||||||
function stopSosAlarm() {
|
function stopSosAlarm() {
|
||||||
document.getElementById('sos-modal').classList.remove('active');
|
document.getElementById('sos-modal').classList.remove('active');
|
||||||
|
acknowledgedViolationCount = activeViolations.length;
|
||||||
|
sosDismissed = true;
|
||||||
|
|
||||||
const sosBtn = document.getElementById('sos-alert-btn');
|
const sosBtn = document.getElementById('sos-alert-btn');
|
||||||
if (sosBtn) {
|
if (sosBtn) {
|
||||||
sosBtn.style.animation = 'none';
|
sosBtn.style.animation = 'none';
|
||||||
sosBtn.innerText = '✅ SOS Acknowledged';
|
sosBtn.innerText = '✅ SOS Muted (Auto-rearm active)';
|
||||||
sosBtn.style.background = '#475569';
|
sosBtn.style.background = '#475569';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
Route::post('/candidate/submit-code/{id}', [InterviewController::class, 'submitCode'])->name('interview.submit');
|
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/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/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/notes/{id}', [InterviewController::class, 'saveCandidateNotes'])->name('interview.candidate.notes');
|
||||||
Route::post('/candidate/drawing/{id}', [InterviewController::class, 'saveCandidateDrawing'])->name('interview.candidate.drawing');
|
Route::post('/candidate/drawing/{id}', [InterviewController::class, 'saveCandidateDrawing'])->name('interview.candidate.drawing');
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user