Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #22072: Display compliance by directive #4600

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"type": "application",
"source-directories": [
"sources"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"NoRedInk/elm-json-decode-pipeline": "1.0.0",
"TSFoster/elm-tuple-extra": "2.0.0",
"TSFoster/elm-uuid": "4.1.0",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
"elm/random": "1.0.0",
"elm/url": "1.0.0",
"elm-community/dict-extra": "2.4.0",
"elm-community/list-extra": "8.5.1",
"elm-community/maybe-extra": "5.2.1",
"isaacseymour/deprecated-time": "1.0.0",
"mcordova47/elm-natural-ordering": "1.0.5",
"toastal/either": "3.6.3",
"webbhuset/elm-json-decode": "1.1.0",
"myrho/elm-round": "1.0.4"
},
"indirect": {
"TSFoster/elm-bytes-extra": "1.3.0",
"TSFoster/elm-md5": "2.0.1",
"TSFoster/elm-sha1": "2.1.1",
"danfishgold/base64-bytes": "1.1.0",
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/parser": "1.1.0",
"elm/regex": "1.0.0",
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.3",
"kuon/elm-string-normalize": "1.0.2",
"rtfeldman/elm-hex": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module ApiCalls exposing (..)

import DataTypes exposing (..)
import Http exposing (..)
import JsonDecoder exposing (..)
import Url
import Url.Builder exposing (QueryParameter, int, string)


--
-- This files contains all API calls for the Directive compliance UI
--

getUrl: DataTypes.Model -> List String -> List QueryParameter -> String
getUrl m url p=
Url.Builder.relative (m.contextPath :: "secure" :: "api" :: url) p

getPolicyMode : Model -> Cmd Msg
getPolicyMode model =
let
req =
request
{ method = "GET"
, headers = []
, url = getUrl model [ "settings", "global_policy_mode" ] []
, body = emptyBody
, expect = expectJson GetPolicyModeResult decodeGetPolicyMode
, timeout = Nothing
, tracker = Nothing
}
in
req

getAllRules : Model -> Cmd Msg
getAllRules model =
let
req =
request
{ method = "GET"
, headers = []
, url = getUrl model [ "rules" ] []
, body = emptyBody
, expect = expectJson GetRulesList decodeGetRules
, timeout = Nothing
, tracker = Nothing
}
in
req

getNodesList : Model -> Cmd Msg
getNodesList model =
let
req =
request
{ method = "GET"
, headers = []
, url = getUrl model ["nodes"] []
, body = emptyBody
, expect = expectJson GetNodesList decodeGetNodesList
, timeout = Nothing
, tracker = Nothing
}
in
req

getDirectiveCompliance : Model -> Cmd Msg
getDirectiveCompliance model =
let
req =
request
{ method = "GET"
, headers = []
, url = getUrl model [ "compliance", "directives", model.directiveId.value ] []
, body = emptyBody
, expect = expectJson GetDirectiveComplianceResult decodeGetDirectiveCompliance
, timeout = Nothing
, tracker = Nothing
}
in
req
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
module ComplianceUtils exposing (..)

import DataTypes exposing (..)
import Dict
import Dict.Extra
import Html exposing (Html, button, div, i, span, text, h1, h4, ul, li, input, a, p, form, label, textarea, select, option, table, thead, tbody, tr, th, td, small)
import Html.Attributes exposing (id, class, type_, placeholder, value, for, href, colspan, rowspan, style, selected, disabled, attribute)
import Html.Events exposing (onClick, onInput)
import List.Extra
import List
import String exposing (fromFloat)
import Tuple exposing (first, second)
import ApiCalls exposing (..)

getValueCompliance : Maybe Float -> Float
getValueCompliance f =
case f of
Just v -> v
Nothing -> 0

getAllComplianceValues : ComplianceDetails ->
{ okStatus : {value : Float, rounded : Int, details : String}
, nonCompliant : {value : Float, rounded : Int, details : String}
, error : {value : Float, rounded : Int, details : String}
, unexpected : {value : Float, rounded : Int, details : String}
, pending : {value : Float, rounded : Int, details : String}
, reportsDisabled : {value : Float, rounded : Int, details : String}
, noReport : {value : Float, rounded : Int, details : String}
}
getAllComplianceValues complianceDetails =
let
barContent : List (Float, String) -> String
barContent lst =
let
content = lst
|> List.map (\x ->
if first x > 0 then
"<li>" ++ second x ++ ": " ++ String.fromFloat (first x) ++ "%</li>"
else
""
)
in
String.join "" (List.append ["</ul>"] ("<ul>" :: content))

valSuccessNotApplicable = getValueCompliance complianceDetails.successNotApplicable -- 0
valSuccessAlreadyOK = getValueCompliance complianceDetails.successAlreadyOK -- 0
valSuccessRepaired = getValueCompliance complianceDetails.successRepaired -- 0
valAuditCompliant = getValueCompliance complianceDetails.auditCompliant -- 0
valAuditNotApplicable = getValueCompliance complianceDetails.auditNotApplicable -- 0

valAuditNonCompliant = getValueCompliance complianceDetails.auditNonCompliant -- 1

valError = getValueCompliance complianceDetails.error -- 2
valAuditError = getValueCompliance complianceDetails.auditError -- 2

valUnexpectedUnknownComponent = getValueCompliance complianceDetails.unexpectedUnknownComponent -- 3
valUnexpectedMissingComponent = getValueCompliance complianceDetails.unexpectedMissingComponent -- 3
valBadPolicyMode = getValueCompliance complianceDetails.badPolicyMode -- 3

valApplying = getValueCompliance complianceDetails.applying -- 4

valReportsDisabled = getValueCompliance complianceDetails.reportsDisabled -- 5

valNoReport = getValueCompliance complianceDetails.noReport -- 6

okStatus = valSuccessNotApplicable + valSuccessAlreadyOK + valSuccessRepaired + valAuditCompliant + valAuditNotApplicable
nonCompliant = valAuditNonCompliant
error = valError + valAuditError
unexpected = valUnexpectedUnknownComponent + valUnexpectedMissingComponent + valBadPolicyMode
pending = valApplying
reportsDisabled = valReportsDisabled
noReport = valNoReport

okStatusText =
if okStatus > 0 then
let
checks =
[ ( valSuccessAlreadyOK , "Success (enforce)" )
, ( valAuditCompliant , "Compliant" )
, ( valSuccessRepaired , "Repaired" )
, ( valSuccessNotApplicable , "Not applicable (enforce)" )
, ( valAuditNotApplicable , "Not applicable (audit)" )
]
content = barContent checks
in
content
else
""

nonCompliantText =
if nonCompliant > 0 then
barContent [( nonCompliant , "Non compliance" )]
else
""

errorText =
if error > 0 then
let
checks =
[ ( valError , "Errors (enforce)" )
, ( valAuditError , "Errors (audit)" )
]
content = barContent checks
in
content
else
""

unexpectedText =
if unexpected > 0 then
let
checks =
[ ( valUnexpectedUnknownComponent , "Unknown reports" )
, ( valUnexpectedMissingComponent , "Missing reports" )
, ( valBadPolicyMode , "Not supported mixed mode on directive from same Technique" )
]
content = barContent checks
in
content
else
""

pendingText =
if pending > 0 then
barContent [( valApplying , "Applying" )]
else
""

reportsDisabledText =
if reportsDisabled > 0 then
barContent [( valReportsDisabled , "Reports Disabled" )]
else
""

noReportText =
if noReport > 0 then
barContent [( valNoReport , "No report" )]
else
""

-- This prevents the sum of the rounded compliances from being greater or less than 100%, using the largest remainder method.
-- 1. Rounding everything down
compliances = Dict.fromList
[ ("okStatus" , { floor = Basics.floor okStatus , decimal = okStatus - toFloat (Basics.floor okStatus )})
, ("nonCompliant" , { floor = Basics.floor nonCompliant , decimal = nonCompliant - toFloat (Basics.floor nonCompliant )})
, ("error" , { floor = Basics.floor error , decimal = error - toFloat (Basics.floor error )})
, ("unexpected" , { floor = Basics.floor unexpected , decimal = unexpected - toFloat (Basics.floor unexpected )})
, ("pending" , { floor = Basics.floor pending , decimal = pending - toFloat (Basics.floor pending )})
, ("reportsDisabled" , { floor = Basics.floor reportsDisabled , decimal = reportsDisabled - toFloat (Basics.floor reportsDisabled )})
, ("noReport" , { floor = Basics.floor noReport , decimal = noReport - toFloat (Basics.floor noReport )})
]
-- 2. Getting the difference in sum and 100
getDiff = 100 - ( compliances
|> Dict.map (\k a -> a.floor)
|> Dict.values
|> List.sum
)
-- 3. Distributing the difference by adding 1 to items in decreasing order of their decimal parts
flippedComparison a b =
case compare (Tuple.second a).decimal (Tuple.second b).decimal of
LT -> GT
EQ -> EQ
GT -> LT
getNumbersToUpdate = compliances
|> Dict.toList
|> List.sortWith flippedComparison
|> List.take getDiff
|> List.map (\x -> (Tuple.first x))
roundedCompliance = compliances
|> Dict.map (\k a -> if (List.member k getNumbersToUpdate) then {a | floor = a.floor+1} else a )
getRoundedValue : String -> Float -> Int
getRoundedValue key fallback =
let
roundedValue =
case ( roundedCompliance
|> Dict.get key ) of
Just v -> v.floor
Nothing -> Basics.round fallback
in
roundedValue

allComplianceValues =
{ okStatus = { value = okStatus , rounded = (getRoundedValue "okStatus" okStatus ), details = okStatusText }
, nonCompliant = { value = nonCompliant , rounded = (getRoundedValue "nonCompliant" nonCompliant ), details = nonCompliantText }
, error = { value = error , rounded = (getRoundedValue "error" error ), details = errorText }
, unexpected = { value = unexpected , rounded = (getRoundedValue "unexpected" unexpected ), details = unexpectedText }
, pending = { value = pending , rounded = (getRoundedValue "pending" pending ), details = pendingText }
, reportsDisabled = { value = reportsDisabled , rounded = (getRoundedValue "reportsDisabled" reportsDisabled ), details = reportsDisabledText }
, noReport = { value = noReport , rounded = (getRoundedValue "noReport" noReport ), details = noReportText }
}
in
allComplianceValues
Loading