Squashed commit of the following clean up:

commit 9b05ee5947709da1ed8dbfd2b17bf61d8d7cc4e3
Author: Simon Redfern <simon@tesobe.com>
Date:   Sun Nov 15 23:37:25 2015 +0100

    Clean up 6

commit 88965dede5c2e4dba57f00a1bc339fb4d9e48d08
Author: Simon Redfern <simon@tesobe.com>
Date:   Sun Nov 15 23:31:07 2015 +0100

    Clean up 5

commit 7a2fba3f3ce9a41b417472df961b0fcf09fcd0b9
Author: Simon Redfern <simon@tesobe.com>
Date:   Sun Nov 15 23:25:19 2015 +0100

    Clean up 3

commit 07436d3330e04e07deaf826465ad6cb9f50e6396
Author: Simon Redfern <simon@tesobe.com>
Date:   Sun Nov 15 23:11:55 2015 +0100

    Clean up 2

commit ca2e56ef1bdb5a23cca4b2005469c5292f6a94d7
Author: Simon Redfern <simon@tesobe.com>
Date:   Sun Nov 15 23:05:45 2015 +0100

    Clean up 1
This commit is contained in:
Simon Redfern 2015-11-15 23:38:30 +01:00
parent 41898e0fb6
commit 22876cb7c3
20 changed files with 1 additions and 3089 deletions

View File

@ -144,184 +144,10 @@ class Boot extends Loggable{
result
}
//getTransactionsAndView can be called twice by lift, so it's best to memoize the result of the potentially expensive calculation
object transactionsMemo extends RequestVar[Box[Box[((TransactionsJson, AccountJson, TransactionsListURLParams))]]](Empty)
def getTransactions(URLParameters: List[String]): Box[(TransactionsJson, AccountJson, TransactionsListURLParams)] =
{
def calculateTransactions() = {
val bank = URLParameters(0)
val account = URLParameters(1)
val viewName = URLParameters(2)
val transactionsURLParams = TransactionsListURLParams(bankId = bank, accountId = account, viewId = viewName)
val result = logOrReturnResult {
for {
//TODO: Pagination: This is not totally trivial, since the transaction list groups by date and 2 pages may have some transactions on the same date
transactionsJson <- ObpAPI.transactions(bank, account, viewName, Some(10000), Some(0), None, None, None)
accountJson <- ObpAPI.getAccount(bank, account, viewName) //TODO: Execute this request and the one above in parallel
} yield {
(transactionsJson, accountJson, transactionsURLParams)
}
}
transactionsMemo.set(Full(result))
result
}
transactionsMemo.get match {
case Full(something) => something
case _ => calculateTransactions()
}
}
//getAccount can be called twice by lift, so it's best to memoize the result of the potentially expensive calculation
object accountMemo extends RequestVar[Box[Box[(code.lib.ObpJson.OtherAccountsJson, code.snippet.ManagementURLParams)]]](Empty)
def getAccount(URLParameters : List[String]) =
{
def calculateAccount() = {
val bankUrl = URLParameters(0)
val accountUrl = URLParameters(1)
val urlParams = ManagementURLParams(bankUrl, accountUrl)
val result = logOrReturnResult {
for {
otherAccountsJson <- ObpGet("/v1.2/banks/" + bankUrl + "/accounts/" + accountUrl + "/owner/" + "other_accounts").flatMap(x => x.extractOpt[OtherAccountsJson])
} yield (otherAccountsJson, urlParams)
}
accountMemo.set(Full(result))
result
}
accountMemo.get match {
case Full(something) => something
case _ => calculateAccount()
}
}
def getAccounts(URLParameters : List[String]): Box[List[BarebonesAccountJson]] =
{
val result =
for {
accountJson <- ObpGet("/v1.2.1/accounts/private").flatMap(_.extractOpt[BarebonesAccountsJson])
} yield (accountJson.accounts)
result match {
case Full(s) => s
case _ => None
}
}
//getTransaction can be called twice by lift, so it's best to memoize the result of the potentially expensive calculation
object transactionMemo extends RequestVar[Box[Box[(code.lib.ObpJson.TransactionJson, code.snippet.CommentsURLParams)]]](Empty)
def getTransaction(URLParameters: List[String]) =
{
def calculateTransaction() = {
if (URLParameters.length == 4) {
val bank = URLParameters(0)
val account = URLParameters(1)
val transactionID = URLParameters(2)
val viewName = URLParameters(3)
val transactionJson = ObpGet("/v1.2/banks/" + bank + "/accounts/" + account + "/" + viewName +
"/transactions/" + transactionID + "/transaction").flatMap(x => x.extractOpt[TransactionJson])
val commentsURLParams = CommentsURLParams(bankId = bank, accountId = account, transactionId = transactionID, viewId = viewName)
val result = logOrReturnResult {
for {
tJson <- transactionJson
} yield (tJson, commentsURLParams)
}
transactionMemo.set(Full(result))
result
} else
Empty
}
transactionMemo.get match {
case Full(something) => something
case _ => calculateTransaction()
}
}
def getAccountViewsAndPermission(URLParameters: List[String]): Box[(List[ViewJson], AccountJson, PermissionsUrlParams)] = {
if (URLParameters.length == 2) {
val bank = URLParameters(0)
val account = URLParameters(1)
logOrReturnResult {
for {
viewsJson <- ObpAPI.getViews(bank, account)
accountJson <- ObpAPI.getAccount(bank, account, "owner" /*TODO: This shouldn't be hardcoded*/) //TODO: Execute this request and the one above in parallel
} yield {
(viewsJson, accountJson, PermissionsUrlParams(bank, account))
}
}
} else Empty
}
def getAccountViews(URLParameters: List[String]): Box[(List[ViewJson])] = {
if (URLParameters.length == 2) {
val bank = URLParameters(0)
val account = URLParameters(1)
logOrReturnResult {
for {
viewsJson <- ObpAPI.getViews(bank, account)
} yield {
viewsJson
}
}
} else Empty
}
def getCompleteAccountViews(URLParameters: List[String]): Box[ViewsDataJSON] = {
if (URLParameters.length == 2) {
val bank = URLParameters(0)
val account = URLParameters(1)
logOrReturnResult {
for {
viewsJson <- ObpAPI.getCompleteViews(bank, account)
} yield {
ViewsDataJSON(viewsJson, bank,account)
}
}
} else Empty
}
def getPermissions(URLParameters: List[String]): Box[(PermissionsJson, AccountJson, List[ViewJson], PermissionsUrlParams)] = {
if (URLParameters.length == 2) {
val bank = URLParameters(0)
val account = URLParameters(1)
logOrReturnResult {
for {
permissionsJson <- ObpAPI.getPermissions(bank, account)
accountViewsJson <- ObpAPI.getViews(bank, account)
accountJson <- ObpAPI.getAccount(bank, account, "owner" /*TODO: This shouldn't be hardcoded*/) //TODO: Execute this request and the one above in parallel
} yield (permissionsJson, accountJson, accountViewsJson, PermissionsUrlParams(bank, account))
}
} else Empty
}
// Build SiteMap
// Note: See Nav.scala which modifies the menu
val sitemap = List(
Menu.i("OBP API Explorer") / "api-explorer",

View File

@ -1,170 +0,0 @@
/**
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
*/
package code.snippet
import net.liftweb.http.js.JE.JsRaw
import net.liftweb.http.js.JsCmd
import net.liftweb.util.Helpers._
import net.liftweb.common.{Box, Full, Loggable}
import net.liftweb.http.{S, SHtml}
import scala.xml.{NodeSeq, Text}
import net.liftweb.http.js.JsCmds.{Script, _Noop, Noop}
import code.lib.ObpGet
import code.lib.ObpJson._
import net.liftweb.json.JsonAST.{JInt, JArray}
import net.liftweb.json.JsonAST.JField
import net.liftweb.json.JsonAST.JObject
import net.liftweb.json.JsonAST.JString
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.JBool
import code.lib.ObpAPI
import code.lib.OAuthClient
class AccountsOverview extends Loggable {
//val banksJsonBox = ObpAPI.allBanks
//val bankJsons : List[BankJson] = banksJsonBox.map(_.bankJsons).toList.flatten.distinct
/*val bankIds = for {
bank <- bankJsons
id <- bank.id
} yield id */
//logger.info("Accounts Overview: Bank ids found: " + bankIds)
type BankID = String
val publicAccountJsons : List[(BankID, BarebonesAccountJson)] = for {
publicAccountsJson <- ObpAPI.publicAccounts.toList
barebonesAccountJson <- publicAccountsJson.accounts.toList.flatten
bankId <- barebonesAccountJson.bank_id
} yield (bankId, barebonesAccountJson)
logger.info("Accounts Overview: Public accounts found: " + publicAccountJsons)
val privateAccountJsons : List[(BankID, BarebonesAccountJson)] = for {
privateAccountsJson <- ObpAPI.privateAccounts.toList
barebonesAccountJson <- privateAccountsJson.accounts.toList.flatten
bankId <- barebonesAccountJson.bank_id
} yield (bankId, barebonesAccountJson)
logger.info("Accounts Overview: Private accounts found: " + privateAccountJsons)
def publicAccounts = {
if (publicAccountJsons.size == 0) {
".accountList" #> "No public accounts"
} else {
".accountList" #> publicAccountJsons.map {
case (bankId, accountJson) => {
//TODO: It might be nice to ensure that the same view is picked each time the page loads
val views = accountJson.views_available.toList.flatten
val aPublicViewId: String = (for {
aPublicView <- views.filter(view => view.is_public.getOrElse(false)).headOption
viewId <- aPublicView.id
} yield viewId).getOrElse("")
".accLink *" #> accountDisplayName(accountJson) &
".accLink [href]" #> {
val accountId = accountJson.id.getOrElse("")
"/banks/" + bankId + "/accounts/" + accountId + "/" + aPublicViewId
}
}
}
}
}
def accountDisplayName(accountJson : BarebonesAccountJson) : String = {
val label = accountJson.label.getOrElse("")
if(label != "") label else accountJson.id.getOrElse("unknown account name")
}
def authorisedAccounts = {
def loggedInSnippet = {
".accountList" #> privateAccountJsons.map {case (bankId, accountJson) => {
//TODO: It might be nice to ensure that the same view is picked each time the page loads
val views = accountJson.views_available.toList.flatten
val accountId : String = accountJson.id.getOrElse("")
val aPrivateViewId: String = (for {
aPrivateView <- views.filterNot(view => view.is_public.getOrElse(false)).headOption
viewId <- aPrivateView.id
} yield viewId).getOrElse("")
".accLink *" #> accountDisplayName(accountJson) &
".accLink [href]" #> {
"/banks/" + bankId + "/accounts/" + accountId + "/" + aPrivateViewId
}
}}
}
def loggedOutSnippet = {
".accountList" #> SHtml.span(Text("You are logged out. No authorised accounts available."), Noop,("id","accountsMsg"))
}
if(OAuthClient.loggedIn) loggedInSnippet
else loggedOutSnippet
}
def authorisedAccountsWithManageLinks = {
def loggedInSnippet = {
".accountList" #> privateAccountJsons.map { case (bankId, accountJson) => {
//TODO: It might be nice to ensure that the same view is picked each time the page loads
val views = accountJson.views_available.toList.flatten
val accountId: String = accountJson.id.getOrElse("")
val aPrivateViewId: String = (for {
aPrivateView <- views.filterNot(view => view.is_public.getOrElse(false)).headOption
viewId <- aPrivateView.id
} yield viewId).getOrElse("")
val removeAjax = SHtml.ajaxCall(JsRaw("this.getAttribute('data-accountid')"), accountId => {
ObpAPI.deleteAccount(bankId, accountId)
Noop
})
".accLink *" #> accountDisplayName(accountJson) &
".accLink [href]" #> {
"/banks/" + bankId + "/accounts/" + accountId + "/" + aPrivateViewId
} &
".remove [data-bankid]" #> bankId &
".remove [data-accountid]" #> accountId &
".remove [onclick]" #> removeAjax
}
}
}
def loggedOutSnippet = {
".accountList" #> SHtml.span(Text("You are logged out. No authorised accounts available."), Noop,("id","accountsMsg"))
}
if(OAuthClient.loggedIn) loggedInSnippet
else loggedOutSnippet
}
}

