<commit_before>/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero GPL v3, 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 Affero GPL v3
* licence, 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.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/agpl.html>.
*
*************************************************************************************
*/
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._
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.rudder.web.model.JsInitContextLinkUtil._
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._

/**
 * Used to display the event list, in the pending modification (AsyncDeployment), 
 * or in the administration EventLogsViewer
 */
class EventListDisplayer(
      logDetailsService: EventLogDetailsService
    , repos            : EventLogRepository
) extends Loggable {

  private[this] val xmlPretty = new scala.xml.PrettyPrinter(80, 2)
 // private[this] val gridName = "eventLogsGrid"
 // private[this] val jsGridName = "oTable" + gridName
  
  def display(events:Seq[EventLog], gridName:String) : NodeSeq  = {
    (
      "tbody *" #> ("tr" #> events.map { event => 
        ".eventLine [jsuuid]" #> Text(gridName + "-" + event.id.getOrElse(0).toString) &
        ".eventLine [class]" #> {
          if (event.details != <entry></entry> ) 
            Text("curspoint")
          else
            NodeSeq.Empty
        } &
        ".logId [class]" #> {
          if (event.details != <entry></entry> ) 
            Text("listopen")
          else
            Text("listEmpty")
        } &
        ".logId *" #> event.id.getOrElse(0).toString &
        ".logDatetime *" #> DateFormaterService.getFormatedDate(event.creationDate) &
        ".logActor *" #> event.principal.name &
        ".logType *" #> S.?("rudder.log.eventType.names." + event.eventType.serialize) &
        ".logDescription *" #> displayDescription(event) 
      })
     )(dataTableXml(gridName)) 
  }
  
  
  def initJs(gridName : String) : JsCmd = {
    val jsGridName = "oTable" + gridName
    JsRaw("var %s;".format(jsGridName)) &
    OnLoad(
        JsRaw("""
          /* Event handler function */
          #table_var# = $('#%s').dataTable({
            "asStripClasses": [ 'color1', 'color2' ],
            "bAutoWidth": false,
            "bFilter" :true,
            "bPaginate" :true,
            "bLengthChange": false,
            "sPaginationType": "full_numbers",
            "oLanguage": {
              "sSearch": "Filter:"
            },
            "bJQueryUI": false,
            "aaSorting":[],
            "aoColumns": [
                { "sWidth": "30px" }
              , { "sWidth": "110px" }
              , { "sWidth": "110px" }
              , { "sWidth": "110px" }
              , { "sWidth": "100px" }
            ]
          });moveFilterAndFullPaginateArea('#%s');""".format(gridName,gridName).replaceAll("#table_var#",jsGridName)
        )  &
        JsRaw("""
        /* Formating function for row details */          
          function fnFormatDetails(id) {
            var sOut = '<span id="'+id+'" class="sgridbph"/>';
            return sOut;
          };
          
          $('td', #table_var#.fnGetNodes() ).each( function () {
            $(this).click( function () {
              var nTr = this.parentNode;
              var jTr = jQuery(nTr);
              if (jTr.hasClass('curspoint')) {
                var opened = jTr.prop("open");
                if (opened && opened.match("opened")) {
                  jTr.prop("open", "closed");
                  jQuery(nTr).find("td.listclose").removeClass("listclose").addClass("listopen");
                  #table_var#.fnClose(nTr);
                } else {
                  jTr.prop("open", "opened");
                  jQuery(nTr).find("td.listopen").removeClass("listopen").addClass("listclose");
                  var jsid = jTr.attr("jsuuid");
                  #table_var#.fnOpen( nTr, fnFormatDetails(jsid), 'details' );
                  %s;
                }
              }
            } );
          })
            
          function showParameters(){
            document.getElementById("show_parameters_info").style.display = "block";
          }
            
      """.format(
          SHtml.ajaxCall(JsVar("jsid"), details _)._2.toJsCmd).replaceAll("#table_var#",
             jsGridName)
     )
    )    
  }
  
  /*
   * Expect something like gridName-eventId 
   */
  private def details(jsid:String) : JsCmd = {
    val arr = jsid.split("-")
    if (arr.length != 2) {
      Alert("Called ID is not valid: %s".format(jsid))
    } else {
      val eventId = arr(1).toInt
      repos.getEventLog(eventId) match {
        case Full(event) => 
          SetHtml(jsid,displayDetails(event))
        case e:EmptyBox =>
          logger.debug((e ?~! "error").messageChain)
          Alert("Called id is not valid: %s".format(jsid))
      }
    }
  }
  
  
  private[this] def dataTableXml(gridName:String) = {
    <div>
      <table id={gridName} cellspacing="0">
        <thead>
          <tr class="head">
            <th class="titleId">ID</th>
            <th>Date</th>
            <th>Actor</th>
            <th>Event Type</th>
            <th>Description</th>
          </tr>
        </thead>
    
        <tbody>
          <tr class="eventLine" jsuuid="id">
            <td class="logId">[ID of event]</td>
            <td class="logDatetime">[Date and time of event]</td>
            <td class="logActor">[actor of the event]</td>
            <td class="logType">[type of event]</td>
            <td class="logDescription">[some user readable info]</td>
          </tr>
        </tbody>
      </table>
      
      <div id="logsGrid_paginate_area" class="paginate"></div>
    
    </div>   
     
  }
  
  
    //////////////////// Display description/details of ////////////////////
  
  //convention: "X" means "ignore"
  
  def displayDescription(event:EventLog) = {
    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 <a href={ruleLink(RuleId(id))}>{name}</a> ++ 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 <a href={directiveLink(DirectiveId(id))}>{name}</a> ++ 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 <a href={groupLink(NodeGroupId(id))}>{name}</a> ++ 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 <a href={nodeLink(NodeId(id))}>{name}</a> ++ 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 deploy Directive on nodes")
      case x:ManualStartDeployement => Text("Manually deploy Directive on nodes")
      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 of all nodes")
      case x:UpdatePolicyServer => Text("Change Policy Server authorized network")
      case x:ReloadTechniqueLibrary => Text("Technique library reloaded")
      case x:SuccessfulDeployment => Text("Successful deployment")
      case x:FailedDeployment => Text("Failed deployment")
      case x:ExportGroupsArchive => Text("New groups archive")
      case x:ExportTechniqueLibraryArchive => Text("New Directive library archive")
      case x:ExportRulesArchive => Text("New Rules 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:ImportFullArchive => Text("Restoring full archive")
      case _ => Text("Unknow event type")
      
    }
  }

  def displayDetails(event:EventLog) = {
    
    def xmlParameters(eventId: String) = {
        <h4 id={"showParameters%s".format(eventId)}
        class="curspoint showParameters" 
        onclick={"showParameters(%s)".format(eventId)} >Show Parameters</h4>
        <pre id={"showParametersInfo%s".format(eventId)} 
        style="display:none">{ event.details.map { n => xmlPretty.format(n) + "\n"} }</pre>
    }
    
    val reasonHtml = {
      val r = event.eventDetails.reason.getOrElse("")  
      if(r == "") NodeSeq.Empty 
      else <div style="margin-top:2px;"><b>Reason: </b>{r}</div>
    }
    
    def errorMessage(e:EmptyBox) = {
      logger.debug(e ?~! "Error when parsing details.", e)
      <xml:group>
        <div class="evloglmargin">
          <h4>Details for that node were not in a recognized format. 
            Raw data are displayed next:</h4>
          <pre>{ event.details.map { n => xmlPretty.format(n) + "\n"} }</pre>
        </div>
      </xml:group>
    }
                
    (event match {
    
      case add:AddRule =>
        "*" #> (logDetailsService.getRuleAddDetails(add.details) match {
          case Full(addDiff) => 
            <div class="evloglmargin">
              { ruleDetails(crDetailsXML, addDiff.rule)}
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Failure(m,_,_) => <p>{m}</p>
          case e:EmptyBox => errorMessage(e)
        })
      
      case del:DeleteRule =>
        "*" #> (logDetailsService.getRuleDeleteDetails(del.details) match {
          case Full(delDiff) =>
            <div class="evloglmargin">
              { ruleDetails(crDetailsXML, delDiff.rule) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      case mod:ModifyRule =>
        "*" #> (logDetailsService.getRuleModifyDetails(mod.details) match {
          case Full(modDiff) =>            
            <div class="evloglmargin">
              <h4>Rule overview:</h4>
              <ul class="evlogviewpad">
                <li><b>Rule ID:</b> { modDiff.id.value }</li>
                <li><b>Name:</b> { 
                  modDiff.modName.map(diff => diff.newValue).getOrElse(modDiff.name)
               }</li>
              </ul>
              {(
                "#name" #>  mapSimpleDiff(modDiff.modName) &
                "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivatedStatus) &
                "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) &
                "#shortDescription *" #> mapSimpleDiff(modDiff.modShortDescription) &
                "#longDescription *" #> mapSimpleDiff(modDiff.modLongDescription) &
                "#target" #> (
                  modDiff.modTarget.map { diff =>
                   ".diffOldValue *" #> groupTargetDetails(diff.oldValue) &
                   ".diffNewValue *" #> groupTargetDetails(diff.newValue)
                  }
                ) &
                "#policies" #> (
                   modDiff.modDirectiveIds.map { diff =>
                   val mapList = (set:Set[DirectiveId]) => {
                     if(set.size < 1) Text("None")
                     else 
                         { 
                           set.toSeq.sortWith( _.value < _.value ).map { id =>
                             <span>policy(<a href={directiveLink(id)}>{id.value}</a>)&nbsp;</span>
                           }
                         }
                   }
                   ".diffOldValue *" #> mapList(diff.oldValue) &
                   ".diffNewValue *" #> mapList(diff.newValue)
                   }
                )
              )(crModDetailsXML) ++
               Script(JsRaw("""
                 function showParameters(s){
                   if(document.getElementById("showParametersInfo" + s).style.display == "none")
                     document.getElementById("showParametersInfo" + s).style.display = "block";
                   else
                     document.getElementById("showParametersInfo" + s).style.display = "none";
                 }
              """))
              }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })

      ///////// Directive /////////
        
      case x:ModifyDirective =>   
        "*" #> (logDetailsService.getDirectiveModifyDetails(x.details) match {
          case Full(modDiff) =>
            <div class="evloglmargin">
              <h4>Directive overview:</h4>
              <ul class="evlogviewpad">
                <li><b>Directive ID:</b> { modDiff.id.value }</li>
                <li><b>Name:</b> {
                  modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name)
                }</li>
              </ul>
              {(
                "#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 =>
                    ".diffOldValue *" #> 
                      <pre>{xmlPretty.format(SectionVal.toXml(diff.oldValue))}</pre> &
                    ".diffNewValue *" #> 
                      <pre>{xmlPretty.format(SectionVal.toXml(diff.newValue))}</pre>
                  }
                ) 
              )(piModDetailsXML)}
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Failure(m, _, _) => <p>{m}</p>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:AddDirective =>   
        "*" #> (logDetailsService.getDirectiveAddDetails(x.details) match {
          case Full((diff,sectionVal)) =>
            <div class="evloglmargin">
              { directiveDetails(piDetailsXML, diff.techniqueName, 
                  diff.directive, sectionVal) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:DeleteDirective =>   
        "*" #> (logDetailsService.getDirectiveDeleteDetails(x.details) match {
          case Full((diff,sectionVal)) =>
            <div class="evloglmargin">
              { directiveDetails(piDetailsXML, diff.techniqueName, 
                  diff.directive, sectionVal) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      ///////// Node Group /////////
        
      case x:ModifyNodeGroup =>   
        "*" #> (logDetailsService.getNodeGroupModifyDetails(x.details) match {
          case Full(modDiff) => 
            <div class="evloglmargin">
              <h4>Node overview:</h4>
              <ul class="evlogviewpad">
                <li><b>Node ID:</b> { modDiff.id.value }</li>
                <li><b>Name:</b> {
                  modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name)
                }</li>
              </ul>
              {(
                "#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 =>
                   val mapList = (set:Set[NodeId]) =>
                   if(set.size == 0) 
                     Text("None")
                   else { 
                     set.toSeq.sortWith( _.value < _.value )
                       .map(id => <a href={nodeLink(id)}>{id.value}</a>)
                       .reduceLeft[NodeSeq]((a,b) => a ++ <span>,&nbsp;</span> ++ b)
                     }
                   ".diffOldValue *" #> mapList(diff.oldValue) &
                   ".diffNewValue *" #> mapList(diff.newValue)
                   }
                ) 
              )(groupModDetailsXML)}
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:AddNodeGroup =>   
        "*" #> (logDetailsService.getNodeGroupAddDetails(x.details) match {
          case Full(diff) =>
            <div class="evloglmargin">
              { groupDetails(groupDetailsXML, diff.group) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:DeleteNodeGroup =>   
        "*" #> (logDetailsService.getNodeGroupDeleteDetails(x.details) match {
          case Full(diff) =>
            <div class="evloglmargin">
            { groupDetails(groupDetailsXML, diff.group) }
            { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      ///////// Node Group /////////
        
      case x:AcceptNodeEventLog =>   
        "*" #> (logDetailsService.getAcceptNodeLogDetails(x.details) match {
          case Full(details) =>
            <div class="evloglmargin">
              <h4>Node accepted overview:</h4>
              { nodeDetails(details) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:RefuseNodeEventLog =>   
        "*" #> (logDetailsService.getRefuseNodeLogDetails(x.details) match {
          case Full(details) =>
            <div class="evloglmargin">
              <h4>Node refused overview:</h4>
              { nodeDetails(details) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:DeleteNodeEventLog =>   
        "*" #> (logDetailsService.getDeleteNodeLogDetails(x.details) match {
          case Full(details) =>
            <div class="evloglmargin">
              <h4>Node deleted overview:</h4>
              { nodeDetails(details) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      ////////// deployment //////////
      case x:SuccessfulDeployment => 
        "*" #> (logDetailsService.getDeploymentStatusDetails(x.details) match {
          case Full(SuccessStatus(id,started,ended,_)) =>
            <div class="evloglmargin">
              <h4>Successful deployment:</h4>
              <ul class="evlogviewpad">
                <li><b>ID:</b>&nbsp;{id}</li>
                <li><b>Start time:</b>&nbsp;{DateFormaterService.getFormatedDate(started)}</li>
                <li><b>End Time:</b>&nbsp;{DateFormaterService.getFormatedDate(ended)}</li>
              </ul>
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Full(_) => errorMessage(Failure("Unconsistant deployment status"))
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:FailedDeployment => 
        "*" #> (logDetailsService.getDeploymentStatusDetails(x.details) match {
          case Full(ErrorStatus(id,started,ended,failure)) =>
            <div class="evloglmargin">
              <h4>Failed deployment:</h4>
              <ul class="evlogviewpad">
                <li><b>ID:</b>&nbsp;{id}</li>
                <li><b>Start time:</b>&nbsp;{DateFormaterService.getFormatedDate(started)}</li>
                <li><b>End Time:</b>&nbsp;{DateFormaterService.getFormatedDate(ended)}</li>
                <li><b>Error stack trace:</b>&nbsp;{failure.messageChain}</li>
              </ul>
             { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Full(_) => errorMessage(Failure("Unconsistant deployment status"))
          case e:EmptyBox => errorMessage(e)
        })
        
      ////////// change authorized networks //////////
      
      case x:UpdatePolicyServer =>
        "*" #> (logDetailsService.getUpdatePolicyServerDetails(x.details) match {
          case Full(details) => 
            
            def networksToXML(nets:Seq[String]) = {
              <ul>{ nets.map { n => <li class="eventLogUpdatePolicy">{n}</li> } }</ul>
            }
            
            <div class="evloglmargin">{
              (
                  ".diffOldValue *" #> networksToXML(details.oldNetworks) &
                  ".diffNewValue *" #> networksToXML(details.newNetworks)
              )(authorizedNetworksXML)
            }
            { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
      
      // Technique library reloaded
      
      case x:ReloadTechniqueLibrary =>
        "*" #> (logDetailsService.getTechniqueLibraryReloadDetails(x.details) match {
          case Full(details) => 
              <div class="evloglmargin">
                <b>The Technique library was reloaded and following Techniques were updated:</b>
                <ul>{ details.map {technique => 
                  <li class="eventLogUpdatePolicy">{ "%s (version %s)".format(technique.name.value, technique.version.toString)}</li>
                } }</ul>
                { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
              </div>
            
          case e:EmptyBox => errorMessage(e)
        })
        
      // archiving & restoring 
        
      case x:ExportEventLog => 
        "*" #> (logDetailsService.getNewArchiveDetails(x.details, x) match {
          case Full(gitArchiveId) => displayExportArchiveDetails(gitArchiveId)
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:ImportEventLog => 
        "*" #> (logDetailsService.getRestoreArchiveDetails(x.details, x) match {
          case Full(gitArchiveId) => displayImportArchiveDetails(gitArchiveId)
          case e:EmptyBox => errorMessage(e)
        })

      // other case: do not display details at all
      case _ => "*" #> ""
    
    })(event.details)
  }
  
  private[this] def displayExportArchiveDetails(gitArchiveId: GitArchiveId) = 
    <div class="evloglmargin">
      <h4>Details of the new archive:</h4>
      <ul class="evlogviewpad">
        <li><b>Git path of the archive: </b>{gitArchiveId.path.value}</li>
        <li><b>Commit ID (hash): </b>{gitArchiveId.commit.value}</li>
        <li><b>Commiter name: </b>{gitArchiveId.commiter.getName}</li>
        <li><b>Commiter email: </b>{gitArchiveId.commiter.getEmailAddress}</li>
      </ul>
    </div>
        
  private[this] def displayImportArchiveDetails(gitCommitId: GitCommitId) = 
    <div class="evloglmargin">
      <h4>Details of the restored archive:</h4>
      <ul class="evlogviewpad">
        <li><b>Commit ID (hash): </b>{gitCommitId.value}</li>
      </ul>
    </div><commit_msg>Refs #2572 Improve templates for archive details in event logs<commit_after>/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero GPL v3, 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 Affero GPL v3
