clarity 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+