diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala index 9c4a29e052f..6ddb0e78481 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala @@ -374,6 +374,8 @@ trait EventLogRepository { */ def getEventLogByCriteria(criteria: Option[String], limit :Option[Int] = None, orderBy: Option[String] = None) : IOResult[Vector[EventLog]] + def getEventLogCount: IOResult[Long] + def getEventLogByChangeRequest( changeRequest : ChangeRequestId , xpath : String diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala index 68aa070dcdb..aab153af5d2 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala @@ -200,6 +200,16 @@ class EventLogJdbcRepository( }).transact(xa)) } + def getEventLogCount: IOResult[Long] = { + val q ="SELECT count(*) FROM eventlog" + //sql"SELECT count(*) FROM eventlog".query[Long].option.transact(xa).unsafeRunSync + transactIOResult(s"Error when retrieving event logs count with request: ${q}")(xa => (for { + entries <- query[Long](q).unique + } yield { + entries + }).transact(xa)) + } + def getEventLogByCriteria(criteria : Option[String], optLimit:Option[Int] = None, orderBy:Option[String]) : IOResult[Vector[EventLog]] = { val where = criteria.map(c => s"where ${c}").getOrElse("") diff --git a/webapp/sources/rudder/rudder-rest/pom.xml b/webapp/sources/rudder/rudder-rest/pom.xml index ee598f7b4c4..d073c007ca0 100644 --- a/webapp/sources/rudder/rudder-rest/pom.xml +++ b/webapp/sources/rudder/rudder-rest/pom.xml @@ -98,6 +98,11 @@ along with Rudder. If not, see . + + org.springframework.security + spring-security-core + ${spring-security-version} + com.normation.rudder diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/EventLogAPI.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/EventLogAPI.scala new file mode 100644 index 00000000000..3a8695792a8 --- /dev/null +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/EventLogAPI.scala @@ -0,0 +1,161 @@ +/* +************************************************************************************* +* Copyright 2019 Normation SAS +************************************************************************************* +* +* This file is part of Rudder. +* +* Rudder is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* In accordance with the terms of section 7 (7. Additional Terms.) of +* the GNU General Public License version 3, the copyright holders add +* the following Additional permissions: +* Notwithstanding to the terms of section 5 (5. Conveying Modified Source +* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General +* Public License version 3, when you create a Related Module, this +* Related Module is not considered as a part of the work and may be +* distributed under the license agreement of your choice. +* A "Related Module" means a set of sources files including their +* documentation that, without modification of the Source Code, enables +* supplementary functions or services in addition to those offered by +* the Software. +* +* Rudder is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Rudder. If not, see . + +* +************************************************************************************* +*/ + +package com.normation.rudder.rest.internal +import com.normation.box._ +import com.normation.eventlog._ +import com.normation.rudder.repository.EventLogRepository +import com.normation.rudder.rest.RestExtractorService +import com.normation.rudder.rest.RestUtils._ +import com.normation.rudder.web.components.DateFormaterService +import com.normation.rudder.web.services._ +import net.liftweb.common._ +import net.liftweb.http.rest.RestHelper +import net.liftweb.http.{JsonResponse, LiftResponse, Req, S} +import net.liftweb.json.JValue +import net.liftweb.json.JsonDSL._ + +class EventLogAPI ( + repos: EventLogRepository + , restExtractor : RestExtractorService + , eventLogDetail : EventListDisplayer +) extends RestHelper with Loggable { + + def serialize(event: EventLog): JValue = { + import net.liftweb.json.JsonDSL._ + + ( ("id" -> (event.id.map(_.toString).getOrElse("Unknown"): String)) + ~ ("date" -> DateFormaterService.getFormatedDate(event.creationDate)) + ~ ("actor" -> event.principal.name) + ~ ("type" -> S.?("rudder.log.eventType.names." + event.eventType.serialize)) + ~ ("description" -> eventLogDetail.displayDescription(event).toString) + ~ ("hasDetails" -> (if(event.details != ) true else false)) + ) + } + + def responseFormater(draw: Int, totalRecord: Long, totalFiltered: Long, logs: Vector[EventLog], errorMsg: Option[String] = None): JValue = { + errorMsg match { + case Some(msg) => + ( ("draw" -> draw) + ~ ("recordsTotal" -> totalRecord) + ~ ("recordsFiltered" -> totalFiltered) + ~ ("data" -> logs.map(serialize)) + ~ ("error" -> msg) + ) + case _ => + ( ("draw" -> draw) + ~ ("recordsTotal" -> totalRecord) + ~ ("recordsFiltered" -> totalFiltered) + ~ ("data" -> logs.map(serialize)) + ) + } + } + + def getEventLogBySlice(start: Int, nbelement: Int, criteria : Option[String], optLimit:Option[Int] = None, orderBy:Option[String]): Box[(Int,Vector[EventLog])] = { + repos.getEventLogByCriteria(criteria, optLimit, orderBy).toBox match { + case Full(events) => Full((events.size, events.slice(start,start+nbelement))) + case eb: EmptyBox => eb ?~! s"Error when trying fetch eventlogs from database for page ${(start/nbelement)+1}" + } + } + + def requestDispatch: PartialFunction[Req, () => Box[LiftResponse]] = { + case Get(Nil, req) => + + val draw = req.params.get("draw") match { + case Some(value :: Nil) => Full(value.toInt) + case None => Failure("Missing 'draw' field from datatable's request") + } + val start = req.params.get("start") match { + case Some(value :: Nil) => Full(value) + case None => Failure("Missing 'start' field from datatable's request") + } + val length = req.params.get("length") match { + case Some(value :: Nil) => Full(value) + case None => Failure("Missing 'length' field from datatable's request") + } + + val response = (draw, start, length) match { + case (Full(d), Full(s), Full(l)) => + repos.getEventLogCount.toBox match { + case Full(totalRecord) => + getEventLogBySlice(s.toInt, l.toInt, None, None, Some("creationdate DESC" )) match { + case Full((totalFiltered, events)) => + responseFormater(d, totalRecord, totalFiltered, events) + case eb: EmptyBox => + val fail = eb ?~! "Failed to get eventlogs" + responseFormater(d, totalRecord, 0, Vector.empty, Some(fail.messageChain)) + } + case eb: EmptyBox => + val fail = eb ?~! "Failed to get event log's count" + responseFormater(d, 0, 0, Vector.empty, Some(fail.messageChain)) + } + + case (ebDraw: EmptyBox,_, _) => + val fail = ebDraw ?~! "Missing parameter in request" + responseFormater(0, 0, 0, Vector.empty, Some(fail.messageChain)) + + case (_,ebStart: EmptyBox, _) => + val fail = ebStart ?~! "Missing parameter in request" + responseFormater(0, 0, 0, Vector.empty, Some(fail.messageChain)) + + case (_,_, ebLength: EmptyBox) => + val fail = ebLength ?~! "Missing parameter in request" + responseFormater(0, 0, 0, Vector.empty, Some(fail.messageChain)) + } + JsonResponse(response, Nil, Nil, 200) + + case Get(id :: "details" :: Nil, _) => + repos.getEventLogByCriteria(Some(s"id = $id")).toBox match { + case Full(e) => + e.headOption match { + case Some(eventLog) => + val crid = eventLog.id.flatMap(repos.getEventLogWithChangeRequest(_).toBox match { + case Full(Some((_, crId))) => crId + case _ => None + }) + val htmlDetails = eventLogDetail.displayDetails(eventLog, crid) + toJsonResponse(None, "content" -> htmlDetails.toString())("eventdetails", prettify = false) + case None => + toJsonError(None, s"EventLog $id not found")("eventdetails", prettify = false) + } + case eb: EmptyBox => + val e = eb ?~! s"Error when trying to retrieve eventlog : $id" + toJsonError(None, e.messageChain)("eventdetails", prettify = false) + } + } + serve("secure" / "api" / "eventlog" prefix requestDispatch) +} diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/CurrentUserService.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/CurrentUserService.scala new file mode 100644 index 00000000000..533218b7d44 --- /dev/null +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/CurrentUserService.scala @@ -0,0 +1,86 @@ +/* +************************************************************************************* +* Copyright 2011 Normation SAS +************************************************************************************* +* +* This file is part of Rudder. +* +* Rudder is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* In accordance with the terms of section 7 (7. Additional Terms.) of +* the GNU General Public License version 3, the copyright holders add +* the following Additional permissions: +* Notwithstanding to the terms of section 5 (5. Conveying Modified Source +* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General +* Public License version 3, when you create a Related Module, this +* Related Module is not considered as a part of the work and may be +* distributed under the license agreement of your choice. +* A "Related Module" means a set of sources files including their +* documentation that, without modification of the Source Code, enables +* supplementary functions or services in addition to those offered by +* the Software. +* +* Rudder is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Rudder. If not, see . + +* +************************************************************************************* +*/ + +package com.normation.rudder.web.services + +import com.normation.rudder.{AuthorizationType, Rights, RudderAccount, User} +import com.normation.rudder.api.ApiAuthorization +import net.liftweb.http.SessionVar +import org.springframework.security.core.context.SecurityContextHolder + +/** + * An utility class that get the currently logged user + * (if any) + * + */ +object CurrentUserService extends SessionVar[Option[RudderUserDetail]] ({ + SecurityContextHolder.getContext.getAuthentication match { + case null => None + case auth => auth.getPrincipal match { + case u:RudderUserDetail => Some(u) + case _ => None + } + } + +}) with User { + + def getRights : Rights = this.get match { + case Some(u) => u.authz + case None => new Rights(AuthorizationType.NoRights) + } + + def account : RudderAccount = this.get match { + case None => RudderAccount.User("unknown", "") + case Some(u) => u.account + } + + def checkRights(auth:AuthorizationType) : Boolean = { + val authz = getRights.authorizationTypes + if (authz.contains(AuthorizationType.NoRights)) false + else auth match{ + case AuthorizationType.NoRights => false + case _ => authz.contains(auth) + } + } + + def getApiAuthz: ApiAuthorization = { + this.get match { + case None => ApiAuthorization.None + case Some(u) => u.apiAuthz + } + } +} diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala similarity index 69% rename from webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala rename to webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala index d461a32a1ac..d1b751da901 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala @@ -37,17 +37,14 @@ package com.normation.rudder.web.services -import scala.xml.NodeSeq -import com.normation.rudder.domain.policies.DirectiveId -import bootstrap.liftweb.RudderConfig +import com.normation.rudder.domain.policies.{DirectiveId, _} import com.normation.rudder.repository.FullNodeGroupCategory -import com.normation.rudder.rule.category.RuleCategoryId -import net.liftweb.common.Full -import net.liftweb.common.EmptyBox -import net.liftweb.common.Loggable -import com.normation.rudder.domain.policies._ -import com.normation.rudder.rule.category.RuleCategory +import com.normation.rudder.rule.category.{RuleCategory, RuleCategoryId, RuleCategoryService} +import com.normation.rudder.web.model.LinkUtil +import net.liftweb.common.{EmptyBox, Full, Loggable} + import scala.language.implicitConversions +import scala.xml.NodeSeq trait DiffItem[T] { @@ -56,39 +53,39 @@ trait DiffItem[T] { } case class Added[T]( - value:T + value:T ) extends DiffItem[T] { val newValue = Some(value) def display(implicit displayer : T => NodeSeq) : NodeSeq =
  • - + {displayer(value)} + + {displayer(value)}
  • } case class Deleted[T]( - value:T + value:T ) extends DiffItem[T] { def display(implicit displayer : T => NodeSeq) : NodeSeq =
  • - -  {displayer(value)} + -  {displayer(value)}
  • } case class Unchanged[T]( - value:T + value:T ) extends DiffItem[T] { def display(implicit displayer : T => NodeSeq) : NodeSeq =
  • -    {displayer(value)} +    {displayer(value)}
  • } // Not used yet, but for later use case class Modified[T]( - oldValue:T + oldValue:T , newValue:T ) extends DiffItem[T] { @@ -96,20 +93,17 @@ case class Modified[T]( private[this] val add = Added(oldValue) def display(implicit displayer : T => NodeSeq) : NodeSeq = - delete.display ++ add.display + delete.display ++ add.display } -object DiffDisplayer extends Loggable { - - //Directive targets Displayer - private[this] val linkUtil = RudderConfig.linkUtil +class DiffDisplayer(linkUtil: LinkUtil) extends Loggable { private[this] implicit def displayDirective(directiveId: DirectiveId) = { Directive {linkUtil.createDirectiveLink(directiveId)} } def displayDirectiveChangeList ( - oldDirectives:Seq[DirectiveId] - , newDirectives:Seq[DirectiveId] + oldDirectives:Seq[DirectiveId] + , newDirectives:Seq[DirectiveId] ) : NodeSeq = { // First, find unchanged and deleted (have find no clean way to make a 3 way partition) @@ -123,19 +117,19 @@ object DiffDisplayer extends Loggable { val changeMap:Seq[DiffItem[DirectiveId]] = deletedMap ++ unchangedMap ++ added
      { for { - change <- changeMap - } yield { - // Implicit used here (displayDirective) - change.display - } } + change <- changeMap + } yield { + // Implicit used here (displayDirective) + change.display + } }
    } // Almost the same as display Directive see comments there for more details def displayRuleTargets ( - oldTargets:Seq[RuleTarget] - , newTargets:Seq[RuleTarget] - , groupLib: FullNodeGroupCategory + oldTargets:Seq[RuleTarget] + , newTargets:Seq[RuleTarget] + , groupLib: FullNodeGroupCategory ) : NodeSeq = { implicit def displayNodeGroup(target: RuleTarget) : NodeSeq= { @@ -146,16 +140,16 @@ object DiffDisplayer extends Loggable { Nodes that belongs to all these groups:
      {targets.map(t =>
    • {displayNodeGroup(t)}
    • )}
    case TargetExclusion(included,excluded) => Include {displayNodeGroup(included)} -
    Exclude {displayNodeGroup(excluded)} +
    Exclude {displayNodeGroup(excluded)} case GroupTarget(nodeGroupId) => Group {linkUtil.createGroupLink(nodeGroupId)} case x => groupLib.allTargets.get(x).map{ targetInfo => - - {targetInfo.name} - {if (targetInfo.isSystem) (System)} - - }.getOrElse( {x.target}) + + {targetInfo.name} + {if (targetInfo.isSystem) (System)} + + }.getOrElse( {x.target}) } } @@ -166,30 +160,30 @@ object DiffDisplayer extends Loggable { case _:TargetUnion => all Nodes from: case _:TargetIntersection => - Nodes that belongs to all these groups: + Nodes that belongs to all these groups: } } - val includedKind = { - ((newIncluded,oldIncluded) match { - case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) => - Seq(Unchanged (newIncluded)) - case _ => - (Seq(Deleted(oldIncluded),Added(newIncluded))) - }).flatMap(_.display(displayKind)) - } - val excludedKind = { - ((newExcluded,oldExcluded) match { - case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) => - Seq(Unchanged (newExcluded)) - case _ => - (Seq(Deleted(oldExcluded),Added(newExcluded))) - }).flatMap(_.display(displayKind)) - } - val includedTargets = displayRuleTargets(newIncluded.targets.toSeq,oldIncluded.targets.toSeq, groupLib) - val excludedTargets = displayRuleTargets(newExcluded.targets.toSeq,oldExcluded.targets.toSeq, groupLib) - Include ++ includedKind ++ includedTargets ++ - Exclude ++ excludedKind ++ excludedTargets + val includedKind = { + ((newIncluded,oldIncluded) match { + case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) => + Seq(Unchanged (newIncluded)) + case _ => + (Seq(Deleted(oldIncluded),Added(newIncluded))) + }).flatMap(_.display(displayKind)) + } + val excludedKind = { + ((newExcluded,oldExcluded) match { + case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) => + Seq(Unchanged (newExcluded)) + case _ => + (Seq(Deleted(oldExcluded),Added(newExcluded))) + }).flatMap(_.display(displayKind)) + } + val includedTargets = displayRuleTargets(newIncluded.targets.toSeq,oldIncluded.targets.toSeq, groupLib) + val excludedTargets = displayRuleTargets(newExcluded.targets.toSeq,oldExcluded.targets.toSeq, groupLib) + Include ++ includedKind ++ includedTargets ++ + Exclude ++ excludedKind ++ excludedTargets case (_,_) => val (unchanged,deleted) = oldTargets.partition(newTargets.contains) @@ -200,20 +194,21 @@ object DiffDisplayer extends Loggable { val changeMap:Seq[DiffItem[RuleTarget]] = deletedMap ++ unchangedMap ++ added
      { for { - change <- changeMap - } yield { - // Implicit used here (displayNodeGroup) - change.display - } + change <- changeMap + } yield { + // Implicit used here (displayNodeGroup) + change.display + } }
    } } - private[this] val ruleCategoryService = RudderConfig.ruleCategoryService + // + private[this] val ruleCategoryService = new RuleCategoryService() def displayRuleCategory ( - rootCategory: RuleCategory + rootCategory: RuleCategory , oldCategory : RuleCategoryId , newCategory : Option[RuleCategoryId] ) = { @@ -227,21 +222,21 @@ object DiffDisplayer extends Loggable { } } implicit def displayRuleCategory(ruleCategoryId: RuleCategoryId) = { - {getCategoryFullName(ruleCategoryId)} + {getCategoryFullName(ruleCategoryId)} } newCategory match { case Some(newCategory) => val changes = Seq(Deleted(oldCategory),Added(newCategory)) -
      - { for { +
        + { for { change <- changes } yield { // Implicit used here (displayRuleCategory) change.display } - } -
      + } +
    case None => displayRuleCategory(oldCategory) } diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsService.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsService.scala new file mode 100644 index 00000000000..890aa6c57d5 --- /dev/null +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsService.scala @@ -0,0 +1,1567 @@ +/* +************************************************************************************* +* Copyright 2011 Normation SAS +************************************************************************************* +* +* This file is part of Rudder. +* +* Rudder is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* In accordance with the terms of section 7 (7. Additional Terms.) of +* the GNU General Public License version 3, the copyright holders add +* the following Additional permissions: +* Notwithstanding to the terms of section 5 (5. Conveying Modified Source +* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General +* Public License version 3, when you create a Related Module, this +* Related Module is not considered as a part of the work and may be +* distributed under the license agreement of your choice. +* A "Related Module" means a set of sources files including their +* documentation that, without modification of the Source Code, enables +* supplementary functions or services in addition to those offered by +* the Software. +* +* Rudder is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Rudder. If not, see . + +* +************************************************************************************* +*/ +package com.normation.rudder.web.services + +import com.normation.box._ +import com.normation.cfclerk.domain.TechniqueName +import com.normation.eventlog.EventLog +import com.normation.inventory.domain.NodeId +import com.normation.rudder.api._ +import com.normation.rudder.batch.{ErrorStatus, SuccessStatus} +import com.normation.rudder.domain.eventlog.{WorkflowStepChanged, _} +import com.normation.rudder.domain.nodes._ +import com.normation.rudder.domain.parameters._ +import com.normation.rudder.domain.policies._ +import com.normation.rudder.domain.queries.Query +import com.normation.rudder.domain.workflows.{ChangeRequestId, WorkflowStepChange} +import com.normation.rudder.reports.{AgentRunInterval, HeartbeatConfiguration} +import com.normation.rudder.repository._ +import com.normation.rudder.rule.category.{RoRuleCategoryRepository, RuleCategory} +import com.normation.rudder.services.eventlog.{EventLogDetailsService, RollbackInfo} +import com.normation.rudder.services.modification.ModificationService +import com.normation.rudder.services.nodes.NodeInfoService +import com.normation.rudder.services.user.PersonIdentService +import com.normation.rudder.web.components.DateFormaterService +import com.normation.rudder.web.model.LinkUtil +import net.liftweb.common._ +import net.liftweb.http.{S, SHtml} +import net.liftweb.http.js.JE._ +import net.liftweb.http.js.JsCmds._ +import net.liftweb.http.js._ +import net.liftweb.json._ +import net.liftweb.util.Helpers._ +import org.eclipse.jgit.lib.PersonIdent +import org.joda.time.DateTime +import org.joda.time.format.{DateTimeFormat, ISODateTimeFormat} + +import scala.util.{Success, Try, Failure => Catch} +import scala.xml._ + +/** + * Used to display the event list, in the pending modification (AsyncDeployment), + * or in the administration EventLogsViewer + */ +class EventListDisplayer( + logDetailsService : EventLogDetailsService + , repos : EventLogRepository + , nodeGroupRepository : RoNodeGroupRepository + , directiveRepository : RoDirectiveRepository + , nodeInfoService : NodeInfoService + , ruleCatRepository : RoRuleCategoryRepository + , modificationService : ModificationService + , personIdentService : PersonIdentService + , linkUtil : LinkUtil +) extends Loggable { + + private[this] val xmlPretty = new scala.xml.PrettyPrinter(80, 2) + + private[this] val gridName = "eventLogsGrid" + + def display(refreshEvents:() => Box[Seq[EventLog]]) : NodeSeq = { + val limit: Int = 500 + //common part between last events and interval + def displayEvents(events: Box[Seq[EventLog]]) :JsCmd = { + events match { + case Full(events) => + val lines = { + val el = events.map(EventLogLine(_)).toList.sortWith(_.event.creationDate.getMillis > _.event.creationDate.getMillis) + if(el.size > limit) JsTableData(el.take(limit)) else JsTableData(el) + } + JsRaw(s"refreshTable('${gridName}',${lines.json.toJsCmd})") + case eb : EmptyBox => + val fail = eb ?~! "Could not get latest event logs" + logger.error(fail.messageChain) + val xml =
    Error when trying to get last event logs. Error message was: {fail.messageChain}
    + SetHtml("eventLogsError",xml) + } + } + + def getLastEvents : JsCmd = { + displayEvents(refreshEvents()) + } + + def getEventsInterval(jsonInterval: String): JsCmd = { + import java.sql.Timestamp + val format = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss") + + displayEvents(for { + parsed <- tryo(parse(jsonInterval)) ?~! s"Error when trying to parse '${jsonInterval}' as a JSON datastructure with fields 'start' and 'end'" + startStr <- parsed \ "start" match { + case JString(startStr) if startStr.nonEmpty => + val date = tryo(DateTime.parse(startStr, format)) ?~! s"Error when trying to parse start date '${startStr}" + date match { + case Full(d) => Full(Some(new Timestamp(d.getMillis))) + case eb: EmptyBox => + eb ?~! s"Invalid start date" + } + case _ => Full(None) + } + endStr <- parsed \ "end" match { + case JString(endStr) if endStr.nonEmpty => + val date = tryo(DateTime.parse(endStr, format)) ?~! s"Error when trying to parse end date '${endStr}" + date match { + case Full(d) => Full(Some(new Timestamp(d.getMillis))) + case eb: EmptyBox => + eb ?~! s"Invalid end date" + } + case _ => Full(None) + } + whereStatement = (startStr, endStr) match { + case (None, None) => None + case (Some(start), None) => Some(s" creationdate > '$start'") + case (None, Some(end)) => Some(s" creationdate < '$end'") + case (Some(start), Some(end)) => + val orderedDate = if(start.after(end)) (end, start) else (start, end) + Some(s" creationdate > '${orderedDate._1}' and creationdate < '${orderedDate._2}'") + } + logs <- repos.getEventLogByCriteria(whereStatement, None, Some("id DESC")).toBox + } yield { + logs + }) + } + + val refresh = AnonFunc(SHtml.ajaxInvoke( () => getLastEvents)) + + Script(OnLoad(JsRaw(s""" + var pickEventLogsInInterval = ${AnonFunc(SHtml.ajaxCall(JsRaw( + """'{"start":"'+$(".pickStartInput").val()+'", "end":"'+$(".pickEndInput").val()+'"}'""" + ), getEventsInterval)._2).toJsCmd} + var refreshEventLogs = ${refresh.toJsCmd}; + createEventLogTable('${gridName}',[], '${S.contextPath}', refreshEventLogs, pickEventLogsInInterval) + refreshEventLogs(); + """))) + + } + + /* + * Javascript object containing all data to create a line in event logs table + * { "id" : Event log id [Int] + * , "date": date the event log was produced [Date/String] + * , "actor": Name of the actor making the event [String] + * , "type" : Type of the event log [String] + * , "description" : Description of the event [String] + * , "details" : function/ajax call, setting the details content, takes the id of the element to set [Function(String)] + * , "hasDetails" : do our event needs to display details (do we need to be able to open the row [Boolean] + * } + */ + case class EventLogLine(event : EventLog) extends JsTableLine { + val json = { + JsObj( + "id" -> (event.id.map(_.toString).getOrElse("Unknown"): String) + , "date" -> DateFormaterService.getFormatedDate(event.creationDate) + , "actor" -> event.principal.name + , "type" -> S.?("rudder.log.eventType.names." + event.eventType.serialize) + , "description" -> displayDescription(event).toString + , "hasDetails" -> boolToJsExp(event.details != ) + ) + } + } + + //////////////////// Display description/details of //////////////////// + + //convention: "X" means "ignore" + + def displayDescription(event:EventLog) = { + import linkUtil._ + def crDesc(x:EventLog, actionName: NodeSeq) = { + val id = (x.details \ "rule" \ "id").text + val name = (x.details \ "rule" \ "displayName").text + Text("Rule ") ++ { + if(id.size < 1) Text(name) + else {name} ++ actionName + } + } + + def piDesc(x:EventLog, actionName: NodeSeq) = { + val id = (x.details \ "directive" \ "id").text + val name = (x.details \ "directive" \ "displayName").text + Text("Directive ") ++ { + if(id.size < 1) Text(name) + else {name} ++ actionName + } + } + + def groupDesc(x:EventLog, actionName: NodeSeq) = { + val id = (x.details \ "nodeGroup" \ "id").text + val name = (x.details \ "nodeGroup" \ "displayName").text + Text("Group ") ++ { + if(id.size < 1) Text(name) + else {name} ++ actionName + } + } + + def nodeDesc(x:EventLog, actionName: NodeSeq) = { + val id = (x.details \\ "node" \ "id").text + val name = (x.details \\ "node" \ "hostname").text + Text("Node ") ++ { + if ((id.size < 1)||(actionName==Text(" deleted"))) Text(name) + else {name} ++ actionName + } + } + + def techniqueDesc(x:EventLog, actionName: NodeSeq) = { + val name = (x.details \ "activeTechnique" \ "techniqueName").text + Text("Technique %s".format(name)) ++ actionName + } + + def globalParamDesc(x:EventLog, actionName: NodeSeq) = { + val name = (x.details \ "globalParameter" \ "name").text + Text(s"Global Parameter ${name} ${actionName}") + } + + def changeRequestDesc(x:EventLog, actionName: NodeSeq) = { + val name = (x.details \ "changeRequest" \ "name").text + val idNode = (x.details \ "changeRequest" \ "id").text.trim + val xml = Try(idNode.toInt) match { + case Success(id) => + if (actionName==Text(" deleted")) + Text(name) + else + {name} + + case Catch(e) => + logger.error(s"could not translate ${idNode} to a correct chage request identifier: ${e.getMessage()}") + Text(name) + } + Text("Change request ") ++ xml ++ actionName + } + + def workflowStepDesc(x:EventLog) = { + logDetailsService.getWorkflotStepChange(x.details) match { + case Full(WorkflowStepChange(crId,from,to)) => + Text("Change request #") ++ + {crId} ++ + Text(s" status modified from ${from} to ${to}") + + case eb:EmptyBox => val fail = eb ?~! "could not display workflow step event log" + logger.error(fail.msg) + Text("Change request status modified") + } + } + + def apiAccountDesc(x:EventLog, actionName: NodeSeq) = { + val name = (x.details \ "apiAccount" \ "name").text + Text(s"API Account ${name} ${actionName}") + } + + event match { + case x:ActivateRedButton => Text("Stop Rudder agents on all nodes") + case x:ReleaseRedButton => Text("Start again Rudder agents on all nodes") + case x:AcceptNodeEventLog => nodeDesc(x, Text(" accepted")) + case x:RefuseNodeEventLog => nodeDesc(x, Text(" refused")) + case x:DeleteNodeEventLog => nodeDesc(x, Text(" deleted")) + case x:LoginEventLog => Text("User '%s' login".format(x.principal.name)) + case x:LogoutEventLog => Text("User '%s' logout".format(x.principal.name)) + case x:BadCredentialsEventLog => Text("User '%s' failed to login: bad credentials".format(x.principal.name)) + case x:AutomaticStartDeployement => Text("Automatically update policies") + case x:ManualStartDeployement => Text("Manually update policies") + case x:ApplicationStarted => Text("Rudder starts") + case x:ModifyRule => crDesc(x,Text(" modified")) + case x:DeleteRule => crDesc(x,Text(" deleted")) + case x:AddRule => crDesc(x,Text(" added")) + case x:ModifyDirective => piDesc(x,Text(" modified")) + case x:DeleteDirective => piDesc(x,Text(" deleted")) + case x:AddDirective => piDesc(x,Text(" added")) + case x:ModifyNodeGroup => groupDesc(x,Text(" modified")) + case x:DeleteNodeGroup => groupDesc(x,Text(" deleted")) + case x:AddNodeGroup => groupDesc(x,Text(" added")) + case x:ClearCacheEventLog => Text("Clear caches") + case x:UpdatePolicyServer => Text("Change Policy Server authorized network") + case x:ReloadTechniqueLibrary => Text("Technique library updated") + case x:ModifyTechnique => techniqueDesc(x, Text(" modified")) + case x:DeleteTechnique => techniqueDesc(x, Text(" deleted")) + case x:SuccessfulDeployment => Text("Successful policy update") + case x:FailedDeployment => Text("Failed policy update") + case x:ExportGroupsArchive => Text("New groups archive") + case x:ExportTechniqueLibraryArchive => Text("New Directive library archive") + case x:ExportRulesArchive => Text("New Rules archives") + case x:ExportParametersArchive => Text("New Parameters archives") + case x:ExportFullArchive => Text("New full archive") + case x:ImportGroupsArchive => Text("Restoring group archive") + case x:ImportTechniqueLibraryArchive => Text("Restoring Directive library archive") + case x:ImportRulesArchive => Text("Restoring Rules archive") + case x:ImportParametersArchive => Text("Restoring Parameters archive") + case x:ImportFullArchive => Text("Restoring full archive") + case _:AddChangeRequest => changeRequestDesc(event,Text(" created")) + case _:DeleteChangeRequest => changeRequestDesc(event,Text(" deleted")) + case _:ModifyChangeRequest => changeRequestDesc(event,Text(" modified")) + case _:Rollback => Text("Restore a previous state of configuration policy") + case x:WorkflowStepChanged => workflowStepDesc(x) + case x:AddGlobalParameter => globalParamDesc(x, Text(" added")) + case x:ModifyGlobalParameter => globalParamDesc(x, Text(" modified")) + case x:DeleteGlobalParameter => globalParamDesc(x, Text(" deleted")) + case x:CreateAPIAccountEventLog => apiAccountDesc(x, Text(" added")) + case x:ModifyAPIAccountEventLog => apiAccountDesc(x, Text(" modified")) + case x:DeleteAPIAccountEventLog => apiAccountDesc(x, Text(" deleted")) + case x:ModifyGlobalProperty => Text(s"Modify '${x.propertyName}' global property") + case x:ModifyNode => nodeDesc(x, Text(" modified")) + case _ => Text("Unknow event type") + } + } + + def displayDetails(event:EventLog,changeRequestId:Option[ChangeRequestId]): NodeSeq = { + + val groupLib = nodeGroupRepository.getFullGroupLibrary().toBox.openOr(return
    System error when trying to get the group library
    ) + val rootRuleCategory = ruleCatRepository.getRootCategory.toBox.openOr(return
    System error when trying to get the rule categories
    ) + + val generatedByChangeRequest = + changeRequestId match { + case None => NodeSeq.Empty + case Some(id) => + NodeSeq.Empty + //

    This change was introduced by change request {SHtml.a(() => S.redirectTo(linkUtil.changeRequestLink(id)),Text(s"#${id}"))}

    + } + def xmlParameters(eventId: Option[Int]) = { + eventId match { + case None => NodeSeq.Empty + case Some(id) => + + + } + } + + val reasonHtml = { + val r = event.eventDetails.reason.getOrElse("") + if(r == "") NodeSeq.Empty + else
    Reason: {r}
    + } + + def errorMessage(e:EmptyBox) = { + logger.debug(e ?~! "Error when parsing details.", e) + +
    +

    Details for that node were not in a recognized format. + Raw data are displayed next:

    + { xmlParameters(event.id) } +
    +
    + } + { + (event match { + /* + * bug in scalac : https://issues.scala-lang.org/browse/SI-6897 + * A workaround is to type with a Nodeseq + */ + case add:AddRule => + "*" #> { val xml : NodeSeq = logDetailsService.getRuleAddDetails(add.details) match { + case Full(addDiff) => +
    + { generatedByChangeRequest } + + { ruleDetails(crDetailsXML, addDiff.rule, groupLib, rootRuleCategory)} + { reasonHtml } + { xmlParameters(event.id) } +
    + case Failure(m,_,_) =>

    {m}

    + case e:EmptyBox => errorMessage(e) + } + xml + } + + case del:DeleteRule => + "*" #> { val xml : NodeSeq = logDetailsService.getRuleDeleteDetails(del.details) match { + case Full(delDiff) => +
    + + { generatedByChangeRequest } + { ruleDetails(crDetailsXML, delDiff.rule, groupLib, rootRuleCategory) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + case mod:ModifyRule => + val diffDisplayer = new DiffDisplayer(linkUtil) + "*" #> { val xml : NodeSeq = logDetailsService.getRuleModifyDetails(mod.details) match { + case Full(modDiff) => +
    + + { generatedByChangeRequest } +

    Rule overview:

    +
      +
    • Rule ID: { modDiff.id.value }
    • +
    • Name: { + modDiff.modName.map(diff => diff.newValue).getOrElse(modDiff.name) + }
    • +
    + { + val modCategory = modDiff.modCategory.map { + case SimpleDiff(oldOne,newOne) => +
  • Rule category changed:
  • ++ + diffDisplayer.displayRuleCategory(rootRuleCategory, oldOne, Some(newOne)) + } + + ( + "#name" #> mapSimpleDiff(modDiff.modName) & + "#category" #> modCategory & + "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivatedStatus) & + "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) & + "#shortDescription *" #> mapSimpleDiff(modDiff.modShortDescription) & + "#longDescription *" #> mapSimpleDiff(modDiff.modLongDescription) & + "#target" #> ( + modDiff.modTarget.map{ + case SimpleDiff(oldOnes,newOnes) => +
  • Group Targets changed:
  • ++ + diffDisplayer.displayRuleTargets(oldOnes.toSeq,newOnes.toSeq, groupLib) + } ) & + "#policies" #> ( + modDiff.modDirectiveIds.map{ + case SimpleDiff(oldOnes,newOnes) => +
  • Directives changed:
  • ++ + diffDisplayer.displayDirectiveChangeList(oldOnes.toSeq,newOnes.toSeq) + } ) + )(crModDetailsXML) + } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + ///////// Directive ///////// + + case x:ModifyDirective => + "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveModifyDetails(x.details) match { + case Full(modDiff) => +
    + + { generatedByChangeRequest } +

    Directive overview:

    +
      +
    • Directive ID: { modDiff.id.value }
    • +
    • Name: { + modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name) + }
    • +
    + {( + "#name" #> mapSimpleDiff(modDiff.modName, modDiff.id) & + "#priority *" #> mapSimpleDiff(modDiff.modPriority) & + "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivated) & + "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) & + "#shortDescription *" #> mapSimpleDiff(modDiff.modShortDescription) & + "#longDescription *" #> mapSimpleDiff(modDiff.modLongDescription) & + "#ptVersion *" #> mapSimpleDiff(modDiff.modTechniqueVersion) & + "#parameters" #> ( + modDiff.modParameters.map { diff => + "#diff" #> displaydirectiveInnerFormDiff(diff, event.id) + } + ) + )(piModDirectiveDetailsXML)} + { reasonHtml } + { xmlParameters(event.id) } +
    + case Failure(m, _, _) =>

    {m}

    + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:AddDirective => + "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveAddDetails(x.details) match { + case Full((diff,sectionVal)) => +
    + + { generatedByChangeRequest } + { directiveDetails(piDetailsXML, diff.techniqueName, + diff.directive, sectionVal) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml} + + case x:DeleteDirective => + "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveDeleteDetails(x.details) match { + case Full((diff,sectionVal)) => +
    + + { generatedByChangeRequest } + { directiveDetails(piDetailsXML, diff.techniqueName, + diff.directive, sectionVal) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + ///////// Node Group ///////// + + case x:ModifyNodeGroup => + "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupModifyDetails(x.details) match { + case Full(modDiff) => +
    + + { generatedByChangeRequest } +

    Group overview:

    +
      +
    • Node Group ID: { modDiff.id.value }
    • +
    • Name: { + modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name) + }
    • +
    + {( + "#name" #> mapSimpleDiff(modDiff.modName) & + "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivated) & + "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) & + "#isDynamic *" #> mapSimpleDiff(modDiff.modIsDynamic) & + "#shortDescription *" #> mapSimpleDiff(modDiff.modDescription) & + "#query" #> ( + modDiff.modQuery.map { diff => + val mapOptionQuery = (opt:Option[Query]) => + opt match { + case None => Text("None") + case Some(q) => Text(q.toJSONString) + } + + ".diffOldValue *" #> mapOptionQuery(diff.oldValue) & + ".diffNewValue *" #> mapOptionQuery(diff.newValue) + } + ) & + "#nodes" #> ( + modDiff.modNodeList.map { diff => + ".diffOldValue *" #> nodeGroupDetails(diff.oldValue) & + ".diffNewValue *" #> nodeGroupDetails(diff.newValue) + } + ) + )(groupModDetailsXML)} + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:AddNodeGroup => + "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupAddDetails(x.details) match { + case Full(diff) => +
    + + { generatedByChangeRequest } + { groupDetails(groupDetailsXML, diff.group) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:DeleteNodeGroup => + "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupDeleteDetails(x.details) match { + case Full(diff) => +
    + + { generatedByChangeRequest } + { groupDetails(groupDetailsXML, diff.group) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + ///////// Node Group ///////// + + case x:AcceptNodeEventLog => + "*" #> { val xml : NodeSeq = logDetailsService.getAcceptNodeLogDetails(x.details) match { + case Full(details) => +
    + +

    Node accepted overview:

    + { nodeDetails(details) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:RefuseNodeEventLog => + "*" #> { val xml : NodeSeq = logDetailsService.getRefuseNodeLogDetails(x.details) match { + case Full(details) => +
    + +

    Node refused overview:

    + { nodeDetails(details) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:DeleteNodeEventLog => + "*" #> { val xml : NodeSeq = logDetailsService.getDeleteNodeLogDetails(x.details) match { + case Full(details) => +
    + +

    Node deleted overview:

    + { nodeDetails(details) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + ////////// deployment ////////// + case x:SuccessfulDeployment => + "*" #> { val xml : NodeSeq = logDetailsService.getDeploymentStatusDetails(x.details) match { + case Full(SuccessStatus(id,started,ended,_)) => +
    + +

    Successful policy update:

    +
      +
    • ID: {id}
    • +
    • Start time: {DateFormaterService.getFormatedDate(started)}
    • +
    • End Time: {DateFormaterService.getFormatedDate(ended)}
    • +
    + { reasonHtml } + { xmlParameters(event.id) } +
    + case Full(_) => errorMessage(Failure("Unconsistant policy update status")) + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:FailedDeployment => + "*" #> { val xml : NodeSeq = logDetailsService.getDeploymentStatusDetails(x.details) match { + case Full(ErrorStatus(id,started,ended,failure)) => +
    + +

    Failed policy update:

    +
      +
    • ID: {id}
    • +
    • Start time: {DateFormaterService.getFormatedDate(started)}
    • +
    • End Time: {DateFormaterService.getFormatedDate(ended)}
    • +
    • Error stack trace: {failure.messageChain}
    • +
    + { reasonHtml } + { xmlParameters(event.id) } +
    + case Full(_) => errorMessage(Failure("Unconsistant policy update status")) + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:AutomaticStartDeployement => + "*" #> +
    + + { xmlParameters(event.id) } +
    + + ////////// change authorized networks ////////// + + case x:UpdatePolicyServer => + "*" #> { val xml : NodeSeq = logDetailsService.getUpdatePolicyServerDetails(x.details) match { + case Full(details) => + + def networksToXML(nets:Seq[String]) = { +
      { nets.map { n =>
    • {n}
    • } }
    + } + +
    + { + ( + ".diffOldValue *" #> networksToXML(details.oldNetworks) & + ".diffNewValue *" #> networksToXML(details.newNetworks) + )(authorizedNetworksXML) + } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + // Technique library reloaded + + case x:ReloadTechniqueLibrary => + "*" #> { val xml : NodeSeq = logDetailsService.getTechniqueLibraryReloadDetails(x.details) match { + case Full(details) => +
    + + The Technique library was reloaded and following Techniques were updated: +
      { details.map {technique => +
    • { "%s (version %s)".format(technique.name.value, technique.version.toString)}
    • + } }
    + { reasonHtml } + { xmlParameters(event.id) } +
    + + case e:EmptyBox => errorMessage(e) + } + xml } + + // Technique modified + case x:ModifyTechnique => + "*" #> { val xml : NodeSeq = logDetailsService.getTechniqueModifyDetails(x.details) match { + case Full(modDiff) => +
    + +

    Technique overview:

    +
      +
    • Technique ID: { modDiff.id.value }
    • +
    • Name: { modDiff.name }
    • +
    + {( + "#isEnabled *" #> mapSimpleDiff(modDiff.modIsEnabled) + ).apply(liModDetailsXML("isEnabled", "Activation status")) + } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + // Technique modified + case x:DeleteTechnique => + "*" #> { val xml : NodeSeq = logDetailsService.getTechniqueDeleteDetails(x.details) match { + case Full(techDiff) => +
    + + { techniqueDetails(techniqueDetailsXML, techDiff.technique) } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => errorMessage(e) + } + xml } + + // archiving & restoring + + case x:ExportEventLog => + "*" #> { val xml : NodeSeq = logDetailsService.getNewArchiveDetails(x.details, x) match { + case Full(gitArchiveId) => + displayExportArchiveDetails(gitArchiveId, xmlParameters(event.id)) + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:Rollback => + "*" #> { val xml : NodeSeq = logDetailsService.getRollbackDetails(x.details) match { + case Full(eventLogs) => + displayRollbackDetails(eventLogs,event.id.get) + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + xml } + + case x:ImportEventLog => + "*" #> { val xml : NodeSeq = logDetailsService.getRestoreArchiveDetails(x.details, x) match { + case Full(gitArchiveId) => + displayImportArchiveDetails(gitArchiveId, xmlParameters(event.id)) + case e:EmptyBox => errorMessage(e) + } + xml } + + case x:ChangeRequestEventLog => + "*" #> { logDetailsService.getChangeRequestDetails(x.details) match { + case Full(diff) => + val (name,desc) = x.id match { + case None => (Text(diff.changeRequest.info.name),Text(diff.changeRequest.info.description)) + case Some(id) => + val modName = displaySimpleDiff(diff.diffName, s"name${id}", diff.changeRequest.info.name) + val modDesc = displaySimpleDiff(diff.diffDescription, s"description${id}", diff.changeRequest.info.description) + (modName,modDesc) + } +
    +

    Change request details:

    +
      +
    • Id: {diff.changeRequest.id}
    • +
    • Name: {name}
    • +
    • Description: {desc}
    • +
    +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + case x:WorkflowStepChanged => + "*" #> { logDetailsService.getWorkflotStepChange(x.details) match { + case Full(step) => +
    +

    Change request status modified:

    +
      +
    • Id: {step.id}
    • +
    • From status: {step.from}
    • +
    • To status: {step.to}
    • + {reasonHtml} +
    +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + // Global parameters + + case x:AddGlobalParameter => + "*" #> { logDetailsService.getGlobalParameterAddDetails(x.details) match { + case Full(globalParamDiff) => +
    + + { generatedByChangeRequest } + { globalParameterDetails(globalParamDetailsXML, globalParamDiff.parameter)} + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + case x:DeleteGlobalParameter => + "*" #> { logDetailsService.getGlobalParameterDeleteDetails(x.details) match { + case Full(globalParamDiff) => +
    + + { generatedByChangeRequest } + { globalParameterDetails(globalParamDetailsXML, globalParamDiff.parameter)} + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + case mod:ModifyGlobalParameter => + "*" #> { logDetailsService.getGlobalParameterModifyDetails(mod.details) match { + case Full(modDiff) => +
    + + { generatedByChangeRequest } +

    Global Parameter overview:

    +
      +
    • Global Parameter name: { modDiff.name.value }
    • +
    + {( + "#name" #> modDiff.name.value & + "#value" #> mapSimpleDiff(modDiff.modValue) & + "#description *" #> mapSimpleDiff(modDiff.modDescription) & + "#overridable *" #> mapSimpleDiff(modDiff.modOverridable) + )(globalParamModDetailsXML) + } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + case x:CreateAPIAccountEventLog => + "*" #> { logDetailsService.getApiAccountAddDetails(x.details) match { + case Full(apiAccountDiff) => +
    + + { generatedByChangeRequest } + { apiAccountDetails(apiAccountDetailsXML, apiAccountDiff.apiAccount)} + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + case x:DeleteAPIAccountEventLog => + "*" #> { logDetailsService.getApiAccountDeleteDetails(x.details) match { + case Full(apiAccountDiff) => +
    + + { generatedByChangeRequest } + { apiAccountDetails(apiAccountDetailsXML, apiAccountDiff.apiAccount)} + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + case mod:ModifyAPIAccountEventLog => + "*" #> { logDetailsService.getApiAccountModifyDetails(mod.details) match { + case Full(apiAccountDiff) => + +
    + + { generatedByChangeRequest } +

    API account overview:

    +
      +
    • Account ID: { apiAccountDiff.id.value }
    • +
    + {( + "#name" #> mapSimpleDiff(apiAccountDiff.modName) & + "#token" #> mapSimpleDiff(apiAccountDiff.modToken) & + "#description *" #> mapSimpleDiff(apiAccountDiff.modDescription) & + "#isEnabled *" #> mapSimpleDiff(apiAccountDiff.modIsEnabled) & + "#tokenGenerationDate *" #> mapSimpleDiff(apiAccountDiff.modTokenGenerationDate) & + "#expirationDate *" #> mapSimpleDiffT[Option[DateTime]](apiAccountDiff.modExpirationDate, _.fold("")(_.toString(ISODateTimeFormat.dateTime())) + ) & + "#accountKind *" #> mapSimpleDiff(apiAccountDiff.modAccountKind) & + //make list of ACL unsderstandable + "#acls *" #> mapSimpleDiff(apiAccountDiff.modAccountAcl.map(o => { + val f = (l: List[ApiAclElement]) => l.sortBy(_.path.value).map(x => s"[${x.actions.map(_.name.toUpperCase()).mkString(",")}] ${x.path.value}").mkString(" | ") + SimpleDiff(f(o.oldValue), f(o.newValue)) + })) + )(apiAccountModDetailsXML) + } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + case mod:ModifyGlobalProperty => + "*" #> { logDetailsService.getModifyGlobalPropertyDetails(mod.details) match { + case Full((oldProp,newProp)) => +
    +

    Global property overview:

    +
      +
    • Name: { mod.propertyName }
    • +
    • Value: + { val diff = Some(SimpleDiff(oldProp.value,newProp.value)) + displaySimpleDiff(diff,"value",newProp.value) }
    • +
    + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + // Node modifiction + case mod:ModifyNode => + "*" #> { logDetailsService.getModifyNodeDetails(mod.details) match { + case Full(modDiff) => + logger.info(modDiff.modAgentRun) +
    + + { generatedByChangeRequest } +

    Node '{modDiff.id.value}' modified:

    +
      +
    • Node ID:{modDiff.id.value}
    • +
    + { + mapComplexDiff(modDiff.modAgentRun){ (optAr:Option[AgentRunInterval]) => + optAr match { + case None => No value + case Some(ar) => agentRunDetails(ar) + } + } + } + { + mapComplexDiff(modDiff.modHeartbeat){ (optHb:Option[HeartbeatConfiguration]) => + optHb match { + case None => No value + case Some(hb) => heartbeatDetails(hb) + } + } + } + { + mapComplexDiff(modDiff.modProperties){ (props:Seq[NodeProperty]) => + nodePropertiesDetails(props) + } + } + { + mapComplexDiff(modDiff.modPolicyMode){ (optMode:Option[PolicyMode]) => + optMode match { + case None => Use global policy mode + case Some(mode) => {mode.name} + } + } + } + { reasonHtml } + { xmlParameters(event.id) } +
    + case e:EmptyBox => logger.warn(e) + errorMessage(e) + } + } + + // other case: do not display details at all + case _ => "*" #> "" + + })(event.details)} + } + + private[this] def agentRunDetails(ar: AgentRunInterval): NodeSeq = { + ( + "#override" #> ar.overrides.map(_.toString()).getOrElse("false") + & "#interval" #> ar.interval + & "#startMinute" #> ar.startMinute + & "#startHour" #> ar.startHour + & "#splaytime" #> ar.splaytime + ).apply( +
      +
    • Override global value:
    • +
    • Period:
    • +
    • Start at minute:
    • +
    • Start at hour:
    • +
    • Splay time:
    • +
    + ) + } + + private[this] def heartbeatDetails(hb: HeartbeatConfiguration): NodeSeq = { + ( + "#override" #> hb.overrides + & "#interval" #> hb.heartbeatPeriod + ).apply( +
      +
    • Override global value:
    • +
    • Period:
    • +
    + ) + } + + private[this] def nodePropertiesDetails(props: Seq[NodeProperty]): NodeSeq = { + ( + "#kv *" #> props.map { p => s"${p.name}: ${p.value}" } + ).apply( +
      +
    • +
    + ) + } + + private[this] def displaySimpleDiff[T] ( + diff : Option[SimpleDiff[T]] + , name : String + , default : String + ) = diff.map(value => displayFormDiff(value, name)).getOrElse(Text(default)) + private[this] def displayFormDiff[T](diff: SimpleDiff[T], name:String)(implicit fun: T => String = (t:T) => t.toString) = { +
    {fun(diff.oldValue)}
    +
    {fun(diff.newValue)}
    +
      ++
    +      Script(
    +        OnLoad(
    +          JsRaw(
    +            s"""
    +            var before = "before${name}";
    +            var after  = "after${name}";
    +            var result = "result${name}";
    +            makeDiff(before,after,result);"""
    +          )
    +        )
    +      )
    +  }
    +  private[this] def displaydirectiveInnerFormDiff(diff: SimpleDiff[SectionVal], eventId: Option[Int]): NodeSeq = {
    +    eventId match {
    +      case None => NodeSeq.Empty
    +      case Some(id) =>
    +        (
    +          
    {xmlPretty.format(SectionVal.toXml(diff.oldValue))}
    +
    {xmlPretty.format(SectionVal.toXml(diff.newValue))}
    +
    
    +          ) ++ Script(OnLoad(JsRaw(s"""
    +            var before = "before${id}";
    +            var after  = "after${id}";
    +            var result = "result${id}";
    +            makeDiff(before,after,result);
    +            """)))
    +    }
    +  }
    +
    +  private[this] def displayExportArchiveDetails(gitArchiveId: GitArchiveId, rawData: NodeSeq) =
    +    
    +

    Details of the new archive:

    +
      +
    • Git path of the archive: {gitArchiveId.path.value}
    • +
    • Commit ID (hash): {gitArchiveId.commit.value}
    • +
    • Commiter name: {gitArchiveId.commiter.getName}
    • +
    • Commiter email: {gitArchiveId.commiter.getEmailAddress}
    • +
    + {rawData} +
    + + private[this] def displayImportArchiveDetails(gitCommitId: GitCommitId, rawData: NodeSeq) = +
    +

    Details of the restored archive:

    +
      +
    • Commit ID (hash): {gitCommitId.value}
    • +
    + {rawData} +
    + + private[this] def nodeGroupDetails(nodes: Set[NodeId]): NodeSeq = { + val res = nodes.toSeq match { + case Seq() => NodeSeq.Empty + case t => + nodes + .toSeq + .map { id => linkUtil.createNodeLink(id) } + .reduceLeft[NodeSeq]((a,b) => a ++ ++ b) + } + ( + ".groupSeparator" #> ", " + ).apply(res) + + } + + private[this] def ruleDetails(xml:NodeSeq, rule:Rule, groupLib: FullNodeGroupCategory, rootRuleCategory: RuleCategory) = { + val diffDisplayer = new DiffDisplayer(linkUtil) + ( + "#ruleID" #> rule.id.value & + "#ruleName" #> rule.name & + "#category" #> diffDisplayer.displayRuleCategory(rootRuleCategory, rule.categoryId, None) & + "#target" #> diffDisplayer.displayRuleTargets(rule.targets.toSeq, rule.targets.toSeq, groupLib) & + "#policy" #> diffDisplayer.displayDirectiveChangeList(rule.directiveIds.toSeq, rule.directiveIds.toSeq) & + "#isEnabled" #> rule.isEnabled & + "#isSystem" #> rule.isSystem & + "#shortDescription" #> rule.shortDescription & + "#longDescription" #> rule.longDescription + ) (xml) + } + + private[this] def directiveDetails(xml:NodeSeq, ptName: TechniqueName, directive:Directive, sectionVal:SectionVal) = ( + "#directiveID" #> directive.id.value & + "#directiveName" #> directive.name & + "#ptVersion" #> directive.techniqueVersion.toString & + "#ptName" #> ptName.value & + "#ptVersion" #> directive.techniqueVersion.toString & + "#ptName" #> ptName.value & + "#priority" #> directive.priority & + "#isEnabled" #> directive.isEnabled & + "#isSystem" #> directive.isSystem & + "#shortDescription" #> directive.shortDescription & + "#longDescription" #> directive.longDescription + )(xml) + + private[this] def groupDetails(xml:NodeSeq, group: NodeGroup) = ( + "#groupID" #> group.id.value & + "#groupName" #> group.name & + "#shortDescription" #> group.description & + "#shortDescription" #> group.description & + "#query" #> (group.query match { + case None => Text("None") + case Some(q) => Text(q.toJSONString) + } ) & + "#isDynamic" #> group.isDynamic & + "#nodes" #>( + { + val l = group.serverList.toList + l match { + case Nil => Text("None") + case _ => l + .map(id => {id.value}) + .reduceLeft[NodeSeq]((a,b) => a ++ ++ b) + } + } + ) & + "#isEnabled" #> group.isEnabled & + "#isSystem" #> group.isSystem + )(xml) + + private[this] def techniqueDetails(xml: NodeSeq, technique: ActiveTechnique) = ( + "#techniqueID" #> technique.id.value & + "#techniqueName" #> technique.techniqueName.value & + "#isEnabled" #> technique.isEnabled & + "#isSystem" #> technique.isSystem + )(xml) + + private[this] def globalParameterDetails(xml: NodeSeq, globalParameter: GlobalParameter) = ( + "#name" #> globalParameter.name.value & + "#value" #> globalParameter.value & + "#description" #> globalParameter.description & + "#overridable" #> globalParameter.overridable + )(xml) + + private[this] def apiAccountDetails(xml: NodeSeq, apiAccount: ApiAccount) = ( + "#id" #> apiAccount.id.value & + "#name" #> apiAccount.name.value & + "#token" #> apiAccount.token.value & + "#description" #> apiAccount.description & + "#isEnabled" #> apiAccount.isEnabled & + "#creationDate" #> DateFormaterService.getFormatedDate(apiAccount.creationDate) & + "#tokenGenerationDate" #> DateFormaterService.getFormatedDate(apiAccount.tokenGenerationDate) + )(xml) + + + private[this] def mapSimpleDiffT[T](opt:Option[SimpleDiff[T]], t: T => String) = opt.map { diff => + ".diffOldValue *" #> t(diff.oldValue) & + ".diffNewValue *" #> t(diff.newValue) + } + + private[this] def mapSimpleDiff[T](opt:Option[SimpleDiff[T]]) = mapSimpleDiffT(opt, (x:T) => x.toString) + + private[this] def mapSimpleDiff[T](opt:Option[SimpleDiff[T]], id: DirectiveId) = opt.map { diff => + ".diffOldValue *" #> diff.oldValue.toString & + ".diffNewValue *" #> diff.newValue.toString & + "#directiveID" #> id.value + } + + private[this] def mapComplexDiff[T](opt:Option[SimpleDiff[T]])(display: T => NodeSeq) = { + opt match { + case None => NodeSeq.Empty + case Some(diff) => + ( + ".diffOldValue *" #> display(diff.oldValue) & + ".diffNewValue *" #> display(diff.newValue) + ).apply( +
      +
    • Old value: old value
    • +
    • New value: new value
    • +
    + ) + } + } + + private[this] def nodeDetails(details:InventoryLogDetails) = ( + "#nodeID" #> details.nodeId.value & + "#nodeName" #> details.hostname & + "#os" #> details.fullOsName & + "#version" #> DateFormaterService.getFormatedDate(details.inventoryVersion) + )( +
      +
    • Node ID:
    • +
    • Name:
    • +
    • Operating System:
    • +
    • Date inventory last received:
    • +
    + ) + private[this] val crDetailsXML = +
    +

    Rule overview:

    +
      +
    • ID: 
    • +
    • Name: 
    • +
    • Category: 
    • +
    • Description: 
    • +
    • Target: 
    • +
    • Directives: 
    • +
    • Enabled: 
    • +
    • System: 
    • +
    • Details: 
    • +
    +
    + + private[this] val piDetailsXML = +
    +

    Directive overview:

    +
      +
    • ID: 
    • +
    • Name: 
    • +
    • Description: 
    • +
    • Technique name: 
    • +
    • Technique version: 
    • +
    • Priority: 
    • +
    • Enabled: 
    • +
    • System: 
    • +
    • Details: 
    • +
    +
    + + private[this] val groupDetailsXML = +
    +

    Group overview:

    +
      +
    • ID:
    • +
    • Name:
    • +
    • Description:
    • +
    • Enabled:
    • +
    • Dynamic:
    • +
    • System:
    • +
    • Query:
    • +
    • Node list:
    • +
    +
    + + private[this] val techniqueDetailsXML = +
    +

    Technique overview:

    +
      +
    • ID: 
    • +
    • Name: 
    • +
    • Enabled: 
    • +
    • System: 
    • +
    +
    + + private[this] val globalParamDetailsXML = +
    +

    Global Parameter overview:

    +
      +
    • Name: 
    • +
    • Value: 
    • +
    • Description: 
    • +
    • Overridable: 
    • +
    +
    + + private[this] val apiAccountDetailsXML = +
    +

    API account overview:

    +
      +
    • Rudder ID:
    • +
    • Name: 
    • +
    • Token: 
    • +
    • Description: 
    • +
    • Enabled: 
    • +
    • Creation date: 
    • +
    • Token Generation date: 
    • +
    • Token Expiration date: 
    • +
    • Account Kind: 
    • +
    • ACLs: 
    • +
    +
    + + private[this] val apiAccountModDetailsXML = + + {liModDetailsXML("name", "Name")} + {liModDetailsXML("token", "Token")} + {liModDetailsXML("description", "Description")} + {liModDetailsXML("isEnabled", "Enabled")} + {liModDetailsXML("tokenGenerationDate", "Token Generation Date")} + {liModDetailsXML("expirationDate", "Token Expiration Date")} + {liModDetailsXML("accountKind", "Account Kind")} + {liModDetailsXML("acls", "ACL list")} + + + + private[this] def liModDetailsXML(id:String, name:String) = ( +
    + {name} changed: +
      +
    • Old value: old value
    • +
    • New value: new value
    • +
    +
    + ) + + private[this] def liModDirectiveDetailsXML(id:String, name:String) = ( +
    + {name} changed: +
      +
    • Differences:
    • +
    +
    + ) + + private[this] val groupModDetailsXML = + + {liModDetailsXML("name", "Name")} + {liModDetailsXML("shortDescription", "Description")} + {liModDetailsXML("nodes", "Node list")} + {liModDetailsXML("query", "Query")} + {liModDetailsXML("isDynamic", "Dynamic group")} + {liModDetailsXML("isEnabled", "Activation status")} + {liModDetailsXML("isSystem", "System")} + + + private[this] val crModDetailsXML = + + {liModDetailsXML("name", "Name")} + {liModDetailsXML("category", "Category")} + {liModDetailsXML("shortDescription", "Description")} + {liModDetailsXML("longDescription", "Details")} + {liModDetailsXML("target", "Target")} + {liModDetailsXML("policies", "Directives")} + {liModDetailsXML("isEnabled", "Activation status")} + {liModDetailsXML("isSystem", "System")} + + + private[this] val piModDirectiveDetailsXML = + + {liModDetailsXML("name", "Name")} + {liModDetailsXML("shortDescription", "Description")} + {liModDetailsXML("longDescription", "Details")} + {liModDetailsXML("ptVersion", "Target")} + {liModDirectiveDetailsXML("parameters", "Policy parameters")} + {liModDetailsXML("priority", "Priority")} + {liModDetailsXML("isEnabled", "Activation status")} + {liModDetailsXML("isSystem", "System")} + + + private[this] val globalParamModDetailsXML = + + {liModDetailsXML("name", "Name")} + {liModDetailsXML("value", "Value")} + {liModDetailsXML("description", "Description")} + {liModDetailsXML("overridable", "Overridable")} + + + + private[this] def displayRollbackDetails(rollbackInfo:RollbackInfo,id:Int) = { + val rollbackedEvents = rollbackInfo.rollbacked +
    +
    +
    +

    Details of the rollback:

    +
    + A rollback to {rollbackInfo.rollbackType} event + { SHtml.a(() => + SetHtml("currentId%s".format(id),Text(rollbackInfo.target.id.toString)) & + JsRaw("""$('#%1$s').dataTable().fnFilter("%2$s|%3$s",0,true,false); + $("#cancel%3$s").show(); + scrollToElement('%2$s', ".rudder_col"); + if($('#%2$s').prop('open') != "opened") + $('#%2$s').click();""".format(gridName,rollbackInfo.target.id,id)) + , Text(rollbackInfo.target.id.toString) + ) + } has been completed. + +

    + Events that were rollbacked can be consulted in the table below. +
    + Those events are no longer applied by the configuration policy. + +
    + + + + + + + + + + + {rollbackedEvents.map{ ev => + + + + + + + } } + +
    IDDateActorEvent type
    + {SHtml.a(() => + SetHtml("currentId%s".format(id),Text(ev.id.toString)) & + JsRaw("""$('#%1$s').dataTable().fnFilter("%2$s|%3$s",0,true,false); + $("#cancel%3$s").show(); + scrollToElement('%2$s', ".rudder_col"); + if($('#%2$s').prop('open') != "opened") + $('#%2$s').click();""".format(gridName,ev.id,id)) + , Text(ev.id.toString) + ) + } + {ev.date} {ev.author} {S.?("rudder.log.eventType.names." + ev.eventType)}
    + +
    + +
    +
    +
    ++ Script(JsRaw(s""" + $$('#rollbackTable${id}').dataTable({ + "asStripeClasses": [ 'color1', 'color2' ], + "bAutoWidth": false, + "bFilter" :true, + "bPaginate" :true, + "bLengthChange": true, + "bStateSave": true, + "fnStateSave": function (oSettings, oData) { + localStorage.setItem( 'DataTables_rollbackTable${id}', JSON.stringify(oData) ); + }, + "fnStateLoad": function (oSettings) { + return JSON.parse( localStorage.getItem('DataTables_rollbackTable${id}') ); + }, + "sPaginationType": "full_numbers", + "bJQueryUI": true, + "oLanguage": { + "sSearch": "" + }, + "aaSorting":[], + "aoColumns": [ + { "sWidth": "100px" } + , { "sWidth": "100px" } + , { "sWidth": "100px" } + , { "sWidth": "100px" } + ], + "sDom": '<"dataTables_wrapper_top"f>rt<"dataTables_wrapper_bottom"lip>' + }); + """)) + } + + private[this] def authorizedNetworksXML() = ( +
    + Networks authorized on policy server were updated: + + + + + + + + +
    from:to:
    old valuenew value
    +
    + ) + + trait RollBackAction { + def name : String + def op : String + def action : (EventLog,PersonIdent,Seq[EventLog],EventLog) => Box[GitCommitId] + def selectRollbackedEventsRequest(id:Int) = s" id ${op} ${id} and modificationid IS NOT NULL" + } + + case object RollbackTo extends RollBackAction{ + val name = "after" + val op = ">" + def action = modificationService.restoreToEventLog _ + } + + case object RollbackBefore extends RollBackAction{ + val name = "before" + val op = ">=" + def action = modificationService.restoreBeforeEventLog _ + } + +} diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/JsTableData.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/JsTableData.scala new file mode 100644 index 00000000000..4baa7aeaedf --- /dev/null +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/JsTableData.scala @@ -0,0 +1,71 @@ +/* +************************************************************************************* +* Copyright 2014 Normation SAS +************************************************************************************* +* +* This file is part of Rudder. +* +* Rudder is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* In accordance with the terms of section 7 (7. Additional Terms.) of +* the GNU General Public License version 3, the copyright holders add +* the following Additional permissions: +* Notwithstanding to the terms of section 5 (5. Conveying Modified Source +* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General +* Public License version 3, when you create a Related Module, this +* Related Module is not considered as a part of the work and may be +* distributed under the license agreement of your choice. +* A "Related Module" means a set of sources files including their +* documentation that, without modification of the Source Code, enables +* supplementary functions or services in addition to those offered by +* the Software. +* +* Rudder is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Rudder. If not, see . + +* +************************************************************************************* +*/ + +package com.normation.rudder.web.services + +import net.liftweb.http.js.JsExp +import net.liftweb.http.js.JsObj +import net.liftweb.http.js.JE.JsArray +import com.normation.rudder.domain.reports.ComplianceLevel +import net.liftweb.common.Loggable + + +/* + * That class represent a Line in a DataTable. + */ +trait JsTableLine extends Loggable { + + def json : JsObj + + // this is needed because DataTable doesn't escape HTML element when using table.rows.add + def escapeHTML(s: String): JsExp = JsExp.strToJsExp(xml.Utility.escape(s)) + + import com.normation.rudder.domain.reports.ComplianceLevelSerialisation._ + def jsCompliance(compliance: ComplianceLevel) = compliance.toJsArray() + +} + +/* + * That class a set of Data to use in datatable + * It should be used as data parameter when creating datatable in javascript + */ +case class JsTableData[T <: JsTableLine] ( lines : List[T] ) { + + def json = JsArray(lines.map(_.json)) + +} + diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderUserDetails.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderUserDetails.scala new file mode 100644 index 00000000000..7af61571633 --- /dev/null +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderUserDetails.scala @@ -0,0 +1,64 @@ +package com.normation.rudder.web.services + +import java.util.Collection +import com.normation.rudder.{AuthorizationType, Rights, Role, RudderAccount} +import com.normation.rudder.api.ApiAuthorization +import com.normation.utils.HashcodeCaching +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.UserDetails +import scala.collection.JavaConverters.asJavaCollectionConverter + +/** + * We don't use at all Spring Authority to implements + * our authorizations. + * That because we want something more typed than String for + * authority, and as a bonus, that allows to be able to switch + * from Spring more easily + * + * So we have one Authority type known by Spring Security for + * authenticated user: ROLE_USER + * And one other for API accounts: ROLE_REMOTE + */ +sealed trait RudderAuthType { + def grantedAuthorities: Collection[GrantedAuthority] +} + + +final object RudderAuthType { + // build a GrantedAuthority from the string + private def buildAuthority(s: String): Collection[GrantedAuthority] = { + Seq(new GrantedAuthority { override def getAuthority: String = s }).asJavaCollection + } + + final case object User extends RudderAuthType { + override val grantedAuthorities = buildAuthority("ROLE_USER") + } + final case object Api extends RudderAuthType { + override val grantedAuthorities = buildAuthority("ROLE_REMOTE") + + val apiRudderRights = new Rights(AuthorizationType.NoRights) + val apiRudderRole: Set[Role] = Set(Role.NoRights) + } +} + +/** + * Our simple model for for user authentication and authorizations. + * Note that authorizations are not managed by spring, but by the + * 'authz' token of RudderUserDetail. + */ +final case class RudderUserDetail( + account : RudderAccount + , roles : Set[Role] + , apiAuthz: ApiAuthorization +) extends UserDetails with HashcodeCaching { + // merge roles rights + val authz = new Rights(roles.flatMap(_.rights.authorizationTypes).toSeq:_*) + override val (getUsername, getPassword, getAuthorities) = account match { + case RudderAccount.User(login, password) => (login , password , RudderAuthType.User.grantedAuthorities) + case RudderAccount.Api(api) => (api.name.value, api.token.value, RudderAuthType.Api.grantedAuthorities) + } + override val isAccountNonExpired = true + override val isAccountNonLocked = true + override val isCredentialsNonExpired = true + override val isEnabled = true +} \ No newline at end of file diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala index 669d74ede5f..1e2660b2e43 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala @@ -282,6 +282,7 @@ class Boot extends Loggable { LiftRules.statelessDispatch.append(RudderConfig.restQuicksearch) LiftRules.statelessDispatch.append(RudderConfig.restCompletion) LiftRules.statelessDispatch.append(RudderConfig.sharedFileApi) + LiftRules.statelessDispatch.append(RudderConfig.eventLogApi) // REST API (all public/internal API) // we need to add "info" API here to have all used API (even plugins) diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala index b92eeb2909a..f21ecb934da 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala @@ -806,8 +806,10 @@ object RudderConfig extends Loggable { api } + // Internal APIs val sharedFileApi = new SharedFilesAPI(restExtractorService,RUDDER_DIR_SHARED_FILES_FOLDER) + val eventLogApi= new EventLogAPI(eventLogRepository, restExtractorService, eventListDisplayerImpl) lazy val asyncWorkflowInfo = new AsyncWorkflowInfo lazy val configService: ReadConfigService with UpdateConfigService = { diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala index fb67f5e6ce2..4fe1e4406d5 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala @@ -36,51 +36,27 @@ */ package com.normation.rudder.web.services -import scala.xml._ +import com.normation.box._ import com.normation.eventlog.EventLog -import com.normation.rudder.domain.eventlog._ -import net.liftweb.common._ -import net.liftweb.util.Helpers._ -import com.normation.rudder.domain.policies._ -import com.normation.rudder.domain.nodes._ +import com.normation.rudder.repository._ +import com.normation.rudder.rule.category.RoRuleCategoryRepository import com.normation.rudder.services.eventlog.EventLogDetailsService +import com.normation.rudder.services.modification.ModificationService +import com.normation.rudder.services.nodes.NodeInfoService +import com.normation.rudder.services.user.PersonIdentService import com.normation.rudder.web.components.DateFormaterService -import com.normation.cfclerk.domain.TechniqueName -import com.normation.inventory.domain.NodeId -import com.normation.rudder.domain.queries.Query -import net.liftweb.http.js._ +import com.normation.rudder.web.model.LinkUtil +import net.liftweb.common._ +import net.liftweb.http.{S, SHtml} import net.liftweb.http.js.JE._ import net.liftweb.http.js.JsCmds._ -import net.liftweb.http.SHtml -import net.liftweb.http.S -import com.normation.rudder.batch.SuccessStatus -import com.normation.rudder.batch.ErrorStatus -import com.normation.rudder.repository._ -import com.normation.rudder.services.nodes.NodeInfoService -import com.normation.rudder.services.modification.ModificationService -import com.normation.rudder.services.user.PersonIdentService -import com.normation.rudder.web.model.CurrentUser -import org.eclipse.jgit.lib.PersonIdent -import org.joda.time.DateTime -import com.normation.rudder.services.eventlog.RollbackInfo -import com.normation.rudder.domain.workflows.ChangeRequestId - -import scala.util.Try -import scala.util.Success -import scala.util.{Failure => Catch} -import com.normation.rudder.domain.eventlog.WorkflowStepChanged -import com.normation.rudder.domain.workflows.WorkflowStepChange -import com.normation.rudder.domain.parameters._ -import com.normation.rudder.api._ +import net.liftweb.http.js._ import net.liftweb.json._ -import com.normation.rudder.reports.HeartbeatConfiguration -import com.normation.rudder.reports.AgentRunInterval -import com.normation.rudder.rule.category.RuleCategory -import com.normation.rudder.rule.category.RoRuleCategoryRepository +import net.liftweb.util.Helpers._ +import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat -import com.normation.rudder.web.model.LinkUtil -import org.joda.time.format.ISODateTimeFormat -import com.normation.box._ + +import scala.xml._ /** * Used to display the event list, in the pending modification (AsyncDeployment), @@ -112,9 +88,7 @@ class EventListDisplayer( val el = events.map(EventLogLine(_)).toList.sortWith(_.event.creationDate.getMillis > _.event.creationDate.getMillis) if(el.size > limit) JsTableData(el.take(limit)) else JsTableData(el) } - val limitMsg = if(events.size > limit)
    {limit} results taken over {events.size} results
    else
    - val call = JsRaw(s"refreshTable('${gridName}',${lines.json.toJsCmd})") & SetHtml("limitEventDisplayer", limitMsg) - call + JsRaw(s"refreshTable('${gridName}',${lines.json.toJsCmd})") case eb : EmptyBox => val fail = eb ?~! "Could not get latest event logs" logger.error(fail.messageChain) @@ -127,7 +101,6 @@ class EventListDisplayer( displayEvents(refreshEvents()) } - def getEventsInterval(jsonInterval: String): JsCmd = { import java.sql.Timestamp val format = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss") @@ -178,7 +151,6 @@ class EventListDisplayer( createEventLogTable('${gridName}',[], '${S.contextPath}', refreshEventLogs, pickEventLogsInInterval) refreshEventLogs(); """))) - } /* @@ -187,1516 +159,18 @@ class EventListDisplayer( * , "date": date the event log was produced [Date/String] * , "actor": Name of the actor making the event [String] * , "type" : Type of the event log [String] - * , "description" : Description of the event [String] - * , "details" : function/ajax call, setting the details content, takes the id of the element to set [Function(String)] * , "hasDetails" : do our event needs to display details (do we need to be able to open the row [Boolean] * } */ case class EventLogLine(event : EventLog) extends JsTableLine { - val detailsCallback = { - AnonFunc("details",SHtml.ajaxCall(JsVar("details"), {(abc) => - val crId = event.id.flatMap(repos.getEventLogWithChangeRequest(_).toBox match { - case Full(Some((_,crId))) => crId - case _ => None - }) - SetHtml(abc,displayDetails(event,crId)) - } - ) - ) - } val json = { JsObj( "id" -> (event.id.map(_.toString).getOrElse("Unknown"): String) , "date" -> DateFormaterService.getFormatedDate(event.creationDate) , "actor" -> event.principal.name , "type" -> S.?("rudder.log.eventType.names." + event.eventType.serialize) - , "description" -> displayDescription(event).toString - , "details" -> detailsCallback , "hasDetails" -> boolToJsExp(event.details != ) ) } } - - //////////////////// Display description/details of //////////////////// - - //convention: "X" means "ignore" - - def displayDescription(event:EventLog) = { - import linkUtil._ - def crDesc(x:EventLog, actionName: NodeSeq) = { - val id = (x.details \ "rule" \ "id").text - val name = (x.details \ "rule" \ "displayName").text - Text("Rule ") ++ { - if(id.size < 1) Text(name) - else {name} ++ actionName - } - } - - def piDesc(x:EventLog, actionName: NodeSeq) = { - val id = (x.details \ "directive" \ "id").text - val name = (x.details \ "directive" \ "displayName").text - Text("Directive ") ++ { - if(id.size < 1) Text(name) - else {name} ++ actionName - } - } - - def groupDesc(x:EventLog, actionName: NodeSeq) = { - val id = (x.details \ "nodeGroup" \ "id").text - val name = (x.details \ "nodeGroup" \ "displayName").text - Text("Group ") ++ { - if(id.size < 1) Text(name) - else {name} ++ actionName - } - } - - def nodeDesc(x:EventLog, actionName: NodeSeq) = { - val id = (x.details \\ "node" \ "id").text - val name = (x.details \\ "node" \ "hostname").text - Text("Node ") ++ { - if ((id.size < 1)||(actionName==Text(" deleted"))) Text(name) - else {name} ++ actionName - } - } - - def techniqueDesc(x:EventLog, actionName: NodeSeq) = { - val name = (x.details \ "activeTechnique" \ "techniqueName").text - Text("Technique %s".format(name)) ++ actionName - } - - def globalParamDesc(x:EventLog, actionName: NodeSeq) = { - val name = (x.details \ "globalParameter" \ "name").text - Text(s"Global Parameter ${name} ${actionName}") - } - - def changeRequestDesc(x:EventLog, actionName: NodeSeq) = { - val name = (x.details \ "changeRequest" \ "name").text - val idNode = (x.details \ "changeRequest" \ "id").text.trim - val xml = Try(idNode.toInt) match { - case Success(id) => - if (actionName==Text(" deleted")) - Text(name) - else - {name} - - case Catch(e) => - logger.error(s"could not translate ${idNode} to a correct chage request identifier: ${e.getMessage()}") - Text(name) - } - Text("Change request ") ++ xml ++ actionName - } - - def workflowStepDesc(x:EventLog) = { - logDetailsService.getWorkflotStepChange(x.details) match { - case Full(WorkflowStepChange(crId,from,to)) => - Text("Change request #") ++ - {crId} ++ - Text(s" status modified from ${from} to ${to}") - - case eb:EmptyBox => val fail = eb ?~! "could not display workflow step event log" - logger.error(fail.msg) - Text("Change request status modified") - } - } - - def apiAccountDesc(x:EventLog, actionName: NodeSeq) = { - val name = (x.details \ "apiAccount" \ "name").text - Text(s"API Account ${name} ${actionName}") - } - - event match { - case x:ActivateRedButton => Text("Stop Rudder agents on all nodes") - case x:ReleaseRedButton => Text("Start again Rudder agents on all nodes") - case x:AcceptNodeEventLog => nodeDesc(x, Text(" accepted")) - case x:RefuseNodeEventLog => nodeDesc(x, Text(" refused")) - case x:DeleteNodeEventLog => nodeDesc(x, Text(" deleted")) - case x:LoginEventLog => Text("User '%s' login".format(x.principal.name)) - case x:LogoutEventLog => Text("User '%s' logout".format(x.principal.name)) - case x:BadCredentialsEventLog => Text("User '%s' failed to login: bad credentials".format(x.principal.name)) - case x:AutomaticStartDeployement => Text("Automatically update policies") - case x:ManualStartDeployement => Text("Manually update policies") - case x:ApplicationStarted => Text("Rudder starts") - case x:ModifyRule => crDesc(x,Text(" modified")) - case x:DeleteRule => crDesc(x,Text(" deleted")) - case x:AddRule => crDesc(x,Text(" added")) - case x:ModifyDirective => piDesc(x,Text(" modified")) - case x:DeleteDirective => piDesc(x,Text(" deleted")) - case x:AddDirective => piDesc(x,Text(" added")) - case x:ModifyNodeGroup => groupDesc(x,Text(" modified")) - case x:DeleteNodeGroup => groupDesc(x,Text(" deleted")) - case x:AddNodeGroup => groupDesc(x,Text(" added")) - case x:ClearCacheEventLog => Text("Clear caches") - case x:UpdatePolicyServer => Text("Change Policy Server authorized network") - case x:ReloadTechniqueLibrary => Text("Technique library updated") - case x:ModifyTechnique => techniqueDesc(x, Text(" modified")) - case x:DeleteTechnique => techniqueDesc(x, Text(" deleted")) - case x:SuccessfulDeployment => Text("Successful policy update") - case x:FailedDeployment => Text("Failed policy update") - case x:ExportGroupsArchive => Text("New groups archive") - case x:ExportTechniqueLibraryArchive => Text("New Directive library archive") - case x:ExportRulesArchive => Text("New Rules archives") - case x:ExportParametersArchive => Text("New Parameters archives") - case x:ExportFullArchive => Text("New full archive") - case x:ImportGroupsArchive => Text("Restoring group archive") - case x:ImportTechniqueLibraryArchive => Text("Restoring Directive library archive") - case x:ImportRulesArchive => Text("Restoring Rules archive") - case x:ImportParametersArchive => Text("Restoring Parameters archive") - case x:ImportFullArchive => Text("Restoring full archive") - case _:AddChangeRequest => changeRequestDesc(event,Text(" created")) - case _:DeleteChangeRequest => changeRequestDesc(event,Text(" deleted")) - case _:ModifyChangeRequest => changeRequestDesc(event,Text(" modified")) - case _:Rollback => Text("Restore a previous state of configuration policy") - case x:WorkflowStepChanged => workflowStepDesc(x) - case x:AddGlobalParameter => globalParamDesc(x, Text(" added")) - case x:ModifyGlobalParameter => globalParamDesc(x, Text(" modified")) - case x:DeleteGlobalParameter => globalParamDesc(x, Text(" deleted")) - case x:CreateAPIAccountEventLog => apiAccountDesc(x, Text(" added")) - case x:ModifyAPIAccountEventLog => apiAccountDesc(x, Text(" modified")) - case x:DeleteAPIAccountEventLog => apiAccountDesc(x, Text(" deleted")) - case x:ModifyGlobalProperty => Text(s"Modify '${x.propertyName}' global property") - case x:ModifyNode => nodeDesc(x, Text(" modified")) - case _ => Text("Unknow event type") - } - } - - def displayDetails(event:EventLog,changeRequestId:Option[ChangeRequestId]): NodeSeq = { - - val groupLib = nodeGroupRepository.getFullGroupLibrary().toBox.openOr(return
    System error when trying to get the group library
    ) - val rootRuleCategory = ruleCatRepository.getRootCategory.toBox.openOr(return
    System error when trying to get the rule categories
    ) - - val generatedByChangeRequest = - changeRequestId match { - case None => NodeSeq.Empty - case Some(id) =>

    This change was introduced by change request {SHtml.a(() => S.redirectTo(linkUtil.changeRequestLink(id)),Text(s"#${id}"))}

    - } - def xmlParameters(eventId: Option[Int]) = { - eventId match { - case None => NodeSeq.Empty - case Some(id) => - - - } - } - - def showConfirmationDialog(action : RollBackAction, commiter:PersonIdent) : JsCmd = { - val cancel : JsCmd = { - SetHtml("confirm%d".format(event.id.getOrElse(0)), NodeSeq.Empty) & - JsRaw(""" $('#rollback%d').show();""".format(event.id.getOrElse(0))) - } - - val dialog = -

    - - {"Are you sure you want to restore configuration policy %s this change".format(action.name)} -

    - { - SHtml.ajaxButton( - "Cancel" - , () => cancel - , ("class","btn btn-default") - ) - } - { - SHtml.ajaxButton( - "Confirm" - , () => { - event.id match { - case Some(id) => val select = action.selectRollbackedEventsRequest(id) - repos.getEventLogByCriteria(Some(select)).toBox match { - case Full(events) => - val rollbackedEvents = events.filter(_.canRollBack) - action.action(event,commiter,rollbackedEvents,event) - S.redirectTo("eventLogs") - case eb => S.error("Problem while performing a rollback") - logger.error("Problem while performing a rollback : ",eb) - cancel - } - case None => val failure = "Problem while performing a rollback, could not find event id" - S.error(failure) - logger.error(failure) - cancel - } - } - ,("class" ,"btn btn-danger") - ) - } - - def showDialog : JsCmd = { - SetHtml("confirm%d".format(event.id.getOrElse(0)), dialog) & - JsRaw(""" $('#rollback%d').hide(); - $('#confirm%d').stop(true, true).slideDown(1000); """.format(event.id.getOrElse(0),event.id.getOrElse(0))) - } - - showDialog - } - - def addRestoreAction() = - personIdentService.getPersonIdentOrDefault(CurrentUser.actor.name).toBox match { - case Full(commiter) => - var rollbackAction : RollBackAction = null - - if (event.canRollBack) - modificationService.getCommitsfromEventLog(event).map{ commit => -
    -
    Rollback -
    - Restore configuration policy to -
      { - SHtml.radio( - Seq("before","after") - , Full("before") - , {value:String => - rollbackAction = value match { - case "after" => RollbackTo - case "before" => RollbackBefore - } - } - , ("class", "radio") - ).flatMap(e => -
    • - -
    • - ) - } -
    - this change - - { SHtml.ajaxSubmit( - "Restore" - , () => showConfirmationDialog(rollbackAction,commiter) - , ("style","vertical-align:50%;") - , ("class","btn btn-default btn-sm") - ) - } - -
    - -
    -
    - }.getOrElse(NodeSeq.Empty) - else - NodeSeq.Empty - case eb:EmptyBox => logger.error("this should not happen, as person identifier service always return a working value") - NodeSeq.Empty - } - - val reasonHtml = { - val r = event.eventDetails.reason.getOrElse("") - if(r == "") NodeSeq.Empty - else
    Reason: {r}
    - } - - def errorMessage(e:EmptyBox) = { - logger.debug(e ?~! "Error when parsing details.", e) - -
    -

    Details for that node were not in a recognized format. - Raw data are displayed next:

    - { xmlParameters(event.id) } -
    -
    - } - { - (event match { - /* - * bug in scalac : https://issues.scala-lang.org/browse/SI-6897 - * A workaround is to type with a Nodeseq - */ - case add:AddRule => - "*" #> { val xml : NodeSeq = logDetailsService.getRuleAddDetails(add.details) match { - case Full(addDiff) => -
    - { generatedByChangeRequest } - { addRestoreAction } - { ruleDetails(crDetailsXML, addDiff.rule, groupLib, rootRuleCategory)} - { reasonHtml } - { xmlParameters(event.id) } -
    - case Failure(m,_,_) =>

    {m}

    - case e:EmptyBox => errorMessage(e) - } - xml - } - - case del:DeleteRule => - "*" #> { val xml : NodeSeq = logDetailsService.getRuleDeleteDetails(del.details) match { - case Full(delDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { ruleDetails(crDetailsXML, delDiff.rule, groupLib, rootRuleCategory) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - case mod:ModifyRule => - "*" #> { val xml : NodeSeq = logDetailsService.getRuleModifyDetails(mod.details) match { - case Full(modDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } -

    Rule overview:

    -
      -
    • Rule ID: { modDiff.id.value }
    • -
    • Name: { - modDiff.modName.map(diff => diff.newValue).getOrElse(modDiff.name) - }
    • -
    - { - val modCategory = modDiff.modCategory.map { - case SimpleDiff(oldOne,newOne) => -
  • Rule category changed:
  • ++ - DiffDisplayer.displayRuleCategory(rootRuleCategory, oldOne, Some(newOne)) - } - ( - "#name" #> mapSimpleDiff(modDiff.modName) & - "#category" #> modCategory & - "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivatedStatus) & - "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) & - "#shortDescription *" #> mapSimpleDiff(modDiff.modShortDescription) & - "#longDescription *" #> mapSimpleDiff(modDiff.modLongDescription) & - "#target" #> ( - modDiff.modTarget.map{ - case SimpleDiff(oldOnes,newOnes) => -
  • Group Targets changed:
  • ++ - DiffDisplayer.displayRuleTargets(oldOnes.toSeq,newOnes.toSeq, groupLib) - } ) & - "#policies" #> ( - modDiff.modDirectiveIds.map{ - case SimpleDiff(oldOnes,newOnes) => -
  • Directives changed:
  • ++ - DiffDisplayer.displayDirectiveChangeList(oldOnes.toSeq,newOnes.toSeq) - } ) - )(crModDetailsXML) - } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - ///////// Directive ///////// - - case x:ModifyDirective => - "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveModifyDetails(x.details) match { - case Full(modDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } -

    Directive overview:

    -
      -
    • Directive ID: { modDiff.id.value }
    • -
    • Name: { - modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name) - }
    • -
    - {( - "#name" #> mapSimpleDiff(modDiff.modName, modDiff.id) & - "#priority *" #> mapSimpleDiff(modDiff.modPriority) & - "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivated) & - "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) & - "#shortDescription *" #> mapSimpleDiff(modDiff.modShortDescription) & - "#longDescription *" #> mapSimpleDiff(modDiff.modLongDescription) & - "#ptVersion *" #> mapSimpleDiff(modDiff.modTechniqueVersion) & - "#parameters" #> ( - modDiff.modParameters.map { diff => - "#diff" #> displaydirectiveInnerFormDiff(diff, event.id) - } - ) - )(piModDirectiveDetailsXML)} - { reasonHtml } - { xmlParameters(event.id) } -
    - case Failure(m, _, _) =>

    {m}

    - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:AddDirective => - "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveAddDetails(x.details) match { - case Full((diff,sectionVal)) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { directiveDetails(piDetailsXML, diff.techniqueName, - diff.directive, sectionVal) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml} - - case x:DeleteDirective => - "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveDeleteDetails(x.details) match { - case Full((diff,sectionVal)) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { directiveDetails(piDetailsXML, diff.techniqueName, - diff.directive, sectionVal) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - ///////// Node Group ///////// - - case x:ModifyNodeGroup => - "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupModifyDetails(x.details) match { - case Full(modDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } -

    Group overview:

    -
      -
    • Node Group ID: { modDiff.id.value }
    • -
    • Name: { - modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name) - }
    • -
    - {( - "#name" #> mapSimpleDiff(modDiff.modName) & - "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivated) & - "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) & - "#isDynamic *" #> mapSimpleDiff(modDiff.modIsDynamic) & - "#shortDescription *" #> mapSimpleDiff(modDiff.modDescription) & - "#query" #> ( - modDiff.modQuery.map { diff => - val mapOptionQuery = (opt:Option[Query]) => - opt match { - case None => Text("None") - case Some(q) => Text(q.toJSONString) - } - - ".diffOldValue *" #> mapOptionQuery(diff.oldValue) & - ".diffNewValue *" #> mapOptionQuery(diff.newValue) - } - ) & - "#nodes" #> ( - modDiff.modNodeList.map { diff => - ".diffOldValue *" #> nodeGroupDetails(diff.oldValue) & - ".diffNewValue *" #> nodeGroupDetails(diff.newValue) - } - ) - )(groupModDetailsXML)} - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:AddNodeGroup => - "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupAddDetails(x.details) match { - case Full(diff) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { groupDetails(groupDetailsXML, diff.group) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:DeleteNodeGroup => - "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupDeleteDetails(x.details) match { - case Full(diff) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { groupDetails(groupDetailsXML, diff.group) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - ///////// Node Group ///////// - - case x:AcceptNodeEventLog => - "*" #> { val xml : NodeSeq = logDetailsService.getAcceptNodeLogDetails(x.details) match { - case Full(details) => -
    - { addRestoreAction } -

    Node accepted overview:

    - { nodeDetails(details) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:RefuseNodeEventLog => - "*" #> { val xml : NodeSeq = logDetailsService.getRefuseNodeLogDetails(x.details) match { - case Full(details) => -
    - { addRestoreAction } -

    Node refused overview:

    - { nodeDetails(details) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:DeleteNodeEventLog => - "*" #> { val xml : NodeSeq = logDetailsService.getDeleteNodeLogDetails(x.details) match { - case Full(details) => -
    - { addRestoreAction } -

    Node deleted overview:

    - { nodeDetails(details) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - ////////// deployment ////////// - case x:SuccessfulDeployment => - "*" #> { val xml : NodeSeq = logDetailsService.getDeploymentStatusDetails(x.details) match { - case Full(SuccessStatus(id,started,ended,_)) => -
    - { addRestoreAction } -

    Successful policy update:

    -
      -
    • ID: {id}
    • -
    • Start time: {DateFormaterService.getFormatedDate(started)}
    • -
    • End Time: {DateFormaterService.getFormatedDate(ended)}
    • -
    - { reasonHtml } - { xmlParameters(event.id) } -
    - case Full(_) => errorMessage(Failure("Inconsistant policy update status")) - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:FailedDeployment => - "*" #> { val xml : NodeSeq = logDetailsService.getDeploymentStatusDetails(x.details) match { - case Full(ErrorStatus(id,started,ended,failure)) => -
    - { addRestoreAction } -

    Failed policy update:

    -
      -
    • ID: {id}
    • -
    • Start time: {DateFormaterService.getFormatedDate(started)}
    • -
    • End Time: {DateFormaterService.getFormatedDate(ended)}
    • -
    • Error stack trace: {failure.messageChain}
    • -
    - { reasonHtml } - { xmlParameters(event.id) } -
    - case Full(_) => errorMessage(Failure("Inconsistant policy update status")) - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:AutomaticStartDeployement => - "*" #> -
    - { addRestoreAction } - { xmlParameters(event.id) } -
    - - ////////// change authorized networks ////////// - - case x:UpdatePolicyServer => - "*" #> { val xml : NodeSeq = logDetailsService.getUpdatePolicyServerDetails(x.details) match { - case Full(details) => - - def networksToXML(nets:Seq[String]) = { -
      { nets.map { n =>
    • {n}
    • } }
    - } - -
    - { addRestoreAction }{ - ( - ".diffOldValue *" #> networksToXML(details.oldNetworks) & - ".diffNewValue *" #> networksToXML(details.newNetworks) - )(authorizedNetworksXML) - } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - // Technique library reloaded - - case x:ReloadTechniqueLibrary => - "*" #> { val xml : NodeSeq = logDetailsService.getTechniqueLibraryReloadDetails(x.details) match { - case Full(details) => -
    - { addRestoreAction } - The Technique library was reloaded and following Techniques were updated: -
      { details.map {technique => -
    • { "%s (version %s)".format(technique.name.value, technique.version.toString)}
    • - } }
    - { reasonHtml } - { xmlParameters(event.id) } -
    - - case e:EmptyBox => errorMessage(e) - } - xml } - - // Technique modified - case x:ModifyTechnique => - "*" #> { val xml : NodeSeq = logDetailsService.getTechniqueModifyDetails(x.details) match { - case Full(modDiff) => -
    - { addRestoreAction } -

    Technique overview:

    -
      -
    • Technique ID: { modDiff.id.value }
    • -
    • Name: { modDiff.name }
    • -
    - {( - "#isEnabled *" #> mapSimpleDiff(modDiff.modIsEnabled) - ).apply(liModDetailsXML("isEnabled", "Activation status")) - } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - // Technique modified - case x:DeleteTechnique => - "*" #> { val xml : NodeSeq = logDetailsService.getTechniqueDeleteDetails(x.details) match { - case Full(techDiff) => -
    - { addRestoreAction } - { techniqueDetails(techniqueDetailsXML, techDiff.technique) } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => errorMessage(e) - } - xml } - - // archiving & restoring - - case x:ExportEventLog => - "*" #> { val xml : NodeSeq = logDetailsService.getNewArchiveDetails(x.details, x) match { - case Full(gitArchiveId) => - addRestoreAction ++ - displayExportArchiveDetails(gitArchiveId, xmlParameters(event.id)) - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:Rollback => - "*" #> { val xml : NodeSeq = logDetailsService.getRollbackDetails(x.details) match { - case Full(eventLogs) => - addRestoreAction ++ - displayRollbackDetails(eventLogs,event.id.get) - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - xml } - - case x:ImportEventLog => - "*" #> { val xml : NodeSeq = logDetailsService.getRestoreArchiveDetails(x.details, x) match { - case Full(gitArchiveId) => - addRestoreAction ++ - displayImportArchiveDetails(gitArchiveId, xmlParameters(event.id)) - case e:EmptyBox => errorMessage(e) - } - xml } - - case x:ChangeRequestEventLog => - "*" #> { logDetailsService.getChangeRequestDetails(x.details) match { - case Full(diff) => - val (name,desc) = x.id match { - case None => (Text(diff.changeRequest.info.name),Text(diff.changeRequest.info.description)) - case Some(id) => - val modName = displaySimpleDiff(diff.diffName, s"name${id}", diff.changeRequest.info.name) - val modDesc = displaySimpleDiff(diff.diffDescription, s"description${id}", diff.changeRequest.info.description) - (modName,modDesc) - } -
    -

    Change request details:

    -
      -
    • Id: {diff.changeRequest.id}
    • -
    • Name: {name}
    • -
    • Description: {desc}
    • -
    -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - case x:WorkflowStepChanged => - "*" #> { logDetailsService.getWorkflotStepChange(x.details) match { - case Full(step) => -
    -

    Change request status modified:

    -
      -
    • Id: {step.id}
    • -
    • From status: {step.from}
    • -
    • To status: {step.to}
    • - {reasonHtml} -
    -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - // Global parameters - - case x:AddGlobalParameter => - "*" #> { logDetailsService.getGlobalParameterAddDetails(x.details) match { - case Full(globalParamDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { globalParameterDetails(globalParamDetailsXML, globalParamDiff.parameter)} - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - case x:DeleteGlobalParameter => - "*" #> { logDetailsService.getGlobalParameterDeleteDetails(x.details) match { - case Full(globalParamDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { globalParameterDetails(globalParamDetailsXML, globalParamDiff.parameter)} - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - case mod:ModifyGlobalParameter => - "*" #> { logDetailsService.getGlobalParameterModifyDetails(mod.details) match { - case Full(modDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } -

    Global Parameter overview:

    -
      -
    • Global Parameter name: { modDiff.name.value }
    • -
    - {( - "#name" #> modDiff.name.value & - "#value" #> mapSimpleDiff(modDiff.modValue) & - "#description *" #> mapSimpleDiff(modDiff.modDescription) & - "#overridable *" #> mapSimpleDiff(modDiff.modOverridable) - )(globalParamModDetailsXML) - } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - case x:CreateAPIAccountEventLog => - "*" #> { logDetailsService.getApiAccountAddDetails(x.details) match { - case Full(apiAccountDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { apiAccountDetails(apiAccountDetailsXML, apiAccountDiff.apiAccount)} - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - case x:DeleteAPIAccountEventLog => - "*" #> { logDetailsService.getApiAccountDeleteDetails(x.details) match { - case Full(apiAccountDiff) => -
    - { addRestoreAction } - { generatedByChangeRequest } - { apiAccountDetails(apiAccountDetailsXML, apiAccountDiff.apiAccount)} - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - case mod:ModifyAPIAccountEventLog => - "*" #> { logDetailsService.getApiAccountModifyDetails(mod.details) match { - case Full(apiAccountDiff) => - -
    - { addRestoreAction } - { generatedByChangeRequest } -

    API account overview:

    -
      -
    • Account ID: { apiAccountDiff.id.value }
    • -
    - {( - "#name" #> mapSimpleDiff(apiAccountDiff.modName) & - "#token" #> mapSimpleDiff(apiAccountDiff.modToken) & - "#description *" #> mapSimpleDiff(apiAccountDiff.modDescription) & - "#isEnabled *" #> mapSimpleDiff(apiAccountDiff.modIsEnabled) & - "#tokenGenerationDate *" #> mapSimpleDiff(apiAccountDiff.modTokenGenerationDate) & - "#expirationDate *" #> mapSimpleDiffT[Option[DateTime]](apiAccountDiff.modExpirationDate, _.fold("")(_.toString(ISODateTimeFormat.dateTime())) - ) & - "#accountKind *" #> mapSimpleDiff(apiAccountDiff.modAccountKind) & - //make list of ACL unsderstandable - "#acls *" #> mapSimpleDiff(apiAccountDiff.modAccountAcl.map(o => { - val f = (l: List[ApiAclElement]) => l.sortBy(_.path.value).map(x => s"[${x.actions.map(_.name.toUpperCase()).mkString(",")}] ${x.path.value}").mkString(" | ") - SimpleDiff(f(o.oldValue), f(o.newValue)) - })) - )(apiAccountModDetailsXML) - } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - case mod:ModifyGlobalProperty => - "*" #> { logDetailsService.getModifyGlobalPropertyDetails(mod.details) match { - case Full((oldProp,newProp)) => -
    -

    Global property overview:

    -
      -
    • Name: { mod.propertyName }
    • -
    • Value: - { val diff = Some(SimpleDiff(oldProp.value,newProp.value)) - displaySimpleDiff(diff,"value",newProp.value) }
    • -
    - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - // Node modifiction - case mod:ModifyNode => - "*" #> { logDetailsService.getModifyNodeDetails(mod.details) match { - case Full(modDiff) => - logger.info(modDiff.modAgentRun) -
    - { addRestoreAction } - { generatedByChangeRequest } -

    Node '{modDiff.id.value}' modified:

    -
      -
    • Node ID:{modDiff.id.value}
    • -
    - { - mapComplexDiff(modDiff.modAgentRun){ (optAr:Option[AgentRunInterval]) => - optAr match { - case None => No value - case Some(ar) => agentRunDetails(ar) - } - } - } - { - mapComplexDiff(modDiff.modHeartbeat){ (optHb:Option[HeartbeatConfiguration]) => - optHb match { - case None => No value - case Some(hb) => heartbeatDetails(hb) - } - } - } - { - mapComplexDiff(modDiff.modProperties){ (props:Seq[NodeProperty]) => - nodePropertiesDetails(props) - } - } - { - mapComplexDiff(modDiff.modPolicyMode){ (optMode:Option[PolicyMode]) => - optMode match { - case None => Use global policy mode - case Some(mode) => {mode.name} - } - } - } - { - mapComplexDiff(modDiff.modKeyValue) { keyValue => {keyValue.key} } - } - { - mapComplexDiff(modDiff.modKeyStatus) { keyStatus => {keyStatus.value} } - } - { reasonHtml } - { xmlParameters(event.id) } -
    - case e:EmptyBox => logger.warn(e) - errorMessage(e) - } - } - - // other case: do not display details at all - case _ => "*" #> "" - - })(event.details)} - } - - private[this] def agentRunDetails(ar: AgentRunInterval): NodeSeq = { - ( - "#override" #> ar.overrides.map(_.toString()).getOrElse("false") - & "#interval" #> ar.interval - & "#startMinute" #> ar.startMinute - & "#startHour" #> ar.startHour - & "#splaytime" #> ar.splaytime - ).apply( -
      -
    • Override global value:
    • -
    • Period:
    • -
    • Start at minute:
    • -
    • Start at hour:
    • -
    • Splay time:
    • -
    - ) - } - - private[this] def heartbeatDetails(hb: HeartbeatConfiguration): NodeSeq = { - ( - "#override" #> hb.overrides - & "#interval" #> hb.heartbeatPeriod - ).apply( -
      -
    • Override global value:
    • -
    • Period:
    • -
    - ) - } - - private[this] def nodePropertiesDetails(props: Seq[NodeProperty]): NodeSeq = { - ( - "#kv *" #> props.map { p => s"${p.name}: ${p.value}" } - ).apply( -
      -
    • -
    - ) - } - - private[this] def displaySimpleDiff[T] ( - diff : Option[SimpleDiff[T]] - , name : String - , default : String - ) = diff.map(value => displayFormDiff(value, name)).getOrElse(Text(default)) - private[this] def displayFormDiff[T](diff: SimpleDiff[T], name:String)(implicit fun: T => String = (t:T) => t.toString) = { -
    {fun(diff.oldValue)}
    -
    {fun(diff.newValue)}
    -
      ++
    -    Script(
    -      OnLoad(
    -        JsRaw(
    -          s"""
    -            var before = "before${name}";
    -            var after  = "after${name}";
    -            var result = "result${name}";
    -            makeDiff(before,after,result);"""
    -        )
    -      )
    -    )
    -  }
    -  private[this] def displaydirectiveInnerFormDiff(diff: SimpleDiff[SectionVal], eventId: Option[Int]): NodeSeq = {
    -      eventId match {
    -        case None => NodeSeq.Empty
    -        case Some(id) =>
    -        (
    -          
    {xmlPretty.format(SectionVal.toXml(diff.oldValue))}
    -
    {xmlPretty.format(SectionVal.toXml(diff.newValue))}
    -
    
    -        ) ++ Script(OnLoad(JsRaw(s"""
    -            var before = "before${id}";
    -            var after  = "after${id}";
    -            var result = "result${id}";
    -            makeDiff(before,after,result);
    -            """)))
    -      }
    -  }
    -
    -  private[this] def displayExportArchiveDetails(gitArchiveId: GitArchiveId, rawData: NodeSeq) =
    -    
    -

    Details of the new archive:

    -
      -
    • Git path of the archive: {gitArchiveId.path.value}
    • -
    • Commit ID (hash): {gitArchiveId.commit.value}
    • -
    • Commiter name: {gitArchiveId.commiter.getName}
    • -
    • Commiter email: {gitArchiveId.commiter.getEmailAddress}
    • -
    - {rawData} -
    - - private[this] def displayImportArchiveDetails(gitCommitId: GitCommitId, rawData: NodeSeq) = -
    -

    Details of the restored archive:

    -
      -
    • Commit ID (hash): {gitCommitId.value}
    • -
    - {rawData} -
    - - private[this] def nodeGroupDetails(nodes: Set[NodeId]): NodeSeq = { - val res = nodes.toSeq match { - case Seq() => NodeSeq.Empty - case t => - nodes - .toSeq - .map { id => linkUtil.createNodeLink(id) } - .reduceLeft[NodeSeq]((a,b) => a ++ ++ b) - } - ( - ".groupSeparator" #> ", " - ).apply(res) - - } - - private[this] def ruleDetails(xml:NodeSeq, rule:Rule, groupLib: FullNodeGroupCategory, rootRuleCategory: RuleCategory) = { - - ( - "#ruleID" #> rule.id.value & - "#ruleName" #> rule.name & - "#category" #> DiffDisplayer.displayRuleCategory(rootRuleCategory, rule.categoryId, None) & - "#target" #> DiffDisplayer.displayRuleTargets(rule.targets.toSeq, rule.targets.toSeq, groupLib) & - "#policy" #> DiffDisplayer.displayDirectiveChangeList(rule.directiveIds.toSeq, rule.directiveIds.toSeq) & - "#isEnabled" #> rule.isEnabled & - "#isSystem" #> rule.isSystem & - "#shortDescription" #> rule.shortDescription & - "#longDescription" #> rule.longDescription - ) (xml) - } - - private[this] def directiveDetails(xml:NodeSeq, ptName: TechniqueName, directive:Directive, sectionVal:SectionVal) = ( - "#directiveID" #> directive.id.value & - "#directiveName" #> directive.name & - "#ptVersion" #> directive.techniqueVersion.toString & - "#ptName" #> ptName.value & - "#ptVersion" #> directive.techniqueVersion.toString & - "#ptName" #> ptName.value & - "#priority" #> directive.priority & - "#isEnabled" #> directive.isEnabled & - "#isSystem" #> directive.isSystem & - "#shortDescription" #> directive.shortDescription & - "#longDescription" #> directive.longDescription - )(xml) - - private[this] def groupDetails(xml:NodeSeq, group: NodeGroup) = ( - "#groupID" #> group.id.value & - "#groupName" #> group.name & - "#shortDescription" #> group.description & - "#shortDescription" #> group.description & - "#query" #> (group.query match { - case None => Text("None") - case Some(q) => Text(q.toJSONString) - } ) & - "#isDynamic" #> group.isDynamic & - "#nodes" #>( - { - val l = group.serverList.toList - l match { - case Nil => Text("None") - case _ => l - .map(id => {id.value}) - .reduceLeft[NodeSeq]((a,b) => a ++ ++ b) - } - } - ) & - "#isEnabled" #> group.isEnabled & - "#isSystem" #> group.isSystem - )(xml) - - private[this] def techniqueDetails(xml: NodeSeq, technique: ActiveTechnique) = ( - "#techniqueID" #> technique.id.value & - "#techniqueName" #> technique.techniqueName.value & - "#isEnabled" #> technique.isEnabled & - "#isSystem" #> technique.isSystem - )(xml) - - private[this] def globalParameterDetails(xml: NodeSeq, globalParameter: GlobalParameter) = ( - "#name" #> globalParameter.name.value & - "#value" #> globalParameter.value & - "#description" #> globalParameter.description & - "#overridable" #> globalParameter.overridable - )(xml) - - private[this] def apiAccountDetails(xml: NodeSeq, apiAccount: ApiAccount) = ( - "#id" #> apiAccount.id.value & - "#name" #> apiAccount.name.value & - "#token" #> apiAccount.token.value & - "#description" #> apiAccount.description & - "#isEnabled" #> apiAccount.isEnabled & - "#creationDate" #> DateFormaterService.getFormatedDate(apiAccount.creationDate) & - "#tokenGenerationDate" #> DateFormaterService.getFormatedDate(apiAccount.tokenGenerationDate) - )(xml) - - - private[this] def mapSimpleDiffT[T](opt:Option[SimpleDiff[T]], t: T => String) = opt.map { diff => - ".diffOldValue *" #> t(diff.oldValue) & - ".diffNewValue *" #> t(diff.newValue) - } - - private[this] def mapSimpleDiff[T](opt:Option[SimpleDiff[T]]) = mapSimpleDiffT(opt, (x:T) => x.toString) - - private[this] def mapSimpleDiff[T](opt:Option[SimpleDiff[T]], id: DirectiveId) = opt.map { diff => - ".diffOldValue *" #> diff.oldValue.toString & - ".diffNewValue *" #> diff.newValue.toString & - "#directiveID" #> id.value - } - - private[this] def mapComplexDiff[T](opt:Option[SimpleDiff[T]])(display: T => NodeSeq) = { - opt match { - case None => NodeSeq.Empty - case Some(diff) => - ( - ".diffOldValue *" #> display(diff.oldValue) & - ".diffNewValue *" #> display(diff.newValue) - ).apply( -
      -
    • Old value: old value
    • -
    • New value: new value
    • -
    - ) - } - } - - private[this] def nodeDetails(details:InventoryLogDetails) = ( - "#nodeID" #> details.nodeId.value & - "#nodeName" #> details.hostname & - "#os" #> details.fullOsName & - "#version" #> DateFormaterService.getFormatedDate(details.inventoryVersion) - )( -
      -
    • Node ID:
    • -
    • Name:
    • -
    • Operating System:
    • -
    • Date inventory last received:
    • -
    - ) - private[this] val crDetailsXML = -
    -

    Rule overview:

    -
      -
    • ID: 
    • -
    • Name: 
    • -
    • Category: 
    • -
    • Description: 
    • -
    • Target: 
    • -
    • Directives: 
    • -
    • Enabled: 
    • -
    • System: 
    • -
    • Details: 
    • -
    -
    - - private[this] val piDetailsXML = -
    -

    Directive overview:

    -
      -
    • ID: 
    • -
    • Name: 
    • -
    • Description: 
    • -
    • Technique name: 
    • -
    • Technique version: 
    • -
    • Priority: 
    • -
    • Enabled: 
    • -
    • System: 
    • -
    • Details: 
    • -
    -
    - - private[this] val groupDetailsXML = -
    -

    Group overview:

    -
      -
    • ID:
    • -
    • Name:
    • -
    • Description:
    • -
    • Enabled:
    • -
    • Dynamic:
    • -
    • System:
    • -
    • Query:
    • -
    • Node list:
    • -
    -
    - - private[this] val techniqueDetailsXML = -
    -

    Technique overview:

    -
      -
    • ID: 
    • -
    • Name: 
    • -
    • Enabled: 
    • -
    • System: 
    • -
    -
    - - private[this] val globalParamDetailsXML = -
    -

    Global Parameter overview:

    -
      -
    • Name: 
    • -
    • Value: 
    • -
    • Description: 
    • -
    • Overridable: 
    • -
    -
    - - private[this] val apiAccountDetailsXML = -
    -

    API account overview:

    -
      -
    • Rudder ID:
    • -
    • Name: 
    • -
    • Token: 
    • -
    • Description: 
    • -
    • Enabled: 
    • -
    • Creation date: 
    • -
    • Token Generation date: 
    • -
    • Token Expiration date: 
    • -
    • Account Kind: 
    • -
    • ACLs: 
    • -
    -
    - - private[this] val apiAccountModDetailsXML = - - {liModDetailsXML("name", "Name")} - {liModDetailsXML("token", "Token")} - {liModDetailsXML("description", "Description")} - {liModDetailsXML("isEnabled", "Enabled")} - {liModDetailsXML("tokenGenerationDate", "Token Generation Date")} - {liModDetailsXML("expirationDate", "Token Expiration Date")} - {liModDetailsXML("accountKind", "Account Kind")} - {liModDetailsXML("acls", "ACL list")} - - - - private[this] def liModDetailsXML(id:String, name:String) = ( -
    - {name} changed: -
      -
    • Old value: old value
    • -
    • New value: new value
    • -
    -
    - ) - - private[this] def liModDirectiveDetailsXML(id:String, name:String) = ( -
    - {name} changed: -
      -
    • Differences:
    • -
    -
    - ) - - private[this] val groupModDetailsXML = - - {liModDetailsXML("name", "Name")} - {liModDetailsXML("shortDescription", "Description")} - {liModDetailsXML("nodes", "Node list")} - {liModDetailsXML("query", "Query")} - {liModDetailsXML("isDynamic", "Dynamic group")} - {liModDetailsXML("isEnabled", "Activation status")} - {liModDetailsXML("isSystem", "System")} - - - private[this] val crModDetailsXML = - - {liModDetailsXML("name", "Name")} - {liModDetailsXML("category", "Category")} - {liModDetailsXML("shortDescription", "Description")} - {liModDetailsXML("longDescription", "Details")} - {liModDetailsXML("target", "Target")} - {liModDetailsXML("policies", "Directives")} - {liModDetailsXML("isEnabled", "Activation status")} - {liModDetailsXML("isSystem", "System")} - - - private[this] val piModDirectiveDetailsXML = - - {liModDetailsXML("name", "Name")} - {liModDetailsXML("shortDescription", "Description")} - {liModDetailsXML("longDescription", "Details")} - {liModDetailsXML("ptVersion", "Target")} - {liModDirectiveDetailsXML("parameters", "Policy parameters")} - {liModDetailsXML("priority", "Priority")} - {liModDetailsXML("isEnabled", "Activation status")} - {liModDetailsXML("isSystem", "System")} - - - private[this] val globalParamModDetailsXML = - - {liModDetailsXML("name", "Name")} - {liModDetailsXML("value", "Value")} - {liModDetailsXML("description", "Description")} - {liModDetailsXML("overridable", "Overridable")} - - - - private[this] def displayRollbackDetails(rollbackInfo:RollbackInfo,id:Int) = { - val rollbackedEvents = rollbackInfo.rollbacked -
    -
    -
    -

    Details of the rollback:

    -
    - A rollback to {rollbackInfo.rollbackType} event - { SHtml.a(() => - SetHtml("currentId%s".format(id),Text(rollbackInfo.target.id.toString)) & - JsRaw("""$('#%1$s').dataTable().fnFilter("%2$s|%3$s",0,true,false); - $("#cancel%3$s").show(); - scrollToElement('%2$s', ".rudder_col"); - if($('#%2$s').prop('open') != "opened") - $('#%2$s').click();""".format(gridName,rollbackInfo.target.id,id)) - , Text(rollbackInfo.target.id.toString) - ) - } has been completed. - -

    - Events that were rollbacked can be consulted in the table below. -
    - Those events are no longer applied by the configuration policy. - -
    - - - - - - - - - - - {rollbackedEvents.map{ ev => - - - - - - - } } - -
    IDDateActorEvent type
    - {SHtml.a(() => - SetHtml("currentId%s".format(id),Text(ev.id.toString)) & - JsRaw("""$('#%1$s').dataTable().fnFilter("%2$s|%3$s",0,true,false); - $("#cancel%3$s").show(); - scrollToElement('%2$s', ".rudder_col"); - if($('#%2$s').prop('open') != "opened") - $('#%2$s').click();""".format(gridName,ev.id,id)) - , Text(ev.id.toString) - ) - } - {ev.date} {ev.author} {S.?("rudder.log.eventType.names." + ev.eventType)}
    - -
    - -
    -
    -
    ++ Script(JsRaw(s""" - $$('#rollbackTable${id}').dataTable({ - "asStripeClasses": [ 'color1', 'color2' ], - "bAutoWidth": false, - "bFilter" :true, - "bPaginate" :true, - "bLengthChange": true, - "bStateSave": true, - "fnStateSave": function (oSettings, oData) { - localStorage.setItem( 'DataTables_rollbackTable${id}', JSON.stringify(oData) ); - }, - "fnStateLoad": function (oSettings) { - return JSON.parse( localStorage.getItem('DataTables_rollbackTable${id}') ); - }, - "sPaginationType": "full_numbers", - "bJQueryUI": true, - "oLanguage": { - "sSearch": "" - }, - "aaSorting":[], - "aoColumns": [ - { "sWidth": "100px" } - , { "sWidth": "100px" } - , { "sWidth": "100px" } - , { "sWidth": "100px" } - ], - "sDom": '<"dataTables_wrapper_top"f>rt<"dataTables_wrapper_bottom"lip>' - }); - """)) - } - - private[this] def authorizedNetworksXML() = ( -
    - Networks authorized on policy server were updated: - - - - - - - - -
    from:to:
    old valuenew value
    -
    - ) - -trait RollBackAction { - def name : String - def op : String - def action : (EventLog,PersonIdent,Seq[EventLog],EventLog) => Box[GitCommitId] - def selectRollbackedEventsRequest(id:Int) = s" id ${op} ${id} and modificationid IS NOT NULL" -} - -case object RollbackTo extends RollBackAction{ - val name = "after" - val op = ">" - def action = modificationService.restoreToEventLog _ -} - -case object RollbackBefore extends RollBackAction{ - val name = "before" - val op = ">=" - def action = modificationService.restoreBeforeEventLog _ -} - } diff --git a/webapp/sources/rudder/rudder-web/src/main/webapp/javascript/rudder/rudder-datatable.js b/webapp/sources/rudder/rudder-web/src/main/webapp/javascript/rudder/rudder-datatable.js index 4f701571f62..2ef37123ede 100644 --- a/webapp/sources/rudder/rudder-web/src/main/webapp/javascript/rudder/rudder-datatable.js +++ b/webapp/sources/rudder/rudder-web/src/main/webapp/javascript/rudder/rudder-datatable.js @@ -1351,6 +1351,11 @@ function createEventLogTable(gridId, data, contextPath, refresh, pickEventLogsIn var params = { "bFilter" : true + , "serverSide" : true + , "ajax" : { + "type" : "GET" + , "url" : contextPath + "/secure/api/eventlog" + } , "bPaginate" : true , "bLengthChange": true , "sPaginationType": "full_numbers" @@ -1386,7 +1391,11 @@ function createEventLogTable(gridId, data, contextPath, refresh, pickEventLogsIn var detailsTd = $("."+detailsId); detailsTd.attr("id",detailsId); // Set data in the open row with the details function from data - fnData.details(detailsId); +// fnData.details(detailsId); + $.getJSON(contextPath + '/secure/api/eventlog/' + fnData.id + "/details", function(data) { + var html = $.parseHTML( data["data"]["content"] ); + $("td#"+detailsId).append( html ); + }); // Set final css var color = 'color1'; if(tableRow.hasClass('color2'))