src/main/scala/radview/snippet/AccountSnippet.scala
author Tomas Zeman <tzeman@volny.cz>
Tue, 12 Apr 2011 19:10:43 +0200
changeset 10 397aca5b6f87
parent 2 cf829ec742b3
child 22 6b1a7f3429ca
permissions -rw-r--r--
c0b6c3c127e597c9 CDR columns updates - ADSL

/*
 * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package radview.snippet

import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.http.{RewriteRequest => RReq, RewriteResponse => RResp, ParsePath => PP}
import net.liftweb.mapper._
import net.liftweb.sitemap._
import net.liftweb.sitemap.Loc._
import net.liftweb.util._
import net.liftweb.util.Helpers._
import net.tz.lift.snippet._
import net.tz.lift.util._
import org.joda.time.DateTime
import radview.model._
import scala.xml.{NodeSeq, Text}

object AsAccountImsi {
  def unapply(in: String): Option[RadCheckSub] = RadCheckSub.byImsi(in)
}

abstract sealed class AccountLoc
case object AccountSearch extends AccountLoc
case class ViewAccount(acc: RadCheckSub) extends AccountLoc
case class SyslogAccount(acc: RadCheckSub) extends AccountLoc
case class CdrAccount(acc: RadCheckSub) extends AccountLoc

object AsAccount {
  def unapply(loc: Box[AccountLoc]): Option[RadCheckSub] = loc flatMap { _ match {
    case SyslogAccount(a) => Some(a)
    case ViewAccount(a) => Some(a)
    case CdrAccount(a) => Some(a)
    case _ => None
  }}
}

object AccountSnippet extends Loc[AccountLoc] with SnippetHelpers {

  val name = "account"
  val prefix = "account"
  val tpl = "account"
  val params = List(Hidden)
  val defaultValue = Full(AccountSearch)
  val link = new Link[AccountLoc](List(prefix), true) {
    override def createPath(l: AccountLoc): String = l match {
      case AccountSearch => prefix
      case ViewAccount(a) => mkPath(prefix, "imsi", a.imsi.is)
      case SyslogAccount(a) => mkPath(prefix, "imsi", a.imsi.is, "syslog")
      case CdrAccount(a) => mkPath(prefix, "imsi", a.imsi.is, "cdr")
    }
  }

  def url(l: AccountLoc) = link.createPath(l)

  val text = LinkText[AccountLoc](l => Text(l match {
    case AccountSearch => "Account Search"
    case ViewAccount(a) => "Account " + a.imsi
    case SyslogAccount(a) => "Syslog search for account " + a.imsi
    case CdrAccount(a) => "CDR search for account " + a.imsi
  }))

  override def rewrite: LocRewrite = Full({
    case RReq(PP("account" :: Nil, _,_,_), _,_) =>
      (new RResp(PP(List(tpl, "search"), "", true, false), Map.empty,
        true), AccountSearch)
    case RReq(PP("account" :: "imsi" :: AsAccountImsi(a) :: xs, _,_,_), _,_) =>
      val l = xs match {
        case "syslog" :: xs =>
          ActionLinks.append(A(url(ViewAccount(a)), "Back to account"))
          SyslogAccount(a)
        case "cdr" :: xs =>
          ActionLinks.append(A(url(ViewAccount(a)), "Back to account"))
          CdrAccount(a)
        case _ =>
          ActionLinks.append(A(url(SyslogAccount(a)), "Syslog search"))
          ActionLinks.append(A(url(CdrAccount(a)), "CDR search"))
          ViewAccount(a)
      }
      (RResp(List(tpl, "view")), l)
  })

  override def snippets: SnippetTest = {
    case ("form", Full(AccountSearch)) => searchForm
    case ("list", Full(AccountSearch)) => searchList
    case ("sub", AsAccount(a)) => AccountSubPanel(a)
    case ("if-nai", AsAccount(a)) if !a.isAdsl => PassThru
    case ("nai", AsAccount(a)) => AccountNaiPanel(a)

    case ("syslog-list", Full(SyslogAccount(a))) => syslogList(a)
    case ("form", Full(SyslogAccount(a))) => syslogForm(a)
    case ("cdr-list", Full(CdrAccount(a))) => cdrList(a)
    case ("form", Full(CdrAccount(a))) => cdrForm(a)

    case ("form", _) => ClearNodes
    case ("syslog-list", _) => ClearNodes
    case ("cdr-list", _) => ClearNodes
    case ("sub", _) => ClearNodes
    case ("if-nai", _) => ClearNodes
    case ("nai", _) => ClearNodes
  }

  /* Shared in all searches. */
  object mxCnt extends RequestVar[Int](100)

  /* Search account vars. */
  object searchApp extends RequestVar[String]("BOSS")
  object searchFld extends RequestVar[Int](0)
  object searchVal extends RequestVar[String]("")

  val fldsRCS = List(RadCheckSub.msisdn, RadCheckSub.imsi)
  val fldsRRS = List(RadReplySub.statIp)
  val fldsRRN = List(RadReplyNai.esn)

  val searchFlds: List[MappedField[String,_]] = List(
    RadCheckSub.msisdn, RadCheckSub.imsi,
    RadReplySub.statIp,
    RadReplyNai.esn
  )

  def searchForm: (NodeSeq => NodeSeq) = {
      import AttrRow._
      SimpleForm(List(
        formRow(Text(RadCheckSub.app.displayName),
          SHtml.select(RadCheckSub.getApps map(i => (i.app.is, i.app.tr)),
            Full(searchApp.is), searchApp(_))),
        formRow(SHtml.selectObj[Int](
          searchFlds.zipWithIndex.map(i => (i._2, i._1.displayName)),
          Full(searchFld.is), {i => searchFld(i)}),
          SHtml.textElem(searchVal)
        ),
        formRow(Text("Max. results"), SHtml.selectObj[Int](
          List(100, 200, 500) map { i => (i, i.toString) },
          Full(mxCnt.is), mxCnt(_)))
      ), "Search")
  }

  def searchList: (NodeSeq => NodeSeq) = {
    val rowsB = searchVal.is match {
      case "" | null => Empty
      case v =>
        val mx = mxCnt.is
        val fld = searchFlds(searchFld.is)
        val n = fld.name
        fld.fieldOwner match {
          case o: RadCheckSub =>
            o.fieldByName(n) map { f: MappedField[String, RadCheckSub] =>
              RadCheckSub.findAll(Like(f, "%" + v + "%"),
                By(RadCheckSub.app, searchApp.is),
                OrderBy(RadCheckSub.imsi, Ascending), MaxRows(mx))
            }
          case o: RadReplySub =>
            o.fieldByName(n) map { f: MappedField[String, RadReplySub] =>
              val imsis = RadReplySub.findAllFields(List(RadReplySub.imsi),
                Like(f, "%" + v + "%"),
                By(RadReplySub.app, searchApp.is)) map (_.imsi.is)
              RadCheckSub.findAll(ByList(RadCheckSub.imsi, imsis),
                OrderBy(RadCheckSub.imsi, Ascending), MaxRows(mx))
            }
          case o: RadReplyNai =>
            o.fieldByName(n) map { f: MappedField[String, RadReplyNai] =>
              val imsis = RadReplyNai.findAllFields(List(RadReplyNai.value),
                Like(f, "%" + v + "%"),
                By(RadReplyNai.app, searchApp.is)) map (_.value.imsi)
              RadCheckSub.findAll(ByList(RadCheckSub.imsi12, imsis),
                OrderBy(RadCheckSub.imsi, Ascending), MaxRows(mx))
            }
        }
    }
    rowsB map { rcs =>
      val rrs = RadReplySub.byImsiMap(rcs map {_.imsi.is})
      val rrn = RadReplyNai.byImsi12map(rcs map {_.imsi12.is})
      val rcn = RadCheckNai.byRrnMap(rrn.values.toSeq)
      AccountTable(rcs, rrs, rrn, rcn)
    } openOr ClearNodes
  }

  /* Vars common to syslog + cdr search */
  object from extends RequestVar[DateTime]((new DateTime).minusDays(1))
  object to extends RequestVar[DateTime](new DateTime)
  object fromD extends RequestVar[String](AsDateMidnight(from))
  object fromT extends RequestVar[String](AsTimePeriod(from))
  object toD extends RequestVar[String](AsDateMidnight(to))
  object toT extends RequestVar[String](AsTimePeriod(to))
  private def setFromToDateTimes() = {
    import AsDateMidnight._
    AsDateMidnight(fromD) map { from(_) }
    AsTimePeriod(fromT) map { p => from(from.plus(p)) }
    AsDateMidnight(toD) map { to(_) }
    AsTimePeriod(toT) map { p => to(to.plus(p)) }
    ()
  }

  /* Shared between syslogForm + syslogList */
  object syslogQp extends RequestVar[Box[List[QueryParam[Syslog]]]](Empty)

  def syslogForm(a: RadCheckSub): (NodeSeq => NodeSeq) = {
    object byQp extends RequestVar[Box[QueryParam[Syslog]]](Empty)

    val selectQp = List[(QueryParam[Syslog], String)](
      (By(Syslog.imsi, asLong(a.imsi).openOr(0L)), a.imsi.displayName)) ++
      a.replyNai.map { n => (By(Syslog.username, n.esn.esn), "ESN") }

    def setD() = {
      setFromToDateTimes()
      import AsDateMidnight.dt2d
      val qp = List[QueryParam[Syslog]](
        By_<(Syslog.ts, to.plusMinutes(1)),
        By_>(Syslog.ts, from.minusMinutes(1)),
        OrderBy(Syslog.ts, Ascending)
      ) ++ byQp.is
      syslogQp(Full(qp))
    }

    import AttrRow._
    SimpleForm(List(
      formRow(Text("Search by"), SHtml.selectObj[QueryParam[Syslog]](selectQp,
        byQp, { qp => byQp(Full(qp)) } )),
      formRow(Text("From"), SHtml.textElem(fromD, ("class", "date")) ++
        SHtml.textElem(fromT)),
      formRow(Text("To"), SHtml.textElem(toD, ("class", "date")) ++
        SHtml.textElem(toT)),
      formRow(Text("Max. results"), SHtml.selectObj[Int](
        List(100, 200, 500) map { i => (i, i.toString) },
        Full(mxCnt.is), mxCnt(_)))
    ), "Search", setD)
  }

  def syslogList(a: RadCheckSub): (NodeSeq => NodeSeq) = syslogQp map { qp =>
    ".num-rows-panel" #> Panel(List(
        ("Records found", Syslog.count(qp:_*)),
        ("Number of successful logins",
          Syslog.count((qp:+By(Syslog.loginStatus, 1)):_*)),
        ("Number of failed logins",
          Syslog.count((qp:+By(Syslog.loginStatus, 0)):_*))
      ) map { r => new AttrRow(Text(r._1), Text(r._2.toString),
        "attr-name-wide", "attr-value") }) &
    ".list" #> SyslogTable(Syslog.findAll((qp:+MaxRows(mxCnt.is) ):_*),
      qp.find { _ match {
        case Cmp(f, _, _, _, _) if f.name == Syslog.imsi.name => true
        case _ => false
      }}.isDefined)
  } openOr ClearNodes

  /* Shared between cdrForm and cdrList. */
  object cdrQp extends RequestVar[Box[List[QueryParam[Cdr]]]](Empty)
  object byCdrQp extends RequestVar[Box[QueryParam[Cdr]]](Empty)

  def cdrForm(a: RadCheckSub): (NodeSeq => NodeSeq) = {

    val selectQp = List[(QueryParam[Cdr], String)](
      (By(Cdr.callingNo, asLong(a.imsi).openOr(0L)), a.imsi.displayName)) ++
      a.replySub.map { r => (By(Cdr.framedIp, r.statIp.is),
        r.statIp.displayName) }

    def setD() = {
      setFromToDateTimes()
      import AsDateMidnight.dt2d
      val qp = List[QueryParam[Cdr]](
        By_<(Cdr.ts, to.plusMinutes(1)),
        By_>(Cdr.ts, from.minusMinutes(1)),
        OrderBy(Cdr.ts, Ascending)
      ) ++ byCdrQp.is
      cdrQp(Full(qp))
    }

    import AttrRow._
    SimpleForm(List(
      formRow(Text("Search by"), SHtml.selectObj[QueryParam[Cdr]](selectQp,
        byCdrQp, { qp => byCdrQp(Full(qp)) } )),
      formRow(Text("From"), SHtml.textElem(fromD, ("class", "date")) ++
        SHtml.textElem(fromT)),
      formRow(Text("To"), SHtml.textElem(toD, ("class", "date")) ++
        SHtml.textElem(toT)),
      formRow(Text("Max. results"), SHtml.selectObj[Int](
        List(100, 200, 500, 1000, 2000) map { i => (i, i.toString) },
        Full(mxCnt.is), mxCnt(_)))
    ), "Search", setD)
  }

  def cdrList(a: RadCheckSub): (NodeSeq => NodeSeq) = cdrQp map { qp =>
    import AsDateMidnight.dt2d
    val sess = byCdrQp map { _ match {
      case Cmp(f, OprEnum.Eql, Full(v), _, _) =>
        Cdr.sessions(from.is, to.is, f, v.toString)
      case _ => Nil
    }} openOr Nil

    ".num-rows-panel" #> Panel(List(
        ("Records found", Cdr.count(qp:_*)),
        ("No. of sessions", sess.length),
        ("Total bytes in/out [MB]",
          Bytes.mb(sess map { _.inBytes.is } sum) ++ Text(" / ") ++
          Bytes.mb(sess map { _.outBytes.is } sum))
      ) map { r => new AttrRow(Text(r._1), Text(r._2.toString),
        "attr-name-wide", "attr-value") } ) &
    ".list" #> CdrTable(Cdr.findAll( (qp:+MaxRows(mxCnt.is) ):_*), a.isAdsl)
  } openOr ClearNodes
}

