diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/data/Compliance.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/data/Compliance.scala index d401cf86f22..31307f96c44 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/data/Compliance.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/data/Compliance.scala @@ -38,12 +38,15 @@ package com.normation.rudder.rest.data import com.normation.inventory.domain.NodeId +import com.normation.rudder.domain.policies.Directive import com.normation.rudder.domain.policies.DirectiveId import com.normation.rudder.domain.policies.PolicyMode +import com.normation.rudder.domain.policies.Rule import com.normation.rudder.domain.policies.RuleId import com.normation.rudder.domain.reports.* import com.normation.rudder.domain.reports.ComplianceLevel import com.normation.rudder.reports.ComplianceModeName +import com.normation.rudder.repository.FullActiveTechnique import java.lang import net.liftweb.json.* import net.liftweb.json.JsonAST @@ -123,10 +126,11 @@ final case class ByDirectiveByNodeRuleCompliance( ) final case class ByRuleDirectiveCompliance( - id: DirectiveId, - name: String, - compliance: ComplianceLevel, - components: Seq[ByRuleComponentCompliance] + id: DirectiveId, + name: String, + compliance: ComplianceLevel, + skippedDetails: Option[SkippedDetails], + components: Seq[ByRuleComponentCompliance] ) sealed trait ByRuleComponentCompliance { @@ -194,6 +198,44 @@ final case class ByRuleByNodeByDirectiveByValueCompliance( values: Seq[ComponentValueStatusReport] ) extends ByRuleByNodeByDirectiveByComponentCompliance +final case class SkippedDetails( + overridingRuleId: RuleId, + overridingRuleName: String +) +final case class DirectiveComplianceOverride( + overridenRuleId: RuleId, + directiveId: DirectiveId, + directiveName: String, + overridingRuleId: RuleId +) { + def toComplianceByRule(rules: Map[RuleId, Rule]): ByRuleDirectiveCompliance = { + ByRuleDirectiveCompliance( + directiveId, + directiveName, + ComplianceLevel(), + Some(SkippedDetails(overridingRuleId, rules.get(overridingRuleId).map(_.name).getOrElse("unknown rule"))), + Seq.empty + ) + } +} + +object ComplianceOverrides { + def getOverridenDirective( + overrides: List[OverridenPolicy], + directives: Map[DirectiveId, (FullActiveTechnique, Directive)] + ): List[DirectiveComplianceOverride] = { + val overridesData = for { + over <- overrides + (_, overridenDir) <- directives.get(over.policy.directiveId) + (_, overridingDir) <- directives.get(over.overridenBy.directiveId) + } yield { + DirectiveComplianceOverride(over.policy.ruleId, over.policy.directiveId, overridenDir.name, over.overridenBy.ruleId) + + } + overridesData.toList + } +} + object GroupComponentCompliance { // This function do the recursive treatment of components, we will have each time a pair of Sequence of tuple (NodeId , component compliance structure) def recurseComponent( @@ -644,6 +686,9 @@ object JsonCompliance { ~ ("name" -> directive.name) ~ ("compliance" -> directive.compliance.complianceWithoutPending(precision)) ~ ("complianceDetails" -> percents(directive.compliance, precision)) + ~ ("skippedDetails" -> directive.skippedDetails.map(s => + ("overridingRuleId" -> s.overridingRuleId.serialize) ~ ("overridingRuleName" -> s.overridingRuleName) + )) ~ ("components" -> components(directive.components, level, precision)) ) }) diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/ComplianceApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/ComplianceApi.scala index 109dedeb08c..cdef49d6c48 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/ComplianceApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/ComplianceApi.scala @@ -66,6 +66,7 @@ import com.normation.rudder.rest.RestUtils.* import com.normation.rudder.rest.data.* import com.normation.rudder.services.nodes.NodeInfoService import com.normation.rudder.services.reports.ReportingService +import com.normation.rudder.services.reports.ReportingServiceUtils import com.normation.zio.currentTimeMillis import net.liftweb.common.* import net.liftweb.http.LiftResponse @@ -74,6 +75,7 @@ import net.liftweb.http.Req import net.liftweb.json.* import net.liftweb.json.JsonDSL.* import scala.collection.immutable +import zio.ZIO import zio.syntax.* class ComplianceApi( @@ -559,12 +561,35 @@ class ComplianceAPIService( t6 <- currentTimeMillis _ <- TimingDebugLoggerPure.trace(s"getByRulesCompliance - findRuleNodeStatusReports in ${t6 - t5} ms") + reportsByRule = reportsByNode.flatMap { case (_, status) => status.reports }.groupBy(_.ruleId) + t7 = System.currentTimeMillis() + _ <- TimingDebugLoggerPure.trace(s"getByRulesCompliance - group reports by rules in ${t7 - t6} ms") + + // make a map of directive overrides for each rule, to add to the directives of a rule + directiveOverridesByRules = ruleObjects.keys.map { ruleId => + val overridenDirectives = ComplianceOverrides + .getOverridenDirective( + ReportingServiceUtils.buildRuleStatusReport(ruleId, reportsByNode).overrides, + directives + ) + ruleId -> overridenDirectives + }.toMap + + // we need to fetch info for rules pulled from overriden directives of our rules + allRuleObjects <- + ZIO + .foreach( + directiveOverridesByRules.values.toList + .flatMap(_.map(_.overridingRuleId)) + )(rulesRepo.getOpt(_)) + .map(rules => ruleObjects ++ rules.flatten.map(r => (r.id, r)).toMap) + + directivesOverrides = directiveOverridesByRules.view.mapValues(_.map(_.toComplianceByRule(allRuleObjects))) + + t8 <- currentTimeMillis + _ <- TimingDebugLoggerPure.trace(s"getByRulesCompliance - get directive overrides and rules infos in ${t8 - t7} ms") } yield { - val reportsByRule = reportsByNode.flatMap { case (_, status) => status.reports }.groupBy(_.ruleId) - val t7 = System.currentTimeMillis() - TimingDebugLoggerPure.logEffect.trace(s"getByRulesCompliance - group reports by rules in ${t7 - t6} ms") - // for each rule for each node, we want to have a // directiveId -> reporttype map val nonEmptyRules = reportsByRule.toSeq.map { @@ -588,9 +613,10 @@ class ComplianceAPIService( directives.get(directiveId).map(_._2.name).getOrElse("Unknown directive"), ComplianceLevel.sum( nodeDirectives.map(_._2.compliance) - ), // here we want the compliance by components of the directive. - // if level is high enough, get all components and group by their name - { + ), + None, { + // here we want the compliance by components of the directive. + // if level is high enough, get all components and group by their name val byComponents: Map[String, immutable.Iterable[(NodeId, ComponentStatusReport)]] = if (computedLevel < 3) { Map() } else { @@ -638,7 +664,9 @@ class ComplianceAPIService( ) // return the full list - val result = nonEmptyRules ++ initializedCompliances + val singleRuleCompliance = nonEmptyRules ++ initializedCompliances + // add overrides to that result + val result = singleRuleCompliance.map(r => r.copy(directives = r.directives ++ directivesOverrides(r.id))) val t10 = System.currentTimeMillis() TimingDebugLoggerPure.logEffect.trace(s"getByRulesCompliance - Compute result in ${t10 - t9} ms") diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm index 360391d7c3e..bfa72099cc0 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm @@ -155,10 +155,10 @@ type alias DirectiveCompliance value = , name : String , compliance : Float , complianceDetails : ComplianceDetails + , skippedDetails : Maybe SkippedDetails , components : List (ComponentCompliance value) } - type ComponentCompliance value = Block (BlockCompliance value) | Value (ComponentValueCompliance value) type alias BlockCompliance value = { component : String @@ -191,6 +191,11 @@ type alias ValueLine = , status : String } +type alias SkippedDetails = + { overridingRuleId : RuleId + , overridingRuleName : String + } + type alias Report = { status : String , message : Maybe String diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/JsonDecoder.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/JsonDecoder.elm index bc0c09bac74..1c8e8b002bc 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/JsonDecoder.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/JsonDecoder.elm @@ -202,8 +202,15 @@ decodeDirectiveCompliance elem decoder = |> required "name" string |> required "compliance" float |> required "complianceDetails" decodeComplianceDetails + |> optional "skippedDetails" (maybe decodeSkippedDirectiveDetails) Nothing |> required "components" (list (decodeComponentCompliance elem decoder ) ) +decodeSkippedDirectiveDetails : Decoder SkippedDetails +decodeSkippedDirectiveDetails = + succeed SkippedDetails + |> required "overridingRuleId" (map RuleId string) + |> required "overridingRuleName" string + decodeRuleChanges: Decoder (Dict String (List Changes)) decodeRuleChanges = at [ "data" ] (dict (list decodeChanges)) diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm index 81063b8a819..a81aa774c2f 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm @@ -216,7 +216,7 @@ byDirectiveCompliance mod subFun = List.sortWith sortFunction item.components ) (\m i -> (Maybe.withDefault (Directive i.directiveId i.name "" "" "" False False "" []) (Dict.get i.directiveId.value m.directives), i )) - [ ("Directive", \(d,_) -> span [] [ badgePolicyMode globalPolicy d.policyMode, text d.displayName, buildTagsTree d.tags, goToBtn (getDirectiveLink contextPath d.id) ], (\(_,d1) (_,d2) -> N.compare d1.name d2.name )) + [ ("Directive", \(d,c) -> span [] [ c.skippedDetails |> Maybe.map badgeSkipped |> Maybe.withDefault (badgePolicyMode globalPolicy d.policyMode), text d.displayName, buildTagsTree d.tags, goToBtn (getDirectiveLink contextPath d.id) ], (\(_,d1) (_,d2) -> N.compare d1.name d2.name )) , ("Compliance", \(_,i) -> buildComplianceBar i.complianceDetails, (\(_,d1) (_,d2) -> Basics.compare d1.compliance d2.compliance )) ] (.directiveId >> .value) @@ -466,6 +466,13 @@ badgePolicyMode globalPolicyMode policyMode = in span [class ("treeGroupName tooltipable bs-tooltip rudder-label label-sm label-" ++ mode), attribute "data-toggle" "tooltip", attribute "data-placement" "bottom", attribute "data-container" "body", attribute "data-html" "true", attribute "data-original-title" (buildTooltipContent "Policy mode" msg)][] +badgeSkipped : SkippedDetails -> Html Msg +badgeSkipped { overridingRuleId, overridingRuleName } = + let + msg = "This directive is skipped because it is overridden by the rule " ++ overridingRuleName ++ " (with id " ++ overridingRuleId.value ++ ")." + in + span [class "treeGroupName tooltipable bs-tooltip rudder-label label-sm label-overriden", attribute "data-toggle" "tooltip", attribute "data-placement" "bottom", attribute "data-container" "body", attribute "data-html" "true", attribute "data-original-title" (buildTooltipContent "Skipped directive" msg)][] + -- WARNING: -- -- Here the content is an HTML so it need to be already escaped.