blinkr 0.1.0 → 0.2.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.
@@ -0,0 +1,66 @@
1
+ .h2
2
+ | Duplicate Descriptions
3
+ .label.label-primary.pull-right = descriptions.length
4
+ - descriptions.each_with_index do |(description, pages), index|
5
+
6
+ .panel.panel-danger
7
+ .panel-heading
8
+ a> data-toggle="collapse" href="#meta-d-#{index}" id="control-meta-d-#{index}"
9
+ i.fa.fa-caret-square-o-right>
10
+ ' #{description}
11
+ .panel-body.collapse id="meta-d-#{index}"
12
+ ul.list-group
13
+ - pages.each do |url, page|
14
+ li.list-group-item
15
+ ' #{url}
16
+ a> href="#{url}" target='_blank'
17
+ i.fa.fa-external-link
18
+ a href="view-source:#{url}" target='_blank'
19
+ i.fa.fa-file-code-o
20
+ javascript:
21
+ $('#meta-d-#{index}').on('show.bs.collapse', function () {
22
+ $('a#control-meta-d-#{index}').html('<i class="fa fa-caret-square-o-down"></i>');
23
+ });
24
+ $('#meta-d-#{index}').on('hide.bs.collapse', function () {
25
+ $('a#control-meta-d-#{index}').html('<i class="fa fa-caret-square-o-right"></i>');
26
+ });
27
+
28
+ - if descriptions.length == 0
29
+ .panel.panel-success
30
+ .panel-heading
31
+ a id='resources'
32
+ | No duplicate descriptions detected
33
+
34
+ .h2
35
+ | Duplicate Titles
36
+ .label.label-primary.pull-right = titles.length
37
+ - titles.each_with_index do |(title, pages), index|
38
+
39
+ .panel.panel-danger
40
+ .panel-heading
41
+ a> data-toggle="collapse" href="#meta-t-#{index}" id="control-meta-t-#{index}"
42
+ i.fa.fa-caret-square-o-right>
43
+ ' #{title}
44
+ .panel-body.collapse id="meta-t-#{index}"
45
+ ul.list-group
46
+ - pages.each do |url, page|
47
+ li.list-group-item
48
+ ' #{url}
49
+ a> href="#{url}" target='_blank'
50
+ i.fa.fa-external-link
51
+ a href="view-source:#{url}" target='_blank'
52
+ i.fa.fa-file-code-o
53
+ javascript:
54
+ $('#meta-t-#{index}').on('show.bs.collapse', function () {
55
+ $('a#control-meta-t-#{index}').html('<i class="fa fa-caret-square-o-down"></i>');
56
+ });
57
+ $('#meta-t-#{index}').on('hide.bs.collapse', function () {
58
+ $('a#control-meta-t-#{index}').html('<i class="fa fa-caret-square-o-right"></i>');
59
+ });
60
+
61
+ - if titles.length == 0
62
+ .panel.panel-success
63
+ .panel-heading
64
+ a id='resources'
65
+ | No duplicate titles detected
66
+
@@ -0,0 +1,84 @@
1
+ require 'ostruct'
2
+ require 'slim'
3
+
4
+ module Blinkr
5
+ module Extensions
6
+ class Meta
7
+
8
+ TMPL = File.expand_path('meta.html.slim', File.dirname(__FILE__))
9
+
10
+ def initialize config
11
+ @config = config
12
+ @descriptions = {}
13
+ @titles = {}
14
+ end
15
+
16
+ def collect page
17
+ description page
18
+ title page
19
+ end
20
+
21
+ def analyze context, typhoeus
22
+ context.duplicate_descriptions = @descriptions.reject{ |description, pages| pages.length <= 1 }
23
+ context.duplicate_titles = @titles.reject{ |title, pages| pages.length <= 1 }
24
+ end
25
+
26
+ def append context
27
+ Slim::Template.new(TMPL).render(OpenStruct.new({ :descriptions => context.duplicate_descriptions, :titles => context.duplicate_titles }))
28
+ end
29
+
30
+ private
31
+
32
+ def title page
33
+ elms = page.body.css('title')
34
+ if elms.length > 1
35
+ snippets = []
36
+ lines = []
37
+ elms.each do |elm|
38
+ lines << elm.line
39
+ snippets << elm.to_s
40
+ end
41
+ page.errors << OpenStruct.new({ :severity => 'info', :category => 'HTML Compatibility/Correctness', :type => '<title> tag declared more than once', :title => %Q{<title> declared more than once (lines #{lines.join(', ')})}, :message => %Q{<title> declared more than onc}, :snippet => snippets.join('\n'), :icon => 'fa-header' })
42
+ elsif elms.empty?
43
+ page.errors << OpenStruct.new({ :severity => 'warning', :category => 'SEO', :type => '<title> tag missing', :title => %Q{<title> tag missing}, :message => %Q{<title> tag missing}, :icon => 'fa-header' })
44
+ else
45
+ title = elms.first.text
46
+ @titles[title] ||= {}
47
+ @titles[title][page.response.effective_url] = page
48
+ if title.length < 20
49
+ page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => 'page title too short', :title => %Q{<title> too short (line #{elms.first.line})}, :message => %Q{<title> too short (< 20 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
50
+ end
51
+ if title.length > 55
52
+ page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => 'page title too long', :title => %Q{<title> too long (line #{elms.first.line})}, :message => %Q{<title> too long (> 55 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
53
+ end
54
+ end
55
+ end
56
+
57
+ def description page
58
+ elms = page.body.css('meta[name=description]')
59
+ if elms.length > 1
60
+ snippets = []
61
+ lines = []
62
+ elms.each do |elm|
63
+ lines << elm.line
64
+ snippets << elm.to_s
65
+ end
66
+ page.errors << OpenStruct.new({ :severity => 'info', :category => 'HTML Compatibility/Correctness', :type => '<meta name="description"> tag declared more than once', :title => %Q{<meta name="description"> tag declared more than once (lines #{lines.join(', ')})}, :message => %Q{<meta name="description"> tag declared more than once}, :snippet => snippets.join('\n'), :icon => 'fa-header' })
67
+ elsif elms.empty?
68
+ page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => '<meta name="description"> tag missing', :title => %Q{<meta name="description"> tag missing}, :message => %Q{<meta name="description"> tag missing}, :icon => 'fa-header' })
69
+ else
70
+ desc = elms.first['content']
71
+ @descriptions[desc] ||= {}
72
+ @descriptions[desc][page.response.effective_url] = page
73
+ if desc.length < 60
74
+ page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => '<meta name="description"> too short', :title => %Q{<meta name="description"> too short (lines #{elms.first.line})}, :message => %Q{<meta name="description"> too short (< 60 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
75
+ end
76
+ if desc.length > 115
77
+ page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => '<meta name="description"> too long', :title => %Q{<meta name="description"> too long (lines #{elms.first.line})}, :message => %Q{<meta name="description"> too long (> 115 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
78
+ end
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,39 @@
1
+ Dir[ File.join( File.dirname(__FILE__), '*.rb' ) ].each do |f|
2
+ begin
3
+ require f
4
+ rescue LoadError => e
5
+ $LOG.warn "Missing required dependency to activate optional built-in extension #{File.basename(f)}\n #{e}" if $LOG.debug?
6
+ rescue StandardError => e
7
+ $LOG.warn "Missing runtime configuration to activate optional built-in extension #{File.basename(f)}\n #{e}" if $LOG.debug?
8
+ end
9
+ end
10
+
11
+ module Blinkr
12
+ module Extensions
13
+ class Pipeline
14
+
15
+ attr_reader :extensions
16
+
17
+ def initialize(&block)
18
+ @extensions = []
19
+ @block = block
20
+ end
21
+
22
+ def load config
23
+ begin
24
+ instance_exec config, &@block if @block && @block.arity == 1
25
+ instance_exec &@block if @block && @block.arity == 0
26
+ self
27
+ rescue Exception => e
28
+ abort("Failed to initialize pipeline: #{e}")
29
+ end
30
+ end
31
+
32
+ def extension(ext)
33
+ @extensions << ext
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,20 @@
1
+ module Blinkr
2
+ module Extensions
3
+ class Resources
4
+
5
+ def initialize config
6
+ @config = config
7
+ end
8
+
9
+ def collect page
10
+ page.resource_errors.each do |error|
11
+ start = error['errorString'].rindex('server replied: ')
12
+ message = error['errorString'].slice(start.nil? ? 0 : start + 16, error['errorString'].length) unless error['errorString'].nil?
13
+ code = error['errorCode'].nil? ? nil : error['errorCode'].to_i
14
+ page.errors << OpenStruct.new({ :severity => 'danger', :category => 'Resources missing', :type => 'Resource loading error', :title => error['url'], :code => code, :message => message, :icon => 'fa-file-image-o' })
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ require 'uri'
2
+
3
+ module Blinkr
4
+ module HttpUtils
5
+
6
+ def sanitize dest, src
7
+ # src is the page that tried to load the URL
8
+ # URI fails to handle #! style fragments, so we chomp them
9
+ src = src[0, src.rindex('#!')] unless src.rindex('#!').nil?
10
+ src_uri = URI(src)
11
+
12
+ # Remove the query and fragment from the SRC URI, as we are going to use it to resolve the relative dest URIs
13
+ src_uri.query = nil
14
+ src_uri.fragment = nil
15
+
16
+ # base is the web root of the site
17
+ base_uri = URI(@config.base_url)
18
+
19
+ begin
20
+
21
+ # dest is the URL we are trying to load
22
+ dest_uri = URI(dest)
23
+ dest_uri.fragment = nil if @config.ignore_fragments
24
+
25
+ # 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 ( empty?(dest_uri.path) && !empty?(dest_uri.fragment ) ) || dest_uri.relative?
27
+
28
+ # If we have an absolute path URI, join it to the base URL
29
+ 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
+
31
+ dest = dest_uri.to_s
32
+ rescue URI::InvalidURIError, URI::InvalidComponentError, URI::BadURIError
33
+ end
34
+ dest.chomp('#').chomp('index.html')
35
+ end
36
+
37
+ def retry? resp
38
+ resp.timed_out? || (resp.code == 0 && [ "Server returned nothing (no headers, no data)", "SSL connect error", "Failure when receiving data from the peer" ].include?(resp.return_message) )
39
+ end
40
+
41
+ private
42
+
43
+ def empty? str
44
+ str.nil? || str.empty?
45
+ end
46
+
47
+ end
48
+ end
49
+
@@ -0,0 +1,65 @@
1
+ require 'typhoeus'
2
+ require 'ostruct'
3
+ require 'tempfile'
4
+ require 'blinkr/http_utils'
5
+ require 'parallel'
6
+
7
+
8
+ module Blinkr
9
+ class PhantomJSWrapper
10
+ include HttpUtils
11
+
12
+ SNAP_JS = File.expand_path('snap.js', File.dirname(__FILE__))
13
+
14
+ attr_reader :count
15
+
16
+ def initialize config, context
17
+ @config = config.validate
18
+ @context = context
19
+ @count = 0
20
+ end
21
+
22
+ def process_all urls, limit, &block
23
+ Parallel.each(urls, :in_threads => @config.phantomjs_threads) do |url|
24
+ process url, limit, &block
25
+ end
26
+ end
27
+
28
+ def process url, limit, &block
29
+ _process url, limit, limit, &block
30
+ end
31
+
32
+ def name
33
+ 'phantomjs'
34
+ end
35
+
36
+ private
37
+
38
+ def _process url, limit, max, &block
39
+ unless @config.skipped? url
40
+ Tempfile.open('blinkr') do|f|
41
+ if system "phantomjs #{SNAP_JS} #{url} #{@config.viewport} #{f.path}"
42
+ json = JSON.load(File.read(f.path))
43
+ response = Typhoeus::Response.new(code: 200, body: json['content'], effective_url: json['url'], mock: true)
44
+ response.request = Typhoeus::Request.new(url)
45
+ Typhoeus.stub(url).and_return(response)
46
+ block.call response, json['resourceErrors'], json['javascriptErrors']
47
+ else
48
+ if limit > 1
49
+ puts "Loading #{url} via phantomjs (attempt #{max - limit + 2} of #{max})" if verbose
50
+ _process url, limit - 1, max, &block
51
+ else
52
+ puts "Loading #{url} via phantomjs failed" if @config.verbose
53
+ response = Typhoeus::Response.new(code: 0, status_message: "Server timed out after #{max} retries", mock: true)
54
+ response.request = Typhoeus::Request.new(url)
55
+ Typhoeus.stub(url).and_return(response)
56
+ block.call response, nil, nil
57
+ end
58
+ end
59
+ end
60
+ @count += 1
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -9,141 +9,230 @@ head
9
9
  body
10
10
 
11
11
  .container
12
- h1 Blinkr
13
- ol
14
- li
15
- a href='#links'
16
- p class="#{errors.links.empty? ? 'text-success' : 'text-danger'}"
17
- |Broken links
18
- - unless errors.javascript.nil?
19
- li
20
- a href='#javascript'
21
- p class="#{errors.javascript.empty? ? 'text-success' : 'text-danger'}"
22
- | Page load JavaScript errors
23
- - unless errors.resources.nil?
24
- li
25
- a href='#resources'
26
- p class="#{errors.resources.empty? ? 'text-success' : 'text-danger'}"
27
- | Resource loading errors
12
+ .row
13
+ h1 Blinkr
14
+ p.lead Blackbox testing for websites
28
15
 
29
- a id='links'
30
- h2
31
- |Broken links (
32
- code
33
- |a[href]
34
- |)
35
- - errors.links.each do |url, err|
36
- .panel.panel-danger
16
+ h2
17
+ | Pages
18
+ .label.label-primary.pull-right = blinkr.error_count
19
+ .panel.panel-default
37
20
  .panel-heading
38
- a data-toggle="collapse" href="#a-#{err.uid}" id="control-a-#{err.uid}"
39
- i.fa.fa-caret-square-o-right
40
- '
41
- a href="#{url}" target='_blank'
42
- | #{url}
43
- i.fa.fa-external-link
44
- .pull-right.badge
45
- - if err.code > 0 || !err.status_message.nil?
46
- span data-toggle="tooltip" title="Status message"
47
- ' #{err.code if err.code > 0} #{err.status_message}
48
- - elsif !err.return_message.nil? && err.return_message != 'No error'
49
- span data-toggle="tooltip" title="Return message"
50
- ' #{err.return_message}
51
- .panel-body.collapse id="a-#{err.uid}"
52
- - unless err.return_message.nil? || err.return_message == 'No error' || (err.code == 0 && err.status_message.nil?)
53
- |Message: #{err.return_message}
54
- ul.list-group
55
- - err.refs.each do |ref|
56
- li.list-group-item
57
- '#{ref.src}
58
- a href="#{ref.src}" target='_blank'
21
+ h4
22
+ a> data-toggle="collapse" href="#filters" id="control-filters"
23
+ i.fa.fa-caret-square-o-down
24
+ | Filters
25
+ .panel-collapse.collapse.in#filters
26
+ .panel-body
27
+ form.row
28
+ .col-md-4#severities
29
+ .panel.panel-default
30
+ .panel-heading
31
+ .checkbox
32
+ label
33
+ input.severities type="checkbox" value="severities" onchange="severities()" checked="true"
34
+ | Severity
35
+ .label.label-primary< = blinkr.error_count
36
+ .panel-body
37
+ - blinkr.severities.each do |severity, metadata|
38
+ .checkbox
39
+ label
40
+ input.severity type="checkbox" value="severity-#{metadata.id}" onchange="updateSeverities(); filter()" checked="true"
41
+ | #{severity.capitalize}
42
+ div< class="label label-#{severity}" = metadata.count
43
+ .col-md-8#types style="padding-left:20px; border-left: 1px solid #ccc;"
44
+ .row
45
+ - blinkr.categories.each do |category, metadata|
46
+ .col-md-6
47
+ .panel.panel-default
48
+ .panel-heading
49
+ .checkbox
50
+ label
51
+ input.category type="checkbox" value="category-#{metadata.id}" onchange="category()" checked="true"
52
+ | #{category}
53
+ - metadata.severities.each do |severity, count|
54
+ div< class="label label-#{severity}" = count
55
+ .panel-body
56
+ - metadata.types.each do |type, type_metadata|
57
+ .checkbox
58
+ label
59
+ input.type type="checkbox" value="type-#{metadata.id}-#{type_metadata.id}" onchange="updateCategory(); filter();" checked="true" data-category="category-#{metadata.id}"
60
+ | #{type}
61
+ - type_metadata.severities.each do |severity, count|
62
+ div< class="label label-#{severity}" = count
63
+ javascript:
64
+ $('#filters').on('show.bs.collapse', function () {
65
+ $('a#control-filters').html('<i class="fa fa-caret-square-o-down"></i>');
66
+ });
67
+ $('#filters').on('hide.bs.collapse', function () {
68
+ $('a#control-filters').html('<i class="fa fa-caret-square-o-right"></i>');
69
+ });
70
+ .spinner.center-block style="padding-top: 2em"
71
+ img src="http://static.jboss.org/www/images/_images_icons_ajax-loader-8.gif"
72
+ javascript:
73
+ $( '.spinner' ).hide();
74
+ #pages
75
+ - blinkr.pages.each_with_index do |(url, page), index|
76
+
77
+ div class="page panel panel-#{page.max_severity}"
78
+ .panel-heading
79
+ a> data-toggle="collapse" href="#p-#{index}" id="control-p-#{index}"
80
+ i.fa.fa-caret-square-o-right>
81
+ ' #{url}
82
+ a> href="#{url}" target='_blank'
59
83
  i.fa.fa-external-link
60
- '
61
- a href="view-source:#{ref.src}" target='_blank'
84
+ a href="view-source:#{url}" target='_blank'
62
85
  i.fa.fa-file-code-o
63
- .pull-right
64
- |(#{ref.src_location})
65
- pre #{ref.snippet}
66
- javascript:
67
- $('#a-#{err.uid}').on('show.bs.collapse', function () {
68
- $('a#control-a-#{err.uid}').html('<i class="fa fa-caret-square-o-down"></i>');
69
- });
70
- $('#a-#{err.uid}').on('hide.bs.collapse', function () {
71
- $('a#control-a-#{err.uid}').html('<i class="fa fa-caret-square-o-right"></i>');
72
- });
73
- - if errors.links.empty?
74
- .panel.panel-success
75
- .panel-heading
76
- a id='resources'
77
- | No broken links detected
78
- - unless errors.javascript.nil?
79
- a id='javascript'
80
- h2 Page load JavaScript errors
81
- - errors.javascript.each do |url, err|
82
- .panel.panel-danger
83
- .panel-heading
84
- a data-toggle="collapse" href="#j-#{err.uid}" id="control-j-#{err.uid}"
85
- i.fa.fa-caret-square-o-right
86
- '
87
- a href="#{url}" target='_blank'
88
- | #{url}
89
- i.fa.fa-external-link
90
- .panel-body.collapse id="j-#{err.uid}"
91
- ul.list-group
92
- - err.messages.each do |message|
93
- li.list-group-item
94
- pre= message.msg
95
- pre= message.trace
96
- javascript:
97
- $('#j-#{err.uid}').on('show.bs.collapse', function () {
98
- $('a#control-j-#{err.uid}').html('<i class="fa fa-caret-square-o-down"></i>');
99
- });
100
- $('#j-#{err.uid}').on('hide.bs.collapse', function () {
101
- $('a#control-j-#{err.uid}').html('<i class="fa fa-caret-square-o-right"></i>');
102
- });
103
- - if errors.javascript.empty?
104
- .panel.panel-success
105
- .panel-heading
106
- a id='javascript'
107
- | No JavaScript errors detected
108
-
109
- - unless errors.resources.nil?
110
- a id='resources'
111
- h2 Resource loading errors
112
- - errors.resources.each do |url, err|
113
- .panel.panel-danger
114
- .panel-heading
115
- a data-toggle="collapse" href="#r-#{err.uid}" id="control-r-#{err.uid}"
116
- i.fa.fa-caret-square-o-right
117
- '
118
- a href="#{url}" target='_blank'
119
- | #{url}
120
- i.fa.fa-external-link
121
- .panel-body.collapse id="r-#{err.uid}"
122
- ul.list-group
123
- - err.messages.each do |message|
124
- li.list-group-item
125
- '#{message.url}
126
- a href="#{message.url}" target='_blank'
127
- i.fa.fa-external-link
128
- .pull-right.badge.alert-danger
129
- - if message.errorCode > 0
130
- ' #{message.errorCode}
131
- - unless message.errorString.nil?
132
- | #{message.errorString}
86
+ .panel-body.collapse id="p-#{index}"
87
+ ul.list-group
88
+ - page.errors.each do |error|
89
+ == engine.transform page, error do
90
+ li.list-group-item.error data-ids="type-#{blinkr.categories[error.category].id}-#{blinkr.categories[error.category].types[error.type].id} severity-#{error.severity} category-#{blinkr.categories[error.category].id}"
91
+ - unless error.icon.nil?
92
+ i class="fa fa-2x #{error.icon} pull-left"
93
+ - unless error.code.nil? && error.message.nil?
94
+ div class="pull-right label label-#{error.severity}"
95
+ - unless error.code.nil?
96
+ ' #{error.code}
97
+ - unless error.message.nil?
98
+ | #{error.message}
99
+ div
100
+ ' #{error.title}
101
+ - if error.url
102
+ a href="#{error.url}" target='_blank'
103
+ i.fa.fa-external-link
104
+ - if error.detail
105
+ p= error.detail
106
+ - if error.snippet
107
+ pre #{error.snippet}
108
+ javascript:
109
+ $('#p-#{index}').on('show.bs.collapse', function () {
110
+ $('a#control-p-#{index}').html('<i class="fa fa-caret-square-o-down"></i>');
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
+ });
133
115
 
134
- javascript:
135
- $('#r-#{err.uid}').on('show.bs.collapse', function () {
136
- $('a#control-r-#{err.uid}').html('<i class="fa fa-caret-square-o-down"></i>');
137
- });
138
- $('#r-#{err.uid}').on('hide.bs.collapse', function () {
139
- $('a#control-r-#{err.uid}').html('<i class="fa fa-caret-square-o-right"></i>');
140
- });
141
- - if errors.resources.empty?
116
+ - if blinkr.pages.length == 0
142
117
  .panel.panel-success
143
118
  .panel-heading
144
119
  a id='resources'
145
- | No resource loading errors detected
146
-
120
+ | No errors detected
121
+
122
+ - engine.append(blinkr).each do |result|
123
+ == result
124
+
147
125
  javascript:
148
126
  $('[data-toggle="tooltip"]').tooltip({'placement': 'top'});
149
127
 
128
+ function category() {
129
+ $( "#filters #types input.category[type=checkbox]" ).each( function() {
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 );
134
+ });
135
+ });
136
+ filter();
137
+ }
138
+
139
+ function severities() {
140
+ $( "#filters #severities input.severities[type=checkbox]" ).each( function() {
141
+ var checked = $( this ).prop("checked");
142
+ $( "#filters #severities input.severity[type=checkbox]" ).each( function() {
143
+ $( this ).prop( "checked", checked );
144
+ });
145
+ });
146
+ filter();
147
+ }
148
+
149
+ function updateCategory() {
150
+ $( "#filters #types input.category[type=checkbox]" ).each( function() {
151
+ var category = $( this ).val();
152
+ var checked = 0;
153
+ var unchecked = 0;
154
+ $( "#filters #types input.type[type=checkbox][data-category=" + category + "]" ).each( function() {
155
+ if ( $( this ).prop( "checked" ) ) {
156
+ checked++;
157
+ } else {
158
+ unchecked++;
159
+ }
160
+ });
161
+ if (unchecked == 0) {
162
+ $( this ).prop( "checked", true );
163
+ } else {
164
+ $( this ).prop( "checked", false );
165
+ }
166
+ });
167
+ }
168
+
169
+ function updateSeverities() {
170
+ $( "#filters #severities input.severities[type=checkbox]" ).each( function() {
171
+ var checked = 0;
172
+ var unchecked = 0;
173
+ $( "#filters #severities input.severity[type=checkbox]" ).each( function() {
174
+ if ( $( this ).prop( "checked" ) ) {
175
+ checked++;
176
+ } else {
177
+ unchecked++;
178
+ }
179
+ });
180
+ if (unchecked == 0) {
181
+ $( this ).prop( "checked", true );
182
+ } else {
183
+ $( this ).prop( "checked", false );
184
+ }
185
+ });
186
+ }
187
+
188
+ function filter() {
189
+ $( ".pages" ).hide();
190
+ $( ".spinner" ).show();
191
+ var selectedTypes = [];
192
+ var selectedSeverities = [];
193
+ $( "#filters #severities input[type=checkbox]" ).each( function() {
194
+ if ( $( this ).prop( "checked" ) ) {
195
+ selectedSeverities.push( $( this ).val() );
196
+ }
197
+ });
198
+ $( "#filters #types input.type[type=checkbox]" ).each( function() {
199
+ if ( $( this ).prop( "checked" ) ) {
200
+ selectedTypes.push( $( this ).val() );
201
+ }
202
+ });
203
+ $( "#pages .page" ).each( function() {
204
+ var count = 0;
205
+ $( "li.error[data-ids]", this ).each( function() {
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
+ }
214
+ }
215
+ for (i = 0; i < selectedSeverities.length; i++) {
216
+ if (ids.indexOf(selectedSeverities[i]) >= 0) {
217
+ showSeverity = true;
218
+ continue;
219
+ }
220
+ }
221
+ if (showSeverity && showType) {
222
+ $( this ).show();
223
+ count++;
224
+ } else {
225
+ $( this ).hide();
226
+ }
227
+ });
228
+ if (count == 0) {
229
+ $( this ).hide();
230
+ } else {
231
+ $( this ).show();
232
+ }
233
+ });
234
+ $( ".pages" ).show();
235
+ $( ".spinner" ).hide();
236
+ }
237
+
238
+