LCOV - code coverage report
Current view: top level - pkg/selinux - selinux.js Coverage Total Hit
Test: cockpit Lines: 52.2 % 205 107
Test Date: 2026-07-17 07:33:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2016 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6            3 : import cockpit from "cockpit";
       7              : import 'cockpit-dark-theme'; // once per page
       8              : 
       9            3 : import React from "react";
      10            3 : import { createRoot } from "react-dom/client";
      11              : 
      12              : import '../lib/patternfly/patternfly-6-cockpit.scss';
      13              : 
      14              : import * as troubleshootClient from "./setroubleshoot-client.js";
      15              : import * as selinuxClient from "./selinux-client.js";
      16              : import { SETroubleshootPage } from "./setroubleshoot-view.jsx";
      17              : import { superuser } from 'superuser';
      18              : 
      19              : import "./setroubleshoot.scss";
      20              : 
      21            3 : const _ = cockpit.gettext;
      22              : 
      23            3 : superuser.reload_page_on_change();
      24              : 
      25            3 : const initStore = function(rootElement) {
      26            3 :     const dataStore = { };
      27            3 :     dataStore.domRootElement = rootElement;
      28              : 
      29            3 :     dataStore.entries = [];
      30              : 
      31              :     // connected to the dbus api of setroubleshootd
      32            3 :     dataStore.connected = false;
      33              : 
      34              :     // currently trying to connect / used for timer
      35            3 :     dataStore.connecting = null;
      36              : 
      37              :     // did we have a connection error?
      38            3 :     dataStore.error = null;
      39              : 
      40            3 :     dataStore.client = troubleshootClient.client;
      41              : 
      42            3 :     dataStore.selinuxStatusError = undefined;
      43              : 
      44            3 :     const selinuxStatusChanged = function(status, errorMessage) {
      45            3 :         dataStore.selinuxStatus = status;
      46            3 :         if (errorMessage !== undefined)
      47            1 :             dataStore.selinuxStatusError = errorMessage;
      48            3 :         dataStore.render();
      49            3 :     };
      50            0 :     const selinuxStatusDismissError = function() {
      51            0 :         dataStore.selinuxStatusError = undefined;
      52            0 :         dataStore.render();
      53            0 :     };
      54            1 :     const selinuxChangeMode = function(_event, newMode) {
      55            1 :         selinuxClient.setEnforcing(newMode).then(
      56            1 :             function() {
      57            1 :                 dataStore.selinuxStatus.enforcing = newMode;
      58            1 :                 dataStore.render();
      59            1 :             },
      60            0 :             function(error) {
      61            0 :                 dataStore.selinuxStatusError = cockpit.format(_("Error while setting SELinux mode: '$0'"), error.message);
      62            0 :                 dataStore.render();
      63            0 :             }
      64            1 :         );
      65            1 :     };
      66            3 :     dataStore.selinuxStatus = selinuxClient.init(selinuxStatusChanged);
      67              : 
      68              :     // run a fix and update the entries accordingly
      69            0 :     const runFix = function(alertId, analysisId, fixId, runCommand) {
      70            0 :         let idx;
      71            0 :         for (idx = dataStore.entries.length - 1; idx >= 0; --idx) {
      72            0 :             if (dataStore.entries[idx].key == alertId)
      73            0 :                 break;
      74            0 :         }
      75            0 :         if (idx < 0) {
      76            0 :             console.log("Unable to find alert entry for element requesting fix: " + alertId + " (" + analysisId + ").");
      77            0 :             return;
      78            0 :         }
      79            0 :         dataStore.entries[idx].details.pluginAnalysis[fixId].fix = {
      80            0 :             plugin: analysisId,
      81            0 :             running: true,
      82            0 :             result: null,
      83            0 :             success: false,
      84            0 :         };
      85            0 :         dataStore.render();
      86            0 :         const promise = runCommand
      87            0 :             ? cockpit.script(runCommand, { err: "message", superuser: "require" })
      88            0 :             : dataStore.client.runFix(alertId, analysisId);
      89              : 
      90            0 :         promise
      91            0 :                 .then(output => {
      92            0 :                     dataStore.entries[idx].details.pluginAnalysis[fixId].fix = {
      93            0 :                         plugin: analysisId,
      94            0 :                         running: false,
      95            0 :                         result: output,
      96            0 :                         success: true,
      97            0 :                     };
      98            0 :                     selinuxClient.getModifications(selinuxStatusChanged);
      99            0 :                     dataStore.render();
     100            0 :                 })
     101            0 :                 .catch(error => {
     102            0 :                     dataStore.entries[idx].details.pluginAnalysis[fixId].fix = {
     103            0 :                         plugin: analysisId,
     104            0 :                         running: false,
     105            0 :                         result: error.toString(),
     106            0 :                         success: false,
     107            0 :                     };
     108            0 :                     dataStore.render();
     109            0 :                 });
     110            0 :     };
     111              : 
     112              :     /* Delete an alert via the client
     113              :      * if it goes wrong, show an error
     114              :      * remove the entry if successful
     115              :      * This function will only be called if the backend functionality is actually present
     116              :      */
     117            0 :     const deleteAlert = function(alertId) {
     118            0 :         return dataStore.client.deleteAlert(alertId)
     119            0 :                 .then(() => {
     120            0 :                     let idx;
     121            0 :                     for (idx = dataStore.entries.length - 1; idx >= 0; --idx) {
     122            0 :                         if (dataStore.entries[idx].key == alertId)
     123            0 :                             break;
     124            0 :                     }
     125            0 :                     if (idx < 0)
     126            0 :                         return;
     127            0 :                     dataStore.entries.splice(idx, 1);
     128            0 :                     dataStore.render();
     129            0 :                 })
     130            0 :                 .catch(error => {
     131            0 :                     dataStore.error = error.toString();
     132            0 :                     dataStore.render();
     133            0 :                 });
     134            0 :     };
     135              : 
     136            0 :     const dismissError = function() {
     137            0 :         dataStore.error = null;
     138            0 :         dataStore.render();
     139            0 :     };
     140              : 
     141            3 :     const render = function() {
     142            3 :         if (!dataStore.reactRoot)
     143            3 :             dataStore.reactRoot = createRoot(rootElement);
     144              : 
     145            3 :         dataStore.reactRoot.render(React.createElement(SETroubleshootPage, {
     146            3 :             connected: dataStore.connected,
     147            3 :             connecting: dataStore.connecting,
     148            3 :             error: dataStore.error,
     149            3 :             dismissError,
     150            3 :             entries: dataStore.entries,
     151            3 :             runFix,
     152            3 :             deleteAlert,
     153            3 :             selinuxStatus: dataStore.selinuxStatus,
     154            3 :             selinuxStatusError: dataStore.selinuxStatusError,
     155            3 :             changeSelinuxMode: selinuxChangeMode,
     156            3 :             dismissStatusError: selinuxStatusDismissError,
     157            3 :         }));
     158            3 :     };
     159            3 :     dataStore.render = render;
     160              : 
     161              :     /* Update an alert entry if it exists, otherwise create one
     162              :        Details: if undefined, we don't have info on them yet,
     163              :        while null means an error occurred while retrieving them
     164              :        The function doesn't trigger a render
     165              :     */
     166            2 :     const maybeUpdateAlert = function(localId, description, count, details) {
     167              :         // if we already know about this alert, ignore unless the repetition count changed
     168              :         // we start at the back because that's where we push new entries
     169              :         // if we receive an alert multiple times, this is where it will be
     170            2 :         for (let idx = dataStore.entries.length - 1; idx >= 0; --idx) {
     171            2 :             if (dataStore.entries[idx].key == localId) {
     172            1 :                 if (description === undefined || count === undefined) {
     173            1 :                     dataStore.entries[idx].details = details;
     174            1 :                     return;
     175            1 :                 }
     176              :                 // don't update newer information
     177              :                 // this can happen in cases of highly frequent updates
     178            2 :                 if (dataStore.entries[idx].count <= count) {
     179              :                     // don't tamper with the status of a fix being run
     180              :                     // new alerts might be coming in while a fix is running and we don't want
     181              :                     // to lose the progress or result
     182              : 
     183              :                     // only allow details to be null if the count has increased
     184            1 :                     if ((details !== undefined) || (dataStore.entries[idx].count < count)) {
     185            2 :                         dataStore.entries[idx].details = details;
     186            2 :                     }
     187            2 :                     dataStore.entries[idx].description = description;
     188            2 :                     dataStore.entries[idx].count = count;
     189            2 :                 }
     190            2 :                 return;
     191            2 :             }
     192            2 :         }
     193              :         // nothing found, so we create a new entry
     194            2 :         dataStore.entries.push({ key: localId, description, count, details, fix: null });
     195            2 :     };
     196              : 
     197              :     /* Add a list of messages and triggers getting details for each of them
     198              :        The list is added without details at first (if it's a new entry) to preserve the order
     199              :      */
     200            2 :     const handleMultipleMessages = function(entries) {
     201            2 :         const detailPromises = [];
     202            2 :         for (let idxEntry = 0; idxEntry != entries.length; ++idxEntry) {
     203            2 :             const entry = entries[idxEntry];
     204            2 :             maybeUpdateAlert(entry.localId, entry.summary, entry.reportCount, undefined);
     205            2 :             detailPromises.push(dataStore.getAlertDetails(entry.localId));
     206            2 :         }
     207              : 
     208            2 :         Promise.all(detailPromises).then(render);
     209            2 :     };
     210              : 
     211            0 :     dataStore.handleAlert = function(level, localId) {
     212              :         // right now the level is unused, since we can't access it for existing alerts
     213              : 
     214              :         // we receive the item details in added delayed fashion, render only once we have the full info
     215            0 :         dataStore.getAlertDetails(localId);
     216            0 :         render();
     217            0 :     };
     218              : 
     219            2 :     const getAlertDetails = function(id) {
     220            2 :         return dataStore.client.getAlert(id)
     221            2 :                 .then(details => {
     222            2 :                     maybeUpdateAlert(id, details.summary, details.reportCount, details);
     223            2 :                 })
     224            0 :                 .catch(() => {
     225            0 :                     maybeUpdateAlert(id, undefined, undefined, null);
     226            0 :                 });
     227            2 :     };
     228            3 :     dataStore.getAlertDetails = getAlertDetails;
     229              : 
     230            0 :     const setDisconnected = function() {
     231            0 :         dataStore.connected = false;
     232            0 :         render();
     233            0 :     };
     234              : 
     235            0 :     const setErrorIfNotConnected = function() {
     236            0 :         if (dataStore.connecting === null)
     237            0 :             return;
     238            0 :         dataStore.error = _("Not connected");
     239            0 :         render();
     240            0 :     };
     241              : 
     242            3 :     dataStore.connectionTimeout = 5000;
     243              : 
     244              :     // try to connect
     245            3 :     dataStore.tryConnect = function() {
     246            3 :         if (dataStore.connecting === null) {
     247            3 :             dataStore.connecting = window.setTimeout(setErrorIfNotConnected, dataStore.connectionTimeout);
     248            3 :             render();
     249              :             // initialize our setroubleshootd client
     250            3 :             dataStore.client.init()
     251            2 :                     .then(() => {
     252            2 :                         dataStore.connected = true;
     253            2 :                         window.clearTimeout(dataStore.connecting);
     254            2 :                         dataStore.connecting = null;
     255            2 :                         dataStore.error = null; // reset "not connected"
     256            2 :                         render();
     257              :                         // now register a callback to get new entries and get all existing ones
     258              :                         // the order is important, since we don't want to miss an entry
     259            2 :                         dataStore.client.handleAlert(dataStore.handleAlert);
     260            2 :                         dataStore.client.getAlerts()
     261            2 :                                 .then(handleMultipleMessages)
     262            0 :                                 .catch(() => {
     263            0 :                                     console.error("Unable to get setroubleshootd messages");
     264            0 :                                     setDisconnected();
     265            0 :                                 });
     266            2 :                     })
     267            0 :                     .catch(() => {
     268            0 :                         dataStore.connected = false;
     269            0 :                         window.clearTimeout(dataStore.connecting);
     270            0 :                         dataStore.connecting = null;
     271            0 :                         render();
     272            0 :                     });
     273            3 :         }
     274            3 :     };
     275              : 
     276              :     // render once initially
     277            3 :     render();
     278              : 
     279              :     // try to connect immediately
     280            3 :     dataStore.tryConnect();
     281              : 
     282            3 :     return dataStore;
     283            3 : };
     284              : 
     285            3 : document.addEventListener("DOMContentLoaded", function() {
     286            3 :     initStore(document.getElementById('app'));
     287            3 : });
        

Generated by: LCOV version 2.0-1