/* * This file is part of Cockpit. * * Copyright (C) 2016 Red Hat, Inc. * * Cockpit is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * Cockpit is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Cockpit; If not, see . */ var cockpit = require("cockpit"); var _ = cockpit.gettext; var React = require("react"); var cockpitListing = require("cockpit-components-listing.jsx"); var OnOffSwitch = require("cockpit-components-onoff.jsx").OnOffSwitch; /* Show details for an alert, including possible solutions * Props correspond to an item in the setroubleshoot dataStore */ var SELinuxEventDetails = React.createClass({ getInitialState: function() { var expanded; // all details are collapsed by default if (this.props.details) expanded = this.props.details.pluginAnalysis.map(function() { return false; } ); return { solutionExpanded: expanded, // show details for solution }; }, handleSolutionDetailsClick: function(itmIdx, e) { var solutionExpanded = this.state.solutionExpanded; solutionExpanded[itmIdx] = !solutionExpanded[itmIdx]; this.setState( { solutionExpanded: solutionExpanded } ); e.stopPropagation(); e.preventDefault(); }, runFix: function(itmIdx) { // make sure the details for the solution are collapsed, or they can hide the progress and result var solutionExpanded = this.state.solutionExpanded; if (solutionExpanded[itmIdx]) { solutionExpanded[itmIdx] = false; this.setState( { solutionExpanded: solutionExpanded } ); } var localId = this.props.details.localId; var analysisId = this.props.details.pluginAnalysis[itmIdx].analysisId; this.props.runFix(localId, analysisId); }, render: function() { if (!this.props.details) { // details should be requested by default, so we just need to wait for them var waiting = (this.props.details === undefined); return ( ); } var self = this; var fixEntries = this.props.details.pluginAnalysis.map(function(itm, itmIdx) { var fixit = null; var msg = null; if (itm.fixable) { if ((self.props.fix) && (self.props.fix.plugin == itm.analysisId)) { if (self.props.fix.running) { msg = (
{ _("Applying solution...") }
); } else { if (self.props.fix.success) { msg = (
{ _("Solution applied successfully") }: {self.props.fix.result}
); } else { msg = (
{ _("Solution failed") }: {self.props.fix.result}
); } } } fixit = (
); } else { fixit = (
{ _("Unable to apply this solution automatically") }
); } var detailsLink = { _("solution details") }; var doState; var doElem; var caret; if (self.state.solutionExpanded[itmIdx]) { caret = ; doState =
{caret} {detailsLink}
; doElem =
{itm.doText}
; } else { caret = ; doState =
{caret} {detailsLink}
; doElem = null; } return (
{itm.ifText}
{fixit}
{itm.thenText}
{doState} {doElem} {msg}
); }); return (
{fixEntries}
); } }); /* Show the audit log events for an alert */ var SELinuxEventLog = React.createClass({ render: function() { if (!this.props.details) { // details should be requested by default, so we just need to wait for them var waiting = (this.props.details === undefined); return ( ); } var self = this; var logEntries = this.props.details.auditEvent.map(function(itm, idx) { // use the alert id and index in the event log array as the data key for react // if the log becomes dynamic, the entire log line might need to be considered as the key return (
{itm}
); }); return (
{logEntries}
); } }); /* Implements a subset of the PatternFly Empty State pattern * https://www.patternfly.org/patterns/empty-state/ * Special values for icon property: * - 'waiting' - display spinner * - 'error' - display error icon */ var EmptyState = React.createClass({ render: function() { var description = null; if (this.props.description) description =

{this.props.description}

; var message = null; if (this.props.message) message =

{this.props.message}

; var curtains = "curtains-ct"; if (this.props.relative) curtains = "curtains-relative"; var icon = this.props.icon; if (icon == 'waiting') icon =
; else if (icon == 'error') icon =
; return (
{icon}
{description} {message}
); } }); /* Component to show a dismissable error, message as child text * dismissError callback function triggered when the close button is pressed */ var DismissableError = React.createClass({ handleDismissError: function(e) { // only consider primary mouse button if (!e || e.button !== 0) return; if (this.props.dismissError) this.props.dismissError(); e.stopPropagation(); }, render: function() { return (
{this.props.children}
); } }); /* Component to show selinux status and offer an option to change it * selinuxStatus status of selinux on the system, properties as defined in selinux-client.js * selinuxStatusError error message from reading or setting selinux status/mode * changeSelinuxMode function to use for changing the selinux enforcing mode * dismissError function to dismiss the error message */ var SELinuxStatus = React.createClass({ render: function() { var errorMessage; if (this.props.selinuxStatusError) { errorMessage = ( {this.props.selinuxStatusError} ); } if (this.props.selinuxStatus.enabled === undefined) { // we don't know the current state return (
{errorMessage}

{_("SELinux system status is unknown.")}

); } else if (!this.props.selinuxStatus.enabled) { // selinux is disabled on the system, not much we can do return (
{errorMessage}

{_("SELinux is disabled on the system.")}

); } var note; var configUnknown = (this.props.selinuxStatus.configEnforcing === undefined); if (configUnknown) note = {_("The configured state is unknown, it might change on the next boot.")}; else if (!configUnknown && this.props.selinuxStatus.enforcing !== this.props.selinuxStatus.configEnforcing) note = {_("Setting deviates from the configured state and will revert on the next boot.")}; return (

{_("SELinux Policy")}

{errorMessage} {note}
); } }); /* The listing only shows if we have a connection to the dbus API * Otherwise we have blank slate: trying to connect, error * Expected properties: * connected true if the client is connected to setroubleshoot-server via dbus * error error message to show (in EmptyState if not connected, as a dismissable alert otherwise * dismissError callback, triggered for the dismissable error in connected state * deleteAlert callback, triggered with an alert id as parameter to trigger deletion * entries setroubleshoot entries * - runFix function to run fix * - details fix details as provided by the setroubleshoot client * - description brief description of the error * - count how many times (>= 1) this alert occurred * selinuxStatus status of selinux on the system, properties as defined in selinux-client.js * selinuxStatusError error message from reading or setting selinux status/mode * changeSelinuxMode function to use for changing the selinux enforcing mode * dismissStatusError function that is triggered to dismiss the selinux status error */ var SETroubleshootPage = React.createClass({ handleDeleteAlert: function(alertId, e) { // only consider primary mouse button if (!e || e.button !== 0) return; if (this.props.deleteAlert) this.props.deleteAlert(alertId); e.stopPropagation(); }, handleDismissError: function(e) { // only consider primary mouse button if (!e || e.button !== 0) return; if (this.props.dismissError) this.props.dismissError(); e.stopPropagation(); }, render: function() { // if selinux is disabled, we only show EmptyState if (this.props.selinuxStatus.enabled === false) { return ( } description={ _("SELinux is disabled on the system") } message={null} relative={false}/> ); } var self = this; var entries; var troubleshooting; var title = _("SELinux Access Control Errors"); var emptyCaption = _("No SELinux alerts."); if (!this.props.connected) { if (this.props.connecting) { emptyCaption = (
{_("Connecting to SETroubleshoot daemon...")}
); } else { // if we don't have setroubleshoot-server, be more subtle about saying that title = ""; emptyCaption = ( {_("Install setroubleshoot-server to troubleshoot SELinux events.")} ); } } else { entries = this.props.entries.map(function(itm) { itm.runFix = self.props.runFix; var listingDetail; if (itm.details && 'firstSeen' in itm.details) { if (itm.details.reportCount >= 2) { listingDetail = cockpit.format(_("Occurred between $0 and $1"), itm.details.firstSeen.calendar(), itm.details.lastSeen.calendar() ); } else { listingDetail = cockpit.format(_("Occurred $0"), itm.details.firstSeen.calendar()); } } var onDeleteClick; if (itm.details) onDeleteClick = self.handleDeleteAlert.bind(self, itm.details.localId); var dismissAction = ( ); } return (
{errorMessage} {troubleshooting}
); } }); module.exports = { SETroubleshootPage: SETroubleshootPage, };