680 lines
26 KiB
HTML
680 lines
26 KiB
HTML
{% extends "layout/base.html" %}
|
|
|
|
<style>
|
|
/* Inbox popup: wider + internal scroll areas */
|
|
.modal-xxl { max-width: 98vw; }
|
|
@media (min-width: 1400px) { .modal-xxl { max-width: 1400px; } }
|
|
|
|
#msg_body_container_iframe { height: 55vh; }
|
|
#msg_objects_container { max-height: 25vh; overflow: auto; }
|
|
</style>
|
|
|
|
{# Pager macro must be defined before it is used #}
|
|
{% macro pager(position, page, total_pages, has_prev, has_next) -%}
|
|
<div class="d-flex justify-content-between align-items-center my-2">
|
|
<div>
|
|
{% if has_prev %}
|
|
<a class="btn btn-outline-secondary btn-sm" href="{{ url_for('main.inbox', page=page-1) }}">Previous</a>
|
|
{% else %}
|
|
<button class="btn btn-outline-secondary btn-sm" disabled>Previous</button>
|
|
{% endif %}
|
|
{% if has_next %}
|
|
<a class="btn btn-outline-secondary btn-sm ms-2" href="{{ url_for('main.inbox', page=page+1) }}">Next</a>
|
|
{% else %}
|
|
<button class="btn btn-outline-secondary btn-sm ms-2" disabled>Next</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if current_user.is_authenticated and active_role in ["admin", "operator"] %}
|
|
<form method="POST" action="{{ url_for('main.inbox_reparse_all') }}" class="me-3 mb-0">
|
|
<button type="submit" class="btn btn-outline-secondary btn-sm">Re-parse all</button>
|
|
</form>
|
|
{% endif %}
|
|
|
|
<div class="d-flex align-items-center">
|
|
<span class="me-2">Page {{ page }} of {{ total_pages }}</span>
|
|
<form method="get" class="d-flex align-items-center mb-0">
|
|
<label for="page_{{ position }}" class="form-label me-1 mb-0">Go to:</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
max="{{ total_pages }}"
|
|
class="form-control form-control-sm me-1"
|
|
id="page_{{ position }}"
|
|
name="page"
|
|
value="{{ page }}"
|
|
style="width: 5rem;"
|
|
/>
|
|
<button type="submit" class="btn btn-primary btn-sm">Go</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{%- endmacro %}
|
|
|
|
{% block content %}
|
|
<h2 class="mb-3">Inbox</h2>
|
|
|
|
{{ pager("top", page, total_pages, has_prev, has_next) }}
|
|
|
|
{% if can_bulk_delete %}
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-sm btn-outline-danger" id="btn_inbox_delete_selected" disabled>Delete selected</button>
|
|
</div>
|
|
</div>
|
|
<div class="small text-muted mb-2" id="inbox_status"></div>
|
|
{% endif %}
|
|
|
|
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-hover align-middle" id="inboxTable">
|
|
<thead class="table-light">
|
|
<tr>
|
|
{% if can_bulk_delete %}
|
|
<th scope="col" style="width: 34px;">
|
|
<input class="form-check-input" type="checkbox" id="inbox_select_all" />
|
|
</th>
|
|
{% endif %}
|
|
<th scope="col">From</th>
|
|
<th scope="col">Subject</th>
|
|
<th scope="col">Date / time</th>
|
|
<th scope="col">Backup</th>
|
|
<th scope="col">Type</th>
|
|
<th scope="col">Job name</th>
|
|
<th scope="col">Overall</th>
|
|
<th scope="col">Parsed</th>
|
|
<th scope="col">EML</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if rows %}
|
|
{% for row in rows %}
|
|
<tr class="inbox-row" data-message-id="{{ row.id }}" style="cursor: pointer;">
|
|
{% if can_bulk_delete %}
|
|
<td onclick="event.stopPropagation();">
|
|
<input class="form-check-input inbox_row_cb" type="checkbox" value="{{ row.id }}" />
|
|
</td>
|
|
{% endif %}
|
|
<td>{{ row.from_address }}</td>
|
|
<td>{{ row.subject }}</td>
|
|
<td>{{ row.received_at }}</td>
|
|
<td>{{ row.backup_software }}</td>
|
|
<td>{{ row.backup_type }}</td>
|
|
<td>{{ row.job_name }}</td>
|
|
<td>{{ row.overall_status }}</td>
|
|
<td>{{ row.parsed_at }}</td>
|
|
<td>
|
|
{% if row.has_eml %}
|
|
<a class="eml-download" href="{{ url_for('main.inbox_message_eml', message_id=row.id) }}">EML</a>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="10" class="text-center text-muted py-3">
|
|
No messages found.
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{ pager("bottom", page, total_pages, has_prev, has_next) }}
|
|
|
|
<!-- Inline popup modal for message details -->
|
|
<div class="modal fade" id="inboxMessageModal" tabindex="-1" aria-labelledby="inboxMessageModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-dialog-scrollable modal-xxl">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="inboxMessageModalLabel">Message details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<dl class="row mb-0 dl-compact">
|
|
<dt class="col-4">From</dt>
|
|
<dd class="col-8 ellipsis-field" id="msg_from"></dd>
|
|
|
|
<dt class="col-4">Backup</dt>
|
|
<dd class="col-8 ellipsis-field" id="msg_backup"></dd>
|
|
|
|
<dt class="col-4">Type</dt>
|
|
<dd class="col-8 ellipsis-field" id="msg_type"></dd>
|
|
|
|
<dt class="col-4">Job</dt>
|
|
<dd class="col-8 ellipsis-field" id="msg_job"></dd>
|
|
|
|
<dt class="col-4">Overall</dt>
|
|
<dd class="col-8 ellipsis-field" id="msg_overall"></dd>
|
|
|
|
<dt class="col-4">Customer</dt>
|
|
<dd class="col-8">
|
|
{% if current_user.is_authenticated and active_role in ["admin", "operator"] %}
|
|
<input id="msg_customer_input" class="form-control form-control-sm" list="customerList" placeholder="Select customer" autocomplete="off" />
|
|
<datalist id="customerList">
|
|
{% for c in customers %}
|
|
<option value="{{ c.name }}"></option>
|
|
{% endfor %}
|
|
</datalist>
|
|
{% else %}
|
|
<span id="msg_customer_display" class="ellipsis-field"></span>
|
|
{% endif %}
|
|
</dd>
|
|
|
|
<dt class="col-4">Received</dt>
|
|
<dd class="col-8 ellipsis-field" id="msg_received"></dd>
|
|
|
|
<dt class="col-4">Parsed</dt>
|
|
<dd class="col-8 ellipsis-field" id="msg_parsed"></dd>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="col-md-9">
|
|
<div class="mb-2">
|
|
<h6 class="mb-1">Details</h6>
|
|
<div id="msg_overall_message" class="border rounded p-2" style="white-space: pre-wrap; max-height: 20vh; overflow: auto;"></div>
|
|
</div>
|
|
<div class="border rounded p-2 p-0" style="overflow:hidden;">
|
|
<iframe id="msg_body_container_iframe" class="w-100" style="height:55vh; border:0; background:transparent;" sandbox="allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<div id="msg_objects_container">
|
|
<!-- Parsed objects will be rendered here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
{% if current_user.is_authenticated and active_role in ["admin", "operator"] %}
|
|
<form id="inboxApproveForm" method="POST" action="" class="me-auto mb-0">
|
|
<input type="hidden" id="msg_customer_id" name="customer_id" value="" />
|
|
<button type="submit" class="btn btn-primary" id="inboxApproveBtn">Approve job</button>
|
|
<button type="button" class="btn btn-outline-primary ms-2 d-none" id="vspcMapCompaniesBtn">Map companies</button>
|
|
</form>
|
|
<form id="inboxDeleteForm" method="POST" action="" class="mb-0">
|
|
<button type="submit" class="btn btn-outline-danger" onclick="return confirm('Delete this message from the Inbox?');">Delete</button>
|
|
</form>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- VSPC company mapping modal (for multi-company summary emails) -->
|
|
<div class="modal fade" id="vspcCompanyMapModal" tabindex="-1" aria-labelledby="vspcCompanyMapModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="vspcCompanyMapModalLabel">Map companies to customers</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form id="vspcCompanyMapForm" method="POST" action="">
|
|
<div class="modal-body">
|
|
<p class="mb-2">This message contains multiple companies. Map each company to a customer to approve.</p>
|
|
|
|
<datalist id="vspcCustomerList">
|
|
{% for c in customers %}
|
|
<option value="{{ c.name }}"></option>
|
|
{% endfor %}
|
|
</datalist>
|
|
|
|
<div class="table-responsive" style="max-height:55vh; overflow-y:auto;">
|
|
<table class="table table-sm align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 40%;">Company</th>
|
|
<th style="width: 60%;">Customer</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="vspcCompanyMapTbody">
|
|
<!-- rows injected by JS -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<input type="hidden" id="vspc_company_mappings_json" name="company_mappings_json" value="" />
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Approve mapped companies</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var customers = {{ customers|tojson|safe }};
|
|
|
|
var table = document.getElementById('inboxTable');
|
|
var selectAll = document.getElementById('inbox_select_all');
|
|
var btnDeleteSelected = document.getElementById('btn_inbox_delete_selected');
|
|
var statusEl = document.getElementById('inbox_status');
|
|
|
|
function getSelectedMessageIds() {
|
|
if (!table) return [];
|
|
var cbs = table.querySelectorAll('tbody .inbox_row_cb');
|
|
var ids = [];
|
|
cbs.forEach(function (cb) {
|
|
if (cb.checked) ids.push(parseInt(cb.value, 10));
|
|
});
|
|
return ids.filter(function (x) { return Number.isFinite(x); });
|
|
}
|
|
|
|
function refreshRowHighlights() {
|
|
if (!table) return;
|
|
var cbs = table.querySelectorAll('tbody .inbox_row_cb');
|
|
cbs.forEach(function (cb) {
|
|
var tr = cb.closest ? cb.closest('tr') : null;
|
|
if (!tr) return;
|
|
if (cb.checked) tr.classList.add('table-active');
|
|
else tr.classList.remove('table-active');
|
|
});
|
|
}
|
|
|
|
function refreshSelectAll() {
|
|
if (!selectAll || !table) return;
|
|
var cbs = table.querySelectorAll('tbody .inbox_row_cb');
|
|
var total = cbs.length;
|
|
var checked = 0;
|
|
cbs.forEach(function (cb) { if (cb.checked) checked++; });
|
|
selectAll.indeterminate = checked > 0 && checked < total;
|
|
selectAll.checked = total > 0 && checked === total;
|
|
}
|
|
|
|
function updateBulkDeleteUi() {
|
|
var ids = getSelectedMessageIds();
|
|
refreshRowHighlights();
|
|
if (btnDeleteSelected) btnDeleteSelected.disabled = ids.length === 0;
|
|
if (statusEl) statusEl.textContent = ids.length ? (ids.length + ' selected') : '';
|
|
refreshSelectAll();
|
|
}
|
|
|
|
function postJson(url, payload) {
|
|
return fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify(payload || {})
|
|
}).then(function (r) {
|
|
return r.json().then(function (data) { return { ok: r.ok, status: r.status, data: data }; });
|
|
});
|
|
}
|
|
|
|
if (selectAll && table) {
|
|
function setAllSelection(checked) {
|
|
var cbs = table.querySelectorAll('tbody .inbox_row_cb');
|
|
cbs.forEach(function (cb) { cb.checked = !!checked; });
|
|
selectAll.indeterminate = false;
|
|
selectAll.checked = !!checked;
|
|
setTimeout(function () {
|
|
selectAll.indeterminate = false;
|
|
selectAll.checked = !!checked;
|
|
}, 0);
|
|
updateBulkDeleteUi();
|
|
}
|
|
|
|
selectAll.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
selectAll.addEventListener('change', function () {
|
|
setAllSelection(selectAll.checked);
|
|
});
|
|
}
|
|
|
|
if (table) {
|
|
table.addEventListener('change', function (e) {
|
|
var t = e.target;
|
|
if (t && t.classList && t.classList.contains('inbox_row_cb')) {
|
|
updateBulkDeleteUi();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (btnDeleteSelected) {
|
|
btnDeleteSelected.addEventListener('click', function () {
|
|
var ids = getSelectedMessageIds();
|
|
if (!ids.length) return;
|
|
|
|
var msg = 'Delete ' + ids.length + ' selected message' + (ids.length === 1 ? '' : 's') + ' from the Inbox?';
|
|
if (!confirm(msg)) return;
|
|
|
|
if (statusEl) statusEl.textContent = 'Deleting...';
|
|
|
|
postJson('{{ url_for('main.api_inbox_bulk_delete') }}', { message_ids: ids })
|
|
.then(function (res) {
|
|
if (!res.ok || !res.data || res.data.status !== 'ok') {
|
|
var err = (res.data && (res.data.message || res.data.error)) ? (res.data.message || res.data.error) : 'Request failed.';
|
|
if (statusEl) statusEl.textContent = err;
|
|
alert(err);
|
|
return;
|
|
}
|
|
window.location.reload();
|
|
})
|
|
.catch(function () {
|
|
var err = 'Request failed.';
|
|
if (statusEl) statusEl.textContent = err;
|
|
alert(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Initialize UI state
|
|
updateBulkDeleteUi();
|
|
|
|
|
|
|
|
|
|
|
|
function wrapMailHtml(html) {
|
|
html = html || "";
|
|
var trimmed = (typeof html === "string") ? html.trim() : "";
|
|
|
|
// If the content already looks like a full HTML document (common for report attachments),
|
|
// do not wrap it again.
|
|
if (trimmed.toLowerCase().indexOf("<!doctype") === 0 || trimmed.toLowerCase().indexOf("<html") === 0) {
|
|
return trimmed;
|
|
}
|
|
|
|
// Ensure we render the mail HTML with its own CSS, isolated from the site styling.
|
|
return (
|
|
"<!doctype html><html><head><meta charset=\"utf-8\">" +
|
|
"<base target=\"_blank\">" +
|
|
"</head><body style=\"margin:0; padding:8px;\">" +
|
|
html +
|
|
"</body></html>"
|
|
);
|
|
}
|
|
function findCustomerIdByName(name) {
|
|
if (!name) return null;
|
|
for (var i = 0; i < customers.length; i++) {
|
|
if (customers[i].name === name) return customers[i].id;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function renderObjects(objects) {
|
|
var container = document.getElementById("msg_objects_container");
|
|
if (!container) return;
|
|
|
|
if (!objects || !objects.length) {
|
|
container.innerHTML = "<p class=\"text-muted mb-0\">No objects parsed for this message.</p>";
|
|
return;
|
|
}
|
|
|
|
function objectSeverityRank(o) {
|
|
var st = String((o && o.status) || "").trim().toLowerCase();
|
|
var err = String((o && o.error_message) || "").trim();
|
|
if (st === "error" || st === "failed" || st === "failure" || err) return 0;
|
|
if (st === "warning") return 1;
|
|
return 2;
|
|
}
|
|
|
|
function sortObjects(list) {
|
|
return (list || []).slice().sort(function (a, b) {
|
|
var ra = objectSeverityRank(a);
|
|
var rb = objectSeverityRank(b);
|
|
if (ra !== rb) return ra - rb;
|
|
|
|
var na = String((a && a.name) || "").toLowerCase();
|
|
var nb = String((b && b.name) || "").toLowerCase();
|
|
if (na < nb) return -1;
|
|
if (na > nb) return 1;
|
|
|
|
var ta = String((a && a.type) || "").toLowerCase();
|
|
var tb = String((b && b.type) || "").toLowerCase();
|
|
if (ta < tb) return -1;
|
|
if (ta > tb) return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
var sorted = sortObjects(objects);
|
|
|
|
var html = "<div class=\"table-responsive\"><table class=\"table table-sm table-bordered mb-0\">";
|
|
html += "<thead><tr><th>Object</th><th>Type</th><th>Status</th><th>Error</th></tr></thead><tbody>";
|
|
for (var i = 0; i < sorted.length; i++) {
|
|
var o = sorted[i] || {};
|
|
html += "<tr>";
|
|
html += "<td>" + (o.name || "") + "</td>";
|
|
html += "<td>" + (o.type || "") + "</td>";
|
|
html += "<td>" + (o.status || "") + "</td>";
|
|
html += "<td>" + (o.error_message || "") + "</td>";
|
|
html += "</tr>";
|
|
}
|
|
html += "</tbody></table></div>";
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function attachHandlers() {
|
|
var emlLinks = document.querySelectorAll("a.eml-download");
|
|
emlLinks.forEach(function (a) {
|
|
a.addEventListener("click", function (ev) {
|
|
ev.stopPropagation();
|
|
});
|
|
});
|
|
|
|
|
|
var rows = document.querySelectorAll("tr.inbox-row");
|
|
var modalEl = document.getElementById("inboxMessageModal");
|
|
if (!modalEl) return;
|
|
var modal = new bootstrap.Modal(modalEl);
|
|
|
|
rows.forEach(function (row) {
|
|
row.addEventListener("click", function () {
|
|
var id = row.getAttribute("data-message-id");
|
|
if (!id) return;
|
|
|
|
fetch("{{ url_for('main.inbox_message_detail', message_id=0) }}".replace("0", id))
|
|
.then(function (resp) {
|
|
if (!resp.ok) throw new Error("Failed to load message details");
|
|
return resp.json();
|
|
})
|
|
.then(function (data) {
|
|
if (data.status !== "ok") throw new Error("Unexpected response");
|
|
var meta = data.meta || {};
|
|
|
|
document.getElementById("inboxMessageModalLabel").textContent = meta.subject || "Message details";
|
|
document.getElementById("msg_from").textContent = meta.from_address || "";
|
|
document.getElementById("msg_backup").textContent = meta.backup_software || "";
|
|
document.getElementById("msg_type").textContent = meta.backup_type || "";
|
|
document.getElementById("msg_job").textContent = meta.job_name || "";
|
|
document.getElementById("msg_overall").textContent = meta.overall_status || "";
|
|
document.getElementById("msg_overall_message").textContent = meta.overall_message || "";
|
|
document.getElementById("msg_received").textContent = meta.received_at || "";
|
|
document.getElementById("msg_parsed").textContent = meta.parsed_at || "";
|
|
|
|
var bodyFrame = document.getElementById("msg_body_container_iframe");
|
|
if (bodyFrame) bodyFrame.srcdoc = wrapMailHtml(data.body_html || "");
|
|
|
|
renderObjects(data.objects || []);
|
|
|
|
// VSPC multi-company mapping support (Active alarms summary)
|
|
(function () {
|
|
var mapBtn = document.getElementById("vspcMapCompaniesBtn");
|
|
var approveBtn = document.getElementById("inboxApproveBtn");
|
|
if (!mapBtn) return;
|
|
|
|
// reset
|
|
mapBtn.classList.add("d-none");
|
|
if (approveBtn) approveBtn.classList.remove("d-none");
|
|
var ciReset = document.getElementById("msg_customer_input");
|
|
if (ciReset) {
|
|
ciReset.removeAttribute("disabled");
|
|
ciReset.placeholder = "Select customer";
|
|
}
|
|
|
|
var bsw = String(meta.backup_software || "").trim();
|
|
var btype = String(meta.backup_type || "").trim();
|
|
var jname = String(meta.job_name || "").trim();
|
|
|
|
if (bsw !== "Veeam" || btype !== "Service Provider Console" || jname !== "Active alarms summary") {
|
|
return;
|
|
}
|
|
|
|
var companies = (data.vspc_companies || meta.vspc_companies || []);
|
|
var defaults = (data.vspc_company_defaults || {});
|
|
if (!Array.isArray(companies)) companies = [];
|
|
|
|
// Fallback for older stored messages where companies were embedded in object names.
|
|
if (!companies.length) {
|
|
var objs = data.objects || [];
|
|
var seen = {};
|
|
objs.forEach(function (o) {
|
|
var name = String((o && o.name) || "");
|
|
var ix = name.indexOf(" | ");
|
|
if (ix > 0) {
|
|
var c = name.substring(0, ix).trim();
|
|
if (c && !seen[c]) { seen[c] = true; companies.push(c); }
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!companies.length) return;
|
|
|
|
// Show mapping button; hide regular approve
|
|
mapBtn.classList.remove("d-none");
|
|
if (approveBtn) approveBtn.classList.add("d-none");
|
|
var ci = document.getElementById("msg_customer_input");
|
|
if (ci) {
|
|
ci.value = "";
|
|
ci.setAttribute("disabled", "disabled");
|
|
ci.placeholder = "Use \"Map companies\"";
|
|
}
|
|
|
|
mapBtn.onclick = function () {
|
|
var tbody = document.getElementById("vspcCompanyMapTbody");
|
|
var form = document.getElementById("vspcCompanyMapForm");
|
|
if (!tbody || !form) return;
|
|
|
|
// set form action
|
|
form.action = "{{ url_for('main.inbox_message_approve_vspc_companies', message_id=0) }}".replace("0", String(meta.id || id));
|
|
|
|
// build rows
|
|
tbody.innerHTML = "";
|
|
companies.forEach(function (company) {
|
|
var tr = document.createElement("tr");
|
|
|
|
var tdC = document.createElement("td");
|
|
tdC.textContent = company;
|
|
tr.appendChild(tdC);
|
|
|
|
var tdS = document.createElement("td");
|
|
var inp = document.createElement("input");
|
|
inp.type = "text";
|
|
inp.className = "form-control form-control-sm";
|
|
inp.setAttribute("list", "vspcCustomerList");
|
|
inp.setAttribute("data-company", company);
|
|
inp.placeholder = "Select customer";
|
|
|
|
// Prefill with existing mapping when available.
|
|
try {
|
|
var d = defaults && defaults[company];
|
|
if (d && d.customer_name) {
|
|
inp.value = String(d.customer_name);
|
|
}
|
|
} catch (e) {}
|
|
tdS.appendChild(inp);
|
|
tr.appendChild(tdS);
|
|
|
|
tbody.appendChild(tr);
|
|
});
|
|
|
|
// clear hidden field
|
|
var hidden = document.getElementById("vspc_company_mappings_json");
|
|
if (hidden) hidden.value = "";
|
|
|
|
var mapModalEl = document.getElementById("vspcCompanyMapModal");
|
|
if (mapModalEl && window.bootstrap) {
|
|
var mm = bootstrap.Modal.getOrCreateInstance(mapModalEl);
|
|
mm.show();
|
|
}
|
|
};
|
|
|
|
// Attach submit handler once
|
|
var mapForm = document.getElementById("vspcCompanyMapForm");
|
|
if (mapForm && !mapForm.getAttribute("data-bound")) {
|
|
mapForm.setAttribute("data-bound", "1");
|
|
mapForm.addEventListener("submit", function (ev) {
|
|
var rows = document.querySelectorAll("#vspcCompanyMapTbody input[data-company]");
|
|
var mappings = [];
|
|
rows.forEach(function (inp) {
|
|
var company = inp.getAttribute("data-company") || "";
|
|
var cname = String(inp.value || "").trim();
|
|
if (!company || !cname) return;
|
|
|
|
var cid = findCustomerIdByName(cname);
|
|
if (!cid) return;
|
|
mappings.push({ company: company, customer_id: cid });
|
|
});
|
|
|
|
var hidden = document.getElementById("vspc_company_mappings_json");
|
|
if (hidden) hidden.value = JSON.stringify(mappings);
|
|
});
|
|
}
|
|
})();
|
|
|
|
|
|
var customerName = meta.customer_name || "";
|
|
var approveForm = document.getElementById("inboxApproveForm");
|
|
|
|
{% if current_user.is_authenticated and active_role in ["admin", "operator"] %}
|
|
var customerInput = document.getElementById("msg_customer_input");
|
|
var customerIdField = document.getElementById("msg_customer_id");
|
|
if (customerInput) customerInput.value = customerName;
|
|
if (customerIdField) {
|
|
var existingId = findCustomerIdByName(customerName);
|
|
customerIdField.value = existingId !== null ? String(existingId) : "";
|
|
}
|
|
if (approveForm) {
|
|
approveForm.action = "{{ url_for('main.inbox_message_approve', message_id=0) }}".replace("0", id);
|
|
approveForm.onsubmit = function (ev) {
|
|
if (!customerInput || !customerIdField) return;
|
|
var cid = findCustomerIdByName(customerInput.value);
|
|
if (!cid) {
|
|
ev.preventDefault();
|
|
alert("Please select an existing customer name from the list.");
|
|
return false;
|
|
}
|
|
customerIdField.value = String(cid);
|
|
};
|
|
}
|
|
|
|
var deleteForm = document.getElementById("inboxDeleteForm");
|
|
if (deleteForm) {
|
|
deleteForm.action = "{{ url_for('main.inbox_message_delete', message_id=0) }}".replace("0", id);
|
|
}
|
|
{% else %}
|
|
var customerDisplay = document.getElementById("msg_customer_display");
|
|
if (customerDisplay) customerDisplay.textContent = customerName || "";
|
|
if (approveForm) approveForm.style.display = "none";
|
|
var deleteForm = document.getElementById("inboxDeleteForm");
|
|
if (deleteForm) deleteForm.style.display = "none";
|
|
{% endif %}
|
|
|
|
modal.show();
|
|
})
|
|
.catch(function (err) {
|
|
console.error(err);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", attachHandlers);
|
|
})();
|
|
</script>
|
|
|
|
{% endblock %}
|