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:
-
- - Bugs are fixed in all existing versions of Rudder techniques. Make sure you update your Rudder packages frequently.
- - A new minor technique version is created for any new features
- - A new major version is created for any architectural change (such as refactoring)
-
-
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)
-
- "*" #> {
-
+ ".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:
+
+ -
+ Bugs
+ are fixed in all existing versions of Rudder techniques. Make sure you update your Rudder packages frequently.
+ - A new
+ minor
+ technique version is created for any new features
+ - A new
+ major
+ version is created for any
+ architectural change
+ (such as refactoring)
+
+
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)
+
+ "*" #> {
+
+ }
- 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
+
+