rack-oauth2-server 2.0.0.beta5 → 2.0.0.beta6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -2,8 +2,17 @@ require "rake/testtask"
2
2
 
3
3
  spec = Gem::Specification.load(Dir["*.gemspec"].first)
4
4
 
5
+ desc "Run this in development mode when updating the CoffeeScript file"
6
+ task :coffee do
7
+ sh "coffee -w -o lib/rack/oauth2/admin/js/ lib/rack/oauth2/admin/js/application.coffee"
8
+ end
9
+
10
+ task :compile do
11
+ sh "coffee -c -l -o lib/rack/oauth2/admin/js/ lib/rack/oauth2/admin/js/application.coffee"
12
+ end
13
+
5
14
  desc "Build the Gem"
6
- task :build do
15
+ task :build=>:compile do
7
16
  sh "gem build #{spec.name}.gemspec"
8
17
  end
9
18
 
@@ -37,6 +46,7 @@ Rake::TestTask.new do |task|
37
46
  task.ruby_opts << "-I."
38
47
  end
39
48
 
49
+ RUBIES = %w{1.8.7 1.9.2}
40
50
  namespace :test do
41
51
  task :all=>["test:sinatra", "test:rails2", "test:rails3"]
42
52
  desc "Run all tests against Sinatra"
@@ -55,6 +65,15 @@ namespace :test do
55
65
  task :rails3 do
56
66
  sh "env BUNDLE_GEMFILE=Rails3 bundle exec rake test FRAMEWORK=rails"
57
67
  end
68
+
69
+ desc "Test in all supported RVMs"
70
+ task :rubies do
71
+ RUBIES.each do |ruby|
72
+ puts "*** #{ruby} ***"
73
+ sh "rvm #{ruby}@rack-oauth2-server rake test:all"
74
+ puts
75
+ end
76
+ end
58
77
  end
59
78
  task :default=>"test:all"
60
79
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.beta5
1
+ 2.0.0.beta6
data/bin/oauth2-server CHANGED
@@ -57,6 +57,7 @@ when "setup"
57
57
  fail "No database. Use the --db option to tell us which database to use" unless Server.database
58
58
  puts "Where would you mount the Web console? This is a URL that must end with /admin,"
59
59
  puts "for example, http://example.com/oauth/admin"
60
+ print ": "
60
61
  uri = URI.parse($stdin.gets)
61
62
  begin
62
63
  uri.normalize!
@@ -143,6 +144,26 @@ when "practice"
143
144
  map("/oauth/admin") { run Server::Admin.new }
144
145
  end.start
145
146
 
147
+ when "migrate"
148
+
149
+ fail "No database. Use the --db option to tell us which database to use" unless Server.database
150
+ puts "Set all clients to this scope (can change later by calling Client.register):"
151
+ print ": "
152
+ scope = $stdin.gets.strip.split
153
+ puts "Updating Client scope to #{scope.join(", ")}"
154
+ Server::Client.collection.find({ :scope=>{ :$exists=>false } }, :fields=>[]).each do |client|
155
+ update = { :scope=>scope,
156
+ :tokens_granted=>Server::AccessToken.count(:client_id=>client["_id"]),
157
+ :tokens_revoked=>Server::AccessToken.count(:client_id=>client["_id"], :revoked=>true) }
158
+ Server::Client.collection.update({ :_id=>client["_id"] }, { :$set=>update })
159
+ end
160
+ [Server::AccessToken, Server::AccessGrant, Server::AuthRequest].each do |mod|
161
+ puts "Updating #{mod.name} scope from string to array"
162
+ mod.collection.find({ :scope=>{ :$type=>2 } }, :fields=>[]).each do |token|
163
+ scope = token["scope"].split
164
+ mod.collection.update({ :_id=>token["_id"] }, { :$set=>{ :scope=>scope } })
165
+ end
166
+ end
146
167
  else
147
168
 
148
169
  print <<-TEXT
@@ -151,6 +172,7 @@ Version #{Server::VERSION}
151
172
 
152
173
  Commands:
153
174
  list Lists all active clients
175
+ migrate Run this when migrating from 1.x to 2.x
154
176
  practice Runs a dummy OAuth 2.0 server, use this to test your OAuth 2.0 client
155
177
  register Register a new client application
156
178
  setup Create new admin account and help you setup the OAuth Web console