View File

@ -1,473 +0,0 @@
/**
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
*/
package code.snippet
import net.liftweb.http.js.JsCmds.Noop
import net.liftweb.http.Templates
import net.liftweb.util.Helpers._
import net.liftweb.http.S
import net.liftweb.common.Full
import scala.xml.NodeSeq
import net.liftweb.http.SHtml
import net.liftweb.common.Box
import net.liftweb.http.js.JsCmd
import net.liftweb.http.js.JE.JsRaw
import net.liftweb.http.js.JsCmds.RedirectTo
import net.liftweb.http.SessionVar
import scala.xml.Text
import net.liftweb.json._
import net.liftweb.json.JsonAST.JObject
import net.liftweb.json.JsonAST.JField
import net.liftweb.json.JsonAST.JString
import net.liftweb.json.JsonAST.JArray
import net.liftweb.http.StringField
import java.util.Date
import java.text.SimpleDateFormat
import net.liftweb.common.Loggable
import java.util.Currency
import net.liftweb.http.js.jquery.JqJsCmds.{AppendHtml,Hide}
import net.liftweb.http.js.JsCmds.{SetHtml,SetValById}
import net.liftweb.http.js.JE.Str
import net.liftweb.http.js.JsCmds.Alert
import net.liftweb.util.Props
import scala.xml.Utility
import net.liftweb.common.Failure
import java.net.URL
import java.net.URI
import java.text.NumberFormat
import net.liftweb.common.ParamFailure
import net.liftweb.util.CssSel
import code.lib.ObpJson._
import code.lib.ObpAPI
import code.lib.OAuthClient
import net.liftweb.http.RequestVar
case class CommentsURLParams(bankId: String, accountId: String, viewId: String, transactionId: String)
/**
* This whole class is a rather hastily put together mess
*/
class Comments(params : (TransactionJson, CommentsURLParams)) extends Loggable{
val FORBIDDEN = "---"
val transactionJson = params._1
val urlParams = params._2
val details = transactionJson.details
val transactionMetaData = transactionJson.metadata
val otherHolder = transactionJson.other_account.flatMap(_.holder)
val transactionValue = details.flatMap(_.value)
def calcCurrencySymbol(currencyCode: Option[String]) = {
(for {
code <- currencyCode
currency <- tryo { Currency.getInstance(code) }
symbol <- tryo { currency.getSymbol(S.locale) }
} yield symbol) getOrElse FORBIDDEN
}
val commentDateFormat = new SimpleDateFormat("kk:mm:ss EEE MMM dd yyyy")
val NOOP_SELECTOR = "#i_am_an_id_that_should_never_exist" #> ""
/*
TODO: Is this a good name for this function?
*/
def commentPageTitle(xhtml: NodeSeq): NodeSeq = {
val dateFormat = new SimpleDateFormat("EEE MMM dd yyyy")
var theCurrency = FORBIDDEN
def formatDate(date: Box[Date]): String = {
date match {
case Full(d) => dateFormat.format(d)
case _ => FORBIDDEN
}
}
(
".amount *" #>{
val amount : String = transactionValue.flatMap(_.amount) getOrElse FORBIDDEN
val transactionCurrencyCode = transactionValue.flatMap(_.currency)
val currencySymbol = calcCurrencySymbol(transactionCurrencyCode)
//TODO: Would be nice to get localise this in terms of "." vs "," and the location of the currency symbol
// (before or after the number)
amount + " " + currencySymbol
} &
".other_account_holder *" #> {
def otherHolderSelector = {
val holderName = otherHolder.flatMap(_.name)
val isAlias = otherHolder.flatMap(_.is_alias) getOrElse true
def aliasSelector = {
def indicatorClass = urlParams.viewId match {
case "public" => ".alias_indicator [class+]" #> "alias_indicator_public"
case _ => ".alias_indicator [class+]" #> "alias_indicator_private"
}
if (isAlias) {
".alias_indicator *" #> "(Alias)" &
indicatorClass
} else {
NOOP_SELECTOR
}
}
".the_name" #> holderName &
aliasSelector
}
if(otherHolder.isDefined) otherHolderSelector
else "* *" #> FORBIDDEN
} &
".date_cleared *" #> {
val finishDate = details.flatMap(_.completed)
finishDate match {
case Some(date) => formatDate(Full(date))
case _ => FORBIDDEN
}
} &
// Used for the reference / description entered by the account holder when the transaction is posted
// In one version of the API, "label" was the name of this
".description *" #> {
val description = details.flatMap(_.label)
description.getOrElse(FORBIDDEN)
} &
// Narrative is part of metadata (probably entered after the transaction is posted)
".narrative *" #> {
val narrative = transactionMetaData.flatMap(_.narrative)
narrative.getOrElse(FORBIDDEN)
} &
".new_balance *" #> {
val newBalance = details.flatMap(_.new_balance)
newBalance match {
case Some(b) => {
val amount = b.amount getOrElse FORBIDDEN
val accountCurrencyCode = b.currency
val currencySymbol = calcCurrencySymbol(accountCurrencyCode)
amount + " " + currencySymbol
}
case _ => FORBIDDEN
}
}
).apply(xhtml)
}
def images = {
addImage andThen showImages
}
def noImages = ".images_list" #> ""
def imagesNotAllowed = "* *" #> ""
def imageHtmlId(image: TransactionImageJson) : String = "trans-image-" + image.id.getOrElse("")
def showImages = {
def imagesSelector(imageJsons: List[TransactionImageJson]) = {
def deleteImage(imageJson: TransactionImageJson) = {
//TODO: This could be optimised into calling an ajax function with image id as a parameter to avoid
//storing multiple closures server side (i.e. one client side function maps to on server side function
//that takes a parameter)
SHtml.ajaxInvoke(() => {
imageJson.id match {
case Some(id) => {
val deleted = ObpAPI.deleteImage(urlParams.bankId, urlParams.accountId,
urlParams.viewId, urlParams.transactionId, id)
if(!deleted) logger.error("Tried to delete an image but it didn't work")
}
case _ => logger.warn("Tried to delete an image without an id")
}
val jqueryRemoveImage = "$('.image-holder[data-id=\"" + imageHtmlId(imageJson) + "\"]').remove();"
JsRaw(jqueryRemoveImage).cmd
})
}
".noImages" #> "" &
".image-holder" #> imageJsons.map(imageJson => {
".image-holder [data-id]" #> imageHtmlId(imageJson) &
".trans-image [src]" #> imageJson.URL.getOrElse("") &
".image-description *" #> imageJson.label.getOrElse("") &
".postedBy *" #> imageJson.user.flatMap(_.display_name).getOrElse("") &
".postedTime *" #> imageJson.date.map(commentDateFormat.format(_)).getOrElse("") &
".deleteImage [onclick]" #> deleteImage(imageJson)
})
}
val imageJsons = transactionJson.imageJsons
imageJsons match {
case Some(iJsons) => {
if(iJsons.isEmpty) noImages
else imagesSelector(iJsons)
}
case _ => imagesNotAllowed
}
}
def addImage = {
//transloadit requires its parameters to be an escaped json string
val transloadItParams : String = {
import net.liftweb.json.JsonDSL._
import net.liftweb.json._
val authKey = Props.get("transloadit.authkey") getOrElse ""
val addImageTemplate = Props.get("transloadit.addImageTemplate") getOrElse ""
val json =
(
"auth" -> (
"key" -> authKey
)
) ~
("template_id" -> addImageTemplate)
Utility.escape(compact(render(json)), new StringBuilder).toString
}
if(S.post_?) {
val description = S.param("description") getOrElse ""
val addedImage = for {
transloadit <- S.param("transloadit") ?~! "No transloadit data received"
json <- tryo{parse(transloadit)} ?~! "Could not parse transloadit data as json"
imageUrl <- tryo{val JString(a) = json \ "results" \\ "url"; a} ?~! {"Could not extract url string from json: " + compact(render(json))}
addedImage <- ObpAPI.addImage(urlParams.bankId, urlParams.accountId, urlParams.viewId, urlParams.transactionId, imageUrl, description)
} yield addedImage
addedImage match {
case Full(added) => {
//kind of a hack, but we redirect to a get request here so that we get the updated transaction (with the new image)
S.redirectTo(S.uri)
}
case Failure(msg, _ , _) => logger.warn("Problem adding new image: " + msg)
case _ => logger.warn("Problem adding new image")
}
}
if (OAuthClient.loggedIn) {
"#imageUploader [action]" #> S.uri &
"#imageUploader" #> {
"name=params [value]" #> transloadItParams
}
} else ".add *" #> ""
}
def noTags = ".tag" #> ""
def tagsNotAllowed = "* *" #> ""
def deleteTag(tag: TransactionTagJson) = {
SHtml.a(() => {
tag.id match {
case Some(id) => {
val worked = ObpAPI.deleteTag(urlParams.bankId, urlParams.accountId, urlParams.viewId,
urlParams.transactionId, id)
if(!worked) logger.warn("Deleting tag with id " + id + " failed")
}
case _ => logger.warn("Tried to delete a tag without an id")
}
Hide(tag.id.getOrElse(""))
}, Text("x"), ("title", "Remove the tag"))
}
def showTags = {
val tagJsons = transactionJson.tagJsons
def showTags(tags : List[TransactionTagJson]) = {
def orderByDateDescending =
(tag1: TransactionTagJson, tag2: TransactionTagJson) =>
tag1.date.getOrElse(now).before(tag2.date.getOrElse(now))
".tagsContainer" #> {
"#noTags" #> "" &
".tag" #> tags.sortWith(orderByDateDescending).map(getTagSelector)
}
}
tagJsons match {
case Some(tJsons) => {
if (tJsons.isEmpty) noTags
else showTags(tJsons)
}
case _ => tagsNotAllowed
}
}
def getTagSelector (tag: TransactionTagJson) = {
".tagID [id]" #> tag.id.getOrElse("") &
".tagValue" #> tag.value.getOrElse("") &
".deleteTag" #> deleteTag(tag)
}
def addTag(xhtml: NodeSeq): NodeSeq = {
var tagValues : List[String] = Nil
def newTagsXml(newTags: List[TransactionTagJson]): Box[NodeSeq] = {
Templates(List("templates-hidden", "_tag")).map {
".tag" #> newTags.map { getTagSelector }
}
}
def addTagSelector = {
SHtml.ajaxForm(
SHtml.text("",
tags => {
val tagVals = tags.split(" ").toList.filter(tag => !tag.isEmpty)
tagValues = tagVals
},
("placeholder", "Add tags seperated by spaces"),
("id", "addTagInput"),
("size", "30")) ++
SHtml.ajaxSubmit(
"Tag",
() => {
val newTags = ObpAPI.addTags(urlParams.bankId, urlParams.accountId, urlParams.viewId,
urlParams.transactionId, tagValues)
val newXml = newTagsXml(newTags)
//TODO: update the page
val content = Str("")
SetValById("addTagInput",content)&
SetHtml("noTags",NodeSeq.Empty) &
AppendHtml("tags_list", newXml.getOrElse(NodeSeq.Empty))
},
("id", "submitTag")))
}
if(OAuthClient.loggedIn) {
addTagSelector
} else {
(".add" #> "You need to login before you can add tags").apply(xhtml)
}
}
def noComments = ".comment" #> ""
def commentsNotAllowed = "* *" #> ""
def showComments = {
val commentJsons = transactionJson.commentJsons
def showComments(comments: List[TransactionCommentJson]) = {
def orderByDateDescending =
(comment1: TransactionCommentJson, comment2: TransactionCommentJson) =>
comment1.date.getOrElse(now).before(comment2.date.getOrElse(now))
".commentsContainer" #> {
"#noComments" #> "" &
".comment" #> comments.sortWith(orderByDateDescending).zipWithIndex.map {
case (commentJson, position) => commentCssSel(commentJson, position + 1)
}
}
}
commentJsons match {
case Some(cJsons) => {
if (cJsons.isEmpty) noComments
else showComments(cJsons)
}
case _ => commentsNotAllowed
}
}
def commentCssSel(commentJson: TransactionCommentJson, displayPosition : Int) = {
def commentDate: CssSel = {
commentJson.date.map(d => {
".commentDate *" #> commentDateFormat.format(d)
}) getOrElse
".commentDate" #> ""
}
def userInfo: CssSel = {
commentJson.user.map(u => {
".userInfo *" #> {
" -- " + u.display_name.getOrElse("")
}
}) getOrElse
".userInfo" #> ""
}
".text *" #> commentJson.value.getOrElse("") &
".commentLink * " #> { "#" + displayPosition } &
".commentLink [id]" #> displayPosition &
".commentLink [href]" #> { "#" + displayPosition } &
commentDate &
userInfo
}
var commentsListSize : Int = transactionJson.commentJsons.map(_.size).getOrElse(0)
def addComment : CssSel = {
//TODO: Get this from the API once there's a good way to do it
def viewAllowsAddingComments = true
def mustLogIn = ".add" #> "You need to login before you can submit a comment"
def loggedIn : CssSel = if(viewAllowsAddingComments) addCommentSnippet
else ".add" #> "You cannot comment transactions on this view"
def addCommentSnippet : CssSel = {
var commentText = ""
".add" #>
SHtml.ajaxForm(
SHtml.textarea(
"",
commentText = _,
("rows", "4"), ("cols", "50"),
("id", "addCommentTextArea"),
("placeholder", "add a comment here")) ++
SHtml.ajaxSubmit("add a comment", () => {
val newCommentXml = for {
newComment <- ObpAPI.addComment(urlParams.bankId, urlParams.accountId, urlParams.viewId, urlParams.transactionId, commentText)
commentXml <- Templates(List("templates-hidden", "_comment"))
} yield {
commentsListSize = commentsListSize + 1
commentCssSel(newComment, commentsListSize).apply(commentXml)
}
val content = Str("")
SetValById("addCommentTextArea", content) &
SetHtml("noComments", NodeSeq.Empty) &
AppendHtml("comment_list", newCommentXml.getOrElse(NodeSeq.Empty))
}, ("id", "submitComment")))
}
if (!OAuthClient.loggedIn) mustLogIn
else loggedIn
}
}

