lib/logster/middleware/viewer.rb in logster-1.3.1 vs lib/logster/middleware/viewer.rb in logster-1.3.2

- old
+ new

@@ -1,220 +1,221 @@ -require 'json' - -module Logster - module Middleware - class Viewer - - PATH_INFO = "PATH_INFO".freeze - SCRIPT_NAME = "SCRIPT_NAME".freeze - REQUEST_METHOD = "REQUEST_METHOD".freeze - - def initialize(app) - @app = app - - @logs_path = Logster.config.subdirectory - @path_regex = Regexp.new("^(#{@logs_path}$)|^(#{@logs_path}(/.*))$") - @store = Logster.store or raise ArgumentError.new("store") - - @assets_path = File.expand_path("../../../../assets", __FILE__) - @fileserver = Rack::File.new(@assets_path) - end - - def call(env) - path = env[PATH_INFO] - script_name = env[SCRIPT_NAME] - - if script_name && script_name.length > 0 - path = script_name + path - end - - if resource = resolve_path(path) - if resource =~ /\.ico$|\.js$|\.png|\.handlebars$|\.css$|\.woff$|\.ttf$|\.woff2$|\.svg$|\.otf$|\.eot$/ - serve_file(env, resource) - - elsif resource.start_with?("/messages.json") - serve_messages(Rack::Request.new(env)) - - elsif resource =~ /\/message\/([0-9a-f]+)$/ - if env[REQUEST_METHOD] != "DELETE" - return [405, {}, ["GET not allowed for /clear"]] - end - - key = $1 - message = Logster.store.get(key) - unless message - return [404, {}, ["Message not found"]] - end - - Logster.store.delete(message) - return [301, {"Location" => "#{@logs_path}/"}, []] - - elsif resource =~ /\/(un)?protect\/([0-9a-f]+)$/ - off = $1 == "un" - key = $2 - - message = Logster.store.get(key) - unless message - return [404, {}, ["Message not found"]] - end - - if off - if Logster.store.unprotect(key) - return [301, {"Location" => "#{@logs_path}/show/#{key}?protected=false"}, []] - else - return [500, {}, ["Failed"]] - end - else - if Logster.store.protect(key) - return [301, {"Location" => "#{@logs_path}/show/#{key}?protected=true"}, []] - else - return [500, {}, ["Failed"]] - end - end - - elsif resource =~ /\/solve\/([0-9a-f]+)$/ - key = $1 - - message = Logster.store.get(key) - unless message - return [404, {}, ["Message not found"]] - end - - Logster.store.solve(key) - - return [301, {"Location" => "#{@logs_path}"}, []] - - elsif resource =~ /\/clear$/ - if env[REQUEST_METHOD] != "POST" - return [405, {}, ["GET not allowed for /clear"]] - end - Logster.store.clear - return [200, {}, ["Messages cleared"]] - - elsif resource =~ /\/show\/([0-9a-f]+)(\.json)?$/ - key = $1 - json = $2 == ".json" - - message = Logster.store.get(key) - unless message - return [404, {}, ["Message not found"]] - end - - if json - [200, {"Content-Type" => "application/json; charset=utf-8"}, [message.to_json]] - else - preload = preload_json({"/show/#{key}" => message}) - [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload)]] - end - - elsif resource == "/" - [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload_json)]] - - else - [404, {}, ["Not found"]] - end - else - @app.call(env) - end - end - - protected - - def serve_file(env, path) - env[PATH_INFO] = path - # accl redirect is going to be trouble, ensure its bypassed - env['sendfile.type'] = '' - @fileserver.call(env) - end - - def serve_messages(req) - params = req.params - - opts = { - before: params["before"], - after: params["after"] - } - - if(filter = params["filter"]) - filter = filter.split("_").map{|s| s.to_i} - opts[:severity] = filter - end - - if search = params["search"] - search = (parse_regex(search) || search) if params["regex_search"] == "true" - opts[:search] = search - end - - payload = { - messages: @store.latest(opts), - total: @store.count, - search: params['search'] || '', - filter: filter || '', - } - - json = JSON.generate(payload) - [200, {"Content-Type" => "application/json"}, [json]] - end - - def parse_regex(string) - if string =~ /\/(.+)\/(.*)/ - s = $1 - flags = Regexp::IGNORECASE if $2 && $2.include?("i") - Regexp.new(s, flags) rescue nil - end - end - - def resolve_path(path) - if path =~ @path_regex - $3 || "/" - end - end - - def preload_json(extra={}) - values = {} - values.merge!(extra) - end - - def css(name, attrs={}) - attrs = attrs.map do |k,v| - "#{k}='#{v}'" - end.join(" ") - - "<link rel='stylesheet' type='text/css' href='#{@logs_path}/stylesheets/#{name}' #{attrs}>" - end - - def script(prod, dev=nil) - name = ENV['DEBUG_JS'] == "1" && dev ? dev : prod - "<script src='#{@logs_path}/javascript/#{name}'></script>" - end - - def to_json_and_escape(payload) - Rack::Utils.escape_html(JSON.fast_generate(payload)) - end - - def body(preload) - root_url = @logs_path - root_url += "/" if root_url[-1] != "/" - <<~HTML - <!doctype html> - <html> - <head> - <link rel="shortcut icon" href="#{@logs_path}/images/icon_64x64.png"> - <link rel="apple-touch-icon" href="#{@logs_path}/images/icon_144x144.png" /> - <title>#{Logster.config.web_title || "Logs"}</title> - <link href='//fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'> - <link href='//fonts.googleapis.com/css?family=Roboto+Mono' rel='stylesheet' type='text/css'> - #{css("vendor.css")} - #{css("client-app.css")} - #{script("vendor.js")} - <meta id="preloaded-data" data-root-path="#{@logs_path}" data-preloaded="#{to_json_and_escape(preload)}"> - <meta name="client-app/config/environment" content="%7B%22modulePrefix%22%3A%22client-app%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22#{root_url}%22%2C%22locationType%22%3A%22history%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22client-app%22%2C%22version%22%3A%220.0.0+8c60a18b%22%7D%2C%22exportApplicationGlobal%22%3Afalse%7D" /> - </head> - <body> - #{script("client-app.js")} - </body> - </html> - HTML - end - end - end -end +require 'json' + +module Logster + module Middleware + class Viewer + + PATH_INFO = "PATH_INFO".freeze + SCRIPT_NAME = "SCRIPT_NAME".freeze + REQUEST_METHOD = "REQUEST_METHOD".freeze + + def initialize(app) + @app = app + + @logs_path = Logster.config.subdirectory + @path_regex = Regexp.new("^(#{@logs_path}$)|^(#{@logs_path}(/.*))$") + @store = Logster.store or raise ArgumentError.new("store") + + @assets_path = File.expand_path("../../../../assets", __FILE__) + @fileserver = Rack::File.new(@assets_path) + end + + def call(env) + path = env[PATH_INFO] + script_name = env[SCRIPT_NAME] + + if script_name && script_name.length > 0 + path = script_name + path + end + + if resource = resolve_path(path) + if resource =~ /\.ico$|\.js$|\.png|\.handlebars$|\.css$|\.woff$|\.ttf$|\.woff2$|\.svg$|\.otf$|\.eot$/ + serve_file(env, resource) + + elsif resource.start_with?("/messages.json") + serve_messages(Rack::Request.new(env)) + + elsif resource =~ /\/message\/([0-9a-f]+)$/ + if env[REQUEST_METHOD] != "DELETE" + return [405, {}, ["GET not allowed for /clear"]] + end + + key = $1 + message = Logster.store.get(key) + unless message + return [404, {}, ["Message not found"]] + end + + Logster.store.delete(message) + return [301, {"Location" => "#{@logs_path}/"}, []] + + elsif resource =~ /\/(un)?protect\/([0-9a-f]+)$/ + off = $1 == "un" + key = $2 + + message = Logster.store.get(key) + unless message + return [404, {}, ["Message not found"]] + end + + if off + if Logster.store.unprotect(key) + return [301, {"Location" => "#{@logs_path}/show/#{key}?protected=false"}, []] + else + return [500, {}, ["Failed"]] + end + else + if Logster.store.protect(key) + return [301, {"Location" => "#{@logs_path}/show/#{key}?protected=true"}, []] + else + return [500, {}, ["Failed"]] + end + end + + elsif resource =~ /\/solve\/([0-9a-f]+)$/ + key = $1 + + message = Logster.store.get(key) + unless message + return [404, {}, ["Message not found"]] + end + + Logster.store.solve(key) + + return [301, {"Location" => "#{@logs_path}"}, []] + + elsif resource =~ /\/clear$/ + if env[REQUEST_METHOD] != "POST" + return [405, {}, ["GET not allowed for /clear"]] + end + Logster.store.clear + return [200, {}, ["Messages cleared"]] + + elsif resource =~ /\/show\/([0-9a-f]+)(\.json)?$/ + key = $1 + json = $2 == ".json" + + message = Logster.store.get(key) + unless message + return [404, {}, ["Message not found"]] + end + + if json + [200, {"Content-Type" => "application/json; charset=utf-8"}, [message.to_json]] + else + preload = preload_json({"/show/#{key}" => message}) + [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload)]] + end + + elsif resource == "/" + [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload_json)]] + + else + [404, {}, ["Not found"]] + end + else + @app.call(env) + end + end + + protected + + def serve_file(env, path) + env[PATH_INFO] = path + # accl redirect is going to be trouble, ensure its bypassed + env['sendfile.type'] = '' + @fileserver.call(env) + end + + def serve_messages(req) + params = req.params + + opts = { + before: params["before"], + after: params["after"] + } + + if(filter = params["filter"]) + filter = filter.split("_").map{|s| s.to_i} + opts[:severity] = filter + end + + if search = params["search"] + search = (parse_regex(search) || search) if params["regex_search"] == "true" + opts[:search] = search + end + + payload = { + messages: @store.latest(opts), + total: @store.count, + search: params['search'] || '', + filter: filter || '', + } + + json = JSON.generate(payload) + [200, {"Content-Type" => "application/json"}, [json]] + end + + def parse_regex(string) + if string =~ /\/(.+)\/(.*)/ + s = $1 + flags = Regexp::IGNORECASE if $2 && $2.include?("i") + Regexp.new(s, flags) rescue nil + end + end + + def resolve_path(path) + if path =~ @path_regex + $3 || "/" + end + end + + def preload_json(extra={}) + values = {} + values.merge!(extra) + end + + def css(name, attrs={}) + attrs = attrs.map do |k,v| + "#{k}='#{v}'" + end.join(" ") + + "<link rel='stylesheet' type='text/css' href='#{@logs_path}/stylesheets/#{name}' #{attrs}>" + end + + def script(prod, dev=nil) + name = ENV['DEBUG_JS'] == "1" && dev ? dev : prod + "<script src='#{@logs_path}/javascript/#{name}'></script>" + end + + def to_json_and_escape(payload) + Rack::Utils.escape_html(JSON.fast_generate(payload)) + end + + def body(preload) + root_url = @logs_path + root_url += "/" if root_url[-1] != "/" + <<~HTML + <!doctype html> + <html> + <head> + <link rel="shortcut icon" href="#{@logs_path}/images/icon_64x64.png"> + <link rel="apple-touch-icon" href="#{@logs_path}/images/icon_144x144.png" /> + <title>#{Logster.config.web_title || "Logs"}</title> + <link href='//fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'> + <link href='//fonts.googleapis.com/css?family=Roboto+Mono' rel='stylesheet' type='text/css'> + <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=yes"> + #{css("vendor.css")} + #{css("client-app.css")} + #{script("vendor.js")} + <meta id="preloaded-data" data-root-path="#{@logs_path}" data-preloaded="#{to_json_and_escape(preload)}"> + <meta name="client-app/config/environment" content="%7B%22modulePrefix%22%3A%22client-app%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22#{root_url}%22%2C%22locationType%22%3A%22history%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22client-app%22%2C%22version%22%3A%220.0.0+8c60a18b%22%7D%2C%22exportApplicationGlobal%22%3Afalse%7D" /> + </head> + <body> + #{script("client-app.js")} + </body> + </html> + HTML + end + end + end +end