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

Sign up to get free protection for your applications and to get access to all the features.
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);