diff --git a/css/backoffice/application/display-block/_block-csv.scss b/css/backoffice/application/display-block/_block-csv.scss index d0b11ec80..d94d02618 100644 --- a/css/backoffice/application/display-block/_block-csv.scss +++ b/css/backoffice/application/display-block/_block-csv.scss @@ -1,6 +1,6 @@ $ibo-block-csv--textarea--width: 100% !default; $ibo-block-csv--textarea--min-height: 10em !default; -$ibo-block-csv--textarea--margin-top: 10em !default; +$ibo-block-csv--textarea--margin-top: 10px !default; .ibo-block-csv{ textarea{ diff --git a/css/backoffice/pages/_all.scss b/css/backoffice/pages/_all.scss index 21d8e7e2b..2b532ff24 100644 --- a/css/backoffice/pages/_all.scss +++ b/css/backoffice/pages/_all.scss @@ -20,4 +20,5 @@ @import "preferences"; @import "attachments"; @import "tabularfieldsselector"; -@import "impact-analysis"; \ No newline at end of file +@import "impact-analysis"; +@import "audit"; \ No newline at end of file diff --git a/css/backoffice/pages/_audit.scss b/css/backoffice/pages/_audit.scss new file mode 100644 index 000000000..078f4f0e1 --- /dev/null +++ b/css/backoffice/pages/_audit.scss @@ -0,0 +1,64 @@ +$ibo-audit--audit-rules--row--last-rows--text-align: right !default; +$ibo-audit--audit-rules--row--last-rows--width: 100px !default; + +$ibo-audit--audit-category--panel--body--padding-y: 10px !default; +$ibo-audit--audit-category--panel--body--padding-x: $ibo-panel--body--padding-x !default; + +$ibo-audit--dashboard--padding-y: 18px !default; +$ibo-audit--dashboard--padding-x: 0 !default; + +$ibo-audit--audit-line--csv-download--height: 2.5em !default; + +$ibo-audit--audit-line--status-indicator--diameter: 12px !default; +$ibo-audit--audit-line--status-indicator--margin-right: 5px !default; + + +$ibo-audit--status--color: ( + 'red': ( + $ibo-color-red-700, + ), + 'orange': ( + $ibo-color-orange-700, + ), + 'green': ( + $ibo-color-green-800, + ), +); +@each $sColor, $aAttributes in $ibo-audit--status--color { + $bg-color: nth($aAttributes, 1); + .ibo-audit--audit-category--panel .ibo-panel--body { + tr.ibo-is-#{$sColor} td:last-of-type:before { + background-color: $bg-color; + } + } +} + +.ibo-audit--audit-category--panel .ibo-panel--body{ + padding: $ibo-audit--audit-category--panel--body--padding-y $ibo-panel--body--padding-x; + + .ibo-datatable{ + td:not(:nth-child(1)), th:not(:nth-child(1)){ + text-align: $ibo-audit--audit-rules--row--last-rows--text-align; + width: $ibo-audit--audit-rules--row--last-rows--width; + } + } + .ibo-datatable-toolbar{ + display: none; + } + tr td:last-of-type:before{ + content: ''; + height: $ibo-audit--audit-line--status-indicator--diameter; + width: $ibo-audit--audit-line--status-indicator--diameter; + border-radius: $ibo-border-radius-full; + display: inline-block; + margin-right: $ibo-audit--audit-line--status-indicator--margin-right; + vertical-align: middle; + } +} +.ibo-audit--dashboard{ + padding: $ibo-audit--dashboard--padding-y $ibo-audit--dashboard--padding-x; +} +.ibo-audit--audit-line--csv-download{ + height: $ibo-audit--audit-line--csv-download--height; + vertical-align: middle; +} \ No newline at end of file diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 2014f8a70..1f0f3b29b 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -697,6 +697,12 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Audit:PercentageOk' => '% Ok', 'UI:Audit:ErrorIn_Rule_Reason' => 'OQL Error in the Rule %1$s: %2$s.', 'UI:Audit:ErrorIn_Category_Reason' => 'OQL Error in the Category %1$s: %2$s.', + 'UI:Audit:AuditError' => 'Audit Interactif', + 'UI:Audit:Dashboard:ObjectsAudited' => 'Objects audited', + 'UI:Audit:Dashboard:ObjectsInError' => 'Objects in errors', + 'UI:Audit:Dashboard:ObjectsValidated' => 'Objects validated', + 'UI:Audit:AuditCategory:Subtitle' => '%1$s errors ouf of %2$s - %3$s%%', + 'UI:RunQuery:Title' => ITOP_APPLICATION_SHORT.' - OQL Query Evaluation', 'UI:RunQuery:QueryExamples' => 'Query Examples', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 0cf516408..0e73c7f11 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -669,7 +669,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:CSVExport:LostChars' => 'Problème d\'encodage', 'UI:CSVExport:LostChars+' => 'Le fichier téléchargé sera encodé en %1$s. iTop a détecté des caractères incompatible avec ce format. Ces caractères seront soit remplacés par des caractères de substitution (par exemple: \'é\' transformé en \'e\'), soit perdus. Vous pouvez utiliser le copier/coller depuis votre navigateur web, ou bien contacter votre administrateur pour que l\'encodage corresponde mieux à votre besoin (Cf. paramètre \'csv_file_default_charset\').', - 'UI:Audit:Title' => 'iTop - Audit de la CMDB', + 'UI:Audit:Title' => ITOP_APPLICATION_SHORT. ' - Audit de la CMDB', 'UI:Audit:InteractiveAudit' => 'Audit Interactif', 'UI:Audit:HeaderAuditRule' => 'Règle d\'audit', 'UI:Audit:HeaderNbObjects' => 'Nb d\'Objets', diff --git a/pages/audit.php b/pages/audit.php index 1c271d16c..a603359fc 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -17,6 +17,15 @@ * You should have received a copy of the GNU Affero General Public License */ +use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletContainer; +use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory; +use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Panel\Panel; +use Combodo\iTop\Application\UI\Base\Component\Title\Title; +use Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardColumn; +use Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardRow; + /** * Adds the context parameters to the audit rule query * @@ -226,23 +235,30 @@ try } else { - $oP->add(''); - $oP->p('[Back to audit results]'); + $sTitle = Dict::S('UI:Audit:AuditError'); + $oP->SetBreadCrumbEntry('ui-tool-auditerrors', $sTitle, '', '', 'fas fa-stethoscope', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); + + $oBackButton = ButtonUIBlockFactory::MakeIconLink('fas fa-chevron-left', Dict::S('Back to audit results'), "./audit.php?".$oAppContext->GetForLink()); + $oP->AddUiBlock($oBackButton); + $oP->AddUiBlock(new Title($sTitle . $oAuditRule->Get('description'))); + $sBlockId = 'audit_errors'; - $oP->p("
\n"); + $oP->p("
"); $oBlock = DisplayBlock::FromObjectSet($oErrorObjectSet, 'csv', array('show_obsolete_data' => true)); $oBlock->Display($oP, 1); - $oP->p("
\n"); + $oP->p("
"); // Adjust the size of the Textarea containing the CSV to fit almost all the remaining space $oP->add_ready_script(" $('#1>textarea').height(400);"); // adjust the size of the block $sExportUrl = utils::GetAbsoluteUrlAppRoot()."pages/audit.php?operation=csv&category=".$oAuditCategory->GetKey()."&rule=".$oAuditRule->GetKey(); + $oDownloadButton = ButtonUIBlockFactory::MakeForAlternativePrimaryAction('fas fa-chevron-left', Dict::S('Back to audit results'), "./audit.php?".$oAppContext->GetForLink()); + $oP->add_ready_script("$('a[href*=\"webservices/export.php?expression=\"]').attr('href', '".$sExportUrl."&filename=audit.csv".$sAdvanced."');"); $oP->add_ready_script("$('#1 :checkbox').removeAttr('onclick').click( function() { var sAdvanced = ''; if (this.checked) sAdvanced = '&advanced=1'; window.location.href='$sExportUrl'+sAdvanced; } );"); } break; case 'errors': - $sTitle = 'Audit Errors'; + $sTitle = Dict::S('UI:Audit:AuditError'); $oP->SetBreadCrumbEntry('ui-tool-auditerrors', $sTitle, '', '', 'fas fa-stethoscope', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); $iCategory = utils::ReadParam('category', ''); $iRuleIndex = utils::ReadParam('rule', 0); @@ -255,13 +271,14 @@ try $oFilter = GetRuleResultFilter($iRuleIndex, $oDefinitionFilter, $oAppContext); $oErrorObjectSet = new CMDBObjectSet($oFilter); $oAuditRule = MetaModel::GetObject('AuditRule', $iRuleIndex); - $oP->add(''); - $oP->p('[Back to audit results]'); + $oBackButton = ButtonUIBlockFactory::MakeIconLink('fas fa-chevron-left', Dict::S('Back to audit results'), "./audit.php?".$oAppContext->GetForLink()); + $oP->AddUiBlock($oBackButton); + $oP->AddUiBlock(new Title($sTitle . $oAuditRule->Get('description'))); $sBlockId = 'audit_errors'; - $oP->p("
\n"); + $oP->p("
"); $oBlock = DisplayBlock::FromObjectSet($oErrorObjectSet, 'list', array('show_obsolete_data' => true)); $oBlock->Display($oP, 1); - $oP->p("
\n"); + $oP->p("
"); $sExportUrl = utils::GetAbsoluteUrlAppRoot()."pages/audit.php?operation=csv&category=".$oAuditCategory->GetKey()."&rule=".$oAuditRule->GetKey(); $oP->add_ready_script("$('a[href*=\"pages/UI.php?operation=search\"]').attr('href', '".$sExportUrl."')"); break; @@ -269,15 +286,42 @@ try case 'audit': default: $oP->SetBreadCrumbEntry('ui-tool-audit', Dict::S('Menu:Audit'), Dict::S('UI:Audit:InteractiveAudit'), '', 'fas fa-stethoscope', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); - $oP->add(''); + $oP->AddUiBlock(new Title(Dict::S('UI:Audit:InteractiveAudit'))); + $oTotalBlock = DashletFactory::MakeForDashletBadge('../images/icons/icons8-audit.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsAudited')); + $oErrorBlock = DashletFactory::MakeForDashletBadge('../images/icons/icons8-delete.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsInError')); + $oWorkingBlock = DashletFactory::MakeForDashletBadge('../images/icons/icons8-checkmark.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsValidated')); + + $aCSSClasses = ['ibo-dashlet--is-inline','ibo-dashlet-badge']; + + $oDashletContainerTotal = new DashletContainer(); + $oDashletContainerError = new DashletContainer(); + $oDashletContainerWorking = new DashletContainer(); + + $oDashletContainerTotal->AddSubBlock($oTotalBlock)->AddCSSClasses($aCSSClasses); + $oDashletContainerError->AddSubBlock($oErrorBlock)->AddCSSClasses($aCSSClasses); + $oDashletContainerWorking->AddSubBlock($oWorkingBlock)->AddCSSClasses($aCSSClasses); + + $oDashboardRow = new DashboardRow(); + + $oDashboardColumnTotal = new DashboardColumn(false, true); + $oDashboardColumnError = new DashboardColumn(false, true); + $oDashboardColumnWorking = new DashboardColumn(false, true); + + $oDashboardColumnTotal->AddUIBlock($oDashletContainerTotal); + $oDashboardColumnError->AddUIBlock($oDashletContainerError); + $oDashboardColumnWorking->AddUIBlock($oDashletContainerWorking); + + $oDashboardRow->AddDashboardColumn($oDashboardColumnTotal); + $oDashboardRow->AddDashboardColumn($oDashboardColumnError); + $oDashboardRow->AddDashboardColumn($oDashboardColumnWorking); + + $oDashboardRow->AddCSSClass('ibo-audit--dashboard'); + + $oP->AddUiBlock($oDashboardRow); + $oAuditFilter = new DBObjectSearch('AuditCategory'); $oCategoriesSet = new DBObjectSet($oAuditFilter); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); + while($oAuditCategory = $oCategoriesSet->fetch()) { try @@ -323,7 +367,7 @@ try { $aObjectsWithErrors[$aErrorRow['id']] = true; } - $aRow['nb_errors'] = ($iErrorsCount == 0) ? '0' : "GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">$iErrorsCountGetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">(CSV)"; + $aRow['nb_errors'] = ($iErrorsCount == 0) ? '0' : "GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">$iErrorsCountGetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">"; $aRow['percent_ok'] = sprintf('%.2f', 100.0 * (($iCount - $iErrorsCount) / $iCount)); $aRow['class'] = GetReportColor($iCount, $iErrorsCount); } @@ -350,26 +394,45 @@ try $aRow['percent_ok'] = ''; $aRow['class'] = 'red'; $sMessage = Dict::Format('UI:Audit:ErrorIn_Category_Reason', $oAuditCategory->GetHyperlink(), utils::HtmlEntities($e->getMessage())); - $oP->p(" ".$sMessage); + $oP->p(" ".$sMessage); $aResults[] = $aRow; $sClass = 'red'; $iTotalErrors = 'n/a'; $sOverallPercentOk = ''; } - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); + + $oTotalBlock->SetCount((int)$oTotalBlock->GetCount() + ($iCount)); + $oErrorBlock->SetCount((int)$oErrorBlock->GetCount() + $iTotalErrors); + $oWorkingBlock->SetCount((int)$oWorkingBlock->GetCount() + ($iCount - $iTotalErrors)); + + $oAuditCategoryPanelBlock = new Panel($oAuditCategory->GetName()); + $oAuditCategoryPanelBlock->SetIsCollapsible(true); + $oAuditCategoryPanelBlock->SetSubTitle(Dict::Format('UI:Audit:AuditCategory:Subtitle', $iTotalErrors, $iCount, $sOverallPercentOk)); + $oAuditCategoryPanelBlock->SetColor($sClass); + $oAuditCategoryPanelBlock->AddCSSClass('ibo-audit--audit-category--panel'); + $aData = []; foreach($aResults as $aRow) { - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); + $aData[] = array( + 'audit_rule' => $aRow['description'], + 'nb_err' => $aRow['nb_errors'], + 'percentage_ok' => $aRow['percent_ok'], + '@class' => 'ibo-is-'.$aRow['class'].'', + ); } + + $aAttribs = array( + 'audit_rule' => array('label' => Dict::S('UI:Audit:HeaderAuditRule'), 'description' => Dict::S('UI:Audit:HeaderAuditRule')), + 'nb_err' => array('label' => Dict::S('UI:Audit:HeaderNbErrors'), 'description' => Dict::S('UI:Audit:HeaderNbErrors')), + 'percentage_ok' => array('label' => Dict::S('UI:Audit:PercentageOk'), 'description' => Dict::S('UI:Audit:PercentageOk')), + ); + + $oAttachmentTableBlock = DataTableUIBlockFactory::MakeForStaticData('', $aAttribs, $aData); + $oAuditCategoryPanelBlock->AddSubBlock($oAttachmentTableBlock); + $oP->AddUiBlock($oAuditCategoryPanelBlock); } - $oP->add("
".Dict::S('UI:Audit:HeaderAuditRule')."".Dict::S('UI:Audit:HeaderNbObjects')."".Dict::S('UI:Audit:HeaderNbErrors')."".Dict::S('UI:Audit:PercentageOk')."
".$oAuditCategory->GetName()."$iCount$iTotalErrors$sOverallPercentOk %
 ".$aRow['description']."".$aRow['nb_errors']."".$aRow['percent_ok']." %
\n"); - $oP->add("
\n"); + } $oP->output(); }