View File

@ -1,30 +0,0 @@
package code.snippet
import code.lib.OAuthClient
import net.liftweb.http.{S, SHtml}
import net.liftweb.util.{Props}
import net.liftweb.util.Helpers._
import scala.xml.NodeSeq
class ConnectBankAccount {
def connect : NodeSeq => NodeSeq = {
val hide = "* *" #> NodeSeq.Empty
def showIfHomepage() = {
val currentPage = S.uri
//only show something if we're on the home page
if(currentPage == "/" || currentPage == "/index") {
".navlink [onclick+]" #> SHtml.onEvent(_ => {
OAuthClient.redirectToConnectBankAccount()
})
} else hide
}
if(Props.getBool("showConnectBankAccountLink", false)) showIfHomepage()
else hide
}
}

View File

@ -1,10 +0,0 @@
package code.snippet
import net.liftweb.util.Helpers._
import net.liftweb.util.Props
class ConnectLink {
def link = "* [href]" #> Props.get("api_hostname").map(_ + "/connect").getOrElse("#")
}

View File

@ -1,87 +0,0 @@
package code.snippet
import net.liftweb.common.Loggable
import net.liftweb.http.S
import net.liftweb.http.js.JsCmd
import net.liftweb.http.js.JsCmds._
import scala.xml.Text
import net.liftweb.util.Helpers._
import code.lib.ObpAPI
import net.liftweb.http.SHtml
import net.liftweb.common.Failure
import code.lib.ObpJson.PermissionsJson
import code.lib.ObpJson.AccountJson
import code.lib.ObpJson.ViewJson
import net.liftweb.util.CssSel
class CreatePermissionForm(params : (List[ViewJson], AccountJson, PermissionsUrlParams)) extends Loggable {
case class ViewData(view : ViewJson, allowed: Boolean)
val views = params._1
val accountJson = params._2
val urlParams = params._3
val url = S.uri.split("/")
var email = ""
val allowed : scala.collection.mutable.Map[ViewJson, Boolean] = scala.collection.mutable.Map(views.map(v => (v, false)).toSeq : _*)
val nonPublicViews = views.filterNot(_.is_public.getOrElse(true))
val nonBadViews = nonPublicViews.filterNot(_.id == None)
def viewData = nonBadViews.map(view => ViewData(view, allowed(view)))
def render = {
def process(): JsCmd = {
def showMsg(msg : String) = {
SetHtml("create-permission-message", Text(msg))
}
def invalidEmail() : Boolean = !email.contains("@")
if(viewData.forall(_.allowed == false)) showMsg("You must select at least one view to grant access to.")
else if(email.isEmpty()) showMsg("You must enter an e-mail address")
else if(invalidEmail()) showMsg("Invalid e-mail address")
else {
//create the permission and return any failure
if(url.length > 4) {
val bankId = url(2)
val accountId = url(4)
val viewIds = for {
vData <- viewData
if(vData.allowed)
vId <- vData.view.id
} yield vId
val result = ObpAPI.addPermissions(bankId, accountId, email, viewIds)
result match {
case Failure(msg, _, _) => showMsg(msg)
case _ => {
//Redirect to permissions overview
//TODO: Would be nice to calculate this but at the moment there is nothing to do it
S.redirectTo("/banks/" + bankId + "/accounts/" + accountId + "/permissions")
Noop
}
}
} else {
logger.warn("Couldn't determine bank and account from url:" + url.toList.toString)
showMsg("error")
}
}
}
".view_row *" #> viewData.map(vData => {
".view_name *" #> vData.view.short_name.getOrElse(vData.view.id.getOrElse("")) &
".view_check" #> SHtml.checkbox(allowed(vData.view), allowed.put(vData.view, _))
}) &
"name=email" #> (SHtml.text(email, email = _) ++ SHtml.hidden(process))
}
//def accountInfo = ".account-label *" #> accountJson.label.getOrElse("---")
}

View File

@ -1,156 +0,0 @@
/**
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
Nina Gänsdorfer: nina AT tesobe.com
*/
package code.snippet
import net.liftweb.http.SHtml
import scala.xml.NodeSeq
object CustomEditable {
import net.liftweb.http.js
import net.liftweb.util.Helpers
import js.{ jquery, JsCmd, JsCmds, JE }
import jquery.JqJsCmds
import JsCmds.{ Noop, SetHtml }
import JE.Str
import JqJsCmds.{ Hide, Show }
val addClass = "add"
val editClass = "edit"
val removeClass = "remove"
val noAliasTooltip = "No alias is set, so the real account name will be displayed."
val confirmRemoval = "If no alias is set, the real account name will be displayed."
def dispName(divName: String) : String = divName + "_display"
def editName(divName: String) : String = divName + "_edit"
/*
* Depending on what was clicked only either the "_edit" or the "_display" div is visible
*/
def swapJsCmd(show: String, hide: String): JsCmd = Show(show) & Hide(hide)
def setAndSwap(show: String, showContents: => NodeSeq, hide: String): JsCmd =
(SHtml.ajaxCall(Str("ignore"), { ignore: String => SetHtml(show, showContents) })._2.cmd & swapJsCmd(show, hide))
/*
* The edit markup consists of the input field (editForm), a submit button and a cancel button
* Clicking on either of buttons will swap back to the display markup, the submit button will save the data of the input field
*/
def editMarkup(label : => String, editForm: => NodeSeq, onSubmit: () => JsCmd, onDelete: () => Unit, defaultValue: String, divName: String, removable: Boolean): NodeSeq = {
val formData: NodeSeq =
editForm ++ <br />
<input class="submit" style="float:left;" type="image" src="/media/images/submit.png"/> ++
SHtml.hidden(onSubmit, ("float", "left")) ++
<input type="image" src="/media/images/cancel.png" onclick={ swapJsCmd(dispName(divName), editName(divName)).toJsCmd + " return false;" }/>
SHtml.ajaxForm(formData,
Noop,
setAndSwap(dispName(divName), displayMarkup(label, editForm, onSubmit, onDelete, defaultValue, divName, removable), editName(divName)))
}
/*
* The display markup shows the label (default value if none is set), an edit button and for some fields (currently only the alias fields) also a remove button
* Clicking on the edit button will swap to the edit markup
* Clicking on the remove button will pop up a confirmation window
*/
def displayMarkup(label : => String, editForm: => NodeSeq, onSubmit: () => JsCmd, onDelete: () => Unit, defaultValue: String, divName: String, removable: Boolean): NodeSeq = {
label match {
case "" => {
if(removable){
<div title={ noAliasTooltip } >
<a href="#" class={ editClass } onclick={ setAndSwap(editName(divName), editMarkup(label, editForm, onSubmit, onDelete, defaultValue, divName, removable), dispName(divName)).toJsCmd + " return false;" } />
<br/><span class="text-add-edit">{ defaultValue }</span>
</div>
} else{
<div>
<a href="#" class={ addClass } onclick={ setAndSwap(editName(divName), editMarkup(label, editForm, onSubmit, onDelete, defaultValue, divName, removable), dispName(divName)).toJsCmd + " return false;" }>
{ " " ++ defaultValue }
</a>
</div>
}
}
case _ => {
<div>
<a href="#" class={ editClass } onclick={ setAndSwap(editName(divName), editMarkup(label, editForm, onSubmit, onDelete, defaultValue, divName, removable), dispName(divName)).toJsCmd + " return false;" }/>
{ if (removable)
<a href="#" class={ removeClass } onclick={ removeAlias("", editForm, onSubmit, onDelete, defaultValue, divName, removable) } />
}
<br/><span class="text">{ label }</span>
</div>
}
}
}
/*
* Pop-up window when removing alias: on approval deletes alias and empties value in input field of the edit markup
*/
def removeAlias(label: String, editForm: NodeSeq, onSubmit: () => JsCmd, onDelete: () => Unit, defaultValue: String, divName: String, removable: Boolean): String = {
def removalConfirmed: JsCmd = {
SHtml.ajaxInvoke(() => {
onDelete()
var empty = ""
setAndSwap(dispName(divName), displayMarkup(empty, SHtml.text(empty, empty = _), onSubmit, onDelete, defaultValue, divName, removable), editName(divName))
})._2.cmd
}
val confirmationPopup = JsCmds.Confirm(confirmRemoval, removalConfirmed)
confirmationPopup
}
/*
*
*/
def editable(label : => String, editForm: => NodeSeq, onSubmit: () => JsCmd, onDelete: () => Unit, defaultValue: String, removable: Boolean): NodeSeq ={
val divName = Helpers.nextFuncName
<div>
<div id={ dispName(divName) }>
{ displayMarkup(label, editForm, onSubmit, onDelete, defaultValue, divName, removable) }
</div>
<div id={ editName(divName) } style="display: none;">
{ editMarkup(label, editForm, onSubmit, onDelete, defaultValue, divName, removable) }
</div>
</div>
}
}

