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 +20 -1
- data/VERSION +1 -1
- data/bin/oauth2-server +22 -0
- data/lib/rack/oauth2/admin/css/screen.css +3 -2
- data/lib/rack/oauth2/admin/js/application.coffee +219 -0
- data/lib/rack/oauth2/admin/js/application.js +248 -199
- data/lib/rack/oauth2/admin/views/client.tmpl +2 -0
- data/lib/rack/oauth2/admin/views/index.html +1 -0
- data/lib/rack/oauth2/models/access_grant.rb +3 -2
- data/lib/rack/oauth2/models/access_token.rb +19 -3
- data/lib/rack/oauth2/models/client.rb +4 -0
- data/lib/rack/oauth2/rails.rb +18 -7
- data/lib/rack/oauth2/server.rb +3 -2
- data/lib/rack/oauth2/server/admin.rb +1 -0
- data/lib/rack/oauth2/server/errors.rb +1 -1
- data/test/oauth/access_grant_test.rb +7 -0
- data/test/rails2/log/test.log +36806 -0
- data/test/rails3/log/test.log +33726 -0
- metadata +6 -5
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.
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
})
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
}
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
context.
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
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);
|