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..b8010099e69 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 count: 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..60b5eea4e62 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 count: 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..492641e31c3
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/EventLogAPI.scala
@@ -0,0 +1,163 @@
+/*
+*************************************************************************************
+* 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.db.Doobie
+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
+ , doobie : Doobie
+) 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.count.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-rest/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
new file mode 100644
index 00000000000..646f285fb44
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala
@@ -0,0 +1,257 @@
+/*
+*************************************************************************************
+* Copyright 2013 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.domain.{NodeDit, RudderDit}
+
+import scala.xml.NodeSeq
+import com.normation.rudder.domain.policies.DirectiveId
+import com.normation.rudder.repository.{FullNodeGroupCategory, RoRuleRepository}
+import com.normation.rudder.rule.category.{RuleCategory, RuleCategoryId, RuleCategoryService}
+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.repository.ldap.{LDAPEntityMapper, RoLDAPNodeGroupRepository, RoLDAPRuleRepository, ScalaLock}
+import com.normation.rudder.web.model.LinkUtil
+import RudderProperties._
+import com.normation.inventory.ldap.core.{InventoryDit, InventoryDitService, InventoryDitServiceImpl, InventoryMapper}
+import com.normation.ldap.sdk.ROPooledSimpleAuthConnectionProvider
+import com.normation.rudder.domain.logger.ApplicationLogger
+import com.normation.rudder.domain.queries.{DitQueryData, ObjectCriterion, SubGroupChoice}
+import com.normation.rudder.services.queries.{CmdbQueryParser, DefaultStringQueryParser, JsonQueryLexer}
+import com.typesafe.config.ConfigException
+
+import scala.language.implicitConversions
+
+trait DiffItem[T] {
+
+ def display(implicit displayer : T => NodeSeq) : NodeSeq
+
+}
+
+case class Added[T](
+ value:T
+) extends DiffItem[T] {
+ val newValue = Some(value)
+
+ def display(implicit displayer : T => NodeSeq) : NodeSeq =
+
+}
+
+// Not used yet, but for later use
+case class Modified[T](
+ oldValue:T
+ , newValue:T
+) extends DiffItem[T] {
+
+ private[this] val delete = Deleted(oldValue)
+ private[this] val add = Added(oldValue)
+
+ def display(implicit displayer : T => NodeSeq) : NodeSeq =
+ delete.display ++ add.display
+}
+
+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]
+ ) : NodeSeq = {
+
+ // First, find unchanged and deleted (have find no clean way to make a 3 way partition)
+ val (unchanged,deleted) = oldDirectives.partition(newDirectives.contains)
+ // Get the added ones
+ val added = newDirectives.filterNot(unchanged.contains).map(Added(_))
+ val deletedMap = deleted.map(Deleted(_))
+ val unchangedMap = unchanged.map(Unchanged(_))
+
+ // Finally mix all maps together in one and display it
+ val changeMap:Seq[DiffItem[DirectiveId]] = deletedMap ++ unchangedMap ++ added
+
+ { for {
+ 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
+ ) : NodeSeq = {
+
+ implicit def displayNodeGroup(target: RuleTarget) : NodeSeq= {
+ target match {
+ case TargetUnion(targets) =>
+ all Nodes from:
{targets.map(t =>
{displayNodeGroup(t)}
)}
+ case TargetIntersection(targets) =>
+ Nodes that belongs to all these groups:
{targets.map(t =>
{displayNodeGroup(t)}
)}
+ case TargetExclusion(included,excluded) =>
+ Include {displayNodeGroup(included)}
+ 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})
+ }
+ }
+
+ (oldTargets,newTargets) match {
+ case (Seq(TargetExclusion(newIncluded,newExcluded)),Seq(TargetExclusion(oldIncluded,oldExcluded))) =>
+ def displayKind(kind: TargetComposition) : NodeSeq= {
+ kind match {
+ case _:TargetUnion =>
+ all Nodes from:
+ case _:TargetIntersection =>
+ 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
+
+ case (_,_) =>
+ val (unchanged,deleted) = oldTargets.partition(newTargets.contains)
+ val added = newTargets.filterNot(unchanged.contains).map(Added(_))
+ val deletedMap = deleted.map(Deleted(_))
+ val unchangedMap = unchanged.map(Unchanged(_))
+
+ val changeMap:Seq[DiffItem[RuleTarget]] = deletedMap ++ unchangedMap ++ added
+
+ { for {
+ change <- changeMap
+ } yield {
+ // Implicit used here (displayNodeGroup)
+ change.display
+ }
+ }
+
+ }
+ }
+
+ //
+ private[this] val ruleCategoryService = new RuleCategoryService()
+
+ def displayRuleCategory (
+ rootCategory: RuleCategory
+ , oldCategory : RuleCategoryId
+ , newCategory : Option[RuleCategoryId]
+ ) = {
+
+ def getCategoryFullName(category : RuleCategoryId) = {
+ ruleCategoryService.shortFqdn(rootCategory, category) match {
+ case Full(fqdn) => fqdn
+ case eb : EmptyBox =>
+ logger.error(s"Error while looking for category ${category.value}")
+ category.value
+ }
+ }
+ implicit def displayRuleCategory(ruleCategoryId: RuleCategoryId) = {
+ {getCategoryFullName(ruleCategoryId)}
+ }
+
+ newCategory match {
+ case Some(newCategory) =>
+ val changes = Seq(Deleted(oldCategory),Added(newCategory))
+
+ { 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..295dfa74aee
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsService.scala
@@ -0,0 +1,1699 @@
+/*
+*************************************************************************************
+* 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 scala.xml._
+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.services.eventlog.EventLogDetailsService
+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 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 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.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 org.joda.time.format.DateTimeFormat
+import com.normation.rudder.web.model.LinkUtil
+import org.joda.time.format.ISODateTimeFormat
+import com.normation.box._
+import scala.concurrent.Future
+
+/**
+ * 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)
+ }
+ 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
+ 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 detailsCallback = {
+// AnonFunc("details",SHtml.ajaxCall(JsVar("details"), {(abc) =>
+// val crId = event.id.flatMap(repos.getEventLogWithChangeRequest(_) 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) =>
+
+
{ event.details.map { n =>xmlPretty.format(n) + "\n"} }
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) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml
+ }
+
+ case del:DeleteRule =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getRuleDeleteDetails(del.details) match {
+ case Full(delDiff) =>
+
+ 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) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:AddDirective =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveAddDetails(x.details) match {
+ case Full((diff,sectionVal)) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ ///////// Node Group /////////
+
+ case x:ModifyNodeGroup =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupModifyDetails(x.details) match {
+ case Full(modDiff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:AddNodeGroup =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupAddDetails(x.details) match {
+ case Full(diff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:DeleteNodeGroup =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupDeleteDetails(x.details) match {
+ case Full(diff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ ///////// Node Group /////////
+
+ case x:AcceptNodeEventLog =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getAcceptNodeLogDetails(x.details) match {
+ case Full(details) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:RefuseNodeEventLog =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getRefuseNodeLogDetails(x.details) match {
+ case Full(details) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:DeleteNodeEventLog =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getDeleteNodeLogDetails(x.details) match {
+ case Full(details) =>
+
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)) =>
+
+ {
+ 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)}
+ Networks authorized on policy server were updated:
+
+
from:
to:
+
+
+
old value
+
new 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/RudderProperties.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderProperties.scala
new file mode 100644
index 00000000000..083bad1faf6
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderProperties.scala
@@ -0,0 +1,51 @@
+package com.normation.rudder.web.services
+
+import java.io.File
+
+import com.normation.rudder.domain.logger.ApplicationLogger
+import com.typesafe.config.{Config, ConfigFactory}
+import org.springframework.core.io.ClassPathResource
+/**
+ * Define a resource for configuration.
+ * For now, config properties can only be loaded from either
+ * a file in the classpath, or a file in the file system.
+ */
+sealed trait ConfigResource
+final case class ClassPathResource(name: String) extends ConfigResource
+final case class FileSystemResource(file: File) extends ConfigResource
+
+object RudderProperties {
+
+ val JVM_CONFIG_FILE_KEY = "rudder.configFile"
+ val DEFAULT_CONFIG_FILE_NAME = "configuration.properties"
+
+ /**
+ * Where to go to look for properties
+ */
+ val configResource = System.getProperty(JVM_CONFIG_FILE_KEY) match {
+ case null | "" => //use default location in classpath
+ ApplicationLogger.info("JVM property -D%s is not defined, use configuration file in classpath".format(JVM_CONFIG_FILE_KEY))
+ ClassPathResource(DEFAULT_CONFIG_FILE_NAME)
+ case x => //so, it should be a full path, check it
+ val config = new File(x)
+ if(config.exists && config.canRead) {
+ ApplicationLogger.info("Use configuration file defined by JVM property -D%s : %s".format(JVM_CONFIG_FILE_KEY, config.getPath))
+ FileSystemResource(config)
+ } else {
+ ApplicationLogger.error("Can not find configuration file specified by JVM property %s: %s ; abort".format(JVM_CONFIG_FILE_KEY, config.getPath))
+ throw new javax.servlet.UnavailableException("Configuration file not found: %s".format(config.getPath))
+ }
+ }
+
+ // some value used as defaults for migration
+ val migrationConfig =
+ s"""rudder.batch.reportscleaner.compliancelevels.delete.TTL=15
+ """
+
+ val config : Config = {
+ (configResource match {
+ case ClassPathResource(name) => ConfigFactory.load(name)
+ case FileSystemResource(file) => ConfigFactory.load(ConfigFactory.parseFile(file))
+ }).withFallback(ConfigFactory.parseString(migrationConfig))
+ }
+}
\ No newline at end of file
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..01112241f17 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, doobie)
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..b2a0ddbc23e 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
@@ -80,6 +80,7 @@ import com.normation.rudder.rule.category.RoRuleCategoryRepository
import org.joda.time.format.DateTimeFormat
import com.normation.rudder.web.model.LinkUtil
import org.joda.time.format.ISODateTimeFormat
+import scala.concurrent.Future
import com.normation.box._
/**
@@ -204,6 +205,7 @@ class EventListDisplayer(
)
)
}
+
val json = {
JsObj(
"id" -> (event.id.map(_.toString).getOrElse("Unknown"): String)
@@ -367,8 +369,10 @@ class EventListDisplayer(
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}"))}
- }
+ case Some(id) =>
+// val htmlCR =
This change was introduced by change request {SHtml.a(() => S.redirectTo(linkUtil.changeRequestLink(id)),Text(s"#${id}"))}
+ NodeSeq.Empty
+ }
def xmlParameters(eventId: Option[Int]) = {
eventId match {
case None => NodeSeq.Empty
@@ -433,52 +437,55 @@ class EventListDisplayer(
}
def addRestoreAction() =
- personIdentService.getPersonIdentOrDefault(CurrentUser.actor.name).toBox match {
+ personIdentService.getPersonIdentOrDefault(CurrentUserService.actor.name).toBox match {
case Full(commiter) =>
var rollbackAction : RollBackAction = null
- if (event.canRollBack)
- modificationService.getCommitsfromEventLog(event).map{ commit =>
-
- }.getOrElse(NodeSeq.Empty)
- else
+ if (event.canRollBack) {
+// val rollDisplay = modificationService.getCommitsfromEventLog(event).map { commit =>
+//
+// }.getOrElse(NodeSeq.Empty)
+ 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
@@ -784,7 +791,7 @@ class EventListDisplayer(
{ reasonHtml }
{ xmlParameters(event.id) }
- case Full(_) => errorMessage(Failure("Inconsistant policy update status"))
+ case Full(_) => errorMessage(Failure("Unconsistant policy update status"))
case e:EmptyBox => errorMessage(e)
}
xml }
@@ -804,7 +811,7 @@ class EventListDisplayer(
{ reasonHtml }
{ xmlParameters(event.id) }
- case Full(_) => errorMessage(Failure("Inconsistant policy update status"))
+ case Full(_) => errorMessage(Failure("Unconsistant policy update status"))
case e:EmptyBox => errorMessage(e)
}
xml }
@@ -909,8 +916,9 @@ class EventListDisplayer(
case x:Rollback =>
"*" #> { val xml : NodeSeq = logDetailsService.getRollbackDetails(x.details) match {
case Full(eventLogs) =>
- addRestoreAction ++
- displayRollbackDetails(eventLogs,event.id.get)
+// addRestoreAction ++
+// displayRollbackDetails(eventLogs,event.id.get)
+ NodeSeq.Empty // To prevent a stateful error
case e:EmptyBox => logger.warn(e)
errorMessage(e)
}
@@ -1147,12 +1155,6 @@ class EventListDisplayer(
}
}
}
- {
- mapComplexDiff(modDiff.modKeyValue) { keyValue => {keyValue.key} }
- }
- {
- mapComplexDiff(modDiff.modKeyStatus) { keyStatus => {keyStatus.value} }
- }
{ reasonHtml }
{ xmlParameters(event.id) }
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..f74f000758f 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,12 @@ 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) {
+ console.log(data["data"]["content"])
+ var html = $.parseHTML( data["data"]["content"] );
+ $("td#"+detailsId).append( html );
+ });
// Set final css
var color = 'color1';
if(tableRow.hasClass('color2'))