View File

@ -1,229 +0,0 @@
/**
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
Nina Gänsdorfer: nina AT tesobe.com
*/
package code.snippet
import code.util.Helper._
import net.liftweb.json.JsonAST._
import net.liftweb.util.Helpers._
import scala.xml.NodeSeq
import net.liftweb.http.SHtml
import net.liftweb.http.js.JsCmds.{Confirm, Alert, Noop}
import code.widgets.tableSorter.{CustomTableSorter, DisableSorting, Sorting, Sorter}
import code.lib.ObpJson.OtherAccountsJson
import code.lib.ObpJson.OtherAccountJson
import code.lib.ObpDeleteBoolean
import code.lib.ObpPost
import code.lib.ObpPut
import net.liftweb.json.JsonDSL._
case class ManagementURLParams(bankId: String, accountId: String)
class Management(params : (OtherAccountsJson, ManagementURLParams)) {
val otherAccountsJson = params._1
val urlParams = params._2
val headers = (0, Sorter("text")) :: (5, DisableSorting()) :: (6, DisableSorting()) :: Nil
val sortList = (0, Sorting.ASC) :: Nil
val options = CustomTableSorter.options(headers, sortList)
def setAccountTitle = ".account_title *" #> getAccountTitle(urlParams.bankId, urlParams.accountId)
def tableSorter(xhtml: NodeSeq) : NodeSeq = {
CustomTableSorter("#other_acc_management", options)
}
def showAll(xhtml: NodeSeq) : NodeSeq = {
def editablePublicAlias(initialValue : String, holder: String, otherAccountId: String) = {
apiAliasEditable(initialValue, holder, "alias", "public_alias", otherAccountId, displayHolder(holder, "Public Alias"), true)
}
def editablePrivateAlias(initialValue : String, holder: String, otherAccountId: String) = {
apiAliasEditable(initialValue, holder, "alias", "private_alias", otherAccountId, displayHolder(holder, "Private Alias"), true)
}
def editableImageUrl(initialValue : String, holder: String, otherAccountId: String) = {
apiEditable(initialValue, holder, "image_URL", "image_url", otherAccountId, "Image URL", false)
}
def editableUrl(initialValue : String, holder: String, otherAccountId: String) = {
apiEditable(initialValue, holder, "URL", "url", otherAccountId, "Website", false)
}
def editableMoreInfo(initialValue : String, holder: String, otherAccountId: String) = {
apiEditable(initialValue, holder, "more_info", "more_info", otherAccountId, "Information", false)
}
def editableOpenCorporatesUrl(initialValue : String, holder: String, otherAccountId: String) = {
apiEditable(initialValue, holder, "open_corporates_URL", "open_corporates_url", otherAccountId, "Open Corporates URL", false)
}
def displayHolder(holder: String, default: String): String = {
if (holder.isEmpty)
default
else
"("+holder+")"
}
/**
* TODO: If there are a lot of CustomEditables on a page, and lots of these pages open, that creates
* a lot of closures to exist on the server. It would be good to refactor this to only require one function
* per page on the server.
*/
def apiEditable(
initialValue: String,
holder: String,
jsonKey: String,
apiProperty: String,
otherAccountId: String,
defaultValue: String,
removable: Boolean ) = {
var currentValue = initialValue
var exists = currentValue.nonEmpty
def json() : JValue = (jsonKey -> currentValue)
def saveValue() = {
if(currentValue.isEmpty) {
//Send a delete
ObpDeleteBoolean("/v1.2/banks/" + urlParams.bankId + "/accounts/" + urlParams.accountId + "/owner/other_accounts/" + otherAccountId + "/" + apiProperty)
exists = false
} else {
if(exists) {
ObpPut("/v1.2/banks/" + urlParams.bankId + "/accounts/" + urlParams.accountId + "/owner/other_accounts/" + otherAccountId + "/" + apiProperty,
json())
} else {
ObpPost("/v1.2/banks/" + urlParams.bankId + "/accounts/" + urlParams.accountId + "/owner/other_accounts/" + otherAccountId + "/" + apiProperty,
json())
exists = true
}
}
}
CustomEditable.editable(currentValue, SHtml.text(currentValue, currentValue = _),
onSubmit = () => {
saveValue()
Noop
},
onDelete = () => {
currentValue = ""
saveValue()
},
defaultValue = defaultValue,
removable = removable
)
}
def apiAliasEditable(
initialValue: String,
holder: String,
jsonKey: String,
apiProperty: String,
otherAccountId: String,
defaultLabel: String, // is displayed when no alias is set
removable: Boolean ) = {
var currentValue = initialValue
var inputDefaultValue = initialValue
CustomEditable.editable(inputDefaultValue, SHtml.text(inputDefaultValue, currentValue = _),
onSubmit = () => {
if(currentValue.isEmpty || (holder.toLowerCase == currentValue.toLowerCase && apiProperty == "public_alias")){
// delete Alias
ObpDeleteBoolean("/v1.2/banks/" + urlParams.bankId + "/accounts/" + urlParams.accountId + "/owner/other_accounts/" + otherAccountId + "/" + apiProperty)
inputDefaultValue = ""
Noop
} else{
val json = jsonKey -> currentValue
ObpPut("/v1.2/banks/" + urlParams.bankId + "/accounts/" + urlParams.accountId + "/owner/other_accounts/" + otherAccountId + "/" + apiProperty,
json)
inputDefaultValue = currentValue
Noop
}
},
onDelete = () => {
ObpDeleteBoolean("/v1.2/banks/" + urlParams.bankId + "/accounts/" + urlParams.accountId + "/owner/other_accounts/" + otherAccountId + "/" + apiProperty)
},
defaultValue = defaultLabel,
removable = removable
)
}
val otherAccountJsons : List[OtherAccountJson] = otherAccountsJson.other_accounts.getOrElse(Nil).
sortBy[String](oAcc => oAcc.holder.flatMap(_.name).getOrElse(""))
otherAccountJsons.flatMap {
oAccJson => {
val account = oAccJson.holder.flatMap(_.name).getOrElse("")
val publicAlias = oAccJson.metadata.flatMap(_.public_alias).getOrElse("")
val privateAlias = oAccJson.metadata.flatMap(_.private_alias).getOrElse("")
val moreInfo = oAccJson.metadata.flatMap(_.more_info).getOrElse("")
val website = oAccJson.metadata.flatMap(_.URL).getOrElse("")
val openCorporates = oAccJson.metadata.flatMap(_.open_corporates_URL).getOrElse("")
val imageURL = oAccJson.metadata.flatMap(_.image_URL).getOrElse("")
val accountId = oAccJson.id.getOrElse("")
val accountSelector = ".account *" #> account
val accountIdSelector = ".account [id]" #> accountId
val publicSelector = ".public *" #> editablePublicAlias(publicAlias, account, accountId)
val privateSelector = ".private *" #> editablePrivateAlias(privateAlias, account, accountId)
val websiteSelector = ".website *" #> editableUrl(website, account, accountId)
val openCorporatesSelector = ".open_corporates *" #> editableOpenCorporatesUrl(openCorporates, account, accountId)
val moreInfoSelector = ".information *" #> editableMoreInfo(moreInfo, account, accountId)
val imageURLSelector = ".imageURL *" #> editableImageUrl(imageURL, account, accountId)
(accountSelector &
accountIdSelector &
publicSelector &
privateSelector &
websiteSelector &
openCorporatesSelector &
moreInfoSelector &
imageURLSelector).apply(xhtml)
}
}
}
}

View File

