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>
222 lines
No EOL
7.3 KiB
JavaScript
222 lines
No EOL
7.3 KiB
JavaScript
const listbox = document.querySelector('#list');
|
|
const modal = document.querySelector(".modal");
|
|
const overlay = document.querySelector(".overlay");
|
|
const openModalBtn = document.querySelector(".btn-open");
|
|
const closeModalBtn = document.querySelector(".btn-close");
|
|
|
|
var pathArray = window.location.pathname.split('/'); //Gets the path of the URL called
|
|
var appName = pathArray[2]; //Gets the appName from the path.
|
|
|
|
document.getElementById('sendMessage').onsubmit = function (event) {
|
|
|
|
event.preventDefault() // prevent form from posting without JS
|
|
var xhttp = new XMLHttpRequest(); // create new AJAX request
|
|
|
|
|
|
xhttp.onreadystatechange = function () {
|
|
if (this.readyState == this.DONE && this.status == 200) { // sucess from server
|
|
result = JSON.parse(xhttp.response);
|
|
console.log(xhttp.responseText)
|
|
console.log(xhttp.response);
|
|
var message = "I sent you a test message in your Webex Client for review.<br><br>";
|
|
message += "Recipients: " + result.memberList.length + "<br><br>";
|
|
if (document.getElementById("scheduledFor").value) {
|
|
message += "Scheduled for " + document.getElementById("scheduledFor").value;
|
|
} else {
|
|
message += "Message will be sent immediately.";
|
|
}
|
|
|
|
|
|
document.getElementById("modalMessage").innerHTML = message;
|
|
openModal();
|
|
} else { // errors occured
|
|
|
|
}
|
|
}
|
|
|
|
var formData = new FormData()
|
|
formData.append('uploadImage', document.getElementById('uploadImage').files[0]) // since inputs allow multi files submission, therefore files are in array
|
|
formData.append('message', message.value())
|
|
formData.append('uploadCSV', document.getElementById('uploadCSV').files[0]) // since inputs allow multi files submission, therefore files are in array
|
|
formData.append('appName', appName);
|
|
|
|
var selectedGroups = [];
|
|
for (var group of document.querySelector('#favGroups').getSelectedOptions()) {
|
|
selectedGroups.push(group.value);
|
|
}
|
|
for (var group of document.querySelector('#newGroups').getSelectedOptions()) {
|
|
selectedGroups.push(group.value);
|
|
}
|
|
|
|
formData.append('groups', selectedGroups);
|
|
formData.append('scheduledFor', document.getElementById("scheduledFor").value);
|
|
xhttp.open("POST", "/CollabCentral/" + appName + "/jobs/edit")
|
|
console.log(xhttp);
|
|
xhttp.send(formData)
|
|
}
|
|
|
|
function showImagePreview(event) {
|
|
if (event.target.files.length > 0) {
|
|
var src = URL.createObjectURL(event.target.files[0]);
|
|
var preview = document.getElementById("file-ip-1-preview");
|
|
preview.src = src;
|
|
preview.style.display = "block";
|
|
preview.style.maxHeight = "350px";
|
|
preview.style.maxWidth = "500px";
|
|
}
|
|
}
|
|
|
|
|
|
let id = getCookie("id");
|
|
|
|
// Fetch bot metadata + authorization state, then either render the form or
|
|
// show the "not authorized" panel. All bot-specific labels and icons come from
|
|
// /info so adding a new bot doesn't require any front-end changes.
|
|
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 + "/monitorJobs.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("formPanel").classList.remove("hidden");
|
|
loadFavoriteGroups();
|
|
loadNewGroups();
|
|
})
|
|
.catch(err => {
|
|
console.error("Failed to load /info:", err);
|
|
});
|
|
|
|
function loadFavoriteGroups() {
|
|
var x = document.getElementById("favGroups");
|
|
fetch('/CollabCentral/' + appName + '/user/groups/list')
|
|
.then(res => res.json())
|
|
.then((res) => {
|
|
for (var group of res) {
|
|
var option = document.createElement("option");
|
|
option.text = group.name;
|
|
option.value = group.id;
|
|
x.add(option);
|
|
}
|
|
VirtualSelect.init({ ele: '#favGroups' });
|
|
});
|
|
}
|
|
|
|
function loadNewGroups() {
|
|
var y = document.getElementById("newGroups");
|
|
fetch('/CollabCentral/' + appName + '/user/groups/find')
|
|
.then(res => res.json())
|
|
.then((res) => {
|
|
for (var group of res) {
|
|
var option = document.createElement("option");
|
|
option.text = group.displayName;
|
|
option.value = group.id;
|
|
y.add(option);
|
|
}
|
|
VirtualSelect.init({ ele: '#newGroups' });
|
|
});
|
|
}
|
|
|
|
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 "";
|
|
}
|
|
|
|
const message = new EasyMDE({
|
|
element: document.getElementById('my-text-area'),
|
|
toolbar: ["bold", "italic", "|", "heading-1", "heading-2", "heading-3", "|", "code", "unordered-list", "ordered-list", "link", "quote", "|", "preview"],
|
|
maxHeight: "200px"
|
|
});
|
|
|
|
|
|
|
|
function submit() {
|
|
if (document.getElementById("scheduledFor").value) {
|
|
fetch("/CollabCentral/" + appName + "/jobs/schedule", {
|
|
method: "POST",
|
|
redirect: "follow"
|
|
})
|
|
.then(response => {
|
|
clearForm();
|
|
// HTTP 301 response
|
|
if (response.redirected) {
|
|
window.location.href = response.url;
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
console.info(err + " url: " + url);
|
|
});
|
|
|
|
} else {
|
|
console.log("RunNow selected.");
|
|
fetch("/CollabCentral/" + appName + "/jobs/runNow", {
|
|
method: "POST",
|
|
redirect: "follow"
|
|
})
|
|
.then(response => {
|
|
clearForm();
|
|
// HTTP 301 response
|
|
if (response.redirected) {
|
|
window.location.href = response.url;
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
console.info(err);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
closeModal();
|
|
}
|
|
|
|
function continueEditing() {
|
|
closeModal();
|
|
}
|
|
|
|
const openModal = function () {
|
|
modal.classList.remove("hidden");
|
|
overlay.classList.remove("hidden");
|
|
};
|
|
|
|
const closeModal = function () {
|
|
modal.classList.add("hidden");
|
|
overlay.classList.add("hidden");
|
|
};
|
|
|
|
overlay.addEventListener("click", closeModal);
|
|
|
|
function clearForm() {
|
|
document.getElementById('uploadImage').innerHTML = "";
|
|
message.value("");
|
|
document.getElementById('uploadCSV').innerHTML = "";
|
|
document.getElementById('favGroups').selected = null;
|
|
document.getElementById('newGroups').selected = null;
|
|
document.getElementById('scheduledFor').innerHTML = "";
|
|
}
|
|
|
|
|