memcached-manager 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/.travis.yml +19 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +133 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +72 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/config.ru +8 -0
- data/features/api/create_memcached_key.feature +6 -0
- data/features/api/delete_memcached_key.feature +6 -0
- data/features/api/list_memcached_keys.feature +6 -0
- data/features/api/set_memcached_info.feature +5 -0
- data/features/api/show_memcached_key.feature +10 -0
- data/features/api/show_memcached_stats.feature +5 -0
- data/features/api/update_memcached_key.feature +7 -0
- data/features/step_definitions/api/create_memcached_key.rb +21 -0
- data/features/step_definitions/api/delete_memcached_key.rb +7 -0
- data/features/step_definitions/api/list_memcached_keys.rb +16 -0
- data/features/step_definitions/api/show_memcached_key.rb +18 -0
- data/features/step_definitions/api/show_memcached_stats.rb +3 -0
- data/features/step_definitions/api/update_memcached_key.rb +18 -0
- data/features/step_definitions/set_memcached_info.rb +25 -0
- data/features/step_definitions/webapp/create_memcached_key.rb +10 -0
- data/features/step_definitions/webapp/delete_memcached_key.rb +16 -0
- data/features/step_definitions/webapp/edit_memcached_key.rb +3 -0
- data/features/step_definitions/webapp/list_memcached_keys.rb +4 -0
- data/features/step_definitions/webapp/show_memcached_key.rb +4 -0
- data/features/step_definitions/webapp/show_memcached_stats.rb +5 -0
- data/features/support/env.rb +27 -0
- data/features/support/hooks.rb +3 -0
- data/features/webapp/create_memcached_key.feature +9 -0
- data/features/webapp/delete_memcached_key.feature +8 -0
- data/features/webapp/edit_memcached_key.feature +9 -0
- data/features/webapp/list_memcached_keys.feature +8 -0
- data/features/webapp/show_memcached_key.feature +7 -0
- data/features/webapp/show_memcached_stats.feature +6 -0
- data/lib/api.rb +85 -0
- data/lib/extensions/api_response.rb +15 -0
- data/lib/extensions/errors.rb +19 -0
- data/lib/extensions/memcached_connection.rb +15 -0
- data/lib/extensions/memcached_inspector.rb +49 -0
- data/lib/extensions/memcached_settings.rb +18 -0
- data/lib/extensions.rb +14 -0
- data/lib/public/images/glyphicons-halflings-white.png +0 -0
- data/lib/public/images/glyphicons-halflings.png +0 -0
- data/lib/public/javascripts/angular/controllers.js +86 -0
- data/lib/public/javascripts/angular/routes.js +66 -0
- data/lib/public/javascripts/angular/services/notification.js +16 -0
- data/lib/public/javascripts/angular/services/resources.js +20 -0
- data/lib/public/javascripts/angular/services/response.js +10 -0
- data/lib/public/javascripts/angular-resource.min.js +10 -0
- data/lib/public/javascripts/angular-ui-states.min.js +7 -0
- data/lib/public/javascripts/angular.min.js +161 -0
- data/lib/public/javascripts/application.js +18 -0
- data/lib/public/javascripts/jquery-1.9.1.min.js +5 -0
- data/lib/public/javascripts/noty/jquery.noty.js +547 -0
- data/lib/public/javascripts/noty/layouts/top.js +34 -0
- data/lib/public/javascripts/noty/themes/default.js +156 -0
- data/lib/public/javascripts/noty_config.js +25 -0
- data/lib/public/stylesheets/app.css +50 -0
- data/lib/public/stylesheets/base.css +269 -0
- data/lib/public/stylesheets/icons.css +492 -0
- data/lib/public/stylesheets/layout.css +58 -0
- data/lib/public/stylesheets/skeleton.css +242 -0
- data/lib/public/templates/edit.html.erb +6 -0
- data/lib/public/templates/keys.html.erb +36 -0
- data/lib/public/templates/new.html.erb +6 -0
- data/lib/public/templates/show.html.erb +2 -0
- data/lib/public/templates/stats.html.erb +8 -0
- data/lib/views/index.erb +13 -0
- data/lib/views/layout.erb +52 -0
- data/lib/webapp.rb +14 -0
- data/memcached-manager.gemspec +145 -0
- data/spec/javascripts/angular/controllers/list_keys_controller_spec.js +26 -0
- data/spec/javascripts/angular/services/notification_spec.js +45 -0
- data/spec/javascripts/angular/services/resource_spec.js +41 -0
- data/spec/javascripts/angular/services/response_spec.js +26 -0
- data/spec/javascripts/helpers/SpecHelper.js +2 -0
- data/spec/javascripts/helpers/angular-mocks.js +1962 -0
- data/spec/javascripts/support/jasmine.yml +96 -0
- data/spec/javascripts/support/jasmine_helper.rb +11 -0
- data/spec/lib/extensions/api_response_spec.rb +28 -0
- data/spec/lib/extensions/error_spec.rb +23 -0
- data/spec/lib/extensions/memcached_connection_spec.rb +25 -0
- data/spec/lib/extensions/memcached_inspector_spec.rb +41 -0
- data/spec/lib/extensions/memcached_settings_spec.rb +53 -0
- data/spec/spec_helper.rb +41 -0
- metadata +252 -0
data/lib/api.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Sinatra
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'sinatra/contrib'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
# Libs
|
7
|
+
require 'dalli'
|
8
|
+
require 'net/telnet'
|
9
|
+
|
10
|
+
# Project files
|
11
|
+
require_relative './extensions'
|
12
|
+
|
13
|
+
module MemcachedManager
|
14
|
+
class API < Sinatra::Base
|
15
|
+
enable :inline_templates
|
16
|
+
enable :sessions
|
17
|
+
|
18
|
+
set :public_folder, 'public'
|
19
|
+
|
20
|
+
helpers Sinatra::MemcachedSettings
|
21
|
+
helpers Sinatra::MemcachedConnection
|
22
|
+
helpers Sinatra::MemcachedInspector
|
23
|
+
helpers Sinatra::Errors
|
24
|
+
helpers Sinatra::APIResponse
|
25
|
+
|
26
|
+
before do
|
27
|
+
content_type :json
|
28
|
+
|
29
|
+
setup_errors
|
30
|
+
|
31
|
+
try { setup_memcached(memcached_host(session), memcached_port(session)) }
|
32
|
+
end
|
33
|
+
|
34
|
+
after do
|
35
|
+
close_memcached
|
36
|
+
end
|
37
|
+
|
38
|
+
get '/config.json' do
|
39
|
+
api_response { { host: memcached_host(session), port: memcached_port(session) } }
|
40
|
+
end
|
41
|
+
|
42
|
+
post '/config.json' do
|
43
|
+
session['host'] = params['host']
|
44
|
+
session['port'] = params['port']
|
45
|
+
|
46
|
+
api_response { { host: memcached_host(session), port: memcached_port(session) } }
|
47
|
+
end
|
48
|
+
|
49
|
+
post '/keys.json' do
|
50
|
+
try { memcached_connection.set(params[:key], params[:value]) }
|
51
|
+
|
52
|
+
api_response { memcached_inspect(host: memcached_host(session), port: memcached_port(session), key: params[:key]) }
|
53
|
+
end
|
54
|
+
|
55
|
+
put '/keys.json' do
|
56
|
+
try { memcached_connection.replace(params[:key], params[:value]) }
|
57
|
+
|
58
|
+
api_response { memcached_inspect(host: memcached_host(session), port: memcached_port(session), key: params[:key]) }
|
59
|
+
end
|
60
|
+
|
61
|
+
get '/keys.json' do
|
62
|
+
api_response do
|
63
|
+
memcached_inspect(host: memcached_host(session), port: memcached_port(session))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
get '/keys/:key.json' do
|
68
|
+
value = memcached_connection.get(params[:key])
|
69
|
+
|
70
|
+
try { raise 'Key not found.' if value.nil? }
|
71
|
+
|
72
|
+
api_response { { key: params[:key], value: value } }
|
73
|
+
end
|
74
|
+
|
75
|
+
delete '/keys/:key.json' do
|
76
|
+
try { memcached_connection.delete(params[:key]) }
|
77
|
+
|
78
|
+
api_response { { key: params[:key] } }
|
79
|
+
end
|
80
|
+
|
81
|
+
get '/stats.json' do
|
82
|
+
api_response { memcached_connection.stats }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module APIResponse
|
3
|
+
# This makes the api very DRY by taking care of API errors and converting the object
|
4
|
+
# or value passed by the block to json.
|
5
|
+
def api_response(&block)
|
6
|
+
raise 'No block given' unless block_given?
|
7
|
+
|
8
|
+
if errors.any?
|
9
|
+
return { errors: errors }.to_json
|
10
|
+
else
|
11
|
+
block.call.tap {|response| return response.to_json }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module MemcachedInspector
|
3
|
+
def memcached_inspect options
|
4
|
+
host = options[:host]
|
5
|
+
port = options[:port]
|
6
|
+
key = options[:key]
|
7
|
+
|
8
|
+
inspect = inspector host, port
|
9
|
+
|
10
|
+
# Filter by key if defined
|
11
|
+
if !key.nil?
|
12
|
+
inspect = inspect.select{|pair| pair[:key] == key }.first
|
13
|
+
end
|
14
|
+
|
15
|
+
inspect
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def inspector host, port
|
20
|
+
# Looks horrible, got it from a gist... yet, it works.
|
21
|
+
|
22
|
+
keys = []
|
23
|
+
cache_dump_limit = 9_000 # It's over...
|
24
|
+
|
25
|
+
localhost = Net::Telnet::new("Host" => host, "Port" => port, "Timeout" => 3)
|
26
|
+
slab_ids = []
|
27
|
+
|
28
|
+
localhost.cmd("String" => "stats items", "Match" => /^END/) do |c|
|
29
|
+
matches = c.scan(/STAT items:(\d+):/)
|
30
|
+
slab_ids = matches.flatten.uniq
|
31
|
+
end
|
32
|
+
|
33
|
+
slab_ids.each do |slab_id|
|
34
|
+
localhost.cmd("String" => "stats cachedump #{slab_id} #{cache_dump_limit}", "Match" => /^END/) do |c|
|
35
|
+
matches = c.scan(/^ITEM (.+?) \[(\d+) b; (\d+) s\]$/).each do |key_data|
|
36
|
+
(cache_key, bytes, expires_time) = key_data
|
37
|
+
humanized_expires_time = Time.at(expires_time.to_i).to_s
|
38
|
+
expired = false
|
39
|
+
expired = true if Time.at(expires_time.to_i) < Time.now
|
40
|
+
keys << { key: cache_key, bytes: bytes, expires_at: humanized_expires_time, expired: expired }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
keys
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module MemcachedSettings
|
3
|
+
def memcached_host session
|
4
|
+
return session['host'] if configured? session, 'host'
|
5
|
+
'localhost'
|
6
|
+
end
|
7
|
+
|
8
|
+
def memcached_port session
|
9
|
+
return session['port'] if configured? session, 'port'
|
10
|
+
'11211'
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def configured? session, parameter
|
15
|
+
session.key?(parameter)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/extensions.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#TODO: autoload extensions
|
2
|
+
require_relative 'extensions/errors'
|
3
|
+
require_relative 'extensions/memcached_settings'
|
4
|
+
require_relative 'extensions/memcached_connection'
|
5
|
+
require_relative 'extensions/memcached_inspector'
|
6
|
+
require_relative 'extensions/api_response'
|
7
|
+
|
8
|
+
module Sinatra
|
9
|
+
helpers Errors
|
10
|
+
helpers MemcachedSettings
|
11
|
+
helpers MemcachedConnection
|
12
|
+
helpers MemcachedInspector
|
13
|
+
helpers APIResponse
|
14
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,86 @@
|
|
1
|
+
controllers.controller('ListKeysController', ['$scope', 'Keys', '$state', '$location', function($scope, Keys, $state, $location) {
|
2
|
+
$scope.keys = Keys.get();
|
3
|
+
}]);
|
4
|
+
|
5
|
+
controllers.controller('CreateKeyController', ['$scope', 'Keys', '$state', '$location', 'Notification', 'Response', function($scope, Keys, $state, $location, Notification, Response) {
|
6
|
+
$scope.createKey = function() {
|
7
|
+
pair = { key: $scope.pair.key, value: $scope.pair.value };
|
8
|
+
|
9
|
+
$state.transitionTo('processingRequest');
|
10
|
+
|
11
|
+
Keys.save(pair, function(response) {
|
12
|
+
if (Response.valid(response)) { // Checks if response is valid
|
13
|
+
Notification.success('Yay! Key created with success.');
|
14
|
+
|
15
|
+
// Refreshing list
|
16
|
+
$state.transitionTo('showKey', pair);
|
17
|
+
|
18
|
+
// Redirecting to / so the list gets refreshed.
|
19
|
+
$location.path('/');
|
20
|
+
} else {
|
21
|
+
Notification.error(Response.errors(response));
|
22
|
+
}
|
23
|
+
});
|
24
|
+
}
|
25
|
+
}]);
|
26
|
+
|
27
|
+
controllers.controller('DeleteKeyController', ['$state', '$scope', 'Key', '$location', 'Notification', 'Response', function($state, $scope, Key, $location, Notification, Response){
|
28
|
+
$scope.removeKey = function(pair) {
|
29
|
+
$state.transitionTo('processingRequest');
|
30
|
+
|
31
|
+
Key.delete({key: pair.key}, function(response) {
|
32
|
+
if (Response.valid(response)){ // Checks if response is valid
|
33
|
+
Notification.info('Yay! Key deleted with success.');
|
34
|
+
|
35
|
+
// Refreshing list
|
36
|
+
$state.transitionTo('showKeys');
|
37
|
+
|
38
|
+
// Redirecting to / so the list gets refreshed.
|
39
|
+
$location.path('/');
|
40
|
+
} else {
|
41
|
+
Notification.error(Response.errors(response));
|
42
|
+
}
|
43
|
+
});
|
44
|
+
}
|
45
|
+
}]);
|
46
|
+
|
47
|
+
controllers.controller('UpdateKeyController', ['$state', '$scope', 'Key', 'Keys', '$location', 'Notification', 'Response', function($state, $scope, Key, Keys, $location, Notification, Response) {
|
48
|
+
$scope.updateKey = function() {
|
49
|
+
pair = { key: $scope.pair.key, value: $scope.pair.value };
|
50
|
+
|
51
|
+
$state.transitionTo('processingRequest');
|
52
|
+
|
53
|
+
Keys.update(pair, function(response) {
|
54
|
+
if (Response.valid(response)){ // Checks if response is valid
|
55
|
+
Notification.success('Yay! Key updated with success.');
|
56
|
+
|
57
|
+
// Refreshing list
|
58
|
+
$state.transitionTo('showKey', pair);
|
59
|
+
|
60
|
+
// Redirecting to / so the list gets refreshed.
|
61
|
+
$location.path('/');
|
62
|
+
} else {
|
63
|
+
Notification.error(Response.errors(response));
|
64
|
+
}
|
65
|
+
});
|
66
|
+
}
|
67
|
+
}]);
|
68
|
+
|
69
|
+
controllers.controller('EditKeyController', ['$state', '$scope', 'Key', '$location', '$stateParams', function($state, $scope, Key, $location, $stateParams) {
|
70
|
+
// Set up the current form text 'key' as key
|
71
|
+
$scope.pair = Key.find({key: $stateParams.key});
|
72
|
+
}]);
|
73
|
+
|
74
|
+
controllers.controller('StatsController', ['$state', '$scope', 'Stats', '$location', '$stateParams', function($state, $scope, Stats, $location, $stateParams) {
|
75
|
+
// Set up the current form text 'key' as key
|
76
|
+
$scope.stats = Stats.all();
|
77
|
+
}]);
|
78
|
+
|
79
|
+
controllers.controller('ShowKeyController', ['$state', '$scope', 'Key', '$location', '$stateParams', function($state, $scope, Key, $location, $stateParams) {
|
80
|
+
// Set up the current form text 'key' as key
|
81
|
+
$scope.pair = Key.find({key: $stateParams.key});
|
82
|
+
}]);
|
83
|
+
|
84
|
+
controllers.controller('BootstrapController', ['$state', function($state) {
|
85
|
+
$state.transitionTo('showKeys');
|
86
|
+
}]);
|
@@ -0,0 +1,66 @@
|
|
1
|
+
app.config(function($stateProvider, $routeProvider){
|
2
|
+
$stateProvider
|
3
|
+
.state('showKeys', {
|
4
|
+
url: "",
|
5
|
+
views: {
|
6
|
+
"keys": {
|
7
|
+
templateUrl: "/templates/keys.html.erb",
|
8
|
+
controller: 'ListKeysController'
|
9
|
+
},
|
10
|
+
"key": {
|
11
|
+
templateUrl: '/templates/stats.html.erb',
|
12
|
+
controller: 'StatsController'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
})
|
16
|
+
.state('processingRequest', {
|
17
|
+
url: "/processing",
|
18
|
+
views: {
|
19
|
+
"keys": {
|
20
|
+
templateUrl: "/templates/keys.html.erb",
|
21
|
+
controller: 'ListKeysController'
|
22
|
+
},
|
23
|
+
"key": {
|
24
|
+
template: 'Processing...' // Show a image or somethin', lol
|
25
|
+
}
|
26
|
+
}
|
27
|
+
})
|
28
|
+
.state('editKey', {
|
29
|
+
url: "/edit/:key", // :key is going to be inside $stateParams whenever it's injected in a controller here
|
30
|
+
views: {
|
31
|
+
"keys": {
|
32
|
+
templateUrl: "/templates/keys.html.erb",
|
33
|
+
controller: 'ListKeysController'
|
34
|
+
},
|
35
|
+
"key": {
|
36
|
+
templateUrl: "/templates/edit.html.erb",
|
37
|
+
controller: 'EditKeyController'
|
38
|
+
}
|
39
|
+
}
|
40
|
+
})
|
41
|
+
.state('newKey', {
|
42
|
+
url: "/new",
|
43
|
+
views: {
|
44
|
+
"keys": {
|
45
|
+
templateUrl: "/templates/keys.html.erb",
|
46
|
+
controller: 'ListKeysController'
|
47
|
+
},
|
48
|
+
"key": {
|
49
|
+
templateUrl: "/templates/new.html.erb",
|
50
|
+
}
|
51
|
+
}
|
52
|
+
})
|
53
|
+
.state('showKey', {
|
54
|
+
url: "/key/:key", // :key is going to be inside $stateParams whenever it's injected in a controller here
|
55
|
+
views: {
|
56
|
+
"keys": {
|
57
|
+
templateUrl: "/templates/keys.html.erb",
|
58
|
+
controller: 'ListKeysController'
|
59
|
+
},
|
60
|
+
"key": {
|
61
|
+
templateUrl: "/templates/show.html.erb",
|
62
|
+
controller: 'ShowKeyController'
|
63
|
+
}
|
64
|
+
}
|
65
|
+
});
|
66
|
+
})
|
@@ -0,0 +1,16 @@
|
|
1
|
+
services.factory('Notification', function() {
|
2
|
+
return {
|
3
|
+
alert: function(message) {
|
4
|
+
noty({text: message, type: 'alert'});
|
5
|
+
},
|
6
|
+
error: function(message) {
|
7
|
+
noty({text: message, type: 'error'});
|
8
|
+
},
|
9
|
+
info: function(message) {
|
10
|
+
noty({text: message, type: 'information'})
|
11
|
+
},
|
12
|
+
success: function(message) {
|
13
|
+
noty({text: message, type: 'success'})
|
14
|
+
}
|
15
|
+
};
|
16
|
+
});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
services.factory('Keys', ['$resource', function($resource) {
|
2
|
+
return $resource('/api/keys.json', {}, {
|
3
|
+
'get': { method: 'GET', isArray:true },
|
4
|
+
'save': { method: 'POST' },
|
5
|
+
'update': { method: 'PUT' }
|
6
|
+
});
|
7
|
+
}]);
|
8
|
+
|
9
|
+
services.factory('Key', ['$resource', function($resource) {
|
10
|
+
return $resource('/api/keys/:key.json', { key: '@key' }, {
|
11
|
+
'find': { method: 'GET' },
|
12
|
+
'delete': { method: 'DELETE' }
|
13
|
+
});
|
14
|
+
}]);
|
15
|
+
|
16
|
+
services.factory('Stats', ['$resource', function($resource) {
|
17
|
+
return $resource('/api/stats.json', {}, {
|
18
|
+
'all': { method: 'GET' }
|
19
|
+
});
|
20
|
+
}]);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/*
|
2
|
+
AngularJS v1.0.2
|
3
|
+
(c) 2010-2012 Google, Inc. http://angularjs.org
|
4
|
+
License: MIT
|
5
|
+
*/
|
6
|
+
(function(A,f,u){'use strict';f.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(v,w){function g(b,c){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(c?null:/%20/g,"+")}function l(b,c){this.template=b+="#";this.defaults=c||{};var a=this.urlParams={};j(b.split(/\W/),function(c){c&&b.match(RegExp("[^\\\\]:"+c+"\\W"))&&(a[c]=!0)});this.template=b.replace(/\\:/g,":")}function s(b,c,a){function f(d){var b=
|
7
|
+
{};j(c||{},function(a,x){var m;a.charAt&&a.charAt(0)=="@"?(m=a.substr(1),m=w(m)(d)):m=a;b[x]=m});return b}function e(a){t(a||{},this)}var y=new l(b),a=r({},z,a);j(a,function(d,g){var l=d.method=="POST"||d.method=="PUT"||d.method=="PATCH";e[g]=function(a,b,c,g){var i={},h,k=o,p=null;switch(arguments.length){case 4:p=g,k=c;case 3:case 2:if(q(b)){if(q(a)){k=a;p=b;break}k=b;p=c}else{i=a;h=b;k=c;break}case 1:q(a)?k=a:l?h=a:i=a;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+
|
8
|
+
arguments.length+" arguments.";}var n=this instanceof e?this:d.isArray?[]:new e(h);v({method:d.method,url:y.url(r({},f(h),d.params||{},i)),data:h}).then(function(a){var b=a.data;if(b)d.isArray?(n.length=0,j(b,function(a){n.push(new e(a))})):t(b,n);(k||o)(n,a.headers)},p);return n};e.bind=function(d){return s(b,r({},c,d),a)};e.prototype["$"+g]=function(a,b,d){var c=f(this),i=o,h;switch(arguments.length){case 3:c=a;i=b;h=d;break;case 2:case 1:q(a)?(i=a,h=b):(c=a,i=b||o);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+
|
9
|
+
arguments.length+" arguments.";}e[g].call(this,c,l?this:u,i,h)}});return e}var z={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},o=f.noop,j=f.forEach,r=f.extend,t=f.copy,q=f.isFunction;l.prototype={url:function(b){var c=this,a=this.template,f,b=b||{};j(this.urlParams,function(e,d){f=g(b[d]||c.defaults[d]||"",!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+");a=a.replace(RegExp(":"+d+"(\\W)"),f+"$1")});var a=
|
10
|
+
a.replace(/\/?#$/,""),e=[];j(b,function(a,b){c.urlParams[b]||e.push(g(b)+"="+g(a))});e.sort();a=a.replace(/\/*$/,"");return a+(e.length?"?"+e.join("&"):"")}};return s}])})(window,window.angular);
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* State-based routing for AngularJS
|
3
|
+
* @version v0.0.1 - 2013-04-06
|
4
|
+
* @link http://angular-ui.github.com/
|
5
|
+
* @license MIT License, http://www.opensource.org/licenses/MIT
|
6
|
+
*/
|
7
|
+
(function(t,r,e){"use strict";function n(t,r){return g(new(g(function(){},{prototype:t})),r)}function a(t){return d(arguments,function(r){r!==t&&d(r,function(r,e){t.hasOwnProperty(e)||(t[e]=r)})}),t}function i(t,r,e){this.fromConfig=function(t,r,e){return h(t.template)?this.fromString(t.template,r):h(t.templateUrl)?this.fromUrl(t.templateUrl,r):h(t.templateProvider)?this.fromProvider(t.templateProvider,r,e):null},this.fromString=function(t,r){return p(t)?t(r):t},this.fromUrl=function(e,n){return p(e)&&(e=e(n)),null==e?null:t.get(e,{cache:r}).then(function(t){return t.data})},this.fromProvider=function(t,r,n){return e.invoke(t,null,n||{params:r})}}function o(t){function r(r){if(!/^\w+$/.test(r))throw Error("Invalid parameter name '"+r+"' in pattern '"+t+"'");if(i[r])throw Error("Duplicate parameter name '"+r+"' in pattern '"+t+"'");i[r]=!0,l.push(r)}function e(t){return t.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var n,a=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,i={},o="^",s=0,u=this.segments=[],l=this.params=[];this.source=t;for(var c,f,h;(n=a.exec(t))&&(c=n[2]||n[3],f=n[4]||("*"==n[1]?".*":"[^/]*"),h=t.substring(s,n.index),!(h.indexOf("?")>=0));)o+=e(h)+"("+f+")",r(c),u.push(h),s=a.lastIndex;h=t.substring(s);var p=h.indexOf("?");if(p>=0){var m=this.sourceSearch=h.substring(p);h=h.substring(0,p),this.sourcePath=t.substring(0,s+p),d(m.substring(1).split(/[&?]/),r)}else this.sourcePath=t,this.sourceSearch="";o+=e(h)+"$",u.push(h),this.regexp=RegExp(o),this.prefix=u[0]}function s(){this.compile=function(t){return new o(t)},this.isMatcher=function(t){return t instanceof o},this.$get=function(){return this}}function u(t){function r(t){var r=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(t.source);return null!=r?r[1].replace(/\\(.)/g,"$1"):""}function e(t,r){return t.replace(/\$(\$|\d{1,2})/,function(t,e){return r["$"===e?0:Number(e)]})}function n(t,r,e){if(!e)return!1;var n=r(e,t);return h(n)?n:!0}var a=[],i=null;this.rule=function(t){if(!p(t))throw Error("'rule' must be a function");return a.push(t),this},this.otherwise=function(t){if(m(t)){var r=t;t=function(){return r}}else if(!p(t))throw Error("'rule' must be a function");return i=t,this},this.when=function(a,i){var o,s;if(m(a)&&(a=t.compile(a)),t.isMatcher(a)){if(m(i))s=t.compile(i),i=function(t){return s.format(t)};else if(!p(i))throw Error("invalid 'handler' in when()");o=function(t){return n(t,i,a.exec(t.path(),t.search()))},o.prefix=m(a.prefix)?a.prefix:""}else{if(!(a instanceof RegExp))throw Error("invalid 'what' in when()");if(m(i))s=i,i=function(t){return e(s,t)};else if(!p(i))throw Error("invalid 'handler' in when()");if(a.global||a.sticky)throw Error("when() RegExp must not be global or sticky");o=function(t){return n(t,i,a.exec(t.path()))},o.prefix=r(a)}return this.rule(o)},this.$get=["$location","$rootScope",function(t,r){function e(){var r,e,n=a.length;for(r=0;n>r;r++)if(e=a[r](t)){m(e)&&t.replace().url(e);break}}return i&&a.push(i),r.$on("$locationChangeSuccess",e),{}}]}function l(t,r){function e(t){var r;if(m(t)){if(r=c[t],!r)throw Error("No such state '"+t+"'")}else if(r=c[t.name],!r||r!==t&&r.self!==t)throw Error("Invalid or unregistered state");return r}function i(a){a=n(a,{self:a,toString:function(){return this.name}});var i=a.name;if(!m(i)||i.indexOf("@")>=0)throw Error("State must have a valid name");if(c[i])throw Error("State '"+i+"'' is already defined");var o=u;if(h(a.parent))null!=a.parent&&(o=e(a.parent));else{var s=/^(.+)\.[^.]+$/.exec(i);null!=s&&(o=e(s[1]))}a.parent=o;var f=a.url;if(m(f))f=a.url="^"==f.charAt(0)?r.compile(f.substring(1)):(o.navigable||u).url.concat(f);else if($(f)&&p(f.exec)&&p(f.format)&&p(f.concat));else if(null!=f)throw Error("Invalid url '"+f+"' in state '"+a+"'");a.navigable=f?a:o?o.navigable:null;var w=a.params;if(w){if(!v(w))throw Error("Invalid params in state '"+a+"'");if(f)throw Error("Both params and url specicified in state '"+a+"'")}else w=a.params=f?f.parameters():a.parent.params;var b={};if(d(w,function(t){b[t]=!0}),o){d(o.params,function(t){if(!b[t])throw Error("Missing required parameter '"+t+"' in state '"+i+"'");b[t]=!1});var E=a.ownParams=[];d(b,function(t,r){t&&E.push(r)})}else a.ownParams=w;var x={};d(h(a.views)?a.views:{"":a},function(t,r){0>r.indexOf("@")&&(r=r+"@"+a.parent.name),x[r]=t}),a.views=x,a.path=o?o.path.concat(a):[];var P=a.includes=o?g({},o.includes):{};return P[i]=!0,a.resolve||(a.resolve={}),!a.abstract&&f&&t.when(f,function(t){l.transitionTo(a,t,!1)}),c[i]=a,a}function o(t,r){return $(t)?r=t:r.name=t,i(r),this}function s(t,r,i,o,s,c){function f(t,e,n,s,u){function l(e,n){d(e,function(e,a){f.push(r.when(m(e)?o.get(e):o.invoke(e,t.self,h)).then(function(t){n[a]=t}))})}var c,f=[s];n?c=e:(c={},d(t.params,function(t){c[t]=e[t]}));var h={$stateParams:c},p=u.globals={$stateParams:c};return l(t.resolve,p),p.$$state=t,d(t.views,function(e,n){var a=u[n]={$$controller:e.controller};f.push(r.when(i.fromConfig(e,c,h)||"").then(function(t){a.$template=t})),e.resolve!==t.resolve&&l(e.resolve,a)}),r.all(f).then(function(r){return a(u.globals,r[0].globals),d(t.views,function(t,r){a(u[r],u.globals)}),u})}function p(t,r,e){for(var n=0;e.length>n;n++){var a=e[n];if(t[a]!=r[a])return!1}return!0}var $=r.reject(Error("transition superseded")),v=r.reject(Error("transition prevented"));return l={params:{},current:u.self,$current:u,transition:null},l.transitionTo=function(a,i,m){if(h(m)||(m=!0),a=e(a),a.abstract)throw Error("Cannot transition to abstract state '"+a+"'");var g,b,E=a.path,x=l.$current,P=l.params,C=x.path,S=u.locals,y=[];for(g=0,b=E[g];b&&b===C[g]&&p(i,P,b.ownParams);g++,b=E[g])S=y[g]=b.locals;if(a===x&&S===x.locals)return l.transition=null,r.when(l.current);var j={};if(d(a.params,function(t){var r=i[t];j[t]=null!=r?r+"":null}),i=j,t.$broadcast("$stateChangeStart",a.self,i,x.self,P).defaultPrevented)return v;for(var R=r.when(S),I=g;E.length>I;I++,b=E[I])S=y[I]=n(S),R=f(b,i,b===a,R,S);var k=l.transition=R.then(function(){var r,e,n;if(l.transition!==k)return $;for(r=C.length-1;r>=g;r--)n=C[r],n.self.onExit&&o.invoke(n.self.onExit,n.self,n.locals.globals),n.locals=null;for(r=g;E.length>r;r++)e=E[r],e.locals=y[r],e.self.onEnter&&o.invoke(e.self.onEnter,e.self,e.locals.globals);l.$current=a,l.current=a.self,l.params=i,w(l.params,s),l.transition=null;var u=a.navigable;return m&&u&&c.url(u.url.format(u.locals.globals.$stateParams)),t.$broadcast("$stateChangeSuccess",a.self,i,x.self,P),l.current},function(e){return l.transition!==k?$:(l.transition=null,t.$broadcast("$stateChangeError",a.self,i,x.self,P,e),r.reject(e))});return k},l.is=function(t){return l.$current===e(t)},l.includes=function(t){return l.$current.includes[e(t).name]},l}var u,l,c={};u=i({name:"",url:"^",views:null,"abstract":!0}),u.locals={globals:{$stateParams:{}}},u.navigable=null,this.state=o,this.$get=s,s.$inject=["$rootScope","$q","$templateFactory","$injector","$stateParams","$location","$urlRouter"]}function c(t,r,e,n){var a={restrict:"ECA",terminal:!0,link:function(i,o,s){function u(){var a=t.$current&&t.$current.locals[f];if(a!==c)if(l&&(l.$destroy(),l=null),a){c=a,m.state=a.$$state,o.html(a.$template);var s=r(o.contents());if(l=i.$new(),a.$$controller){a.$scope=l;var u=e(a.$$controller,a);o.contents().data("$ngControllerController",u)}s(l),l.$emit("$viewContentLoaded"),l.$eval(h),n()}else c=null,m.state=null,o.html("")}var l,c,f=s[a.name]||s.name||"",h=s.onload||"",p=o.parent().inheritedData("$uiView");0>f.indexOf("@")&&(f=f+"@"+(p?p.state.name:""));var m={name:f,state:null};o.data("$uiView",m),i.$on("$stateChangeSuccess",u),u()}};return a}function f(t,r){function a(t){this.locals=t.locals.globals,this.params=this.locals.$stateParams}function i(){this.locals=null,this.params=null}function o(e,o){if(null!=o.redirectTo){var s,l=o.redirectTo;if(m(l))s=l;else{if(!p(l))throw Error("Invalid 'redirectTo' in when()");s=function(t,r){return l(t,r.path(),r.search())}}r.when(e,s)}else t.state(n(o,{parent:null,name:"route:"+encodeURIComponent(e),url:e,onEnter:a,onExit:i}));return u.push(o),this}function s(t,r,n){function a(t){return""!==t.name?t:e}var i={routes:u,params:n,current:e};return r.$on("$stateChangeStart",function(t,e,n,i){r.$broadcast("$routeChangeStart",a(e),a(i))}),r.$on("$stateChangeSuccess",function(t,e,n,o){i.current=a(e),r.$broadcast("$routeChangeSuccess",a(e),a(o)),w(n,i.params)}),r.$on("$stateChangeError",function(t,e,n,i,o,s){r.$broadcast("$routeChangeError",a(e),a(i),s)}),i}var u=[];a.$inject=["$$state"],this.when=o,this.$get=s,s.$inject=["$state","$rootScope","$routeParams"]}var h=r.isDefined,p=r.isFunction,m=r.isString,$=r.isObject,v=r.isArray,d=r.forEach,g=r.extend,w=r.copy;r.module("ui.util",["ng"]),r.module("ui.router",["ui.util"]),r.module("ui.state",["ui.router","ui.util"]),r.module("ui.compat",["ui.state"]),i.$inject=["$http","$templateCache","$injector"],r.module("ui.util").service("$templateFactory",i),o.prototype.concat=function(t){return new o(this.sourcePath+t+this.sourceSearch)},o.prototype.toString=function(){return this.source},o.prototype.exec=function(t,r){var e=this.regexp.exec(t);if(!e)return null;var n,a=this.params,i=a.length,o=this.segments.length-1,s={};for(n=0;o>n;n++)s[a[n]]=decodeURIComponent(e[n+1]);for(;i>n;n++)s[a[n]]=r[a[n]];return s},o.prototype.parameters=function(){return this.params},o.prototype.format=function(t){var r=this.segments,e=this.params;if(!t)return r.join("");var n,a,i,o=r.length-1,s=e.length,u=r[0];for(n=0;o>n;n++)i=t[e[n]],null!=i&&(u+=i),u+=r[n+1];for(;s>n;n++)i=t[e[n]],null!=i&&(u+=(a?"&":"?")+e[n]+"="+encodeURIComponent(i),a=!0);return u},r.module("ui.util").provider("$urlMatcherFactory",s),u.$inject=["$urlMatcherFactoryProvider"],r.module("ui.router").provider("$urlRouter",u),l.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider"],r.module("ui.state").value("$stateParams",{}).provider("$state",l),c.$inject=["$state","$compile","$controller","$anchorScroll"],r.module("ui.state").directive("uiView",c),f.$inject=["$stateProvider","$urlRouterProvider"],r.module("ui.compat").provider("$route",f).directive("ngView",c)})(window,window.angular);
|