clarity 0.9.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,47 @@
1
+ require 'action_view'
2
+ require 'uri'
3
+
4
+ class LogRenderer
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Helpers::UrlHelper
7
+
8
+ Prefix = ""
9
+ Suffix = "<br/>\n"
10
+ TagOrder = [ :timestamp, :shop, :labels, :line ]
11
+ MarkTime = 60 * 5 # 5 minutes
12
+
13
+ def initialize()
14
+ @last_timestamp = nil
15
+ end
16
+
17
+ def render(elements = {})
18
+ @elements = elements
19
+ @tags = []
20
+ TagOrder.each do |tag|
21
+ if content = @elements.fetch(tag, nil)
22
+ method = ("tag_"+tag.to_s).to_sym
23
+ @tags << self.send(method, content)
24
+ end
25
+ end
26
+
27
+ @tags.empty? ? "" : Prefix + @tags.join(" ").to_s + Suffix
28
+ end
29
+
30
+
31
+ def tag_timestamp(content, options = {})
32
+ content + " : "
33
+ end
34
+
35
+ def tag_line(content, options = {})
36
+ ERB::Util.h(content)
37
+ end
38
+
39
+ def tag_shop(content, options = {})
40
+ "[<a href='http://#{URI.escape(content)}'>#{content}</a>]"
41
+ end
42
+
43
+ def tag_labels(content, options = {})
44
+ "[#{content}]"
45
+ end
46
+
47
+ end
@@ -0,0 +1,143 @@
1
+ require 'cgi'
2
+ require File.dirname(__FILE__) + '/server/basic_auth'
3
+ require File.dirname(__FILE__) + '/server/mime_types'
4
+ require File.dirname(__FILE__) + '/server/chunk_http'
5
+ require File.dirname(__FILE__) + '/grep_renderer'
6
+
7
+ module Clarity
8
+ class NotFoundError < StandardError; end
9
+ class NotAuthenticatedError < StandardError; end
10
+ class InvalidParameterError < StandardError; end
11
+
12
+ module Server
13
+ include EventMachine::HttpServer
14
+ include Clarity::BasicAuth
15
+ include Clarity::ChunkHttp
16
+
17
+ attr_accessor :required_username, :required_password
18
+ attr_accessor :log_files
19
+
20
+ def self.run(options)
21
+ EventMachine::run do
22
+ EventMachine.epoll
23
+ EventMachine::start_server(options[:address], options[:port], self) do |a|
24
+ a.log_files = options[:log_files]
25
+ a.required_username = options[:username]
26
+ a.required_password = options[:password]
27
+ end
28
+ STDERR.puts "Listening #{options[:address]}:#{options[:port]}..."
29
+ STDERR.puts "Adding log files: #{options[:log_files].inspect}"
30
+ end
31
+ end
32
+
33
+ def process_http_request
34
+ authenticate!
35
+
36
+ puts "action: #{path}"
37
+ puts "params: #{params.inspect}"
38
+
39
+ case path
40
+ when '/'
41
+ respond_with(200, welcome_page)
42
+
43
+ when '/perform'
44
+ if params.empty?
45
+ respond_with(200, welcome_page)
46
+ else
47
+ # get command
48
+ command = case params['tool']
49
+ when 'grep' then CommandBuilder.new(params).command
50
+ when 'tail' then TailCommandBuilder.new(params).command
51
+ else raise InvalidParameterError, "Invalid Tool parameter"
52
+ end
53
+ response = respond_with_chunks
54
+ response.chunk results_page # display page header
55
+
56
+ puts "Running: #{command}"
57
+ EventMachine::popen(command, GrepRenderer) do |grepper|
58
+ @grepper = grepper
59
+ @grepper.marker = 0
60
+ @grepper.params = params
61
+ @grepper.response = response
62
+ end
63
+ end
64
+
65
+ when '/test'
66
+ response = init_chunk_response
67
+ EventMachine::add_periodic_timer(1) do
68
+ response.chunk "Lorem ipsum dolor sit amet<br/>"
69
+ response.send_chunks
70
+ end
71
+
72
+ else
73
+ respond_with(200, public_file(path), :content_type => Mime.for(path))
74
+ end
75
+
76
+ rescue InvalidParameterError => e
77
+ respond_with(500, error_page(e))
78
+ rescue NotFoundError => e
79
+ respond_with(404, "<h1>Not Found</h1>")
80
+ rescue NotAuthenticatedError => e
81
+ puts "Could not authenticate user"
82
+ headers = { "WWW-Authenticate" => %(Basic realm="Clarity")}
83
+ respond_with(401, "HTTP Basic: Access denied.\n", :content_type => 'text/plain', :headers => headers)
84
+ end
85
+
86
+ def error_page(error)
87
+ @error = error
88
+ render "error.html.erb"
89
+ end
90
+
91
+ def welcome_page
92
+ render "index.html.erb"
93
+ end
94
+
95
+ def results_page
96
+ render "index.html.erb"
97
+ end
98
+
99
+ def unbind
100
+ return unless @grepper
101
+ kill_processes(@grepper.get_status.pid)
102
+ close_connection
103
+ end
104
+
105
+ def kill_processes(ppid)
106
+ return if ppid.nil?
107
+ all_pids = [ppid] + get_child_pids(ppid).flatten.uniq.compact
108
+ puts "=== pids are #{all_pids.inspect}"
109
+ all_pids.each do |pid|
110
+ Process.kill('TERM',pid.to_i)
111
+ puts "=== killing #{pid}"
112
+ end
113
+ rescue Exception => e
114
+ puts "!Error killing processes: #{e}"
115
+ end
116
+
117
+ def get_child_pids(ppid)
118
+ out = `ps -opid,ppid | grep #{ppid.to_s}`
119
+ ids = out.split("\n").map {|line| $1 if line =~ /^\s*([0-9]+)\s.*/ }.compact
120
+ ids.delete(ppid.to_s)
121
+ if ids.empty?
122
+ ids
123
+ else
124
+ ids << ids.map {|id| get_child_pids(id) }
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def authenticate!
131
+ login, pass = authentication_data
132
+
133
+ if (required_username && required_username != login) || (required_password && required_password != pass)
134
+ raise NotAuthenticatedError
135
+ end
136
+
137
+ true
138
+ end
139
+
140
+ end
141
+
142
+
143
+ end
@@ -0,0 +1,24 @@
1
+ module Clarity
2
+ module BasicAuth
3
+
4
+ def decode_credentials(request)
5
+ Base64.decode64(request).split.last
6
+ end
7
+
8
+ def user_name_and_password(request)
9
+ decode_credentials(request).split(/:/, 2)
10
+ end
11
+
12
+ def authentication_data
13
+ headers = @http_headers.split("\000")
14
+ auth_header = headers.detect {|head| head =~ /Authorization: / }
15
+ header = auth_header.nil? ? "" : auth_header.split("Authorization: Basic ").last
16
+ return (user_name_and_password(header) rescue ['', ''])
17
+ end
18
+
19
+ def authenticate!(http_header)
20
+ raise NotAuthenticatedError unless authenticate(http_header)
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,57 @@
1
+ module Clarity
2
+
3
+ module ChunkHttp
4
+
5
+ LeadIn = ' ' * 1024
6
+
7
+ def respond_with_chunks
8
+ response = EventMachine::DelegatedHttpResponse.new( self )
9
+ response.status = 200
10
+ response.headers['Content-Type'] = 'text/html'
11
+ response.chunk LeadIn
12
+ response
13
+ end
14
+
15
+ def respond_with(status, content, options = {})
16
+ response = EventMachine::DelegatedHttpResponse.new( self )
17
+ response.headers['Content-Type'] = options.fetch(:content_type, 'text/html')
18
+ response.headers['Cache-Control'] = 'private, max-age=0'
19
+ headers = options.fetch(:headers, {})
20
+ headers.each_pair {|h, v| response.headers[h] = v }
21
+ response.status = status
22
+ response.content = content
23
+ response.send_response
24
+ end
25
+
26
+ def render(view)
27
+ @toolbar = template("_toolbar.html.erb")
28
+ @content_for_header = template("_header.html.erb")
29
+ template(view)
30
+ end
31
+
32
+ def template(filename)
33
+ content = File.read( File.join(Clarity::Templates, filename) )
34
+ ERB.new(content).result(binding)
35
+ end
36
+
37
+ def public_file(filename)
38
+ File.read( File.join(Clarity::Public, filename) )
39
+ rescue Errno::ENOENT
40
+ raise NotFoundError
41
+ end
42
+
43
+ def logfiles
44
+ log_files.map {|f| Dir[f] }.flatten.compact.uniq.select{|f| File.file?(f) }.sort
45
+ end
46
+
47
+ def params
48
+ ENV['QUERY_STRING'].split('&').inject({}) {|p,s| k,v = s.split('=');p[k.to_s] = CGI.unescape(v.to_s);p}
49
+ end
50
+
51
+ def path
52
+ ENV["PATH_INFO"]
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,23 @@
1
+ module Clarity
2
+ module Mime
3
+ def self.for(filename)
4
+
5
+ content_type = TYPES[File.extname(filename)]
6
+ content_type || 'text/plain'
7
+ end
8
+
9
+ TYPES = {
10
+ '.jpg' => 'image/jpg',
11
+ '.jpeg' => 'image/jpeg',
12
+ '.gif' => 'image/gif',
13
+ '.png' => 'image/png',
14
+ '.bmp' => 'image/bmp',
15
+ '.bitmap' => 'image/x-ms-bmp',
16
+ '.js' => 'application/javascript',
17
+ '.txt' => 'text/plain',
18
+ '.css' => 'text/css',
19
+ '.html' => 'text/html',
20
+ '.htm' => 'text/html'
21
+ }
22
+ end
23
+ end
@@ -0,0 +1,154 @@
1
+ /* ----------------------------------------------------------------------------
2
+ * Log Search Server JS
3
+ * by John Tajima
4
+ * requires jQuery 1.3.2
5
+ * ----------------------------------------------------------------------------
6
+ */
7
+
8
+
9
+ var Search = {
10
+ search_form : 'search', // domId of the form
11
+ resultsId : 'results',
12
+ search_fields: [ 'term1', 'term2', 'term3' ], // domIds of search term fields
13
+ file_list : 'file-list', // domId of select for logfiles
14
+ logfiles : {}, // hash of log files
15
+ past_params : null, // recent request
16
+ url : '/perform',
17
+ scroll_fnId : null,
18
+
19
+ // initialize Search form
20
+ // { 'grep': [ log, files, for, grep], 'tail': [ 'log', 'files', 'for', 'tail']}
21
+ init: function(logfiles, params) {
22
+ this.logfiles = logfiles;
23
+ this.past_params = params;
24
+
25
+ this.bind_grep_tool();
26
+ this.bind_tail_tool();
27
+ this.bind_options();
28
+
29
+ if (!this.past_params) return; // return if no prev settings, nothing to set
30
+
31
+ // init tool selector
32
+ (this.past_params['tool'] == 'grep') ? $('#grep-label').trigger('click') : $('#tail-tool').trigger('click');
33
+
34
+ // init log file selector
35
+ $('#'+this.file_list).val(this.past_params['file']);
36
+
37
+ // init search fields
38
+ jQuery.each(this.search_fields, function(){
39
+ $('#'+this).val(Search.past_params[this]);
40
+ });
41
+
42
+ // advanced options usd?
43
+ // time was set - so show advanced options
44
+ if ((this.past_params['sh']) || (this.past_params['eh'])) {
45
+ this.showAdvanced();
46
+ if (this.past_params['sh']) {
47
+ jQuery.each(['sh', 'sm', 'ss'], function(){ $('#'+this).val(Search.past_params[this]) });
48
+ }
49
+ if (this.past_params['eh']) {
50
+ jQuery.each(['eh', 'em', 'es'], function(){ $('#'+this).val(Search.past_params[this]) });
51
+ }
52
+ }
53
+
54
+ },
55
+
56
+ // bind option selectors
57
+ bind_options: function() {
58
+ $('#auto-scroll').bind('change', function(){
59
+ Search.autoScroll(this.checked);
60
+ });
61
+ $('#auto-scroll').attr('checked', true).trigger('change'); // by default, turn on
62
+ },
63
+
64
+ // bind change grep tool
65
+ bind_grep_tool: function() {
66
+ $('#grep-tool').bind('change', function(e){
67
+ var newlist = ""
68
+ jQuery.each(Search.logfiles['grep'], function(){
69
+ newlist += "<option value='" + this + "'>" + this + "</option>\n"
70
+ });
71
+ $('#'+Search.file_list).html(newlist);
72
+ });
73
+ // watch clicking label as well
74
+ $('#grep-label').bind('click', function(e){
75
+ $('#grep-tool').attr('checked', 'checked').val('grep').trigger('change');
76
+ });
77
+ },
78
+
79
+
80
+ // bind change tail tool
81
+ bind_tail_tool: function() {
82
+ $('#tail-tool').bind('change', function(e){
83
+ var newlist = ""
84
+ jQuery.each(Search.logfiles['tail'], function(){
85
+ newlist += "<option value='" + this + "'>" + this + "</option>\n"
86
+ });
87
+ $('#'+ Search.file_list).html(newlist);
88
+ });
89
+ // watch clicking label as well
90
+ $('#tail-label').bind('click', function(e){
91
+ $('#tail-tool').attr('checked', 'checked').val('tail').trigger('change');
92
+ });
93
+ },
94
+
95
+
96
+ // clears the terms fields
97
+ clear: function() {
98
+ jQuery.each(this.search_fields, function(){
99
+ $('#'+this).val("");
100
+ });
101
+ },
102
+
103
+ showAdvanced: function() {
104
+ $('#enable-advanced').hide();
105
+ $('#disable-advanced').show();
106
+ $('.advanced-options').show();
107
+ },
108
+
109
+ hideAdvanced: function() {
110
+ this.clearAdvanced();
111
+ $('#enable-advanced').show();
112
+ $('#disable-advanced').hide();
113
+ $('.advanced-options').hide();
114
+ },
115
+
116
+ clearAdvanced: function() {
117
+ $('#advanced-options input').val("");
118
+ },
119
+
120
+ // gathers form elements and submits to proper url
121
+ submit: function() {
122
+ $('#'+this.search_form).submit();
123
+ $('#'+this.resultsId).html("Sending new query...");
124
+ },
125
+
126
+ //
127
+ // Misc utitilies
128
+ //
129
+
130
+ autoScroll: function(enabled) {
131
+ if (enabled == true) {
132
+ if (this.scroll_fnId) return; // already running
133
+
134
+ //console.log("scroll ON!")
135
+ window._currPos = 0; // init pos
136
+ this.scroll_fnId = setInterval( function(){
137
+ if (window._currPos < document.height) {
138
+ window.scrollTo(0, document.height);
139
+ window._currPos = document.height;
140
+ }
141
+ }, 100 );
142
+ } else {
143
+ if (!this.scroll_fnId) return;
144
+ //console.log("scroll off")
145
+ if (this.scroll_fnId) {
146
+ clearInterval(this.scroll_fnId);
147
+ window._currPost = 0;
148
+ this.scroll_fnId = null;
149
+ }
150
+ }
151
+ }
152
+
153
+ };
154
+