blinkr 0.2.9 → 0.3.0
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.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/bin/blinkr +1 -0
- data/blinkr.gemspec +16 -8
- data/lib/blinkr.rb +2 -1
- data/lib/blinkr/cache.rb +5 -1
- data/lib/blinkr/config.rb +2 -1
- data/lib/blinkr/engine.rb +49 -36
- data/lib/blinkr/error.rb +30 -0
- data/lib/blinkr/extensions/a_title.rb +7 -1
- data/lib/blinkr/extensions/empty_a_href.rb +7 -1
- data/lib/blinkr/extensions/img_alt.rb +7 -1
- data/lib/blinkr/extensions/inline_css.rb +13 -2
- data/lib/blinkr/extensions/javascript.rb +5 -1
- data/lib/blinkr/extensions/links.rb +66 -22
- data/lib/blinkr/extensions/meta.rb +37 -8
- data/lib/blinkr/extensions/resources.rb +5 -1
- data/lib/blinkr/hacks.rb +20 -0
- data/lib/blinkr/http_utils.rb +10 -5
- data/lib/blinkr/manticore_wrapper.rb +59 -0
- data/lib/blinkr/phantomjs_wrapper.rb +22 -9
- data/lib/blinkr/report.html.slim +186 -210
- data/lib/blinkr/report.rb +22 -19
- data/lib/blinkr/sitemap.rb +5 -4
- data/lib/blinkr/typhoeus_wrapper.rb +19 -16
- data/lib/blinkr/version.rb +1 -1
- metadata +8 -6
- data/.gitignore +0 -18
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'blinkr/error'
|
1
2
|
require 'ostruct'
|
2
3
|
require 'slim'
|
3
4
|
|
@@ -38,18 +39,30 @@ module Blinkr
|
|
38
39
|
lines << elm.line
|
39
40
|
snippets << elm.to_s
|
40
41
|
end
|
41
|
-
page.errors <<
|
42
|
+
page.errors << Blinkr::Error.new({:severity => :info, :category => 'HTML Compatibility/Correctness',
|
43
|
+
:type => '<title> tag declared more than once',
|
44
|
+
:title => %Q{<title> declared more than once (lines #{lines.join(', ')})},
|
45
|
+
:message => %Q{<title> declared more than onc},
|
46
|
+
:snippet => snippets.join('\n'), :icon => 'fa-header'})
|
42
47
|
elsif elms.empty?
|
43
|
-
page.errors <<
|
48
|
+
page.errors << Blinkr::Error.new({:severity => :warning, :category => 'SEO', :type => '<title> tag missing',
|
49
|
+
:title => %Q{<title> tag missing}, :message => %Q{<title> tag missing},
|
50
|
+
:icon => 'fa-header'})
|
44
51
|
else
|
45
52
|
title = elms.first.text
|
46
53
|
@titles[title] ||= {}
|
47
54
|
@titles[title][page.response.effective_url] = page
|
48
55
|
if title.length < 20
|
49
|
-
page.errors <<
|
56
|
+
page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO', :type => 'page title too short',
|
57
|
+
:title => %Q{<title> too short (line #{elms.first.line})},
|
58
|
+
:message => %Q{<title> too short (< 20 characters)},
|
59
|
+
:snippet => elms.first.to_s, :icon => 'fa-header'})
|
50
60
|
end
|
51
61
|
if title.length > 55
|
52
|
-
page.errors <<
|
62
|
+
page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO', :type => 'page title too long',
|
63
|
+
:title => %Q{<title> too long (line #{elms.first.line})},
|
64
|
+
:message => %Q{<title> too long (> 55 characters)},
|
65
|
+
:snippet => elms.first.to_s, :icon => 'fa-header'})
|
53
66
|
end
|
54
67
|
end
|
55
68
|
end
|
@@ -63,18 +76,34 @@ module Blinkr
|
|
63
76
|
lines << elm.line
|
64
77
|
snippets << elm.to_s
|
65
78
|
end
|
66
|
-
page.errors <<
|
79
|
+
page.errors << Blinkr::Error.new({:severity => :info, :category => 'HTML Compatibility/Correctness',
|
80
|
+
:type => '<meta name="description"> tag declared more than once',
|
81
|
+
:title => %Q{<meta name="description"> tag declared more than once (lines #{lines.join(', ')})},
|
82
|
+
:message => %Q{<meta name="description"> tag declared more than once},
|
83
|
+
:snippet => snippets.join('\n'), :icon => 'fa-header'})
|
67
84
|
elsif elms.empty?
|
68
|
-
page.errors <<
|
85
|
+
page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO',
|
86
|
+
:type => '<meta name="description"> tag missing',
|
87
|
+
:title => %Q{<meta name="description"> tag missing},
|
88
|
+
:message => %Q{<meta name="description"> tag missing},
|
89
|
+
:icon => 'fa-header'})
|
69
90
|
else
|
70
91
|
desc = elms.first['content']
|
71
92
|
@descriptions[desc] ||= {}
|
72
93
|
@descriptions[desc][page.response.effective_url] = page
|
73
94
|
if desc.length < 60
|
74
|
-
page.errors <<
|
95
|
+
page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO',
|
96
|
+
:type => '<meta name="description"> too short',
|
97
|
+
:title => %Q{<meta name="description"> too short (lines #{elms.first.line})},
|
98
|
+
:message => %Q{<meta name="description"> too short (< 60 characters)},
|
99
|
+
:snippet => elms.first.to_s, :icon => 'fa-header'})
|
75
100
|
end
|
76
101
|
if desc.length > 115
|
77
|
-
page.errors <<
|
102
|
+
page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO',
|
103
|
+
:type => '<meta name="description"> too long',
|
104
|
+
:title => %Q{<meta name="description"> too long (lines #{elms.first.line})},
|
105
|
+
:message => %Q{<meta name="description"> too long (> 115 characters)},
|
106
|
+
:snippet => elms.first.to_s, :icon => 'fa-header'})
|
78
107
|
end
|
79
108
|
end
|
80
109
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'blinkr/error'
|
2
|
+
|
1
3
|
module Blinkr
|
2
4
|
module Extensions
|
3
5
|
class Resources
|
@@ -11,7 +13,9 @@ module Blinkr
|
|
11
13
|
start = error['errorString'].rindex('server replied: ')
|
12
14
|
message = error['errorString'].slice(start.nil? ? 0 : start + 16, error['errorString'].length) unless error['errorString'].nil?
|
13
15
|
code = error['errorCode'].nil? ? nil : error['errorCode'].to_i
|
14
|
-
page.errors <<
|
16
|
+
page.errors << Blinkr::Error.new(:severity => 'danger', :category => 'Resources missing',
|
17
|
+
:type => 'Resource loading error', :title => error['url'],
|
18
|
+
:code => code, :message => message, :icon => 'fa-file-image-o')
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
data/lib/blinkr/hacks.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class Numeric
|
2
|
+
def duration
|
3
|
+
rest, secs = self.divmod( 60 ) # self is the time difference t2 - t1
|
4
|
+
rest, mins = rest.divmod( 60 )
|
5
|
+
days, hours = rest.divmod( 24 )
|
6
|
+
|
7
|
+
# the above can be factored out as:
|
8
|
+
# days, hours, mins, secs = self.duration_as_arr
|
9
|
+
#
|
10
|
+
# this is not so great, because it could include zero values:
|
11
|
+
# self.duration_as_arr.zip ['Days','Hours','Minutes','Seconds']).flatten.join ' '
|
12
|
+
|
13
|
+
result = []
|
14
|
+
result << "#{days} Days" if days > 0
|
15
|
+
result << "#{hours} Hours" if hours > 0
|
16
|
+
result << "#{mins} Minutes" if mins > 0
|
17
|
+
result << "#{secs} Seconds" if secs > 0
|
18
|
+
return result.join(' ')
|
19
|
+
end
|
20
|
+
end
|
data/lib/blinkr/http_utils.rb
CHANGED
@@ -2,13 +2,15 @@ require 'uri'
|
|
2
2
|
|
3
3
|
module Blinkr
|
4
4
|
module HttpUtils
|
5
|
-
|
6
|
-
def sanitize
|
5
|
+
|
6
|
+
def sanitize(dest, src)
|
7
|
+
return nil if (dest.nil? || src.nil?)
|
8
|
+
|
7
9
|
# src is the page that tried to load the URL
|
8
10
|
# URI fails to handle #! style fragments, so we chomp them
|
9
11
|
src = src[0, src.rindex('#!')] unless src.rindex('#!').nil?
|
10
12
|
src_uri = URI(src)
|
11
|
-
|
13
|
+
|
12
14
|
# Remove the query and fragment from the SRC URI, as we are going to use it to resolve the relative dest URIs
|
13
15
|
src_uri.query = nil
|
14
16
|
src_uri.fragment = nil
|
@@ -23,13 +25,16 @@ module Blinkr
|
|
23
25
|
dest_uri.fragment = nil if @config.ignore_fragments
|
24
26
|
|
25
27
|
# If we have a relative URI, or just a fragment, join what we have to our base URL
|
26
|
-
dest_uri = URI.join(src_uri, dest) if (
|
28
|
+
dest_uri = URI.join(src_uri, dest) if (empty?(dest_uri.path) && !empty?(dest_uri.fragment)) || dest_uri.relative?
|
27
29
|
|
28
30
|
# If we have an absolute path URI, join it to the base URL
|
29
31
|
dest_uri = URI.join(base_uri.scheme, base_uri.hostname, base_uri.port, dest_uri) if empty?(dest_uri.scheme) && empty?(dest_uri.hostname)
|
30
|
-
|
32
|
+
|
31
33
|
dest = dest_uri.to_s
|
32
34
|
rescue URI::InvalidURIError, URI::InvalidComponentError, URI::BadURIError
|
35
|
+
# ignored
|
36
|
+
rescue StandardError
|
37
|
+
return nil
|
33
38
|
end
|
34
39
|
dest.chomp('#').chomp('index.html')
|
35
40
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'manticore'
|
2
|
+
require 'blinkr/cache'
|
3
|
+
require 'blinkr/http_utils'
|
4
|
+
|
5
|
+
module Blinker
|
6
|
+
class ManticoreWrapper
|
7
|
+
include HttpUtils
|
8
|
+
|
9
|
+
attr_reader count
|
10
|
+
|
11
|
+
def initialize(config, context)
|
12
|
+
@config = config.validate
|
13
|
+
@client = Manticore::Client.new({:pool_max => (@config.maxconnects || 50),
|
14
|
+
:pool_max_per_route => 10
|
15
|
+
})
|
16
|
+
@count = 0
|
17
|
+
@context = context
|
18
|
+
end
|
19
|
+
|
20
|
+
def process_all(urls, limit, opts = {}, &block)
|
21
|
+
urls.each do |url|
|
22
|
+
process url, limit, opts, &block
|
23
|
+
end
|
24
|
+
@client.execute!
|
25
|
+
end
|
26
|
+
|
27
|
+
def process(url, limit, opts = {}, &block)
|
28
|
+
_process url, limit, limit, opts, &block
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
'manticore'
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def _process(url, limit, max, opts = {}, &block)
|
38
|
+
unless @config.skipped? url
|
39
|
+
resp = @client.async.get(url)
|
40
|
+
resp.on_complete do |resp|
|
41
|
+
if retry? resp
|
42
|
+
if limit > 1
|
43
|
+
puts "Loading #{url} via manticore (attempt #{max - limit + 2} of #{max})" if @config.verbose
|
44
|
+
_process(url, limit - 1, max, &Proc.new)
|
45
|
+
else
|
46
|
+
puts "Loading #{url} via manticore failed" if @config.verbose
|
47
|
+
response = @client.respond_with(:code => 0, :status_message => "Server timed out after #{max} retries",
|
48
|
+
:body => '').get(url)
|
49
|
+
block.call response, nil, nil
|
50
|
+
end
|
51
|
+
else
|
52
|
+
block.call resp, nil, nil
|
53
|
+
end
|
54
|
+
@count += 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -2,9 +2,9 @@ require 'typhoeus'
|
|
2
2
|
require 'ostruct'
|
3
3
|
require 'tempfile'
|
4
4
|
require 'blinkr/http_utils'
|
5
|
+
require 'blinkr/cache'
|
5
6
|
require 'parallel'
|
6
7
|
|
7
|
-
|
8
8
|
module Blinkr
|
9
9
|
class PhantomJSWrapper
|
10
10
|
include HttpUtils
|
@@ -17,16 +17,17 @@ module Blinkr
|
|
17
17
|
@config = config.validate
|
18
18
|
@context = context
|
19
19
|
@count = 0
|
20
|
+
@cache = Blinkr::Cache.new
|
20
21
|
end
|
21
22
|
|
22
|
-
def process_all urls, limit, &block
|
23
|
-
Parallel.each(urls, :in_threads => @config.phantomjs_threads) do |url|
|
24
|
-
process url, limit, &block
|
23
|
+
def process_all urls, limit, opts = {}, &block
|
24
|
+
Parallel.each(urls, :in_threads => (@config.phantomjs_threads || Parallel.processor_count * 2)) do |url|
|
25
|
+
process url, limit, opts, &block
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
def process url, limit, &block
|
29
|
-
_process url, limit, limit, &block
|
29
|
+
def process url, limit, opts = {}, &block
|
30
|
+
_process url, limit, limit, opts, &block
|
30
31
|
end
|
31
32
|
|
32
33
|
def name
|
@@ -35,15 +36,25 @@ module Blinkr
|
|
35
36
|
|
36
37
|
private
|
37
38
|
|
38
|
-
def _process url, limit, max, &block
|
39
|
+
def _process url, limit, max, opts = {}, &block
|
39
40
|
raise "limit must be set. url: #{url}, limit: #{limit}, max: #{max}" if limit.nil?
|
40
41
|
unless @config.skipped? url
|
41
42
|
Tempfile.open('blinkr') do|f|
|
43
|
+
# Try and sidestep any unnecessary requests by checking the cache
|
44
|
+
request = Typhoeus::Request.new(url)
|
45
|
+
if @cache.get(request)
|
46
|
+
return block.call response, @cache.get("#{url}-resourceErrors"), @cache.get("#{url}-javascriptErrors")
|
47
|
+
end
|
48
|
+
|
42
49
|
if system "phantomjs #{SNAP_JS} #{url} #{@config.viewport} #{f.path}"
|
43
50
|
json = JSON.load(File.read(f.path))
|
44
|
-
response = Typhoeus::Response.new(code
|
51
|
+
response = Typhoeus::Response.new(:code => 200, :body => json['content'], :effective_url => json['url'],
|
52
|
+
:mock => true)
|
45
53
|
response.request = Typhoeus::Request.new(url)
|
46
54
|
Typhoeus.stub(url).and_return(response)
|
55
|
+
@cache.set(response.request, response)
|
56
|
+
@cache.set("#{url}-resourceErrors", json['resourceErrors'])
|
57
|
+
@cache.set("#{url}-javascriptErrors", json['javascriptErrors'])
|
47
58
|
block.call response, json['resourceErrors'], json['javascriptErrors']
|
48
59
|
else
|
49
60
|
if limit > 1
|
@@ -51,9 +62,11 @@ module Blinkr
|
|
51
62
|
_process url, limit - 1, max, &block
|
52
63
|
else
|
53
64
|
puts "Loading #{url} via phantomjs failed" if @config.verbose
|
54
|
-
response = Typhoeus::Response.new(code
|
65
|
+
response = Typhoeus::Response.new(:code => 0, :status_message => "Server timed out after #{max} retries",
|
66
|
+
:mock => true)
|
55
67
|
response.request = Typhoeus::Request.new(url)
|
56
68
|
Typhoeus.stub(url).and_return(response)
|
69
|
+
@cache.set(response.request, response)
|
57
70
|
block.call response, nil, nil
|
58
71
|
end
|
59
72
|
end
|
data/lib/blinkr/report.html.slim
CHANGED
@@ -1,238 +1,214 @@
|
|
1
|
-
doctype html
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
.panel-body
|
37
|
-
- blinkr.severities.each do |severity, metadata|
|
1
|
+
doctype html
|
2
|
+
html[ng-app="blinkr"]
|
3
|
+
head
|
4
|
+
meta[http-equiv="Content-Type" content="text/html; charset=UTF-8"]
|
5
|
+
link[href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"]
|
6
|
+
link[href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet"]
|
7
|
+
title
|
8
|
+
| Blinkr
|
9
|
+
meta[content="width=device-width, initial-scale=1" name="viewport"]
|
10
|
+
body
|
11
|
+
div[ng-controller="ErrorCtrl"]
|
12
|
+
.container
|
13
|
+
h1
|
14
|
+
| Blinkr
|
15
|
+
p.lead
|
16
|
+
| Blackbox testing for websites
|
17
|
+
h2
|
18
|
+
' Pages
|
19
|
+
.label.label-default>
|
20
|
+
| {{numPages}}
|
21
|
+
' Errors
|
22
|
+
.label.label-default
|
23
|
+
| {{report.total}}
|
24
|
+
.panel.panel-default
|
25
|
+
.panel-heading
|
26
|
+
h4
|
27
|
+
a#control-filters[data-toggle="collapse" href="#filters"]
|
28
|
+
i.fa.fa-caret-square-o-down
|
29
|
+
| Filters
|
30
|
+
#filters.panel-collapse.collapse.in
|
31
|
+
.panel-body
|
32
|
+
form.row
|
33
|
+
#severities.col-md-2
|
34
|
+
.panel.panel-default
|
35
|
+
.panel-heading
|
38
36
|
.checkbox
|
39
37
|
label
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
38
|
+
' Severity
|
39
|
+
.panel-body[ng-repeat="(key, value) in filters.severities"]
|
40
|
+
.checkbox
|
41
|
+
label>
|
42
|
+
input.type>[ng-model="filters.severities[key].enabled" type="checkbox"]
|
43
|
+
' {{key}}
|
44
|
+
div[class="label label-{{key}}"]
|
45
|
+
' {{value.count}}
|
46
|
+
#category.col-md-5[style="padding-left:20px; border-left: 1px solid #ccc;"]
|
47
|
+
.row
|
48
|
+
.col-md-12
|
47
49
|
.panel.panel-default
|
48
50
|
.panel-heading
|
49
51
|
.checkbox
|
50
52
|
label
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
div class="page panel panel-#{page.max_severity}"
|
53
|
+
' Category
|
54
|
+
.panel-body[ng-repeat="(key, value) in filters.categories"]
|
55
|
+
.checkbox
|
56
|
+
label>
|
57
|
+
input.type[ng-model="filters.categories[key].enabled" type="checkbox"]
|
58
|
+
' {{key}}
|
59
|
+
.label.label-default
|
60
|
+
| {{value.count}}
|
61
|
+
#types.col-md-5[style="padding-left:20px; border-left: 1px solid #ccc;"]
|
62
|
+
.row
|
63
|
+
.col-md-12
|
64
|
+
.panel.panel-default
|
65
|
+
.panel-heading
|
66
|
+
.checkbox
|
67
|
+
label>
|
68
|
+
' Type
|
69
|
+
.panel-body[ng-repeat="(key, value) in filters.types"]
|
70
|
+
.checkbox
|
71
|
+
label>
|
72
|
+
input.type[ng-model="filters.types[key].enabled" type="checkbox"]
|
73
|
+
| {{key}}
|
74
|
+
.label.label-default
|
75
|
+
| {{value.count}}
|
76
|
+
.page[ng-repeat="(url,page) in report.pages | showPage:filters"]
|
77
|
+
div[class="panel panel-{{page.max_severity}}"]
|
78
78
|
.panel-heading
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
});
|
112
|
-
$('#p-#{index}').on('hide.bs.collapse', function () {
|
113
|
-
$('a#control-p-#{index}').html('<i class="fa fa-caret-square-o-right"></i>');
|
114
|
-
});
|
115
|
-
|
116
|
-
- if blinkr.pages.length == 0
|
117
|
-
.panel.panel-success
|
118
|
-
.panel-heading
|
119
|
-
a id='resources'
|
120
|
-
| No errors detected
|
79
|
+
.panel-title
|
80
|
+
ul.list-inline
|
81
|
+
li
|
82
|
+
a>[data-toggle="collapse" href="#"]
|
83
|
+
i.fa.fa-caret-square-o-right
|
84
|
+
| {{url}} ({{page.errors.length}} total errors)
|
85
|
+
li
|
86
|
+
a[href="{{url}}" target="_blank"]
|
87
|
+
i.fa.fa-external-link
|
88
|
+
li
|
89
|
+
a[href="{{url}}" target="_blank"]
|
90
|
+
i.fa.fa-file-code-o
|
91
|
+
.panel-body.collapse.panel-collapse
|
92
|
+
ul
|
93
|
+
li.list-group-item.error[ng-repeat="(i,page_error) in page.errors | displayError:filters"]
|
94
|
+
i.fa.fa-2x.fa-bookmark-o.pull-left
|
95
|
+
div[class="pull-right label label-{{page_error.severity}}"]
|
96
|
+
| {{page_error.code}} {{page_error.message}}
|
97
|
+
div
|
98
|
+
' {{page_error.title}}
|
99
|
+
a[href="{{page_error.title}}" target="_blank"]
|
100
|
+
i.fa.fa-external-link
|
101
|
+
pre
|
102
|
+
| {{page_error.snippet}}
|
103
|
+
script[src="http://code.jquery.com/jquery-2.1.4.min.js"]
|
104
|
+
script[src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"]
|
105
|
+
script[src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"]
|
106
|
+
javascript:
|
107
|
+
var blinkr = angular.module('blinkr', []);
|
108
|
+
blinkr.factory('report', function reportFactory() {
|
109
|
+
return #{{errors}};
|
110
|
+
});
|
121
111
|
|
122
|
-
|
123
|
-
|
112
|
+
blinkr.controller("ErrorCtrl", ['$scope', 'report', function ($scope, report) {
|
113
|
+
$scope.report = report;
|
114
|
+
$scope.filters = {severities: {}, categories: {}, types: {}};
|
115
|
+
$scope.report = report;
|
116
|
+
$scope.numPages = Object.keys(report.pages).length;
|
117
|
+
window.scope = $scope;
|
124
118
|
|
125
|
-
|
126
|
-
|
119
|
+
Object.keys($scope.report.severity).forEach(function (s) {
|
120
|
+
$scope.filters.severities[s] = {enabled: false, count: report.severity[s].count};
|
121
|
+
});
|
127
122
|
|
128
|
-
|
129
|
-
|
130
|
-
var category = $( this ).val();
|
131
|
-
var checked = $( this ).prop("checked");
|
132
|
-
$( "#filters #types input.type[type=checkbox][data-category=" + category + "]" ).each( function() {
|
133
|
-
$( this ).prop( "checked", checked );
|
123
|
+
Object.keys($scope.report.category).forEach(function (c) {
|
124
|
+
$scope.filters.categories[c] = {enabled: false, count: report.category[c].count};
|
134
125
|
});
|
135
|
-
});
|
136
|
-
filter();
|
137
|
-
}
|
138
126
|
|
139
|
-
|
140
|
-
|
141
|
-
var checked = $( this ).prop("checked");
|
142
|
-
$( "#filters #severities input.severity[type=checkbox]" ).each( function() {
|
143
|
-
$( this ).prop( "checked", checked );
|
127
|
+
Object.keys($scope.report.type).forEach(function (t) {
|
128
|
+
$scope.filters.types[t] = {enabled: false, count: report.type[t].count};
|
144
129
|
});
|
130
|
+
}]);
|
131
|
+
|
132
|
+
blinkr.filter('displayError', function () {
|
133
|
+
return function (errors, filters) {
|
134
|
+
var returned_errors = [];
|
135
|
+
errors.forEach(function (error) {
|
136
|
+
if (showError(error, filters)) {
|
137
|
+
returned_errors.push(error);
|
138
|
+
}
|
139
|
+
});
|
140
|
+
panelBind();
|
141
|
+
return returned_errors;
|
142
|
+
}
|
145
143
|
});
|
146
|
-
filter();
|
147
|
-
}
|
148
144
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
}
|
160
|
-
|
161
|
-
if (unchecked == 0) {
|
162
|
-
$( this ).prop( "checked", true );
|
163
|
-
} else {
|
164
|
-
$( this ).prop( "checked", false );
|
145
|
+
blinkr.filter('showPage', function () {
|
146
|
+
return function (all_pages, filters) {
|
147
|
+
var pages = {};
|
148
|
+
Object.keys(all_pages).forEach(function (url) {
|
149
|
+
var page = all_pages[url];
|
150
|
+
page.errors.forEach(function (error) {
|
151
|
+
if (showError(error, filters)) {
|
152
|
+
pages[url] = all_pages[url];
|
153
|
+
}
|
154
|
+
});
|
155
|
+
});
|
156
|
+
return pages;
|
165
157
|
}
|
166
158
|
});
|
167
|
-
}
|
168
159
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
if ( $( this ).prop( "checked" ) ) {
|
175
|
-
checked++;
|
176
|
-
} else {
|
177
|
-
unchecked++;
|
160
|
+
function enabledFilters(filters, filterName) {
|
161
|
+
var enabled = [];
|
162
|
+
Object.keys(filters[filterName]).forEach(function (f) {
|
163
|
+
if (filters[filterName][f].enabled) {
|
164
|
+
enabled.push(f);
|
178
165
|
}
|
179
166
|
});
|
180
|
-
|
181
|
-
|
182
|
-
} else {
|
183
|
-
$( this ).prop( "checked", false );
|
184
|
-
}
|
185
|
-
});
|
186
|
-
}
|
167
|
+
return enabled;
|
168
|
+
}
|
187
169
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
if (
|
195
|
-
|
196
|
-
}
|
197
|
-
});
|
198
|
-
$( "#filters #types input.type[type=checkbox]" ).each( function() {
|
199
|
-
if ( $( this ).prop( "checked" ) ) {
|
200
|
-
selectedTypes.push( $( this ).val() );
|
170
|
+
function showError(error, filters) {
|
171
|
+
var categories = enabledFilters(filters, 'categories'),
|
172
|
+
severities = enabledFilters(filters, 'severities'),
|
173
|
+
types = enabledFilters(filters, 'types'),
|
174
|
+
possibleReturn = false;
|
175
|
+
|
176
|
+
if (categories.length > 0 && categories.indexOf(error.category) > -1) {
|
177
|
+
possibleReturn = true;
|
201
178
|
}
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
var showType = false;
|
207
|
-
var showSeverity = false;
|
208
|
-
var ids = $( this ).attr("data-ids");
|
209
|
-
for (i = 0; i < selectedTypes.length; i++) {
|
210
|
-
if (ids.indexOf(selectedTypes[i]) >= 0) {
|
211
|
-
showType = true;
|
212
|
-
continue;
|
213
|
-
}
|
179
|
+
|
180
|
+
if (possibleReturn) {
|
181
|
+
if (severities.length > 0) {
|
182
|
+
possibleReturn = severities.indexOf(error.severity) > -1;
|
214
183
|
}
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
continue;
|
219
|
-
}
|
184
|
+
} else {
|
185
|
+
if (severities.length > 0) {
|
186
|
+
possibleReturn = severities.indexOf(error.severity) > -1;
|
220
187
|
}
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
188
|
+
}
|
189
|
+
|
190
|
+
if (possibleReturn) {
|
191
|
+
if (types.length > 0) {
|
192
|
+
possibleReturn = !!(types.length > 0 && types.indexOf(error.type) > -1);
|
226
193
|
}
|
227
|
-
});
|
228
|
-
if (count == 0) {
|
229
|
-
$( this ).hide();
|
230
194
|
} else {
|
231
|
-
|
195
|
+
if (types.length > 0) {
|
196
|
+
possibleReturn = !!(types.length > 0 && types.indexOf(error.type) > -1);
|
197
|
+
}
|
232
198
|
}
|
233
|
-
});
|
234
|
-
$( ".pages" ).show();
|
235
|
-
$( ".spinner" ).hide();
|
236
|
-
}
|
237
199
|
|
238
|
-
|
200
|
+
return possibleReturn;
|
201
|
+
}
|
202
|
+
|
203
|
+
window.panelBind = function () {
|
204
|
+
$('.panel-title').find('a[data-toggle=collapse]').on('click', function (e) {
|
205
|
+
e.preventDefault();
|
206
|
+
var target = $(this).parents('.panel').find('.panel-collapse');
|
207
|
+
target.collapse('toggle');
|
208
|
+
});
|
209
|
+
$('.page').find('panel-collapse').collapse({toggle: false});
|
210
|
+
};
|
211
|
+
|
212
|
+
angular.element(document).ready(function () {
|
213
|
+
panelBind();
|
214
|
+
});
|