diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/policies/DependencyAndDeletionService.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/policies/DependencyAndDeletionService.scala index 8f92422c0d1..b5044d96537 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/policies/DependencyAndDeletionService.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/policies/DependencyAndDeletionService.scala @@ -250,8 +250,8 @@ class DependencyAndDeletionServiceImpl( } } - // group by target, and check if target status is enable - // if the target is disable, we can't change the rule status anyhow + // group by target, and check if target status is enabled + // if the target is disabled, we can't change the rule status anyhow ZIO .foreach(switchableCr) { case (rule, targets) => diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/DirectiveEditForm.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/DirectiveEditForm.scala index 99b10a96849..dec38161958 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/DirectiveEditForm.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/DirectiveEditForm.scala @@ -97,7 +97,8 @@ class DirectiveEditForm( onSuccessCallback: (Either[Directive, ChangeRequestId]) => JsCmd, onMigrationCallback: (Directive, Option[Directive]) => JsCmd, onFailureCallback: () => JsCmd = { () => Noop }, - onRemoveSuccessCallBack: () => JsCmd = { () => Noop } + onRemoveSuccessCallBack: () => JsCmd = { () => Noop }, + displayTechniqueDetails: ActiveTechniqueId => JsCmd = { _ => Noop } ) extends DispatchSnippet with Loggable { import DirectiveEditForm.* @@ -206,7 +207,7 @@ class DirectiveEditForm( ).display } - val versionSelect = if (isADirectiveCreation) { + val versionSelect = if (isADirectiveCreation) {
} else { directiveVersion.toForm_! } - val currentVersion = showDeprecatedVersion(directive.techniqueVersion) + val currentVersion = showDeprecatedVersion(directive.techniqueVersion) // It is always a Full, but in case add a warning - val versionSelectId = directiveVersion.uniqueFieldId match { + val versionSelectId = directiveVersion.uniqueFieldId match { case Full(id) => id case _ => logger.warn("could not find id for migration select version") "id_not_found" } + val (disableMessage, enableBtn) = (activeTechnique.isEnabled, directive._isEnabled) match { case (false, false) => ( "This Directive and its Technique are disabled.", {SHtml.ajaxSubmit("Enable Directive", () => onSubmitDisable(DGModAction.Enable), ("class", "btn btn-sm btn-default"))} - Edit Technique + { + SHtml.ajaxSubmit( + "Enable Technique", + () => displayTechniqueDetails(activeTechnique.id), + ("class", "btn btn-sm btn-default") + ) + } ) case (false, true) => ( "The Technique of this Directive is disabled.", - Edit Technique + SHtml.ajaxSubmit( + "Enable Technique", + () => displayTechniqueDetails(activeTechnique.id), + ("class", "btn btn-sm btn-default") + ) ) case (true, false) => ( diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayDirectiveTree.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayDirectiveTree.scala index 5c916d97e29..c3695a55eb7 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayDirectiveTree.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayDirectiveTree.scala @@ -130,7 +130,7 @@ object DisplayDirectiveTree extends Loggable { usedDirectiveIds: Seq[(DirectiveUid, Int)], onClickCategory: Option[FullActiveTechniqueCategory => JsCmd], - onClickTechnique: Option[(FullActiveTechniqueCategory, FullActiveTechnique) => JsCmd], + onClickTechnique: Option[FullActiveTechnique => JsCmd], onClickDirective: Option[(FullActiveTechniqueCategory, FullActiveTechnique, Directive) => JsCmd], createDirective: Option[(Technique, FullActiveTechnique) => JsCmd], addEditLink: Boolean, @@ -146,8 +146,6 @@ object DisplayDirectiveTree extends Loggable { nodeId: String ): JsTreeNode = new JsTreeNode { - private val localOnClickTechnique = onClickTechnique.map(_.curried(category)) - private val localOnClickDirective = onClickDirective.map(_.curried(category)) private val tooltipContent = s""" @@ -190,7 +188,7 @@ object DisplayDirectiveTree extends Loggable { }) // We want our technique sorty by human name, default to bundle name in case we don't have any version but that should not happen .sortBy(at => at.newestAvailableTechnique.map(_.name).getOrElse(at.techniqueName.value)) - .map(at => displayActiveTechnique(at, localOnClickTechnique, localOnClickDirective)) + .map(at => displayActiveTechnique(at, onClickTechnique, localOnClickDirective)) ) override val attrs = diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/configuration/DirectiveManagement.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/configuration/DirectiveManagement.scala index fa25eb3817e..2b03605b7a4 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/configuration/DirectiveManagement.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/configuration/DirectiveManagement.scala @@ -47,7 +47,9 @@ import com.normation.cfclerk.domain.TechniqueId import com.normation.cfclerk.domain.TechniqueVersion import com.normation.errors import com.normation.eventlog.ModificationId +import com.normation.rudder.domain.logger.ApplicationLogger import com.normation.rudder.domain.policies.ActiveTechnique +import com.normation.rudder.domain.policies.ActiveTechniqueId import com.normation.rudder.domain.policies.Directive import com.normation.rudder.domain.policies.DirectiveId import com.normation.rudder.domain.policies.DirectiveUid @@ -56,12 +58,17 @@ import com.normation.rudder.domain.policies.Rule import com.normation.rudder.domain.workflows.ChangeRequestId import com.normation.rudder.repository.FullActiveTechnique import com.normation.rudder.repository.FullActiveTechniqueCategory +import com.normation.rudder.services.policies.DontCare import com.normation.rudder.users.CurrentUser import com.normation.rudder.web.components.DirectiveEditForm +import com.normation.rudder.web.components.DisplayColumn +import com.normation.rudder.web.components.RuleGrid import com.normation.rudder.web.services.AgentCompat import com.normation.rudder.web.services.DisplayDirectiveTree import com.normation.utils.DateFormaterService import com.normation.zio.* +import enumeratum.Enum +import enumeratum.EnumEntry import net.liftweb.common.* import net.liftweb.common.Box.* import net.liftweb.http.* @@ -98,10 +105,12 @@ class DirectiveManagement extends DispatchSnippet with Loggable { private val techniqueRepository = RudderConfig.techniqueRepository private val getDirectiveLib = () => RudderConfig.roDirectiveRepository.getFullDirectiveLibrary() private val getRules = () => RudderConfig.roRuleRepository.getAll() + private val getGroups = () => RudderConfig.roNodeGroupRepository.getFullGroupLibrary() private val uuidGen = RudderConfig.stringUuidGenerator private val linkUtil = RudderConfig.linkUtil private val configService = RudderConfig.configService private val configRepo = RudderConfig.configurationRepository + private val dependencyService = RudderConfig.dependencyAndDeletionService def dispatch: PartialFunction[String, NodeSeq => NodeSeq] = { case "head" => { _ => head() } @@ -259,109 +268,143 @@ class DirectiveManagement extends DispatchSnippet with Loggable { case _ =>
} - def initTechniqueDetails(): MemoizeTransform = SHtml.memoize { - "#techniqueDetails *" #> (currentTechnique match { - case None => - ".main-header [class+]" #> "no-header" & - "#details *" #> { -
- -
-

Directives

-

A directive is an instance of a technique, which allows to set values for the parameters of the latter.

-

Each directive can have a unique name, and should be completed with a short and a long description, and a collection of parameters for the variables defined by the technique.

-

Techniques are often available in several versions, numbered X.Y, X being the major version number and Y the minor version number:

-
    -
  1. Bugs are fixed in all existing versions of Rudder techniques. Make sure you update your Rudder packages frequently.
  2. -
  3. A new minor technique version is created for any new features
  4. -
  5. A new major version is created for any architectural change (such as refactoring)
  6. -
-

You can find your own techniques written in the technique editor in the User Techniques category.

-
-
- } - - case Some((fullActiveTechnique, version)) => - fullActiveTechnique.techniques.get(version) match { + def initTechniqueDetails(): MemoizeTransform = { + SHtml.memoize { + "#techniqueDetails *" #> ( + currentTechnique match { case None => - val m = s"There was an error when trying to read version ${version.debugString} of the technique." + - "Please check if that version exists on the filesystem and is correctly registered in the technique library." - - logger.error(m) - - "*" #> { -
-
-

{m}

-
-
+ ".main-header [class+]" #> "no-header" & + "#details *" #> { +
+ +
+

Directives

+

A directive is an instance of a technique, which allows to set values for the parameters of the latter.

+

Each directive can have a unique name, and should be completed with a short and a long description, and a collection of parameters for the variables defined by the technique.

+

Techniques are often available in several versions, numbered X.Y, X being the major version number and Y the minor version number:

+
    +
  1. + Bugs + are fixed in all existing versions of Rudder techniques. Make sure you update your Rudder packages frequently.
  2. +
  3. A new + minor + technique version is created for any new features
  4. +
  5. A new + major + version is created for any + architectural change + (such as refactoring)
  6. +
+

You can find your own techniques written in the technique editor in the + User Techniques + category.

+
+
} - case Some(technique) => - /* - * We want to filter technique to only show the one - * with registered acceptation date time. - * Also sort by version, reverse - */ - - val validTechniqueVersions = fullActiveTechnique.techniques.map { - case (v, t) => - fullActiveTechnique.acceptationDatetimes.get(v) match { - case Some(timeStamp) => Some((v, t, timeStamp)) - case None => - logger.error( - "Inconsistent technique version state for technique with ID '%s' and its version '%s': ".format( - fullActiveTechnique.techniqueName, - v.debugString - ) + - "that version was not correctly registered into Rudder and can not be use for now." - ) - logger.info( - "A workaround is to remove that version manually from Rudder (move the directory for that version of the technique out " + - "of your configuration-repository directory (for example in /tmp) and 'git commit' the modification), " + - "reload the technique library, then add back the version back (move it back at its place, 'git add' the directory, 'git commit' the" + - "modification), and reload the technique library again." - ) + case Some((fullActiveTechnique, version)) => + fullActiveTechnique.techniques.get(version) match { + case None => + val m = s"There was an error when trying to read version ${version.debugString} of the technique." + + "Please check if that version exists on the filesystem and is correctly registered in the technique library." + + logger.error(m) + + "*" #> { +
+
+

+ {m} +

+
+
+ } - None + case Some(technique) => + /* + * We want to filter technique to only show the one + * with registered acceptation date time. + * Also sort by version, reverse + */ + + val validTechniqueVersions = fullActiveTechnique.techniques.map { + case (v, t) => + fullActiveTechnique.acceptationDatetimes.get(v) match { + case Some(timeStamp) => Some((v, t, timeStamp)) + case None => + logger.error( + "Inconsistent technique version state for technique with ID '%s' and its version '%s': ".format( + fullActiveTechnique.techniqueName, + v.debugString + ) + + "that version was not correctly registered into Rudder and can not be use for now." + ) + logger.info( + "A workaround is to remove that version manually from Rudder (move the directory for that version of the technique out " + + "of your configuration-repository directory (for example in /tmp) and 'git commit' the modification), " + + "reload the technique library, then add back the version back (move it back at its place, 'git add' the directory, 'git commit' the" + + "modification), and reload the technique library again." + ) + + None + } + }.toSeq.flatten.sortBy(_._1) + ".main-container [class-]" #> "no-header" & + "#directiveIntro " #> { + currentDirectiveSettingForm.get.map(piForm => (".directive *" #> piForm.directive.name)) + } & + "#techniqueName" #> + {technique.name}{ + if (fullActiveTechnique.isEnabled) NodeSeq.Empty + else } - }.toSeq.flatten.sortBy(_._1) - ".main-container [class-]" #> "no-header" & - "#directiveIntro " #> { - currentDirectiveSettingForm.get.map(piForm => (".directive *" #> piForm.directive.name)) - } & - "#techniqueName" #> {technique.name} { - if (fullActiveTechnique.isEnabled) NodeSeq.Empty - else - } & - "#techniqueID *" #> technique.id.name.value & - "#techniqueDocumentation [class]" #> (if (technique.longDescription.isEmpty) "visually-hidden" else "") & - "#techniqueLongDescription *" #> Script( - JsRaw( - s"""generateMarkdown(${Str(technique.longDescription).toJsCmd}, "#techniqueLongDescription");""" - ) // JsRaw ok, escaped - ) & - "#techniqueDescription" #> technique.description & - "#isSingle *" #> showIsSingle(technique) & - "#isDisabled" #> { - if (!fullActiveTechnique.isEnabled) { -
- - This Technique is disabled. - Edit Technique -
- } else NodeSeq.Empty - } & - "#techniqueversion-app *+" #> showVersions(fullActiveTechnique, validTechniqueVersions) +
& + "#techniqueID *" #> technique.id.name.value & + "#techniqueDocumentation [class]" #> (if (technique.longDescription.isEmpty) "visually-hidden" else "") & + "#techniqueLongDescription *" #> Script( + JsRaw( + s"""generateMarkdown(${Str(technique.longDescription).toJsCmd}, "#techniqueLongDescription");""" + ) // JsRaw ok, escaped + ) & + "#techniqueDescription" #> technique.description & + "#isSingle *" #> showIsSingle(technique) & + "#isDisabled" #> { + def showPopup(nextStatus: NextStatus): JsCmd = { + SetHtml( + "showTechniqueValidationPopup", + showTechniquePopup("showTechniqueValidationPopup", fullActiveTechnique, nextStatus) + ) + } + + if (!fullActiveTechnique.isEnabled) { +
+ + This Technique is disabled. + { + SHtml.ajaxButton("Enable", () => showPopup(NextStatus.Enabled), ("class", "btn btn-sm btn-default mx-2")) + } +
+ } else { +
+ + This Technique is enabled. + { + SHtml.ajaxButton("Disable", () => showPopup(NextStatus.Disabled), ("class", "btn btn-sm btn-default mx-2")) + } +
+ } + } & + "#techniqueversion-app *+" #> showVersions(fullActiveTechnique, validTechniqueVersions) + } } - }) + ) + } } + private val (monoMessage, multiMessage, limitedMessage) = { ( "A unique directive derived from that technique can be deployed on a given server.", @@ -483,6 +526,95 @@ class DirectiveManagement extends DispatchSnippet with Loggable { ) } + // validation / warning pop-up when enabling a technique + + def showTechniquePopup(popupId: String, technique: FullActiveTechnique, nextStatus: NextStatus): NodeSeq = { + def closePopup(): JsCmd = SetHtml(popupId, NodeSeq.Empty) + + dependencyService + .techniqueDependencies(technique.id, getGroups().toBox, DontCare) + .map(_.rules.values) match { + case box: EmptyBox => + box match { + case Empty => // no more log + case f: Failure => ApplicationLogger.error(f.msg) + } +
+ Error when trying to retrieve information about technique status change. Please contact your administrator. +
+ + case Full(rules) => + val showDependentRules = { + if (rules.size <= 0) NodeSeq.Empty + else { + val noDisplay = DisplayColumn.Force(display = false) + val cmp = new RuleGrid( + "technique_status_popup_grid", + None, + showCheckboxColumn = false, + directiveApplication = None, + columnCompliance = noDisplay, + graphRecentChanges = noDisplay + ) + cmp.rulesGridWithUpdatedInfo(Some(rules.toSeq), showActionsColumn = false, isPopup = true) + } + } + + ( + "#validationForm" #> { (xml: NodeSeq) => SHtml.ajaxForm(xml) } andThen + "#changeStatus" #> (SHtml.ajaxSubmit( + nextStatus.action, + () => + DirectiveManagement.setEnabled( + technique.id, + technique.techniqueName.value, + nextStatus.booleanValue, + () => { + (closePopup() & updateDirectiveLibrary() & onClickActiveTechnique( + technique.copy(isEnabled = nextStatus.booleanValue) + )) + } + ), + ("class" -> "btn-danger") + ) % ("id" -> "createDirectiveSaveButton") % ("tabindex" -> "3")) + )(
+ + +
) + } + } + ///////////// finish migration pop-up /////////////// private def displayFinishMigrationPopup: JsCmd = { JsRaw(""" callPopupWithTimeout(200,"finishMigrationPopup") """) // JsRaw ok, const @@ -668,7 +800,8 @@ class DirectiveManagement extends DispatchSnippet with Loggable { isADirectiveCreation = isADirectiveCreation, onSuccessCallback = directiveEditFormSuccessCallBack(), onMigrationCallback = (dir, optDir) => updateDirectiveForm(Left(dir), optDir) & displayFinishMigrationPopup, - onRemoveSuccessCallBack = () => onRemoveSuccessCallBack() + onRemoveSuccessCallBack = () => onRemoveSuccessCallBack(), + displayTechniqueDetails = onClickTechnique ) } @@ -750,7 +883,7 @@ class DirectiveManagement extends DispatchSnippet with Loggable { JsRaw(s"""this.window.location.hash = "#" + JSON.stringify(${json})""".stripMargin) } - private def onClickActiveTechnique(cat: FullActiveTechniqueCategory, fullActiveTechnique: FullActiveTechnique): JsCmd = { + private def onClickActiveTechnique(fullActiveTechnique: FullActiveTechnique): JsCmd = { currentTechnique = fullActiveTechnique.newestAvailableTechnique.map(fat => (fullActiveTechnique, fat.id.version)) currentDirectiveSettingForm.set(Empty) // Update UI @@ -759,6 +892,10 @@ class DirectiveManagement extends DispatchSnippet with Loggable { JsRaw("""initBsTooltips();""") // JsRaw ok, const } + private def onClickTechnique(id: ActiveTechniqueId): JsCmd = { + onClickActiveTechnique(directiveLibrary.runNow.allActiveTechniques(id)) + } + } object DirectiveManagement { @@ -771,4 +908,46 @@ object DirectiveManagement { val htmlId_currentActiveTechniqueActions = "currentActiveTechniqueActions" val html_addPiInActiveTechnique = "addNewDirective" val html_techniqueDetails = "techniqueDetails" + + def setEnabled(activeTechniqueId: ActiveTechniqueId, name: String, status: Boolean, successCallback: () => JsCmd): JsCmd = { + val msg = (if (status) "Enable" else "Disable") ++ "technique from directive library screen" + RudderConfig.woDirectiveRepository + .changeStatus( + activeTechniqueId, + status, + ModificationId(RudderConfig.stringUuidGenerator.newUuid), + CurrentUser.actor, + Some(msg) + ) + .either + .runNow match { + case Left(err) => + JsRaw( + s"""createErrorNotification("Error when changing status of technique ${name}: ${err.fullMsg}")""" + ).cmd // JsRaw ok, no user inputs + case Right(_) => + val msg = s"Technique '${name} was correctly ${if (status) "enabled" else "disabled"}" + JsRaw( + s"""createSuccessNotification("${msg}")""" + ).cmd & // JsRaw ok, no user inputs + successCallback() + } + } +} + +sealed trait NextStatus extends EnumEntry { + def action: String + def booleanValue: Boolean +} +object NextStatus extends Enum[NextStatus] { + final case object Enabled extends NextStatus { + val action = "Enable" + val booleanValue = true + } + final case object Disabled extends NextStatus { + val action = "Disable" + val booleanValue = false + } + + override def values: IndexedSeq[NextStatus] = findValues } diff --git a/webapp/sources/rudder/rudder-web/src/main/webapp/secure/configurationManager/directiveManagement.html b/webapp/sources/rudder/rudder-web/src/main/webapp/secure/configurationManager/directiveManagement.html index e913c00a258..338aadfa323 100644 --- a/webapp/sources/rudder/rudder-web/src/main/webapp/secure/configurationManager/directiveManagement.html +++ b/webapp/sources/rudder/rudder-web/src/main/webapp/secure/configurationManager/directiveManagement.html @@ -58,15 +58,15 @@

The Directive [Directive] is based on following Technique:
[Disabled Technique]
-
+

Description

[technique.description]
-
+

Technique ID

[technique.techniqueName]
-
+

Documentation

[technique.documentation] @@ -134,4 +134,5 @@

Characteristics

}); initBsTooltips(); - \ No newline at end of file +
+