* licence, 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.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/agpl.html>.
*
*************************************************************************************
*/
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._
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.rudder.web.model.JsInitContextLinkUtil._
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._

/**
 * Used to display the event list, in the pending modification (AsyncDeployment), 
 * or in the administration EventLogsViewer
 */
class EventListDisplayer(
      logDetailsService: EventLogDetailsService
    , repos            : EventLogRepository
) extends Loggable {

  private[this] val xmlPretty = new scala.xml.PrettyPrinter(80, 2)
 // private[this] val gridName = "eventLogsGrid"
 // private[this] val jsGridName = "oTable" + gridName
  
  def display(events:Seq[EventLog], gridName:String) : NodeSeq  = {
    (
      "tbody *" #> ("tr" #> events.map { event => 
        ".eventLine [jsuuid]" #> Text(gridName + "-" + event.id.getOrElse(0).toString) &
        ".eventLine [class]" #> {
          if (event.details != <entry></entry> ) 
            Text("curspoint")
          else
            NodeSeq.Empty
        } &
        ".logId [class]" #> {
          if (event.details != <entry></entry> ) 
            Text("listopen")
          else
            Text("listEmpty")
        } &
        ".logId *" #> event.id.getOrElse(0).toString &
        ".logDatetime *" #> DateFormaterService.getFormatedDate(event.creationDate) &
        ".logActor *" #> event.principal.name &
        ".logType *" #> S.?("rudder.log.eventType.names." + event.eventType.serialize) &
        ".logDescription *" #> displayDescription(event) 
      })
     )(dataTableXml(gridName)) 
  }
  
  
  def initJs(gridName : String) : JsCmd = {
    val jsGridName = "oTable" + gridName
    JsRaw("var %s;".format(jsGridName)) &
    OnLoad(
        JsRaw("""
          /* Event handler function */
          #table_var# = $('#%s').dataTable({
            "asStripClasses": [ 'color1', 'color2' ],
            "bAutoWidth": false,
            "bFilter" :true,
            "bPaginate" :true,
            "bLengthChange": false,
            "sPaginationType": "full_numbers",
            "oLanguage": {
              "sSearch": "Filter:"
            },
            "bJQueryUI": false,
            "aaSorting":[],
            "aoColumns": [
                { "sWidth": "30px" }
              , { "sWidth": "110px" }
              , { "sWidth": "110px" }
              , { "sWidth": "110px" }
              , { "sWidth": "100px" }
            ]
          });moveFilterAndFullPaginateArea('#%s');""".format(gridName,gridName).replaceAll("#table_var#",jsGridName)
        )  &
        JsRaw("""
        /* Formating function for row details */          
          function fnFormatDetails(id) {
            var sOut = '<span id="'+id+'" class="sgridbph"/>';
            return sOut;
          };
          
          $('td', #table_var#.fnGetNodes() ).each( function () {
            $(this).click( function () {
              var nTr = this.parentNode;
              var jTr = jQuery(nTr);
              if (jTr.hasClass('curspoint')) {
                var opened = jTr.prop("open");
                if (opened && opened.match("opened")) {
                  jTr.prop("open", "closed");
                  jQuery(nTr).find("td.listclose").removeClass("listclose").addClass("listopen");
                  #table_var#.fnClose(nTr);
                } else {
                  jTr.prop("open", "opened");
                  jQuery(nTr).find("td.listopen").removeClass("listopen").addClass("listclose");
                  var jsid = jTr.attr("jsuuid");
                  #table_var#.fnOpen( nTr, fnFormatDetails(jsid), 'details' );
                  %s;
                }
              }
            } );
          })
            
          function showParameters(){
            document.getElementById("show_parameters_info").style.display = "block";
          }
            
      """.format(
          SHtml.ajaxCall(JsVar("jsid"), details _)._2.toJsCmd).replaceAll("#table_var#",
             jsGridName)
     )
    )    
  }
  
  /*
   * Expect something like gridName-eventId 
   */
  private def details(jsid:String) : JsCmd = {
    val arr = jsid.split("-")
    if (arr.length != 2) {
      Alert("Called ID is not valid: %s".format(jsid))
    } else {
      val eventId = arr(1).toInt
      repos.getEventLog(eventId) match {
        case Full(event) => 
          SetHtml(jsid,displayDetails(event))
        case e:EmptyBox =>
          logger.debug((e ?~! "error").messageChain)
          Alert("Called id is not valid: %s".format(jsid))
      }
    }
  }
  
  
  private[this] def dataTableXml(gridName:String) = {
    <div>
      <table id={gridName} cellspacing="0">
        <thead>
          <tr class="head">
            <th class="titleId">ID</th>
            <th>Date</th>
            <th>Actor</th>
            <th>Event Type</th>
            <th>Description</th>
          </tr>
        </thead>
    
        <tbody>
          <tr class="eventLine" jsuuid="id">
            <td class="logId">[ID of event]</td>
            <td class="logDatetime">[Date and time of event]</td>
            <td class="logActor">[actor of the event]</td>
            <td class="logType">[type of event]</td>
            <td class="logDescription">[some user readable info]</td>
          </tr>
        </tbody>
      </table>
      
      <div id="logsGrid_paginate_area" class="paginate"></div>
    
    </div>   
     
  }
  
  
    //////////////////// Display description/details of ////////////////////
  
  //convention: "X" means "ignore"
  
  def displayDescription(event:EventLog) = {
    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 <a href={ruleLink(RuleId(id))}>{name}</a> ++ 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 <a href={directiveLink(DirectiveId(id))}>{name}</a> ++ 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 <a href={groupLink(NodeGroupId(id))}>{name}</a> ++ 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 <a href={nodeLink(NodeId(id))}>{name}</a> ++ 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 deploy Directive on nodes")
      case x:ManualStartDeployement => Text("Manually deploy Directive on nodes")
      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 of all nodes")
      case x:UpdatePolicyServer => Text("Change Policy Server authorized network")
      case x:ReloadTechniqueLibrary => Text("Technique library reloaded")
      case x:SuccessfulDeployment => Text("Successful deployment")
      case x:FailedDeployment => Text("Failed deployment")
      case x:ExportGroupsArchive => Text("New groups archive")
      case x:ExportTechniqueLibraryArchive => Text("New Directive library archive")
      case x:ExportRulesArchive => Text("New Rules 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:ImportFullArchive => Text("Restoring full archive")
      case _ => Text("Unknow event type")
      
    }
  }

  def displayDetails(event:EventLog) = {
    
    def xmlParameters(eventId: String) = {
        <h4 id={"showParameters%s".format(eventId)}
        class="curspoint showParameters" 
        onclick={"showParameters(%s)".format(eventId)} >Raw Technical Details</h4>
        <pre id={"showParametersInfo%s".format(eventId)} 
        style="display:none">{ event.details.map { n => xmlPretty.format(n) + "\n"} }</pre>
    }
    
    val reasonHtml = {
      val r = event.eventDetails.reason.getOrElse("")  
      if(r == "") NodeSeq.Empty 
      else <div style="margin-top:2px;"><b>Reason: </b>{r}</div>
    }
    
    def errorMessage(e:EmptyBox) = {
      logger.debug(e ?~! "Error when parsing details.", e)
      <xml:group>
        <div class="evloglmargin">
          <h4>Details for that node were not in a recognized format. 
            Raw data are displayed next:</h4>
          <pre>{ event.details.map { n => xmlPretty.format(n) + "\n"} }</pre>
        </div>
      </xml:group>
    }
                
    (event match {
    
      case add:AddRule =>
        "*" #> (logDetailsService.getRuleAddDetails(add.details) match {
          case Full(addDiff) => 
            <div class="evloglmargin">
              { ruleDetails(crDetailsXML, addDiff.rule)}
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Failure(m,_,_) => <p>{m}</p>
          case e:EmptyBox => errorMessage(e)
        })
      
      case del:DeleteRule =>
        "*" #> (logDetailsService.getRuleDeleteDetails(del.details) match {
          case Full(delDiff) =>
            <div class="evloglmargin">
              { ruleDetails(crDetailsXML, delDiff.rule) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      case mod:ModifyRule =>
        "*" #> (logDetailsService.getRuleModifyDetails(mod.details) match {
          case Full(modDiff) =>            
            <div class="evloglmargin">
              <h4>Rule overview:</h4>
              <ul class="evlogviewpad">
                <li><b>Rule ID:</b> { modDiff.id.value }</li>
                <li><b>Name:</b> { 
                  modDiff.modName.map(diff => diff.newValue).getOrElse(modDiff.name)
               }</li>
              </ul>
              {(
                "#name" #>  mapSimpleDiff(modDiff.modName) &
                "#isEnabled *" #> mapSimpleDiff(modDiff.modIsActivatedStatus) &
                "#isSystem *" #> mapSimpleDiff(modDiff.modIsSystem) &
                "#shortDescription *" #> mapSimpleDiff(modDiff.modShortDescription) &
                "#longDescription *" #> mapSimpleDiff(modDiff.modLongDescription) &
                "#target" #> (
                  modDiff.modTarget.map { diff =>
                   ".diffOldValue *" #> groupTargetDetails(diff.oldValue) &
                   ".diffNewValue *" #> groupTargetDetails(diff.newValue)
                  }
                ) &
                "#policies" #> (
                   modDiff.modDirectiveIds.map { diff =>
                   val mapList = (set:Set[DirectiveId]) => {
                     if(set.size < 1) Text("None")
                     else 
                         { 
                           set.toSeq.sortWith( _.value < _.value ).map { id =>
                             <span>policy(<a href={directiveLink(id)}>{id.value}</a>)&nbsp;</span>
                           }
                         }
                   }
                   ".diffOldValue *" #> mapList(diff.oldValue) &
                   ".diffNewValue *" #> mapList(diff.newValue)
                   }
                )
              )(crModDetailsXML) ++
               Script(JsRaw("""
                 function showParameters(s){
                   if(document.getElementById("showParametersInfo" + s).style.display == "none")
                     document.getElementById("showParametersInfo" + s).style.display = "block";
                   else
                     document.getElementById("showParametersInfo" + s).style.display = "none";
                 }
              """))
              }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })

      ///////// Directive /////////
        
      case x:ModifyDirective =>   
        "*" #> (logDetailsService.getDirectiveModifyDetails(x.details) match {
          case Full(modDiff) =>
            <div class="evloglmargin">
              <h4>Directive overview:</h4>
              <ul class="evlogviewpad">
                <li><b>Directive ID:</b> { modDiff.id.value }</li>
                <li><b>Name:</b> {
                  modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name)
                }</li>
              </ul>
              {(
                "#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 =>
                    ".diffOldValue *" #> 
                      <pre>{xmlPretty.format(SectionVal.toXml(diff.oldValue))}</pre> &
                    ".diffNewValue *" #> 
                      <pre>{xmlPretty.format(SectionVal.toXml(diff.newValue))}</pre>
                  }
                ) 
              )(piModDetailsXML)}
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Failure(m, _, _) => <p>{m}</p>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:AddDirective =>   
        "*" #> (logDetailsService.getDirectiveAddDetails(x.details) match {
          case Full((diff,sectionVal)) =>
            <div class="evloglmargin">
              { directiveDetails(piDetailsXML, diff.techniqueName, 
                  diff.directive, sectionVal) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:DeleteDirective =>   
        "*" #> (logDetailsService.getDirectiveDeleteDetails(x.details) match {
          case Full((diff,sectionVal)) =>
            <div class="evloglmargin">
              { directiveDetails(piDetailsXML, diff.techniqueName, 
                  diff.directive, sectionVal) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      ///////// Node Group /////////
        
      case x:ModifyNodeGroup =>   
        "*" #> (logDetailsService.getNodeGroupModifyDetails(x.details) match {
          case Full(modDiff) => 
            <div class="evloglmargin">
              <h4>Node overview:</h4>
              <ul class="evlogviewpad">
                <li><b>Node ID:</b> { modDiff.id.value }</li>
                <li><b>Name:</b> {
                  modDiff.modName.map(diff => diff.newValue.toString).getOrElse(modDiff.name)
                }</li>
              </ul>
              {(
                "#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 =>
                   val mapList = (set:Set[NodeId]) =>
                   if(set.size == 0) 
                     Text("None")
                   else { 
                     set.toSeq.sortWith( _.value < _.value )
                       .map(id => <a href={nodeLink(id)}>{id.value}</a>)
                       .reduceLeft[NodeSeq]((a,b) => a ++ <span>,&nbsp;</span> ++ b)
                     }
                   ".diffOldValue *" #> mapList(diff.oldValue) &
                   ".diffNewValue *" #> mapList(diff.newValue)
                   }
                ) 
              )(groupModDetailsXML)}
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:AddNodeGroup =>   
        "*" #> (logDetailsService.getNodeGroupAddDetails(x.details) match {
          case Full(diff) =>
            <div class="evloglmargin">
              { groupDetails(groupDetailsXML, diff.group) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
    
      case x:DeleteNodeGroup =>   
        "*" #> (logDetailsService.getNodeGroupDeleteDetails(x.details) match {
          case Full(diff) =>
            <div class="evloglmargin">
            { groupDetails(groupDetailsXML, diff.group) }
            { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      ///////// Node Group /////////
        
      case x:AcceptNodeEventLog =>   
        "*" #> (logDetailsService.getAcceptNodeLogDetails(x.details) match {
          case Full(details) =>
            <div class="evloglmargin">
              <h4>Node accepted overview:</h4>
              { nodeDetails(details) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:RefuseNodeEventLog =>   
        "*" #> (logDetailsService.getRefuseNodeLogDetails(x.details) match {
          case Full(details) =>
            <div class="evloglmargin">
              <h4>Node refused overview:</h4>
              { nodeDetails(details) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:DeleteNodeEventLog =>   
        "*" #> (logDetailsService.getDeleteNodeLogDetails(x.details) match {
          case Full(details) =>
            <div class="evloglmargin">
              <h4>Node deleted overview:</h4>
              { nodeDetails(details) }
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
        
      ////////// deployment //////////
      case x:SuccessfulDeployment => 
        "*" #> (logDetailsService.getDeploymentStatusDetails(x.details) match {
          case Full(SuccessStatus(id,started,ended,_)) =>
            <div class="evloglmargin">
              <h4>Successful deployment:</h4>
              <ul class="evlogviewpad">
                <li><b>ID:</b>&nbsp;{id}</li>
                <li><b>Start time:</b>&nbsp;{DateFormaterService.getFormatedDate(started)}</li>
                <li><b>End Time:</b>&nbsp;{DateFormaterService.getFormatedDate(ended)}</li>
              </ul>
              { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Full(_) => errorMessage(Failure("Unconsistant deployment status"))
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:FailedDeployment => 
        "*" #> (logDetailsService.getDeploymentStatusDetails(x.details) match {
          case Full(ErrorStatus(id,started,ended,failure)) =>
            <div class="evloglmargin">
              <h4>Failed deployment:</h4>
              <ul class="evlogviewpad">
                <li><b>ID:</b>&nbsp;{id}</li>
                <li><b>Start time:</b>&nbsp;{DateFormaterService.getFormatedDate(started)}</li>
                <li><b>End Time:</b>&nbsp;{DateFormaterService.getFormatedDate(ended)}</li>
                <li><b>Error stack trace:</b>&nbsp;{failure.messageChain}</li>
              </ul>
             { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case Full(_) => errorMessage(Failure("Unconsistant deployment status"))
          case e:EmptyBox => errorMessage(e)
        })
        
      ////////// change authorized networks //////////
      
      case x:UpdatePolicyServer =>
        "*" #> (logDetailsService.getUpdatePolicyServerDetails(x.details) match {
          case Full(details) => 
            
            def networksToXML(nets:Seq[String]) = {
              <ul>{ nets.map { n => <li class="eventLogUpdatePolicy">{n}</li> } }</ul>
            }
            
            <div class="evloglmargin">{
              (
                  ".diffOldValue *" #> networksToXML(details.oldNetworks) &
                  ".diffNewValue *" #> networksToXML(details.newNetworks)
              )(authorizedNetworksXML)
            }
            { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
            </div>
          case e:EmptyBox => errorMessage(e)
        })
      
      // Technique library reloaded
      
      case x:ReloadTechniqueLibrary =>
        "*" #> (logDetailsService.getTechniqueLibraryReloadDetails(x.details) match {
          case Full(details) => 
              <div class="evloglmargin">
                <b>The Technique library was reloaded and following Techniques were updated:</b>
                <ul>{ details.map {technique => 
                  <li class="eventLogUpdatePolicy">{ "%s (version %s)".format(technique.name.value, technique.version.toString)}</li>
                } }</ul>
                { reasonHtml }
              { xmlParameters(event.id.getOrElse(0).toString) }
              </div>
            
          case e:EmptyBox => errorMessage(e)
        })
        
      // archiving & restoring 
        
      case x:ExportEventLog => 
        "*" #> (logDetailsService.getNewArchiveDetails(x.details, x) match {
          case Full(gitArchiveId) => 
              { displayExportArchiveDetails(gitArchiveId, xmlParameters(event.id.getOrElse(0).toString)) }
            
          case e:EmptyBox => errorMessage(e)
        })
        
      case x:ImportEventLog => 
        "*" #> (logDetailsService.getRestoreArchiveDetails(x.details, x) match {
          case Full(gitArchiveId) => 
              { displayImportArchiveDetails(gitArchiveId, xmlParameters(event.id.getOrElse(0).toString)) }
          case e:EmptyBox => errorMessage(e)
        })

      // other case: do not display details at all
      case _ => "*" #> ""
    
    })(event.details)
  }
  
  private[this] def displayExportArchiveDetails(gitArchiveId: GitArchiveId, rawData: NodeSeq) = 
    <div class="evloglmargin">
      <h4>Details of the new archive:</h4>
      <ul class="evlogviewpad">
        <li><b>Git path of the archive: </b>{gitArchiveId.path.value}</li>
        <li><b>Commit ID (hash): </b>{gitArchiveId.commit.value}</li>
        <li><b>Commiter name: </b>{gitArchiveId.commiter.getName}</li>
        <li><b>Commiter email: </b>{gitArchiveId.commiter.getEmailAddress}</li>
      </ul>
      {rawData}
    </div>
        
  private[this] def displayImportArchiveDetails(gitCommitId: GitCommitId, rawData: NodeSeq) = 
    <div class="evloglmargin">
      <h4>Details of the restored archive:</h4>
      <ul class="evlogviewpad">
        <li><b>Commit ID (hash): </b>{gitCommitId.value}</li>
      </ul>
      {rawData}
    </div>
{'fraction_non_alphanumeric': 0.14704216376960386, 'fraction_numerical': 0.0031153750840797255, 'mean_word_length': 1.917226066301766, 'pattern_counts': {'":': 28, '<': 481, '<?xml version=': 0, '>': 825, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 54}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2281005', 'n_tokens_mistral': 16478, 'n_tokens_neox': 15928, 'n_words': 2971}