Table of Contents

Organizations

Le bouton refresh va faire un appel API pour récupèrer la liste des organisations. Sélectionner les organisations relevantes (soit en cliquant dans la liste ou en utilisant les boutons de sélection) puis sauvegarder.

WebOrganizations.py

WebOrganizations.py
# WebOrganizations.py
import PersistentData
import CockpitITSM
 
def init_organizations_interface(ui):
    """
    Registers the bridge between Organizations.html and Python logic.
    """
 
    def handle_get_organizations(_, __):
        """Syncs with API then returns the active organizations list from DB."""
        success = CockpitITSM.update_organizations()
 
        if not success:
            ui.send_message("organizations_response", {
                "success": False,
                "message": "Failed to refresh organizations. Check API configuration."
            })
            return
 
        orgs = PersistentData.get_active_organizations()
        ui.send_message("organizations_data", {"organizations": orgs})
 
    def handle_save_organizations(_, data):
        """Persists the selected state for each organization."""
        try:
            organizations = data.get("organizations", [])
            for org in organizations:
                PersistentData.db.update(
                    "organizations",
                    {"selected": org["selected"]},
                    condition=f"id = {org['id']}"
                )
            print(f"DEBUG: Saved selection for {len(organizations)} organizations.")
            ui.send_message("organizations_response", {
                "success": True,
                "message": f"Selection saved ({len(organizations)} organizations)."
            })
        except Exception as e:
            print(f"ERROR saving organizations: {str(e)}")
            ui.send_message("organizations_response", {
                "success": False,
                "message": f"Save error: {str(e)}"
            })
 
    # Registering the handlers
    ui.on_message("get_organizations",  handle_get_organizations)
    ui.on_message("save_organizations", handle_save_organizations)

Organizations.html

Organizations.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 - Organizations</title>
    <link rel="stylesheet" href="style.css">
    <link rel="icon" type="image/x-icon" href="img/favicon.ico">
    <style>
        .org-list {
            margin: 1.2rem 0;
            max-height: 320px;
            overflow-y: auto;
            border: 1px solid #333;
            border-radius: 4px;
            background-color: #2a2a2a;
        }
 
        .org-list-empty {
            padding: 1.2rem;
            text-align: center;
            color: #666;
            font-size: 0.85rem;
        }
 
        .org-item {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 0.65rem 1rem;
            border-bottom: 1px solid #333;
            cursor: pointer;
            transition: background-color 0.15s;
        }
 
        .org-item:last-child {
            border-bottom: none;
        }
 
        .org-item:hover {
            background-color: #333;
        }
 
        .org-item input[type="checkbox"] {
            width: 16px;
            height: 16px;
            accent-color: #00aaff;
            cursor: pointer;
            flex-shrink: 0;
        }
 
        .org-item label {
            cursor: pointer;
            font-size: 0.9rem;
            color: #e0e0e0;
            flex: 1;
        }
 
        .selection-group {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
            margin-bottom: 1rem;
        }
 
        .selection-group button {
            flex: 1;
            padding: 0.5rem 0.4rem;
            font-size: 0.78rem;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>
                <img src="img/company-building-filled.svg" alt="" class="icon-api-svg">
                Organizations
            </h1>
        </header>
 
        <div class="button-group" style="margin-bottom: 1.2rem;">
            <button id="btnRefresh" class="btn-secondary" onclick="refreshOrganizations()">
                <img src="img/cloud-download.svg" alt="" class="btn-icon">Refresh
            </button>
        </div>
 
        <div id="orgList" class="org-list">
            <div class="org-list-empty">Click Refresh to load organizations.</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="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_organizations.js"></script>
</body>
</html>

app_organizations.js

app_organizations.js
// app_organizations.js
const socket = io();
 
// ── Socket events ─────────────────────────────────────────────────────────────
 
socket.on("connect", () => {
    console.log("Connected to server");
    refreshOrganizations();
});
 
socket.on("connect_error", (err) => {
    console.error("Connection error:", err);
    setStatus("Connection error. Please reload.", false);
});
 
socket.on("organizations_data", function(data) {
    renderOrganizations(data.organizations || []);
    setStatus("", null);
    setRefreshEnabled(true);
});
 
socket.on("organizations_response", function(data) {
    setStatus(data.message, data.success);
    setRefreshEnabled(true);
});
 
// ── Actions ───────────────────────────────────────────────────────────────────
 
function refreshOrganizations() {
    setStatus("Loading organizations...", null);
    setRefreshEnabled(false);
    socket.emit("get_organizations", {});
}
 
function saveSelection() {
    const checkboxes = document.querySelectorAll(".org-checkbox");
    const selected = Array.from(checkboxes).map(cb => ({
        id: parseInt(cb.dataset.id),
        selected: cb.checked ? 1 : 0
    }));
 
    if (selected.length === 0) {
        setStatus("No organizations to save.", false);
        return;
    }
 
    setStatus("Saving...", null);
    socket.emit("save_organizations", { organizations: selected });
}
 
function selectAll() {
    document.querySelectorAll(".org-checkbox").forEach(cb => cb.checked = true);
}
 
function selectNone() {
    document.querySelectorAll(".org-checkbox").forEach(cb => cb.checked = false);
}
 
function invertSelection() {
    document.querySelectorAll(".org-checkbox").forEach(cb => cb.checked = !cb.checked);
}
 
// ── Rendering ─────────────────────────────────────────────────────────────────
 
function renderOrganizations(orgs) {
    const container = document.getElementById("orgList");
 
    if (orgs.length === 0) {
        container.innerHTML = '<div class="org-list-empty">No active organizations found.</div>';
        return;
    }
 
    container.innerHTML = orgs.map(org => `
        <div class="org-item" onclick="toggleCheckbox(${org.id})">
            <input
                type="checkbox"
                class="org-checkbox"
                id="org_${org.id}"
                data-id="${org.id}"
                ${org.selected ? "checked" : ""}
                onclick="event.stopPropagation()"
            >
            <label for="org_${org.id}">${escapeHtml(org.name)}</label>
        </div>
    `).join("");
}
 
function toggleCheckbox(orgId) {
    const cb = document.getElementById(`org_${orgId}`);
    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, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;");
}