kastner-clarity 0.9.7

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,130 @@
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
+ require File.dirname(__FILE__) + '/process_tree'
7
+
8
+ module Clarity
9
+ class NotFoundError < StandardError; end
10
+ class NotAuthenticatedError < StandardError; end
11
+ class InvalidParameterError < StandardError; end
12
+
13
+ module Server
14
+ include EventMachine::HttpServer
15
+ include Clarity::BasicAuth
16
+ include Clarity::ChunkHttp
17
+
18
+ attr_accessor :required_username, :required_password, :relative_root
19
+ attr_accessor :log_files
20
+
21
+ def self.run(options)
22
+
23
+ EventMachine::run do
24
+ EventMachine.epoll
25
+ EventMachine::start_server(options[:address], options[:port], self) do |a|
26
+ a.log_files = options[:log_files]
27
+ a.required_username = options[:username]
28
+ a.required_password = options[:password]
29
+ a.relative_root = options[:relative_root] || ""
30
+ end
31
+
32
+ STDERR.puts "Clarity #{Clarity::VERSION} starting up."
33
+ STDERR.puts " * listening on #{options[:address]}:#{options[:port]}"
34
+
35
+ if options[:user]
36
+ STDERR.puts " * Running as user #{options[:user]}"
37
+ EventMachine.set_effective_user(options[:user])
38
+ end
39
+
40
+ STDERR.puts " * Log mask(s): #{options[:log_files].join(', ')}"
41
+ STDERR.puts
42
+ end
43
+ end
44
+
45
+ def process_http_request
46
+ authenticate!
47
+
48
+ puts "action: #{path}"
49
+ puts "params: #{params.inspect}"
50
+
51
+ case path
52
+ when '/'
53
+ respond_with(200, welcome_page)
54
+
55
+ when '/perform'
56
+ if params.empty?
57
+ respond_with(200, welcome_page)
58
+ else
59
+ # get command
60
+ command = case params['tool']
61
+ when 'grep' then GrepCommandBuilder.new(params).command
62
+ when 'tail' then TailCommandBuilder.new(params).command
63
+ else raise InvalidParameterError, "Invalid Tool parameter"
64
+ end
65
+ response = respond_with_chunks
66
+ response.chunk results_page # display page header
67
+
68
+ puts "Running: #{command}"
69
+
70
+ EventMachine::popen(command, GrepRenderer) do |grepper|
71
+ @grepper = grepper
72
+ @grepper.response = response
73
+ end
74
+ end
75
+
76
+ when '/test'
77
+ response = respond_with_chunks
78
+ EventMachine::add_periodic_timer(1) do
79
+ response.chunk "Lorem ipsum dolor sit amet<br/>"
80
+ response.send_chunks
81
+ end
82
+
83
+ else
84
+ respond_with(200, public_file(path), :content_type => Mime.for(path))
85
+ end
86
+
87
+ rescue InvalidParameterError => e
88
+ respond_with(500, error_page(e))
89
+ rescue NotFoundError => e
90
+ respond_with(404, "<h1>Not Found</h1>")
91
+ rescue NotAuthenticatedError => e
92
+ puts "Could not authenticate user"
93
+ headers = { "WWW-Authenticate" => %(Basic realm="Clarity")}
94
+ respond_with(401, "HTTP Basic: Access denied.\n", :content_type => 'text/plain', :headers => headers)
95
+ end
96
+
97
+ def error_page(error)
98
+ @error = error
99
+ render "error.html.erb"
100
+ end
101
+
102
+ def welcome_page
103
+ render "index.html.erb"
104
+ end
105
+
106
+ def results_page
107
+ render "index.html.erb"
108
+ end
109
+
110
+ def unbind
111
+ @grepper.close_connection if @grepper
112
+ close_connection
113
+ end
114
+
115
+ private
116
+
117
+ def authenticate!
118
+ login, pass = authentication_data
119
+
120
+ if (required_username && required_username != login) || (required_password && required_password != pass)
121
+ raise NotAuthenticatedError
122
+ end
123
+
124
+ true
125
+ end
126
+
127
+ end
128
+
129
+
130
+ 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,59 @@
1
+ require 'erb'
2
+
3
+ module Clarity
4
+
5
+ module ChunkHttp
6
+
7
+ LeadIn = ' ' * 1024
8
+
9
+ def respond_with_chunks
10
+ response = EventMachine::DelegatedHttpResponse.new( self )
11
+ response.status = 200
12
+ response.headers['Content-Type'] = 'text/html'
13
+ response.chunk LeadIn
14
+ response
15
+ end
16
+
17
+ def respond_with(status, content, options = {})
18
+ response = EventMachine::DelegatedHttpResponse.new( self )
19
+ response.headers['Content-Type'] = options.fetch(:content_type, 'text/html')
20
+ response.headers['Cache-Control'] = 'private, max-age=0'
21
+ headers = options.fetch(:headers, {})
22
+ headers.each_pair {|h, v| response.headers[h] = v }
23
+ response.status = status
24
+ response.content = content
25
+ response.send_response
26
+ end
27
+
28
+ def render(view)
29
+ @toolbar = template("_toolbar.html.erb")
30
+ @content_for_header = template("_header.html.erb")
31
+ template(view)
32
+ end
33
+
34
+ def template(filename)
35
+ content = File.read( File.join(Clarity::Templates, filename) )
36
+ ERB.new(content).result(binding)
37
+ end
38
+
39
+ def public_file(filename)
40
+ File.read( File.join(Clarity::Public, filename) )
41
+ rescue Errno::ENOENT
42
+ raise NotFoundError
43
+ end
44
+
45
+ def logfiles
46
+ log_files.map {|f| Dir[f] }.flatten.compact.uniq.select{|f| File.file?(f) }.sort
47
+ end
48
+
49
+ def params
50
+ ENV['QUERY_STRING'].split('&').inject({}) {|p,s| k,v = s.split('=');p[k.to_s] = CGI.unescape(v.to_s);p}
51
+ end
52
+
53
+ def path
54
+ ENV["PATH_INFO"]
55
+ end
56
+
57
+ end
58
+
59
+ 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
+
@@ -0,0 +1,54 @@
1
+ /* ----------------------------------------------------------------------------
2
+ * Log Search Server CSS
3
+ * by John Tajima
4
+ * ----------------------------------------------------------------------------
5
+ */
6
+
7
+ /* 960 reset */
8
+ html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}
9
+ /* 960 text */
10
+ body{font:13px/1.5 'Helvetica Neue',Arial,'Liberation Sans',FreeSans,sans-serif}a:focus{outline:1px dotted invert}hr{border:0 #ccc solid;border-top-width:1px;clear:both;height:0}h1{font-size:25px}h2{font-size:23px}h3{font-size:21px}h4{font-size:19px}h5{font-size:17px}h6{font-size:15px}ol{list-style:decimal}ul{list-style:disc}li{margin-left:30px}p,dl,hr,h1,h2,h3,h4,h5,h6,ol,ul,pre,table,address,fieldset{margin-bottom:20px}
11
+
12
+ /*
13
+ * Toolbar Styling
14
+ *
15
+ */
16
+
17
+
18
+ #toolbar { color: #333; font-size: 12px; min-width: 980px; height: 130px;
19
+ padding: 0 10px; background: #f0f0f0; border-bottom: 2px solid #000;
20
+ position:fixed; top: 0; margin: 0 -10px; width: 100%;
21
+ font-family: 'Helvetica Neue',Arial,'Liberation Sans',FreeSans,sans-serif;
22
+ }
23
+ #toolbar a { color: blue; background: none; }
24
+ #toolbar a:hover { background: none; }
25
+
26
+ #toolbar #header { height: 20px; padding: 3px 5px; margin: 0 -10px 10px; overflow:hidden; background: #333; border-bottom: 2px solid #222; color: #eee; }
27
+ #toolbar #header h1 { font-size: 16px; line-height: 20px; }
28
+ #toolbar #header a { text-decoration: none; color: #eee; }
29
+ #toolbar #header a:hover { text-decoration: none; color: #fff;}
30
+ #toolbar .small { font-size: 11px; color: #333; font-weight: bold;}
31
+ #toolbar input.time { width: 20px;}
32
+
33
+ table.actions { width: auto; border-collapse: collapse; }
34
+ table.actions th { font-size: 11px; color: #333; text-align: left; min-width: 100px; padding: 0 5px; }
35
+ table.actions td { padding: 2px 5px; text-align: left; vertical-align: middle;}
36
+ table.actions span.note { font-weight: normal; color: #999; }
37
+ table.actions span.and { font-size: 11px; color: red; font-weight: bold; }
38
+ table.actions span.label { font-weight: bold; cursor:pointer;}
39
+ table.actions input[type=text] { width: 200px; padding: 2px; border: 1px solid #aaa;}
40
+
41
+
42
+ div#option-ctrl { position: fixed; z-index: 100; bottom: 0; right: 0; min-width: 60px; min-height: 20px; height: 20px; padding: 5px; border: 1px solid #ddd; background: #f0f0f0; font-size: 11px; color: #000;
43
+ font-family: 'Helvetica Neue',Arial,'Liberation Sans',FreeSans,sans-serif; }
44
+ div#option-ctrl ul { list-style:none; margin: 0; padding: 0;}
45
+ div#option-ctrl ul li { list-style:none; margin: 0 5px 0 0; float:left; }
46
+
47
+
48
+
49
+ /* output */
50
+ body { margin: 10px; padding-top: 130px; font-family: 'Monaco', 'Deja Vu Sans Mono', 'Inconsolata' ,'Consolas',monospace; background:#111 none repeat scroll 0 0; color:#fff; font-size:10px;}
51
+ a { color: #0f0; }
52
+ a:hover { background-color: #03c; color: white; text-decoration: none; }
53
+
54
+