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 +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);
|