start:utils:cockpit:p1:cpu:status
Table of Contents
Ticket Status
Le bouton refresh va faire un appel API pour récupérer la liste des status qui existent.
Hélas l'API ne remonte que les status qui ont été définis par les administrateurs de votre instance. Pour les status définis par défaut (avec un cadenas) il faut trouver manuellement la valeur de ces status. Pour cela il faut introduire un ticket exemple par status, soit pour:
- INCIDENT / New
- INCIDENT / Acknowledged
- INCIDENT / Auto-completed
- REQUEST / New
- REQUEST / Acknowledged
- REQUEST / To Validate
- CHANGE / New
- CHANGE / Acknowledged
- CHANGE / To Validate
- PROBLEM / New
- RELASE / New
Ensuite il faut sélectionner les status relevants (soit en cliquant dans la liste ou en utilisant les boutons de sélection) puis sauvegarder.
Et une page d'aide pour expliquer ce qu'il faut faire.
WebStatus.py
- WebStatus.py
# WebStatus.py import PersistentData import CockpitITSM def init_status_interface(ui): """ Registers the bridge between Status.html and Python logic. """ def handle_get_ticket_statuses(_, __): """Syncs with API then returns ticket statuses from DB, sorted by subType / reference.""" success = CockpitITSM.update_ticket_status() if not success: ui.send_message("status_response", { "success": False, "message": "Failed to refresh ticket statuses. Check API configuration." }) return statuses = PersistentData.get_ticket_statuses() ui.send_message("status_data", {"statuses": statuses}) def handle_save_ticket_statuses(_, data): """Persists the selected state for each ticket status.""" try: statuses = data.get("statuses", []) for s in statuses: PersistentData.db.update( "objects", {"selected": s["selected"]}, condition=f"id = {s['id']} AND subType = '{s['subType']}'" ) print(f"DEBUG: Saved selection for {len(statuses)} ticket statuses.") ui.send_message("status_response", { "success": True, "message": f"Selection saved ({len(statuses)} statuses)." }) except Exception as e: print(f"ERROR saving ticket statuses: {str(e)}") ui.send_message("status_response", { "success": False, "message": f"Save error: {str(e)}" }) def handle_resolve_status_reference(_, data): """ Fetches a ticket by ID and updates the status reference in DB for the matching (status_id, sub_type) entry. """ ticket_id = data.get("ticket_id") sub_type = data.get("sub_type", "") if not ticket_id: ui.send_message("status_resolve_response", { "success": False, "message": "No ticket ID provided.", "sub_type": sub_type }) return success, result = CockpitITSM.resolve_status_from_ticket(ticket_id, sub_type) if not success: ui.send_message("status_resolve_response", { "success": False, "message": result, "sub_type": sub_type }) return status_id = result["status_id"] status_ref = result["status_reference"] obj_type = "TICKET_STATUS" cond = f"id = {status_id} AND type = '{obj_type}' AND subType = '{sub_type}'" existing = PersistentData.db.read("objects", condition=cond) if existing: PersistentData.db.update("objects", {"reference": status_ref}, condition=cond) else: PersistentData.db.store("objects", { "id": status_id, "module": "TICKETING", "type": obj_type, "subType": sub_type, "reference": status_ref, "selected": 0, "manual": 1 }) print(f"DEBUG: Resolved status for {sub_type} #{status_id} → '{status_ref}'") ui.send_message("status_resolve_response", { "success": True, "message": f"Resolved: {sub_type} / {status_ref}", "sub_type": sub_type, "status_id": status_id, "reference": status_ref }) # Refresh the list from DB only — no API call to avoid overwriting resolved entries statuses = PersistentData.get_ticket_statuses() ui.send_message("status_data", {"statuses": statuses}) # Registering the handlers ui.on_message("get_ticket_statuses", handle_get_ticket_statuses) ui.on_message("save_ticket_statuses", handle_save_ticket_statuses) ui.on_message("resolve_status_reference", handle_resolve_status_reference)
Status.html
- Status.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>P1 Notifications 4 Cockpit ITSM - Ticket Status</title> <link rel="stylesheet" href="style.css"> <link rel="icon" type="image/x-icon" href="img/favicon.ico"> </head> <body> <div class="container"> <header> <h1> <img src="img/monitoring.svg" alt="" class="icon-api-svg"> Ticket Status </h1> </header> <div class="button-group" style="margin-bottom: 1.2rem;"> <button id="btnRefresh" class="btn-secondary" onclick="refreshStatus()"> <img src="img/cloud-download.svg" alt="" class="btn-icon">Refresh </button> </div> <div id="statusList" class="org-list"> <div class="org-list-empty">Click Refresh to load ticket statuses.</div> </div> <div class="selection-group"> <button class="btn-secondary" onclick="selectAll()"><img src="img/select-all.svg" alt="" class="btn-icon">Select All</button> <button class="btn-secondary" onclick="invertSelection()"><img src="img/select-inverse.svg" alt="" class="btn-icon">Invert</button> <button class="btn-secondary" onclick="selectNone()"><img src="img/select-none.svg" alt="" class="btn-icon">Select None</button> </div> <div class="button-group"> <button id="btnSave" class="btn-primary" onclick="saveSelection()"> <img src="img/save.svg" alt="" class="btn-icon">Save </button> </div> <div id="statusMessage" class="status-message"></div> <div class="resolve-section"> <div class="resolve-title"> Status Reference Resolution <a href="Help.html" class="help-link" title="How to find reference tickets">?</a> </div> <div class="resolve-subtitle"> Enter one ticket ID per type to resolve the status references for this instance. </div> <div id="resolveList"></div> </div> <div class="navigation-group" style="margin-top: 1rem; border-top: 1px solid #333; padding-top: 1rem; text-align: center;"> <a href="index.html" class="btn-secondary" style="display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; text-decoration: none; padding: 0.8rem; border-radius: 4px; font-weight: bold; box-sizing: border-box;"> <img src="img/home.svg" alt="Home" style="width: 20px; height: 20px; filter: invert(1);">Back to Home </a> </div> </div> <script src="libs/socket.io.min.js"></script> <script src="app_status.js"></script> </body> </html>
app_status.js
- app_status.js
// app_status.js const socket = io(); // ── Socket events ───────────────────────────────────────────────────────────── socket.on("connect", () => { console.log("Connected to server"); refreshStatus(); }); socket.on("connect_error", (err) => { console.error("Connection error:", err); setStatus("Connection error. Please reload.", false); }); socket.on("status_data", function(data) { renderStatusList(data.statuses || []); setStatus("", null); setRefreshEnabled(true); }); socket.on("status_response", function(data) { setStatus(data.message, data.success); setRefreshEnabled(true); }); // ── Actions ─────────────────────────────────────────────────────────────────── function refreshStatus() { setStatus("Loading ticket statuses...", null); setRefreshEnabled(false); socket.emit("get_ticket_statuses", {}); } function saveSelection() { const checkboxes = document.querySelectorAll(".status-checkbox"); const selected = Array.from(checkboxes).map(cb => ({ id: parseInt(cb.dataset.id), subType: cb.dataset.subtype, selected: cb.checked ? 1 : 0 })); if (selected.length === 0) { setStatus("No statuses to save.", false); return; } setStatus("Saving...", null); socket.emit("save_ticket_statuses", { statuses: selected }); } function selectAll() { document.querySelectorAll(".status-checkbox").forEach(cb => cb.checked = true); } function selectNone() { document.querySelectorAll(".status-checkbox").forEach(cb => cb.checked = false); } function invertSelection() { document.querySelectorAll(".status-checkbox").forEach(cb => cb.checked = !cb.checked); } // ── Rendering ───────────────────────────────────────────────────────────────── function renderStatusList(statuses) { const container = document.getElementById("statusList"); if (statuses.length === 0) { container.innerHTML = '<div class="org-list-empty">No ticket statuses found.</div>'; return; } container.innerHTML = statuses.map(s => ` <div class="org-item" onclick="toggleCheckbox('status_${s.id}_${escapeAttr(s.subType)}')"> <input type="checkbox" class="status-checkbox" id="status_${s.id}_${escapeAttr(s.subType)}" data-id="${s.id}" data-subtype="${escapeAttr(s.subType)}" ${s.selected ? "checked" : ""} onclick="event.stopPropagation()" > <label for="status_${s.id}_${escapeAttr(s.subType)}">${escapeHtml(s.subType)} / ${escapeHtml(s.reference)}</label> </div> `).join(""); } function toggleCheckbox(id) { const cb = document.getElementById(id); if (cb) cb.checked = !cb.checked; } // ── Helpers ─────────────────────────────────────────────────────────────────── function setStatus(message, success) { const el = document.getElementById("statusMessage"); el.innerText = message; if (success === true) el.style.color = "#4db6ac"; else if (success === false) el.style.color = "#e57373"; else el.style.color = "#bb86fc"; } function setRefreshEnabled(enabled) { document.getElementById("btnRefresh").disabled = !enabled; } function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """); } function escapeAttr(str) { return String(str).replace(/[^a-zA-Z0-9_-]/g, "_"); } // ── Status Reference Resolution ─────────────────────────────────────────────── const SUB_TYPES = ["INCIDENT", "REQUEST", "CHANGE", "PROBLEM", "RELEASE"]; function renderResolveList() { const container = document.getElementById("resolveList"); container.innerHTML = SUB_TYPES.map(sub => ` <div class="resolve-item"> <label>${sub}</label> <input type="text" inputmode="numeric" pattern="[0-9]*" class="resolve-input" id="resolve_input_${sub}" placeholder="Ticket ID" oninput="this.value = this.value.replace(/[^0-9]/g, '')" > <button class="btn-resolve" id="resolve_btn_${sub}" onclick="resolveStatus('${sub}')"> Resolve </button> <span class="resolve-result" id="resolve_result_${sub}"></span> </div> `).join(""); } function resolveStatus(subType) { const input = document.getElementById(`resolve_input_${subType}`); const btn = document.getElementById(`resolve_btn_${subType}`); const result = document.getElementById(`resolve_result_${subType}`); const ticketId = parseInt(input.value); if (!ticketId || ticketId < 1) { input.classList.add("error"); result.textContent = "Invalid ID"; result.style.color = "#e57373"; return; } input.classList.remove("resolved", "error"); result.textContent = "Resolving..."; result.style.color = "#bb86fc"; btn.disabled = true; socket.emit("resolve_status_reference", { ticket_id: ticketId, sub_type: subType }); } socket.on("status_resolve_response", function(data) { const subType = data.sub_type; const input = document.getElementById(`resolve_input_${subType}`); const btn = document.getElementById(`resolve_btn_${subType}`); const result = document.getElementById(`resolve_result_${subType}`); btn.disabled = false; if (data.success) { input.classList.add("resolved"); input.classList.remove("error"); result.textContent = data.reference; result.style.color = "#4db6ac"; // Refresh the status list to reflect the newly resolved entry refreshStatus(); } else { input.classList.add("error"); input.classList.remove("resolved"); result.textContent = data.message; result.style.color = "#e57373"; } }); // Render resolve list on page load document.addEventListener("DOMContentLoaded", renderResolveList);
Help.html
- Help.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>P1 Notifications 4 Cockpit ITSM - Help</title> <link rel="stylesheet" href="style.css"> <link rel="icon" type="image/x-icon" href="img/favicon.ico"> </head> <body> <div class="container" style="max-width: 520px;"> <header> <h1> <img src="img/monitoring.svg" alt="" class="icon-api-svg"> Help — Status Resolution </h1> </header> <div class="help-content"> <div class="help-section"> <div class="help-section-title">Why is this needed?</div> <p> Cockpit ITSM uses numeric IDs for ticket statuses. These IDs <strong>differ from one instance to another</strong>, so the application needs a reference ticket per type to retrieve the correct ID from your instance. </p> </div> <div class="help-section"> <div class="help-section-title">How to find a reference ticket</div> <ol class="help-steps"> <li>Log in to your <strong>Cockpit ITSM</strong> instance.</li> <li> Go to the <strong>Tickets</strong> module <em>(1)</em>, choose <strong>All tickets</strong> <em>(2)</em>, then select the <strong>History — Tickets</strong> tab <em>(3)</em>. Select a wide date range <em>(4)</em> and apply the filters <em>(5)</em>. <img src="img/SearchSampleTickets.png" alt="Type / Status combinations" class="help-img"> </li> <li> Apply the following filters one at a time and note one ticket ID for each: <ul> <li>Type: INCIDENT / Status: New</li> <li>Type: INCIDENT / Status: Acknowledged</li> <li>Type: INCIDENT / Status: Auto-Completed</li> <li>Type: REQUEST / Status: New</li> <li>Type: REQUEST / Status: Acknowledged</li> <li>Type: REQUEST / Status: To validate</li> <li>Type: CHANGE / Status: New</li> <li>Type: CHANGE / Status: Acknowledged</li> <li>Type: CHANGE / Status: To validate</li> <li>Type: PROBLEM / Status: New</li> <li>Type: RELEASE / Status: New</li> </ul> </li> <li> Enter <strong>only the ticket number</strong> (digits only) in the corresponding field on the <strong>Ticket Status</strong> page and click <em>Resolve</em>. </li> </ol> </div> <div class="help-section"> <div class="help-section-title">Important notes</div> <ul class="help-notes"> <li>Enter <strong>only the ticket number</strong> — do not include any prefix, hash, or text.</li> <li>The ticket ID must match the type — an <em>Incident</em> ticket ID cannot be used to resolve a <em>Request</em> status.</li> <li>You only need to resolve statuses for the ticket types you intend to monitor.</li> <li>You can use different tickets to resolve different statuses of the same type — one ticket per status you want to capture.</li> <li>Once resolved, the status appears in the list and can be selected for filtering.</li> </ul> </div> </div> <div class="navigation-group" style="margin-top: 1rem; border-top: 1px solid #333; padding-top: 1rem; text-align: center;"> <a href="Status.html" class="btn-secondary" style="display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; text-decoration: none; padding: 0.8rem; border-radius: 4px; font-weight: bold; box-sizing: border-box;"> <img src="img/monitoring.svg" alt="" style="width: 20px; height: 20px; filter: invert(1);">Back to Ticket Status </a> </div> </div> </body> </html>
start/utils/cockpit/p1/cpu/status.txt · Last modified: by admin_wiki
