Main project functionality
authorTomas Zeman <tzeman@volny.cz>
Sun, 03 Apr 2011 15:55:02 +0200
changeset 2 cf829ec742b3
parent 1 69e26359f2c8
child 3 009d59a415c1
Main project functionality
src/main/resources/default.props
src/main/resources/logback.xml
src/main/scala/bootstrap/liftweb/Boot.scala
src/main/scala/net/tz/lift/boot/ProtoBoot.scala
src/main/scala/net/tz/lift/snippet/Panel.scala
src/main/scala/net/tz/lift/snippet/SnippetHelpers.scala
src/main/scala/net/tz/lift/snippet/Table.scala
src/main/scala/net/tz/lift/util/DateExtension.scala
src/main/scala/net/tz/lift/util/Helpers.scala
src/main/scala/net/tz/lift/util/YmdDateTimeConverter.scala
src/main/scala/radview/model/Account.scala
src/main/scala/radview/model/Cdr.scala
src/main/scala/radview/model/Cell.scala
src/main/scala/radview/model/Common.scala
src/main/scala/radview/model/Syslog.scala
src/main/scala/radview/snippet/AccountSnippet.scala
src/main/scala/radview/snippet/CellSnippet.scala
src/main/scala/radview/snippet/SessionSnippet.scala
src/main/webapp/WEB-INF/web.xml
src/main/webapp/account/search.html
src/main/webapp/account/view.html
src/main/webapp/cell/search.html
src/main/webapp/cell/view.html
src/main/webapp/css/jquery-ui-1.8.9.custom.css
src/main/webapp/css/site.css
src/main/webapp/index.html
src/main/webapp/js/jquery-ui-1.8.9.custom.min.js
src/main/webapp/session.html
src/main/webapp/templates-hidden/default.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/default.props	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,14 @@
+# RadView application properties
+#
+
+db.radacct.url=jdbc:mysql://127.0.0.1:7803/radacct
+db.radacct.user=dev
+db.radacct.pass=dev
+
+db.radius.url=jdbc:mysql://127.0.0.1:7803/radius
+db.radius.user=dev
+db.radius.pass=dev
+
+db.syslog.url=jdbc:mysql://127.0.0.1:7803/syslog
+db.syslog.user=dev
+db.syslog.pass=dev
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/logback.xml	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,14 @@
+<configuration debug="true"> 
+
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
+    <!-- encoders are  by default assigned the type
+    ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="debug">
+    <appender-ref ref="STDOUT" />
+  </root>
+</configuration>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/bootstrap/liftweb/Boot.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,97 @@
+/*
+ * 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 bootstrap.liftweb
+
+import net.liftweb.common._
+import net.liftweb.http._
+import net.liftweb.mapper._
+import net.liftweb.sitemap._
+import net.liftweb.sitemap.Loc._
+import net.liftweb.util._
+import net.liftweb.widgets.menu.MenuWidget
+import net.tz.lift.snippet.ActionLinks
+import net.tz.lift.util.YmdDateTimeConverter
+import radview.model._
+import radview.snippet._
+import scala.xml.NodeSeq
+
+class Boot extends Logger {
+
+  def boot = {
+    /* DB stuff */
+    initDb("radacct", RadAcctConnectionIdentifier)
+    initDb("radius", RadiusConnectionIdentifier)
+    initDb("syslog", SyslogConnectionIdentifier)
+    S.addAround(DB.buildLoanWrapper(List(RadAcctConnectionIdentifier,
+      RadiusConnectionIdentifier, SyslogConnectionIdentifier)))
+
+    if (Props.mode == Props.RunModes.Development)
+      DB.addLogFunc { (dbLog, l) => dbLog.statementEntries.foreach { e =>
+        debug("Query: " + e.statement)
+    }}
+
+    /* Date format */
+    LiftRules.dateTimeConverter.default.set { () => YmdDateTimeConverter }
+
+    /* Handle end slash and drop it (except for home page) */
+    LiftRules.statelessRewrite.append {
+      case RewriteRequest(ParsePath(xs,_,_,true),_,_) if (xs.size > 1) &&
+        (xs.lastOption == Some("index")) =>
+          RewriteResponse(xs dropRight 1)
+    }
+
+    /* Snippet dispatch */
+    LiftRules.snippetDispatch.append {
+      case "Menubar" => new AnyRef with DispatchSnippet {
+        def dispatch: DispatchIt = {
+          case _ => { xhtml => MenuWidget() }
+        }
+      }
+      case "action-links" => ActionLinks
+    }
+
+    /* Sitemap */
+    SiteMap.enforceUniqueLinks = false
+
+    LiftRules.setSiteMap(SiteMap(
+      Menu.i("Home") / "index" >> Hidden,
+      Menu(CellSnippet),
+      Menu.i("Cells") / "cell",
+      Menu(SessionSnippet),
+      Menu(AccountSnippet),
+      Menu.i("Accounts") / "account"
+    ))
+
+    /* Menu widget */
+    MenuWidget.init()
+
+    /* Http conf */
+    LiftRules.logServiceRequestTiming = false
+    LiftRules.early.append(_.setCharacterEncoding("UTF-8"))
+  }
+
+  def initDb(ident: String, connId: ConnectionIdentifier) = {
+    val driver = "com.mysql.jdbc.Driver"
+    val vendor = new StandardDBVendor(driver,
+      Props.get("db."+ident+".url") openOr ("jdbc:mysql://localhost/"+ident),
+      Props.get("db."+ident+".user"), Props.get("db."+ident+".pass"))
+    //LiftRules.unloadHooks.append(vendor.closeAllConnections_! _)
+    DB.defineConnectionManager(connId, vendor)
+    vendor.newConnection(connId)
+  }
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/boot/ProtoBoot.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,83 @@
+/*
+ * 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 net.tz.lift.boot
+
+import net.liftweb.common._
+import net.liftweb.http._
+import net.liftweb.mapper._
+import net.liftweb.sitemap._
+import net.liftweb.sitemap.Loc._
+import net.liftweb.util._
+import net.liftweb.widgets.menu.MenuWidget
+import net.tz.lift.snippet.ActionLinks
+import net.tz.lift.util.YmdDateTimeConverter
+
+/**
+ * Prototype boot class to be either extended or copied.
+ */
+class ProtoBoot extends Logger {
+
+  def boot = {
+    /* DB stuff */
+    /*
+    S.addAround(DB.buildLoanWrapper())
+    */
+
+    if (Props.mode == Props.RunModes.Development)
+      DB.addLogFunc { (dbLog, l) => dbLog.statementEntries.foreach { e =>
+        debug("Query: " + e.statement)
+    }}
+
+    /* Date format */
+    LiftRules.dateTimeConverter.default.set { () => YmdDateTimeConverter }
+
+    /* Handle end slash and drop it (except for home page) */
+    LiftRules.statelessRewrite.append {
+      case RewriteRequest(ParsePath(xs,_,_,true),_,_) if (xs.size > 1) &&
+        (xs.lastOption == Some("index")) =>
+          RewriteResponse(xs dropRight 1)
+    }
+
+    /* Snippet dispatch */
+    LiftRules.snippetDispatch.append {
+      case "Menubar" => new AnyRef with DispatchSnippet {
+        def dispatch: DispatchIt = {
+          case _ => { xhtml => MenuWidget() }
+        }
+      }
+      case "action-links" => ActionLinks
+    }
+
+    /* Sitemap */
+    SiteMap.enforceUniqueLinks = false
+
+    /*
+    LiftRules.setSiteMap(SiteMap(
+      Menu.i("Home") / "index" >> Hidden
+    ))
+    */
+
+    /* Menu widget */
+    MenuWidget.init()
+
+    /* Http conf */
+    LiftRules.logServiceRequestTiming = false
+    LiftRules.early.append(_.setCharacterEncoding("UTF-8"))
+  }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/snippet/Panel.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,57 @@
+/*
+ * 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 net.tz.lift.snippet
+
+import net.liftweb.mapper.MappedField
+import scala.xml.{NodeSeq, Text}
+
+object AttrRow {
+  def apply(name: => NodeSeq, value: => NodeSeq) =  new AttrRow(name, value,
+    "attr-name", "attr-value")
+  def apply(f: MappedField[_, _]) = new AttrRow(Text(f.displayName), f.asHtml,
+    "attr-name", "attr-value")
+  def formRow(name: => NodeSeq, input: => NodeSeq) = new AttrRow(name, input,
+    "form-name", "form-value")
+  def submitRow(button: => NodeSeq) = new SpanRow(button)
+}
+
+class AttrRow(name: => NodeSeq, value: => NodeSeq, nameCss: String,
+  valueCss: String) extends Function0[NodeSeq] {
+  def tdN = <td class={nameCss}>{name}</td>
+  def tdV = <td class={valueCss}>{value}</td>
+  def apply(): NodeSeq = <tr>{tdN :: tdV :: Nil}</tr>
+}
+
+class SpanRow(value: => NodeSeq) extends AttrRow(NodeSeq.Empty, value, "", "")
+{
+  override def apply(): NodeSeq = <tr><td colspan="2">{value}</td></tr>
+}
+
+object Panel {
+  def apply(attrs: Iterable[AttrRow]) = new Panel(attrs)
+  def fromFields(fields: Iterable[MappedField[_,_]]) =
+    new Panel(fields.map(AttrRow(_)))
+}
+
+class Panel(attrs: => Iterable[AttrRow]) extends Function1[NodeSeq, NodeSeq]
+{
+  def apply(in: NodeSeq): NodeSeq = <table>{attrs.map(_())}</table>
+  def &(other: Function1[NodeSeq, NodeSeq]) = new Function1[NodeSeq, NodeSeq] {
+    def apply(in: NodeSeq): NodeSeq = List(Panel.this, other) flatMap (_(in))
+  }
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/snippet/SnippetHelpers.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,74 @@
+/*
+ * 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 net.tz.lift.snippet
+
+import net.liftweb.http._
+import scala.xml.{NodeSeq, Text}
+
+trait SnippetHelpers {
+  def mkPath(prefix: String, l: String*) =
+    (prefix :: l.toList) mkString ("/", "/", "")
+}
+
+class A(href: => String) extends Function1[NodeSeq, NodeSeq] {
+  def apply(in: NodeSeq): NodeSeq = <a href={href}>{in}</a>
+}
+
+object A {
+  def apply(href: String)(in: NodeSeq): NodeSeq = (new A(href))(in)
+  def apply(href: String): A = new A(href)
+  def apply(href: String, cnt: String): NodeSeq = (new A(href))(Text(cnt))
+}
+
+object ActionLinks extends DispatchSnippet {
+  def dispatch: DispatchIt = {
+    case "are" => { xhtml => if (links.is.isEmpty) NodeSeq.Empty else xhtml }
+    case _ => render _
+  }
+
+  import scala.collection.mutable.{BufferLike, ListBuffer}
+
+  private object links extends RequestVar[ListBuffer[Either[Function0[NodeSeq], Function1[NodeSeq, NodeSeq]]]](new ListBuffer())
+
+  def render(in: NodeSeq): NodeSeq = links.is.flatMap { _.fold( _(), _(in) ) ++
+    Text("  ") }
+
+  def append(l: NodeSeq) = links.is.append(Left( { () => l } ))
+  def append(l: () => NodeSeq) = links.is.append(Left(l))
+  def append(l: NodeSeq => NodeSeq) = links.is.append(Right(l))
+}
+
+object SimpleForm {
+  def apply(l: Iterable[AttrRow], submitVal: String, onSubmit: () => Any):
+    (NodeSeq => NodeSeq) = {
+    val curS = S.currentSnippet
+    def doit() = {
+      onSubmit()
+      curS foreach { S.mapSnippet(_, loop) }
+    }
+
+    def loop: (NodeSeq => NodeSeq) = {
+      Panel(l ++ List(AttrRow.submitRow(SHtml.submit(submitVal, doit))))
+    }
+
+    loop
+  }
+
+  def apply(l: Iterable[AttrRow], submitVal: String): (NodeSeq => NodeSeq) =
+    apply(l, submitVal, () => ())
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/snippet/Table.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,78 @@
+/*
+ * 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 net.tz.lift.snippet
+
+import net.liftweb.common._
+import net.liftweb.util.Helpers._
+import scala.xml.{Elem, NodeSeq, Text}
+
+object Column {
+  def apply[T](name: String, f: T => NodeSeq): Column[T] =
+    new Column[T](Text(name), f, { _ => Empty})
+  def apply[T](name: String, f: T => NodeSeq, tdCss: String): Column[T] =
+    new Column[T](Text(name), f, { _ => Full(tdCss)})
+}
+
+class Column[T](name: => NodeSeq, f: T => NodeSeq, tdCss: T => Box[String])
+  extends Function1[T, NodeSeq] {
+
+  def th: Elem = <th>{name}</th>
+  def td(in: T): Elem = {
+    val r = <td>{apply(in)}</td>
+    tdCss(in) map { css => r % ("class" -> css) } openOr r
+  }
+  def apply(in: T): NodeSeq = f(in)
+}
+
+object Table {
+  def apply[T](cols: Iterable[Column[T]], rows: Iterable[T]) = new Table(Empty,
+    cols, rows, Empty)
+  def apply[T](css: String, cols: Iterable[Column[T]], rows: Iterable[T],
+    foot: NodeSeq) = new Table(Full(css), cols, rows, Full(foot))
+}
+
+class Table[T](css: Box[String], cols: Iterable[Column[T]],
+  rows: => Iterable[T], foot: Box[NodeSeq]) extends
+  Function1[NodeSeq, NodeSeq] {
+
+  def oddEven(i: Int) = (i % 2) match {
+    case 1 => "even"
+    case 0 => "odd"
+  }
+
+  def thead: Elem = <thead><tr>{ cols.map(c => c.th) }</tr></thead>
+  def tbody: Elem = <tbody>{
+    rows.zipWithIndex.map( r => tr(r._1, r._2))
+  }</tbody>
+  def tr(in: T, idx: Int): Elem = <tr class={oddEven(idx)}>{
+    cols.map(_.td(in))
+  }</tr>
+
+  def tfoot: Elem = <tfoot>{
+    foot map { f => <tr><td colspan={cols.size.toString}>{f}</td></tr>
+    } openOr NodeSeq.Empty
+  }</tfoot>
+
+  def apply(ns: NodeSeq): NodeSeq = {
+    val t = <table>{
+      List(thead, tfoot, tbody)
+    }</table>
+    css map { cl => t % ("class" -> cl) } openOr t
+  }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/util/DateExtension.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,44 @@
+/*
+ * Refactored from net.liftweb.util.TimeHelpers.scala, which is
+ *
+ * Copyright 2006-2011 WorldWide Conferencing, LLC and
+ *
+ * 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 net.tz.lift.util
+
+import java.util.{Calendar, Date}
+
+/**
+ * Fixed version of DateExtension (from
+ * net.liftweb.util.TimeSpan.DateExtension).
+ */
+class DateExtension(d: Date) {
+  /** @returns a Date object starting at 00:00 from date */
+  def noTime = {
+    val calendar = Calendar.getInstance
+    calendar.setTime(d)
+    calendar.set(Calendar.HOUR_OF_DAY, 0)
+    calendar.set(Calendar.MINUTE, 0)
+    calendar.set(Calendar.SECOND, 0)
+    calendar.set(Calendar.MILLISECOND, 0)
+    calendar.getTime
+  }
+}
+
+object DateExtension {
+  implicit def date2dateExtension(d: Date): DateExtension =
+    new DateExtension(d)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/util/Helpers.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,79 @@
+/*
+ * 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 net.tz.lift.util
+
+import java.text.SimpleDateFormat
+import java.util.Date
+import net.liftweb.http.LiftRules
+import net.liftweb.util.Helpers.tryo
+import org.joda.time.{DateMidnight, DateTime, Period, ReadableInstant}
+import org.joda.time.format.{DateTimeFormat, PeriodFormatterBuilder}
+import scala.xml.{NodeSeq, Text}
+
+object AsIsoDateTime {
+  val df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
+  def unapply(in: String): Option[Date] = tryo { df.parse(in) }
+  def apply(d: Date): String = df.format(d)
+}
+
+object AsDate {
+  import DateExtension._
+  def unapply(in: String): Option[Date] = LiftRules.dateTimeConverter().
+    parseDate(in).map(_.noTime)
+  def apply(d: Date) = LiftRules.dateTimeConverter().formatDate(d)
+}
+
+object AsDateTime {
+  def unapply(in: String): Option[Date] = LiftRules.dateTimeConverter().
+    parseDateTime(in)
+  def apply(d: Date) = LiftRules.dateTimeConverter().formatDateTime(d)
+}
+
+object AsDateMidnight {
+  lazy val fmt = DateTimeFormat.forPattern("yyyy-MM-dd")
+  def unapply(in: String): Option[DateMidnight] = apply(in)
+  def apply(in: String): Option[DateMidnight] = tryo {
+    fmt.parseDateTime(in).toDateMidnight
+  }
+  def apply(d: ReadableInstant) = fmt.print(d)
+
+  implicit def dt2d(d: DateTime): Date = d.toDate
+  implicit def dm2d(d: DateMidnight): Date = d.toDate
+  implicit def d2dm(d: Date): DateMidnight = new DateMidnight(d)
+  implicit def dm2dt(d: DateMidnight): DateTime = d.toDateTime
+}
+
+object AsTimePeriod {
+  lazy val fmt = (new PeriodFormatterBuilder).printZeroAlways.
+    minimumPrintedDigits(2).appendHours.
+    appendSeparator(":").appendMinutes.toFormatter
+  lazy val fmtDt = DateTimeFormat.forPattern("HH:mm")
+
+  /** Parses HH:mm time into period. */
+  def apply(in: String): Option[Period] = tryo { fmt.parsePeriod(in) }
+  def apply(p: Period) = fmt.print(p)
+  def apply(dt: DateTime) = fmtDt.print(dt)
+}
+
+object Bytes {
+  implicit def dbl2ns(d: Double): NodeSeq =
+    Text(String.format(fmt, d.asInstanceOf[AnyRef]))
+  def fmt = "%.2f"
+  def kb(v: Long): NodeSeq = v.toDouble / 1024
+  def mb(v: Long): NodeSeq = v.toDouble / (1024*1024)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/util/YmdDateTimeConverter.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,40 @@
+/*
+ * 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 net.tz.lift.util
+
+import java.text.SimpleDateFormat
+import java.util.Date
+import net.liftweb.common._
+import net.liftweb.util.DateTimeConverter
+import net.liftweb.util.TimeHelpers._
+
+object YmdDateTimeConverter extends DateTimeConverter {
+  val df = new SimpleDateFormat("yyyy-MM-dd")
+  val dtf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+
+  def formatDateTime(d: Date) = dtf.format(d)
+  def formatDate(d: Date) = df.format(d)
+  /**  Uses Helpers.hourFormat which includes seconds but not time zone */
+  def formatTime(d: Date) = hourFormat.format(d)
+
+  def parseDateTime(s: String) = tryo { dtf.parse(s) }
+  def parseDate(s: String) = tryo { df.parse(s) }
+  /** Tries Helpers.hourFormat and Helpers.timeFormat */
+  def parseTime(s: String) =
+    tryo{hourFormat.parse(s)} or tryo{timeFormatter.parse(s)}
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/model/Account.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,197 @@
+/*
+ * 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.model
+
+import net.liftweb.common._
+import net.liftweb.mapper._
+import radview.snippet._
+import scala.xml.{Node, NodeSeq, Text}
+
+trait ImsiAware {
+  self: BaseMapper =>
+  object imsi extends MappedPoliteStringColName(this.asInstanceOf[MapperType],
+    17, "imsi", "IMSI") {
+    def imsi = is takeRight 12
+    override def displayName = calcImsiDisplayName
+  }
+  def calcImsiDisplayName = "IMSI"
+}
+
+trait Imsi12Aware {
+  self: BaseMapper =>
+  object imsi12 extends MappedPoliteStringColName(this.asInstanceOf[MapperType],
+    14, "imsi_12", "IMSI") {
+  }
+}
+
+
+trait UsernameAware {
+  self: BaseMapper =>
+  object username extends MappedPoliteStringColName(
+    this.asInstanceOf[MapperType], 64, "username", "Username")
+}
+
+trait AppAware {
+  self: BaseMapper =>
+  object app extends MappedPoliteStringColName(this.asInstanceOf[MapperType],
+    5, "appl", "Service") {
+    override def asHtml = Text(tr)
+    def tr = is match {
+      case "BOSS" => "Mobile"
+      case x => x
+    }
+  }
+}
+
+trait ServiceStatus { self: MappedInt[_] =>
+  override def asHtml = Text(is match {
+    case 0 => "service disabled (0)"
+    case 1 => "service enabled (1)"
+    case x => x.toString
+  })
+}
+
+/*
+  Table: radcheck_SUB
+ */
+object RadCheckSub extends RadCheckSub with LongKeyedMetaMapper[RadCheckSub] {
+  override def dbTableName = "v_radcheck_SUB"
+  override def dbDefaultConnectionIdentifier = RadiusConnectionIdentifier
+
+  def getApps = findAllFields(List(app), Distinct[RadCheckSub],
+    OrderBy(app, Ascending))
+
+  def byImsi(imsiVal: String) = Box(findAll(By(imsi, imsiVal)))
+}
+
+class RadCheckSub extends LongKeyedMapper[RadCheckSub] with ImsiAware with
+  UsernameAware with AppAware with IdPK with Imsi12Aware {
+
+  def getSingleton = RadCheckSub
+
+  object userStatus extends MappedIntColName(this, "user_status",
+    "User Status") with ServiceStatus
+
+  object msisdn extends MappedPoliteStringColName(this, 22, "msisdn",
+    "Phone No.")
+
+  override def calcImsiDisplayName = ifAdsl("Phone No.",
+    super.calcImsiDisplayName)
+
+  def fieldsForDisplay = List(msisdn) ++ ifAdsl(Empty, Full(imsi)) ++
+    List(app, username, userStatus)
+
+  def fieldsForList = List(username) ++
+    ifAdsl(Empty, Full(msisdn)) :+ userStatus
+
+  def ifAdsl[T](yes: => T, no: => T) = app.is match {
+    case "ADSL" => yes
+    case _ => no
+  }
+
+  def isAdsl = app.is == "ADSL"
+
+  lazy val replySub = RadReplySub.byImsi(imsi)
+  lazy val replyNai = RadReplyNai.byImsi12(imsi12)
+  lazy val checkNai = replyNai flatMap { rn => RadCheckNai.byEsn(rn.esn) }
+}
+
+/*
+  Table: radreply_SUB
+ */
+object RadReplySub extends RadReplySub with LongKeyedMetaMapper[RadReplySub] {
+  override def dbTableName = "radreply_SUB"
+  override def dbDefaultConnectionIdentifier = RadiusConnectionIdentifier
+
+  def byImsiMap(imsis: Seq[String]) = Map.empty ++ 
+    (findAll(ByList(imsi, imsis)) map { i => (i.imsi.is, i) })
+
+  def fieldsForList = List(statIp)
+  def byImsi(imsiVal: String) = Box(findAll(By(imsi, imsiVal)))
+}
+
+class RadReplySub extends LongKeyedMapper[RadReplySub] with ImsiAware with
+  AppAware with IdPK {
+
+  def getSingleton = RadReplySub
+
+  object statIp extends MappedPoliteStringColName(this, 253, "value",
+    "Static IP")
+
+  def fieldsForDisplay = List(statIp)
+}
+
+trait EsnAware {
+  self: BaseMapper =>
+  object esn extends MappedPoliteStringColName(
+    this.asInstanceOf[MapperType], 64, "username", "ESN") {
+    def esn = is.takeWhile {_ != '@'}
+    override def asHtml: Node = Text(esn)
+  }
+}
+
+/*
+  Table: radcheck_NAI
+ */
+object RadCheckNai extends RadCheckNai with LongKeyedMetaMapper[RadCheckNai] {
+  override def dbTableName = "radcheck_NAI"
+  override def dbDefaultConnectionIdentifier = RadiusConnectionIdentifier
+
+  def byRrnMap(rrn: Seq[RadReplyNai]) = Map.empty ++
+    (findAll(ByList(esn, rrn map { _.esn.is })) map { i => (i.esn.is, i) })
+
+  def fieldsForList = List(termStatus)
+  def byEsn(fullEsn: String) = Box(findAll(By(esn, fullEsn)))
+}
+
+class RadCheckNai extends LongKeyedMapper[RadCheckNai] with EsnAware
+  with AppAware with IdPK {
+
+  def getSingleton = RadCheckNai
+
+  object termStatus extends MappedIntColName(this, "term_status",
+    "Term Status") with ServiceStatus
+  def fieldsForDisplay = List(termStatus)
+}
+
+/*
+  Table: radreply_NAI
+ */
+object RadReplyNai extends RadReplyNai with LongKeyedMetaMapper[RadReplyNai] {
+  override def dbTableName = "v_radreply_NAI"
+  override def dbDefaultConnectionIdentifier = RadiusConnectionIdentifier
+
+  def byImsi12map(imsis12: Seq[String]) = Map.empty ++ 
+    (findAll(ByList(imsi12, imsis12)) map { i => (i.imsi12.is, i) })
+
+  def fieldsForList = List(esn)
+  def byImsi12(in: String) = Box(findAll(By(imsi12, in)))
+}
+
+class RadReplyNai extends LongKeyedMapper[RadReplyNai] with EsnAware with
+  AppAware with IdPK with Imsi12Aware {
+
+  def getSingleton = RadReplyNai
+
+  object value extends MappedPoliteStringColName(this, 253, "value", "Value") {
+    def imsi = is takeRight 12
+    override def asHtml: Node = Text(imsi)
+  }
+
+  def fieldsForDisplay = List(esn)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/model/Cdr.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,188 @@
+/*
+ * 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.model
+
+import java.util.Date
+import net.liftweb.common._
+import net.liftweb.mapper._
+import radview.snippet.SessionSnippet
+
+/*
+  Table: v_radacct
+ */
+object Cdr extends Cdr with LongKeyedMetaMapper[Cdr] with Loggable {
+  override def dbTableName = "v_radacct"
+  override def dbDefaultConnectionIdentifier = RadAcctConnectionIdentifier
+
+  def bySession(s: CdrSession) = findAll(By(sid, s.sid),
+    OrderBy(radacctid, Ascending))
+
+  def fieldsForList = List(sid, ts, inBytes, outBytes, statusType, acctuniqueid,
+    username, groupname, realm, nasipaddress, nasportid, nasporttype,
+    acctsessiontime, acctauthentic, serviceType, framedProto, framedIp,
+    statusType, cell)
+
+  import java.sql.{Date => SqlDate}
+  implicit def d2sqlD(d: Date): SqlDate = new SqlDate(d.getTime)
+
+  private def diff(col: MappedField[_, _]) = String.format(
+    "MAX(%1$s) - MIN(%1$s) AS %1$s", col.dbColumnName)
+  private def min(col: MappedField[_, _]) = String.format(
+    "MIN(%1$s) AS %1$s", col.dbColumnName)
+  private def max(col: MappedField[_, _]) = String.format(
+    "MAX(%1$s) AS %1$s", col.dbColumnName)
+
+  def sessions(from: Date, to: Date, field: MappedField[_, Cdr],
+    value: String) = findAllByPreparedStatement { sc =>
+      val selectS = List(sid.dbColumnName, diff(inBytes), diff(outBytes))
+      val sql = String.format(
+        "SELECT %s FROM %s WHERE %s = ? AND %s BETWEEN ? AND ? GROUP BY %s",
+        selectS mkString ",", dbTableName, field.dbColumnName,
+        ts.dbColumnName, sid.dbColumnName)
+      logger.debug(sql + "; params: " + from + ", " + to + ", " +
+        field.dbColumnName + "=" + value)
+      val stm = sc.connection.prepareStatement(sql)
+      stm.setString(1, value)
+      stm.setDate(2, from)
+      stm.setDate(3, to)
+      stm
+    }
+
+  def session(sessionId: String): Box[CdrSession] =
+    Box(sessions(List(sessionId)))
+
+  def sessions(sessionIds: List[String]): List[CdrSession] = sessionIds match {
+    case Nil => Nil
+    case xs =>
+      val cdr1 = findAllByPreparedStatement { sc =>
+        val selectS = List(sid.dbColumnName, diff(inBytes), diff(outBytes),
+          min(radacctid), max(ts))
+        val sql = String.format(
+          "SELECT %s FROM %s WHERE %s IN (%s) GROUP BY %s",
+          selectS.mkString(","), dbTableName, sid.dbColumnName,
+          sessionIds.map(r=>"?").mkString(","), sid.dbColumnName)
+        logger.debug(sql)
+        val stm = sc.connection.prepareStatement(sql)
+        sessionIds.zipWithIndex.map { r => stm.setString(r._2 + 1, r._1) }
+        stm
+      }
+      val cdr2 = Cdr.findAll(ByList(radacctid, cdr1 map { _.radacctid.is }))
+      val tsMap = Map[String, Cdr]() ++ (cdr2 map { c => (c.sid.is, c) })
+      for {
+        c1 <- cdr1
+        c2 <- tsMap.get(c1.sid.is)
+      } yield {
+        CdrSession(c1.sid, c2.ts, c1.ts, c1.inBytes, c1.outBytes, c2.cell)
+      }
+  }
+}
+
+class Cdr extends LongKeyedMapper[Cdr] with CellAware {
+  def getSingleton = Cdr
+  def primaryKeyField = radacctid
+
+  object radacctid extends MappedLongIndex(this) {
+    override def dbColumnName = "radacctid"
+  }
+
+  object sid extends MappedPoliteStringColName(this, 64, "acctsessionid",
+    "Session Id") {
+    override def asHtml =
+      <a href={SessionSnippet.url(is)}>{super.asHtml}</a>
+  }
+
+  object acctuniqueid extends MappedPoliteStringColName(this, 32,
+    "acctuniqueid", "Unique Id")
+
+  object username extends MappedPoliteStringColName(this, 64, "username",
+    "User")
+
+  object groupname extends MappedPoliteStringColName(this, 64, "groupname",
+    "Group")
+
+  object realm extends MappedPoliteStringColName(this, 64, "realm", "Realm")
+
+  object nasipaddress extends MappedPoliteStringColName(this, 15,
+    "nasipaddress", "NAS IP")
+
+  object nasportid extends MappedPoliteStringColName(this, 15, "nasportid",
+    "NAS Port Id")
+
+  object nasporttype extends MappedPoliteStringColName(this, 32,
+    "nasporttype", "NAS Port Type")
+
+  object acctsessiontime extends MappedIntColName(this, "acctsessiontime",
+    "Session Time")
+
+  object acctauthentic extends MappedPoliteStringColName(this, 32,
+    "acctauthentic", "Authenticator")
+
+  object inBytes extends MappedLongColName(this, "acctinputoctets",
+    "Input bytes")
+
+  object outBytes extends MappedLongColName(this, "acctoutputoctets",
+    "Output bytes")
+
+  object calledNo extends MappedPoliteStringColName(this, 50,
+    "calledstationid", "Called No.")
+
+  object callingNo extends MappedLongColName(this, "callingstationid",
+    "Calling No.")
+
+  object termCause extends MappedPoliteStringColName(this, 32,
+    "acctterminatecause", "Termination Cause")
+
+  object serviceType extends MappedPoliteStringColName(this, 32,
+    "servicetype", "Service Type")
+
+  object framedProto extends MappedPoliteStringColName(this, 32,
+    "framedprotocol", "Framed Protocol")
+
+  object framedIp extends MappedPoliteStringColName(this, 15,
+    "framedipaddress", "Framed IP")
+
+  object startDelay extends MappedIntColName(this, "acctstartdelay",
+    "Start Delay")
+
+  object stopDelay extends MappedIntColName(this, "acctstopdelay",
+    "Stop Delay")
+
+  object corrId extends MappedPoliteStringColName(this, 64,
+    "3GPP2_Correlation_Id", "3GPP2 Correlation Id")
+
+  object statusType extends MappedPoliteStringColName(this, 15,
+    "Acct_Status_Type", "Status Type")
+
+  object ts extends MappedDateTime(this) {
+    override def dbColumnName = "Event_Timestamp"
+    override def displayName = "Timestamp"
+  }
+
+  object releaseInd extends MappedIntColName(this, "3GPP2_Release_Indicator",
+    "3GPP2 Release Indicator")
+
+  object activeTime extends MappedIntColName(this, "3GPP2_Active_Time",
+    "3GPP2 Active Time")
+
+}
+
+case class CdrSession(sid: String, start: Date, end: Date, inBytes: Long,
+  outBytes: Long, cellId: String) {
+
+  def cell: Box[Cell] = Cell.findByKey(cellId)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/model/Cell.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,88 @@
+/*
+ * 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.model
+
+import net.liftweb.common._
+import net.liftweb.mapper._
+import radview.snippet._
+
+/*
+  Table: v_cells
+ */
+object Cell extends Cell with StringKeyedMetaMapper[Cell] {
+  override def dbTableName = "v_cells"
+  override def dbDefaultConnectionIdentifier = RadAcctConnectionIdentifier
+
+  lazy val fieldsForSearch = List(service, serviceNumber, bscIntId, bssId,
+    bssIntDesc, btsSysId, btsSysIdHex, btsName, btsDesc, ci, ip)
+  lazy val fieldsForList = List(service, serviceNumber, bscIntId, bssId,
+    bssIntDesc, btsSysId, btsSysIdHex, btsName, btsDesc, ci, ip)
+}
+
+class Cell extends StringKeyedMapper[Cell] {
+
+  def getSingleton = Cell
+
+  def primaryKeyField = idpk
+
+  object idpk extends MappedStringIndex(this, 60) {
+    override def dbColumnName = "idpk"
+  }
+  object service extends MappedPoliteStringColName(this, 4, "SERVICE",
+    "Service")
+  object serviceNumber extends MappedIntColName(this, "SERVICE_NUMBER",
+    "Service No.")
+  object bscIntId extends MappedPoliteStringColName(this, 6, "BSC_INT_ID",
+    "BSC id")
+  object bssId extends MappedPoliteStringColName(this, 2, "BSS_ID", "BSS id")
+  object bssIntDesc extends MappedPoliteStringColName(this, 12, "BSC_INT_DESC",
+    "BSC Description")
+  object btsSysId extends MappedIntColName(this, "BTS_SYSTEM_ID",
+    "BTS system id")
+  object btsSysIdHex extends MappedPoliteStringColName(this, 12,
+    "BTS_SYSTEM_ID_HEX", "BTS system id(hex)")
+  object btsName extends MappedPoliteStringColName(this, 15, "BTS_NAME",
+    "BTS Name") {
+    override def asHtml =
+      <a href={CellSnippet.url(CellView(fieldOwner))}>{super.asHtml}</a>
+  }
+  object btsDesc extends MappedPoliteStringColName(this, 100, "BTS_DESC",
+    "BTS Description")
+  object ci extends MappedPoliteStringColName(this, 10, "CI", "CI")
+  object ip extends MappedPoliteStringColName(this, 15, "IP", "IP")
+
+  def fieldsForDisplay = List(service, serviceNumber, bscIntId, bssId,
+    bssIntDesc, btsSysId, btsSysIdHex, btsDesc, ci, ip)
+
+}
+
+trait CellAware {
+  self: BaseMapper =>
+
+  object cell extends MappedStringForeignKey[MapperType, Cell](
+    self.asInstanceOf[MapperType], Cell, 41) {
+
+    override def dbColumnName = "bts_idpk"
+    override def displayName = "Cell"
+    def foreignMeta = Cell
+    override def asHtml = obj.map { c =>
+      <a href={CellSnippet.url(CellView(c))}>{c.btsName}</a>
+    } openOr super.asHtml
+  }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/model/Common.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,62 @@
+/*
+ * 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.model
+
+import net.liftweb.common._
+import net.liftweb.mapper._
+
+case object RadAcctConnectionIdentifier extends ConnectionIdentifier {
+  val jndiName = "radacct"
+}
+
+case object RadiusConnectionIdentifier extends ConnectionIdentifier {
+  val jndiName = "radius"
+}
+
+case object SyslogConnectionIdentifier extends ConnectionIdentifier {
+  val jndiName = "syslog"
+}
+
+trait StringKeyedMapper[OwnerType <: StringKeyedMapper[OwnerType]] extends
+  KeyedMapper[String, OwnerType] { self: OwnerType =>
+
+  override type TheKeyType = String
+}
+
+trait StringKeyedMetaMapper[A <: StringKeyedMapper[A]] extends
+  KeyedMetaMapper[String, A] { self: A =>
+}
+
+abstract class MappedPoliteStringColName[T <: Mapper[T]](
+  own: T, maxLen: Int, colName: String, dispName: String) extends
+  MappedPoliteString[T](own, maxLen) {
+  override def dbColumnName = colName
+  override def displayName = dispName
+}
+
+abstract class MappedIntColName[T <: Mapper[T]](own: T, colName: String,
+  dispName: String) extends MappedInt(own) {
+  override def dbColumnName = colName
+  override def displayName = dispName
+}
+
+abstract class MappedLongColName[T <: Mapper[T]](own: T, colName: String,
+  dispName: String) extends MappedLong(own) {
+  override def dbColumnName = colName
+  override def displayName = dispName
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/model/Syslog.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,55 @@
+/*
+ * 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.model
+
+import net.liftweb.common._
+import net.liftweb.mapper._
+import radview.snippet._
+import scala.xml.{Node, NodeSeq, Text}
+
+trait LoginStatus { self: MappedInt[_] =>
+  override def asHtml = Text(is match {
+    case 0 => "login failure (0)"
+    case 1 => "login success (1)"
+    case x => x.toString
+  })
+}
+
+/*
+  Table: AAA_SYSLOG
+*/
+object Syslog extends Syslog with MetaMapper[Syslog] {
+  override def dbTableName = "AAA_SYSLOG"
+  override def dbDefaultConnectionIdentifier = SyslogConnectionIdentifier
+  def fieldsForList = List(ts, source, loginStatus, username)
+}
+
+class Syslog extends Mapper[Syslog] {
+  def getSingleton = Syslog
+  object ts extends MappedDateTime(this) {
+    override def dbColumnName = "TIMESTAMP"
+    override def displayName = "Timestamp"
+  }
+  object source extends MappedPoliteStringColName(this, 22, "FROMHOST",
+    "Source host")
+  object loginStatus extends MappedIntColName(this, "LOGIN_STATUS",
+    "Login status") with LoginStatus
+  object username extends MappedPoliteStringColName(this, 22, "USERNAME",
+    "Username")
+  object imsi extends MappedLongColName(this, "IMSI", "Imsi")
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/snippet/AccountSnippet.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,385 @@
+/*
+ * 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) ):_*))
+  } 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:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/snippet/CellSnippet.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,193 @@
+/*
+ * 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 java.util.Date
+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.{asInt, now}
+import net.tz.lift.snippet._
+import net.tz.lift.util._
+import org.joda.time.{DateMidnight, DateTime, Period}
+import radview.model.{Cdr, Cell}
+import scala.xml.{Elem, NodeSeq, Text}
+
+object AsCell {
+  def unapply(in: String): Option[Cell] = Cell.findByKey(in)
+  def unapply(in: CellLoc): Option[Cell] = in match {
+    case CellView(c) => Some(c)
+    case ActiveSessions(c) => Some(c)
+    case _ => None
+  }
+}
+
+abstract sealed class CellLoc
+case object NoSuchCell extends CellLoc
+case class CellView(c: Cell) extends CellLoc
+case object CellSearch extends CellLoc
+case class ActiveSessions(c: Cell) extends CellLoc 
+
+object CellSnippet extends Loc[CellLoc] with SnippetHelpers with Loggable {
+
+  val name = "cell"
+  val prefix = "cell"
+  val tpl = "cell"
+  val params = List(Hidden)
+  val defaultValue = Full(CellSearch)
+  val link = new Link[CellLoc](List(prefix), true) {
+    override def createPath(l: CellLoc): String = l match {
+      case CellView(c) => mkPath(prefix, c.idpk.is)
+      case ActiveSessions(c) => mkPath(prefix, c.idpk.is, "active-sessions")
+      case _ => prefix
+    }
+  }
+  def url(l: CellLoc) = link.createPath(l)
+
+  val text = LinkText[CellLoc](l => Text(l match {
+    case NoSuchCell => "No such cell"
+    case CellView(c) => "Cell " + c.btsName
+    case CellSearch => "Cell search"
+    case ActiveSessions(c) => "Active sessions on " + c.btsName
+  }))
+
+  import AsDateMidnight.d2dm
+
+  override def rewrite: LocRewrite = Full({
+    case RReq(PP("cell" :: AsCell(c) :: xs, _, _,_), _, _) => xs match {
+      case "active-sessions" :: Nil =>
+        ActionLinks append A(url(CellView(c)), "Back to cell")
+        (RResp(List(tpl, "view")), ActiveSessions(c))
+      case _ =>
+        ActionLinks append A(url(ActiveSessions(c)), "Active sessions")
+        (RResp(List(tpl, "view")), CellView(c))
+    }
+
+    case RReq(PP(List("cell"), _, _,_), _, _) =>
+      (RResp(List(tpl, "search")), CellSearch)
+  })
+
+  override def snippets: SnippetTest = {
+    case ("panel", Full(AsCell(c))) => CellPanel(c)
+
+    case ("form", Full(CellSearch)) => searchForm
+    case ("form", Full(ActiveSessions(_))) => activeSessionsForm
+    case ("list", Full(CellSearch)) => searchList
+    case ("list", Full(ActiveSessions(c))) => activeSessionsTable(c)
+
+    case ("panel", _) => ClearNodes
+    case ("form", _) => ClearNodes
+    case ("list", _) => ClearNodes
+  }
+
+  object mxCnt extends RequestVar[Int](100)
+  object searchField extends RequestVar[Box[Int]](Empty)
+  object searchValue extends RequestVar[String]("")
+
+  lazy val searchFields: List[MappedField[_, Cell]] = Cell.fieldsForSearch
+
+  def searchForm: (NodeSeq => NodeSeq) = {
+    import AttrRow.{formRow, submitRow}
+    SimpleForm(List(
+      formRow(SHtml.selectObj[Int](
+        searchFields.zipWithIndex.map(f => (f._2, f._1.displayName)),
+        searchField.is, { i => searchField(Full(i)) } ),
+        SHtml.textElem(searchValue)
+      ),
+      formRow(Text("Max. results"), SHtml.selectObj[Int](
+        List(100, 200, 500) map { i => (i, i.toString) }, Full(mxCnt.is),
+        mxCnt(_)))
+    ), "Search")
+  }
+
+  object from extends RequestVar[DateTime]((new DateTime).minusHours(1))
+  object to extends RequestVar[DateTime](new DateTime)
+  object runQuery extends RequestVar[Box[Any]](Empty)
+
+  def activeSessionsForm: (NodeSeq => NodeSeq) = {
+    object dateVal extends RequestVar[String](AsDateMidnight(from))
+    object fromT extends RequestVar[String](AsTimePeriod(from))
+    object toT extends RequestVar[String](AsTimePeriod(to))
+
+    def setD() = {
+      for {
+        d <- AsDateMidnight(dateVal)
+        f <- AsTimePeriod(fromT)
+        t <- AsTimePeriod(toT)
+      } yield {
+        from(d.toDateTime.plus(f))
+        to(d.toDateTime.plus(t))
+      }
+      dateVal(AsDateMidnight(from))
+      fromT(AsTimePeriod(from))
+      toT(AsTimePeriod(to))
+      runQuery(Full(1))
+    }
+
+    import AttrRow._
+    SimpleForm(List(
+      formRow(Text("Date"), SHtml.textElem(dateVal, ("class", "date"))),
+      formRow(Text("Time from"), SHtml.textElem(fromT)),
+      formRow(Text("Time to"), SHtml.textElem(toT))), "Submit", setD)
+  }
+
+  def qp(f: MappedField[_, Cell], v: String): Box[QueryParam[Cell]] = {
+    val strCls = classOf[String]
+    val intCls = classOf[Int]
+    val clz = f.dbFieldClass
+    f match {
+      case fx: MappedField[String, Cell] if clz == classOf[String] =>
+        Full(Like(fx, "%" + v + "%"))
+      case fx: MappedField[Int, Cell] if clz == classOf[Int] =>
+        asInt(v) map { By(fx, _) }
+      case _ => Empty
+    }
+  }
+
+  def searchList: (NodeSeq => NodeSeq) = searchField.is map { idx =>
+    val rows = searchValue.is match {
+      case "" | null => Nil
+      case v =>
+        qp(searchFields(idx), v) map { p =>
+          Cell.findAll(p, OrderBy(Cell.btsName, Ascending), MaxRows(mxCnt.is))
+        } openOr Nil
+    }
+    Table[Cell](Cell.fieldsForList.map(f => Column[Cell](f.displayName,
+      {i: Cell => f.actualField(i).asHtml})), rows)
+  } openOr ClearNodes
+
+
+  def activeSessionsTable(c: Cell): (NodeSeq => NodeSeq) = runQuery map { v =>
+    import AsDateMidnight.dt2d
+    val sess1 = Cdr.findAllFields(List(Cdr.sid),
+      By_<(Cdr.ts, to.plusMinutes(1)),
+      By_>(Cdr.ts, from.minusMinutes(1)), By(Cdr.cell, c.idpk), Distinct[Cdr])
+    val sessions = Cdr.sessions(sess1 map { _.sid.is })
+    CdrSessionTable(sessions)
+  } openOr ClearNodes
+
+}
+
+object CellPanel {
+  def apply(c: Cell): (NodeSeq => NodeSeq) =
+    Panel.fromFields(c.fieldsForDisplay)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/radview/snippet/SessionSnippet.scala	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,134 @@
+/*
+ * 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.{Cell => _}
+import net.tz.lift.snippet._
+import net.tz.lift.util._
+import radview.model.{Cdr, CdrSession, Cell}
+import scala.xml.{Elem, NodeSeq, Text}
+
+object AsCdrSession {
+  def unapply(in: String): Option[CdrSession] = Cdr.session(in)
+}
+
+object SessionSnippet extends Loc[CdrSession] with SnippetHelpers {
+
+  object showCdr extends RequestVar[Boolean](false)
+
+  val name = "session"
+  val prefix = "session"
+  val tpl = "session"
+  val params = List(Hidden)
+  val defaultValue = Empty
+  val link = new Link[CdrSession](List(prefix), true) {
+    override def createPath(s: CdrSession): String =
+      mkPath(prefix, s.sid)
+  }
+  val text = LinkText[CdrSession](s => Text("Session " + s.sid))
+
+  override def rewrite: LocRewrite = Full({
+    case RReq(PP("session" :: AsCdrSession(s) :: xs, _,_,_), _,_) =>
+      showCdr(xs contains "cdr")
+      ActionLinks.append(showCdr.is match {
+        case true =>  A(url(s, false), "Hide CDR")
+        case false => A(url(s, true), "Show CDR")
+      })
+      (RResp(List("session")), s)
+  })
+
+  def url(sid: String): String = "/" + prefix + "/" + sid
+  def url(s: CdrSession): String = url(s, false)
+  def url(s: CdrSession, _showCdr: Boolean): String = link.createPath(s) +
+    (if (_showCdr) "/cdr" else "")
+
+  override def snippets: SnippetTest = {
+    case ("panel", Full(s)) => SessionPanel(s)
+    case ("cdr-list", Full(s)) if showCdr => CdrTable(s)
+
+    case ("panel", _) => ClearNodes
+    case ("cdr-list", _) => ClearNodes
+  }
+
+}
+
+object SessionPanel {
+  def apply(s: CdrSession): (NodeSeq => NodeSeq) = {
+    val l1 = List(
+      (Cdr.sid.displayName, s.sid),
+      ("Session start", AsDateTime(s.start)),
+      ("Session end", AsDateTime(s.end)),
+      (Cdr.inBytes.displayName + " [MB]", Bytes.mb(s.inBytes)),
+      (Cdr.outBytes.displayName + " [MB]", Bytes.mb(s.outBytes))
+    ) map { r: (String, Any) =>
+      AttrRow(Text(r._1), Text(r._2.toString))
+    }
+    Panel(l1 :+ AttrRow(Text("Cell"), s.cell.map { c =>
+      A(CellSnippet.url(CellView(c)), c.btsName.is) } openOr NodeSeq.Empty))
+  }
+}
+
+object CdrSessionTable {
+  def apply(l: Iterable[CdrSession]): (NodeSeq => NodeSeq) = {
+    val cells: Map[String, Cell] = Map() ++
+      (Cell.findAll(ByList(Cell.idpk, l.map { _.cellId }.toList.distinct)).
+      map { c => (c.idpk.is, c) })
+    Table[CdrSession](List(
+      Column(Cdr.sid.displayName, { s: CdrSession =>
+        A(SessionSnippet.url(s), s.sid) } ),
+      Column("Session start", { s: CdrSession => Text(AsDateTime(s.start)) } ),
+      Column("Session end", { s: CdrSession => Text(AsDateTime(s.end)) } ),
+      Column(Cdr.inBytes.displayName + " [MB]",
+        { s: CdrSession => Bytes.mb(s.inBytes) }, "td-right"),
+      Column(Cdr.outBytes.displayName + " [MB]",
+        { s: CdrSession => Bytes.mb(s.outBytes) }, "td-right"),
+      Column("Cell", { s: CdrSession => cells.get(s.cellId).map { c =>
+        A(CellSnippet.url(CellView(c)), c.btsName.is) } getOrElse NodeSeq.Empty })
+      ), l)
+  }
+}
+
+object CdrTable {
+  import Cdr._
+  def apply(cdr: Iterable[Cdr]): (NodeSeq => NodeSeq) = {
+
+    def cssF(f: MappedField[_, Cdr])(cdr: Cdr): Box[String] = {
+      f.actualField(cdr).is match {
+        case l: Long => Full("td-right")
+        case i: Int => Full("td-right")
+        case _ => Empty
+      }
+    }
+
+    def valF(f: MappedField[_, Cdr])(cdr: Cdr): NodeSeq =
+      f.actualField(cdr).asHtml
+
+    Table[Cdr](fieldsForList.map { f =>
+      new Column[Cdr](Text(f.displayName), valF(f) _, cssF(f) _) }, cdr)
+  }
+
+  def apply(s: CdrSession): (NodeSeq => NodeSeq) = apply(bySession(s))
+}
+
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/WEB-INF/web.xml	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+-->
+
+<!DOCTYPE web-app
+PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+"http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<web-app>
+<filter>
+  <filter-name>LiftFilter</filter-name>
+  <display-name>RadView Lift Filter</display-name>
+  <description>RadView Lift Filter</description>
+  <filter-class>net.liftweb.http.LiftFilter</filter-class>
+</filter>
+
+<filter-mapping>
+  <filter-name>LiftFilter</filter-name>
+  <url-pattern>/*</url-pattern>
+</filter-mapping>
+
+</web-app>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/account/search.html	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>Account Search</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <div class="span-24 last">
+        <h2><span class="lift:Menu.title"/></h2>
+      </div>
+      <div class="span-24 last">
+        <form class="lift:form?form=post"/>
+      </div>
+      <div class="span-24 last">
+        <span class="lift:list"/>
+      </div>
+    </div>
+  </body>
+</html>
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/account/view.html	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>Account View</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <head>
+        <script type="text/javascript" src="/js/jquery-ui-1.8.9.custom.min.js"></script>
+        <script type="text/javascript">
+          $(document).ready(function() {
+            $.datepicker.setDefaults({
+              dateFormat: "yy-mm-dd"
+            });
+            $(".date").datepicker();
+          });
+        </script>
+        <link type="text/css" href="/css/jquery-ui-1.8.9.custom.css" rel="stylesheet" />
+      </head>
+      <div class="span-24 last">
+        <h2><span class="lift:Menu.title"/></h2>
+      </div>
+      <div class="span-11">
+        <fieldset>
+          <legend>Subscriber data</legend>
+          <span class="lift:sub"/>
+        </fieldset>
+      </div>
+      <div class="span-1">&nbsp;</div>
+      <div class="span-12 last">
+        <span class="lift:if-nai">
+          <fieldset>
+            <legend>Network Access Identifier info</legend>
+            <span class="lift:nai"/>
+          </fieldset>
+        </span>
+      </div>
+      <div class="span-24 last">
+        <form class="lift:form?form=post"/>
+      </div>
+      <div class="span-24 last lift:syslog-list">
+        <span class="num-rows-panel"/><br/>
+        <span class="list"/><br/>
+      </div>
+      <div class="span-24 last lift:cdr-list">
+        <span class="num-rows-panel"/><br/>
+        <span class="list"/><br/>
+      </div>
+    </div>
+  </body>
+</html>
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/cell/search.html	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>Cell Search</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <div class="span-24 last">
+        <h2><span class="lift:Menu.title"/></h2>
+      </div>
+      <div class="span-24 last">
+        <form class="lift:form?form=post"/>
+      </div>
+      <div class="span-24 last">
+        <span class="lift:list"/>
+      </div>
+    </div>
+  </body>
+</html>
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/cell/view.html	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>Cell view</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <head>
+        <script type="text/javascript" src="/js/jquery-ui-1.8.9.custom.min.js"></script>
+        <script type="text/javascript">
+          $(document).ready(function() {
+            $.datepicker.setDefaults({
+              dateFormat: "yy-mm-dd"
+            });
+            $(".date").datepicker();
+          });
+        </script>
+        <link type="text/css" href="/css/jquery-ui-1.8.9.custom.css" rel="stylesheet" />
+      </head>
+ 
+      <div class="span-24 last">
+        <h2><span class="lift:Menu.title"/></h2>
+      </div>
+      <div class="span-24 last">
+        <span class="lift:panel"/>
+      </div>
+      <div class="span-24 last">
+        <form class="lift:form?form=post"/>
+      </div>
+      <div class="span-24 last">
+        <span class="lift:list"/>
+      </div>
+    </div>
+  </body>
+</html>
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/css/jquery-ui-1.8.9.custom.css	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,362 @@
+/*
+ * jQuery UI CSS Framework 1.8.9
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework 1.8.9
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
+.ui-widget-content a { color: #333333; }
+.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-right {  -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*
+ * jQuery UI Datepicker 1.8.9
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/css/site.css	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+ */
+
+.list {
+  border-collapse: collapse;
+}
+
+.list td, .list thead th {
+  border: 1px solid black;
+}
+
+.attr-name {
+  font-weight: bold;
+  width: 150px;
+}
+
+.attr-name-wide {
+  font-weight: bold;
+  width: 230px;
+}
+
+
+.attr-value {
+}
+
+.form-name {
+  font-weight: bold;
+  width: 150px;
+}
+
+.form-value {
+}
+
+td.td-right {
+  text-align: right;
+}
+
+/* fix for active sf-menu item in span tag */
+.sf-menu span {
+  padding: 0.75em 1em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/index.html	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>Home</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <h2>Welcome to Radius Viewer</h2>
+    </div>
+  </body>
+</html>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/js/jquery-ui-1.8.9.custom.min.js	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,99 @@
+/*!
+ * jQuery UI 1.8.9
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI
+ */
+(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.9",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,
+NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,
+"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");
+if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f,
+"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,
+d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}});
+c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&
+b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
+;/*
+ * jQuery UI Datepicker 1.8.9
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ */
+(function(d,G){function K(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass=
+"ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su",
+"Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",
+minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};d.extend(this._defaults,this.regional[""]);this.dpDiv=d('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')}function E(a,b){d.extend(a,b);for(var c in b)if(b[c]==
+null||b[c]==G)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.9"}});var y=(new Date).getTime();d.extend(K.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){E(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=f}}}e=a.nodeName.toLowerCase();
+f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:d('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')}},
+_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&&
+b.append.remove();if(c){b.append=d('<span class="'+this._appendClass+'">'+c+"</span>");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("<img/>").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('<button type="button"></button>').addClass(this._triggerClass).html(f==
+""?c:d("<img/>").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;g<f.length;g++)if(f[g].length>h){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a,
+c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b),
+true);this._updateDatepicker(b);this._updateAlternate(b);b.dpDiv.show()}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('<input type="text" id="'+("dp"+this.uuid)+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}E(a.settings,e||{});
+b=b&&b.constructor==Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);
+this._showDatepicker(this._dialogInput[0]);d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",
+this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,
+function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:
+f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return true;return false},_getInst:function(a){try{return d.data(a,"datepicker")}catch(b){throw"Missing instance data for this datepicker";}},_optionDatepicker:function(a,b,c){var e=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?d.extend({},d.datepicker._defaults):e?b=="all"?d.extend({},
+e.settings):this._get(e,b):null;var f=b||{};if(typeof b=="string"){f={};f[b]=c}if(e){this._curInst==e&&this._hideDatepicker();var h=this._getDateDatepicker(a,true);E(e.settings,f);this._attachments(d(a),e);this._autoSize(e);this._setDateDatepicker(a,h);this._updateDatepicker(e)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){(a=this._getInst(a))&&this._updateDatepicker(a)},_setDateDatepicker:function(a,b){if(a=this._getInst(a)){this._setDate(a,b);
+this._updateDatepicker(a);this._updateAlternate(a)}},_getDateDatepicker:function(a,b){(a=this._getInst(a))&&!a.inline&&this._setDateFromField(a,b);return a?this._getDate(a):null},_doKeyDown:function(a){var b=d.datepicker._getInst(a.target),c=true,e=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=true;if(d.datepicker._datepickerShowing)switch(a.keyCode){case 9:d.datepicker._hideDatepicker();c=false;break;case 13:c=d("td."+d.datepicker._dayOverClass+":not(."+d.datepicker._currentClass+")",b.dpDiv);c[0]?
+d.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,c[0]):d.datepicker._hideDatepicker();return false;case 27:d.datepicker._hideDatepicker();break;case 33:d.datepicker._adjustDate(a.target,a.ctrlKey?-d.datepicker._get(b,"stepBigMonths"):-d.datepicker._get(b,"stepMonths"),"M");break;case 34:d.datepicker._adjustDate(a.target,a.ctrlKey?+d.datepicker._get(b,"stepBigMonths"):+d.datepicker._get(b,"stepMonths"),"M");break;case 35:if(a.ctrlKey||a.metaKey)d.datepicker._clearDate(a.target);c=a.ctrlKey||
+a.metaKey;break;case 36:if(a.ctrlKey||a.metaKey)d.datepicker._gotoToday(a.target);c=a.ctrlKey||a.metaKey;break;case 37:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,e?+1:-1,"D");c=a.ctrlKey||a.metaKey;if(a.originalEvent.altKey)d.datepicker._adjustDate(a.target,a.ctrlKey?-d.datepicker._get(b,"stepBigMonths"):-d.datepicker._get(b,"stepMonths"),"M");break;case 38:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,-7,"D");c=a.ctrlKey||a.metaKey;break;case 39:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,
+e?-1:+1,"D");c=a.ctrlKey||a.metaKey;if(a.originalEvent.altKey)d.datepicker._adjustDate(a.target,a.ctrlKey?+d.datepicker._get(b,"stepBigMonths"):+d.datepicker._get(b,"stepMonths"),"M");break;case 40:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,+7,"D");c=a.ctrlKey||a.metaKey;break;default:c=false}else if(a.keyCode==36&&a.ctrlKey)d.datepicker._showDatepicker(this);else c=false;if(c){a.preventDefault();a.stopPropagation()}},_doKeyPress:function(a){var b=d.datepicker._getInst(a.target);if(d.datepicker._get(b,
+"constrainInput")){b=d.datepicker._possibleChars(d.datepicker._get(b,"dateFormat"));var c=String.fromCharCode(a.charCode==G?a.keyCode:a.charCode);return a.ctrlKey||a.metaKey||c<" "||!b||b.indexOf(c)>-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},
+_showDatepicker:function(a){a=a.target||a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);d.datepicker._curInst&&d.datepicker._curInst!=b&&d.datepicker._curInst.dpDiv.stop(true,true);var c=d.datepicker._get(b,"beforeShow");E(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos=
+d.datepicker._findPos(a);d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.empty();b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b,
+c,e);b.dpDiv.css({position:d.datepicker._inDialog&&d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){d.datepicker._datepickerShowing=true;var i=b.dpDiv.find("iframe.ui-datepicker-cover");if(i.length){var g=d.datepicker._getBorders(b.dpDiv);i.css({left:-g[0],top:-g[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex(d(a).zIndex()+1);d.effects&&
+d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this,c=d.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a));var e=a.dpDiv.find("iframe.ui-datepicker-cover");e.length&&e.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()});a.dpDiv.find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",
+function(){d(this).removeClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!b._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){d(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");d(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=
+-1&&d(this).addClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).addClass("ui-datepicker-next-hover")}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();c=this._getNumberOfMonths(a);e=c[1];e>1?a.dpDiv.addClass("ui-datepicker-multi-"+e).css("width",17*e+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(c[0]!=1||c[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,
+"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input.focus();if(a.yearshtml){var f=a.yearshtml;setTimeout(function(){f===a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml);f=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},
+_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),j=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-
+g):0);b.top-=Math.min(b.top,b.top+f>j&&j>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1);)a=a[b?"previousSibling":"nextSibling"];a=d(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]?
+b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();if(a=this._get(b,"onClose"))a.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},
+_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):
+0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear=
+false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var b=this._getInst(d(a)[0]);b.input&&b._selectingMonthYear&&setTimeout(function(){b.input.focus()},0);b._selectingMonthYear=!b._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=
+d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=d(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);
+else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=
+a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;e=typeof e!="string"?e:(new Date).getFullYear()%100+parseInt(e,10);for(var f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,
+g=(c?c.monthNames:null)||this._defaults.monthNames,j=c=-1,l=-1,u=-1,k=false,o=function(p){(p=z+1<a.length&&a.charAt(z+1)==p)&&z++;return p},m=function(p){var v=o(p);p=new RegExp("^\\d{1,"+(p=="@"?14:p=="!"?20:p=="y"&&v?4:p=="o"?3:2)+"}");p=b.substring(s).match(p);if(!p)throw"Missing number at position "+s;s+=p[0].length;return parseInt(p[0],10)},n=function(p,v,H){p=o(p)?H:v;for(v=0;v<p.length;v++)if(b.substr(s,p[v].length).toLowerCase()==p[v].toLowerCase()){s+=p[v].length;return v+1}throw"Unknown name at position "+
+s;},r=function(){if(b.charAt(s)!=a.charAt(z))throw"Unexpected literal at position "+s;s++},s=0,z=0;z<a.length;z++)if(k)if(a.charAt(z)=="'"&&!o("'"))k=false;else r();else switch(a.charAt(z)){case "d":l=m("d");break;case "D":n("D",f,h);break;case "o":u=m("o");break;case "m":j=m("m");break;case "M":j=n("M",i,g);break;case "y":c=m("y");break;case "@":var w=new Date(m("@"));c=w.getFullYear();j=w.getMonth()+1;l=w.getDate();break;case "!":w=new Date((m("!")-this._ticksTo1970)/1E4);c=w.getFullYear();j=w.getMonth()+
+1;l=w.getDate();break;case "'":if(o("'"))r();else k=true;break;default:r()}if(c==-1)c=(new Date).getFullYear();else if(c<100)c+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c<=e?0:-100);if(u>-1){j=1;l=u;do{e=this._getDaysInMonth(c,j-1);if(l<=e)break;j++;l-=e}while(1)}w=this._daylightSavingAdjust(new Date(c,j-1,l));if(w.getFullYear()!=c||w.getMonth()+1!=j||w.getDate()!=l)throw"Invalid date";return w},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",
+RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=k+1<a.length&&
+a.charAt(k+1)==o)&&k++;return o},g=function(o,m,n){m=""+m;if(i(o))for(;m.length<n;)m="0"+m;return m},j=function(o,m,n,r){return i(o)?r[m]:n[m]},l="",u=false;if(b)for(var k=0;k<a.length;k++)if(u)if(a.charAt(k)=="'"&&!i("'"))u=false;else l+=a.charAt(k);else switch(a.charAt(k)){case "d":l+=g("d",b.getDate(),2);break;case "D":l+=j("D",b.getDay(),e,f);break;case "o":l+=g("o",(b.getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864E5,3);break;case "m":l+=g("m",b.getMonth()+1,2);break;case "M":l+=j("M",
+b.getMonth(),h,c);break;case "y":l+=i("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case "@":l+=b.getTime();break;case "!":l+=b.getTime()*1E4+this._ticksTo1970;break;case "'":if(i("'"))l+="'";else u=true;break;default:l+=a.charAt(k)}return l},_possibleChars:function(a){for(var b="",c=false,e=function(h){(h=f+1<a.length&&a.charAt(f+1)==h)&&f++;return h},f=0;f<a.length;f++)if(c)if(a.charAt(f)=="'"&&!e("'"))c=false;else b+=a.charAt(f);else switch(a.charAt(f)){case "d":case "m":case "y":case "@":b+=
+"0123456789";break;case "D":case "M":return null;case "'":if(e("'"))b+="'";else c=true;break;default:b+=a.charAt(f)}return b},_get:function(a,b){return a.settings[b]!==G?a.settings[b]:this._defaults[b]},_setDateFromField:function(a,b){if(a.input.val()!=a.lastVal){var c=this._get(a,"dateFormat"),e=a.lastVal=a.input?a.input.val():null,f,h;f=h=this._getDefaultDate(a);var i=this._getFormatConfig(a);try{f=this.parseDate(c,e,i)||h}catch(g){this.log(g);e=b?"":e}a.selectedDay=f.getDate();a.drawMonth=a.selectedMonth=
+f.getMonth();a.drawYear=a.selectedYear=f.getFullYear();a.currentDay=e?f.getDate():0;a.currentMonth=e?f.getMonth():0;a.currentYear=e?f.getFullYear():0;this._adjustInstDate(a)}},_getDefaultDate:function(a){return this._restrictMinMax(a,this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var e=function(h){var i=new Date;i.setDate(i.getDate()+h);return i},f=function(h){try{return d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),h,d.datepicker._getFormatConfig(a))}catch(i){}var g=
+(h.toLowerCase().match(/^c/)?d.datepicker._getDate(a):null)||new Date,j=g.getFullYear(),l=g.getMonth();g=g.getDate();for(var u=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,k=u.exec(h);k;){switch(k[2]||"d"){case "d":case "D":g+=parseInt(k[1],10);break;case "w":case "W":g+=parseInt(k[1],10)*7;break;case "m":case "M":l+=parseInt(k[1],10);g=Math.min(g,d.datepicker._getDaysInMonth(j,l));break;case "y":case "Y":j+=parseInt(k[1],10);g=Math.min(g,d.datepicker._getDaysInMonth(j,l));break}k=u.exec(h)}return new Date(j,
+l,g)};if(b=(b=b==null||b===""?c:typeof b=="string"?f(b):typeof b=="number"?isNaN(b)?c:e(b):new Date(b.getTime()))&&b.toString()=="Invalid Date"?c:b){b.setHours(0);b.setMinutes(0);b.setSeconds(0);b.setMilliseconds(0)}return this._daylightSavingAdjust(b)},_daylightSavingAdjust:function(a){if(!a)return null;a.setHours(a.getHours()>12?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=
+a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),
+b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),j=this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),k=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n=
+this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=k&&n<k?k:n;this._daylightSavingAdjust(new Date(m,g,1))>n;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-j,1)),this._getFormatConfig(a));n=this._canAdjustMonth(a,-1,m,g)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+y+".datepicker._adjustDate('#"+a.id+"', -"+j+", 'M');\" title=\""+n+'"><span class="ui-icon ui-icon-circle-triangle-'+
+(c?"e":"w")+'">'+n+"</span></a>":f?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+n+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+n+"</span></a>";var r=this._get(a,"nextText");r=!h?r:this.formatDate(r,this._daylightSavingAdjust(new Date(m,g+j,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+y+".datepicker._adjustDate('#"+a.id+"', +"+j+", 'M');\" title=\""+r+'"><span class="ui-icon ui-icon-circle-triangle-'+
+(c?"w":"e")+'">'+r+"</span></a>":f?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+r+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+r+"</span></a>";j=this._get(a,"currentText");r=this._get(a,"gotoCurrent")&&a.currentDay?u:b;j=!h?j:this.formatDate(j,r,this._getFormatConfig(a));h=!a.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+y+'.datepicker._hideDatepicker();">'+this._get(a,
+"closeText")+"</button>":"";e=e?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?h:"")+(this._isInRange(a,r)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+y+".datepicker._gotoToday('#"+a.id+"');\">"+j+"</button>":"")+(c?"":h)+"</div>":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;j=this._get(a,"showWeek");r=this._get(a,"dayNames");this._get(a,"dayNamesShort");var s=this._get(a,"dayNamesMin"),z=
+this._get(a,"monthNames"),w=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),v=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var L=this._getDefaultDate(a),I="",C=0;C<i[0];C++){for(var M="",D=0;D<i[1];D++){var N=this._daylightSavingAdjust(new Date(m,g,a.selectedDay)),t=" ui-corner-all",x="";if(l){x+='<div class="ui-datepicker-group';if(i[1]>1)switch(D){case 0:x+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]-
+1:x+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:x+=" ui-datepicker-group-middle";t="";break}x+='">'}x+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+t+'">'+(/all|left/.test(t)&&C==0?c?f:n:"")+(/all|right/.test(t)&&C==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,k,o,C>0||D>0,z,w)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var A=j?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(t=0;t<7;t++){var q=
+(t+h)%7;A+="<th"+((t+h+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+r[q]+'">'+s[q]+"</span></th>"}x+=A+"</tr></thead><tbody>";A=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay,A);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;A=l?6:Math.ceil((t+A)/7);q=this._daylightSavingAdjust(new Date(m,g,1-t));for(var O=0;O<A;O++){x+="<tr>";var P=!j?"":'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(q)+"</td>";for(t=0;t<7;t++){var F=
+p?p.apply(a.input?a.input[0]:null,[q]):[true,""],B=q.getMonth()!=g,J=B&&!H||!F[0]||k&&q<k||o&&q>o;P+='<td class="'+((t+h+6)%7>=5?" ui-datepicker-week-end":"")+(B?" ui-datepicker-other-month":"")+(q.getTime()==N.getTime()&&g==a.selectedMonth&&a._keyEvent||L.getTime()==q.getTime()&&L.getTime()==N.getTime()?" "+this._dayOverClass:"")+(J?" "+this._unselectableClass+" ui-state-disabled":"")+(B&&!v?"":" "+F[1]+(q.getTime()==u.getTime()?" "+this._currentClass:"")+(q.getTime()==b.getTime()?" ui-datepicker-today":
+""))+'"'+((!B||v)&&F[2]?' title="'+F[2]+'"':"")+(J?"":' onclick="DP_jQuery_'+y+".datepicker._selectDay('#"+a.id+"',"+q.getMonth()+","+q.getFullYear()+', this);return false;"')+">"+(B&&!v?"&#xa0;":J?'<span class="ui-state-default">'+q.getDate()+"</span>":'<a class="ui-state-default'+(q.getTime()==b.getTime()?" ui-state-highlight":"")+(q.getTime()==u.getTime()?" ui-state-active":"")+(B?" ui-priority-secondary":"")+'" href="#">'+q.getDate()+"</a>")+"</td>";q.setDate(q.getDate()+1);q=this._daylightSavingAdjust(q)}x+=
+P+"</tr>"}g++;if(g>11){g=0;m++}x+="</tbody></table>"+(l?"</div>"+(i[0]>0&&D==i[1]-1?'<div class="ui-datepicker-row-break"></div>':""):"");M+=x}I+=M}I+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':"");a._keyEvent=false;return I},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var j=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),k='<div class="ui-datepicker-title">',
+o="";if(h||!j)o+='<span class="ui-datepicker-month">'+i[b]+"</span>";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+y+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" onclick=\"DP_jQuery_"+y+".datepicker._clickMonthYear('#"+a.id+"');\">";for(var n=0;n<12;n++)if((!i||n>=e.getMonth())&&(!m||n<=f.getMonth()))o+='<option value="'+n+'"'+(n==b?' selected="selected"':"")+">"+g[n]+"</option>";o+="</select>"}u||(k+=o+(h||!(j&&
+l)?"&#xa0;":""));a.yearshtml="";if(h||!l)k+='<span class="ui-datepicker-year">'+c+"</span>";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b,i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(a.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+y+".datepicker._selectMonthYear('#"+
+a.id+"', this, 'Y');\" onclick=\"DP_jQuery_"+y+".datepicker._clickMonthYear('#"+a.id+"');\">";b<=g;b++)a.yearshtml+='<option value="'+b+'"'+(b==c?' selected="selected"':"")+">"+b+"</option>";a.yearshtml+="</select>";if(d.browser.mozilla)k+='<select class="ui-datepicker-year"><option value="'+c+'" selected="selected">'+c+"</option></select>";else{k+=a.yearshtml;a.yearshtml=null}}k+=this._get(a,"yearSuffix");if(u)k+=(h||!(j&&l)?"&#xa0;":"")+o;k+="</div>";return k},_adjustInstDate:function(a,b,c){var e=
+a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&b<c?c:b;return b=a&&b>a?a:b},_notifyChange:function(a){var b=this._get(a,
+"onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a);
+c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,
+"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker=
+function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));
+return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new K;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.9";window["DP_jQuery_"+y]=d})(jQuery);
+;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/session.html	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>Session</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <div class="span-24 last">
+        <h2><span class="lift:Menu.title"/></h2>
+      </div>
+      <div class="span-10">
+        <span class="lift:panel"/>
+      </div>
+      <div class="span-14 last">
+      </div>
+      <div class="span-24 last">
+        <span class="lift:cdr-list"/>
+      </div>
+    </div>
+  </body>
+</html>
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/templates-hidden/default.html	Sun Apr 03 15:55:02 2011 +0200
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:lift="http://liftweb.net/">
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+    <meta name="description" content="Readius Viewer" />
+    <meta name="keywords" content="radius, radacct, syslog" />
+    <title class="lift:Menu.title"></title>
+    <style class="lift:CSS.blueprint"></style>
+    <style class="lift:CSS.fancyType"></style>
+    <script id="jquery" src="/classpath/jquery.js" type="text/javascript"></script>
+    <link type="text/css" href="/css/site.css" rel="stylesheet" />
+  </head>
+  <body>
+    <div class="container">
+      <div class="column span-24 last" style="text-align: center">
+        <h1 class="alt">Radius Viewer</h1>
+      </div>
+      <hr />
+      <div class="column span-24 last">
+        <span class="lift:Menubar"></span>
+        <div class="lift:Msgs?showAll=true"></div>
+      </div>
+      <div class="span-10 last lift:action-links.are">
+        <span class="lift:action-links"/>
+      </div>
+      <div id="content"></div>
+      <hr />
+      <div class="column span-24 last" style="text-align: center">
+        <h4 class="alt"></h4>
+      </div>
+    </div>
+  </body>
+</html>
+