try to fix multiuser sync
This commit is contained in:
@@ -11,14 +11,19 @@
|
||||
<!-- Session Header -->
|
||||
<div class="row align-items-center mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="d-flex align-items-center flex-wrap gap-2">
|
||||
<a href="{% url 'atlas:multiuser_dashboard' %}" class="btn btn-outline-secondary btn-sm" title="Back to Dashboard">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</a>
|
||||
<h1 class="h4 mb-0 text-white">
|
||||
<i class="bi bi-people text-info me-2"></i>{{ session.name|default:"Unnamed Session" }}
|
||||
</h1>
|
||||
<span class="badge bg-secondary font-monospace small">{{ session.id|stringformat:".8s" }}</span>
|
||||
<span class="badge bg-secondary font-monospace small me-2">{{ session.id|stringformat:".8s" }}</span>
|
||||
<!-- Debug Mode Toggle -->
|
||||
<div class="form-check form-switch small text-light mb-0 d-flex align-items-center gap-2 ms-md-2">
|
||||
<input class="form-check-input" type="checkbox" id="syncDebugModeCheckbox" onchange="toggleSyncDebugMode(this.checked)">
|
||||
<label class="form-check-label text-muted small" for="syncDebugModeCheckbox"><i class="bi bi-bug-fill text-info me-1"></i>Debug Mode</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small text-muted mt-1">
|
||||
Managed by <strong>{{ session.creator.get_full_name|default:session.creator.username }}</strong>
|
||||
@@ -102,17 +107,109 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live Debug Logs Panel -->
|
||||
<div id="syncDebugLogsPanel" class="card bg-dark border-info text-white mt-4" style="display: none;">
|
||||
<div class="card-header bg-info bg-opacity-25 text-info d-flex justify-content-between align-items-center py-2 px-3">
|
||||
<span class="fw-semibold"><i class="bi bi-terminal me-2"></i>Live Multiuser Sync Logs</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button class="btn btn-outline-info btn-xs py-0 px-2 small" style="font-size: 0.75rem;" onclick="clearDebugLogs()">Clear</button>
|
||||
<span class="badge bg-info text-dark font-monospace small">DEBUG MODE ACTIVE</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="syncDebugLogsContainer" class="bg-black text-success font-monospace p-3 small" style="height: 180px; overflow-y: auto; line-height: 1.5; font-size: 0.85rem;">
|
||||
<div class="text-muted">Debug mode activated. Listening for WebSocket messages...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/javascript">
|
||||
function escapeHtml(text) {
|
||||
if (typeof text !== "string") {
|
||||
try {
|
||||
text = JSON.stringify(text);
|
||||
} catch (e) {
|
||||
text = String(text);
|
||||
}
|
||||
}
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function logDebug(message, data) {
|
||||
console.log("[Multiuser Session]", message, data || "");
|
||||
|
||||
// Append to on-screen console if visible
|
||||
const container = document.getElementById("syncDebugLogsContainer");
|
||||
if (container) {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const line = document.createElement("div");
|
||||
line.className = "mb-1 border-bottom border-secondary pb-1 border-opacity-25";
|
||||
line.innerHTML = `<span class="text-muted">[${timestamp}]</span> <strong>${escapeHtml(message)}</strong>`;
|
||||
if (data) {
|
||||
line.innerHTML += ` <span class="text-info">${escapeHtml(data)}</span>`;
|
||||
}
|
||||
container.appendChild(line);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function clearDebugLogs() {
|
||||
const container = document.getElementById("syncDebugLogsContainer");
|
||||
if (container) {
|
||||
container.innerHTML = '<div class="text-muted">Logs cleared.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSyncDebugMode(enabled) {
|
||||
localStorage.setItem("multiuser_sync_debug_mode", enabled ? "true" : "false");
|
||||
|
||||
const panel = document.getElementById("syncDebugLogsPanel");
|
||||
if (panel) {
|
||||
panel.style.display = enabled ? "block" : "none";
|
||||
}
|
||||
|
||||
const checkbox = document.getElementById("syncDebugModeCheckbox");
|
||||
if (checkbox && checkbox.checked !== enabled) {
|
||||
checkbox.checked = enabled;
|
||||
}
|
||||
|
||||
if (typeof window.setDicomViewerLogging === "function") {
|
||||
window.setDicomViewerLogging({ minLevel: enabled ? "debug" : "warn" });
|
||||
logDebug("DV3D viewer log level set to " + (enabled ? "debug" : "warn"));
|
||||
} else {
|
||||
logDebug("DV3D viewer logging control not ready yet (will apply when viewer mounts).");
|
||||
}
|
||||
}
|
||||
|
||||
// Set invite link input on load
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const inviteInput = document.getElementById("inviteLinkInput");
|
||||
if (inviteInput) {
|
||||
inviteInput.value = window.location.href;
|
||||
}
|
||||
|
||||
// Initialize Debug Mode from LocalStorage
|
||||
const debugSaved = localStorage.getItem("multiuser_sync_debug_mode") === "true";
|
||||
toggleSyncDebugMode(debugSaved);
|
||||
|
||||
// Keep attempting to apply log level as viewer mounts
|
||||
const checkInterval = setInterval(function() {
|
||||
if (typeof window.setDicomViewerLogging === "function") {
|
||||
const isDebug = localStorage.getItem("multiuser_sync_debug_mode") === "true";
|
||||
window.setDicomViewerLogging({ minLevel: isDebug ? "debug" : "warn" });
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
}, 200);
|
||||
setTimeout(function() { clearInterval(checkInterval); }, 15000);
|
||||
});
|
||||
|
||||
// Copy to clipboard helper
|
||||
@@ -147,47 +244,60 @@
|
||||
|
||||
// Session-level WebSocket to listen for control signals
|
||||
(function() {
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const host = window.location.host || "localhost:8080";
|
||||
const wsUrl = `${protocol}//${host}/ws/multiuser/{{ session.id }}/`;
|
||||
|
||||
console.log("Connecting session WS to", wsUrl);
|
||||
const socket = new WebSocket(wsUrl);
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("Session WS event:", data.type, data);
|
||||
|
||||
if (data.type === 'change_case') {
|
||||
console.log("Session host changed case to", data.case_id);
|
||||
// Fetch new case content via HTMX
|
||||
htmx.ajax('GET', "{% url 'atlas:multiuser_case_content_partial' session_id=session.id %}", {
|
||||
target: "#multiuser-case-container",
|
||||
swap: "innerHTML"
|
||||
});
|
||||
} else if (data.type === 'room_status') {
|
||||
// Update user count
|
||||
const userCount = Object.keys(data.users || {}).length;
|
||||
const mgrCountBadge = document.getElementById("connectedUsersCount");
|
||||
const partCountBadge = document.getElementById("connectedUsersCountParticipant");
|
||||
let socket = null;
|
||||
|
||||
function connect() {
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const host = window.location.host || "localhost:8080";
|
||||
const wsUrl = `${protocol}//${host}/ws/multiuser/{{ session.id }}/?client_type=session`;
|
||||
|
||||
logDebug("Connecting session WS to", wsUrl);
|
||||
socket = new WebSocket(wsUrl);
|
||||
|
||||
socket.onopen = function() {
|
||||
logDebug("Session WS connected successfully");
|
||||
};
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
logDebug("Session WS event received (type=" + data.type + ")", data);
|
||||
|
||||
if (mgrCountBadge) mgrCountBadge.textContent = userCount;
|
||||
if (partCountBadge) partCountBadge.textContent = userCount;
|
||||
if (data.type === 'change_case') {
|
||||
logDebug("Session host changed case to " + data.case_id + ". Fetching new case via HTMX...");
|
||||
// Fetch new case content via HTMX
|
||||
htmx.ajax('GET', "{% url 'atlas:multiuser_case_content_partial' session_id=session.id %}", {
|
||||
target: "#multiuser-case-container",
|
||||
swap: "innerHTML"
|
||||
});
|
||||
} else if (data.type === 'room_status') {
|
||||
// Update user count
|
||||
const userCount = Object.keys(data.users || {}).length;
|
||||
logDebug("Room status update. Active users: " + userCount, data.users);
|
||||
const mgrCountBadge = document.getElementById("connectedUsersCount");
|
||||
const partCountBadge = document.getElementById("connectedUsersCountParticipant");
|
||||
|
||||
if (mgrCountBadge) mgrCountBadge.textContent = userCount;
|
||||
if (partCountBadge) partCountBadge.textContent = userCount;
|
||||
}
|
||||
} catch (err) {
|
||||
logDebug("Error processing session WS message: " + err.message);
|
||||
console.error("Error processing session WS message", err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error processing session WS message", err);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = function(e) {
|
||||
console.warn("Session WS closed. Reconnecting in 3s...", e);
|
||||
setTimeout(arguments.callee, 3000);
|
||||
};
|
||||
|
||||
socket.onerror = function(err) {
|
||||
console.error("Session WS error:", err);
|
||||
};
|
||||
};
|
||||
|
||||
socket.onclose = function(e) {
|
||||
logDebug("Session WS closed (code=" + e.code + "). Reconnecting in 3s...");
|
||||
setTimeout(connect, 3000);
|
||||
};
|
||||
|
||||
socket.onerror = function(err) {
|
||||
logDebug("Session WS error encountered");
|
||||
console.error("Session WS error:", err);
|
||||
};
|
||||
}
|
||||
|
||||
connect();
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user