@ -1,174 +0,0 @@
/**
* Open Bank Project - Transparency / Social Finance Web Application
* Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE / Music Pictures Ltd
* Osloerstrasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
* by
* Simon Redfern : simon AT tesobe DOT com
* Stefan Bethge : stefan AT tesobe DOT com
* Everett Sochowski : everett AT tesobe DOT com
* Ayoub Benali: ayoub AT tesobe DOT com
*
*/
package code.snippet
import net.liftweb.http.S
import net.liftweb.http.LiftRules
import net.liftweb.util.Helpers._
import net.liftweb.sitemap.Loc
import net.liftweb.common.Box
import net.liftweb.common.Full
import net.liftweb.common.Empty
import net.liftweb.sitemap.SiteMapSingleton
import code.lib.ObpJson._
import code.lib.ObpAPI
import net.liftweb.util.CssSel
class Nav {
val url = S.uri.split("/", 0)
val accountJson : Option[AccountJson]= {
if (url.size > 4) {
val viewId = "owner" //if we can't access the owner view, account returns nothing
val bankId = url( url.indexOf("banks")+1 )
val accountId = url( url.indexOf("accounts")+1 )
ObpAPI.getAccount(bankId, accountId, viewId)
} else {
None
}
}
val viewJsons: List[ViewJson] = {
accountJson.flatMap(accJson => {
accJson.views_available
}).toList.flatten
}
def eraseMenu =
"* * " #> ""
def views: net.liftweb.util.CssSel = {
val url = S.uri.split("/", 0)
if (url.size > 4) {
".navitem *" #> {
viewJsons.map(viewJson => {
val viewUrl = "/banks/" + url(2) + "/accounts/" + url(4) + "/" + viewJson.id.getOrElse("")
".navlink [href]" #> viewUrl &
".navlink *" #> viewJson.short_name.getOrElse("") &
".navlink [class+]" #> markIfSelected(viewUrl)
})
}
} else
eraseMenu
}
def management = {
val url = S.uri.split("/", 0)
// Menu for a page which lists counterparties and their metadata (and edits the metadata)
def getManagement = {
val views = accountJson.flatMap(_.views_available).toList.flatten
//TODO: Determine this in a better way
val hasOwnerPermissions = views.exists(v => v.id == Some("owner"))
if (hasOwnerPermissions) {
val managementUrl = "/banks/" + url(2) + "/accounts/" + url(4) + "/management"
Some(".navlink [href]" #> { managementUrl } &
".navlink *" #> "Counterparties" &
".navlink [class+]" #> markIfSelected(managementUrl))
} else None
}
if (url.size > 4) getManagement getOrElse eraseMenu
else eraseMenu
}
// Menu For Entitlements / permissions on an account / view
def editViews : CssSel = {
val views = accountJson.flatMap(_.views_available).toList.flatten
val hasOwnerPermissions = views.exists(v => v.id == Some("owner"))
if(hasOwnerPermissions) {
val editViewsUrl = "/banks/" + url(2) + "/accounts/" + url(4) + "/views/list"
".navlink [href]" #> { editViewsUrl } &
".navlink *" #> "Views" &
".navlink [class+]" #> markIfSelected(editViewsUrl)
} else eraseMenu
}
def item = {
val name = S.attr("name").getOrElse("")
val loc = (for {
sitemap <- LiftRules.siteMap
l <- new SiteMapSingleton().findAndTestLoc(name)
} yield l)
".navitem *" #> {
loc.map(navItemSelector)
}
}
def navItemSelector(l: Loc[_]) = {
".navlink [href]" #> l.calcDefaultHref &
".navlink *" #> l.linkText &
".navlink [class+]" #> markIfSelected(l.calcDefaultHref)
}
// Menu for which Users have access to which Views
def privilegeAdmin = {
val url = S.uri.split("/", 0)
def getPrivilegeAdmin = {
val views = accountJson.flatMap(_.views_available).toList.flatten
//TODO: Determine this in a better way
val hasOwnerPermissions = views.exists(v => v.id == Some("owner"))
if (hasOwnerPermissions) {
val permissionsUrls = "/banks/" + url(2) + "/accounts/" + url(4) + "/permissions"
Some(".navitem *" #> {
".navlink [href]" #> permissionsUrls &
".navlink *" #> "Users" &
".navlink [class+]" #> markIfSelected(permissionsUrls)
})
} else None
}
def hide = ".navitem *" #> ""
if (url.size > 4) getPrivilegeAdmin.getOrElse(hide)
else hide
}
def markIfSelected(href: String): Box[String] = {
val currentHref = S.uri
if (href.equals(currentHref)) Full("selected")
else Empty
}
}

View File

@ -1,134 +0,0 @@
package code.snippet
import net.liftweb.http.S
import net.liftweb.util.Helpers._
import net.liftweb.http.SHtml
import net.liftweb.http.js.JE.JsRaw
import net.liftweb.common.Loggable
import net.liftweb.http.JsonHandler
import net.liftweb.json._
import net.liftweb.common.Full
import net.liftweb.common.Failure
import code.lib.Provider
import code.lib.OAuthClient
import net.liftweb.http.js.JsCmds.{Script, Noop}
import code.lib.ObpAPI
import code.lib.ObpJson._
import net.liftweb.util.CssSel
import net.liftweb.http.js.JsCmds.Replace
import scala.xml.NodeSeq
import net.liftweb.http.js.jquery.JqJsCmds.FadeOut
import net.liftweb.http.js.JsCmd
import net.liftweb.http.js.JsCmds.SetHtml
import scala.xml.Text
import net.liftweb.http.js.jquery.JqJsCmds.Show
import code.util.Helper._
case class PermissionsUrlParams(bankId : String, accountId: String)
case class ClickJson(userId: String, checked: Boolean, viewId : String)
class PermissionManagement(params : (PermissionsJson, AccountJson, List[ViewJson], PermissionsUrlParams)) extends Loggable {
val permissionsJson = params._1
val accountJson = params._2
val nonPublicViews : List[ViewJson] = params._3.filterNot(_.is_public.getOrElse(true))
val urlParams = params._4
val NOOP_SELECTOR = "#i_am_an_id_that_should_never_exist" #> ""
implicit val formats = DefaultFormats
def rowId(userId: String) = "permission_row_" + userId
val clickAjax = SHtml.ajaxCall(JsRaw("permissionsCheckBoxCallback(this)"), checkBoxClick)
val removeAjax = SHtml.ajaxCall(JsRaw("this.getAttribute('data-userid')"), userId => {
ObpAPI.removeAllPermissions(urlParams.bankId, urlParams.accountId, userId)
Noop
})
def checkBoxClick(rawData : String) = {
val data = tryo{parse(rawData).extract[ClickJson]}
data match {
case Full(d) => {
if(d.checked) ObpAPI.addPermission(urlParams.bankId, urlParams.accountId, d.userId, d.viewId)
else ObpAPI.removePermission(urlParams.bankId, urlParams.accountId, d.userId, d.viewId)
}
case Failure(msg, _, _) => logger.warn("Could not parse raw checkbox click data: " + rawData + ", " + msg)
case _ => logger.warn("Could not parse raw checkbox click data: " + rawData)
}
Noop
}
val checkBoxJsFunc = JsRaw("""
function permissionsCheckBoxCallback(checkbox) {
var json = {
"userId" : checkbox.getAttribute("data-userid"),
"checked" : checkbox.checked,
"viewId" : checkbox.getAttribute("data-viewid")
}
return JSON.stringify(json);
}
""").cmd
def accountInfo = ".account-label *" #> getAccountTitle(accountJson)
def accountViewHeaders = {
val viewNames : List[String] = nonPublicViews.map(_.short_name.getOrElse(""))
".view_name *" #> viewNames
}
def checkBoxes(permission : PermissionJson) = {
".view-checkbox *" #> nonPublicViews.map(view => {
val onClick = "." + view + " [onclick]"
val userIdData = "." + view + " [data-userid]"
val viewIdData = "." + view + " [data-viewid]"
val permissionExists = (for {
views <- permission.views
}yield {
views.exists(_.id == (view.id))
}).getOrElse(false)
val checkedSelector : CssSel =
if(permissionExists) {{".check [checked]"} #> "checked"}
else NOOP_SELECTOR
checkedSelector &
".check [onclick]" #> clickAjax &
".check [data-userid]" #> permission.user.flatMap(_.id) &
".check [data-viewid]" #> view.id
})
}
def manage = {
permissionsJson.permissions match {
case None => "* *" #> "No permissions exist"
case Some(ps) => {
".callback-script" #> Script(checkBoxJsFunc) &
".row" #> {
ps.map(permission => {
val userId = permission.user.flatMap(_.id).getOrElse("")
"* [id]" #> rowId(userId) &
".user *" #> permission.user.flatMap(_.display_name).getOrElse("") &
checkBoxes(permission) &
".remove [data-userid]" #> userId &
".remove [onclick]" #> removeAjax
})
}
}
}
}
def addPermissionLink = {
//TODO: Should generate this url instead of hardcode it
"* [href]" #> {S.uri + "/create"}
}
}

View File

