Wire up events for creating new public reports

This involved moving a lot of stuff to the platform layer since I don't
want event interfaces leaking out.

Also this includes a fix to the user authentication which I had
previously broken by making a platform-layer user object independent of
the database layer.
This commit is contained in:
Eli Ribble 2026-03-13 17:33:39 +00:00
parent 9a5cc4cf97
commit e8d865d0ab
No known key found for this signature in database
24 changed files with 915 additions and 541 deletions

120
html/static/js/events.js Normal file
View file

@ -0,0 +1,120 @@
// sse-manager.js - Include this in your common template
window.SSEManager = (function () {
let eventSource = null;
let subscribers = new Map();
let isConnected = false;
let connectionPromise = null;
function subscribe(eventType, handler) {
if (!subscribers.has(eventType)) {
subscribers.set(eventType, []);
}
subscribers.get(eventType).push(handler);
// If already connected, attach the listener immediately
if (isConnected && eventSource) {
eventSource.addEventListener(eventType, handler);
}
}
function unsubscribe(eventType, handler) {
if (subscribers.has(eventType)) {
const handlers = subscribers.get(eventType);
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
if (eventSource) {
eventSource.removeEventListener(eventType, handler);
}
}
function connect(url) {
if (connectionPromise) {
return connectionPromise;
}
connectionPromise = new Promise((resolve, reject) => {
eventSource = new EventSource(url);
eventSource.onopen = function () {
isConnected = true;
// Attach all pre-registered handlers
subscribers.forEach((handlers, eventType) => {
handlers.forEach((handler) => {
eventSource.addEventListener("message", (message) => {
const data = JSON.parse(message.data);
handler(data);
});
});
});
console.log("SSE connected");
resolve(eventSource);
};
eventSource.onerror = function (err) {
console.error("SSE error:", err);
isConnected = false;
// Reconnect after delay
setTimeout(() => {
connectionPromise = null;
connect(url);
}, 5000);
if (!isConnected) {
reject(err);
}
};
});
return connectionPromise;
}
function disconnect() {
if (eventSource) {
eventSource.close();
eventSource = null;
isConnected = false;
connectionPromise = null;
}
}
function ready(callback) {
if (connectionPromise) {
connectionPromise.then(callback);
} else {
// If connect hasn't been called yet, queue it
const checkInterval = setInterval(() => {
if (connectionPromise) {
clearInterval(checkInterval);
connectionPromise.then(callback);
}
}, 50);
}
}
return {
connect,
disconnect,
subscribe,
unsubscribe,
ready,
};
})();
// Initialize SSE for navigation notifications
document.addEventListener("DOMContentLoaded", function () {
SSEManager.connect("/api/events");
});
function updateNotificationBadge(data) {
const badge = document.querySelector(".notification-badge");
if (badge) {
badge.textContent = data.count;
badge.style.display = data.count > 0 ? "block" : "none";
}
}

View file

@ -10,8 +10,12 @@
<link rel="stylesheet" href="/static/vendor/css/bootstrap-icons.min.css" />
<!-- favicon -->
<link rel="icon" href="/static/favicon-sync.ico" type="image/x-icon" />
<script src="/static/js/events.js"></script>
{{ block "extraheader" . }}{{ end }}
<script>
SSEManager.subscribe("*", function (e) {
console.log("event", e);
});
function restoreLocalStorage() {
const expanded = localStorage.getItem("sidebar.expanded");
if (expanded == "false") {
@ -60,7 +64,6 @@
setTooltipsForSidebar();
});
</script>
<script src="/static/js/events.js"></script>
{{ if not .Config.IsProductionEnvironment }}
<script src="/.flogo/injector.js"></script>
{{ end }}

View file

@ -196,6 +196,50 @@
</div>
</div>
<!-- SSE Testing -->
<div class="card mb-5">
<div class="card-header bg-danger text-white">
<i class="bi bi-bell"></i> Server-sent event testing
</div>
<div class="card-body">
<form action="/sudo/sse" method="POST">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="organizationID" class="form-label"
>Organization ID</label
>
<input
class="form-control"
id="organization-id"
name="organizationID"
placeholder="Organization ID"
type="text"
value="{{ .Organization.ID }}"
/>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<input
class="form-control"
id="content"
name="content"
placeholder="Message content"
type="text"
/>
</div>
</div>
</div>
<button type="submit" class="btn btn-danger">
Send SSE<i class="bi bi-send"></i>
</button>
</form>
</div>
</div>
<hr class="my-5" />
<!-- Push Notification Testing -->
<div class="card mb-5">
<div class="card-header bg-danger text-white">