An error has occurred when submitting your answers. A copy of your answers are displayed below, you may wish to save a copy if subsequent submissions still fail. Please try submitting again or refresh this page to continue (answers will be saved locally in the browser unless you clear local storage).
${html.get(0).innerHTML}
${JSON.stringify(
- answer_json
- )}
`
- );
-
- $("#btn-submit-nav").prependTo("#submit-error-overlay");
-
- $("#submit-error-overlay").height(docHeight);
-
- let file = new Blob([JSON.stringify(answer_json)], {type: "application/json"})
-
- let link = document.getElementById("save-failed-answers");
- link.href = URL.createObjectURL(file);
- link.download = "answers.json";
+ let errorMsg = "An error occurred while submitting your answers.";
+ if (data && data.error) {
+ errorMsg = `Server returned error: ${data.error}`;
+ } else if (data && data.statusText) {
+ errorMsg = `Network error: ${data.statusText} (${data.status})`;
}
+
+ let $overlay = $(`
+
+
+
Submission Failed
+
${errorMsg}
+
+ Your answers are saved locally in the browser's database and will not be lost unless you clear your local data.
+ Please try to submit again, or save a backup file of your answers and contact support.
+
+
+
+
Submitted Answers:
+
+ ${$list.prop("outerHTML")}
+
+
+
+
+ View Raw Submission JSON
+
${rawJsonStr}
+
+
+
+
+
+
+
+
+
+ `);
+
+ $("body").append($overlay);
+
+ // Setup download backup link
+ let fileBlob = new Blob([JSON.stringify(answer_json)], {type: "application/json"});
+ let backupUrl = URL.createObjectURL(fileBlob);
+ $overlay.find("#btn-err-backup").attr("href", backupUrl).attr("download", `answers_backup_${exam_details.eid.replace(/\//g, "_")}.json`);
+
+ // Close handler
+ $overlay.find("#btn-err-close").on("click", function() {
+ $("#submit-error-overlay").remove();
+ });
+
+ // Retry handler
+ $overlay.find("#btn-err-retry").on("click", function() {
+ postAnswers(answer_json, URLS, exam_details);
+ });
}
export function getQuestion(url, question_number, question_total) {
diff --git a/js/main.js b/js/main.js
index 109d86d..7587521 100644
--- a/js/main.js
+++ b/js/main.js
@@ -7,8 +7,42 @@ import { db, question_db } from "./db.js";
import { initSync, triggerImmediateSync, updateSyncUI } from "./sync.js";
// const { v4: uuidv4 } = require('uuid');
-log.setDefaultLevel("warn")
-log.setDefaultLevel("debug")
+function initLogging() {
+ try {
+ const urlParams = new URLSearchParams(window.location.search);
+ let logLevel = urlParams.get("log");
+
+ if (logLevel !== null) {
+ if (logLevel === "1" || logLevel === "true") {
+ logLevel = "debug";
+ } else if (logLevel === "0" || logLevel === "false") {
+ logLevel = "silent";
+ }
+ localStorage.setItem("rts.logLevel", logLevel);
+ } else {
+ logLevel = localStorage.getItem("rts.logLevel");
+ }
+
+ if (!logLevel || !["silent", "error", "warn", "info", "debug"].includes(logLevel)) {
+ logLevel = "warn";
+ }
+
+ log.setLevel(logLevel);
+
+ // Redirect console methods through loglevel so they can be silenced consistently
+ console.debug = log.debug.bind(log);
+ console.log = log.info.bind(log);
+ console.warn = log.warn.bind(log);
+ console.error = log.error.bind(log);
+
+ log.info(`RTS logging level initialized to: ${logLevel}`);
+ } catch (e) {
+ log.setLevel("warn");
+ console.warn("Failed to initialize custom log level, falling back to warn:", e);
+ }
+}
+
+initLogging();
let URLS = {};
@@ -309,7 +343,7 @@ function showLoadingState($btn, $targetContainer) {
}
async function retrievePacketList(target = "all", onComplete = null) {
- log.debug(`Load users from database...`)
+ log.info(`Load users from database...`)
let cid = localStorage.getItem("cid.cid")
let passcode = localStorage.getItem("cid.passcode")
@@ -327,7 +361,7 @@ async function retrievePacketList(target = "all", onComplete = null) {
// Update the candidate details display (only shows when both values exist)
showCandidateDetails();
} else {
- log.debug("Try and set CID / Passcode from current URL")
+ log.info("Try and set CID / Passcode from current URL")
try {
const params = new URLSearchParams(window.location.search);
const cidParam = params.get("cid");
@@ -337,8 +371,8 @@ async function retrievePacketList(target = "all", onComplete = null) {
if (cidParam !== null && passParam !== null && cidParam !== "" && passParam !== "") {
cid = cidParam;
passcode = passParam;
- log.debug("CID and passcode extracted from URL", cid, passcode);
- log.debug("adding to local db");
+ log.info("CID and passcode extracted from URL", cid, passcode);
+ log.info("adding to local db");
localStorage.setItem("cid.cid", cid);
localStorage.setItem("cid.passcode", passcode);
@@ -348,10 +382,10 @@ async function retrievePacketList(target = "all", onComplete = null) {
showCandidateDetails();
} else {
- log.debug("CID or passcode missing in URL; not setting local storage");
+ log.info("CID or passcode missing in URL; not setting local storage");
}
} catch (e) {
- log.debug("Error parsing URL params for CID/passcode", e);
+ log.error("Error parsing URL params for CID/passcode", e);
}
}
@@ -385,7 +419,7 @@ async function retrievePacketList(target = "all", onComplete = null) {
}
},
error: function(jqXHR, textStatus, errorThrown) {
- log.debug("Unable to load packets, trying legacy '/packets'");
+ log.info("Unable to load packets, trying legacy '/packets'");
$.getJSON("packets", function(data) {
if (!data.hasOwnProperty("exams")) {
loadPacketList(data);
@@ -410,7 +444,7 @@ async function retrievePacketList(target = "all", onComplete = null) {
}
}
} catch (e) {
- log.debug("Unable to set exam query url", e);
+ log.warn("Unable to set exam query url", e);
}
if (examUrl) {
@@ -506,17 +540,17 @@ async function retrievePacketList(target = "all", onComplete = null) {
async function loadExamList(data, clearExistingList = true, target = "all") {
// If exam mode was enabled via URL param, hide packets by default
if (typeof url_exam_mode !== "undefined" && url_exam_mode) {
- log.debug("URL exam mode active - hiding packets list");
+ log.info("URL exam mode active - hiding packets list");
try {
$(".packet-wrapper").hide();
} catch (e) {
- log.debug("Unable to hide packet UI elements", e);
+ log.warn("Unable to hide packet UI elements", e);
}
}
- log.debug(`Load exam list: ${data}`)
- log.debug("Loading saved sessions")
+ log.info(`Load exam list: ${data}`)
+ log.info("Loading saved sessions")
let sessions = await db.session.toArray().catch(function(error) {
- log.debug("Error loading session", error);
+ log.error("Error loading session", error);
$("#database-error").text(
"Error loading the database, schema has probably changed and needs updating. You will probably need to delete the local database."
);
@@ -539,7 +573,7 @@ async function loadExamList(data, clearExistingList = true, target = "all") {
let exams_started = [];
let exams_completed = [];
- log.debug("Check status of saved exams")
+ log.info("Check status of saved exams")
for (const s of sessions) {
log.debug(`checking status of`, s)
if (s.status == "active") {
@@ -569,7 +603,7 @@ async function loadExamList(data, clearExistingList = true, target = "all") {
$("#cache-details ul").empty();
}
- log.debug("Loop available exams")
+ log.info("Loop available exams")
for (const exam of exam_list) {
log.debug(exam)
const name = exam["name"];
@@ -804,10 +838,10 @@ async function loadExamList(data, clearExistingList = true, target = "all") {
async function loadPacketList(data) {
// NOTE: currently the packet id is not available prior ot downloading the packet
// this severly limits what we can do with it (but to require would limit the utility)
- log.debug(`loadPacketList`);
- log.debug("Loading saved sessions")
+ log.info(`loadPacketList`);
+ log.info("Loading saved sessions")
let sessions = await db.session.toArray().catch(function(error) {
- log.debug(`Error loading session ${error}`);
+ log.error(`Error loading session ${error}`);
$("#database-error").text(
"Error loading the database, schema has probably changed and needs updating. You will probably need to delete the local database."
);
@@ -826,7 +860,7 @@ async function loadPacketList(data) {
// .equals()
let packets_started = [];
let packets_completed = [];
- log.debug("Check status of saved packets")
+ log.info("Check status of saved packets")
sessions.forEach((s) => {
log.debug(`checking status of`, s)
if (s.status == "active") {
@@ -836,16 +870,16 @@ async function loadPacketList(data) {
}
});
- log.debug("Packets started")
+ log.info("Packets started")
log.debug(packets_started)
- log.debug("Packets completed")
+ log.info("Packets completed")
log.debug(packets_completed)
let packet_list = [];
if (data != null) {
packet_list = data.packets;
} else {
- log.debug("data == null")
+ log.warn("data == null")
}
log.debug(packet_list)
@@ -1146,40 +1180,38 @@ function setUpPacket(data, path) {
}
function loadSession() {
- console.debug("load session", exam_details, db.session);
+ log.info("load session", exam_details, db.session);
// Either continue session or create a new one
db.session
- // .where("status")
- // .equals("active")
.where("[eid+aid]")
.between([exam_details.eid, Dexie.minKey], [exam_details.eid, Dexie.maxKey])
.toArray()
.then((sessions) => {
- //console.debug("sessions", sessions);
-
- // let active_sessions = [];
-
- // sessions.forEach((s) => {
- // if (s.status == "active") {
- // active_sessions.push(s);
- // }
- // });
-
- let load_previous = false;
-
- // Start new session if no session found
if (sessions.length < 1) {
exam_details.aid = uuidv4();
date_started = Date.now();
- } else {
- let text = "Select session to continue (leave blank to create new):";
- if (exam_details.exam_mode) {
- text = "Session will be continued";
+ exam_details.start_time = date_started / 1000;
+ setUpQuestions(false);
+ if (!review_mode) {
+ initSync(exam_details, URLS);
}
- //console.debug(sessions.date);
+ } else {
+ // Show resume dialog
+ $("#resume-sessions-list").empty();
+
+ let textPrompt = "Select a session to continue:";
+ if (exam_details.exam_mode) {
+ textPrompt = "An exam session is already in progress. You must continue the existing session:";
+ $("#btn-resume-start-new").hide();
+ } else {
+ $("#btn-resume-start-new").show();
+ }
+
+ $("#resume-text").text(textPrompt);
- for (let i = 0; i < sessions.length; i++) {
- let d = new Date(sessions[i].date);
+ // Populate session items
+ sessions.forEach((session, i) => {
+ let d = new Date(session.date);
let formatted_date =
d.getFullYear() +
"-" +
@@ -1193,58 +1225,83 @@ function loadSession() {
":" +
d.getSeconds();
- if (sessions[i].status == "active") {
- text = text + `\r${i}: ${formatted_date} [In progress]`;
+ let label = `${formatted_date}`;
+ if (session.status == "active") {
+ label += " [In progress]";
} else {
if (!exam_details.exam_mode) {
- text =
- text +
- `\r${i}: ${formatted_date} [Complete - score ${sessions[i].score}/${sessions[i].max_score}]`;
+ label += ` [Complete - score ${session.score}/${session.max_score}]`;
} else {
- text =
- text +
- `\r${i}: ${formatted_date} [Complete]`;
+ label += " [Complete]";
}
}
- }
- // If in exam mode we don't want to allow multiple sessions
- let s;
+ let $item = $(`
+