Sammy “#main”, (app) ->
@.use Sammy.Tmpl @.use Sammy.Session @.use Sammy.Title @.setTitle "OAuth Admin - " @.use Sammy.OAuth2 @.authorize = document.location.pathname + "/authorize" # All XHR errors we don't catch explicitly handled here $(document).ajaxError (evt, xhr)-> if xhr.status == 401 app.loseAccessToken() app.trigger("notice", xhr.responseText) # Show something when XHR request in progress $(document).ajaxStart (evt)-> $("#throbber").show() $(document).ajaxStop (evt)-> $("#throbber").hide() @.requireOAuth() # Show error message if access denied @bind "oauth.denied", (evt, error)-> app.partial("admin/views/no_access.tmpl", { error: error.message }) # Show signout link if authenticated, hide if not @.bind "oauth.connected", ()-> $("#header .signin").hide() $("#header .signout").show() @.bind "oauth.disconnected", ()-> $("#header .signin").show() $("#header .signout").hide() api = "#{document.location.pathname}/api" # Takes array of string with scope names (typically request parameters) and # normalizes them into an array of scope names. mergeScope = (scope) -> if $.isArray(scope) scope = scope.join(" ") scope = (scope || "").trim().split(/\s+/) if scope.length == 1 && scope[0] == "" then [] else _.uniq(scope).sort() commonScope = null # Run callback with list of common scopes. First time, make an API call to # retrieve that list and cache is in memory, since it rarely changes. withCommonScope = (cb) -> if commonScope cb commonScope else $.getJSON "#{api}/clients", (json)-> cb(commonScope = json.scope) # View all clients @.get "#/", (context)-> context.title "All Clients" $.getJSON "#{api}/clients", (clients)-> commonScope = clients.scope context.partial("admin/views/clients.tmpl", { clients: clients.list, tokens: clients.tokens }). load(clients.history). then( (json)-> $("#fig").chart(json.data, "granted") ) # View single client @.get "#/client/:id", (context)-> $.getJSON "#{api}/client/#{context.params.id}", (client)-> context.title client.displayName client.notes = (client.notes || "").split(/\n\n/) context.partial("admin/views/client.tmpl", client). load(client.history).then((json)-> $("#fig").chart(json.data, "granted")) # With pagination @.get "#/client/:id/page/:page", (context)-> $.getJSON "#{api}/client/#{context.params.id}?page=#{context.params.page}", (client)-> context.title client.displayName client.notes = client.notes.split(/\n\n/) context.partial("admin/views/client.tmpl", client). load(client.history).then((json)-> $("#fig").chart(json.data, "granted")) # Revoke token @.post "#/token/:id/revoke", (context)-> $.post "#{api}/token/#{context.params.id}/revoke", ()-> context.redirect "#/" # Edit client @.get "#/client/:id/edit", (context)-> $.getJSON "#{api}/client/#{context.params.id}", (client)-> context.title client.displayName withCommonScope (scope)-> client.common = scope context.partial "admin/views/edit.tmpl", client @.put "#/client/:id", (context)-> context.params.scope = mergeScope(context.params.scope) $.ajax type: "put" url: "#{api}/client/#{context.params.id}" data: displayName: context.params.displayName link: context.params.link imageUrl: context.params.imageUrl redirectUri: context.params.redirectUri notes: context.params.notes scope: context.params.scope success: (client)-> context.redirect "#/client/#{context.params.id}" app.trigger "notice", "Saved your changes" error: (xhr)-> withCommonScope (scope)-> context.params.common = scope context.partial "admin/views/edit.tmpl", context.params # Delete client @.del "#/client/:id", (context)-> $.ajax type: "post" url: "#{api}/client/#{context.params.id}" data: { _method: "delete" } success: ()-> context.redirect("#/") # Revoke client @.post "#/client/:id/revoke", (context)-> $.post "#{api}/client/#{context.params.id}/revoke", ()-> context.redirect "#/" # Create new client @.get "#/new", (context)-> context.title "Add New Client" withCommonScope (scope)-> context.partial "admin/views/edit.tmpl", { common: scope, scope: scope } @.post "#/clients", (context)-> context.title "Add New Client" context.params.scope = mergeScope(context.params.scope) $.ajax type: "post" url: "#{api}/clients" data: displayName: context.params.displayName link: context.params.link imageUrl: context.params.imageUrl redirectUri: context.params.redirectUri notes: context.params.notes scope: context.params.scope success: (client)-> app.trigger "notice", "Added new client application #{client.displayName}" context.redirect "#/" error: (xhr)-> withCommonScope (scope)-> context.params.common = scope context.partial "admin/views/edit.tmpl", context.params # Signout @.get "#/signout", (context)-> context.loseAccessToken() context.redirect "#/" # Links that use forms for various methods (i.e. post, delete). $("a[data-method]").live "click", (evt)-> evt.preventDefault link = $(this) if link.attr("data-confirm") && !confirm(link.attr("data-confirm")) return false method = link.attr("data-method") || "get" form = $("<form>", { style: "display:none", method: method, action: link.attr("href") }) if method != "get" && method != "post" form.append($("<input name='_method' type='hidden' value='#{method}'>")) app.$element().append form form.submit() false # Error/notice at top of screen noticeTimeout = null app.bind "notice", (evt, message)-> if !message || message.trim() == "" message = "Got an error, but don't know why" $("#notice").text(message).fadeIn("fast") if noticeTimeout clearTimeout noticeTimeout noticeTimeout = null noticeTimeout = setTimeout ()-> noticeTimeout = null $("#notice").fadeOut("slow") , 5000 $("#notice").live "click", ()-> $(@).fadeOut("slow")
# Adds thousands separator to integer or float (can also pass formatted string # if you care about precision). $.thousands = (integer)->
integer.toString().replace(/^(\d+?)((\d{3})+)$/g, (x,a,b)-> a + b.replace(/(\d{3})/g, ",$1") ). replace(/\.((\d{3})+)(\d+)$/g, (x,a,b,c)-> "." + a.replace(/(\d{3})/g, "$1,") + c )
# Returns abbr element with short form of the date (e.g. “Nov 21 2010”). THe # title attribute provides the full date/time instance, so you can see more # details by hovering over the element. $.shortdate = (integer)->
date = new Date(integer * 1000) "<abbr title='#{date.toLocaleString()}'>#{date.toDateString().substring(0,10)}</abbr>"
# Draw chart inside the specified container element. # data – Array of objects, each one having a timestamp (ts) and some value we # want to chart # series – Name of the value we want to chart $.fn.chart = (data, series)->
return if typeof pv == "undefined" # no PV in test environment canvas = $(@) w = canvas.width() h = canvas.height() today = Math.floor(new Date() / 86400000) x = pv.Scale.linear(today - 60, today + 1).range(0, w) max = pv.max(data, (d)-> d[series]) y = pv.Scale.linear(0, pv.max([max, 10])).range(0, h) # The root panel. vis = new pv.Panel().width(w).height(h).bottom(20).left(20).right(10).top(5) # X-axis ticks. vis.add(pv.Rule).data(x.ticks()).left(x).strokeStyle("#fff"). add(pv.Rule).bottom(-5).height(5).strokeStyle("#000"). anchor("bottom").add(pv.Label).text((d)-> pv.Format.date("%b %d").format(new Date(d * 86400000)) ) # Y-axis ticks. vis.add(pv.Rule).data(y.ticks(3)).bottom(y).strokeStyle((d)-> if d then "#ddd" else "#000"). anchor("left").add(pv.Label).text(y.tickFormat) # If we only have one data point, can't show a line so show dot instead if data.length == 1 vis.add(pv.Dot).data(data). left((d)-> x(new Date(d.ts)) ).bottom((d)-> y(d[series])).radius(3).lineWidth(2) else vis.add(pv.Line).data(data).interpolate("linear"). left((d)-> x(new Date(d.ts)) ).bottom((d)-> y(d[series]) ).lineWidth(3) vis.canvas(canvas[0]).render()