@@ -261,7 +285,196 @@
}
(function () {
-
+ var currentRunId = null;
+
+ function apiJson(url, opts) {
+ opts = opts || {};
+ opts.headers = opts.headers || {};
+ opts.headers['Content-Type'] = 'application/json';
+ return fetch(url, opts).then(function (r) {
+ return r.json().then(function (j) {
+ if (!r.ok || !j || j.status !== 'ok') {
+ var msg = (j && j.message) ? j.message : ('Request failed (' + r.status + ')');
+ throw new Error(msg);
+ }
+ return j;
+ });
+ });
+ }
+
+ function renderAlerts(payload) {
+ var box = document.getElementById('jhm_alerts');
+ if (!box) return;
+ var tickets = (payload && payload.tickets) || [];
+ var remarks = (payload && payload.remarks) || [];
+
+ if (!tickets.length && !remarks.length) {
+ box.innerHTML = '
No tickets or remarks linked to this run.';
+ return;
+ }
+
+ var html = '';
+
+ if (tickets.length) {
+ html += '
Tickets';
+ tickets.forEach(function (t) {
+ var status = t.resolved_at ? 'Resolved' : 'Active';
+ html += '
' +
+ '
' +
+ '
' +
+ '
' +
+ '🎫' +
+ '' + escapeHtml(t.ticket_code || '') + '' +
+ '' + status + '' +
+ '
' +
+ '
' +
+ '
' +
+ '{% if can_manage_jobs %}' +
+ '' +
+ '{% endif %}' +
+ '
' +
+ '
' +
+ '
';
+ });
+ html += '
';
+ }
+
+ if (remarks.length) {
+ html += '
Remarks';
+ remarks.forEach(function (r) {
+ var status = r.resolved_at ? 'Resolved' : 'Active';
+ html += '
' +
+ '
' +
+ '
' +
+ '
' +
+ '💬' +
+ 'Remark' +
+ '' + status + '' +
+ '
' +
+ (r.body ? ('
' + escapeHtml(r.body) + '
') : '') +
+ '
' +
+ '
' +
+ '{% if can_manage_jobs %}' +
+ '' +
+ '{% endif %}' +
+ '
' +
+ '
' +
+ '
';
+ });
+ html += '
';
+ }
+
+ box.innerHTML = html;
+
+ Array.prototype.forEach.call(box.querySelectorAll('button[data-action]'), function (btn) {
+ btn.addEventListener('click', function (ev) {
+ ev.preventDefault();
+ var action = btn.getAttribute('data-action');
+ var id = btn.getAttribute('data-id');
+ if (!action || !id) return;
+ if (action === 'resolve-ticket') {
+ if (!confirm('Mark ticket as resolved?')) return;
+ apiJson('/api/tickets/' + encodeURIComponent(id) + '/resolve', {method: 'POST', body: '{}'})
+ .then(function () { loadAlerts(currentRunId); })
+ .catch(function (e) { alert(e.message || 'Failed.'); });
+ } else if (action === 'resolve-remark') {
+ if (!confirm('Mark remark as resolved?')) return;
+ apiJson('/api/remarks/' + encodeURIComponent(id) + '/resolve', {method: 'POST', body: '{}'})
+ .then(function () { loadAlerts(currentRunId); })
+ .catch(function (e) { alert(e.message || 'Failed.'); });
+ }
+ });
+ });
+ }
+
+ function loadAlerts(runId) {
+ if (!runId) {
+ renderAlerts({tickets: [], remarks: []});
+ return;
+ }
+ fetch('/api/job-runs/' + encodeURIComponent(runId) + '/alerts')
+ .then(function (r) { return r.json(); })
+ .then(function (j) {
+ if (!j || j.status !== 'ok') throw new Error((j && j.message) || 'Failed');
+ renderAlerts(j);
+ })
+ .catch(function () {
+ renderAlerts({tickets: [], remarks: []});
+ });
+ }
+
+ function bindInlineCreateForms() {
+ var btnTicket = document.getElementById('jhm_ticket_save');
+ var btnRemark = document.getElementById('jhm_remark_save');
+ var tCode = document.getElementById('jhm_ticket_code');
+ var tStatus = document.getElementById('jhm_ticket_status');
+ var rBody = document.getElementById('jhm_remark_body');
+ var rStatus = document.getElementById('jhm_remark_status');
+
+ function clearStatus() {
+ if (tStatus) tStatus.textContent = '';
+ if (rStatus) rStatus.textContent = '';
+ }
+
+ if (btnTicket) {
+ btnTicket.addEventListener('click', function () {
+ if (!currentRunId) { alert('Select a run first.'); return; }
+ clearStatus();
+ var ticket_code = tCode ? (tCode.value || '').trim().toUpperCase() : '';
+ if (!ticket_code) {
+ if (tStatus) tStatus.textContent = 'Ticket number is required.';
+ else alert('Ticket number is required.');
+ return;
+ }
+ if (!/^T\d{8}\.\d{4}$/.test(ticket_code)) {
+ if (tStatus) tStatus.textContent = 'Invalid ticket number format. Expected TYYYYMMDD.####.';
+ else alert('Invalid ticket number format. Expected TYYYYMMDD.####.');
+ return;
+ }
+ if (tStatus) tStatus.textContent = 'Saving...';
+ apiJson('/api/tickets', {
+ method: 'POST',
+ body: JSON.stringify({job_run_id: currentRunId, ticket_code: ticket_code})
+ })
+ .then(function () {
+ if (tCode) tCode.value = '';
+ if (tStatus) tStatus.textContent = '';
+ loadAlerts(currentRunId);
+ })
+ .catch(function (e) {
+ if (tStatus) tStatus.textContent = e.message || 'Failed.';
+ else alert(e.message || 'Failed.');
+ });
+ });
+ }
+
+ if (btnRemark) {
+ btnRemark.addEventListener('click', function () {
+ if (!currentRunId) { alert('Select a run first.'); return; }
+ clearStatus();
+ var body = rBody ? rBody.value : '';
+ if (!body || !body.trim()) {
+ if (rStatus) rStatus.textContent = 'Body is required.';
+ else alert('Body is required.');
+ return;
+ }
+ if (rStatus) rStatus.textContent = 'Saving...';
+ apiJson('/api/remarks', {
+ method: 'POST',
+ body: JSON.stringify({job_run_id: currentRunId, body: body})
+ })
+ .then(function () {
+ if (rBody) rBody.value = '';
+ if (rStatus) rStatus.textContent = '';
+ loadAlerts(currentRunId);
+ })
+ .catch(function (e) {
+ if (rStatus) rStatus.textContent = e.message || 'Failed.';
+ else alert(e.message || 'Failed.');
+ });
+ });
+ }
+ }
function wrapMailHtml(html) {
html = html || "";
@@ -325,10 +538,16 @@
if (!modalEl) return;
var modal = new bootstrap.Modal(modalEl);
+ {% if can_manage_jobs %}
+ bindInlineCreateForms();
+ {% endif %}
+
rows.forEach(function (row) {
row.addEventListener("click", function () {
var messageId = row.getAttribute("data-message-id");
+ var runId = row.getAttribute("data-run-id");
if (!messageId) return;
+ currentRunId = runId ? parseInt(runId, 10) : null;
fetch("{{ url_for('main.inbox_message_detail', message_id=0) }}".replace("0", messageId))
.then(function (resp) {
@@ -396,6 +615,8 @@
if (bodyFrame) bodyFrame.srcdoc = wrapMailHtml(data.body_html || "");
renderObjects(data.objects || []);
+
+ loadAlerts(currentRunId);
modal.show();
})
.catch(function (err) {
diff --git a/docs/changelog.md b/docs/changelog.md
index 66c9e20..cb84dd9 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -4,6 +4,8 @@
- Prevented duplicate ticket creation errors when reusing an existing ticket_code.
- Ensured tickets are reused and linked instead of rejected when already present in the system.
+---
+
## v20260106-24-ticket-scope-resolve-popup
- Fixed missing ticket number display in job and run popups by always creating or reusing a ticket scope when linking an existing ticket to a job.
@@ -11,6 +13,17 @@
- Ensured resolving a ticket from the central Tickets view resolves the ticket globally and closes all associated job scopes.
- Updated ticket active status determination to be based on open job scopes, allowing the same ticket number to remain active for other jobs when applicable.
+---
+
+## v20260108-25-job-history-ticket-popup
+
+- Added Tickets and Remarks section to the Job History mail popup.
+- Aligned ticket handling in Job History with the existing Run Checks popup behavior.
+- Enabled viewing of active and resolved tickets and remarks per job run.
+- Added support for creating new tickets and remarks from the Job History popup.
+- Enabled resolving tickets and remarks directly from the Job History popup.
+- Tickets and remarks are now correctly scoped to the selected run (run_id).
+
================================================================================================================================================
## v0.1.17