@ -1,441 +0,0 @@
/**
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
*/
package code.snippet
import net.liftweb.http.{PaginatorSnippet, StatefulSnippet}
import java.text.SimpleDateFormat
import net.liftweb.http._
import java.util.Calendar
import xml.NodeSeq
import net.liftweb.util.Helpers._
import net.liftweb.util._
import scala.xml.Text
import net.liftweb.common.{Box, Failure, Empty, Full}
import java.util.Date
import net.liftweb.http.js.JsCmds.Noop
import java.util.Currency
import code.lib.ObpJson._
import code.lib._
import net.liftweb.json.JsonDSL._
import code.util.Helper._
case class TransactionsListURLParams(bankId: String, accountId: String, viewId: String)
class OBPTransactionSnippet (params : (TransactionsJson, AccountJson, TransactionsListURLParams)){
val transactionsJson = params._1
val accountJson = params._2
val transactionsURLParams = params._3
val NOOP_SELECTOR = "#i_am_an_id_that_should_never_exist" #> ""
val FORBIDDEN = "---"
val currencySymbol = {
transactionsJson.transactions match {
case None | Some(Nil) => ""
case Some(x :: xs) => {
(for {
details <- x.details
value <- details.value
currencyCode <- value.currency
currency <- tryo{Currency.getInstance(currencyCode)}
symbol <- tryo{currency.getSymbol(S.locale)}
} yield symbol).getOrElse("")
}
}
}
def individualApiTransaction(transaction: TransactionJson): CssSel = {
def otherPartyInfo: CssSel = {
def info(oAcc : OtherAccountJson): CssSel = {
def moreInfo(metadata : OtherAccountMetadataJson) = {
def moreInfoBlank =
".other_account_more_info" #> NodeSeq.Empty &
".other_account_more_info_br" #> NodeSeq.Empty
metadata.more_info.map(".other_account_more_info *" #> _).getOrElse(moreInfoBlank)
}
def logo(metadata : OtherAccountMetadataJson) = {
metadata.image_URL.map(".other_account_logo_img [src]" #> _).getOrElse(NOOP_SELECTOR)
}
def website(metadata : OtherAccountMetadataJson) = {
def websiteBlank =
".other_acc_link" #> NodeSeq.Empty & //If there is no link to display, don't render the <a> element
".other_acc_link_br" #> NodeSeq.Empty
metadata.URL.map(".other_acc_link [href]" #> _).getOrElse(websiteBlank)
}
def openCorporates(metadata : OtherAccountMetadataJson) = {
def openCorporatesBlank =
".open_corporates_link" #> NodeSeq.Empty
metadata.open_corporates_URL.map(".other_acc_link [href]" #> _).getOrElse(openCorporatesBlank)
}
def aliasSelector = {
val hasManagementAccess = {
val availableViews = accountJson.views_available.toList.flatten
availableViews.exists(view => view.id == Some("owner"))
}
def aliasInfo = {
val name = oAcc.holder.flatMap(_.name)
val isAlias = oAcc.holder.flatMap(_.is_alias).getOrElse(true)
val privateAlias = oAcc.metadata.flatMap(_.private_alias)
val publicAlias = oAcc.metadata.flatMap(_.public_alias)
if(isAlias) {
if(publicAlias == name) {
".alias_indicator [class+]" #> "alias_indicator_public" &
".alias_indicator *" #> "(Alias)"
} else if (privateAlias == name) {
".alias_indicator [class+]" #> "alias_indicator_private" &
".alias_indicator *" #> "(Alias)"
} else ".alias_indicator" #> ""
} else ".alias_indicator" #> ""
}
def aliasName = {
val otherAccountLink = "management#" + oAcc.id.getOrElse("")
if (hasManagementAccess) {
".otherAccountLinkForName [href]" #> otherAccountLink &
".otherAccountLinkForName *" #> oAcc.holder.flatMap(_.name).getOrElse(FORBIDDEN) &
".otherAccountLinkForAlias [href]" #> otherAccountLink
} else {
".the_name *" #> oAcc.holder.flatMap(_.name).getOrElse(FORBIDDEN)
}
}
aliasInfo &
aliasName
}
aliasSelector &
(oAcc.metadata match {
case None => ".extra *" #> NodeSeq.Empty
case Some(meta) => {
moreInfo(meta) &
logo(meta) &
website(meta) &
openCorporates(meta)
}
})
}
transaction.other_account match {
case None => ".the_name *" #> NodeSeq.Empty & ".extra *" #> NodeSeq.Empty
case Some(oAcc) => info(oAcc)
}
}
def transactionInformation: CssSel = {
def description = {
".description *" #> {
val description = transaction.details.flatMap(_.label) match {
case Some(a) => a
case _ => FORBIDDEN // TODO Different symbol for forbidden / empty?
}
description
}
}
def amount = {
".amount *" #> {
val amount = transaction.details.flatMap(_.value.flatMap(_.amount)) match {
case Some(a) => a.stripPrefix("-")
case _ => ""
}
currencySymbol + " " + amount
}
}
def narrative = {
val narrativeValue = transaction.metadata.flatMap(_.narrative).getOrElse("")
val narrativeUrl = "/v1.2/banks/" + transactionsURLParams.bankId + "/accounts/" + transactionsURLParams.accountId + "/" + transactionsURLParams.viewId +
"/transactions/" + transaction.id.getOrElse("") + "/metadata/narrative"
var newNarrativeValue = narrativeValue
var exists = !newNarrativeValue.isEmpty
def json() = ("narrative" -> newNarrativeValue)
def saveValue() = {
if (newNarrativeValue.isEmpty) {
//Send a delete
ObpDeleteBoolean(narrativeUrl)
exists = false
} else {
if (exists) {
ObpPut(narrativeUrl, json())
} else {
ObpPost(narrativeUrl, json())
exists = true
}
}
}
def apiEditableNarrative = {
CustomEditable.editable(
newNarrativeValue,
SHtml.text(newNarrativeValue, newNarrativeValue = _),
() => {
saveValue()
Noop
},
() => {},
"Narrative",
false)
}
//TODO: Get this from the api
def canEditNarrative = transactionsURLParams.viewId == "owner"
".narrative *" #> {
if (canEditNarrative) apiEditableNarrative
else Text(narrativeValue)
}
}
/**
* @return a full box containing the answer if everything went well, or something else if the
* information was not available/badly formatted
*/
def isPositiveAmount : Box[Boolean] = {
for {
details <- transaction.details
value <- details.value
amount <- value.amount
amountAsDouble <- tryo{amount.toDouble}
} yield (amountAsDouble > 0)
}
def symbol = {
".symbol *" #> {
isPositiveAmount match {
case Full(isPos) => if (isPos) "+" else "-"
case _ => ""
}
}
}
def transactionInOrOut = {
".out [class]" #> {
isPositiveAmount match {
case Full(isPos) => if (isPos) "in" else "out"
case _ => ""
}
}
}
def comments = {
val commentSelector = for {
metadata <- transaction.metadata
comments <- metadata.comments
} yield {
".comments_ext [href]" #> { "transactions/" + transaction.id.getOrElse("") + "/" + transactionsURLParams.viewId} &
".comment *" #> comments.size.toString
}
commentSelector getOrElse {".comments *" #> NodeSeq.Empty}
}
def images = {
def hideImages = ".transaction_images *" #> ""
val imagesSelector = for {
metadata <- transaction.metadata
images <- metadata.images
} yield {
if(images.nonEmpty) {
".transaction_image [src]" #> images(0).URL.getOrElse("/notfound.png") &
{
if(images(0).label.isDefined) "transaction_image [alt]" #> images(0).label
else NOOP_SELECTOR
}
} else hideImages
}
imagesSelector getOrElse hideImages
}
def tags = {
def hideTags = ".tags *" #> ""
val tagSelector = for {
metadata <- transaction.metadata
tags <- metadata.tags
} yield {
if(tags.nonEmpty) {
//only first 3 elements of the list should be displayed
val tags3 = tags.splitAt(3)._1
".tags *" #> {
".tag *" #> tags3.map(tag => {
".tagContent *" #> tag.value
})}
} else hideTags
}
tagSelector getOrElse hideTags
}
amount &
description &
narrative &
symbol &
transactionInOrOut &
comments &
images &
tags
}
transactionInformation &
otherPartyInfo
}
/*
Used in the display of the transactions list
e.g. http://localhost:8080/banks/bnpp-fr2/accounts/1137869186/public
*/
def displayAll = {
val groupedApiTransactions = groupByDate(transactionsJson.transactions.getOrElse(Nil))
"* *" #> groupedApiTransactions.map(daySummary)
}
def hasSameDate(t1: TransactionJson, t2: TransactionJson) : Boolean = {
def getDate(t : TransactionJson) : Date = (for {
details <- t.details
finishDate <- details.completed
} yield finishDate).getOrElse(now)
val date1 = getDate(t1)
val date2 = getDate(t2)
val cal1 = Calendar.getInstance();
val cal2 = Calendar.getInstance();
cal1.setTime(date1);
cal2.setTime(date2);
//True if the two dates fall on the same day of the same year
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
}
/*
Used in transactions list
*/
def groupByDate(list : List[TransactionJson]) : List[List[TransactionJson]] = {
list match {
case Nil => Nil
case h :: Nil => List(list)
case h :: t => {
//transactions that are identical to the head of the list
val matches = list.filter(hasSameDate(h, _))
List(matches) ++ groupByDate(list diff matches)
}
}
}
def daySummary(transactionsForDay: List[TransactionJson]) = {
val aTransaction = transactionsForDay.last
val date = aTransaction.details.flatMap(_.completed) match {
case Some(d) => (new SimpleDateFormat("MMMM dd, yyyy")).format(d)
case _ => ""
}
val amount : String = (for {
details <- aTransaction.details
newBalance <- details.new_balance
amt <- newBalance.amount
} yield amt).getOrElse("")
".date *" #> date &
".balance_number *" #> {currencySymbol + " " + amount} &
".transaction_row *" #> transactionsForDay.map(individualApiTransaction)
}
def accountDetails = {
//TODO: We don't have access to this information via the api (yet)
def lastUpdated = {
"#lastUpdate *" #> ""
}
def accountLabel = {
val accountTitle = getAccountTitle(accountJson)
"#accountShortDescription *" #> accountTitle
}
/* LocalStorage.getAccount(url(2), url(4)) match {
case Full(account) => "#lastUpdate *"#> {
val dateFormat = new SimpleDateFormat("kk:mm EEE MMM dd yyyy")
if(!account.lastUpdate.asString.contains("Empty"))
"Last updated : " + dateFormat.format(account.lastUpdate.get)
else
"Not yet updated"
}
case _ => NOOP_SELECTOR
}
}
}*/
accountLabel &
lastUpdated
}
def hideSocialWidgets = {
if(transactionsURLParams.viewId != "Public") "#socialButtons *" #> NodeSeq.Empty
else NOOP_SELECTOR
}
}

View File

@ -1,240 +0,0 @@
package code.snippet
import code.util.Helper._
import net.liftweb.http.js.JE.{Call, Str}
import net.liftweb.http.js.JsCmd
import net.liftweb.util.Helpers._
import scala.xml.{Node, NodeSeq, Text}
import code.lib.ObpJson.CompleteViewJson
import net.liftweb.util.CssSel
import net.liftweb.http.{S, SHtml}
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json._
import net.liftweb.http.js.JsCmds.{SetHtml, Alert, RedirectTo}
import net.liftweb.common.{Full, Loggable, Box}
import code.lib.ObpAPI
import net.liftweb.http.SHtml.{text,ajaxSubmit, ajaxButton}
import ObpAPI.{addView, deleteView, updateAccountLabel, getAccount}
import SHtml._
case class ViewUpdateData(
viewId: String,
updateJson: JValue
)
case class ViewsDataJSON(
views: List[CompleteViewJson],
bankId: String,
accountId: String
)
/*
For maintaining permissions on the views (entitlements on the account)
*/
class ViewsOverview(viewsDataJson: ViewsDataJSON) extends Loggable {
val views = viewsDataJson.views
val bankId = viewsDataJson.bankId
val accountId = viewsDataJson.accountId
def setAccountTitle = ".account_title *" #> getAccountTitle(bankId, accountId)
def getTableContent(xhtml: NodeSeq) :NodeSeq = {
//add ajax callback to save view
def saveOnClick(viewId : String): CssSel = {
implicit val formats = DefaultFormats
".save-button [data-id]" #> viewId &
".save-button [onclick+]" #> SHtml.ajaxCall(Call("collectData", Str(viewId)), callResult => {
val result: Box[Unit] = for {
data <- tryo{parse(callResult).extract[ViewUpdateData]}
response <- ObpAPI.updateView(viewsDataJson.bankId, viewsDataJson.accountId, viewId, data.updateJson)
} yield{
response
}
if(result.isDefined) {
val msg = "View " + viewId + " has been updated"
Call("socialFinanceNotifications.notify", msg).cmd
}
else {
val msg = "Error updating view"
Call("socialFinanceNotifications.notifyError", msg).cmd
}
})
}
def deleteOnClick(viewId : String): CssSel = {
".delete-button [data-id]" #> viewId &
".delete-button [onclick+]" #> SHtml.ajaxCall(Call("collectData", Str(viewId)), callResult => {
val result = ObpAPI.deleteView(viewsDataJson.bankId, viewsDataJson.accountId, viewId)
if(result) {
val msg = "View " + viewId + " has been deleted"
Call("socialFinanceNotifications.notify", msg).cmd
RedirectTo("")
}
else {
val msg = "Error deleting view"
Call("socialFinanceNotifications.notifyError", msg).cmd
}
})
}
val permissionsCollection: List[Map[String, Boolean]] = views.map(view => view.permissions)
// Use permissions from the first view as a basis for permission names.
val permissions: Map[String, Boolean] = permissionsCollection(0)
def aliasType(typeInJson : Option[String]) = {
typeInJson match {
case Some("") => "none (display real names only)"
case Some(s) => s
case _ => ""
}
}
val ids = getIds()
val viewNameSel = ".view_name *" #> views.map( view => view.shortName.getOrElse(""))
val shortNamesSel = ".short_name" #> views.map( view => "* *" #> view.shortName.getOrElse("") & "* [data-viewid]" #> view.id )
val aliasSel = ".alias" #> views.map( view => "* *" #> aliasType(view.alias) & "* [data-viewid]" #> view.id )
val descriptionSel = ".description" #> views.map( view => ".desc *" #> view.description.getOrElse("") & "* [data-viewid]" #> view.id )
val isPublicSel = ".is_public *" #> getIfIsPublic()
val addEditSel = ".edit" #> ids.map(x => "* [data-id]" #> x)
val addSaveSel = ".save" #> ids.map(x => ("* [data-id]" #> x) & deleteOnClick(x) & saveOnClick(x))
val addCancelSel = ".cancel" #> ids.map(x => "* [data-id]" #> x)
val permissionNames = permissions.keys
val permSel = ".permissions *" #>
permissionNames.map(
permName => {
".permission_name *" #> permName.capitalize.replace("_", " ") &
".permission_value *" #> getPermissionValues(permName)
}
)
(viewNameSel &
shortNamesSel &
aliasSel &
descriptionSel &
isPublicSel &
permSel &
addEditSel &
addSaveSel &
addCancelSel
).apply(xhtml)
}
def getIds(): List[String] = {
views.map( view => view.id.getOrElse(""))
}
def getIfIsPublic() :List[CssSel] = {
views.map(
view => {
val isPublic = view.isPublic.getOrElse(false)
val viewId: String = view.id.getOrElse("")
val checked =
if(isPublic)
".is_public_cb [checked]" #> "checked" &
".is_public_cb [disabled]" #> "disabled"
else
".is_public_cb [disabled]" #> "disabled"
val checkBox =
checked &
".is_public_cb [data-viewid]" #> viewId
checkBox
}
)
}
def getPermissionValues(permName: String) :List[CssSel] = {
views.map(
view => {
val permValue: Boolean = view.permissions(permName)
val viewId: String = view.id.getOrElse("")
val checked =
if(permValue){
".permission_value_cb [checked]" #> "checked" &
".permission_value_cb [disabled]" #> "disabled"
}
else
".permission_value_cb [disabled]" #> "disabled"
val checkBox =
checked &
".permission_value_cb [value]" #> permName &
".permission_value_cb [name]" #> permName &
".permission_value_cb [data-viewid]" #> viewId
checkBox
}
)
}
//set up ajax handlers to add a new view
def setupAddView(xhtml: NodeSeq): NodeSeq = {
var newViewName = ""
def process(): JsCmd = {
logger.debug(s"ViewsOverview.setupAddView.process: create view called $newViewName")
if (views.find { case (v) => v.shortName.get == newViewName }.isDefined) {
val msg = "Sorry, a View with that name already exists."
Call("socialFinanceNotifications.notifyError", msg).cmd
} else {
// This only adds the view (does not grant the current user access)
val result = addView(bankId, accountId, newViewName)
if (result.isDefined) {
val msg = "View " + newViewName + " has been created. Please use the Access tab to grant yourself or others access."
Call("socialFinanceNotifications.notify", msg).cmd
// After creation, current user does not have access so, we show message above.
// TODO: Redirect to a page where user can give him/her self access - and/or grant access automatically.
// For now, don't reload so user can see the message above // reload page for new view to be shown // RedirectTo("")
} else {
val msg = "View " + newViewName + " could not be created"
Call("socialFinanceNotifications.notifyError", msg).cmd
}
}
}
(
// Bind newViewName field to variable (e.g. http://chimera.labs.oreilly.com/books/1234000000030/ch03.html)
"@new_view_name" #> text(newViewName, s => newViewName = s) &
// Replace the type=submit with Javascript that makes the ajax call.
"type=submit" #> ajaxSubmit("OK", process)
).apply(xhtml)
}
//set up ajax handlers to edit account label
def setupEditLabel(xhtml: NodeSeq): NodeSeq = {
var newLabel = ""
def process(): JsCmd = {
logger.debug(s"ViewsOverview.setupEditLabel.process: edit label $newLabel")
val result = updateAccountLabel(bankId, accountId, newLabel)
if (result.isDefined) {
val msg = "Label " + newLabel + " has been set"
Call("socialFinanceNotifications.notify", msg).cmd
// So we can see the new account title which may use the updated label
RedirectTo("")
} else {
val msg = "Sorry, Label" + newLabel + " could not be set ("+ result +")"
Call("socialFinanceNotifications.notifyError", msg).cmd
}
}
val label = getAccount(bankId, accountId, "owner").get.label.getOrElse("Label")
(
// Bind newViewName field to variable (e.g. http://chimera.labs.oreilly.com/books/1234000000030/ch03.html)
"@new_label" #> text(newLabel, s => newLabel = s) &
// Replace the type=submit with Javascript that makes the ajax call.
"type=submit" #> ajaxSubmit("OK", process) &
"type=text [value]" #> label
).apply(xhtml)
}
}

View File

@ -1,114 +0,0 @@
<!--
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
-->
<div class="lift:surround?with=default;at=content">
<div class="lift:Management.setAccountTitle" id="account_details">
<span id="accountShortDescription" class="account_title">Account Title</span>
</div>
<div class="management">
<table id="other_acc_management" class="management tablesorter">
<thead>
<tr>
<th class="account">Account Name</th>
<th class="public">Public Alias</th>
<th class="private">Private Alias</th>
<th class="website">Website</th>
<th class="open_corporates">Open Corporates URL</th>
<th class="information">More information</th>
<th class="image">Image URL</th>
</tr>
</thead>
<tbody>
<tr class=lift:Management.showAll>
<td class="account" id="123" >Bob Smith</td>
<td class="public">
Edit interface gets bound here
</td>
<td class="private">
Edit interface gets bound here
<!-- IF NO PRIVATE ALIAS
<div class="private_not_set">
<a class="add_private add" href="#">Private alias</a>
</div>
<!-- ELSE PRIVATE ALIAS
<div class="private_set">
<a class="edit" href="#">[edit]</a> <span class="edit_private text">Bobby</span>
</div> -->
</td>
<td class="website">
Edit interface gets bound here
<!-- IF NO WEBSITE
<div class="website_not_set">
<a class="add_website add" href="#">Website</a>
</div>
<!-- ELSE WEBSITE
<div class="website_set">
<a class="edit" href="#">[edit]</a> <span class="edit_website text">http://openbankproject.com</span>
</div> -->
</td>
<td class="open_corporates">
Edit interface gets bound here
<!-- IF NO OPEN CORPORATES
<div class="open_corporates_not_set">
<a class="add_open_corporates add" href="#">Open Corporates URL</a>
</div>
<!-- ELSE OPEN CORPORATES
<div class="open_corporates_set">
<a class="edit" href="#">[edit]</a> <span class="edit_open_corporates text">http://opencorporates.com</span>
</div> -->
</td>
<td class="information">
Edit interface gets bound here
<!-- IF NO INFORMATION
<div class="information_not_set">
<a class="add_information add" href="#">Information</a>
</div>
<!-- ELSE INFORMATION
<div class="information_set">
<a class="edit" href="#">[edit]</a> <span class="edit_information text">Blah blah</span>
</div> -->
</td>
<td class="imageURL">
Edit interface gets bound here
<!-- IF NO IMAGE
<a class="add_image upload" href="#">Upload image</a>
<!-- ELSE IMAGE
<span class="upload"><img class="edit_image" src="#" alt="Image" /></span> -->
</td>
</tr>
</tbody>
</table>
<div class="lift:Management.tableSorter"></div>
</div>
</div>

View File

@ -1,60 +0,0 @@
<!--
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
-->
<div class="lift:surround?with=default;at=content">
<div class="lift:PermissionManagement.accountInfo" id="account_details">
<span id="accountShortDescription" class="account-label">Account</span>
</div>
<div class="lift:PermissionManagement.manage">
<div class="callback-script"></div>
<table>
<tr class="lift:PermissionManagement.accountViewHeaders">
<th>User</th>
<th class="view_name">View Name</th>
<th></th>
</tr>
<tr class="row">
<td><span class="user">bob@example.com</span></td>
<td class="view-checkbox"><input type="checkbox" class="check"></td>
<td><input class="remove" type="button" value="Remove"></td>
</tr>
</table>
<script type="text/javascript">
$('.remove').click(function () {
$(this).closest('.row').css('background-color', '#CC0000').fadeOut(1500, function() {$(this).remove();});
});
</script>
</div>
<a id="add-permission" href="#" class="lift:PermissionManagement.addPermissionLink">Grant access to another user... </a>
</div>

View File

@ -1,64 +0,0 @@
<!--
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
-->
<div class="lift:surround?with=default;at=content">
<!-- <div class="lift:CreatePermissionForm.accountInfo">Add Permission to <span class="account-label">Account</span></div> -->
<div id="create-permission-message"></div>
<form method="post" class="lift:form.ajax">
<table class="lift:CreatePermissionForm create_class" id="create_page">
<tbody>
<tr>
<td>
<span>User email</span>
</td>
<td>
<input name="email" type="text" value="">
</td>
</tr>
<tr class="view_row">
<td>
<span class="view_name">View name</span>
</td>
<td>
<input class="view_check" value="true" type="checkbox">
</td>
</tr>
<tr>
<td></td><td><input class="create-permission" type="submit" value="Create"></td>
</tr>
</tbody></table>
</form>
</div>

View File