object AccountTable {
  def apply(rcs: Seq[RadCheckSub], rrs: Map[String, RadReplySub],
    rrn: Map[String, RadReplyNai], rcn: Map[String, RadCheckNai]) = {

    val tpl = rcs.headOption.getOrElse(RadCheckSub)

    def valF[T<:Mapper[T]](f: MappedField[_, T])(i: T): NodeSeq =
      f.actualField(i).asHtml

    val imsiCol = Column[RadCheckSub](tpl.imsi.displayName, {
      i: RadCheckSub => A(AccountSnippet.url(ViewAccount(i)), i.imsi.is)
    })
    val colsRcs = tpl.fieldsForList map { f =>
      Column[RadCheckSub](f.displayName, valF(f) _)
    }
    val colsRrs = RadReplySub.fieldsForList map { f =>
      Column[RadCheckSub](f.displayName, { i: RadCheckSub =>
        rrs.get(i.imsi.is).map { r =>
          f.actualField(r).asHtml } getOrElse NodeSeq.Empty })
    }
    val colsRrn = RadReplyNai.fieldsForList map { f =>
      Column[RadCheckSub](f.displayName, { i: RadCheckSub =>
        rrn.get(i.imsi12.is).map { r =>
          f.actualField(r).asHtml } getOrElse NodeSeq.Empty })
    }
    val colsRcn = RadCheckNai.fieldsForList map { f =>
      Column[RadCheckSub](f.displayName, { i: RadCheckSub =>
        val o: Option[NodeSeq] = for {
          r <- rrn.get(i.imsi12.is)
          n <- rcn.get(r.esn.is)
        } yield {
          f.actualField(n).asHtml
        }
        o getOrElse NodeSeq.Empty
      })
    }

    Table[RadCheckSub](List(imsiCol) ++ colsRcs ++ colsRrs ++
      tpl.ifAdsl(Nil, colsRrn ++ colsRcn), rcs)
  }
}

object AccountSubPanel {
  def apply(a: RadCheckSub): (NodeSeq => NodeSeq) = {
    val r = a.replySub.openOr(RadReplySub.create)
    Panel(a.fieldsForDisplay.map(AttrRow(_)) ++
      r.fieldsForDisplay.map(AttrRow(_)))
  }
}

object AccountNaiPanel {
  def apply(a: RadCheckSub): (NodeSeq => NodeSeq) = {
    val r  = a.replyNai.openOr(RadReplyNai.create)
    val ch = a.checkNai.openOr(RadCheckNai.create)
    Panel(ch.fieldsForDisplay.map(AttrRow(_)) ++
      r.fieldsForDisplay.map(AttrRow(_)))
  }
}

object SyslogTable {
  def apply(rows: Iterable[Syslog], includeImsi: Boolean) = {
    val imsiCol: List[MappedField[_, Syslog]] = includeImsi match {
      case true => List(Syslog.imsi)
      case false => Nil
    }
    Table[Syslog]((Syslog.fieldsForList ++ imsiCol).map { f =>
      Column[Syslog](f.displayName, { i: Syslog => f.actualField(i).asHtml })
    }, rows)
  }
}

// vim: set ts=2 sw=2 et: