├── .gitignore ├── README.md ├── build ├── lib └── logreader.js ├── localConfig.js ├── package.json ├── runlocal ├── static ├── css │ ├── guiders-1.2.0.css │ ├── page.less │ └── redmond │ │ ├── images │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_55_fbec88_40x100.png │ │ ├── ui-bg_glass_75_d0e5f5_1x400.png │ │ ├── ui-bg_glass_85_dfeffc_1x400.png │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ ├── ui-bg_gloss-wave_55_5c9ccc_500x100.png │ │ ├── ui-bg_inset-hard_100_f5f8f9_1x100.png │ │ ├── ui-bg_inset-hard_100_fcfdfd_1x100.png │ │ ├── ui-icons_217bc0_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_469bdd_256x240.png │ │ ├── ui-icons_6da8d5_256x240.png │ │ ├── ui-icons_cd0a0a_256x240.png │ │ ├── ui-icons_d8e7f3_256x240.png │ │ └── ui-icons_f9bd01_256x240.png │ │ └── jquery-ui-1.8.16.custom.css ├── img │ ├── clock.gif │ ├── error.jpg │ ├── exception.jpg │ ├── fatal.jpg │ ├── guider_arrows.png │ ├── highlight.jpg │ ├── info.jpg │ ├── parts.jpg │ ├── promo_gmail.png │ ├── uncat.jpg │ ├── warn.jpg │ └── x_close_button.jpg ├── index.html └── js │ ├── all.jsc │ ├── all.min.jsc │ ├── date.format.js │ ├── date.format.min.js │ ├── guide.js │ ├── guide.min.js │ ├── guiders-1.2.0.js │ ├── guiders-1.2.0.min.js │ ├── jquery-ui-1.8.16.custom.min.js │ ├── jquery-ui.min.js │ ├── jquery.js │ ├── jquery.min.js │ ├── jstorage.js │ ├── jstorage.min.js │ ├── page.js │ ├── page.min.js │ ├── sha-256.js │ ├── sha-256.min.js │ ├── supergrep.js │ ├── supergrep.min.js │ ├── templating.js │ ├── templating.min.js │ ├── underscore.js │ └── underscore.min.js ├── stream.js └── supergrep.init /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | repos/ 3 | *.log 4 | *.old 5 | .DS_Store 6 | tmp/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Supergrep 2 | 3 | Supergrep is a web based log streamer written in node. It can be used quite nicely to surface new log lines (errors, etc.) that aren't normally expected. 4 | 5 | Essentially, having supergrep running in your browser while changes are being made allows for new/novel log patterns to show up, because under the hood, what we're really doing is: 6 | 7 | $ tail -f {log filename} | grep -v {stuff you'd expect to see in log lines} 8 | 9 | It's intended on being a noise reduction and change-awareness tool. 10 | 11 | ## This is an Archived Project 12 | 13 | Supergrep is no longer actively maintained and is no longer in sync with the version used internally at Etsy. 14 | 15 | ## Prerequisites 16 | 17 | In order to run supergrep, you need [NodeJS](http://nodejs.org/) and [npm](http://howtonode.org/introduction-to-npm) ( NodeJS package manager). 18 | 19 | ## Getting Started 20 | 21 | You can edit variables in localConfig.js and also in static/js/supergrep.js. Most of this should work out of the box but some of the cooler functionality is enabled by assuming certain pieces of data can be found in your logs. You'll have to adjust the regexes to suit your logs. 22 | 23 | Then you can install the node packages: 24 | 25 | $ npm install 26 | 27 | Run it from your install directory like: 28 | 29 | $ ./runlocal 30 | 31 | Point your browser to localhost (default port:3000) and then click on "Guide" to get a tour. 32 | 33 | 34 | ## Installation 35 | 36 | After setting this up locally and seeing how it works you'll probably want to run in production. There are many [init](http://en.wikipedia.org/wiki/Init) daemons that you can run this under. We've included a simple init.d script to get you started. This is known to work under RedHat flavors of linux. You will have to edit the OPTIONS to specify the path where supergrep is installed. It also sets a parameter for prodConfig.js that can take similar options as the localConfig.js that is included. 37 | 38 | ## Log Format 39 | 40 | Here is an example log line from our logs that this tool supports. You can modify it to support your log format. 41 | 42 | web0081 : [Tue Aug 28 13:55:05 2012] [error] [client 10.101.136.5] [e4G2F4OpKIEiCj-eAQuEKo-H-XDB] [error] [ClientLogger] [/somepath/lib/Logger.php:140] [0] uncaught: message="Object doesn't support this action" referrer="http://www.etsy.com/search?q=juliana+bracelets&view_type=gallery&ship_to=ZZ&min=0&max=0&page=8" data="{"url":"http://lognormal.net/boomerang/cb5494ba3c140e877cba92969c4c9f8cd712d8af2f307f956895dd1d","line":5,"userAgent":"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"}", referer: http://www.etsy.com/search?q=juliana+bracelets&view_type=gallery&ship_to=ZZ&min=0&max=0&page=9 43 | 44 | ## Contributing 45 | 46 | Patches welcome! 47 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./node_modules/.bin/uglifyjs static/js/guide.js > static/js/guide.min.js 3 | ./node_modules/.bin/uglifyjs static/js/page.js > static/js/page.min.js 4 | ./node_modules/.bin/uglifyjs static/js/supergrep.js > static/js/supergrep.min.js 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/logreader.js: -------------------------------------------------------------------------------- 1 | var ee = require('events').EventEmitter; 2 | var spawn = require('child_process').spawn; 3 | var util = require('util'); 4 | var sanitize = require('validator').sanitize; 5 | 6 | function LogReader (file, config) { 7 | var self = this; 8 | 9 | ee.call(this); 10 | this.setMaxListeners(0); 11 | 12 | if (!file.maxLines) { file.maxLines = config.defaultMaxLines; } 13 | 14 | this.file = file; 15 | this.lines = []; 16 | this.buffer = ""; 17 | this.paths = []; 18 | 19 | var args = ['-n', file.maxLines, '-F']; 20 | 21 | if (file.path instanceof Array) { 22 | file.path.forEach(function (path) { 23 | args.push(path); 24 | }); 25 | } else { 26 | args.push(file.path); 27 | } 28 | 29 | this.log = spawn('tail', args); 30 | 31 | this.log.stdout.on('data', function (data) { 32 | self.buffer += data; 33 | var lines = self.buffer.split(/\n/); 34 | 35 | if (lines.length) { 36 | self.buffer = lines.pop(); 37 | 38 | lines.forEach(function (line) { 39 | if (self.file.filter) { 40 | line = self.file.filter(line, config); 41 | } 42 | 43 | if (line) { 44 | line = sanitize(line).entityEncode(); 45 | self.lines.push(line); 46 | self.emit("line", line); 47 | } 48 | }); 49 | 50 | // resize internal array 51 | self.lines = self.lines.slice(-1 * self.file.maxLines); 52 | } 53 | }); 54 | } 55 | 56 | util.inherits(LogReader, ee); 57 | 58 | module.exports = LogReader; 59 | -------------------------------------------------------------------------------- /localConfig.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | dev: false, 3 | port: 3000, 4 | lockFile: '/var/run/stream.pid', 5 | cache: { 6 | enabled: false, 7 | defaultExpiration: 60 * 60 * 15 //15 minutes 8 | }, 9 | irccat: { 10 | host: 'irccat.yourdomain.com', 11 | port: 12345, 12 | prefix: 'SUPERGREP ', 13 | maxchars: 450 14 | }, 15 | files: [ 16 | { 17 | name: 'web', 18 | path: ['/var/log/httpd/info.log', '/var/log/httpd/php.log'] 19 | // you can also do this 20 | // filter: function (line, config) { 21 | // if (! line.match(config.noisyErrors)) { 22 | // return line; 23 | // } else { 24 | // return; 25 | // } 26 | // } 27 | // 28 | // make sure to define below (in the config namespace): 29 | // noisyErrors: new RegExp (/(something to ignore)/), 30 | }, 31 | // add more files entries here if you want 32 | { 33 | name: 'chef', 34 | maxLines: 200, 35 | path: '/var/log/chef/client.log', 36 | filter: function (line) { 37 | if (line.match(/./)) { 38 | return line; 39 | } else { 40 | return false; 41 | } 42 | } 43 | } 44 | ], 45 | blamebot: { 46 | 47 | // Where is your git repo? 48 | "git_checkout_dirs" : { 49 | web: { path: 'repos/Web/', git: 'git://github.com/somehwere.git' } 50 | // add more repos here 51 | }, 52 | 53 | // What git command would you like me to use? 54 | "git_command" : "/usr/bin/env git", 55 | 56 | // What git options to use for blame 57 | "git_blame_options" : [ 58 | 'blame' 59 | , '-p' 60 | , '-w' 61 | , '--since=13.weeks' 62 | ], 63 | 64 | // What git options to use for pulling 65 | "git_pull_options" : [ 66 | 'pull' 67 | , '--rebase' 68 | , '--stat' 69 | , 'origin' 70 | , 'master' 71 | ], 72 | 73 | // whether to clone repos 74 | "git_clone_enabled" : true, 75 | 76 | // What git options to use for cloning 77 | "git_clone_options" : [ 78 | 'clone' 79 | ], 80 | 81 | // How frequently should a repo be updated? 82 | refresh_time: 15000, 83 | 84 | // What URI should I respond to? 85 | "uri_match_regexp" : /^\/blame\/([^\/]+)\/(.*)\@(\d+)(?:,(\d+))/ 86 | // In this case, the URI "interface" works like this: 87 | // * Starting slash. 88 | // * The repo name (e.g. "Web/") 89 | // * The resource to get blame info on (e.g. phplib/somefile.php) 90 | // * The line number to get blame info on preceeded by a '@' (e.g. @23) 91 | // * Optional the ending line number to get blame info on preceeded by a ',' (e.g. '@5,10') 92 | // * Optional querystring parameter 'callback' for JSONP (e.g. ?callback=doSomething) 93 | // Untested on Windows, but you would send in the path as it would 94 | // work on your host OS. 95 | } 96 | 97 | }; 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supergrep" 3 | , "description": "Etsy Supergrep - streaming log viewer" 4 | , "version": "0.3.0" 5 | , "homepage": "http://blog.post.url/goes/here" 6 | , "keywords": ["etsy", "supergrep", "realtime", "log", "viewer", "streaming", "grep", "tail"] 7 | 8 | , "author": "Etsy Developers" 9 | , "contributors": [ 10 | { "name": "John Goulah", "email": "jgoulah@etsy.com" } 11 | , { "name": "Seth Walker", "email": "swalker@etsy.com" } 12 | , { "name": "Avleen Vig", "email": "avig@etsy.com" } 13 | , { "name": "Josh Halickman", "email": "jhalickman@etsy.com" } 14 | , { "name": "Erik Kastner", "email": "kastner@etsy.com" } 15 | , { "name": "Ramin Bozorgzadeh", "email": "ramin@etsy.com" } 16 | , { "name": "Chris Fairbanks", "email": "cfairbanks@etsy.com" } 17 | , { "name": "Marcus Barczak", "email": "mbarczak@etsy.com" } 18 | , { "name": "Chris Winberry", "email": "cwinberry@etsy.com" } 19 | , { "name": "Sam Haskins", "email": "shaskins@etsy.com" } 20 | , { "name": "Keyur Govande", "email": "kgovande@etsy.com" } 21 | ] 22 | 23 | , "repository":{ 24 | "type": "git" 25 | , "url": "https://github.com/etsy/supergrep" 26 | } 27 | 28 | , "dependencies": { 29 | "dateformat": ">=1.x" 30 | , "less": "1.1.x" 31 | , "mime": "1.2.x" 32 | , "socket.io": "0.8.x" 33 | , "uglify-js": "1.2.x" 34 | , "validator": "0.3.x" 35 | , "express": ">=2.5.x" 36 | } 37 | 38 | , "engines": { "node": ">= 0.6.0" } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /runlocal: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | node stream.js localConfig.js 3 | 4 | -------------------------------------------------------------------------------- /static/css/guiders-1.2.0.css: -------------------------------------------------------------------------------- 1 | .guider { 2 | background: #FFF; 3 | border: 1px solid #666; 4 | font-family: arial; 5 | position: absolute; 6 | outline: none; 7 | z-index: 100000005 !important; 8 | padding: 4px 12px; 9 | width: 500px; 10 | z-index: 100; 11 | 12 | /* Shadow */ 13 | -moz-box-shadow: 0 0px 8px #111; 14 | -webkit-box-shadow: 0 0px 8px #111; 15 | box-shadow: 0 0px 8px #111; 16 | /* End shadow */ 17 | 18 | /* Rounded corners */ 19 | -moz-border-radius: 4px; 20 | -webkit-border-radius: 4px; 21 | border-radius: 4px; 22 | /* End rounded corners */ 23 | } 24 | 25 | .guider_buttons { 26 | height: 36px; 27 | position: relative; 28 | width: 100%; 29 | } 30 | 31 | .guider_content { 32 | position: relative; 33 | } 34 | 35 | .guider_content h1 { 36 | color: #1054AA; 37 | float: left; 38 | font-size: 21px; 39 | } 40 | 41 | .guider_close { 42 | float: right; 43 | padding: 10px 0 0; 44 | } 45 | 46 | .x_button { 47 | background-image: url('../img/x_close_button.jpg'); 48 | cursor: pointer; 49 | height: 13px; 50 | width: 13px; 51 | } 52 | 53 | .guider_content p { 54 | clear: both; 55 | color: #333; 56 | font-size: 13px; 57 | } 58 | 59 | .guider_button { 60 | background: -moz-linear-gradient(top, #5CA9FF 0%, #3D79C3 100%); 61 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5CA9FF), color-stop(100%, #3D79C3)); 62 | background-color: #4A95E0; /* overruled by background gradient, in browsers where they exist */ 63 | border: solid 1px #4B5D7E; 64 | color: #FFF; 65 | cursor: pointer; 66 | display: inline-block; 67 | float: right; 68 | font-size: 75%; 69 | font-weight: bold; 70 | margin-left: 6px; 71 | min-width: 40px; 72 | padding: 3px 5px; 73 | text-align: center; 74 | text-decoration: none; 75 | /* Rounded corners */ 76 | -moz-border-radius: 2px; 77 | -webkit-border-radius: 2px; 78 | border-radius: 2px; 79 | /* End rounded corners */ 80 | } 81 | 82 | #guider_overlay { 83 | background-color: #000; 84 | width: 100%; 85 | height: 100%; 86 | position: fixed; 87 | top: 0px; 88 | left: 0px; 89 | opacity: 0.5; 90 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; 91 | filter: alpha(opacity=50); 92 | z-index: 10; 93 | } 94 | 95 | .guider_arrow { 96 | width: 42px; 97 | height: 42px; 98 | position: absolute; 99 | display: none; 100 | background-repeat: no-repeat; 101 | z-index: 100000006 !important; 102 | 103 | /** 104 | * For optimization, the arrows image is inlined in the css below. 105 | * 106 | * To use your own arrows image, replace this background-image with your own arrows. 107 | * It should have four arrows, top, right, left, and down. 108 | */ 109 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAACoCAYAAACWu3yIAAAJQ0lEQVR42u2cW2sVVxiGk2xz0EQFTRTBnEBFEpMLDxVyMPceoigRvVFjcqsSTaKCJAhC0Ozkpj+gFPIHWm2htPQfiChoVaqglDYeqP0Hdr3hXWFlZWb2WjNr1syGDHzilT48ew5r3u+bVXHgwIGCqCpWJerr168VeasKAVbPWi+qVtQ6CZ030J2sHaIaRW0UVZc3YIAeFPWNqP2iOkS1imrKGzBAz4g6L2pI1DFRfaL2acCZnxIV79+///PevXvfCYBpUeOihkUN5g0Yfywdr169WpycnPxZABRFTRL4RF6Al0Hl8eLFi88EntWAe7MEXgUqj+fPn3/KE3AoqAL88caNGz9lDVwSNC/AxqAq8NjY2CMCT4i65APYGlQez5498wocG1QDfigAHijAxwncSeBGHdg7qDyePn36IQS4h8AtBG4gcMEG2BmoCnzlypUfXQM7B1WAFxVgPJovKsBY/DSL2solZk2p8zc1UHk8efLkHwH8g4C4T+ALoo5yxbZH1HaevzVRZlMHlcfjx48l8Iyoq1yt9REWd4cNuNAyB1UM/3Xt2rUFATUm6rSoQzxvN4mqDvv5vYPK4+XLl3/cvXt3SoANiNolagt//nyBLi4u/r2wsPAtQXcTtDY3oO/evftSLBYf8sLCeXqYD4XNufjpBeB/MzMzv3Nhfl3UOdrcyyu/nk+tbEABKF51ADgv6raoEb7q9BByBy+k2kxuT2/fvtUBR0WdEnVEVLeoNt6W1CeUvxt+AOCIBtguahstGr+OV7gEFLeb3wh4yxWgM1AATk1N/RoA2O8CMDGoAPziAzA26Js3b/4l4JwPQGvQ169fBwGeTBvQGNQAsC1NwJKgALxz584vBLwp6rIC2OULMBRUZFCfCVjMA+AqUGHwYx4BV8SOYrHwPWPHCQLK2FEFzDTYVYPcs3z5yhVgWDTeqSwWcheNl02zoWzaN2XTECvQ6E6er2dwJ8jqpQ//Ny/wg2QCW6GCJiUoLqrzuF1lBcoOzXmySNAqCbqeF9N+3qam8QDwDYnODO/nQ2TZQbYl0EpeRI28PeFeOoGnlG9QNjfG2ZjrINPSu74EXcfbUhtv+Hg6FfHc9wWJthEf38NkaCXT0iv00hXFn7+ON/ouPkJv+rRKm5P8v/eRpU6+QkvQUKtY7qUNiZ4WewGBNpdBNavbaPWkL6uKzRNBNnVQ3Wo/rc6laRXtoFI2V4BGWcWrSFqgbLLpNlfFOzqoV6uazd4wm6tAI6zeSsOqqc0wUGl1k2IVb55zeKfPwmYgqC+rbE8a2YwCDbWKKMdFW9LGZihogNVul1Zpc8LUZinQMKvzSPAc2LxkajMSNMTqqaRW2di1smkCqlptT2oVDV32Rq1slgSNsop02ZdNU1AnVpPYNAKNsoqmgikoW+ITfIOwsmkDmsgqevdJbBqDRli9bWJVs9lpa9MWVLd6RFpFdy5qsECx2RPHphVoXKscJhhXbDba2owDGmR1NMwqph44onGRNlvi2LQGjbKKznFaNuOChp2rRfTi1ZEMzo9cUGw2xLEZCzTEKt7Fr2NgQIJybuQqJ3I6kthMAqqvrHo4KDCOEQzMi3C4ZYhhVzNtFpJ0RZJabeKAAKYZhjAnwqGW08q40NYkNpOCSqsNTN32cj5kgHVIGcCqT2IzEahitZanwHbezHdzPKhFaVrUJLHpArSSiXUtrW3mWNAWwm9wAZkYVIGt4mlQTega/t1Z48JZM0A2KtRy3Qsti1oDXQNdA012B5Gtz0IeAeU9uZbNsKWmch4B6/jYbeQaYqlNn0fAJi4dO9lmxDLxYJ4AtxGwi8vD4+zLooF7Jo+Ag2xwIAeYRqcbrfGsADdpgP0Mii9zlKmIRpza4c4lYFCzOC+AczQY2nXxCditAI5wIHEO6bVJsy1twPakgE5ADQFP6YBxGmu+AOcBmKRH5QswdrvHCtQA8IgOiGaEC0AjUEtAfBwwj6zUJWBJUO2dvUFZLKiAo2kDmoCqKYiMbHr4LF5hMCoaTxVUyZXqGdXIEOwcP/EpIrj1AWgCWs2IppXhFyLEcWSgamCbB9BaZkgIvQYQJyL7zGq4MAq0hqBI5gaQeSKgzSNoNS+kFmadCGbHEHkjn88FKGHXMTZsYiDbx/MUufwMOh5oz+QBtIo//0Ze+Xv4onWUnQ60Ze4DGN25LEErFdh65vDN7HD08OXrIoEfoN+J5qx3UM2s+oRq5HnbqQBf4suYBP7gHTQuMKYdXALHbTDIyEUmGq0E7g0CxmSOd9CQjEgmHK2cbujl3IgEnsWsUxJg16GWETDm8ryCxgAe5jzeLCYdbYDTjg2dAfvKOUsCY84ZQ9leQZMCYybfK6gFcJ8GXAQwPsnwCmoJPEhgPJqn8ZGLt9gxJvAxrtb8B7kW0XgrFz/ZRuNl12wou/ZN2TXE1nqha6BlDSquKJdVGVBO/m1XcOo4UQ3vgSvGifIAKt/9NzACkgNam3mzXt4nJ0tQNU1p4uvzLiaArUxYlnceyhK0oIS9eximySHCw8o2ScsDrk5BLQcItyoh2mnEk9zCa0jZeGrV55Ml/m2noAWaauZSDGBjyFAR+HLhe44pShtPAaOxYWegis1GrhuR8F1FdipfHbjv2HWu2LvZ9jGy6gRUG3BtoTHEkDNqwIucnx9Nj7Dd025q1RVoVcBn7uPISfU3R26Wdps9KWOriUG1D1ylTWSk94PSZ7R3uB/UqI1VF6DGNuXB/cmsrCYC1Wy20ibCrwdR0bhi1fhcTQoqbTapWzAgYS6VG9lajQ0aYLNX2jTJ7dHMVaz2l7KaBFS1uc/Gpmb1lonVWKAlbBpn9DZW44LqNhFmTSCbt02NuQFgSavWoK5s2lqNA+rMpjy4Y1ykVSvQCJuzSbobAVZXraxsQcNsPkraM1KsngyyarPCD7I5nNSmYvUL9+MLtGoDGmRz0oXNEKtdqlVT0FCbcRpZYQfm82ysGttEP8h1x9jGaiY25YGxTFq9rFjdGGQ1M5ua1ZulrEbZ7EvTpq1V3WadbhONqbTnRbj5ZaRVE5uf0gal1SKt9gVZDbM56MtmgNVBWm1SrUbZLAY1T9M6MHsfZVXfjq6Drb1xnzY1qxMBn7lXBm3whwxpWu3s+jrwyQU3+DsbtMHfqi0T0dHNaliQu8sGbplYFptQ/g/UqiA7u61evwAAAABJRU5ErkJggg==); 110 | *background-image: url('../img/guider_arrows.png'); 111 | } 112 | 113 | .guider_arrow_right { 114 | display: block; 115 | background-position: 0px 0px; 116 | right: -42px; 117 | } 118 | .guider_arrow_down { 119 | display: block; 120 | background-position: 0px -42px; 121 | bottom: -42px; 122 | } 123 | .guider_arrow_up { 124 | display: block; 125 | background-position: 0px -126px; 126 | top: -42px; 127 | } 128 | .guider_arrow_left { 129 | display: block; 130 | background-position: 0px -84px; 131 | left: -42px; 132 | } 133 | -------------------------------------------------------------------------------- /static/css/page.less: -------------------------------------------------------------------------------- 1 | @header-height: 75px; 2 | @header-border: 1px; 3 | @header-padding-top: 0; 4 | @header-padding-bottom: 0; 5 | 6 | @results-margin-top: @header-height + @header-border + @header-padding-top + @header-padding-bottom; 7 | 8 | @color-error: #f00; 9 | @color-warning: #ffd; 10 | @color-info: #00f; 11 | 12 | .rounded-corners (@radius: 3px) { 13 | border-radius: @radius; 14 | -webkit-border-radius: @radius; 15 | -moz-border-radius: @radius; 16 | } 17 | 18 | .highlight (@color: #fff) { 19 | /* 20 | background-color: lighten(@color, 40%); 21 | border: 1px solid desaturate(lighten(@color, 20%), 50%); 22 | */ 23 | background-color: hsl(hue(@color), 45%, 90%); 24 | border: 1px solid hsl(hue(@color), 45%, 80%); 25 | } 26 | 27 | .logentry-color (@color: #fff) { 28 | border-left-color: hsl(hue(@color), 45%, 50%); 29 | } 30 | 31 | body { 32 | font-family: menlo, courier, monospace; 33 | font-size: 11px; 34 | line-height: 1.5; 35 | margin: 0; 36 | padding: 0; 37 | color: #333; 38 | } 39 | 40 | header { 41 | background-color: #fff; 42 | text-align: center; 43 | position: fixed; 44 | top: 0; 45 | width: 100%; 46 | z-index: 100; 47 | border-bottom: @header-border solid #666; 48 | padding: @header-padding-top 0 @header-padding-bottom 0; 49 | height: @header-height; 50 | } 51 | 52 | header #classic-view { 53 | .rounded-corners(5px); 54 | border: 1px solid #999; 55 | color: #999; 56 | text-decoration: none; 57 | float: left; 58 | margin-left: 0.5em; 59 | margin-top: 5px; 60 | padding: 0.1em 0.4em; 61 | } 62 | 63 | header .title { 64 | margin: 25px 0 0 0; 65 | padding: 0; 66 | } 67 | 68 | header .config-profiles { 69 | display: none; 70 | float: left; 71 | clear: left; 72 | margin-left: 5px; 73 | } 74 | 75 | header .display-option-group { 76 | float: right; 77 | display: inline-block; 78 | border-left: 1px solid #999; 79 | padding-left: 10px; 80 | height: 100%; 81 | } 82 | 83 | header .display-option { 84 | float: right; 85 | clear: right; 86 | margin-right: 1em; 87 | } 88 | header .display-option .error { 89 | background-color: #f33; 90 | color: #fff; 91 | } 92 | 93 | header .display-option, 94 | header .display-option input { 95 | font-size: 0.9em; 96 | } 97 | 98 | header .display-option input { 99 | border: none; 100 | padding: 0; 101 | margin: 2px; 102 | width: 3em; 103 | text-align: center; 104 | background-color: #ccc; 105 | } 106 | 107 | header .display-option input { 108 | padding: 2px 0; 109 | } 110 | 111 | header .display-option textarea { 112 | width: 20em; 113 | height: 4em; 114 | padding: 0; 115 | margin: 0; 116 | } 117 | 118 | #filter-invert { 119 | float: right; 120 | } 121 | 122 | header #nav { margin-bottom: 5px; } 123 | header #nav, 124 | header #nav li { padding: 0; margin: 0; list-style: none; } 125 | header #nav li { display: inline-block; } 126 | header #nav li a { padding: 0 10px; } 127 | 128 | .hide { 129 | display: none; 130 | } 131 | 132 | #loading { 133 | font-size: 3em; 134 | margin-top: 5em; 135 | text-align: center; 136 | } 137 | 138 | .help-buttons { 139 | .rounded-corners(5px); 140 | border: 1px solid #999; 141 | color: #999; 142 | text-decoration: none; 143 | float: right; 144 | margin-right: 0.5em; 145 | margin-top: 5px; 146 | padding: 0.1em 0.4em; 147 | } 148 | 149 | .help { 150 | text-decoration: none; 151 | } 152 | .rule-help { 153 | float: right; 154 | } 155 | #rule-help-dialog, 156 | #legend-dialog, 157 | #export-help-dialog { 158 | display: none; 159 | } 160 | .code { 161 | .rounded-corners(6px); 162 | background-color: #efefef; 163 | color: #33c; 164 | margin: 1em 0.5em; 165 | padding: 0.5em 1em; 166 | white-space: pre; 167 | font-family: monospace; 168 | } 169 | 170 | #legend-dialog h1 { 171 | font-size: 1em; 172 | color: #666; 173 | clear: both; 174 | margin:0; 175 | margin-bottom: 2px; 176 | padding: 0; 177 | padding-top: 3x; 178 | border-top: 1px dotted #ddd; 179 | } 180 | #legend-dialog ul { 181 | float: left; 182 | list-style: none; 183 | } 184 | #legend-dialog li { 185 | font-size: 0.8em; 186 | color: #999; 187 | } 188 | #legend-dialog img { 189 | margin-left: 2em; 190 | } 191 | 192 | .clear-field { 193 | float: left; 194 | text-decoration: none; 195 | } 196 | 197 | #gistform { 198 | display: none; 199 | } 200 | 201 | #results { margin-top: @results-margin-top; } 202 | #results ul { margin: 0; padding: 0; } 203 | #results .log { position: relative; padding: 5px 10px; margin: 0; list-style: none; background-color: #fff; 204 | border-left: 10px solid #333; border-bottom: 1px dotted #ccc; color: #333; 205 | white-space: nowrap; 206 | } 207 | 208 | #results .log br { display: none; } 209 | 210 | #results .log a { color: #369; } 211 | #results .log.active { opacity: 1 !important; } 212 | 213 | #results .log.info, 214 | #results .log.debug { 215 | background-color: #f7f7f7; 216 | .logentry-color(@color-info); 217 | } 218 | #results .log.warning { 219 | .logentry-color(@color-warning); 220 | } 221 | #results .log.error { 222 | .logentry-color(@color-error); 223 | } 224 | #results .log.fatal { 225 | color: #fff; 226 | background-color: #c88; 227 | } 228 | 229 | #results .severity { 230 | .rounded-corners; 231 | font-weight: bold; 232 | width: 5.0em; 233 | display: inline-block; 234 | text-align: center; 235 | } 236 | #results .severity.error { 237 | .highlight(@color-error); 238 | } 239 | #results .severity.warning { 240 | .highlight(@color-warning); 241 | } 242 | #results .severity.info, 243 | #results .severity.debug { 244 | .highlight(@color-info); 245 | } 246 | #results .namespace.exception { 247 | .rounded-corners; 248 | border: 1px solid #d99; 249 | } 250 | 251 | #results .log .util { position: absolute; top: 0; right: 0; display: none; background-color: #666; color: #fff; padding: 3px 4px; } 252 | #results .log .util span { padding: 0 2px; } 253 | #results .log .util .remove { display: none; } 254 | #results .log .util:hover { font-weight: bold; } 255 | #results .log:hover .util { display: block; } 256 | 257 | #results .log.highlight { 258 | background-color: yellow; 259 | color: #000; 260 | } 261 | 262 | #results .log .meta-info { display: inline; } 263 | #results .log .stack { color: #999; } 264 | 265 | #results .log.active .meta-info { display: block; padding: 5px 10px; margin: -5px -10px 5px; } 266 | #results .log.active { white-space: pre-line; } 267 | #results .log.active br { display: block; } 268 | 269 | #results.wrapping .log { 270 | white-space: normal; 271 | overflow: auto; 272 | } 273 | 274 | #results .more-info { 275 | .rounded-corners; 276 | padding: 1px 2px; 277 | .highlight(#0f0); 278 | } 279 | 280 | #results .stacktrace, 281 | #results .rawdata { 282 | .rounded-corners(10px); 283 | display: none; 284 | position: relative; 285 | padding: 4px; 286 | border: 1px solid #5b5; 287 | white-space: normal; 288 | z-index: 50; 289 | background-color: white; 290 | } 291 | 292 | #results .gist, 293 | #results .irc, 294 | #results .export-help { 295 | float: right; 296 | text-decoration: none; 297 | } 298 | 299 | #results .stacktrace h1, 300 | #results .rawdata h1 { 301 | background-color: #efe; 302 | color: #5b5; 303 | font-size: 1.2em; 304 | margin: 0; 305 | padding: 0; 306 | text-align: center; 307 | width: 100%; 308 | } 309 | 310 | #results .stacktrace .container, 311 | #results .rawdata .container { 312 | padding: 0.4em 0.3em 0 0.3em; 313 | color: #000; 314 | overflow: auto; 315 | } 316 | 317 | #results .rawdata .container { 318 | font-family: monospace; 319 | } 320 | 321 | #results .stacktrace .raw { 322 | display: none; 323 | } 324 | 325 | #results .stacktrace ul { 326 | list-style: none; 327 | } 328 | 329 | #results .stacktrace .fileline { 330 | color: #999; 331 | font-weight: bold; 332 | width: 3em; 333 | text-align: right; 334 | display: inline-block; 335 | } 336 | 337 | #results-list { background-color: #eee; } 338 | #results-list.inactive { opacity: .6; } 339 | 340 | #results-list .log.info, 341 | #results-list .log.info { 342 | background-color: #eee; 343 | } 344 | #results-list .log.marker { background-color: #ccc; } 345 | 346 | #review-bucket { width: 100%; display: none; position: fixed; bottom: 0; max-height: 50%; overflow: auto; 347 | box-shadow: 0 -3px 3px rgba(0,0,0,0.2); border-top: 2px solid #333; margin-bottom: 30px; } 348 | #review-bucket header { margin-bottom: 0; position: fixed; bottom: 0; top: auto; width: 100%; border: 0; } 349 | #review-bucket .title { background-color: #333; color: #fff; font-size: 14px; } 350 | #review-bucket .log .util .review { display: none; } 351 | #review-bucket .log .util .remove { display: inline; } 352 | 353 | #results .matched_term { 354 | background-color: #dfd; 355 | border-top: 2px solid #3d3; 356 | border-right: 2px solid #3d3; 357 | border-bottom: 2px solid #3d3; 358 | } 359 | 360 | #results .masked_term { 361 | display: none; 362 | } 363 | 364 | #results a.blameLink { 365 | text-decoration: none; 366 | background-color: #00d; 367 | color: #0f0; 368 | font-weight: bold; 369 | padding: 1px 3px; 370 | .rounded-corners(2px); 371 | } 372 | 373 | .blameInfo { 374 | color: #d22; 375 | background-color: #eee; 376 | border: 1px dotted #ccc; 377 | } 378 | -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-icons_217bc0_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-icons_217bc0_256x240.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-icons_469bdd_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-icons_469bdd_256x240.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-icons_6da8d5_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-icons_6da8d5_256x240.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-icons_d8e7f3_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-icons_d8e7f3_256x240.png -------------------------------------------------------------------------------- /static/css/redmond/images/ui-icons_f9bd01_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/css/redmond/images/ui-icons_f9bd01_256x240.png -------------------------------------------------------------------------------- /static/css/redmond/jquery-ui-1.8.16.custom.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI CSS Framework 1.8.16 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Theming/API 9 | */ 10 | 11 | /* Layout helpers 12 | ----------------------------------*/ 13 | .ui-helper-hidden { display: none; } 14 | .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } 15 | .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } 16 | .ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } 17 | .ui-helper-clearfix { display: inline-block; } 18 | /* required comment for clearfix to work in Opera \*/ 19 | * html .ui-helper-clearfix { height:1%; } 20 | .ui-helper-clearfix { display:block; } 21 | /* end clearfix */ 22 | .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } 23 | 24 | 25 | /* Interaction Cues 26 | ----------------------------------*/ 27 | .ui-state-disabled { cursor: default !important; } 28 | 29 | 30 | /* Icons 31 | ----------------------------------*/ 32 | 33 | /* states and images */ 34 | .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } 35 | 36 | 37 | /* Misc visuals 38 | ----------------------------------*/ 39 | 40 | /* Overlays */ 41 | .ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } 42 | 43 | 44 | /* 45 | * jQuery UI CSS Framework 1.8.16 46 | * 47 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 48 | * Dual licensed under the MIT or GPL Version 2 licenses. 49 | * http://jquery.org/license 50 | * 51 | * http://docs.jquery.com/UI/Theming/API 52 | * 53 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande,%20Lucida%20Sans,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=02_glass.png&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px 54 | */ 55 | 56 | 57 | /* Component containers 58 | ----------------------------------*/ 59 | .ui-widget { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1.1em; } 60 | .ui-widget .ui-widget { font-size: 1em; } 61 | .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1em; } 62 | .ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; } 63 | .ui-widget-content a { color: #222222; } 64 | .ui-widget-header { border: 1px solid #4297d7; background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } 65 | .ui-widget-header a { color: #ffffff; } 66 | 67 | /* Interaction states 68 | ----------------------------------*/ 69 | .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #c5dbec; background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #2e6e9e; } 70 | .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #2e6e9e; text-decoration: none; } 71 | .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #79b7e7; background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1d5987; } 72 | .ui-state-hover a, .ui-state-hover a:hover { color: #1d5987; text-decoration: none; } 73 | .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #79b7e7; background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #e17009; } 74 | .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #e17009; text-decoration: none; } 75 | .ui-widget :active { outline: none; } 76 | 77 | /* Interaction Cues 78 | ----------------------------------*/ 79 | .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fad42e; background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; color: #363636; } 80 | .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } 81 | .ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } 82 | .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } 83 | .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } 84 | .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } 85 | .ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } 86 | .ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } 87 | 88 | /* Icons 89 | ----------------------------------*/ 90 | 91 | /* states and images */ 92 | .ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_469bdd_256x240.png); } 93 | .ui-widget-content .ui-icon {background-image: url(images/ui-icons_469bdd_256x240.png); } 94 | .ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); } 95 | .ui-state-default .ui-icon { background-image: url(images/ui-icons_6da8d5_256x240.png); } 96 | .ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_217bc0_256x240.png); } 97 | .ui-state-active .ui-icon {background-image: url(images/ui-icons_f9bd01_256x240.png); } 98 | .ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } 99 | .ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } 100 | 101 | /* positioning */ 102 | .ui-icon-carat-1-n { background-position: 0 0; } 103 | .ui-icon-carat-1-ne { background-position: -16px 0; } 104 | .ui-icon-carat-1-e { background-position: -32px 0; } 105 | .ui-icon-carat-1-se { background-position: -48px 0; } 106 | .ui-icon-carat-1-s { background-position: -64px 0; } 107 | .ui-icon-carat-1-sw { background-position: -80px 0; } 108 | .ui-icon-carat-1-w { background-position: -96px 0; } 109 | .ui-icon-carat-1-nw { background-position: -112px 0; } 110 | .ui-icon-carat-2-n-s { background-position: -128px 0; } 111 | .ui-icon-carat-2-e-w { background-position: -144px 0; } 112 | .ui-icon-triangle-1-n { background-position: 0 -16px; } 113 | .ui-icon-triangle-1-ne { background-position: -16px -16px; } 114 | .ui-icon-triangle-1-e { background-position: -32px -16px; } 115 | .ui-icon-triangle-1-se { background-position: -48px -16px; } 116 | .ui-icon-triangle-1-s { background-position: -64px -16px; } 117 | .ui-icon-triangle-1-sw { background-position: -80px -16px; } 118 | .ui-icon-triangle-1-w { background-position: -96px -16px; } 119 | .ui-icon-triangle-1-nw { background-position: -112px -16px; } 120 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; } 121 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; } 122 | .ui-icon-arrow-1-n { background-position: 0 -32px; } 123 | .ui-icon-arrow-1-ne { background-position: -16px -32px; } 124 | .ui-icon-arrow-1-e { background-position: -32px -32px; } 125 | .ui-icon-arrow-1-se { background-position: -48px -32px; } 126 | .ui-icon-arrow-1-s { background-position: -64px -32px; } 127 | .ui-icon-arrow-1-sw { background-position: -80px -32px; } 128 | .ui-icon-arrow-1-w { background-position: -96px -32px; } 129 | .ui-icon-arrow-1-nw { background-position: -112px -32px; } 130 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; } 131 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } 132 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; } 133 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; } 134 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; } 135 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; } 136 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; } 137 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; } 138 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; } 139 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; } 140 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; } 141 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; } 142 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; } 143 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; } 144 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; } 145 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; } 146 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } 147 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } 148 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } 149 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } 150 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } 151 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } 152 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } 153 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } 154 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } 155 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } 156 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } 157 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } 158 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; } 159 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; } 160 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; } 161 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; } 162 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } 163 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } 164 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } 165 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } 166 | .ui-icon-arrow-4 { background-position: 0 -80px; } 167 | .ui-icon-arrow-4-diag { background-position: -16px -80px; } 168 | .ui-icon-extlink { background-position: -32px -80px; } 169 | .ui-icon-newwin { background-position: -48px -80px; } 170 | .ui-icon-refresh { background-position: -64px -80px; } 171 | .ui-icon-shuffle { background-position: -80px -80px; } 172 | .ui-icon-transfer-e-w { background-position: -96px -80px; } 173 | .ui-icon-transferthick-e-w { background-position: -112px -80px; } 174 | .ui-icon-folder-collapsed { background-position: 0 -96px; } 175 | .ui-icon-folder-open { background-position: -16px -96px; } 176 | .ui-icon-document { background-position: -32px -96px; } 177 | .ui-icon-document-b { background-position: -48px -96px; } 178 | .ui-icon-note { background-position: -64px -96px; } 179 | .ui-icon-mail-closed { background-position: -80px -96px; } 180 | .ui-icon-mail-open { background-position: -96px -96px; } 181 | .ui-icon-suitcase { background-position: -112px -96px; } 182 | .ui-icon-comment { background-position: -128px -96px; } 183 | .ui-icon-person { background-position: -144px -96px; } 184 | .ui-icon-print { background-position: -160px -96px; } 185 | .ui-icon-trash { background-position: -176px -96px; } 186 | .ui-icon-locked { background-position: -192px -96px; } 187 | .ui-icon-unlocked { background-position: -208px -96px; } 188 | .ui-icon-bookmark { background-position: -224px -96px; } 189 | .ui-icon-tag { background-position: -240px -96px; } 190 | .ui-icon-home { background-position: 0 -112px; } 191 | .ui-icon-flag { background-position: -16px -112px; } 192 | .ui-icon-calendar { background-position: -32px -112px; } 193 | .ui-icon-cart { background-position: -48px -112px; } 194 | .ui-icon-pencil { background-position: -64px -112px; } 195 | .ui-icon-clock { background-position: -80px -112px; } 196 | .ui-icon-disk { background-position: -96px -112px; } 197 | .ui-icon-calculator { background-position: -112px -112px; } 198 | .ui-icon-zoomin { background-position: -128px -112px; } 199 | .ui-icon-zoomout { background-position: -144px -112px; } 200 | .ui-icon-search { background-position: -160px -112px; } 201 | .ui-icon-wrench { background-position: -176px -112px; } 202 | .ui-icon-gear { background-position: -192px -112px; } 203 | .ui-icon-heart { background-position: -208px -112px; } 204 | .ui-icon-star { background-position: -224px -112px; } 205 | .ui-icon-link { background-position: -240px -112px; } 206 | .ui-icon-cancel { background-position: 0 -128px; } 207 | .ui-icon-plus { background-position: -16px -128px; } 208 | .ui-icon-plusthick { background-position: -32px -128px; } 209 | .ui-icon-minus { background-position: -48px -128px; } 210 | .ui-icon-minusthick { background-position: -64px -128px; } 211 | .ui-icon-close { background-position: -80px -128px; } 212 | .ui-icon-closethick { background-position: -96px -128px; } 213 | .ui-icon-key { background-position: -112px -128px; } 214 | .ui-icon-lightbulb { background-position: -128px -128px; } 215 | .ui-icon-scissors { background-position: -144px -128px; } 216 | .ui-icon-clipboard { background-position: -160px -128px; } 217 | .ui-icon-copy { background-position: -176px -128px; } 218 | .ui-icon-contact { background-position: -192px -128px; } 219 | .ui-icon-image { background-position: -208px -128px; } 220 | .ui-icon-video { background-position: -224px -128px; } 221 | .ui-icon-script { background-position: -240px -128px; } 222 | .ui-icon-alert { background-position: 0 -144px; } 223 | .ui-icon-info { background-position: -16px -144px; } 224 | .ui-icon-notice { background-position: -32px -144px; } 225 | .ui-icon-help { background-position: -48px -144px; } 226 | .ui-icon-check { background-position: -64px -144px; } 227 | .ui-icon-bullet { background-position: -80px -144px; } 228 | .ui-icon-radio-off { background-position: -96px -144px; } 229 | .ui-icon-radio-on { background-position: -112px -144px; } 230 | .ui-icon-pin-w { background-position: -128px -144px; } 231 | .ui-icon-pin-s { background-position: -144px -144px; } 232 | .ui-icon-play { background-position: 0 -160px; } 233 | .ui-icon-pause { background-position: -16px -160px; } 234 | .ui-icon-seek-next { background-position: -32px -160px; } 235 | .ui-icon-seek-prev { background-position: -48px -160px; } 236 | .ui-icon-seek-end { background-position: -64px -160px; } 237 | .ui-icon-seek-start { background-position: -80px -160px; } 238 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ 239 | .ui-icon-seek-first { background-position: -80px -160px; } 240 | .ui-icon-stop { background-position: -96px -160px; } 241 | .ui-icon-eject { background-position: -112px -160px; } 242 | .ui-icon-volume-off { background-position: -128px -160px; } 243 | .ui-icon-volume-on { background-position: -144px -160px; } 244 | .ui-icon-power { background-position: 0 -176px; } 245 | .ui-icon-signal-diag { background-position: -16px -176px; } 246 | .ui-icon-signal { background-position: -32px -176px; } 247 | .ui-icon-battery-0 { background-position: -48px -176px; } 248 | .ui-icon-battery-1 { background-position: -64px -176px; } 249 | .ui-icon-battery-2 { background-position: -80px -176px; } 250 | .ui-icon-battery-3 { background-position: -96px -176px; } 251 | .ui-icon-circle-plus { background-position: 0 -192px; } 252 | .ui-icon-circle-minus { background-position: -16px -192px; } 253 | .ui-icon-circle-close { background-position: -32px -192px; } 254 | .ui-icon-circle-triangle-e { background-position: -48px -192px; } 255 | .ui-icon-circle-triangle-s { background-position: -64px -192px; } 256 | .ui-icon-circle-triangle-w { background-position: -80px -192px; } 257 | .ui-icon-circle-triangle-n { background-position: -96px -192px; } 258 | .ui-icon-circle-arrow-e { background-position: -112px -192px; } 259 | .ui-icon-circle-arrow-s { background-position: -128px -192px; } 260 | .ui-icon-circle-arrow-w { background-position: -144px -192px; } 261 | .ui-icon-circle-arrow-n { background-position: -160px -192px; } 262 | .ui-icon-circle-zoomin { background-position: -176px -192px; } 263 | .ui-icon-circle-zoomout { background-position: -192px -192px; } 264 | .ui-icon-circle-check { background-position: -208px -192px; } 265 | .ui-icon-circlesmall-plus { background-position: 0 -208px; } 266 | .ui-icon-circlesmall-minus { background-position: -16px -208px; } 267 | .ui-icon-circlesmall-close { background-position: -32px -208px; } 268 | .ui-icon-squaresmall-plus { background-position: -48px -208px; } 269 | .ui-icon-squaresmall-minus { background-position: -64px -208px; } 270 | .ui-icon-squaresmall-close { background-position: -80px -208px; } 271 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; } 272 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } 273 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; } 274 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; } 275 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } 276 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; } 277 | 278 | 279 | /* Misc visuals 280 | ----------------------------------*/ 281 | 282 | /* Corner radius */ 283 | .ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -khtml-border-top-left-radius: 5px; border-top-left-radius: 5px; } 284 | .ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; -khtml-border-top-right-radius: 5px; border-top-right-radius: 5px; } 285 | .ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; -khtml-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } 286 | .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; -khtml-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } 287 | 288 | /* Overlays */ 289 | .ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } 290 | .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* 291 | * jQuery UI Dialog 1.8.16 292 | * 293 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 294 | * Dual licensed under the MIT or GPL Version 2 licenses. 295 | * http://jquery.org/license 296 | * 297 | * http://docs.jquery.com/UI/Dialog#theming 298 | */ 299 | .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } 300 | .ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } 301 | .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } 302 | .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } 303 | .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } 304 | .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } 305 | .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } 306 | .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } 307 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } 308 | .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } 309 | .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } 310 | .ui-draggable .ui-dialog-titlebar { cursor: move; } 311 | -------------------------------------------------------------------------------- /static/img/clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/clock.gif -------------------------------------------------------------------------------- /static/img/error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/error.jpg -------------------------------------------------------------------------------- /static/img/exception.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/exception.jpg -------------------------------------------------------------------------------- /static/img/fatal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/fatal.jpg -------------------------------------------------------------------------------- /static/img/guider_arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/guider_arrows.png -------------------------------------------------------------------------------- /static/img/highlight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/highlight.jpg -------------------------------------------------------------------------------- /static/img/info.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/info.jpg -------------------------------------------------------------------------------- /static/img/parts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/parts.jpg -------------------------------------------------------------------------------- /static/img/promo_gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/promo_gmail.png -------------------------------------------------------------------------------- /static/img/uncat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/uncat.jpg -------------------------------------------------------------------------------- /static/img/warn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/warn.jpg -------------------------------------------------------------------------------- /static/img/x_close_button.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etsy/supergrep/1204a2c2420ff48601b6d78ae62cfb255fe5e23d/static/img/x_close_button.jpg -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Supergrep++ 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 | 41 |
42 | 43 |
44 | 45 |
46 | 47 |
Config Profiles
48 | 49 | Clear Log 50 | Legend 51 | Guide 52 | 53 |

∫upergrep++

54 | 55 |
56 | 57 |
58 | 59 |

Connecting...

60 | 61 | 62 | 63 |
64 | 65 |

Review Bucket

66 |
67 |
68 | 69 |
70 | 71 | 72 | 73 |
74 | 75 |
76 |

Rules can be either lists of strings to match or regular expressions.

77 |

A list consists of one or more strings separated by commas and/or new lines (CR/LF). Matching is case insensitive and terms are ORed together. Some examples:

78 |

parse, fatal, exception

79 |

parse 80 | fatal 81 | exception

82 |

parse 83 | fatal, last

84 | 85 |

Regular expressions are recognized by the format /expression/[options]. Some examples:

86 |

/(parse|fatal|exception)/

87 |

/php\s+fatal/i

88 |

/\.php\((\d+)\)$/i

89 | 90 |

For the filter rules, the default behavior is to hide log entries that match the filter rules. If the "Inverted" option is checked, then only those log entries that match the filter rules will be shown.

91 |
92 | 93 |
94 |
95 |

96 |

Elements of a log entry

97 | 98 | 104 | 110 | 115 |

116 |

117 |

PHP Fatal

118 | 119 |

120 |

121 |

Uncaught Exception

122 | 123 |

124 |

125 |

PHP Error

126 | 127 |

128 |

129 |

PHP Warning

130 | 131 |

132 |

133 |

PHP Info

134 | 135 |

136 |

137 |

Uncategorized

138 | 139 |

140 |

141 |

Highlighted

142 | 143 |

144 |
145 |
146 | 147 |
148 |

Raw logs and stack traces can be exported to either a gist or IRC (via irccat)

149 |

Sending log data to a gist is as simple as clicking the [GIST] link.

150 |

Exporting to IRC consists of clicking the [IRC] link and then entering the channel or nick to send the data to. To specify a channel, use the format #channel:

151 |

#push 152 | #hardware 153 | #etsy

154 |

To specify a nick, use the format @nick:

155 |

@irccat 156 | @dottie

157 |

In addition to the channel/nick, you can also add a note, which will be displayed above the log data. Some examples:

158 |

@milo Was this you? 159 | #etsy LOL, wut?

160 |
161 | 162 | 167 | 168 | 171 | 172 | 175 | 176 | 179 | 180 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /static/js/all.jsc: -------------------------------------------------------------------------------- 1 | jquery.js 2 | jquery-ui-1.8.16.custom.min.js 3 | date.format.js 4 | jstorage.js 5 | templating.js 6 | sha-256.js 7 | guiders-1.2.0.js 8 | supergrep.js 9 | page.js 10 | guide.js 11 | -------------------------------------------------------------------------------- /static/js/all.min.jsc: -------------------------------------------------------------------------------- 1 | jquery.min.js 2 | jquery-ui-1.8.16.custom.min.js 3 | date.format.min.js 4 | jstorage.min.js 5 | templating.min.js 6 | sha-256.min.js 7 | guiders-1.2.0.min.js 8 | supergrep.min.js 9 | page.min.js 10 | guide.min.js 11 | -------------------------------------------------------------------------------- /static/js/date.format.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Date Format 1.2.3 3 | * (c) 2007-2009 Steven Levithan 4 | * MIT license 5 | * 6 | * Includes enhancements by Scott Trenda 7 | * and Kris Kowal 8 | * 9 | * Accepts a date, a mask, or a date and a mask. 10 | * Returns a formatted version of the given date. 11 | * The date defaults to the current date/time. 12 | * The mask defaults to dateFormat.masks.default. 13 | */ 14 | 15 | var dateFormat = function () { 16 | var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, 17 | timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[\-+]\d{4})?)\b/g, 18 | timezoneClip = /[^\-+\dA-Z]/g, 19 | pad = function (val, len) { 20 | val = String(val); 21 | len = len || 2; 22 | while (val.length < len) val = "0" + val; 23 | return val; 24 | }; 25 | 26 | // Regexes and supporting functions are cached through closure 27 | return function (date, mask, utc) { 28 | var dF = dateFormat; 29 | 30 | // You can't provide utc if you skip other args (use the "UTC:" mask prefix) 31 | if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { 32 | mask = date; 33 | date = undefined; 34 | } 35 | 36 | // Passing date through Date applies Date.parse, if necessary 37 | date = date ? new Date(date) : new Date(); 38 | if (isNaN(date)) throw SyntaxError("invalid date"); 39 | 40 | mask = String(dF.masks[mask] || mask || dF.masks["default"]); 41 | 42 | // Allow setting the utc argument via the mask 43 | if (mask.slice(0, 4) == "UTC:") { 44 | mask = mask.slice(4); 45 | utc = true; 46 | } 47 | 48 | var _ = utc ? "getUTC" : "get", 49 | d = date[_ + "Date"](), 50 | D = date[_ + "Day"](), 51 | m = date[_ + "Month"](), 52 | y = date[_ + "FullYear"](), 53 | H = date[_ + "Hours"](), 54 | M = date[_ + "Minutes"](), 55 | s = date[_ + "Seconds"](), 56 | L = date[_ + "Milliseconds"](), 57 | o = utc ? 0 : date.getTimezoneOffset(), 58 | flags = { 59 | d: d, 60 | dd: pad(d), 61 | ddd: dF.i18n.dayNames[D], 62 | dddd: dF.i18n.dayNames[D + 7], 63 | m: m + 1, 64 | mm: pad(m + 1), 65 | mmm: dF.i18n.monthNames[m], 66 | mmmm: dF.i18n.monthNames[m + 12], 67 | yy: String(y).slice(2), 68 | yyyy: y, 69 | h: H % 12 || 12, 70 | hh: pad(H % 12 || 12), 71 | H: H, 72 | HH: pad(H), 73 | M: M, 74 | MM: pad(M), 75 | s: s, 76 | ss: pad(s), 77 | l: pad(L, 3), 78 | L: pad(L > 99 ? Math.round(L / 10) : L), 79 | t: H < 12 ? "a" : "p", 80 | tt: H < 12 ? "am" : "pm", 81 | T: H < 12 ? "A" : "P", 82 | TT: H < 12 ? "AM" : "PM", 83 | Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), 84 | o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 85 | S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] 86 | }; 87 | 88 | return mask.replace(token, function ($0) { 89 | return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); 90 | }); 91 | }; 92 | }(); 93 | 94 | // Some common format strings 95 | dateFormat.masks = { 96 | "default": "ddd mmm dd yyyy HH:MM:ss", 97 | shortDate: "m/d/yy", 98 | mediumDate: "mmm d, yyyy", 99 | longDate: "mmmm d, yyyy", 100 | fullDate: "dddd, mmmm d, yyyy", 101 | shortTime: "h:MM TT", 102 | mediumTime: "h:MM:ss TT", 103 | longTime: "h:MM:ss TT Z", 104 | isoDate: "yyyy-mm-dd", 105 | isoTime: "HH:MM:ss", 106 | isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", 107 | isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" 108 | }; 109 | 110 | // Internationalization strings 111 | dateFormat.i18n = { 112 | dayNames: [ 113 | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 114 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 115 | ], 116 | monthNames: [ 117 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 118 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 119 | ] 120 | }; 121 | 122 | // For convenience... 123 | Date.prototype.format = function (mask, utc) { 124 | return dateFormat(this, mask, utc); 125 | }; 126 | -------------------------------------------------------------------------------- /static/js/date.format.min.js: -------------------------------------------------------------------------------- 1 | var dateFormat=function(){var j=/d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,k=/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[\-+]\d{4})?)\b/g,r=/[^\-+\dA-Z]/g,d=function(a,c){a=""+a;for(c=c||2;a.lengthe?"a":"p",tt:12>e?"am":"pm",T:12>e?"A":"P",TT:12>e?"AM":"PM",Z:h?"UTC":((""+a).match(k)||[""]).pop().replace(r,""),o:(0
Try checking and unchecking the 'Reverse sort' options to see how it affects the display of log entries.

Click 'Next' when you are ready to continue.",attachTo:"#view-options",position:5,buttons:[{name:"Next"}],next:"guide_viewoptions_max"}),guiders.createGuider({id:"guide_viewoptions_max",title:"Max entries",description:"This value is the maximum number of log entries that will be displayed. Once that limit is reached, older log entries are removed.

Try entering '3' into the box then press the 'tab' key to see how it affects the display of log entries.

Click 'Next' when you are ready to continue.",attachTo:"#view-options",position:5,buttons:[{name:"Next",onclick:function(){a("#max-entries").val(500).trigger("change"),Etsy.Supergrep.clearEntries(),guiders.next()}}],next:"guide_highlight"}),guiders.createGuider({id:"guide_highlight",title:"Highlighting",description:"This field allows you to highlight log entries based on matching terms or a regex.",attachTo:"#highlight-options",position:6,buttons:[{name:"Next"}],next:"guide_highlight_help"}),guiders.createGuider({id:"guide_highlight_help",title:"Highlighting",description:"This link will show you the formats allowed for highlighting rules.",attachTo:"#highlight-rule-help",position:6,buttons:[{name:"Next",onclick:function(){a("#highlight-rule-help").trigger("click"),guiders.next()}}],next:"guide_highlight_help_show"}),guiders.createGuider({id:"guide_highlight_help_show",title:"Highlighting Rules",description:"This help dialog shows you how to format your hightlighting and filtering rules.",attachTo:"#rule-help-dialog",position:9,width:300,buttons:[{name:"Next",onclick:function(){a("#rule-help-dialog").dialog("close"),Etsy.Supergrep.writeLogEntries(e()),a("#highlight-log").val("debug, warning").trigger("change"),guiders.next()}}],next:"guide_highlight_example1"}),guiders.createGuider({id:"guide_highlight_example1",title:"Highlighting Example #1",description:"Here we see what happens when we highlight using the keywords 'debug' and 'warning'.",attachTo:"#highlight-log",position:10,buttons:[{name:"Next",onclick:function(){a("#rule-help-dialog").dialog("close"),a("#highlight-log").val("/(debug|warning)/i").trigger("change"),guiders.next()}}],next:"guide_highlight_example2"}),guiders.createGuider({id:"guide_highlight_example2",title:"Highlighting Example #2",description:"Here is the same rule expressed as a regex.",attachTo:"#highlight-log",position:10,buttons:[{name:"Next",onclick:function(){a("#rule-help-dialog").dialog("close"),a("#highlight-log").val("").trigger("change"),guiders.next()}}],next:"guide_filter"}),guiders.createGuider({id:"guide_filter",title:"Filtering",description:"Filtering works just like highlighting except instead of coloring matching entries, it hides them.",attachTo:"#filter-log",position:10,buttons:[{name:"Show me",onclick:function(){a("#filter-log").val("errorhandler").trigger("change"),guiders.next()}}],next:"guide_filter_example"}),guiders.createGuider({id:"guide_filter_example",title:"Filter Example",description:"Here we are filtering any log entries that contain 'errorhandler' (case insensitive). Notice how some of the log entries are now gone.",attachTo:"#clear-field-filter",position:10,buttons:[{name:"Next"}],next:"guide_filter_clear"}),guiders.createGuider({id:"guide_filter_clear",title:"Clearing Rules",description:"Highlighting and filtering rules can be easily cleared by clicking the '[X]' button. Try it now!",attachTo:"#clear-field-filter",position:5,offset:{top:0,left:35},buttons:[{name:"Next",onclick:function(){a("#filter-log").val("").trigger("change"),guiders.next()}}],next:"guide_clearlog"}),guiders.createGuider({id:"guide_clearlog",title:"Clearing Log Entries",description:"If you ever need to clear the log entries, just click this button. Try it now!",attachTo:"#clear-log",position:6,buttons:[{name:"Next",onclick:function(){Etsy.Supergrep.clearEntries(),guiders.next()}}],next:"guide_intrologs"}),guiders.createGuider({id:"guide_intrologs",title:"Log Entries",description:"Next, you are going to be introduced to a few features on the log entries themselves and then you are done!",overlay:!0,buttons:[{name:"Next",onclick:function(){a("#sort-order").attr("checked",!1).trigger("change"),a("#highlight-log").val("").trigger("change"),a("#filter-log").val("").trigger("change"),a("#max-entries").val(500).trigger("change"),Etsy.Supergrep.writeLogEntries(e()),guiders.next()}}],next:"guide_rawlog"}),guiders.createGuider({id:"guide_rawlog",title:"Raw Logs",description:"If you ever need to see the original, raw log entry, just click the 'Raw Log' link.",attachTo:".rawlog-link:first",position:6,buttons:[{name:"Next",onclick:function(){var b=a(".rawlog-link:first").parent("li.log").attr("id");a("#"+b+" .rawdata").show(),guiders.next()}}],next:"guide_rawlog_show"}),guiders.createGuider({id:"guide_rawlog_show",title:"Raw Logs",description:"This is the raw log data, exactly is it was recorded.",attachTo:".rawdata:first",position:6,buttons:[{name:"Next",onclick:function(){var b=a(".rawlog-link:first").parent("li.log").attr("id");a("#"+b+" .rawdata").hide(),guiders.next()}}],next:"guide_stacktrace"}),guiders.createGuider({id:"guide_stacktrace",title:"Stack Trace",description:"If the log entry contains a recognized stack trace, this link will appear.",attachTo:".stacktrace-link:first",position:6,buttons:[{name:"Next",onclick:function(){var b=a(".stacktrace-link:first").parent("li.log").attr("id");a("#"+b+" .stacktrace").show(),guiders.next()}}],next:"guide_stacktrace_show"}),guiders.createGuider({id:"guide_stacktrace_show",title:"Stack Trace",description:"This is the parsed stack trace, showing call depth, function call + params, and a link to the source file and line number in Github.",attachTo:".stacktrace:first",position:7,buttons:[{name:"Next",onclick:function(){var b=a(".rawlog-link:first").parent("li.log").attr("id");guiders.next()}}],next:"guide_export"}),guiders.createGuider({id:"guide_export",title:"Exporting Data",description:"One last thing before we finish.

These links allow you to send a raw log or stack trace to either a gist or to an IRC channel/nick. For more info on how that works, click the '[?]' link.",attachTo:".stacktrace:first .gist:first",position:5,buttons:[{name:"Next",onclick:function(){Etsy.Supergrep.clearEntries(),guiders.next()}}],next:"guide_done"}),guiders.createGuider({id:"guide_done",title:"End of Tour",description:"That's about it. Have fun!",overlay:!0,buttons:[{name:"Next"}],next:"guide_restart"}),guiders.createGuider({id:"guide_restart",title:"Viewing this guide",description:"BTW, if you ever want to view this guide again, just click this button.",attachTo:"#guide-help",position:6,buttons:[{name:"Close",onclick:function(){guiders.hideAll(),j()}}]}),guiders.createGuider({id:"guide_restart_abort",title:"Viewing this guide",description:"BTW, if you ever want to view this guide again, just click this button.",attachTo:"#guide-help",position:6,buttons:[{name:"Close",onclick:function(){guiders.hideAll()}}]});var f="supergreptour",g=c(f);b(f,1,365);if(g)return;var h;guiders.show("guide_welcome")}); -------------------------------------------------------------------------------- /static/js/guiders-1.2.0.js: -------------------------------------------------------------------------------- 1 | /** 2 | * guiders.js 3 | * 4 | * version 1.2.0 5 | * 6 | * Developed at Optimizely. (www.optimizely.com) 7 | * We make A/B testing you'll actually use. 8 | * 9 | * Released under the Apache License 2.0. 10 | * www.apache.org/licenses/LICENSE-2.0.html 11 | * 12 | * Questions about Guiders or Optimizely? 13 | * Email us at jeff+pickhardt@optimizely.com or hello@optimizely.com. 14 | * 15 | * Enjoy! 16 | */ 17 | 18 | var guiders = (function($) { 19 | var guiders = {}; 20 | 21 | guiders.version = "1.2.0"; 22 | 23 | guiders._defaultSettings = { 24 | attachTo: null, 25 | buttons: [{name: "Close"}], 26 | buttonCustomHTML: "", 27 | classString: null, 28 | description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 29 | highlight: null, 30 | isHashable: true, 31 | offset: { 32 | top: null, 33 | left: null 34 | }, 35 | onShow: null, 36 | overlay: false, 37 | position: 0, // 1-12 follows an analog clock, 0 means centered 38 | title: "Sample title goes here", 39 | width: 400, 40 | xButton: false // this places a closer "x" button in the top right of the guider 41 | }; 42 | 43 | guiders._htmlSkeleton = [ 44 | "
", 45 | "
", 46 | "

", 47 | "
", 48 | "

", 49 | "
", 50 | "
", 51 | "
", 52 | "
", 53 | "
", 54 | "
" 55 | ].join(""); 56 | 57 | guiders._arrowSize = 42; // = arrow's width and height 58 | guiders._closeButtonTitle = "Close"; 59 | guiders._currentGuiderID = null; 60 | guiders._guiders = {}; 61 | guiders._lastCreatedGuiderID = null; 62 | guiders._nextButtonTitle = "Next"; 63 | guiders._zIndexForHighlight = 101; 64 | 65 | guiders._addButtons = function(myGuider) { 66 | // Add buttons 67 | var guiderButtonsContainer = myGuider.elem.find(".guider_buttons"); 68 | 69 | if (myGuider.buttons === null || myGuider.buttons.length === 0) { 70 | guiderButtonsContainer.remove(); 71 | return; 72 | } 73 | 74 | for (var i = myGuider.buttons.length-1; i >= 0; i--) { 75 | var thisButton = myGuider.buttons[i]; 76 | var thisButtonElem = $("", { 77 | "class" : "guider_button", 78 | "text" : thisButton.name }); 79 | if (typeof thisButton.classString !== "undefined" && thisButton.classString !== null) { 80 | thisButtonElem.addClass(thisButton.classString); 81 | } 82 | 83 | guiderButtonsContainer.append(thisButtonElem); 84 | 85 | if (thisButton.onclick) { 86 | thisButtonElem.bind("click", thisButton.onclick); 87 | } else if (!thisButton.onclick && 88 | thisButton.name.toLowerCase() === guiders._closeButtonTitle.toLowerCase()) { 89 | thisButtonElem.bind("click", function() { guiders.hideAll(); }); 90 | } else if (!thisButton.onclick && 91 | thisButton.name.toLowerCase() === guiders._nextButtonTitle.toLowerCase()) { 92 | thisButtonElem.bind("click", function() { guiders.next(); }); 93 | } 94 | } 95 | 96 | if (myGuider.buttonCustomHTML !== "") { 97 | var myCustomHTML = $(myGuider.buttonCustomHTML); 98 | myGuider.elem.find(".guider_buttons").append(myCustomHTML); 99 | } 100 | 101 | if (myGuider.buttons.length == 0) { 102 | guiderButtonsContainer.remove(); 103 | } 104 | }; 105 | 106 | guiders._addXButton = function(myGuider) { 107 | var xButtonContainer = myGuider.elem.find(".guider_close"); 108 | var xButton = $("
", { 109 | "class" : "x_button", 110 | "role" : "button" }); 111 | xButtonContainer.append(xButton); 112 | xButton.click(function() { guiders.hideAll(); }); 113 | }; 114 | 115 | guiders._attach = function(myGuider) { 116 | if (myGuider === null) { 117 | return; 118 | } 119 | 120 | var myHeight = myGuider.elem.innerHeight(); 121 | var myWidth = myGuider.elem.innerWidth(); 122 | 123 | if (myGuider.position === 0 || myGuider.attachTo === null) { 124 | myGuider.elem.css("position", "absolute"); 125 | myGuider.elem.css("top", ($(window).height() - myHeight) / 3 + $(window).scrollTop() + "px"); 126 | myGuider.elem.css("left", ($(window).width() - myWidth) / 2 + $(window).scrollLeft() + "px"); 127 | return; 128 | } 129 | 130 | var attachTo = $(myGuider.attachTo); 131 | if (!attachTo.length) { 132 | return; 133 | } 134 | myGuider.attachTo = attachTo; 135 | var base = myGuider.attachTo.offset(); 136 | var attachToHeight = myGuider.attachTo.innerHeight(); 137 | var attachToWidth = myGuider.attachTo.innerWidth(); 138 | 139 | var top = base.top; 140 | var left = base.left; 141 | 142 | var bufferOffset = 0.9 * guiders._arrowSize; 143 | 144 | var offsetMap = { // Follows the form: [height, width] 145 | 1: [-bufferOffset - myHeight, attachToWidth - myWidth], 146 | 2: [0, bufferOffset + attachToWidth], 147 | 3: [attachToHeight/2 - myHeight/2, bufferOffset + attachToWidth], 148 | 4: [attachToHeight - myHeight, bufferOffset + attachToWidth], 149 | 5: [bufferOffset + attachToHeight, attachToWidth - myWidth], 150 | 6: [bufferOffset + attachToHeight, attachToWidth/2 - myWidth/2], 151 | 7: [bufferOffset + attachToHeight, 0], 152 | 8: [attachToHeight - myHeight, -myWidth - bufferOffset], 153 | 9: [attachToHeight/2 - myHeight/2, -myWidth - bufferOffset], 154 | 10: [0, -myWidth - bufferOffset], 155 | 11: [-bufferOffset - myHeight, 0], 156 | 12: [-bufferOffset - myHeight, attachToWidth/2 - myWidth/2] 157 | }; 158 | 159 | offset = offsetMap[myGuider.position]; 160 | top += offset[0]; 161 | left += offset[1]; 162 | 163 | if (myGuider.offset.top !== null) { 164 | top += myGuider.offset.top; 165 | } 166 | 167 | if (myGuider.offset.left !== null) { 168 | left += myGuider.offset.left; 169 | } 170 | 171 | myGuider.elem.css({ 172 | "position": "absolute", 173 | "top": top, 174 | "left": left 175 | }); 176 | }; 177 | 178 | guiders._guiderById = function(id) { 179 | if (typeof guiders._guiders[id] === "undefined") { 180 | throw "Cannot find guider with id " + id; 181 | } 182 | return guiders._guiders[id]; 183 | }; 184 | 185 | guiders._showOverlay = function() { 186 | $("#guider_overlay").fadeIn("fast", function(){ 187 | if (this.style.removeAttribute) { 188 | this.style.removeAttribute("filter"); 189 | } 190 | }); 191 | // This callback is needed to fix an IE opacity bug. 192 | // See also: 193 | // http://www.kevinleary.net/jquery-fadein-fadeout-problems-in-internet-explorer/ 194 | }; 195 | 196 | guiders._highlightElement = function(selector) { 197 | $(selector).css({'z-index': guiders._zIndexForHighlight}); 198 | }; 199 | 200 | guiders._dehighlightElement = function(selector) { 201 | $(selector).css({'z-index': 1}); 202 | }; 203 | 204 | guiders._hideOverlay = function() { 205 | $("#guider_overlay").fadeOut("fast"); 206 | }; 207 | 208 | guiders._initializeOverlay = function() { 209 | if ($("#guider_overlay").length === 0) { 210 | $("
").hide().appendTo("body"); 211 | } 212 | }; 213 | 214 | guiders._styleArrow = function(myGuider) { 215 | var position = myGuider.position || 0; 216 | if (!position) { 217 | return; 218 | } 219 | var myGuiderArrow = $(myGuider.elem.find(".guider_arrow")); 220 | var newClass = { 221 | 1: "guider_arrow_down", 222 | 2: "guider_arrow_left", 223 | 3: "guider_arrow_left", 224 | 4: "guider_arrow_left", 225 | 5: "guider_arrow_up", 226 | 6: "guider_arrow_up", 227 | 7: "guider_arrow_up", 228 | 8: "guider_arrow_right", 229 | 9: "guider_arrow_right", 230 | 10: "guider_arrow_right", 231 | 11: "guider_arrow_down", 232 | 12: "guider_arrow_down" 233 | }; 234 | myGuiderArrow.addClass(newClass[position]); 235 | 236 | var myHeight = myGuider.elem.innerHeight(); 237 | var myWidth = myGuider.elem.innerWidth(); 238 | var arrowOffset = guiders._arrowSize / 2; 239 | var positionMap = { 240 | 1: ["right", arrowOffset], 241 | 2: ["top", arrowOffset], 242 | 3: ["top", myHeight/2 - arrowOffset], 243 | 4: ["bottom", arrowOffset], 244 | 5: ["right", arrowOffset], 245 | 6: ["left", myWidth/2 - arrowOffset], 246 | 7: ["left", arrowOffset], 247 | 8: ["bottom", arrowOffset], 248 | 9: ["top", myHeight/2 - arrowOffset], 249 | 10: ["top", arrowOffset], 250 | 11: ["left", arrowOffset], 251 | 12: ["left", myWidth/2 - arrowOffset] 252 | }; 253 | var position = positionMap[myGuider.position]; 254 | myGuiderArrow.css(position[0], position[1] + "px"); 255 | }; 256 | 257 | /** 258 | * One way to show a guider to new users is to direct new users to a URL such as 259 | * http://www.mysite.com/myapp#guider=welcome 260 | * 261 | * This can also be used to run guiders on multiple pages, by redirecting from 262 | * one page to another, with the guider id in the hash tag. 263 | * 264 | * Alternatively, if you use a session variable or flash messages after sign up, 265 | * you can add selectively add JavaScript to the page: "guiders.show('first');" 266 | */ 267 | guiders._showIfHashed = function(myGuider) { 268 | var GUIDER_HASH_TAG = "guider="; 269 | var hashIndex = window.location.hash.indexOf(GUIDER_HASH_TAG); 270 | if (hashIndex !== -1) { 271 | var hashGuiderId = window.location.hash.substr(hashIndex + GUIDER_HASH_TAG.length); 272 | if (myGuider.id.toLowerCase() === hashGuiderId.toLowerCase()) { 273 | // Success! 274 | guiders.show(myGuider.id); 275 | } 276 | } 277 | }; 278 | 279 | guiders.next = function() { 280 | var currentGuider = guiders._guiders[guiders._currentGuiderID]; 281 | if (typeof currentGuider === "undefined") { 282 | return; 283 | } 284 | var nextGuiderId = currentGuider.next || null; 285 | if (nextGuiderId !== null && nextGuiderId !== "") { 286 | var myGuider = guiders._guiderById(nextGuiderId); 287 | var omitHidingOverlay = myGuider.overlay ? true : false; 288 | guiders.hideAll(omitHidingOverlay); 289 | if (currentGuider.highlight) { 290 | guiders._dehighlightElement(currentGuider.highlight); 291 | } 292 | guiders.show(nextGuiderId); 293 | } 294 | }; 295 | 296 | guiders.createGuider = function(passedSettings) { 297 | if (passedSettings === null || passedSettings === undefined) { 298 | passedSettings = {}; 299 | } 300 | 301 | // Extend those settings with passedSettings 302 | myGuider = $.extend({}, guiders._defaultSettings, passedSettings); 303 | myGuider.id = myGuider.id || String(Math.floor(Math.random() * 1000)); 304 | 305 | var guiderElement = $(guiders._htmlSkeleton); 306 | myGuider.elem = guiderElement; 307 | if (typeof myGuider.classString !== "undefined" && myGuider.classString !== null) { 308 | myGuider.elem.addClass(myGuider.classString); 309 | } 310 | myGuider.elem.css("width", myGuider.width + "px"); 311 | 312 | var guiderTitleContainer = guiderElement.find(".guider_title"); 313 | guiderTitleContainer.html(myGuider.title); 314 | 315 | guiderElement.find(".guider_description").html(myGuider.description); 316 | 317 | guiders._addButtons(myGuider); 318 | 319 | if (myGuider.xButton) { 320 | guiders._addXButton(myGuider); 321 | } 322 | 323 | guiderElement.hide(); 324 | guiderElement.appendTo("body"); 325 | guiderElement.attr("id", myGuider.id); 326 | 327 | // Ensure myGuider.attachTo is a jQuery element. 328 | if (typeof myGuider.attachTo !== "undefined" && myGuider !== null) { 329 | guiders._attach(myGuider); 330 | guiders._styleArrow(myGuider); 331 | } 332 | 333 | guiders._initializeOverlay(); 334 | 335 | guiders._guiders[myGuider.id] = myGuider; 336 | guiders._lastCreatedGuiderID = myGuider.id; 337 | 338 | /** 339 | * If the URL of the current window is of the form 340 | * http://www.myurl.com/mypage.html#guider=id 341 | * then show this guider. 342 | */ 343 | if (myGuider.isHashable) { 344 | guiders._showIfHashed(myGuider); 345 | } 346 | 347 | return guiders; 348 | }; 349 | 350 | guiders.hideAll = function(omitHidingOverlay) { 351 | $(".guider").fadeOut("fast"); 352 | if (typeof omitHidingOverlay !== "undefined" && omitHidingOverlay === true) { 353 | // do nothing for now 354 | } else { 355 | guiders._hideOverlay(); 356 | } 357 | return guiders; 358 | }; 359 | 360 | guiders.show = function(id) { 361 | if (!id && guiders._lastCreatedGuiderID) { 362 | id = guiders._lastCreatedGuiderID; 363 | } 364 | 365 | var myGuider = guiders._guiderById(id); 366 | if (myGuider.overlay) { 367 | guiders._showOverlay(); 368 | // if guider is attached to an element, make sure it's visible 369 | if (myGuider.highlight) { 370 | guiders._highlightElement(myGuider.highlight); 371 | } 372 | } 373 | 374 | guiders._attach(myGuider); 375 | 376 | // You can use an onShow function to take some action before the guider is shown. 377 | if (myGuider.onShow) { 378 | myGuider.onShow(myGuider); 379 | } 380 | 381 | myGuider.elem.fadeIn("fast"); 382 | 383 | var windowHeight = $(window).height(); 384 | var scrollHeight = $(window).scrollTop(); 385 | var guiderOffset = myGuider.elem.offset(); 386 | var guiderElemHeight = myGuider.elem.height(); 387 | 388 | if (guiderOffset.top - scrollHeight < 0 || 389 | guiderOffset.top + guiderElemHeight + 40 > scrollHeight + windowHeight) { 390 | window.scrollTo(0, Math.max(guiderOffset.top + (guiderElemHeight / 2) - (windowHeight / 2), 0)); 391 | } 392 | 393 | guiders._currentGuiderID = id; 394 | return guiders; 395 | }; 396 | 397 | return guiders; 398 | }).call(this, jQuery); 399 | -------------------------------------------------------------------------------- /static/js/guiders-1.2.0.min.js: -------------------------------------------------------------------------------- 1 | var guiders=function(g){var b={version:"1.2.0",_defaultSettings:{attachTo:null,buttons:[{name:"Close"}],buttonCustomHTML:"",classString:null,description:"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", highlight:null,isHashable:!0,offset:{top:null,left:null},onShow:null,overlay:!1,position:0,title:"Sample title goes here",width:400,xButton:!1},_htmlSkeleton:"

",_arrowSize:42,_closeButtonTitle:"Close",_currentGuiderID:null,_guiders:{},_lastCreatedGuiderID:null, _nextButtonTitle:"Next",_zIndexForHighlight:101};b._addButtons=function(a){var c=a.elem.find(".guider_buttons");if(null===a.buttons||0===a.buttons.length)c.remove();else{for(var f=a.buttons.length-1;0<=f;f--){var e=a.buttons[f],d=g("",{"class":"guider_button",text:e.name});"undefined"!==typeof e.classString&&null!==e.classString&&d.addClass(e.classString);c.append(d);e.onclick?d.bind("click",e.onclick):!e.onclick&&e.name.toLowerCase()===b._closeButtonTitle.toLowerCase()?d.bind("click",function(){b.hideAll()}): !e.onclick&&e.name.toLowerCase()===b._nextButtonTitle.toLowerCase()&&d.bind("click",function(){b.next()})}""!==a.buttonCustomHTML&&(f=g(a.buttonCustomHTML),a.elem.find(".guider_buttons").append(f));0==a.buttons.length&&c.remove()}};b._addXButton=function(a){var a=a.elem.find(".guider_close"),c=g("
",{"class":"x_button",role:"button"});a.append(c);c.click(function(){b.hideAll()})};b._attach=function(a){if(null!==a){var c=a.elem.innerHeight(),f=a.elem.innerWidth();if(0===a.position||null=== a.attachTo)a.elem.css("position","absolute"),a.elem.css("top",(g(window).height()-c)/3+g(window).scrollTop()+"px"),a.elem.css("left",(g(window).width()-f)/2+g(window).scrollLeft()+"px");else{var e=g(a.attachTo);if(e.length){a.attachTo=e;var d=a.attachTo.offset(),e=a.attachTo.innerHeight(),i=a.attachTo.innerWidth(),j=d.top,d=d.left,h=0.9*b._arrowSize;offset={1:[-h-c,i-f],2:[0,h+i],3:[e/2-c/2,h+i],4:[e-c,h+i],5:[h+e,i-f],6:[h+e,i/2-f/2],7:[h+e,0],8:[e-c,-f-h],9:[e/2-c/2,-f-h],10:[0,-f-h],11:[-h-c,0], 12:[-h-c,i/2-f/2]}[a.position];j+=offset[0];d+=offset[1];null!==a.offset.top&&(j+=a.offset.top);null!==a.offset.left&&(d+=a.offset.left);a.elem.css({position:"absolute",top:j,left:d})}}}};b._guiderById=function(a){if("undefined"===typeof b._guiders[a])throw"Cannot find guider with id "+a;return b._guiders[a]};b._showOverlay=function(){g("#guider_overlay").fadeIn("fast",function(){this.style.removeAttribute&&this.style.removeAttribute("filter")})};b._highlightElement=function(a){g(a).css({"z-index":b._zIndexForHighlight})}; b._dehighlightElement=function(a){g(a).css({"z-index":1})};b._hideOverlay=function(){g("#guider_overlay").fadeOut("fast")};b._initializeOverlay=function(){0===g("#guider_overlay").length&&g('
').hide().appendTo("body")};b._styleArrow=function(a){var c=a.position||0;if(c){var f=g(a.elem.find(".guider_arrow"));f.addClass({1:"guider_arrow_down",2:"guider_arrow_left",3:"guider_arrow_left",4:"guider_arrow_left",5:"guider_arrow_up",6:"guider_arrow_up",7:"guider_arrow_up",8:"guider_arrow_right", 9:"guider_arrow_right",10:"guider_arrow_right",11:"guider_arrow_down",12:"guider_arrow_down"}[c]);var c=a.elem.innerHeight(),e=a.elem.innerWidth(),d=b._arrowSize/2,c={1:["right",d],2:["top",d],3:["top",c/2-d],4:["bottom",d],5:["right",d],6:["left",e/2-d],7:["left",d],8:["bottom",d],9:["top",c/2-d],10:["top",d],11:["left",d],12:["left",e/2-d]}[a.position];f.css(c[0],c[1]+"px")}};b._showIfHashed=function(a){var c=window.location.hash.indexOf("guider=");-1!==c&&(c=window.location.hash.substr(c+7),a.id.toLowerCase()=== c.toLowerCase()&&b.show(a.id))};b.next=function(){var a=b._guiders[b._currentGuiderID];if("undefined"!==typeof a){var c=a.next||null;if(null!==c&&""!==c){var f=b._guiderById(c).overlay?!0:!1;b.hideAll(f);a.highlight&&b._dehighlightElement(a.highlight);b.show(c)}}};b.createGuider=function(a){if(null===a||void 0===a)a={};myGuider=g.extend({},b._defaultSettings,a);myGuider.id=myGuider.id||""+Math.floor(1E3*Math.random());a=g(b._htmlSkeleton);myGuider.elem=a;"undefined"!==typeof myGuider.classString&& null!==myGuider.classString&&myGuider.elem.addClass(myGuider.classString);myGuider.elem.css("width",myGuider.width+"px");a.find(".guider_title").html(myGuider.title);a.find(".guider_description").html(myGuider.description);b._addButtons(myGuider);myGuider.xButton&&b._addXButton(myGuider);a.hide();a.appendTo("body");a.attr("id",myGuider.id);"undefined"!==typeof myGuider.attachTo&&null!==myGuider&&(b._attach(myGuider),b._styleArrow(myGuider));b._initializeOverlay();b._guiders[myGuider.id]=myGuider; b._lastCreatedGuiderID=myGuider.id;myGuider.isHashable&&b._showIfHashed(myGuider);return b};b.hideAll=function(a){g(".guider").fadeOut("fast");"undefined"!==typeof a&&!0===a||b._hideOverlay();return b};b.show=function(a){if(!a&&b._lastCreatedGuiderID)a=b._lastCreatedGuiderID;var c=b._guiderById(a);c.overlay&&(b._showOverlay(),c.highlight&&b._highlightElement(c.highlight));b._attach(c);if(c.onShow)c.onShow(c);c.elem.fadeIn("fast");var f=g(window).height(),e=g(window).scrollTop(),d=c.elem.offset(), c=c.elem.height();(0>d.top-e||d.top+c+40>e+f)&&window.scrollTo(0,Math.max(d.top+c/2-f/2,0));b._currentGuiderID=a;return b};return b}.call(this,jQuery); -------------------------------------------------------------------------------- /static/js/jstorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ----------------------------- JSTORAGE ------------------------------------- 3 | * Simple local storage wrapper to save data on the browser side, supporting 4 | * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+ 5 | * 6 | * Copyright (c) 2010 Andris Reinman, andris.reinman@gmail.com 7 | * Project homepage: www.jstorage.info 8 | * 9 | * Licensed under MIT-style license: 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | /** 28 | * $.jStorage 29 | * 30 | * USAGE: 31 | * 32 | * jStorage requires Prototype, MooTools or jQuery! If jQuery is used, then 33 | * jQuery-JSON (http://code.google.com/p/jquery-json/) is also needed. 34 | * (jQuery-JSON needs to be loaded BEFORE jStorage!) 35 | * 36 | * Methods: 37 | * 38 | * -set(key, value) 39 | * $.jStorage.set(key, value) -> saves a value 40 | * 41 | * -get(key[, default]) 42 | * value = $.jStorage.get(key [, default]) -> 43 | * retrieves value if key exists, or default if it doesn't 44 | * 45 | * -deleteKey(key) 46 | * $.jStorage.deleteKey(key) -> removes a key from the storage 47 | * 48 | * -flush() 49 | * $.jStorage.flush() -> clears the cache 50 | * 51 | * -storageObj() 52 | * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage 53 | * 54 | * -storageSize() 55 | * $.jStorage.storageSize() -> returns the size of the storage in bytes 56 | * 57 | * -index() 58 | * $.jStorage.index() -> returns the used keys as an array 59 | * 60 | * -storageAvailable() 61 | * $.jStorage.storageAvailable() -> returns true if storage is available 62 | * 63 | * -reInit() 64 | * $.jStorage.reInit() -> reloads the data from browser storage 65 | * 66 | * can be any JSON-able value, including objects and arrays. 67 | * 68 | **/ 69 | 70 | (function($){ 71 | if(!$ || !($.toJSON || Object.toJSON || window.JSON)){ 72 | throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!"); 73 | } 74 | 75 | var 76 | /* This is the object, that holds the cached values */ 77 | _storage = {}, 78 | 79 | /* Actual browser storage (localStorage or globalStorage['domain']) */ 80 | _storage_service = {jStorage:"{}"}, 81 | 82 | /* DOM element for older IE versions, holds userData behavior */ 83 | _storage_elm = null, 84 | 85 | /* How much space does the storage take */ 86 | _storage_size = 0, 87 | 88 | /* function to encode objects to JSON strings */ 89 | json_encode = $.toJSON || Object.toJSON || (window.JSON && (JSON.encode || JSON.stringify)), 90 | 91 | /* function to decode objects from JSON strings */ 92 | json_decode = $.evalJSON || (window.JSON && (JSON.decode || JSON.parse)) || function(str){ 93 | return String(str).evalJSON(); 94 | }, 95 | 96 | /* which backend is currently used */ 97 | _backend = false, 98 | 99 | /* Next check for TTL */ 100 | _ttl_timeout, 101 | 102 | /** 103 | * XML encoding and decoding as XML nodes can't be JSON'ized 104 | * XML nodes are encoded and decoded if the node is the value to be saved 105 | * but not if it's as a property of another object 106 | * Eg. - 107 | * $.jStorage.set("key", xmlNode); // IS OK 108 | * $.jStorage.set("key", {xml: xmlNode}); // NOT OK 109 | */ 110 | _XMLService = { 111 | 112 | /** 113 | * Validates a XML node to be XML 114 | * based on jQuery.isXML function 115 | */ 116 | isXML: function(elm){ 117 | var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement; 118 | return documentElement ? documentElement.nodeName !== "HTML" : false; 119 | }, 120 | 121 | /** 122 | * Encodes a XML node to string 123 | * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/ 124 | */ 125 | encode: function(xmlNode) { 126 | if(!this.isXML(xmlNode)){ 127 | return false; 128 | } 129 | try{ // Mozilla, Webkit, Opera 130 | return new XMLSerializer().serializeToString(xmlNode); 131 | }catch(E1) { 132 | try { // IE 133 | return xmlNode.xml; 134 | }catch(E2){} 135 | } 136 | return false; 137 | }, 138 | 139 | /** 140 | * Decodes a XML node from string 141 | * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/ 142 | */ 143 | decode: function(xmlString){ 144 | var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) || 145 | (window.ActiveXObject && function(_xmlString) { 146 | var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); 147 | xml_doc.async = 'false'; 148 | xml_doc.loadXML(_xmlString); 149 | return xml_doc; 150 | }), 151 | resultXML; 152 | if(!dom_parser){ 153 | return false; 154 | } 155 | resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml'); 156 | return this.isXML(resultXML)?resultXML:false; 157 | } 158 | }; 159 | 160 | ////////////////////////// PRIVATE METHODS //////////////////////// 161 | 162 | /** 163 | * Initialization function. Detects if the browser supports DOM Storage 164 | * or userData behavior and behaves accordingly. 165 | * @returns undefined 166 | */ 167 | function _init(){ 168 | /* Check if browser supports localStorage */ 169 | var localStorageReallyWorks = false; 170 | if("localStorage" in window){ 171 | try { 172 | window.localStorage.setItem('_tmptest', 'tmpval'); 173 | localStorageReallyWorks = true; 174 | window.localStorage.removeItem('_tmptest'); 175 | } catch(BogusQuotaExceededErrorOnIos5) { 176 | // Thanks be to iOS5 Private Browsing mode which throws 177 | // QUOTA_EXCEEDED_ERRROR DOM Exception 22. 178 | } 179 | } 180 | if(localStorageReallyWorks){ 181 | try { 182 | if(window.localStorage) { 183 | _storage_service = window.localStorage; 184 | _backend = "localStorage"; 185 | } 186 | } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */} 187 | } 188 | /* Check if browser supports globalStorage */ 189 | else if("globalStorage" in window){ 190 | try { 191 | if(window.globalStorage) { 192 | _storage_service = window.globalStorage[window.location.hostname]; 193 | _backend = "globalStorage"; 194 | } 195 | } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */} 196 | } 197 | /* Check if browser supports userData behavior */ 198 | else { 199 | _storage_elm = document.createElement('link'); 200 | if(_storage_elm.addBehavior){ 201 | 202 | /* Use a DOM element to act as userData storage */ 203 | _storage_elm.style.behavior = 'url(#default#userData)'; 204 | 205 | /* userData element needs to be inserted into the DOM! */ 206 | document.getElementsByTagName('head')[0].appendChild(_storage_elm); 207 | 208 | _storage_elm.load("jStorage"); 209 | var data = "{}"; 210 | try{ 211 | data = _storage_elm.getAttribute("jStorage"); 212 | }catch(E5){} 213 | _storage_service.jStorage = data; 214 | _backend = "userDataBehavior"; 215 | }else{ 216 | _storage_elm = null; 217 | return; 218 | } 219 | } 220 | 221 | _load_storage(); 222 | 223 | // remove dead keys 224 | _handleTTL(); 225 | } 226 | 227 | /** 228 | * Loads the data from the storage based on the supported mechanism 229 | * @returns undefined 230 | */ 231 | function _load_storage(){ 232 | /* if jStorage string is retrieved, then decode it */ 233 | if(_storage_service.jStorage){ 234 | try{ 235 | _storage = json_decode(String(_storage_service.jStorage)); 236 | }catch(E6){_storage_service.jStorage = "{}";} 237 | }else{ 238 | _storage_service.jStorage = "{}"; 239 | } 240 | _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; 241 | } 242 | 243 | /** 244 | * This functions provides the "save" mechanism to store the jStorage object 245 | * @returns undefined 246 | */ 247 | function _save(){ 248 | try{ 249 | _storage_service.jStorage = json_encode(_storage); 250 | // If userData is used as the storage engine, additional 251 | if(_storage_elm) { 252 | _storage_elm.setAttribute("jStorage",_storage_service.jStorage); 253 | _storage_elm.save("jStorage"); 254 | } 255 | _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; 256 | }catch(E7){/* probably cache is full, nothing is saved this way*/} 257 | } 258 | 259 | /** 260 | * Function checks if a key is set and is string or numberic 261 | */ 262 | function _checkKey(key){ 263 | if(!key || (typeof key != "string" && typeof key != "number")){ 264 | throw new TypeError('Key name must be string or numeric'); 265 | } 266 | if(key == "__jstorage_meta"){ 267 | throw new TypeError('Reserved key name'); 268 | } 269 | return true; 270 | } 271 | 272 | /** 273 | * Removes expired keys 274 | */ 275 | function _handleTTL(){ 276 | var curtime, i, TTL, nextExpire = Infinity, changed = false; 277 | 278 | clearTimeout(_ttl_timeout); 279 | 280 | if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){ 281 | // nothing to do here 282 | return; 283 | } 284 | 285 | curtime = +new Date(); 286 | TTL = _storage.__jstorage_meta.TTL; 287 | for(i in TTL){ 288 | if(TTL.hasOwnProperty(i)){ 289 | if(TTL[i] <= curtime){ 290 | delete TTL[i]; 291 | delete _storage[i]; 292 | changed = true; 293 | }else if(TTL[i] < nextExpire){ 294 | nextExpire = TTL[i]; 295 | } 296 | } 297 | } 298 | 299 | // set next check 300 | if(nextExpire != Infinity){ 301 | _ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime); 302 | } 303 | 304 | // save changes 305 | if(changed){ 306 | _save(); 307 | } 308 | } 309 | 310 | ////////////////////////// PUBLIC INTERFACE ///////////////////////// 311 | 312 | $.jStorage = { 313 | /* Version number */ 314 | version: "0.1.6.1", 315 | 316 | /** 317 | * Sets a key's value. 318 | * 319 | * @param {String} key - Key to set. If this value is not set or not 320 | * a string an exception is raised. 321 | * @param value - Value to set. This can be any value that is JSON 322 | * compatible (Numbers, Strings, Objects etc.). 323 | * @returns the used value 324 | */ 325 | set: function(key, value){ 326 | _checkKey(key); 327 | if(_XMLService.isXML(value)){ 328 | value = {_is_xml:true,xml:_XMLService.encode(value)}; 329 | }else if(typeof value == "function"){ 330 | value = null; // functions can't be saved! 331 | }else if(value && typeof value == "object"){ 332 | // clone the object before saving to _storage tree 333 | value = json_decode(json_encode(value)); 334 | } 335 | _storage[key] = value; 336 | _save(); 337 | return value; 338 | }, 339 | 340 | /** 341 | * Looks up a key in cache 342 | * 343 | * @param {String} key - Key to look up. 344 | * @param {mixed} def - Default value to return, if key didn't exist. 345 | * @returns the key value, default value or 346 | */ 347 | get: function(key, def){ 348 | _checkKey(key); 349 | if(key in _storage){ 350 | if(_storage[key] && typeof _storage[key] == "object" && 351 | _storage[key]._is_xml && 352 | _storage[key]._is_xml){ 353 | return _XMLService.decode(_storage[key].xml); 354 | }else{ 355 | return _storage[key]; 356 | } 357 | } 358 | return typeof(def) == 'undefined' ? null : def; 359 | }, 360 | 361 | /** 362 | * Deletes a key from cache. 363 | * 364 | * @param {String} key - Key to delete. 365 | * @returns true if key existed or false if it didn't 366 | */ 367 | deleteKey: function(key){ 368 | _checkKey(key); 369 | if(key in _storage){ 370 | delete _storage[key]; 371 | // remove from TTL list 372 | if(_storage.__jstorage_meta && 373 | typeof _storage.__jstorage_meta.TTL == "object" && 374 | key in _storage.__jstorage_meta.TTL){ 375 | delete _storage.__jstorage_meta.TTL[key]; 376 | } 377 | _save(); 378 | return true; 379 | } 380 | return false; 381 | }, 382 | 383 | /** 384 | * Sets a TTL for a key, or remove it if ttl value is 0 or below 385 | * 386 | * @param {String} key - key to set the TTL for 387 | * @param {Number} ttl - TTL timeout in milliseconds 388 | * @returns true if key existed or false if it didn't 389 | */ 390 | setTTL: function(key, ttl){ 391 | var curtime = +new Date(); 392 | _checkKey(key); 393 | ttl = Number(ttl) || 0; 394 | if(key in _storage){ 395 | 396 | if(!_storage.__jstorage_meta){ 397 | _storage.__jstorage_meta = {}; 398 | } 399 | if(!_storage.__jstorage_meta.TTL){ 400 | _storage.__jstorage_meta.TTL = {}; 401 | } 402 | 403 | // Set TTL value for the key 404 | if(ttl>0){ 405 | _storage.__jstorage_meta.TTL[key] = curtime + ttl; 406 | }else{ 407 | delete _storage.__jstorage_meta.TTL[key]; 408 | } 409 | 410 | _save(); 411 | 412 | _handleTTL(); 413 | return true; 414 | } 415 | return false; 416 | }, 417 | 418 | /** 419 | * Deletes everything in cache. 420 | * 421 | * @return true 422 | */ 423 | flush: function(){ 424 | _storage = {}; 425 | _save(); 426 | return true; 427 | }, 428 | 429 | /** 430 | * Returns a read-only copy of _storage 431 | * 432 | * @returns Object 433 | */ 434 | storageObj: function(){ 435 | function F() {} 436 | F.prototype = _storage; 437 | return new F(); 438 | }, 439 | 440 | /** 441 | * Returns an index of all used keys as an array 442 | * ['key1', 'key2',..'keyN'] 443 | * 444 | * @returns Array 445 | */ 446 | index: function(){ 447 | var index = [], i; 448 | for(i in _storage){ 449 | if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){ 450 | index.push(i); 451 | } 452 | } 453 | return index; 454 | }, 455 | 456 | /** 457 | * How much space in bytes does the storage take? 458 | * 459 | * @returns Number 460 | */ 461 | storageSize: function(){ 462 | return _storage_size; 463 | }, 464 | 465 | /** 466 | * Which backend is currently in use? 467 | * 468 | * @returns String 469 | */ 470 | currentBackend: function(){ 471 | return _backend; 472 | }, 473 | 474 | /** 475 | * Test if storage is available 476 | * 477 | * @returns Boolean 478 | */ 479 | storageAvailable: function(){ 480 | return !!_backend; 481 | }, 482 | 483 | /** 484 | * Reloads the data from browser storage 485 | * 486 | * @returns undefined 487 | */ 488 | reInit: function(){ 489 | var new_storage_elm, data; 490 | if(_storage_elm && _storage_elm.addBehavior){ 491 | new_storage_elm = document.createElement('link'); 492 | 493 | _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm); 494 | _storage_elm = new_storage_elm; 495 | 496 | /* Use a DOM element to act as userData storage */ 497 | _storage_elm.style.behavior = 'url(#default#userData)'; 498 | 499 | /* userData element needs to be inserted into the DOM! */ 500 | document.getElementsByTagName('head')[0].appendChild(_storage_elm); 501 | 502 | _storage_elm.load("jStorage"); 503 | data = "{}"; 504 | try{ 505 | data = _storage_elm.getAttribute("jStorage"); 506 | }catch(E5){} 507 | _storage_service.jStorage = data; 508 | _backend = "userDataBehavior"; 509 | } 510 | 511 | _load_storage(); 512 | } 513 | }; 514 | 515 | // Initialize jStorage 516 | _init(); 517 | 518 | })(window.jQuery || window.$); -------------------------------------------------------------------------------- /static/js/jstorage.min.js: -------------------------------------------------------------------------------- 1 | (function(g){function m(){if(e.jStorage)try{c=n(""+e.jStorage)}catch(a){e.jStorage="{}"}else e.jStorage="{}";j=e.jStorage?(""+e.jStorage).length:0}function h(){try{e.jStorage=o(c),d&&(d.setAttribute("jStorage",e.jStorage),d.save("jStorage")),j=e.jStorage?(""+e.jStorage).length:0}catch(a){}}function i(a){if(!a||"string"!=typeof a&&"number"!=typeof a)throw new TypeError("Key name must be string or numeric");if("__jstorage_meta"==a)throw new TypeError("Reserved key name");return!0}function k(){var a, 2 | b,d,e=Infinity,f=!1;clearTimeout(p);if(c.__jstorage_meta&&"object"==typeof c.__jstorage_meta.TTL){a=+new Date;d=c.__jstorage_meta.TTL;for(b in d)d.hasOwnProperty(b)&&(d[b]<=a?(delete d[b],delete c[b],f=!0):d[b]Loading...'); 227 | $(link).remove(); 228 | container.append(output); 229 | $.ajax('/blame/web/' + file + '@' + line + ',' + line, { 230 | dataType: 'jsonp' 231 | , success: function (data, textStatus, jqXHR) { 232 | if (data.error) { 233 | 234 | } 235 | output.text(data.error ? 236 | ('[Blamebot error: ' + (data.error.details || data.error.msg) + ']') 237 | : 238 | ('[' + (new Date(data['author-time'] * 1000)).format('m/d HH:MM:ss') + '] ' + data['author'] + ' - ' + data['summary']) 239 | ); 240 | } 241 | }); 242 | }; 243 | 244 | }); 245 | -------------------------------------------------------------------------------- /static/js/page.min.js: -------------------------------------------------------------------------------- 1 | //This is for the benefit of jStorage 2 | jQuery.extend({toJSON:JSON.stringify,evalJSON:JSON.parse}),jQuery(function(a){function d(){if(c)return;var d={};a(".display-option input, .display-option textarea").each(function(a,b){d[b.id]=b.type==="checkbox"?b.checked:b.value}),b=encodeURIComponent(JSON.stringify(d)),window.location.hash=b}function e(){var b=null;try{b=JSON.parse(decodeURIComponent(window.location.hash.replace(/^#/,"")))}catch(c){return}a(".display-option input, .display-option textarea").each(function(c,d){if(d.id in b){var e=null;d.type==="checkbox"?(e=d.checked,d.checked=b[d.id]):(e=d.value,d.value=b[d.id]),e!==b[d.id]&&a(d).trigger("change")}})}window.Etsy=window.Etsy||{},window.Etsy.Page=window.Etsy.Page||{};var b=window.location.hash;(function(){"onhashchange"in window||setInterval(function(){window.location.hash!==b&&(b=window.location.hash,a(window).trigger("hashchange"))},100)})();var c=!0;a(window).bind("hashchange",function(){b!==window.location.hash&&e()}),e(),a(window).bind("blur",function(){Etsy.Supergrep.setBackground(!0)}),a(window).bind("focus",function(){Etsy.Supergrep.setBackground(!1)}).trigger("focus"),a("#clear-log").bind("click",function(){return Etsy.Supergrep.clearEntries(),!1}),a("#legend-help").bind("click",function(){return a("#legend-dialog").dialog({autoOpen:!0,width:600,buttons:{Ok:function(){a(this).dialog("close")}}}),!1}),a("#guide-help").bind("click",function(){return guiders.hideAll(),guiders.show("guide_welcome"),!1}),a(".rule-help").bind("click",function(){return a("#rule-help-dialog").dialog({autoOpen:!0,width:600,buttons:{Ok:function(){a(this).dialog("close")}}}),!1}),a("#wrap-lines").bind("change",function(){d(),a("#results").toggleClass("wrapping",this.checked)}).trigger("change"),a("#sort-order").bind("change",function(){d(),Etsy.Supergrep.setSortOrder(this.checked?Etsy.Supergrep.SortOrder.Ascending:Etsy.Supergrep.SortOrder.Descending)}).trigger("change"),a("#autoscroll").bind("change",function(){d(),Etsy.Supergrep.setAutoscroll(this.checked)}).trigger("change"),a("#max-entries").bind("change",function(){var a=parseInt(this.value,10);if(isNaN(a)){this.value=Etsy.Supergrep.getMaxEntries();return}this.value=a,d(),Etsy.Supergrep.setMaxEntries(a)}).trigger("change"),a("#clear-field-filter").bind("click",function(){return a("#filter-log").val("").trigger("change"),!1}),a("#filter-log").bind("change",function(){var b=!1;try{b=!!Etsy.Supergrep.parseFilterRule(this.value)}catch(c){}if(a.trim(this.value)!==""&&!b){a("#filter-log").addClass("error");return}a("#filter-log").removeClass("error"),d(),Etsy.Supergrep.setFilter(this.value)}).trigger("change"),a("#filter-invert-option").bind("change",function(){Etsy.Supergrep.setFilterInvert(!!a("#filter-invert-option:checked").length)}).trigger("change"),a("#clear-field-highlight").bind("click",function(){return a("#highlight-log").val("").trigger("change"),!1}),a("#highlight-log").bind("change",function(){var b=!1;try{b=!!Etsy.Supergrep.parseFilterRule(this.value)}catch(c){}if(a.trim(this.value)!==""&&!b){a("#highlight-log").addClass("error");return}a("#highlight-log").removeClass("error"),d(),Etsy.Supergrep.setHighlight(this.value)}).trigger("change"),a(".log-option").bind("change",function(){var b=[],c=[];a(".log-option").each(function(){var d=a(this).attr("data-log");a(this).is(":checked")?b.push(d):c.push(d)}),d(),Etsy.Supergrep.watchLogs(b,c)}),a("#log-web").trigger("change"),c=!1,window.Etsy.Page.showBlame=function(c,d,e){var f=a(c).parents("li:first"),g=a('Loading...');a(c).remove(),f.append(g),a.ajax("/blame/web/"+d+"@"+e+","+e,{dataType:"jsonp",success:function(a,b,c){!a.error,g.text(a.error?"[Blamebot error: "+(a.error.details||a.error.msg)+"]":"["+(new Date(a["author-time"]*1e3)).format("m/d HH:MM:ss")+"] "+a.author+" - "+a.summary)}})}}); -------------------------------------------------------------------------------- /static/js/sha-256.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined 3 | * in FIPS 180-2 4 | * Version 2.2 Copyright Angel Marin, Paul Johnston 2000 - 2009. 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for details. 8 | * Also http://anmar.eu.org/projects/jssha2/ 9 | */ 10 | 11 | /* 12 | * Configurable variables. You may need to tweak these to be compatible with 13 | * the server-side, but the defaults work in most cases. 14 | */ 15 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 16 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ 17 | 18 | /* 19 | * These are the functions you'll usually want to call 20 | * They take string arguments and return either hex or base-64 encoded strings 21 | */ 22 | function hex_sha256(s) { return rstr2hex(rstr_sha256(str2rstr_utf8(s))); } 23 | function b64_sha256(s) { return rstr2b64(rstr_sha256(str2rstr_utf8(s))); } 24 | function any_sha256(s, e) { return rstr2any(rstr_sha256(str2rstr_utf8(s)), e); } 25 | function hex_hmac_sha256(k, d) 26 | { return rstr2hex(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d))); } 27 | function b64_hmac_sha256(k, d) 28 | { return rstr2b64(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d))); } 29 | function any_hmac_sha256(k, d, e) 30 | { return rstr2any(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d)), e); } 31 | 32 | /* 33 | * Perform a simple self-test to see if the VM is working 34 | */ 35 | function sha256_vm_test() 36 | { 37 | return hex_sha256("abc").toLowerCase() == 38 | "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; 39 | } 40 | 41 | /* 42 | * Calculate the sha256 of a raw string 43 | */ 44 | function rstr_sha256(s) 45 | { 46 | return binb2rstr(binb_sha256(rstr2binb(s), s.length * 8)); 47 | } 48 | 49 | /* 50 | * Calculate the HMAC-sha256 of a key and some data (raw strings) 51 | */ 52 | function rstr_hmac_sha256(key, data) 53 | { 54 | var bkey = rstr2binb(key); 55 | if(bkey.length > 16) bkey = binb_sha256(bkey, key.length * 8); 56 | 57 | var ipad = Array(16), opad = Array(16); 58 | for(var i = 0; i < 16; i++) 59 | { 60 | ipad[i] = bkey[i] ^ 0x36363636; 61 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 62 | } 63 | 64 | var hash = binb_sha256(ipad.concat(rstr2binb(data)), 512 + data.length * 8); 65 | return binb2rstr(binb_sha256(opad.concat(hash), 512 + 256)); 66 | } 67 | 68 | /* 69 | * Convert a raw string to a hex string 70 | */ 71 | function rstr2hex(input) 72 | { 73 | try { hexcase } catch(e) { hexcase=0; } 74 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 75 | var output = ""; 76 | var x; 77 | for(var i = 0; i < input.length; i++) 78 | { 79 | x = input.charCodeAt(i); 80 | output += hex_tab.charAt((x >>> 4) & 0x0F) 81 | + hex_tab.charAt( x & 0x0F); 82 | } 83 | return output; 84 | } 85 | 86 | /* 87 | * Convert a raw string to a base-64 string 88 | */ 89 | function rstr2b64(input) 90 | { 91 | try { b64pad } catch(e) { b64pad=''; } 92 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 93 | var output = ""; 94 | var len = input.length; 95 | for(var i = 0; i < len; i += 3) 96 | { 97 | var triplet = (input.charCodeAt(i) << 16) 98 | | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) 99 | | (i + 2 < len ? input.charCodeAt(i+2) : 0); 100 | for(var j = 0; j < 4; j++) 101 | { 102 | if(i * 8 + j * 6 > input.length * 8) output += b64pad; 103 | else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); 104 | } 105 | } 106 | return output; 107 | } 108 | 109 | /* 110 | * Convert a raw string to an arbitrary string encoding 111 | */ 112 | function rstr2any(input, encoding) 113 | { 114 | var divisor = encoding.length; 115 | var remainders = Array(); 116 | var i, q, x, quotient; 117 | 118 | /* Convert to an array of 16-bit big-endian values, forming the dividend */ 119 | var dividend = Array(Math.ceil(input.length / 2)); 120 | for(i = 0; i < dividend.length; i++) 121 | { 122 | dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); 123 | } 124 | 125 | /* 126 | * Repeatedly perform a long division. The binary array forms the dividend, 127 | * the length of the encoding is the divisor. Once computed, the quotient 128 | * forms the dividend for the next step. We stop when the dividend is zero. 129 | * All remainders are stored for later use. 130 | */ 131 | while(dividend.length > 0) 132 | { 133 | quotient = Array(); 134 | x = 0; 135 | for(i = 0; i < dividend.length; i++) 136 | { 137 | x = (x << 16) + dividend[i]; 138 | q = Math.floor(x / divisor); 139 | x -= q * divisor; 140 | if(quotient.length > 0 || q > 0) 141 | quotient[quotient.length] = q; 142 | } 143 | remainders[remainders.length] = x; 144 | dividend = quotient; 145 | } 146 | 147 | /* Convert the remainders to the output string */ 148 | var output = ""; 149 | for(i = remainders.length - 1; i >= 0; i--) 150 | output += encoding.charAt(remainders[i]); 151 | 152 | /* Append leading zero equivalents */ 153 | var full_length = Math.ceil(input.length * 8 / 154 | (Math.log(encoding.length) / Math.log(2))) 155 | for(i = output.length; i < full_length; i++) 156 | output = encoding[0] + output; 157 | 158 | return output; 159 | } 160 | 161 | /* 162 | * Encode a string as utf-8. 163 | * For efficiency, this assumes the input is valid utf-16. 164 | */ 165 | function str2rstr_utf8(input) 166 | { 167 | var output = ""; 168 | var i = -1; 169 | var x, y; 170 | 171 | while(++i < input.length) 172 | { 173 | /* Decode utf-16 surrogate pairs */ 174 | x = input.charCodeAt(i); 175 | y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; 176 | if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) 177 | { 178 | x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); 179 | i++; 180 | } 181 | 182 | /* Encode output as utf-8 */ 183 | if(x <= 0x7F) 184 | output += String.fromCharCode(x); 185 | else if(x <= 0x7FF) 186 | output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), 187 | 0x80 | ( x & 0x3F)); 188 | else if(x <= 0xFFFF) 189 | output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), 190 | 0x80 | ((x >>> 6 ) & 0x3F), 191 | 0x80 | ( x & 0x3F)); 192 | else if(x <= 0x1FFFFF) 193 | output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), 194 | 0x80 | ((x >>> 12) & 0x3F), 195 | 0x80 | ((x >>> 6 ) & 0x3F), 196 | 0x80 | ( x & 0x3F)); 197 | } 198 | return output; 199 | } 200 | 201 | /* 202 | * Encode a string as utf-16 203 | */ 204 | function str2rstr_utf16le(input) 205 | { 206 | var output = ""; 207 | for(var i = 0; i < input.length; i++) 208 | output += String.fromCharCode( input.charCodeAt(i) & 0xFF, 209 | (input.charCodeAt(i) >>> 8) & 0xFF); 210 | return output; 211 | } 212 | 213 | function str2rstr_utf16be(input) 214 | { 215 | var output = ""; 216 | for(var i = 0; i < input.length; i++) 217 | output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, 218 | input.charCodeAt(i) & 0xFF); 219 | return output; 220 | } 221 | 222 | /* 223 | * Convert a raw string to an array of big-endian words 224 | * Characters >255 have their high-byte silently ignored. 225 | */ 226 | function rstr2binb(input) 227 | { 228 | var output = Array(input.length >> 2); 229 | for(var i = 0; i < output.length; i++) 230 | output[i] = 0; 231 | for(var i = 0; i < input.length * 8; i += 8) 232 | output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32); 233 | return output; 234 | } 235 | 236 | /* 237 | * Convert an array of big-endian words to a string 238 | */ 239 | function binb2rstr(input) 240 | { 241 | var output = ""; 242 | for(var i = 0; i < input.length * 32; i += 8) 243 | output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF); 244 | return output; 245 | } 246 | 247 | /* 248 | * Main sha256 function, with its support functions 249 | */ 250 | function sha256_S (X, n) {return ( X >>> n ) | (X << (32 - n));} 251 | function sha256_R (X, n) {return ( X >>> n );} 252 | function sha256_Ch(x, y, z) {return ((x & y) ^ ((~x) & z));} 253 | function sha256_Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));} 254 | function sha256_Sigma0256(x) {return (sha256_S(x, 2) ^ sha256_S(x, 13) ^ sha256_S(x, 22));} 255 | function sha256_Sigma1256(x) {return (sha256_S(x, 6) ^ sha256_S(x, 11) ^ sha256_S(x, 25));} 256 | function sha256_Gamma0256(x) {return (sha256_S(x, 7) ^ sha256_S(x, 18) ^ sha256_R(x, 3));} 257 | function sha256_Gamma1256(x) {return (sha256_S(x, 17) ^ sha256_S(x, 19) ^ sha256_R(x, 10));} 258 | function sha256_Sigma0512(x) {return (sha256_S(x, 28) ^ sha256_S(x, 34) ^ sha256_S(x, 39));} 259 | function sha256_Sigma1512(x) {return (sha256_S(x, 14) ^ sha256_S(x, 18) ^ sha256_S(x, 41));} 260 | function sha256_Gamma0512(x) {return (sha256_S(x, 1) ^ sha256_S(x, 8) ^ sha256_R(x, 7));} 261 | function sha256_Gamma1512(x) {return (sha256_S(x, 19) ^ sha256_S(x, 61) ^ sha256_R(x, 6));} 262 | 263 | var sha256_K = new Array 264 | ( 265 | 1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, 266 | -1841331548, -1424204075, -670586216, 310598401, 607225278, 1426881987, 267 | 1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522, 268 | 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 269 | -1740746414, -1473132947, -1341970488, -1084653625, -958395405, -710438585, 270 | 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 271 | 1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885, 272 | -1035236496, -949202525, -778901479, -694614492, -200395387, 275423344, 273 | 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 274 | 1537002063, 1747873779, 1955562222, 2024104815, -2067236844, -1933114872, 275 | -1866530822, -1538233109, -1090935817, -965641998 276 | ); 277 | 278 | function binb_sha256(m, l) 279 | { 280 | var HASH = new Array(1779033703, -1150833019, 1013904242, -1521486534, 281 | 1359893119, -1694144372, 528734635, 1541459225); 282 | var W = new Array(64); 283 | var a, b, c, d, e, f, g, h; 284 | var i, j, T1, T2; 285 | 286 | /* append padding */ 287 | m[l >> 5] |= 0x80 << (24 - l % 32); 288 | m[((l + 64 >> 9) << 4) + 15] = l; 289 | 290 | for(i = 0; i < m.length; i += 16) 291 | { 292 | a = HASH[0]; 293 | b = HASH[1]; 294 | c = HASH[2]; 295 | d = HASH[3]; 296 | e = HASH[4]; 297 | f = HASH[5]; 298 | g = HASH[6]; 299 | h = HASH[7]; 300 | 301 | for(j = 0; j < 64; j++) 302 | { 303 | if (j < 16) W[j] = m[j + i]; 304 | else W[j] = safe_add(safe_add(safe_add(sha256_Gamma1256(W[j - 2]), W[j - 7]), 305 | sha256_Gamma0256(W[j - 15])), W[j - 16]); 306 | 307 | T1 = safe_add(safe_add(safe_add(safe_add(h, sha256_Sigma1256(e)), sha256_Ch(e, f, g)), 308 | sha256_K[j]), W[j]); 309 | T2 = safe_add(sha256_Sigma0256(a), sha256_Maj(a, b, c)); 310 | h = g; 311 | g = f; 312 | f = e; 313 | e = safe_add(d, T1); 314 | d = c; 315 | c = b; 316 | b = a; 317 | a = safe_add(T1, T2); 318 | } 319 | 320 | HASH[0] = safe_add(a, HASH[0]); 321 | HASH[1] = safe_add(b, HASH[1]); 322 | HASH[2] = safe_add(c, HASH[2]); 323 | HASH[3] = safe_add(d, HASH[3]); 324 | HASH[4] = safe_add(e, HASH[4]); 325 | HASH[5] = safe_add(f, HASH[5]); 326 | HASH[6] = safe_add(g, HASH[6]); 327 | HASH[7] = safe_add(h, HASH[7]); 328 | } 329 | return HASH; 330 | } 331 | 332 | function safe_add (x, y) 333 | { 334 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 335 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 336 | return (msw << 16) | (lsw & 0xFFFF); 337 | } 338 | -------------------------------------------------------------------------------- /static/js/sha-256.min.js: -------------------------------------------------------------------------------- 1 | var hexcase=0,b64pad="";function hex_sha256(a){return rstr2hex(rstr_sha256(str2rstr_utf8(a)))}function b64_sha256(a){return rstr2b64(rstr_sha256(str2rstr_utf8(a)))}function any_sha256(a,c){return rstr2any(rstr_sha256(str2rstr_utf8(a)),c)}function hex_hmac_sha256(a,c){return rstr2hex(rstr_hmac_sha256(str2rstr_utf8(a),str2rstr_utf8(c)))}function b64_hmac_sha256(a,c){return rstr2b64(rstr_hmac_sha256(str2rstr_utf8(a),str2rstr_utf8(c)))} function any_hmac_sha256(a,c,b){return rstr2any(rstr_hmac_sha256(str2rstr_utf8(a),str2rstr_utf8(c)),b)}function sha256_vm_test(){return"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"==hex_sha256("abc").toLowerCase()}function rstr_sha256(a){return binb2rstr(binb_sha256(rstr2binb(a),8*a.length))} function rstr_hmac_sha256(a,c){var b=rstr2binb(a);16f;f++)d[f]=b[f]^909522486,e[f]=b[f]^1549556828;b=binb_sha256(d.concat(rstr2binb(c)),512+8*c.length);return binb2rstr(binb_sha256(e.concat(b),768))}function rstr2hex(a){for(var c=hexcase?"0123456789ABCDEF":"0123456789abcdef",b="",d,e=0;e>>4&15)+c.charAt(d&15);return b} function rstr2b64(a){for(var c="",b=a.length,d=0;df;f++)c=8*d+6*f>8*a.length?c+b64pad:c+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e>>>6*(3-f)&63);return c} function rstr2any(a,c){var b=c.length,d=[],e,f,g,j,h=Array(Math.ceil(a.length/2));for(e=0;e=d&&56320<=e&&57343>=e&&(d=65536+((d&1023)<<10)+(e&1023),b++),127>=d?c+=String.fromCharCode(d):2047>=d?c+=String.fromCharCode(192|d>>>6&31,128|d&63):65535>=d?c+=String.fromCharCode(224|d>>>12&15,128|d>>>6&63,128|d&63):2097151>=d&&(c+=String.fromCharCode(240|d>>>18&7,128|d>>>12&63,128|d>>>6&63,128|d&63));return c} function str2rstr_utf16le(a){for(var c="",b=0;b>>8&255);return c}function str2rstr_utf16be(a){for(var c="",b=0;b>>8&255,a.charCodeAt(b)&255);return c}function rstr2binb(a){for(var c=Array(a.length>>2),b=0;b>5]|=(a.charCodeAt(b/8)&255)<<24-b%32;return c} function binb2rstr(a){for(var c="",b=0;b<32*a.length;b+=8)c+=String.fromCharCode(a[b>>5]>>>24-b%32&255);return c}function sha256_S(a,c){return a>>>c|a<<32-c}function sha256_R(a,c){return a>>>c}function sha256_Ch(a,c,b){return a&c^~a&b}function sha256_Maj(a,c,b){return a&c^a&b^c&b}function sha256_Sigma0256(a){return sha256_S(a,2)^sha256_S(a,13)^sha256_S(a,22)}function sha256_Sigma1256(a){return sha256_S(a,6)^sha256_S(a,11)^sha256_S(a,25)} function sha256_Gamma0256(a){return sha256_S(a,7)^sha256_S(a,18)^sha256_R(a,3)}function sha256_Gamma1256(a){return sha256_S(a,17)^sha256_S(a,19)^sha256_R(a,10)}function sha256_Sigma0512(a){return sha256_S(a,28)^sha256_S(a,34)^sha256_S(a,39)}function sha256_Sigma1512(a){return sha256_S(a,14)^sha256_S(a,18)^sha256_S(a,41)}function sha256_Gamma0512(a){return sha256_S(a,1)^sha256_S(a,8)^sha256_R(a,7)}function sha256_Gamma1512(a){return sha256_S(a,19)^sha256_S(a,61)^sha256_R(a,6)} var sha256_K=[1116352408,1899447441,-1245643825,-373957723,961987163,1508970993,-1841331548,-1424204075,-670586216,310598401,607225278,1426881987,1925078388,-2132889090,-1680079193,-1046744716,-459576895,-272742522,264347078,604807628,770255983,1249150122,1555081692,1996064986,-1740746414,-1473132947,-1341970488,-1084653625,-958395405,-710438585,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,-2117940946,-1838011259,-1564481375,-1474664885,-1035236496,-949202525, -778901479,-694614492,-200395387,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,-2067236844,-1933114872,-1866530822,-1538233109,-1090935817,-965641998]; function binb_sha256(a,c){var b=[1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225],d=Array(64),e,f,g,j,h,k,l,n,m,i,o,p;a[c>>5]|=128<<24-c%32;a[(c+64>>9<<4)+15]=c;for(m=0;mi;i++)d[i]=16>i?a[i+m]:safe_add(safe_add(safe_add(sha256_Gamma1256(d[i-2]),d[i-7]),sha256_Gamma0256(d[i-15])),d[i-16]),o=safe_add(safe_add(safe_add(safe_add(n,sha256_Sigma1256(h)),sha256_Ch(h,k,l)),sha256_K[i]), d[i]),p=safe_add(sha256_Sigma0256(e),sha256_Maj(e,f,g)),n=l,l=k,k=h,h=safe_add(j,o),j=g,g=f,f=e,e=safe_add(o,p);b[0]=safe_add(e,b[0]);b[1]=safe_add(f,b[1]);b[2]=safe_add(g,b[2]);b[3]=safe_add(j,b[3]);b[4]=safe_add(h,b[4]);b[5]=safe_add(k,b[5]);b[6]=safe_add(l,b[6]);b[7]=safe_add(n,b[7])}return b}function safe_add(a,c){var b=(a&65535)+(c&65535);return(a>>16)+(c>>16)+(b>>16)<<16|b&65535}; -------------------------------------------------------------------------------- /static/js/supergrep.js: -------------------------------------------------------------------------------- 1 | // Log streaming 2 | (function (window, $) { 3 | window.console = window.console || { log: function () {} }; 4 | 5 | window.Etsy = window.Etsy || {}; 6 | window.Etsy.Supergrep = window.Etsy.Supergrep || {}; 7 | 8 | var sg = window.Etsy.Supergrep; 9 | 10 | sg.SortOrder = { 11 | Ascending: 'asc', 12 | Descending: 'dsc' 13 | }; 14 | 15 | var pvt = { 16 | socket: null, 17 | targetLogs: [], 18 | logEntries: [], 19 | entryCounter: 0, 20 | hashes: {}, 21 | unseen: 0, 22 | config: { 23 | maxEntries: 500, 24 | sortOrder: sg.SortOrder.Descending, 25 | autoscroll: true, 26 | filter: null, 27 | highlight: null, 28 | background: false, 29 | pageTitle: 'Supergrep++' 30 | }, 31 | paths: { 32 | githubWebPrefix: 'https://github.com/your/repo/blob/master/', 33 | }, 34 | regex: { 35 | /* Log parsing regexes */ 36 | meta: /^((?:web)[\d]+)\s*\:?\s*\[([^\]]+)\]\s\[([^\]]+)\]\s\[(?:client\s*)?([^\]]+)\]/i, 37 | //DEV version of META regex 38 | meta_dev: /^((?:web)[\d]+)\s*\:?\s*\[([^\]]+)\]\s\[([^\]]+)\]\s\[(?:client\s*)?([^\]]+)\]/i, 39 | 40 | web: /^((?:web)[\d]+)\s*\:?\s*\[([^\]]+)\]\s(?:\[[^\]]+\])\s\[(?:client\s*)?([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s+/i, 41 | //DEV version of WEB regex 42 | web_dev: /^()\[([^\\[\]]+)\]\s()\[([^\\[\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+/i, 43 | 44 | gearman: /^((?:web)[\d]+)\s*\:?\s*\[([^\]]+)\]\s()\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s+/i, 45 | //DEV version of GEARMAN regex 46 | gearman_dev: /^()\[([^\]]+)\]\s()\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s+/i, 47 | 48 | /* Log data parsing regexes */ 49 | error: /\[(error)\]/ig, 50 | warning: /\[(warning)\]/ig, 51 | info: /\[((info|notice))\]/ig, 52 | debug: /\[(debug)\]/ig, 53 | phpFatal: /PHP Fatal/i, 54 | 55 | /* Stacktrace parsing regexes */ 56 | stacktrace: /(\s#\d+\s)/g, 57 | fullStacktrace: /(?:stack trace:\s+|backtrace:(?:\\n|\s)*)(#\d+.+\.php(?:\:\d+|\(\d+\)))/i, 58 | stackSplitter: /\s*#\d+\s+/, 59 | stackFile: /(\/your\/path\/here\/|\/home\/[^\\\/]+)/i, 60 | phpFile: /\.php/i, 61 | stackInternalFunc: /\[internal\sfunction\]:\s*/i, 62 | stackFileLine: /\:\d+$/, 63 | stackAltFileLine: /\.php\((\d+)\):/i, 64 | 65 | /* Misc */ 66 | codepath: /((\/your\/path\/here\/)([^\.]+.php)(?:\(([\d]+)\)|(?:\s*on)?\s+line\s+(\d+)|:(\d+)))/i, 67 | //Check to see if log is from DEV server 68 | devtest: /^\[/, 69 | token: /\[([a-z0-9-_]{28})\]/i, 70 | severity: /(?:\[[a-z0-9-_]{28}\])\s\[(error|warning)\]/i, 71 | fileNotFound: /File does not exist/i, 72 | url: /((?:https?:\/\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig 73 | } 74 | }; 75 | 76 | function socketReconnect(sleep) { 77 | sg.writeMsg("Lost connection... retrying in " + sleep + " seconds"); 78 | //TODO: configure timeout 79 | setTimeout(function() { 80 | if (!pvt.socket.socket.connected) { 81 | pvt.socket = io.connect(); 82 | } 83 | }, sleep * 1000); 84 | setTimeout(function() { 85 | if (!pvt.socket.socket.connected && !pvt.socket.socket.connecting) { 86 | socketReconnect(sleep * 2); 87 | } 88 | }, (sleep + 3) * 1000); 89 | } 90 | 91 | sg.submitGist = function Etsy$Supergrep$submitGist (ns, timestamp, data) { 92 | $('#gistform-filename').val("supergrep[" + ns + "]" + timestamp); 93 | $('#gistform-data').val(unescape(data)); 94 | $('#gistform').get(0).submit(); 95 | }; 96 | 97 | sg.submitIrc = function Etsy$Supergrep$submitIrc (target, data) { 98 | target = $.trim(target); 99 | 100 | if (!target) { 101 | return; 102 | } 103 | if (!target.match(/^(@|#)/)) { 104 | return; 105 | } 106 | 107 | var targetParts = target.split(' '); 108 | target = targetParts.shift(); 109 | data = targetParts.join(' ') + "\n" + data; 110 | 111 | $.ajax('/v2/irccat', { 112 | type: 'POST', 113 | data: { target: target, data: data } 114 | }); 115 | }; 116 | 117 | sg.watchLogs = function Etsy$Supergrep$watchLogs (subscribedLogs, unsubscribedLogs) { 118 | pvt.subscribedLogs = subscribedLogs; 119 | pvt.unsubscribedLogs = unsubscribedLogs; 120 | 121 | if (!pvt.socket) { 122 | pvt.socket = io.connect(document.location.hostname, { 123 | transports: ['xhr-polling'] 124 | }); 125 | 126 | pvt.socket.on('connect', function () { 127 | $("#loading").hide(); 128 | sg.writeMsg("Connection established"); 129 | pvt.socket.emit('subscriptions', { 130 | subscribeTo: pvt.subscribedLogs, 131 | unsubscribeFrom: pvt.unsubscribedLogs 132 | }); 133 | }); 134 | 135 | pvt.socket.on('lines', function (data) { 136 | sg.writeLogEntries(data); 137 | }); 138 | 139 | pvt.socket.on('disconnect', function () { 140 | if (! pvt.socket.connected) { 141 | socketReconnect(2); 142 | } 143 | }); 144 | } else { 145 | pvt.socket.emit('subscriptions', { 146 | subscribeTo: pvt.subscribedLogs, 147 | unsubscribeFrom: pvt.unsubscribedLogs 148 | }); 149 | } 150 | }; 151 | 152 | sg.setBackground = function Etsy$Supergrep$setBackground (isBackground) { 153 | pvt.config.background = !!isBackground; 154 | if (!pvt.config.background) { 155 | pvt.unseen = 0; 156 | $(document).attr("title", pvt.config.pageTitle); 157 | } 158 | }; 159 | 160 | sg.parseFilterRule = function Etsy$Supergrep$parseFilterRule (rule) { 161 | rule = $.trim(rule); 162 | if (rule === '') { 163 | return null; 164 | } 165 | 166 | var result = rule.match(/^\/(.+)\/([igsm]*)$/); 167 | if (result) { 168 | return new RegExp(result[1], result[2]); 169 | } 170 | 171 | rule = rule.replace(/([\\\/\.\*\+\?\(\)\[\]\{\}\-\|])/g, '\\$1'); 172 | var ruleParts = rule.split(/(?:\r\n|\n|\,)+/); 173 | var rulePartsSanitized = []; 174 | for (var i = 0, len = ruleParts.length; i < len; i++) { 175 | var part = $.trim(ruleParts[i]); 176 | if (part !== '') { 177 | rulePartsSanitized.push(part); 178 | } 179 | } 180 | if (!rulePartsSanitized.length) { 181 | return null; 182 | } 183 | 184 | return new RegExp('(' + rulePartsSanitized.join('|') + ')', 'i'); 185 | }; 186 | 187 | sg.setFilterInvert = function Etsy$Supergrep$setFilterInvert (invert) { 188 | pvt.config.filterInvert = !!invert; 189 | for (var i = 0, len = pvt.logEntries.length; i < len; i++) { 190 | this.evalLogEntry(pvt.logEntries[i]); 191 | } 192 | }; 193 | 194 | sg.setFilter = function Etsy$Supergrep$setFilter (match) { 195 | pvt.config.filter = sg.parseFilterRule(match); 196 | for (var i = 0, len = pvt.logEntries.length; i < len; i++) { 197 | this.evalLogEntry(pvt.logEntries[i]); 198 | } 199 | }; 200 | 201 | sg.setHighlight = function Etsy$Supergrep$setHighlight (match) { 202 | pvt.config.highlight = sg.parseFilterRule(match); 203 | for (var i = 0, len = pvt.logEntries.length; i < len; i++) { 204 | this.evalLogEntry(pvt.logEntries[i]); 205 | } 206 | }; 207 | 208 | sg.evalLogEntry = function Etsy$Supergrep$evalLogEntry (entry) { 209 | if (pvt.config.highlight && entry.extra.rawdata.match(pvt.config.highlight)) { 210 | entry.element.addClass('highlight'); 211 | } else { 212 | entry.element.removeClass('highlight'); 213 | } 214 | if (pvt.config.filter && entry.extra.rawdata.match(pvt.config.filter)) { 215 | if (pvt.config.filterInvert) { 216 | entry.element.removeClass('hide'); 217 | return true; 218 | } else { 219 | entry.element.addClass('hide'); 220 | return false; 221 | } 222 | } else { 223 | if (pvt.config.filter && pvt.config.filterInvert) { 224 | entry.element.addClass('hide'); 225 | return false; 226 | } else { 227 | entry.element.removeClass('hide'); 228 | return true; 229 | } 230 | } 231 | }; 232 | 233 | sg.setAutoscroll = function Etsy$Supergrep$setAutoscroll (autoscroll) { 234 | pvt.config.autoscroll = !!autoscroll; 235 | sg.scrollLog(); 236 | }; 237 | 238 | sg.scrollLog = function Etsy$Supergrep$scrollLog (entry) { 239 | if (pvt.config.sortOrder === this.SortOrder.Ascending) { 240 | if (!pvt.config.autoscroll) { 241 | return; 242 | } 243 | $("html").scrollTop($("html").height()); 244 | } else { 245 | if (!pvt.config.autoscroll && entry) { 246 | $("html").scrollTop($("html").scrollTop() + entry.outerHeight()); 247 | } else { 248 | $("html").scrollTop(0); 249 | } 250 | } 251 | }; 252 | 253 | 254 | sg.setSortOrder = function Etsy$Supergrep$setSortOrder (sortOrder) { 255 | if (pvt.config.sortOrder === sortOrder) { 256 | return; 257 | } 258 | pvt.config.sortOrder = sortOrder; 259 | $('#results-list').empty(); 260 | for (var i = 0, len = pvt.logEntries.length; i < len; i++) { 261 | this.displayEntry(pvt.logEntries[i].element); 262 | } 263 | }; 264 | 265 | sg.getMaxEntries = function Etsy$Supergrep$getMaxEntries () { 266 | return pvt.config.maxEntries; 267 | }; 268 | 269 | sg.setMaxEntries = function Etsy$Supergrep$setMaxEntries (max) { 270 | if (pvt.config.maxEntries === max) { 271 | return; 272 | } 273 | pvt.config.maxEntries = max; 274 | sg.trimEntries(); 275 | }; 276 | 277 | sg.trimEntries = function Etsy$Supergrep$trimEntries () { 278 | while (pvt.logEntries.length > pvt.config.maxEntries) { 279 | var entry = pvt.logEntries.shift(); 280 | entry.element.remove(); 281 | if (entry.extra.hash) { 282 | delete pvt.hashes[entry.extra.hash]; 283 | } 284 | } 285 | }; 286 | 287 | sg.displayEntry = function Etsy$Supergrep$displayEntry (element) { 288 | if (pvt.config.sortOrder === this.SortOrder.Ascending) { 289 | $(element).appendTo('#results-list'); 290 | } else { 291 | $(element).prependTo('#results-list'); 292 | } 293 | sg.scrollLog($(element)); 294 | }; 295 | 296 | sg.addEntry = function Etsy$Supergrep$addEntry (element, extra) { 297 | element = $(element); 298 | var entry = { element: element, extra: extra }; 299 | var showing_entry = sg.evalLogEntry(entry); 300 | pvt.logEntries.push(entry); 301 | this.displayEntry(element); 302 | this.trimEntries(); 303 | if (showing_entry && pvt.config.background) { 304 | pvt.unseen++; 305 | $(document).attr("title", '(' + pvt.unseen + ' new) ' + pvt.config.pageTitle); 306 | } 307 | }; 308 | 309 | sg.clearEntries = function Etsy$Supergrep$clearEntries () { 310 | pvt.logEntries = []; 311 | $('#results-list').empty(); 312 | }; 313 | 314 | sg.writeMsg = function Etsy$Supergrep$writeMsg (data) { 315 | var templateData = { id: 'msg' + pvt.entryCounter++, data: data, rawdata: data }; 316 | this.addEntry($(CWinberry.Templating.render('tpl_msgEntry', templateData)), templateData); 317 | }; 318 | 319 | sg.parseLogEntry = function Etsy$Supergrep$parseLogEntry (ns, data) { 320 | try{ 321 | var output = { ns: ns, rawdata: data, id: 'log' + pvt.entryCounter++ }; 322 | 323 | //Check if the logs are from a DEV server 324 | var isDev = pvt.regex.devtest.test(data); 325 | 326 | var meta_matches = data.match(pvt.regex.meta); 327 | var error_matches = data.match(pvt.regex.error); 328 | var warning_matches = data.match(pvt.regex.warning); 329 | var token_matches = data.match(pvt.regex.token); 330 | var severity_matches = data.match(pvt.regex.severity); 331 | 332 | var severity = severity_matches ? severity_matches[1] : 'info'; 333 | 334 | //TODO: break parsing into multiple functions 335 | if (ns === 'web') { 336 | var web_matches = data.match( 337 | (ns === 'gearman') ? 338 | (isDev ? pvt.regex.gearman_dev : pvt.regex.gearman) 339 | : 340 | (isDev ? pvt.regex.web_dev : pvt.regex.web) 341 | ); 342 | if (web_matches && web_matches.length === 9) { 343 | data = data.replace(web_matches[0], ''); 344 | output.server = web_matches[1] || 'localhost'; 345 | output.timestamp = new Date(web_matches[2]); 346 | //TODO: get better data parsing 347 | if (isNaN(output.timestamp.getTime())) { 348 | output.timestamp = new Date(0); 349 | } 350 | output.client = web_matches[3] || '127.0.0.1'; 351 | output.hash = web_matches[4]; 352 | output.severity = web_matches[5]; 353 | output.namespace = web_matches[6]; 354 | //TODO: standardize this 355 | var pathTmp = web_matches[7] ? web_matches[7].split(':') : ['', 0]; 356 | output.path = { 357 | url: web_matches[7].replace(pvt.regex.codepath, pvt.paths.githubWebPrefix + '$3#L$4$5$6'), 358 | file: pathTmp[0].replace(pvt.regex.stackFile, ''), 359 | line: pathTmp[1] 360 | }; 361 | output.userid = (web_matches[8] && web_matches[8] !== '0') ? web_matches[8] : ''; 362 | 363 | // Parse the callstack 364 | var fullStack = data.match(pvt.regex.fullStacktrace); 365 | if (fullStack) { 366 | output.stacktrace = []; 367 | var stack = fullStack[1].split(pvt.regex.stackSplitter); 368 | stack.shift(); 369 | for (var i = 0, len = stack.length; i < len; i++) { 370 | var stackParts = stack[i].replace(pvt.regex.stackInternalFunc, '').split(' '); 371 | var codeLine; 372 | if (stack[i].match(pvt.regex.phpFile)) { 373 | codeLine = stack[i].match(pvt.regex.stackAltFileLine) ? 374 | stackParts.shift().replace(pvt.regex.stackAltFileLine, '.php:$1') 375 | : 376 | stackParts.pop() 377 | ; 378 | } else { 379 | codeLine = ''; 380 | } 381 | var codeLineParts; 382 | if (codeLine.match(pvt.regex.stackFileLine)) { 383 | codeLineParts = codeLine.split(':'); 384 | } else { 385 | codeLineParts = ['', '0']; 386 | stackParts.push(codeLine); 387 | } 388 | var stackCall = stackParts.join(' '); 389 | var file = codeLineParts[0].replace(pvt.regex.stackFile, ''); 390 | var line = codeLineParts[1].replace('):', ''); 391 | if (file.indexOf('/') === 0) { 392 | file = file.substring(1); 393 | } 394 | output.stacktrace.push({ 395 | call: stackCall, 396 | file: file, 397 | line: line, 398 | url: !file ? '' : (pvt.paths.githubWebPrefix + file + '#L' + line) //TODO: detect search github files 399 | }); 400 | } 401 | 402 | data = data.replace(fullStack[0], ''); 403 | } 404 | } else { 405 | if (meta_matches && meta_matches.length === 5) { 406 | output.server = meta_matches[1]; 407 | output.timestamp = new Date(meta_matches[2]); 408 | output.severity = meta_matches[3]; 409 | output.client = meta_matches[4]; 410 | 411 | // create a mapping of groups to names to be used as classnames 412 | var meta_named_groups = { 413 | 'server' : meta_matches[1], 414 | 'timestamp' : meta_matches[2], 415 | 'apache_info' : meta_matches[3], 416 | 'client' : meta_matches[4] 417 | }; 418 | } 419 | 420 | //TODO: change the way this is rendered 421 | for (var key in meta_named_groups) { 422 | regex = new RegExp('(' + meta_named_groups[key].replace('[', '\\[').replace(']', '\\]') + ')', 'g'); 423 | data = data.replace(regex, '$1'); 424 | } 425 | } 426 | } else { 427 | //Failed to recognize format so make a generic entry 428 | //TODO: change the way this is rendered 429 | data = data + (name ? '[' + name + '] ' : ''); 430 | } 431 | 432 | //Linkify any URLs floating around 433 | data = data.replace(pvt.regex.url, "$1"); 434 | 435 | //Save token if defined 436 | if (token_matches) { 437 | output.token = token_matches[1]; 438 | } 439 | 440 | //Linkify any sourcecode references 441 | data = data.replace(pvt.regex.codepath, '$3:$4$5$6'); 442 | 443 | //Mark PHP fatal errors 444 | if (output.rawdata.match(pvt.regex.phpFatal)) { 445 | output.isFatal = true; 446 | } 447 | 448 | //Remove any extraneous whitespace 449 | output.data = $.trim(data); 450 | 451 | return output; 452 | 453 | } catch (ex) { console.log(ex); } 454 | }; 455 | 456 | sg.writeLogEntry = function Etsy$Supergrep$writeLogEntry (ns, data) { 457 | var dataHash = hex_sha256(data); 458 | if (pvt.hashes[dataHash]) { 459 | return; 460 | } 461 | var templateData = this.parseLogEntry(ns, data); 462 | pvt.hashes[dataHash] = 1; 463 | templateData.hash = dataHash; 464 | this.addEntry($(CWinberry.Templating.render('tpl_logEntry', templateData)), templateData); 465 | }; 466 | 467 | sg.writeLogEntries = function Etsy$Supergrep$writeLogEntries (entries) { 468 | $.each(entries.lines, function (index, value) { 469 | sg.writeLogEntry(entries.name, value); 470 | }); 471 | }; 472 | 473 | })(window, jQuery); 474 | -------------------------------------------------------------------------------- /static/js/supergrep.min.js: -------------------------------------------------------------------------------- 1 | // Log streaming 2 | (function(a,b){function e(a){c.writeMsg("Lost connection... retrying in "+a+" seconds"),setTimeout(function(){d.socket.socket.connected||(d.socket=io.connect())},a*1e3),setTimeout(function(){!d.socket.socket.connected&&!d.socket.socket.connecting&&e(a*2)},(a+3)*1e3)}a.console=a.console||{log:function(){}},a.Etsy=a.Etsy||{},a.Etsy.Supergrep=a.Etsy.Supergrep||{};var c=a.Etsy.Supergrep;c.SortOrder={Ascending:"asc",Descending:"dsc"};var d={socket:null,targetLogs:[],logEntries:[],entryCounter:0,hashes:{},unseen:0,config:{maxEntries:500,sortOrder:c.SortOrder.Descending,autoscroll:!0,filter:null,highlight:null,background:!1,pageTitle:"Supergrep++"},paths:{githubWebPrefix:"https://github.com/your/repo/blob/master/"},regex:{meta:/^((?:web)[\d]+)\s*\:?\s*\[([^\]]+)\]\s\[([^\]]+)\]\s\[(?:client\s*)?([^\]]+)\]/i,meta_dev:/^((?:web)[\d]+)\s*\:?\s*\[([^\]]+)\]\s\[([^\]]+)\]\s\[(?:client\s*)?([^\]]+)\]/i,web:/^((?:web)[\d]+)\s*\:?\s*\[([^\]]+)\]\s(?:\[[^\]]+\])\s\[(?:client\s*)?([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s+/i,web_dev:/^()\[([^\\[\]]+)\]\s()\[([^\\[\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+/i,gearman:/^((?:web|preprod\-web|atlasweb|api|worker)[\d]+)\s*\:?\s*\[([^\]]+)\]\s()\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s+/i,gearman_dev:/^()\[([^\]]+)\]\s()\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s\[([^\]]+)\]\s+/i,error:/\[(error)\]/ig,warning:/\[(warning)\]/ig,info:/\[((info|notice))\]/ig,debug:/\[(debug)\]/ig,phpFatal:/PHP Fatal/i,stacktrace:/(\s#\d+\s)/g,fullStacktrace:/(?:stack trace:\s+|backtrace:(?:\\n|\s)*)(#\d+.+\.php(?:\:\d+|\(\d+\)))/i,stackSplitter:/\s*#\d+\s+/,stackFile:/(\/your\/path\/here\/|\/home\/[^\\\/]+)/i,phpFile:/\.php/i,stackInternalFunc:/\[internal\sfunction\]:\s*/i,stackFileLine:/\:\d+$/,stackAltFileLine:/\.php\((\d+)\):/i,codepath:/((\/your\/path\/here\/)([^\.]+.php)(?:\(([\d]+)\)|(?:\s*on)?\s+line\s+(\d+)|:(\d+)))/i,devtest:/^\[/,token:/\[([a-z0-9-_]{28})\]/i,severity:/(?:\[[a-z0-9-_]{28}\])\s\[(error|warning)\]/i,fileNotFound:/File does not exist/i,url:/((?:https?:\/\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig}};c.submitGist=function(c,d,e){b("#gistform-filename").val("supergrep["+c+"]"+d),b("#gistform-data").val(unescape(e)),b("#gistform").get(0).submit()},c.submitIrc=function(c,d){c=b.trim(c);if(!c)return;if(!c.match(/^(@|#)/))return;var e=c.split(" ");c=e.shift(),d=e.join(" ")+"\n"+d,b.ajax("/v2/irccat",{type:"POST",data:{target:c,data:d}})},c.watchLogs=function(f,g){d.subscribedLogs=f,d.unsubscribedLogs=g,d.socket?d.socket.emit("subscriptions",{subscribeTo:d.subscribedLogs,unsubscribeFrom:d.unsubscribedLogs}):(d.socket=io.connect(document.location.hostname,{transports:["xhr-polling"]}),d.socket.on("connect",function(){b("#loading").hide(),c.writeMsg("Connection established"),d.socket.emit("subscriptions",{subscribeTo:d.subscribedLogs,unsubscribeFrom:d.unsubscribedLogs})}),d.socket.on("lines",function(a){c.writeLogEntries(a)}),d.socket.on("disconnect",function(){d.socket.connected||e(2)}))},c.setBackground=function(c){d.config.background=!!c,d.config.background||(d.unseen=0,b(document).attr("title",d.config.pageTitle))},c.parseFilterRule=function(c){c=b.trim(c);if(c==="")return null;var d=c.match(/^\/(.+)\/([igsm]*)$/);if(d)return new RegExp(d[1],d[2]);c=c.replace(/([\\\/\.\*\+\?\(\)\[\]\{\}\-\|])/g,"\\$1");var e=c.split(/(?:\r\n|\n|\,)+/),f=[];for(var g=0,h=e.length;gd.config.maxEntries){var b=d.logEntries.shift();b.element.remove(),b.extra.hash&&delete d.hashes[b.extra.hash]}},c.displayEntry=function(e){d.config.sortOrder===this.SortOrder.Ascending?b(e).appendTo("#results-list"):b(e).prependTo("#results-list"),c.scrollLog(b(e))},c.addEntry=function(e,f){e=b(e);var g={element:e,extra:f},h=c.evalLogEntry(g);d.logEntries.push(g),this.displayEntry(e),this.trimEntries(),h&&d.config.background&&(d.unseen++,b(document).attr("title","("+d.unseen+" new) "+d.config.pageTitle))},c.clearEntries=function(){d.logEntries=[],b("#results-list").empty()},c.writeMsg=function(c){var e={id:"msg"+d.entryCounter++,data:c,rawdata:c};this.addEntry(b(CWinberry.Templating.render("tpl_msgEntry",e)),e)},c.parseLogEntry=function(c,e){try{var f={ns:c,rawdata:e,id:"log"+d.entryCounter++},g=d.regex.devtest.test(e),h=e.match(d.regex.meta),i=e.match(d.regex.error),j=e.match(d.regex.warning),k=e.match(d.regex.token),l=e.match(d.regex.severity),m=l?l[1]:"info";if(c==="web"){var n=e.match(c==="gearman"?g?d.regex.gearman_dev:d.regex.gearman:g?d.regex.web_dev:d.regex.web);if(n&&n.length===9){e=e.replace(n[0],""),f.server=n[1]||"localhost",f.timestamp=new Date(n[2]),isNaN(f.timestamp.getTime())&&(f.timestamp=new Date(0)),f.client=n[3]||"127.0.0.1",f.hash=n[4],f.severity=n[5],f.namespace=n[6];var o=n[7]?n[7].split(":"):["",0];f.path={url:n[7].replace(d.regex.codepath,d.paths.githubWebPrefix+"$3#L$4$5$6"),file:o[0].replace(d.regex.stackFile,""),line:o[1]},f.userid=n[8]&&n[8]!=="0"?n[8]:"";var p=e.match(d.regex.fullStacktrace);if(p){f.stacktrace=[];var q=p[1].split(d.regex.stackSplitter);q.shift();for(var r=0,s=q.length;r$1')}}else e=e+(name?'['+name+"] ":"")+e;return e=e.replace(d.regex.url,"$1"),k&&(f.token=k[1]),e=e.replace(d.regex.codepath,'$3:$4$5$6'),f.rawdata.match(d.regex.phpFatal)&&(f.isFatal=!0),f.data=b.trim(e),f}catch(B){console.log(B)}},c.writeLogEntry=function(c,e){var f=hex_sha256(e);if(d.hashes[f])return;var g=this.parseLogEntry(c,e);d.hashes[f]=1,g.hash=f,this.addEntry(b(CWinberry.Templating.render("tpl_logEntry",g)),g)},c.writeLogEntries=function(d){b.each(d.lines,function(a,b){c.writeLogEntry(d.name,b)})}})(window,jQuery); -------------------------------------------------------------------------------- /static/js/templating.js: -------------------------------------------------------------------------------- 1 | (function (window, $) { 2 | window.console = window.console || { log: function () {} }; 3 | 4 | window.CWinberry = window.CWinberry || {}; 5 | window.CWinberry.Templating = window.CWinberry.Templating || {}; 6 | 7 | ct = window.CWinberry.Templating; 8 | 9 | var templateCache = {}; 10 | 11 | ct.render = function CWinberry$Templating$render (templateId, data) { 12 | return this.loadTemplate(templateId)(data); 13 | }; 14 | 15 | ct.loadTemplate = function CWinberry$Templating$loadTemplate (templateId) { 16 | if (templateCache[templateId]) { 17 | return templateCache[templateId]; 18 | } 19 | var templateSrc = $('#' + templateId); 20 | if (templateSrc.length === 0) { 21 | throw "Unrecognized template ID: " + templateId; 22 | } 23 | templateCache[templateId] = this.compile(templateSrc.text()); 24 | return templateCache[templateId]; 25 | }; 26 | 27 | ct.compile = function CWinberry$Templating$compile (template) { 28 | function addText (buffer, text, unescaped) { 29 | unescaped = !!unescaped; 30 | buffer.push("\tprint("); 31 | buffer.push(unescaped ? text : "\"" + (text 32 | .split("\r").join("\\r") 33 | .split("\n").join("\\n") 34 | .split("\t").join("\\t") 35 | .split("\"").join("\\\"") 36 | ) + "\"" 37 | ); 38 | buffer.push(");\n"); 39 | } 40 | 41 | function addCode (buffer, code) { 42 | if (code.indexOf("=") === 0) { 43 | addText(buffer, code.substring(1, code.length), true); 44 | } else { 45 | buffer.push("\t" + code); 46 | } 47 | } 48 | 49 | function renderTemplate (templateId, data) { 50 | addText(ct.render(templateId, data)); 51 | } 52 | 53 | var buffer = []; 54 | var re = /(<%|%>)/g; 55 | var prvPos = 0; 56 | var prvSep = ""; 57 | while (re.test(template)) { 58 | var curPos = re.lastIndex; 59 | var curSep = template.substring(curPos - 2, curPos); 60 | if (curSep == "<%") { 61 | addText(buffer, template.substring(prvPos, curPos - 2)); 62 | } else { //curSep == "%>" 63 | addCode(buffer, template.substring(prvPos, curPos - 2)); 64 | } 65 | prvPos = curPos; 66 | prvSep = curSep; 67 | } 68 | if (prvPos < template.length) { 69 | if (prvSep === "%>" || prvSep === "") { 70 | addText(buffer, template.substring(prvPos, template.length)); 71 | } else { //prvSep == "%>" 72 | addCode(buffer, template.substring(prvPos, template.length)); 73 | } 74 | } 75 | 76 | var src = 77 | " var __output = [];\n" + 78 | " var print = function () {\n" + 79 | " __output.push.apply(__output, arguments);\n"+ 80 | " };\n" + 81 | " var renderTemplate = function (templateId, data) {\n" + 82 | " print(CWinberry.Templating.render(templateId, data));\n"+ 83 | " };\n" + 84 | buffer.join('') + 85 | " return(__output.join(''));" 86 | ; 87 | 88 | var templateFunc = null; 89 | try { 90 | templateFunc = new Function("data", src); 91 | } catch (ex) { 92 | throw "Failed to compile template: " + ex; 93 | } 94 | return templateFunc; 95 | }; 96 | 97 | })(window, jQuery); 98 | -------------------------------------------------------------------------------- /static/js/templating.min.js: -------------------------------------------------------------------------------- 1 | (function(c,h){c.console=c.console||{log:function(){}};c.CWinberry=c.CWinberry||{};c.CWinberry.Templating=c.CWinberry.Templating||{};ct=c.CWinberry.Templating;var f={};ct.render=function(a,d){return this.loadTemplate(a)(d)};ct.loadTemplate=function(a){if(f[a])return f[a];var d=h("#"+a);if(0===d.length)throw"Unrecognized template ID: "+a;f[a]=this.compile(d.text());return f[a]};ct.compile=function(a){function d(a,c,b){b=!!b;a.push("\tprint(");a.push(b?c:'"'+c.split("\r").join("\\r").split("\n").join("\\n").split("\t").join("\\t").split('"').join('\\"')+ '"');a.push(");\n")}function c(a,b){0===b.indexOf("=")?d(a,b.substring(1,b.length),!0):a.push("\t"+b)}for(var e=[],f=/(<%|%>)/g,g=0,b="";f.test(a);){var b=f.lastIndex,i=a.substring(b-2,b);"<%"==i?d(e,a.substring(g,b-2)):c(e,a.substring(g,b-2));g=b;b=i}g"===b||""===b?d(e,a.substring(g,a.length)):c(e,a.substring(g,a.length)));a=" var __output = [];\n var print = function () {\n __output.push.apply(__output, arguments);\n };\n var renderTemplate = function (templateId, data) {\n print(CWinberry.Templating.render(templateId, data));\n };\n"+ e.join("")+" return(__output.join(''));";e=null;try{e=new Function("data",a)}catch(h){throw"Failed to compile template: "+h;}return e}})(window,jQuery); -------------------------------------------------------------------------------- /static/js/underscore.min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.2.2 2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);if(b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return String(a)==String(c);case "[object Number]":return a=+a,c=+c,a!=a?c!=c:a==0?1/a==1/c:a==c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,H=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd? 11 | define("underscore",function(){return b}):s._=b;b.VERSION="1.2.2";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,c){var b=e(a,c);(d[b]||(d[b]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e< 17 | f;){var g=e+f>>1;d(a[g])=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=H||function(a){if(a!== 24 | Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)? 25 | a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=l.call(arguments)=="[object Arguments]"?function(a){return l.call(a)=="[object Arguments]"}: 26 | function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null}; 27 | b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b=J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g, 28 | interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g, 29 | "\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e(a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},I=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return u(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped, 30 | arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this); 31 | -------------------------------------------------------------------------------- /stream.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var qs = require('querystring'); 3 | var net = require('net'); 4 | var path = require('path'); 5 | var exec = require("child_process").exec; 6 | var express = require('express'); 7 | var socketio = require('socket.io'); 8 | var less = require('less'); 9 | var uglify = require('uglify-js'); 10 | var LogReader = require('./lib/logreader'); 11 | 12 | var STATIC_PATH = '/static'; 13 | 14 | var errorLines = []; 15 | var logReaders = {}; 16 | var cache = { js: {}, jsc: {}, less: {} }; 17 | 18 | /* Config stuff */ 19 | //Load config specified on command line 20 | var config = require(__dirname + "/" + process.argv[2]).config; 21 | if (!config.defaultMaxLines) { 22 | config.defaultMaxLines = 50; 23 | } 24 | //Set NODE_ENV based on config 'dev' flag; used by Express 25 | process.env.NODE_ENV = config.dev ? 'development' : 'production'; 26 | 27 | //Create a log reader for each log defined in config 28 | config.files.forEach(function (file) { 29 | logReaders[file.name] = new LogReader(file, config); 30 | }); 31 | 32 | // trap TERM signals and close all readers 33 | process.on('SIGTERM', function() { 34 | closeReaders(); 35 | process.exit(0); 36 | }); 37 | 38 | /* Misc helper funcs */ 39 | function closeReaders() { 40 | for (var name in logReaders) { 41 | logReaders[name].log.kill(); 42 | } 43 | } 44 | 45 | function fileExists(path, cb) { 46 | fs.stat(path, function (err, stat) { 47 | cb(!err && stat.isFile()); 48 | }); 49 | } 50 | 51 | function dirExistsSync (path) { 52 | try { 53 | return fs.statSync(path).isDirectory(); 54 | } catch (ex) { 55 | return false; 56 | } 57 | } 58 | 59 | var app = express.createServer(); 60 | //Allow JSONP support 61 | app.enable("jsonp callback"); 62 | app.use(express.logger()); 63 | app.use(express.bodyParser()); 64 | app.use(express.query()); 65 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 66 | 67 | //IRCCat proxy 68 | app.post('/irccat', function (req, res, next) { 69 | var postData = ''; 70 | req.on('data', function (chunk) { 71 | postData += chunk; 72 | }); 73 | req.on('end', function () { 74 | var params = qs.parse(postData); 75 | res.end('OK'); 76 | if (params.target && params.data) { 77 | //TODO: fixed the double prefix 78 | params.data = config.irccat.prefix + params.data 79 | .replace(/(^[\s\r\n]|[\s\r\n]$)/g) 80 | .match(new RegExp('.{1,' + (config.irccat.maxchars - config.irccat.prefix.length) + '}', 'g')) 81 | .join("\n") 82 | .replace(/[\r\n]+/g, "\n" + config.irccat.prefix) 83 | ; 84 | var telnet = new net.Socket(); 85 | telnet.on('connect', function () { 86 | telnet.end(params.target + ' ' + params.data + "\n"); 87 | }) 88 | .connect(config.irccat.port, config.irccat.host); 89 | } 90 | }); 91 | }); 92 | //Compiles LESS files to CSS 93 | app.get('/*.css', function (req, res, next) { 94 | var cssFilename = __dirname + STATIC_PATH + req.url; 95 | fileExists(cssFilename, function (exists) { 96 | if (exists) { 97 | return next(); 98 | } 99 | var lessFilename = cssFilename.replace(/\.css$/, '.less'); 100 | fileExists(lessFilename, function (exists) { 101 | if (!exists) { 102 | return next(); 103 | } 104 | if (cache.less[lessFilename]) { 105 | res.contentType('foo.css'); 106 | return res.end(cache.less[lessFilename]); 107 | } 108 | fs.readFile(lessFilename, function (err, lessData) { 109 | if (err) { 110 | return next(); 111 | } 112 | lessData = lessData.toString(); 113 | var pathParts = lessFilename.split('/'); 114 | var filename = pathParts.pop(); 115 | var lessPath = pathParts.join('/'); 116 | var parser = new less.Parser ({ 117 | paths: [lessPath] 118 | , filename: filename 119 | }); 120 | parser.parse(lessData, function (err, tree) { 121 | if (err) { 122 | return next(); 123 | } 124 | var cssData = tree.toCSS({ compress: !config.dev }); 125 | if (!config.dev) { 126 | cache.less[lessFilename] = cssData; 127 | } 128 | res.contentType('foo.css'); 129 | return res.end(cssData); 130 | }); 131 | }); 132 | }); 133 | }); 134 | }); 135 | //JS compiler/compressor 136 | app.get('/*.jsc', function (req, res, next) { 137 | var jscFilename = __dirname + STATIC_PATH + req.url; 138 | fileExists(jscFilename, function (exists) { 139 | if (!exists) { 140 | return next(); 141 | } 142 | if (cache.jsc[jscFilename]) { 143 | res.contentType('foo.js'); 144 | return res.end(cache.jsc[jscFilename]); 145 | } 146 | fs.readFile(jscFilename, function (err, jscData) { 147 | if (err) { 148 | return next(); 149 | } 150 | jscData = jscData.toString(); 151 | var pathParts = jscFilename.split('/'); 152 | pathParts.pop(); 153 | var jsIncludePath = pathParts.join('/') + '/'; 154 | var files = jscData.replace(/(^\s+|\s+$)/g, '').split(/\n+/g); 155 | 156 | var output = []; 157 | var jsReader = function () { 158 | if (files.length) { 159 | var includeFile = files.shift().replace(/(^\s+|\s+$)/g, ''); 160 | if (includeFile === '') { 161 | return jsReader(); 162 | } 163 | var filePath = jsIncludePath + includeFile; 164 | fs.readFile(filePath, function (err, jsData) { 165 | if (err) { 166 | console.log("Error loading JS file", filePath, err); 167 | } else { 168 | jsData = jsData.toString(); 169 | if (!config.dev) { 170 | var tree = uglify.parser.parse(jsData); 171 | tree = uglify.uglify.ast_squeeze(tree); 172 | jsData = uglify.uglify.gen_code(tree); 173 | } 174 | output.push("\n/* JS include: " + includeFile + " */\n"); 175 | output.push(jsData); 176 | } 177 | jsReader(); 178 | }); 179 | } else { 180 | var compiledOutput = output.join("\n;\n"); 181 | if (!config.dev) { 182 | cache.jsc[jscFilename] = compiledOutput; 183 | } 184 | res.contentType('foo.js'); 185 | return res.end(compiledOutput); 186 | } 187 | }; 188 | jsReader(); 189 | }); 190 | }); 191 | }); 192 | //Blamebot functionality 193 | app.get(config.blamebot.uri_match_regexp, function (req, res, next) { 194 | console.log('DEB', req.params); 195 | var reponame = req.params[0].toLowerCase(); 196 | var filename = req.params[1]; 197 | var startline = req.params[2]; 198 | var endline = req.params[3] || startline; 199 | 200 | var cmd = config.blamebot.git_command + ' ' + 201 | config.blamebot.git_blame_options.join(' ') + 202 | " -L " + startline + "," + endline + " " + 203 | " -- " + filename; 204 | 205 | console.log('REQUEST', reponame, config.blamebot.git_checkout_dirs[reponame].path, filename, startline, endline); 206 | 207 | if (!config.blamebot.git_checkout_dirs[reponame]) { 208 | res.json({ error: { 209 | msg: 'Requested repository not found' 210 | }}); 211 | return; 212 | } 213 | 214 | var child = exec(cmd, {"cwd" : config.blamebot.git_checkout_dirs[reponame].path}, 215 | function (error, stdout, stderr) { 216 | if (error) { 217 | res.json({ error: { 218 | msg: error 219 | , details: stderr 220 | }}); 221 | return; 222 | } 223 | 224 | var lines = stdout.split('\n'); 225 | var details = {}; 226 | for (var i = 0, len = lines.length; i < len; i++) { 227 | var line = lines[i].replace(/(^\s+|\s+^)/g).split(/\s/); 228 | if (line.length < 2) { 229 | continue; 230 | } 231 | var key = line.shift(); 232 | details[key] = line.join(' '); 233 | } 234 | 235 | res.json(details); 236 | }); 237 | }); 238 | //Redirect legacy /v2/* paths to / 239 | app.all('/v2/:respath?', function (req, res, next) { 240 | var qs = req.url.split('?'); 241 | qs.shift(); 242 | res.redirect( 243 | '/' + 244 | (req.params.respath ? req.params.respath : '') + 245 | (qs.length ? '?' + qs.join('?') : '') 246 | ); 247 | }); 248 | app.use(express.staticCache()); 249 | app.use(express.static(__dirname + STATIC_PATH)); 250 | server = app.listen(config.port); 251 | 252 | var websocket = socketio.listen(server); 253 | 254 | websocket.sockets.on('connection', function (client) { 255 | var self = client; 256 | 257 | client.listeners = {}; 258 | 259 | client.on('subscriptions', function(data) { 260 | if (data.subscribeTo) { 261 | data.subscribeTo.forEach(function (feed) { 262 | if (logReaders[feed] && !self.listeners[feed]) { 263 | //console.log("gonna send" + util.inspect(logReaders[feed].lines)); 264 | self.emit('lines', {name: feed, lines: logReaders[feed].lines}); 265 | 266 | var listener = function (line) { 267 | self.emit('lines', {name: feed, lines: [line]}); 268 | }; 269 | 270 | self.listeners[feed] = listener; 271 | 272 | logReaders[feed].on("line", listener); 273 | } 274 | }); 275 | } 276 | 277 | if (data.unsubscribeFrom) { 278 | data.unsubscribeFrom.forEach(function (feed) { 279 | if (self.listeners[feed]) { 280 | logReaders[feed].removeListener("line", self.listeners[feed]); 281 | delete self.listeners[feed]; 282 | } 283 | }); 284 | } 285 | }); 286 | 287 | // remove the listeners so they don't just take up memory 288 | client.on('disconnect', function () { 289 | for (var feed in self.listeners) { 290 | logReaders[feed].removeListener("line", self.listeners[feed]); 291 | delete self.listeners[feed]; 292 | } 293 | }); 294 | 295 | }); 296 | 297 | //Bootstrap for create/updating repos for blamebot 298 | (function () { 299 | var key; 300 | var dir; 301 | 302 | function genUpdateRepoCallback (name) { 303 | return function (error, stdout, stderr) { 304 | setTimeout(function () { updateRepo(name); }, config.blamebot.refresh_time); 305 | console.log('UPDATE (' + name + ')', [error, stdout, stderr]); 306 | }; 307 | } 308 | 309 | function updateRepo (name) { 310 | exec(config.blamebot.git_command + ' ' + config.blamebot.git_pull_options.join(' ') 311 | , { 'cwd' : config.blamebot.git_checkout_dirs[name].path } 312 | , genUpdateRepoCallback(name) 313 | ); 314 | } 315 | 316 | var entry; 317 | var repoPath; 318 | for (key in config.blamebot.git_checkout_dirs) { 319 | entry = config.blamebot.git_checkout_dirs[key]; 320 | repoPath = path.normalize(entry.path); 321 | if (dirExistsSync(repoPath)) { 322 | updateRepo(key); 323 | } else { 324 | if (config.git_clone_enabled) { 325 | exec(config.blamebot.git_command + ' ' + config.blamebot.git_clone_options.join(' ') + ' ' + entry.git + ' ' + repoPath 326 | , genUpdateRepoCallback(key) 327 | ); 328 | } 329 | } 330 | } 331 | })(); 332 | -------------------------------------------------------------------------------- /supergrep.init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # supergrep initd script 3 | # 4 | # chkconfig: - 50 50 5 | # description: Startup script for supergrep 6 | # 7 | # processname: /usr/bin/node 8 | # pidfile: /var/run/stream.pid 9 | 10 | # source function library 11 | . /etc/init.d/functions 12 | 13 | OPTIONS="/path/to/supergrep/stream.js prodConfig.js" 14 | RETVAL=0 15 | prog="node stream.js prodConfig.js" 16 | pidfile="/var/run/stream.pid" 17 | 18 | start() { 19 | echo -n $"Starting $prog: " 20 | if [ $UID -ne 0 ]; then 21 | RETVAL=1 22 | failure 23 | else 24 | daemon /usr/bin/node $OPTIONS &>/dev/null & 25 | RETVAL=$? 26 | [ $RETVAL -eq 0 ] && touch /var/lock/subsys/stream 27 | fi; 28 | echo 29 | return $RETVAL 30 | } 31 | 32 | stop() { 33 | echo -n $"Stopping $prog: " 34 | if [ $UID -ne 0 ]; then 35 | RETVAL=1 36 | failure 37 | else 38 | pkill -f "$OPTIONS" 39 | RETVAL=$? 40 | [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/stream 41 | fi; 42 | echo 43 | return $RETVAL 44 | } 45 | 46 | restart(){ 47 | stop 48 | start 49 | } 50 | 51 | case "$1" in 52 | start) 53 | start 54 | ;; 55 | stop) 56 | stop 57 | ;; 58 | restart) 59 | restart 60 | ;; 61 | status) 62 | status -p $pidfile /usr/bin/node 63 | RETVAL=$? 64 | ;; 65 | *) 66 | echo $"Usage: $0 {start|stop|status|restart}" 67 | RETVAL=1 68 | esac 69 | 70 | exit $RETVAL 71 | --------------------------------------------------------------------------------