@ -1,130 +0,0 @@
<!--
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
-->
<!--
This renders the transaction list.
e.g. http://localhost:8080/banks/bnpp-fr2/accounts/1137869186/owner
-->
<div class="lift:surround?with=default;at=content">
<div id="account_details" class="lift:OBPTransactionSnippet.accountDetails">
<span id="accountShortDescription">Account Name</span>
<span id="lastUpdate"></span>
</div>
<div id="social">
<div class="lift:OBPTransactionSnippet.hideSocialWidgets">
<div id="socialButtons">
<!-- Facebook -->
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<div class="fb-like" data-href="https://sofi.openbankproject.com" data-send="false" data-layout="button_count" data-width="60"data-show-faces="false"></div>
<!-- Twitter -->
<a href="https://twitter.com/share" class="twitter-share-button" data-via="openbankproject" data-hashtags="openbankproject">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
</script>
<!-- Google Plus -->
<script class="lift:head" type="text/javascript" src="https://apis.google.com/js/plusone.js">
{lang: 'en-GB'}
</script>
<g:plusone size="medium" href="https://sofi.openbankproject.com"></g:plusone>
</div>
</div>
</div>
<div class="lift:OBPTransactionSnippet.displayAll">
<table class="transactions">
<thead>
<tr>
<th colspan="9">
<h5 class="date">March 10, 2012</h5>
<h6 class="balance">
<span class="text">Balance</span>
<span class="number balance_number">€2304.42</span>
</h6>
</th>
</tr>
</thead>
<tbody>
<tr class="transaction_row">
<td class="icon"><div class="out">Out</div></td>
<td class="symbol">-</td>
<td class="amount">€24.32</td>
<td class="name">
<span class="the_name">
<a class="otherAccountLinkForName" href="#">Ronald Montgomery</a>
</span>
<span class="alias_indicator">
<a class="otherAccountLinkForAlias" href="#"></a>
</span>
</td>
<td class="description">description</td>
<td class="narrative">Transaction specific notes</td>
<td class="transaction_images">
<img class="transaction_image" src="/media/images/blank.gif" alt="transaction-image">
</td>
<td class="tags">
<span class="tag">
<span class="tagContent"></span><br/>
</span>
</td>
<td class="extra">
<div class="extra_account_logo">
<a href="#" class="other_acc_link">
<img src="/media/images/blank.gif" class="other_account_logo_img" />
</a>
</div>
<div class="extra_info_and_links">
<span class="other_account_more_info">More info</span>
<br class="other_account_more_info_br"/>
<a href="#" class="open_corporates_link">Open Corporates</a>
</div>
</td>
<td class="comments">
<a href="#" class="comments_ext">
<span class="comment">0</span>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,120 +0,0 @@
<!--
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
-->
<!--
This renders the transaction "detail":
http://localhost:8080/banks/bnpp-fr/accounts/1137869186/transactions/transaction44/owner
-->
<div id="main" class="lift:surround?with=default;at=content">
<table>
<thead>
<tr>
<th class="span-1">Cleared</th>
<th class="span-1">Amount</th>
<th class="span-1">Counterparty</th>
<th class="span-1">Reference</th>
<th class="span-1">Narrative</th>
<th class="span-1">Balance</th>
</tr>
</thead>
<tbody>
<tr class=lift:Comments.commentPageTitle>
<td class="date_cleared">completed date</td>
<td class="amount">obp_transaction_amount</td>
<td class="other_account_holder">
<span class="the_name">Ronald Montgomery</span>
<span class="alias_indicator"></span>
</td>
<td class="description">description aka reference aka label</td>
<td class="narrative">narrative</td>
<td class="new_balance">obp_transaction_new_balance</td>
</tr>
</tbody>
</table>
<p id="tagsBloc">
<div id="tagsTitle">Tags:</div>
<div id="tags_list" class="lift:Comments.showTags?eager_eval=true tagsContainer">
<span id="noTags"></span>
<div class="lift:embed?what=_tag"></div>
</div>
<div id="addTagbloc">
<div class="lift:Comments.addTag" >
<span class="add">Add a Tag here</span>
</div>
</div>
</p>
<div id="imagesBlock" class="lift:Comments.images?eager_eval=true">
<div class="imagesTitle">Images:</div>
<div class="images_list">
<span class="noImages"></span>
<div class="image-holder">
<div class="lift:embed?what=_transactionImage"></div>
</div>
</div>
<div id="addImageBlock">
<div class="add">
<form id="imageUploader" method="post" enctype="multipart/form-data">
<span>Add a new image: </span>
<input class="chooseFile" type="file" name="my_file"></input>
<br />
<textarea class="description" rows="4" cols="50" name="description" placeholder="A short description of the image being posted."></textarea>
<input type="hidden" name="params"></input>
<br />
<input class="addImageSubmit" type="submit" value="Add Image"></input>
</form>
<script type="text/javascript" src="https://assets.transloadit.com/js/jquery.transloadit2.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#imageUploader').transloadit({
wait: true
});
});
</script>
</div>
</div>
</div>
<p id="commentsBloc">
<div id="commentsTitle">Comments:</div>
<ul id="comment_list" class="lift:Comments.showComments?eager_eval=true commentsContainer">
<span id="noComments"></span>
<div class="lift:embed?what=_comment"></div>
</ul>
<div id="addCommentbloc">
<div class="lift:Comments.addComment" >
<span class="add">Add a comment here</span>
</div>
</div>
</p>
</div>

View File

@ -1,108 +0,0 @@
<!--
Open Bank Project - Transparency / Social Finance Web Application
Copyright (C) 2011, 2012, TESOBE / Music Pictures Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE / Music Pictures Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
-->
<!--
This renders the entitlements / permissions page for an account.
e.g. http://localhost:8080/banks/bnpparibas-fr/accounts/1219682756/views/list
See views.js for the javascript which manipulates the DOM for editing
-->
<div class="lift:surround?with=default;at=content">
<div class="lift:ViewsOverview.setAccountTitle" id="account_details">
<span id="accountShortDescription" class="account_title">Account Title</span>
</div>
<div class="views">
<div class="action-bar">
<button id="view-add">Add new view</button>
<div id="add-view-form" class="lift:ViewsOverview.setupAddView">
<form class="lift:form.ajax">
<input type="text" name="new_view_name" placeholder="View name">
<input type="submit" value="placeholder">
</form>
</div>
<button id="account-edit-label">Change account label</button>
<div id="account-edit-label-form" class="lift:ViewsOverview.setupEditLabel">
<form class="lift:form.ajax">
<input type="text" name="new_label" placeholder="Label">
<input type="submit" value="placeholder">
</form>
</div>
<button id="view-edit-advanced-options">Hide advanced options</button>
</div>
<div class="lift:ViewsOverview.getTableContent">
<table id="view_table">
<thead>
<tr class="view_head">
<th class="view_permissions"></th>
<th class="view_name" id="123">View name</th>
</tr>
</thead>
<tbody>
<tr class="edit_head">
<td></td>
<td class="edit action"><button>Edit</button></td>
</tr>
<tr class="save_head">
<td></td>
<td class="save">
<button class="save-button action">Save</button>
<button class="delete-button action">Delete</button>
<button class="cancel action">Cancel</button>
</td>
<td></td>
</tr>
<!-- <tr class="short_name_head">
<td>View</td>
<td class="short_name">View short name</td>
</tr> -->
<tr class="is_public_head">
<td class="main-attributes">Visible to anyone (public)</td>
<td class="is_public"><input type="checkbox" class="is_public_cb" name="is_public" value="is_public"></td>
</tr>
<tr class="description_head advanced-option">
<td>Description</td>
<td class="description"><span class="desc">description</span><input class="desc_input" type="text" value=""></td>
</tr>
<tr class="alias_head advanced-option">
<td>Alias</td>
<td class="alias"></td>
</tr>
<tr class="permissions advanced-option">
<td class="permission_name">Permission name</td>
<td class="permission_value"><input type="checkbox" class="permission_value_cb" name="name" value="id"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -1,84 +0,0 @@
<div id="main" class="lift:surround?with=default&at=content">
<h1>Open Bank Project API Explorer</h1>
<div class="lift:ApiExplorer.showResources">
<span id="version">API Version: </span>
<div class="lift:Msgs"></div>
<div id="selectors">
<dif id="introduction">
Use the API in the context of your login. Use the select boxes below to auto populate url parameters such as BANK_ID, ACCOUNT_ID and VIEW_ID.
</dif>
<div id="login_status_message">
Logged In Status
</div>
<br />
<select id="bank_selector">
<option value="bank_id1">Bank 1</option>
<option value="bank_id2">Bank 2</option>
</select>
<select id="account_selector">
<option value="account_id1">Account 1</option>
<option value="account_id2">Account 2</option>
</select>
<select id="view_selector">
<option value="view_id_1">Owner</option>
<option value="view_id_2">Accountant</option>
</select>
<select id="counterparty_selector">
<option value="other_account_id_1">Counterparty 1</option>
<option value="other_account_id_2">Counterparty 2</option>
</select>
<select id="transaction_selector">
<option value="transaction_id_1">Transaction 1</option>
<option value="transaction_id_2">Transaction 2</option>
</select>
</div>
<table>
<!-- Each URL we document appears in a row, inside a table -->
<!-- Multiple instances of this row are generated in ApiExplorer.scala -->
<!-- Note: id's are added so we can manipulate individual elements -->
<tr class="resource">
<!-- Some documentation -->
<td>
<a class="resource_summary" href="#replace-me">Summary</a>
<div name="resource_description" class="markdown"><span name="description_text">Description</span></div>
</td>
<td>
<form class="lift:form.ajax" id="form1">
<!-- Attributes for these fields are replaced -->
<input type="hidden" name="resource_id_input">
<input type="hidden" name="request_verb_input" placeholder="GET, POST, PUT etc.">
<!-- The url_to_call is populated at render (but it can be edited manually) -->
<input type="hidden" name="request_url_input" placeholder="OBP URL to call">
<div name="example_request_body" style="display: block;">
<input name="example_request_body_input" placeholder="JSON body to POST or PUT">
<!-- TODO get this working: <textarea name="request_body_input" form="form1">Enter text here...</textarea> -->
</div>
<!-- This submit button is also modified by ApiExplorer.scala -->
<input class="call_button" type="submit">
</form>
<!-- API call result goes here It is highlighted by javascript called from the submit button -->
<pre>
<code class="result json" id="result" name="response_body">
</code>
</pre>
</td>
</tr>
</table>
</div>
</div>

View File

@ -28,92 +28,6 @@ along with this program. If not, see www.gnu.org/licenses/
<!--
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title class="lift:Menu.title">Open Bank Project: %*%</title>
<link href="/media/css/website.css" rel="stylesheet" type="text/css" />
<script id="jquery" src="/media/js/jquery-1.11.1.min.js" type="text/javascript"></script>
<script src="/media/js/website.js" type="text/javascript"></script>
<script src="/media/js/views.js" type="text/javascript"></script>
<script src="/media/js/toastr.min.js"></script>
<link href="/media/css/toastr.min.css" rel="stylesheet"/>
<script src="/media/js/notifications.js" type="text/javascript"></script>
<link rel="stylesheet" href="/media/css/highlight.js.min.css">
<script src="/media/js/highlight.min.js"></script>
</head>
<body>
<div id="wrapper">
<div id="header">
<h1 id="logo" onclick="javascript:window.location='/';">Open Bank Project</h1>
<div id="account">
<div class="lift:Login.login">
<div class="logged-out">
<a class="start-login" href="#">Login</a>
</div>
<div class="logged-in">
<div class="profile-info">
<span class="logout">Logout</span>
</div>
</div>
</div>
</div>
</div>
<div id="nav">
<ul>
<li class="lift:Nav.item?name=Home navitem">
<a class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</li>
<li class="lift:ConnectBankAccount.connect navitem">
<a class="navlink" href="#">Connect your Bank Account</a>
</li>
<li class="lift:Nav.management navitem">
<a class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</li>
<li class="lift:Nav.editViews navitem">
<a class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</li>
<li class="lift:Nav.privilegeAdmin navitem">
<a class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</li>
<span class="lift:Nav.views">
<li class="navitem">
<a class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</li>
</span>
</ul>
</div>
<div id="content">
<lift:bind name="content" />
The main content gets bound here
</div>
<div id="footer">
<p>
<a href="http://openbankproject.com">Open Bank Project</a> is &copy;2011-
<span class="lift:TimeHelpers.currentYear"><span id="current_year">CurrentYear</span></span>
<a href="http://tesobe.com">TESOBE</a>
</p>
<br/>
<p>
<a href="http://twitter.com/#!/OpenBankProject">Twitter</a> |
<a href="https://github.com/OpenBankProject/API-Explorer">Github</a>
</p>
</div>
</div>
</body>
</html>
-->
<!doctype html>
<html>
@ -123,10 +37,6 @@ along with this program. If not, see www.gnu.org/licenses/
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./media/css/style.css" />
<!-- Used for login ? -->
<!-- <link rel="stylesheet" href="./media/css/reset.css" /> -->
<script id="jquery" src="/media/js/jquery-1.11.1.min.js" type="text/javascript"></script>
<script src="/media/js/notifications.js" type="text/javascript"></script>