diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala index 46a604b8ddd..bd0a7ec3445 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala @@ -425,10 +425,11 @@ object JsonQueryObjects { } } final case class JQUpdateNode( - properties: Option[List[NodeProperty]], - policyMode: Option[Option[PolicyMode]], - state: Option[NodeState], - agentKey: Option[JQAgentKey] + properties: Option[List[NodeProperty]], + policyMode: Option[Option[PolicyMode]], + state: Option[NodeState], + agentKey: Option[JQAgentKey], + documentation: Option[String] ) { val keyInfo: (Option[SecurityToken], Option[KeyStatus]) = agentKey.map(_.toKeyInfo).getOrElse((None, None)) } @@ -927,8 +928,9 @@ class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) { policyMode <- params.parse2("policyMode", PolicyMode.parseDefault(_)) state <- params.parseString("state", NodeState.parse(_)) agentKey <- params.parse("agentKey", JsonDecoder[JQAgentKey]) + doc = params.optGet("documentation") } yield { - JQUpdateNode(properties, policyMode, state, agentKey) + JQUpdateNode(properties, policyMode, state, agentKey, doc) } } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonResponseObjects.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonResponseObjects.scala index ebca3f709a3..7083dbb53ff 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonResponseObjects.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonResponseObjects.scala @@ -216,27 +216,23 @@ object JsonResponseObjects { ) final case class JRUpdateNode( - id: NodeId, - properties: Chunk[JRProperty], // sorted by name - policyMode: Option[PolicyMode], - state: NodeState + id: NodeId, + properties: Chunk[JRProperty], // sorted by name + policyMode: Option[PolicyMode], + state: NodeState, + documentation: Option[String] ) object JRUpdateNode { implicit val transformer: Transformer[CoreNodeFact, JRUpdateNode] = { Transformer .define[CoreNodeFact, JRUpdateNode] - .withFieldComputed( - _.state, - _.rudderSettings.state - ) - .withFieldComputed( - _.policyMode, - _.rudderSettings.policyMode - ) + .withFieldComputed(_.state, _.rudderSettings.state) + .withFieldComputed(_.policyMode, _.rudderSettings.policyMode) .withFieldComputed( _.properties, _.properties.sortBy(_.name).map(JRProperty.fromNodeProp).transformInto[Chunk[JRProperty]] ) + .withFieldComputed(_.documentation, _.documentation) .buildTransformer } } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/nodes/Node.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/nodes/Node.scala index f99bf8496e2..bfdb6f786bf 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/nodes/Node.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/domain/nodes/Node.scala @@ -139,14 +139,15 @@ object NodeState extends Enum[NodeState] { * For now, other simple properties are not handle. */ final case class ModifyNodeDiff( - id: NodeId, - modHeartbeat: Option[SimpleDiff[Option[HeartbeatConfiguration]]], - modAgentRun: Option[SimpleDiff[Option[AgentRunInterval]]], - modProperties: Option[SimpleDiff[List[NodeProperty]]], - modPolicyMode: Option[SimpleDiff[Option[PolicyMode]]], - modKeyValue: Option[SimpleDiff[SecurityToken]], - modKeyStatus: Option[SimpleDiff[KeyStatus]], - modNodeState: Option[SimpleDiff[NodeState]] + id: NodeId, + modHeartbeat: Option[SimpleDiff[Option[HeartbeatConfiguration]]], + modAgentRun: Option[SimpleDiff[Option[AgentRunInterval]]], + modProperties: Option[SimpleDiff[List[NodeProperty]]], + modPolicyMode: Option[SimpleDiff[Option[PolicyMode]]], + modKeyValue: Option[SimpleDiff[SecurityToken]], + modKeyStatus: Option[SimpleDiff[KeyStatus]], + modNodeState: Option[SimpleDiff[NodeState]], + modDocumentation: Option[SimpleDiff[String]] ) object ModifyNodeDiff { @@ -197,7 +198,10 @@ object ModifyNodeDiff { val state = if (oldNode.state == newNode.state) None else Some(SimpleDiff(oldNode.state, newNode.state)) - ModifyNodeDiff(newNode.id, heartbeat, agentRun, properties, policyMode, keyValue, keyStatus, state) + val documentation = + if (oldNode.description == newNode.description) None else Some(SimpleDiff(oldNode.description, newNode.description)) + + ModifyNodeDiff(newNode.id, heartbeat, agentRun, properties, policyMode, keyValue, keyStatus, state, documentation) } def keyInfo( @@ -220,6 +224,6 @@ object ModifyNodeDiff { case Some(s) => if (s == oldStatus) None else Some(SimpleDiff(oldStatus, s)) } - ModifyNodeDiff(nodeId, None, None, None, None, keyInfo, keyStatus, None) + ModifyNodeDiff(nodeId, None, None, None, None, keyInfo, keyStatus, None, None) } } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala index 55710fbfcf1..8344f9b3ccb 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala @@ -234,7 +234,7 @@ object MinimalNodeFactInterface { } eq(_.id) && - eq(_.description) && + eq(_.documentation) && eq(_.fqdn) && eq(_.os) && eq(_.machine) && @@ -257,7 +257,7 @@ object MinimalNodeFactInterface { def toNode(node: MinimalNodeFactInterface): Node = Node( node.id, node.fqdn, - "", // description + node.documentation.getOrElse(""), node.rudderSettings.state, isSystem(node), isSystem((node)), @@ -704,7 +704,7 @@ object NodeFact { case _ => NodeFact( a.id, - a.description, + a.documentation, a.fqdn, a.os, a.machine, @@ -843,7 +843,7 @@ object NodeFact { def updateNode(node: NodeFact, n: Node): NodeFact = { import com.softwaremill.quicklens.* node - .modify(_.description) + .modify(_.documentation) .setTo(Some(n.description)) .modify(_.rudderSettings.state) .setTo(n.state) @@ -871,7 +871,7 @@ object NodeFact { val pad = "\n * " val ignored = new StringBuilder().append(pad).append("ignored: ") val sb = new StringBuilder(s"""id:${diff(n1.id.value, n2.id.value)} - | * description:${diff(n1.description, n2.description)} + | * description:${diff(n1.documentation, n2.documentation)} | * fqdn:${diff(n1.fqdn, n2.fqdn)} | * os:${diff(n1.os, n2.os)} | * machine:${diff(n1.machine, n2.machine)} @@ -915,7 +915,7 @@ object NodeFact { trait MinimalNodeFactInterface { def id: NodeId - def description: Option[String] + def documentation: Option[String] def fqdn: String def os: OsDetails def machine: MachineInfo @@ -995,7 +995,7 @@ trait MinimalNodeFactInterface { */ final case class CoreNodeFact( id: NodeId, - description: Option[String], + documentation: Option[String], @jsonField("hostname") fqdn: String, os: OsDetails, @@ -1018,8 +1018,8 @@ object CoreNodeFact { def updateNode(node: CoreNodeFact, n: Node): CoreNodeFact = { import com.softwaremill.quicklens.* node - .modify(_.description) - .setTo(Some(n.description)) + .modify(_.documentation) + .setTo(if (n.description.isBlank) None else Some(n.description)) .modify(_.rudderSettings.state) .setTo(n.state) .modify(_.rudderSettings.kind) @@ -1040,7 +1040,7 @@ object CoreNodeFact { case _ => CoreNodeFact( a.id, - a.description, + a.documentation, a.fqdn, a.os, a.machine, @@ -1398,7 +1398,7 @@ object SelectFacts { final case class NodeFact( id: NodeId, - description: Option[String], + documentation: Option[String], @jsonField("hostname") fqdn: String, os: OsDetails, @@ -1461,7 +1461,7 @@ final case class NodeFact( val pad = "\n * " val ignored = new StringBuilder().append(pad).append("ignored: ") val sb = new StringBuilder(s"""id:${this.id.value} - | * description:${this.description} + | * documentation:${this.documentation} | * fqdn:${this.fqdn} | * os:${this.os} | * machine:${this.machine} diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala index 9d9a1cb3383..208606015cc 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala @@ -1071,32 +1071,33 @@ class EventLogDetailsServiceImpl( def getModifyNodeDetails(xml: NodeSeq): Box[ModifyNodeDiff] = { for { - entry <- getEntryContent(xml) - node <- (entry \ "node").headOption ?~! ("Entry type is not node : " + entry.toString()) - fileFormatOk <- TestFileFormat(node) - changeTypeOk <- { + entry <- getEntryContent(xml) + node <- (entry \ "node").headOption ?~! ("Entry type is not node : " + entry.toString()) + fileFormatOk <- TestFileFormat(node) + changeTypeOk <- { if (node.attribute("changeType").map(_.text) == Some("modify")) Full("OK") else Failure(s"'Node modification' entry does not have attribute 'changeType' with value 'modify', entry is: ${entry}") } - id <- (node \ "id").headOption.map(x => NodeId(x.text)) ?~! ("Missing element 'id' in entry type Node: " + entry.toString()) - policyMode <- getFromTo[Option[PolicyMode]]((node \ "policyMode").headOption, x => PolicyMode.parseDefault(x.text).toBox) - agentRun <- getFromTo[Option[AgentRunInterval]]((node \ "agentRun").headOption, x => extractAgentRun(xml)(x)) - heartbeat <- + id <- (node \ "id").headOption.map(x => NodeId(x.text)) ?~! ("Missing element 'id' in entry type Node: " + entry.toString()) + policyMode <- getFromTo[Option[PolicyMode]]((node \ "policyMode").headOption, x => PolicyMode.parseDefault(x.text).toBox) + agentRun <- getFromTo[Option[AgentRunInterval]]((node \ "agentRun").headOption, x => extractAgentRun(xml)(x)) + heartbeat <- getFromTo[Option[HeartbeatConfiguration]]((node \ "heartbeat").headOption, x => extractHeartbeatConfiguration(xml)(x)) - properties <- + properties <- getFromTo[List[NodeProperty]]((node \ "properties").headOption, x => extractNodeProperties(xml)(x).map(_.toList)) - agentKey <- getFromTo[SecurityToken]( - (node \ "agentKey").headOption, - { x => - val s = x.text; - if (s.contains("BEGIN CERTIFICATE")) Full(Certificate(s)) else Failure(s"Unrecognized security token") - } - ) - keyStatus <- getFromTo[KeyStatus]( - (node \ "keyStatus").headOption, - x => KeyStatus.apply(x.text).map(Full(_)).getOrElse(Failure(s"Unrecognized agent key status '${x.text}'")) - ) - nodeState <- getFromTo[NodeState]((node \ "nodeState").headOption, x => NodeState.parse(x.text).toBox) + agentKey <- getFromTo[SecurityToken]( + (node \ "agentKey").headOption, + { x => + val s = x.text; + if (s.contains("BEGIN CERTIFICATE")) Full(Certificate(s)) else Failure(s"Unrecognized security token") + } + ) + keyStatus <- getFromTo[KeyStatus]( + (node \ "keyStatus").headOption, + x => KeyStatus.apply(x.text).map(Full(_)).getOrElse(Failure(s"Unrecognized agent key status '${x.text}'")) + ) + nodeState <- getFromTo[NodeState]((node \ "nodeState").headOption, x => NodeState.parse(x.text).toBox) + documentation <- getFromTo[String]((node \ "documentation").headOption, x => Full(x.text)) } yield { ModifyNodeDiff( id, @@ -1106,7 +1107,8 @@ class EventLogDetailsServiceImpl( policyMode, agentKey, keyStatus, - nodeState + nodeState, + documentation ) } } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala index 2567a460820..1899d887e2e 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala @@ -1169,6 +1169,11 @@ class EventLogFactoryImpl( case None => NodeSeq.Empty case Some(x) => SimpleDiff.toXml(, x)(x => Text(x.name)) } + }{ + modifyDiff.modDocumentation match { + case None => NodeSeq.Empty + case Some(x) => SimpleDiff.toXml(, x)(x => Text(x)) + } } ) } diff --git a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/eventlog/NodeEventLogFormatV6Test.scala b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/eventlog/NodeEventLogFormatV6Test.scala index 9bc7bc8d191..bab85642f01 100644 --- a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/eventlog/NodeEventLogFormatV6Test.scala +++ b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/eventlog/NodeEventLogFormatV6Test.scala @@ -84,7 +84,8 @@ class NodeEventLogFormatV6Test extends Specification { modPolicyMode = None, modKeyValue = None, modKeyStatus = None, - modNodeState = None + modNodeState = None, + modDocumentation = None ) val event_32_NodePropertiesModified: Elem = @@ -123,7 +124,8 @@ class NodeEventLogFormatV6Test extends Specification { modPolicyMode = None, modKeyValue = None, modKeyStatus = None, - modNodeState = None + modNodeState = None, + modDocumentation = None ) val event_32_NodeAgentRunPeriodModified: Elem = @@ -147,7 +149,8 @@ class NodeEventLogFormatV6Test extends Specification { modPolicyMode = None, modKeyValue = None, modKeyStatus = None, - modNodeState = None + modNodeState = None, + modDocumentation = None ) "Current EventTypeFactory" should { diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala index fe4891e51bd..7204d17c8fd 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala @@ -1266,15 +1266,21 @@ class NodeApiService( def updateRestNode(nodeId: NodeId, update: JQUpdateNode)(implicit cc: ChangeContext): IOResult[CoreNodeFact] = { def updateNode( - node: CoreNodeFact, - update: JQUpdateNode, - newProperties: List[NodeProperty], - newKey: Option[SecurityToken], - newKeyStatus: Option[KeyStatus] + node: CoreNodeFact, + update: JQUpdateNode, + newProperties: List[NodeProperty], + newKey: Option[SecurityToken], + newKeyStatus: Option[KeyStatus], + newDocumentation: Option[String] ): CoreNodeFact = { import com.softwaremill.quicklens.* val propNames: Set[String] = newProperties.map(_.name).toSet + val documentation = newDocumentation match { + case Some("") => Some(None) + case Some(x) => Some(Some(x)) + case None => None + } node .modify(_.properties) @@ -1291,6 +1297,8 @@ class NodeApiService( .setToIfDefined(newKey) .modify(_.rudderSettings.keyStatus) .setToIfDefined(newKeyStatus) + .modify(_.documentation) + .setToIfDefined(documentation) } implicit val qc: QueryContext = cc.toQuery @@ -1298,7 +1306,7 @@ class NodeApiService( for { nodeFact <- nodeFactRepository.get(nodeId).notOptional(s"node with id '${nodeId.value}' was not found") newProperties <- CompareProperties.updateProperties(nodeFact.properties.toList, update.properties).toIO - updated = updateNode(nodeFact, update, newProperties, update.keyInfo._1, update.keyInfo._2) + updated = updateNode(nodeFact, update, newProperties, update.keyInfo._1, update.keyInfo._2, update.documentation) _ <- if (CoreNodeFact.same(updated, nodeFact)) ZIO.unit else nodeFactRepository.save(updated).unit } yield { diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsGenerator.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsGenerator.scala index 8cd085cecf2..12db96ea86c 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsGenerator.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsGenerator.scala @@ -1039,6 +1039,8 @@ class EventLogDetailsGenerator( mapComplexDiff(modDiff.modKeyStatus, Key status)(x => Text(x.value)) }{mapComplexDiff(modDiff.modKeyValue, Key value)(x => Text(x.key))}{reasonHtml}{ xmlParameters(event.id) + }{ + mapComplexDiff(modDiff.modDocumentation, Description)(x => Text(x)) } case e: EmptyBox => diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala index d283aea65d4..94687b2ece9 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala @@ -634,6 +634,12 @@ object DisplayNode extends Loggable { } +
+

Documentation

+
+
+ {Script(OnLoad(JsRaw(s"generateMarkdown(${Str(nodeFact.documentation.getOrElse("")).toJsCmd}, '#nodeDocumentation')")))} +

Rudder information