collabcentral/html/monitorJobs.js
Joseph B. McQueen e604c7e9c9 Initial commit: multi-bot CollabCentral
Extends the single-Novi codebase into a multi-bot mass-messenger where
each bot has its own token, avatar, label, and per-user authorization.

- Secrets moved out of config.json: per-bot tokens in gitignored
  config/botTokens.json (with enabled flag), service-account OAuth in
  gitignored config/token.json (rewritten by refresh cron), integration
  and Google keys in .env.
- Single Webex integration handles OAuth for all bots via a per-app
  redirect URI derived from OAUTH_CALLBACK_URL_TEMPLATE.
- New requireBot middleware and getBotConfig helper reject requests for
  unknown or disabled bots at the /CollabCentral/:app boundary.
- New /info endpoint plus dynamic frontend loading (sendMessage,
  monitorJobs) so pages self-describe per bot, including bot avatar
  fetched from Webex /people/me at startup.
- Job draft state keyed by cookieId + appName so each bot has its own
  building queue; job list/detail endpoints filter by appName so users
  only see jobs from bots they are authorized on.
- New jobDetail page for a readable per-job view; completed jobs are
  retained for 30 days by the cleanup cron.
- Completion adaptive cards use per-bot avatar and label.
- Miscellaneous fixes: off-by-two in the send loop, removed three dead
  send/process variants, added defensive init for jobs.* on load,
  dropped the deprecated crypto npm shim, and cleaned up stray logger
  labels and typos.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 17:53:07 -04:00

132 lines
4.4 KiB
JavaScript

var pathArray = window.location.pathname.split('/');
var appName = pathArray[2];
if (getCookie("id")) {
console.log("Found ID Cookie: " + getCookie("id"));
// Fetch bot metadata + authorization state, then render the page.
fetch('/CollabCentral/' + appName + '/info')
.then(res => res.json())
.then(info => {
document.title = info.label;
document.getElementById("appLabel").innerHTML = info.label;
document.getElementById("name").innerHTML = getCookie("displayName") || "";
document.getElementById("appName").href = info.faviconUrl;
document.getElementById("jobLink").href = "/CollabCentral/" + appName + "/sendMessage.html";
var appIcon = document.getElementById("appIcon");
appIcon.src = info.iconUrl;
appIcon.style.maxHeight = "150px";
appIcon.style.maxWidth = "150px";
if (!info.authorized) {
document.getElementById("unauthorizedPanel").classList.remove("hidden");
return;
}
document.getElementById("jobsPanel").classList.remove("hidden");
initJobTables();
})
.catch(err => console.error("Failed to load /info:", err));
} else {
console.log("No ID Cookie found.");
var randomNumber = Math.random().toString();
randomNumber = randomNumber.substring(2, randomNumber.length);
fetch('/CollabCentral/' + appName + '/authUrl')
.then(res => res.text())
.then((res) => {
window.location.href = res + randomNumber;
});
}
function initJobTables() {
$('#runningJobs').dataTable({
ajax: {
url: '/CollabCentral/' + appName + '/jobs/list/running',
dataSrc: ''
},
columns: [
{ data: 'senderDisplayName' },
{ data: 'appName' },
{ data: 'startTime' },
{ data: 'totalRecipients' },
{
data: 'completedRecipients',
render: function (val, type, row) {
if (type !== 'display' || row.jobId === undefined || row.jobId === null) return val;
return val + ' <a href="/CollabCentral/' + appName + '/jobDetail.html?jobId=' + encodeURIComponent(row.jobId) + '" style="margin-left: 0.5em; font-size: 0.85em;">view</a>';
}
}
],
searching: false,
info: false,
ordering: false,
paging: false
});
$('#scheduledJobs').dataTable({
ajax: {
url: '/CollabCentral/' + appName + '/jobs/list/scheduled',
dataSrc: ''
},
columns: [
{ data: 'senderDisplayName' },
{ data: 'appName' },
{ data: 'message' },
{ data: 'totalRecipients' },
{ data: 'scheduledFor' }
],
searching: false,
info: false,
ordering: false,
paging: false
});
$('#completedJobs').dataTable({
ajax: {
url: '/CollabCentral/' + appName + '/jobs/list/completed',
dataSrc: ''
},
columns: [
{
data: 'jobId',
render: function (jobId) {
if (jobId === undefined || jobId === null) return '';
return '<a href="/CollabCentral/' + appName + '/jobDetail.html?jobId=' + encodeURIComponent(jobId) + '">#' + jobId + '</a>';
}
},
{ data: 'senderDisplayName' },
{ data: 'appName' },
{ data: 'message.english.html' },
{ data: 'startTime' },
{ data: 'endTime' },
{ data: 'stats.totalTime' },
{ data: 'stats.webexTime' },
{ data: 'stats.averageTime' },
{ data: 'stats.succeededMsgs' },
{ data: 'stats.totalMsgs' },
{ data: 'stats.succeededMsgPct' }
],
searching: false,
info: false,
ordering: false,
paging: false
});
}
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}