@@ -114,7 +114,7 @@ button:active { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(0
114
114
  height: 32px;
115
115
  vertical-align: bottom;
116
116
  }
117
- #header .signout {
117
+ #header .signout, #header .signin {
118
118
  color: #C8E9F3;
119
119
  float: right;
120
120
  font-size: 11pt;
@@ -161,7 +161,7 @@ table td {
161
161
  table tr:hover td {
162
162
  background: #ddf;
163
163
  }
164
- table td.created, table td.revoke {
164
+ table td.created, table td.revoke, table td.accessed {
165
165
  width: 6em;
166
166
  }
167
167
  table tr.revoked td, table tr.revoked a {
@@ -209,6 +209,7 @@ table.tokens td.token {
209
209
  width: 36em;
210
210
  }
211
211
  table.tokens td.scope {
212
+ float: none;
212
213
  }
213
214
 
214
215
  .pagination {
@@ -0,0 +1,219 @@
1
+ Sammy "#main", (app) ->
2
+ @.use Sammy.Tmpl
3
+ @.use Sammy.Session
4
+ @.use Sammy.Title
5
+ @.setTitle "OAuth Admin - "
6
+ @.use Sammy.OAuth2
7
+ @.authorize = "#{document.location.pathname}/authorize"
8
+
9
+ # All XHR errors we don't catch explicitly handled here
10
+ $(document).ajaxError (evt, xhr)->
11
+ if xhr.status == 401
12
+ app.loseAccessToken()
13
+ app.trigger("notice", xhr.responseText)
14
+ # Show something when XHR request in progress
15
+ $(document).ajaxStart (evt)-> $("#throbber").show()
16
+ $(document).ajaxStop (evt)-> $("#throbber").hide()
17
+
18
+ @.requireOAuth()
19
+ # Show error message if access denied
20
+ @bind "oauth.denied", (evt, error)->
21
+ app.partial("admin/views/no_access.tmpl", { error: error.message })
22
+ # Show signout link if authenticated, hide if not
23
+ @.bind "oauth.connected", ()->
24
+ $("#header .signin").hide()
25
+ $("#header .signout").show()
26
+ @.bind "oauth.disconnected", ()->
27
+ $("#header .signin").show()
28
+ $("#header .signout").hide()
29
+
30
+ api = "#{document.location.pathname}/api"
31
+ # Takes array of string with scope names (typically request parameters) and
32
+ # normalizes them into an array of scope names.
33
+ mergeScope = (scope) ->
34
+ if $.isArray(scope)
35
+ scope = scope.join(" ")
36
+ scope = (scope || "").trim().split(/\s+/)
37
+ if scope.length == 1 && scope[0] == "" then [] else _.uniq(scope).sort()
38
+ commonScope = null
39
+ # Run callback with list of common scopes. First time, make an API call to
40
+ # retrieve that list and cache is in memory, since it rarely changes.
41
+ withCommonScope = (cb) ->
42
+ if commonScope
43
+ cb commonScope
44
+ else
45
+ $.getJSON "#{api}/clients", (json)-> cb(commonScope = json.scope)
46
+
47
+ # View all clients
48
+ @.get "#/", (context)->
49
+ context.title "All Clients"
50
+ $.getJSON "#{api}/clients", (clients)->
51
+ commonScope = clients.scope
52
+ context.partial("admin/views/clients.tmpl", { clients: clients.list, tokens: clients.tokens }).
53
+ load(clients.history).
54
+ then( (json)-> $("#fig").chart(json.data, "granted") )
55
+
56
+ # View single client
57
+ @.get "#/client/:id", (context)->
58
+ $.getJSON "#{api}/client/#{context.params.id}", (client)->
59
+ context.title client.displayName
60
+ client.notes = (client.notes || "").split(/\n\n/)
61
+ context.partial("admin/views/client.tmpl", client).
62
+ load(client.history).then((json)-> $("#fig").chart(json.data, "granted"))
63
+ # With pagination
64
+ @.get "#/client/:id/page/:page", (context)->
65
+ $.getJSON "#{api}/client/#{context.params.id}?page=#{context.params.page}", (client)->
66
+ context.title client.displayName
67
+ client.notes = client.notes.split(/\n\n/)
68
+ context.partial("admin/views/client.tmpl", client).
69
+ load(client.history).then((json)-> $("#fig").chart(json.data, "granted"))
70
+
71
+ # Revoke token
72
+ @.post "#/token/:id/revoke", (context)->
73
+ $.post "#{api}/token/#{context.params.id}/revoke", ()->
74
+ context.redirect "#/"
75
+
76
+ # Edit client
77
+ @.get "#/client/:id/edit", (context)->
78
+ $.getJSON "#{api}/client/#{context.params.id}", (client)->
79
+ context.title client.displayName
80
+ withCommonScope (scope)->
81
+ client.common = scope
82
+ context.partial "admin/views/edit.tmpl", client
83
+ @.put "#/client/:id", (context)->
84
+ context.params.scope = mergeScope(context.params.scope)
85
+ $.ajax
86
+ type: "put"
87
+ url: "#{api}/client/#{context.params.id}"
88
+ data:
89
+ displayName: context.params.displayName
90
+ link: context.params.link
91
+ imageUrl: context.params.imageUrl
92
+ redirectUri: context.params.redirectUri
93
+ notes: context.params.notes
94
+ scope: context.params.scope
95
+ success: (client)->
96
+ context.redirect "#/client/#{context.params.id}"
97
+ app.trigger "notice", "Saved your changes"
98
+ error: (xhr)->
99
+ withCommonScope (scope)->
100
+ context.params.common = scope
101
+ context.partial "admin/views/edit.tmpl", context.params
102
+
103
+ # Delete client
104
+ @.del "#/client/:id", (context)->
105
+ $.ajax
106
+ type: "post"
107
+ url: "#{api}/client/#{context.params.id}"
108
+ data: { _method: "delete" }
109
+ success: ()-> context.redirect("#/")
110
+
111
+ # Revoke client
112
+ @.post "#/client/:id/revoke", (context)->
113
+ $.post "#{api}/client/#{context.params.id}/revoke", ()->
114
+ context.redirect "#/"
115
+
116
+ # Create new client
117
+ @.get "#/new", (context)->
118
+ context.title "Add New Client"
119
+ withCommonScope (scope)->
120
+ context.partial "admin/views/edit.tmpl", { common: scope, scope: scope }
121
+ @.post "#/clients", (context)->
122
+ context.title "Add New Client"
123
+ context.params.scope = mergeScope(context.params.scope)
124
+ $.ajax
125
+ type: "post"
126
+ url: "#{api}/clients"
127
+ data:
128
+ displayName: context.params.displayName
129
+ link: context.params.link
130
+ imageUrl: context.params.imageUrl
131
+ redirectUri: context.params.redirectUri
132
+ notes: context.params.notes
133
+ scope: context.params.scope
134
+ success: (client)->
135
+ app.trigger "notice", "Added new client application #{client.displayName}"
136
+ context.redirect "#/"
137
+ error: (xhr)->
138
+ withCommonScope (scope)->
139
+ context.params.common = scope
140
+ context.partial "admin/views/edit.tmpl", context.params
141
+
142
+ # Signout
143
+ @.get "#/signout", (context)->
144
+ context.loseAccessToken()
145
+ context.redirect "#/"
146
+
147
+ # Links that use forms for various methods (i.e. post, delete).
148
+ $("a[data-method]").live "click", (evt)->
149
+ evt.preventDefault
150
+ link = $(this)
151
+ if link.attr("data-confirm") && !confirm(link.attr("data-confirm"))
152
+ return false
153
+ method = link.attr("data-method") || "get"
154
+ form = $("<form>", { style: "display:none", method: method, action: link.attr("href") })
155
+ if method != "get" && method != "post"
156
+ form.append($("<input name='_method' type='hidden' value='#{method}'>"))
157
+ app.$element().append form
158
+ form.submit()
159
+ false
160
+
161
+ # Error/notice at top of screen
162
+ noticeTimeout = null
163
+ app.bind "notice", (evt, message)->
164
+ if !message || message.trim() == ""
165
+ message = "Got an error, but don't know why"
166
+ $("#notice").text(message).fadeIn("fast")
167
+ if noticeTimeout
168
+ clearTimeout noticeTimeout
169
+ noticeTimeout = null
170
+ noticeTimeout = setTimeout ()->
171
+ noticeTimeout = null
172
+ $("#notice").fadeOut("slow")
173
+ , 5000
174
+ $("#notice").live "click", ()-> $(@).fadeOut("slow")
175
+
176
+
177
+ # Adds thousands separator to integer or float (can also pass formatted string
178
+ # if you care about precision).
179
+ $.thousands = (integer)->
180
+ integer.toString().replace(/^(\d+?)((\d{3})+)$/g, (x,a,b)-> a + b.replace(/(\d{3})/g, ",$1") ).
181
+ replace(/\.((\d{3})+)(\d+)$/g, (x,a,b,c)-> "." + a.replace(/(\d{3})/g, "$1,") + c )
182
+
183
+ # Returns abbr element with short form of the date (e.g. "Nov 21 2010"). THe
184
+ # title attribute provides the full date/time instance, so you can see more
185
+ # details by hovering over the element.
186
+ $.shortdate = (integer)->
187
+ date = new Date(integer * 1000)
188
+ "<abbr title='#{date.toLocaleString()}'>#{date.toDateString().substring(0,10)}</abbr>"
189
+
190
+ # Draw chart inside the specified container element.
191
+ # data -- Array of objects, each one having a timestamp (ts) and some value we
192
+ # want to chart
193
+ # series -- Name of the value we want to chart
194
+ $.fn.chart = (data, series)->
195
+ canvas = $(@)
196
+ w = canvas.width()
197
+ h = canvas.height()
198
+ today = Math.floor(new Date() / 86400000)
199
+ x = pv.Scale.linear(today - 60, today + 1).range(0, w)
200
+ max = pv.max(data, (d)-> d[series])
201
+ y = pv.Scale.linear(0, pv.max([max, 10])).range(0, h)
202
+
203
+ # The root panel.
204
+ vis = new pv.Panel().width(w).height(h).bottom(20).left(20).right(10).top(5)
205
+ # X-axis ticks.
206
+ vis.add(pv.Rule).data(x.ticks()).left(x).strokeStyle("#fff").
207
+ add(pv.Rule).bottom(-5).height(5).strokeStyle("#000").
208
+ anchor("bottom").add(pv.Label).text((d)-> pv.Format.date("%b %d").format(new Date(d * 86400000)) )
209
+ # Y-axis ticks.
210
+ vis.add(pv.Rule).data(y.ticks(3)).bottom(y).strokeStyle((d)-> if d then "#ddd" else "#000").
211
+ anchor("left").add(pv.Label).text(y.tickFormat)
212
+ # If we only have one data point, can't show a line so show dot instead
213
+ if data.length == 1
214
+ vis.add(pv.Dot).data(data).
215
+ left((d)-> x(new Date(d.ts)) ).bottom((d)-> y(d[series])).radius(3).lineWidth(2)
216
+ else
217
+ vis.add(pv.Line).data(data).interpolate("linear").
218
+ left((d)-> x(new Date(d.ts)) ).bottom((d)-> y(d[series]) ).lineWidth(3)
219
+ vis.canvas(canvas[0]).render()
@@ -1,210 +1,259 @@
1
- Sammy("#main", function(app) {
2
- this.use(Sammy.Tmpl);
3
- this.use(Sammy.Session);
4
- this.use(Sammy.Title);
5
- this.setTitle("OAuth Admin - ");
6
- this.use(Sammy.OAuth2);
7
- this.authorize = document.location.pathname + "/authorize";
8
-
9
- $(document).ajaxError(function(evt, xhr) { app.trigger("notice", xhr.responseText); });
10
- $(document).ajaxStart(function(evt) { $("#throbber").show(); });
11
- $(document).ajaxStop(function(evt) { $("#throbber").hide(); });
12
-
13
- // For all request (except callback), if we don't have an OAuth access token,
14
- this.requireOAuth();
15
- this.bind("oauth.denied", function(evt, error) {
16
- this.partial("admin/views/no_access.tmpl", { error: error.message });
17
- });
18
- this.bind("oauth.connected", function() { $("#header .signout").show() });
19
- this.bind("oauth.disconnected", function() { $("#header .signout").hide() });
20
-
21
- var api = document.location.pathname + "/api";
22
- // Takes array of string with scope names (typically request parameters) and
23
- // normalizes them into an array of scope names.
24
- function mergeScope(scope) {
25
- if ($.isArray(scope))
26
- scope = scope.join(" ");
27
- scope = (scope || "").trim().split(/\s+/);
28
- return scope.length == 1 && scope[0] == "" ? [] : _.uniq(scope).sort();
29
- }
30
- var commonScope;
31
- function withCommonScope(cb) {
32
- if (commonScope)
33
- cb(commonScope)
34
- else
35
- $.getJSON(api + "/clients", function(json) { cb(commonScope = json.scope); })
36
- }
37
-
38
- // View all clients
39
- this.get("#/", function(context) {
40
- context.title("All Clients");
41
- $.getJSON(api + "/clients", function(clients) {
42
- commonScope = clients.scope;
43
- context.partial("admin/views/clients.tmpl", { clients: clients.list, tokens: clients.tokens }).
44
- load(clients.history).then(function(json) { $("#fig").chart(json.data, "granted"); });
1
+ (function() {
2
+ Sammy("#main", function(app) {
3
+ var api, commonScope, mergeScope, noticeTimeout, withCommonScope;
4
+ this.use(Sammy.Tmpl);
5
+ this.use(Sammy.Session);
6
+ this.use(Sammy.Title);
7
+ this.setTitle("OAuth Admin - ");
8
+ this.use(Sammy.OAuth2);
9
+ this.authorize = ("" + (document.location.pathname) + "/authorize");
10
+ $(document).ajaxError(function(evt, xhr) {
11
+ if (xhr.status === 401) {
12
+ app.loseAccessToken();
13
+ }
14
+ return app.trigger("notice", xhr.responseText);
45
15
  });
46
- });
47
- // View single client
48
- this.get("#/client/:id", function(context) {
49
- $.getJSON(api + "/client/" + context.params.id, function(client) {
50
- context.title(client.displayName);
51
- client.notes = (client.notes || "").split(/\n\n/);
52
- context.partial("admin/views/client.tmpl", client).
53
- load(client.history).then(function(json) { $("#fig").chart(json.data, "granted"); });
16
+ $(document).ajaxStart(function(evt) {
17
+ return $("#throbber").show();
54
18
  });
55
- });
56
- this.get("#/client/:id/page/:page", function(context) {
57
- $.getJSON(api + "/client/" + context.params.id + "?page=" + context.params.page, function(client) {
58
- context.title(client.displayName);
59
- client.notes = client.notes.split(/\n\n/);
60
- context.partial("admin/views/client.tmpl", client).
61
- load(client.history).then(function(json) { $("#fig").chart(json.data, "granted"); });
19
+ $(document).ajaxStop(function(evt) {
20
+ return $("#throbber").hide();
62
21
  });
63
- });
64
- // Edit client
65
- this.get("#/client/:id/edit", function(context) {
66
- $.getJSON(api + "/client/" + context.params.id, function(client) {
67
- context.title(client.displayName);
68
- withCommonScope(function(scope) {
69
- client.common = scope;
70
- context.partial("admin/views/edit.tmpl", client)
71
- })
72
- })
73
- });
74
- this.put("#/client/:id", function(context) {
75
- context.params.scope = mergeScope(context.params.scope);
76
- $.ajax({ type: "put", url: api + "/client/" + context.params.id,
77
- data: {
78
- displayName: context.params.displayName,
79
- link: context.params.link,
80
- imageUrl: context.params.imageUrl,
81
- redirectUri: context.params.redirectUri,
82
- notes: context.params.notes,
83
- scope: context.params.scope
84
- },
85
- success: function(client) {
86
- context.redirect("#/client/" + context.params.id);
87
- app.trigger("notice", "Saved your changes");
88
- },
89
- error: function(xhr) {
90
- withCommonScope(function(scope) {
91
- context.params.common = scope;
92
- context.partial("admin/views/edit.tmpl", context.params);
93
- });
22
+ this.requireOAuth();
23
+ this.bind("oauth.denied", function(evt, error) {
24
+ return app.partial("admin/views/no_access.tmpl", {
25
+ error: error.message
26
+ });
27
+ });
28
+ this.bind("oauth.connected", function() {
29
+ $("#header .signin").hide();
30
+ return $("#header .signout").show();
31
+ });
32
+ this.bind("oauth.disconnected", function() {
33
+ $("#header .signin").show();
34
+ return $("#header .signout").hide();
35
+ });
36
+ api = ("" + (document.location.pathname) + "/api");
37
+ mergeScope = function(scope) {
38
+ if ($.isArray(scope)) {
39
+ scope = scope.join(" ");
94
40
  }
95
- })
96
- });
97
- // Delete/revoke client
98
- this.del("#/client/:id", function(context) {
99
- $.ajax({ type: "post", url: api + "/client/" + context.params.id,
100
- data: { _method: "delete" },
101
- success: function() { context.redirect("#/") }
41
+ scope = (scope || "").trim().split(/\s+/);
42
+ return scope.length === 1 && scope[0] === "" ? [] : _.uniq(scope).sort();
43
+ };
44
+ commonScope = null;
45
+ withCommonScope = function(cb) {
46
+ return commonScope ? cb(commonScope) : $.getJSON("" + (api) + "/clients", function(json) {
47
+ return cb(commonScope = json.scope);
48
+ });
49
+ };
50
+ this.get("#/", function(context) {
51
+ context.title("All Clients");
52
+ return $.getJSON("" + (api) + "/clients", function(clients) {
53
+ commonScope = clients.scope;
54
+ return context.partial("admin/views/clients.tmpl", {
55
+ clients: clients.list,
56
+ tokens: clients.tokens
57
+ }).load(clients.history).then(function(json) {
58
+ return $("#fig").chart(json.data, "granted");
59
+ });
60
+ });
102
61
  });
103
- });
104
- this.post("#/client/:id/revoke", function(context) {
105
- $.post(api + "/client/" + context.params.id + "/revoke", function() { app.refresh() });
106
- });
107
- // Revoke token
108
- this.post("#/token/:id/revoke", function(context) {
109
- $.post(api + "/token/" + context.params.id + "/revoke", function() { app.refresh() });
110
- });
111
- // Create new client
112
- this.get("#/new", function(context) {
113
- context.title("Add New Client");
114
- withCommonScope(function(scope) {
115
- context.partial("admin/views/edit.tmpl", { common: scope, scope: scope });
62
+ this.get("#/client/:id", function(context) {
63
+ return $.getJSON("" + (api) + "/client/" + (context.params.id), function(client) {
64
+ context.title(client.displayName);
65
+ client.notes = (client.notes || "").split(/\n\n/);
66
+ return context.partial("admin/views/client.tmpl", client).load(client.history).then(function(json) {
67
+ return $("#fig").chart(json.data, "granted");
68
+ });
69
+ });
116
70
  });
117
- });
118
- this.post("#/clients", function(context) {
119
- context.title("Add New Client");
120
- context.params.scope = mergeScope(context.params.scope);
121
- $.ajax({ type: "post", url: api + "/clients",
122
- data: {
123
- displayName: context.params.displayName,
124
- link: context.params.link,
125
- imageUrl: context.params.imageUrl,
126
- redirectUri: context.params.redirectUri,
127
- notes: context.params.notes,
128
- scope: context.params.scope
129
- },
130
- success: function(client) {
131
- app.trigger("notice", "Added new client application " + client.displayName);
132
- context.redirect("#/");
133
- },
134
- error: function(xhr) {
135
- withCommonScope(function(scope) {
136
- context.params.common = scope;
137
- context.partial("admin/views/edit.tmpl", context.params);
71
+ this.get("#/client/:id/page/:page", function(context) {
72
+ return $.getJSON("" + (api) + "/client/" + (context.params.id) + "?page=" + (context.params.page), function(client) {
73
+ context.title(client.displayName);
74
+ client.notes = client.notes.split(/\n\n/);
75
+ return context.partial("admin/views/client.tmpl", client).load(client.history).then(function(json) {
76
+ return $("#fig").chart(json.data, "granted");
77
+ });
78
+ });
79
+ });
80
+ this.post("#/token/:id/revoke", function(context) {
81
+ return $.post("" + (api) + "/token/" + (context.params.id) + "/revoke", function() {
82
+ return context.redirect("#/");
83
+ });
84
+ });
85
+ this.get("#/client/:id/edit", function(context) {
86
+ return $.getJSON("" + (api) + "/client/" + (context.params.id), function(client) {
87
+ context.title(client.displayName);
88
+ return withCommonScope(function(scope) {
89
+ client.common = scope;
90
+ return context.partial("admin/views/edit.tmpl", client);
138
91
  });
92
+ });
93
+ });
94
+ this.put("#/client/:id", function(context) {
95
+ context.params.scope = mergeScope(context.params.scope);
96
+ return $.ajax({
97
+ type: "put",
98
+ url: ("" + (api) + "/client/" + (context.params.id)),
99
+ data: {
100
+ displayName: context.params.displayName,
101
+ link: context.params.link,
102
+ imageUrl: context.params.imageUrl,
103
+ redirectUri: context.params.redirectUri,
104
+ notes: context.params.notes,
105
+ scope: context.params.scope
106
+ },
107
+ success: function(client) {
108
+ context.redirect("#/client/" + (context.params.id));
109
+ return app.trigger("notice", "Saved your changes");
110
+ },
111
+ error: function(xhr) {
112
+ return withCommonScope(function(scope) {
113
+ context.params.common = scope;
114
+ return context.partial("admin/views/edit.tmpl", context.params);
115
+ });
116
+ }
117
+ });
118
+ });
119
+ this.del("#/client/:id", function(context) {
120
+ return $.ajax({
121
+ type: "post",
122
+ url: ("" + (api) + "/client/" + (context.params.id)),
123
+ data: {
124
+ _method: "delete"
125
+ },
126
+ success: function() {
127
+ return context.redirect("#/");
128
+ }
129
+ });
130
+ });
131
+ this.post("#/client/:id/revoke", function(context) {
132
+ return $.post("" + (api) + "/client/" + (context.params.id) + "/revoke", function() {
133
+ return context.redirect("#/");
134
+ });
135
+ });
136
+ this.get("#/new", function(context) {
137
+ context.title("Add New Client");
138
+ return withCommonScope(function(scope) {
139
+ return context.partial("admin/views/edit.tmpl", {
140
+ common: scope,
141
+ scope: scope
142
+ });
143
+ });
144
+ });
145
+ this.post("#/clients", function(context) {
146
+ context.title("Add New Client");
147
+ context.params.scope = mergeScope(context.params.scope);
148
+ return $.ajax({
149
+ type: "post",
150
+ url: ("" + (api) + "/clients"),
151
+ data: {
152
+ displayName: context.params.displayName,
153
+ link: context.params.link,
154
+ imageUrl: context.params.imageUrl,
155
+ redirectUri: context.params.redirectUri,
156
+ notes: context.params.notes,
157
+ scope: context.params.scope
158
+ },
159
+ success: function(client) {
160
+ app.trigger("notice", "Added new client application " + (client.displayName));
161
+ return context.redirect("#/");
162
+ },
163
+ error: function(xhr) {
164
+ return withCommonScope(function(scope) {
165
+ context.params.common = scope;
166
+ return context.partial("admin/views/edit.tmpl", context.params);
167
+ });
168
+ }
169
+ });
170
+ });
171
+ this.get("#/signout", function(context) {
172
+ context.loseAccessToken();
173
+ return context.redirect("#/");
174
+ });
175
+ $("a[data-method]").live("click", function(evt) {
176
+ var form, link, method;
177
+ evt.preventDefault;
178
+ link = $(this);
179
+ if (link.attr("data-confirm") && !confirm(link.attr("data-confirm"))) {
180
+ return false;
139
181
  }
182
+ method = link.attr("data-method") || "get";
183
+ form = $("<form>", {
184
+ style: "display:none",
185
+ method: method,
186
+ action: link.attr("href")
187
+ });
188
+ if (method !== "get" && method !== "post") {
189
+ form.append($("<input name='_method' type='hidden' value='" + (method) + "'>"));
190
+ }
191
+ app.$element().append(form);
192
+ form.submit();
193
+ return false;
194
+ });
195
+ noticeTimeout = null;
196
+ app.bind("notice", function(evt, message) {
197
+ if (!message || message.trim() === "") {
198
+ message = "Got an error, but don't know why";
199
+ }
200
+ $("#notice").text(message).fadeIn("fast");
201
+ if (noticeTimeout) {
202
+ clearTimeout(noticeTimeout);
203
+ noticeTimeout = null;
204
+ }
205
+ return (noticeTimeout = setTimeout(function() {
206
+ noticeTimeout = null;
207
+ return $("#notice").fadeOut("slow");
208
+ }, 5000));
209
+ });
210
+ return $("#notice").live("click", function() {
211
+ return $(this).fadeOut("slow");
140
212
  });
141
213
  });
142
- // Signout
143
- this.get("#/signout", function(context) {
144
- context.loseAccessToken();
145
- context.redirect("#/");
146
- });
147
-
148
- // Links that use forms for various methods (i.e. post, delete).
149
- $("a[data-method]").live("click", function(evt) {
150
- evt.preventDefault();
151
- var link = $(this);
152
- if (link.attr("data-confirm") && !confirm(link.attr("data-confirm")))
153
- return fasle;
154
- var method = link.attr("data-method") || "get",
155
- form = $("<form>", { style: "display:none", method: method, action: link.attr("href") });
156
- app.$element().append(form);
157
- form.submit();
158
- });
159
- // Error/notice at top of screen
160
- var noticeTimeout;
161
- app.bind("notice", function(evt, message) {
162
- if (!message || message.trim() == "")
163
- message = "Got an error, but don't know why";
164
- $("#notice").text(message).fadeIn("fast");
165
- if (noticeTimeout) {
166
- cancelTimeout(noticeTimeout);
167
- noticeTimeout = null;
214
+ $.thousands = function(integer) {
215
+ return integer.toString().replace(/^(\d+?)((\d{3})+)$/g, function(x, a, b) {
216
+ return a + b.replace(/(\d{3})/g, ",$1");
217
+ }).replace(/\.((\d{3})+)(\d+)$/g, function(x, a, b, c) {
218
+ return "." + a.replace(/(\d{3})/g, "$1,") + c;
219
+ });
220
+ };
221
+ $.shortdate = function(integer) {
222
+ var date;
223
+ date = new Date(integer * 1000);
224
+ return "<abbr title='" + (date.toLocaleString()) + "'>" + (date.toDateString().substring(0, 10)) + "</abbr>";
225
+ };
226
+ $.fn.chart = function(data, series) {
227
+ var canvas, h, max, today, vis, w, x, y;
228
+ canvas = $(this);
229
+ w = canvas.width();
230
+ h = canvas.height();
231
+ today = Math.floor(new Date() / 86400000);
232
+ x = pv.Scale.linear(today - 60, today + 1).range(0, w);
233
+ max = pv.max(data, function(d) {
234
+ return d[series];
235
+ });
236
+ y = pv.Scale.linear(0, pv.max([max, 10])).range(0, h);
237
+ vis = new pv.Panel().width(w).height(h).bottom(20).left(20).right(10).top(5);
238
+ 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(function(d) {
239
+ return pv.Format.date("%b %d").format(new Date(d * 86400000));
240
+ });
241
+ vis.add(pv.Rule).data(y.ticks(3)).bottom(y).strokeStyle(function(d) {
242
+ return d ? "#ddd" : "#000";
243
+ }).anchor("left").add(pv.Label).text(y.tickFormat);
244
+ if (data.length === 1) {
245
+ vis.add(pv.Dot).data(data).left(function(d) {
246
+ return x(new Date(d.ts));
247
+ }).bottom(function(d) {
248
+ return y(d[series]);
249
+ }).radius(3).lineWidth(2);
250
+ } else {
251
+ vis.add(pv.Line).data(data).interpolate("linear").left(function(d) {
252
+ return x(new Date(d.ts));
253
+ }).bottom(function(d) {
254
+ return y(d[series]);
255
+ }).lineWidth(3);
168
256
  }
169
- noticeTimeout = setTimeout(function() {
170
- noticeTimeout = null;
171
- $("#notice").fadeOut("slow");
172
- }, 5000);
173
- });
174
- $("#notice").live("click", function() { $(this).fadeOut("slow") });
175
- });
176
-
177
- // Adds thousands separator to integer or float (can also pass formatted string
178
- // if you care about precision).
179
- $.thousands = function(integer) {
180
- return integer.toString().replace(/^(\d+?)((\d{3})+)$/g, function(x,a,b) { return a + b.replace(/(\d{3})/g, ",$1") })
181
- .replace(/\.((\d{3})+)(\d+)$/g, function(x,a,b,c) { return "." + a.replace(/(\d{3})/g, "$1,") + c })
182
- }
183
-
184
- $.shortdate = function(integer) {
185
- var date = new Date(integer * 1000);
186
- return "<span title='" + date.toLocaleString() + "'>" + date.toISOString().substring(0,10) + "</span>";
187
- }
188
-
189
- $.fn.chart = function(data, series) {
190
- /* Sizing and scales. */
191
- var canvas = $(this),
192
- w = canvas.width(), h = canvas.height(),
193
- today = new Date() / 86400000,
194
- x = pv.Scale.linear(today - 60, today).range(0, w),
195
- max = pv.max(data, function(d) { return d[series] }),
196
- y = pv.Scale.linear(0, pv.max([max, 10])).range(0, h);
197
-
198
- /* The root panel. */
199
- var vis = new pv.Panel().width(w).height(h).bottom(20).left(20).right(10).top(5);
200
- /* X-axis ticks. */
201
- vis.add(pv.Rule).data(x.ticks()).visible(function(d) { return d > 0 }).left(x).strokeStyle("#fff")
202
- .add(pv.Rule).bottom(-5).height(5).strokeStyle("#000")
203
- .anchor("bottom").add(pv.Label).text(function(d) { return pv.Format.date("%b %d").format(new Date(d * 86400000)) });
204
- /* Y-axis ticks. */
205
- vis.add(pv.Rule).data(y.ticks(3)).bottom(y).strokeStyle(function(d) { return d ? "#fff" : "#000" })
206
- .anchor("left").add(pv.Label).text(y.tickFormat);
207
- /* The line. */
208
- vis.add(pv.Line).data(data).interpolate("linear").left(function(d) { return x(new Date(d.ts)) }).bottom(function(d) { return y(d[series]) }).lineWidth(3);
209
- vis.canvas(canvas[0]).render();
210
- }
257
+ return vis.canvas(canvas[0]).render();
258
+ };
259
+ }).call(this);