diff --git a/app/commands/MigrateOldData.php b/app/commands/MigrateOldData.php index 278b20d6..ed043911 100644 --- a/app/commands/MigrateOldData.php +++ b/app/commands/MigrateOldData.php @@ -14,6 +14,8 @@ } public function fire() { + DB::connection()->disableQueryLog(); + $oldDb = DB::connection('old'); $this->call('migrate:refresh'); diff --git a/app/commands/RefreshCache.php b/app/commands/RefreshCache.php index ca36b05e..b2747ca8 100644 --- a/app/commands/RefreshCache.php +++ b/app/commands/RefreshCache.php @@ -13,6 +13,8 @@ class RefreshCache extends Command { } public function fire() { + DB::connection()->disableQueryLog(); + DB::table('tracks')->update(['comment_count' => DB::raw('(SELECT COUNT(id) FROM comments WHERE comments.track_id = tracks.id AND deleted_at IS NULL)')]); DB::table('albums')->update(['comment_count' => DB::raw('(SELECT COUNT(id) FROM comments WHERE comments.album_id = albums.id AND deleted_at IS NULL)')]); DB::table('playlists')->update(['comment_count' => DB::raw('(SELECT COUNT(id) FROM comments WHERE comments.playlist_id = playlists.id AND deleted_at IS NULL)')]); diff --git a/app/controllers/Api/Web/ArtistsController.php b/app/controllers/Api/Web/ArtistsController.php index 82b3ec78..06a33e96 100644 --- a/app/controllers/Api/Web/ArtistsController.php +++ b/app/controllers/Api/Web/ArtistsController.php @@ -25,6 +25,11 @@ App::abort(404); $favs = Favourite::whereUserId($user->id)->with([ + 'track.genre', + 'track.cover', + 'track.user', + 'album.cover', + 'album.user', 'track' => function($query) { $query->details(); }, 'album' => function($query) { $query->details(); }])->get(); @@ -51,7 +56,7 @@ if (!$user) App::abort(404); - $query = Track::summary()->whereUserId($user->id)->whereNotNull('published_at'); + $query = Track::summary()->with('genre', 'cover', 'user')->details()->whereUserId($user->id)->whereNotNull('published_at'); $tracks = []; $singles = []; @@ -82,7 +87,7 @@ if (!$user) App::abort(404); - $trackQuery = Track::summary()->whereUserId($user->id)->whereNotNull('published_at')->orderBy('created_at', 'desc')->take(20); + $trackQuery = Track::summary()->with('genre', 'cover', 'user')->details()->whereUserId($user->id)->whereNotNull('published_at')->orderBy('created_at', 'desc')->take(20); $latestTracks = []; foreach ($trackQuery->get() as $track) { diff --git a/app/controllers/Api/Web/PlaylistsController.php b/app/controllers/Api/Web/PlaylistsController.php index e1e44398..658ba147 100644 --- a/app/controllers/Api/Web/PlaylistsController.php +++ b/app/controllers/Api/Web/PlaylistsController.php @@ -34,7 +34,7 @@ } public function getShow($id) { - $playlist = Playlist::with(['tracks.user', 'tracks' => function($query) { $query->details(); }, 'comments', 'comments.user'])->details()->find($id); + $playlist = Playlist::with(['tracks.user', 'tracks.genre', 'tracks.cover', 'tracks.album', 'tracks' => function($query) { $query->details(); }, 'comments', 'comments.user'])->details()->find($id); if (!$playlist || !$playlist->canView(Auth::user())) App::abort('404'); diff --git a/app/controllers/Api/Web/ProfilerController.php b/app/controllers/Api/Web/ProfilerController.php new file mode 100644 index 00000000..f1443252 --- /dev/null +++ b/app/controllers/Api/Web/ProfilerController.php @@ -0,0 +1,24 @@ + ProfileRequest::load($request)->toArray()], 200); + } + } \ No newline at end of file diff --git a/app/controllers/Api/Web/TracksController.php b/app/controllers/Api/Web/TracksController.php index 2d9090ef..68e03084 100644 --- a/app/controllers/Api/Web/TracksController.php +++ b/app/controllers/Api/Web/TracksController.php @@ -13,6 +13,7 @@ use Entities\Track; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Input; + use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Response; class TracksController extends \ApiControllerBase { @@ -66,7 +67,7 @@ $query = Track::summary() ->details() ->whereNotNull('published_at') - ->with('user', 'genre'); + ->with('user', 'genre', 'cover', 'album', 'album.user'); $this->applyFilters($query); diff --git a/app/filters.php b/app/filters.php index 9cd091cc..622380bb 100644 --- a/app/filters.php +++ b/app/filters.php @@ -1,79 +1,97 @@ after($request, $response); -/* -|-------------------------------------------------------------------------- -| Authentication Filters -|-------------------------------------------------------------------------- -| -| The following filters are used to verify that the user of the current -| session is logged into this application. The "basic" filter easily -| integrates HTTP Basic authentication for quick, simple checking. -| -*/ + Cache::put('profiler-request-' . $profiler->getId(), $profiler->toString(), 2); + header('X-Request-Id: ' . $profiler->getId()); + } -Route::filter('auth', function() -{ - if (Auth::guest()) return Redirect::guest('login'); -}); + App::error(function($exception) use ($profiler) { + $profiler->log('error', $exception->__toString(), []); + processResponse($profiler, null, null); + }); + App::after(function($request, $response) use ($profiler) { + if ($response->headers->get('content-type') != 'application/json') + return; -Route::filter('auth.basic', function() -{ - return Auth::basic(); -}); + processResponse($profiler, $request, $response); + }); -/* -|-------------------------------------------------------------------------- -| Guest Filter -|-------------------------------------------------------------------------- -| -| The "guest" filter is the counterpart of the authentication filters as -| it simply checks that the current user is not logged in. A redirect -| response will be issued if they are, which you may freely change. -| -*/ - -Route::filter('guest', function() -{ - if (Auth::check()) return Redirect::to('/'); -}); - -/* -|-------------------------------------------------------------------------- -| CSRF Protection Filter -|-------------------------------------------------------------------------- -| -| The CSRF filter is responsible for protecting your application against -| cross-site request forgery attacks. If this special token in a user -| session does not match the one given in this request, we'll bail. -| -*/ - -Route::filter('csrf', function() -{ - if (Session::token() != Input::get('_token') && Session::token() != Request::header('X-Token')) { - throw new Illuminate\Session\TokenMismatchException; + Log::listen(function($level, $message, $context) use ($profiler) { + $profiler->log($level, $message, $context); + }); } -}); \ No newline at end of file + + /* + |-------------------------------------------------------------------------- + | Authentication Filters + |-------------------------------------------------------------------------- + | + | The following filters are used to verify that the user of the current + | session is logged into this application. The "basic" filter easily + | integrates HTTP Basic authentication for quick, simple checking. + | + */ + + Route::filter('auth', function() + { + if (Auth::guest()) return Redirect::guest('login'); + }); + + + Route::filter('auth.basic', function() + { + return Auth::basic(); + }); + + /* + |-------------------------------------------------------------------------- + | Guest Filter + |-------------------------------------------------------------------------- + | + | The "guest" filter is the counterpart of the authentication filters as + | it simply checks that the current user is not logged in. A redirect + | response will be issued if they are, which you may freely change. + | + */ + + Route::filter('guest', function() + { + if (Auth::check()) return Redirect::to('/'); + }); + + /* + |-------------------------------------------------------------------------- + | CSRF Protection Filter + |-------------------------------------------------------------------------- + | + | The CSRF filter is responsible for protecting your application against + | cross-site request forgery attacks. If this special token in a user + | session does not match the one given in this request, we'll bail. + | + */ + + Route::filter('csrf', function() + { + if (Session::token() != Input::get('_token') && Session::token() != Request::header('X-Token')) { + throw new Illuminate\Session\TokenMismatchException; + } + }); \ No newline at end of file diff --git a/app/library/Assets.php b/app/library/Assets.php index 618dcc0e..1ed80501 100644 --- a/app/library/Assets.php +++ b/app/library/Assets.php @@ -41,8 +41,8 @@ } public static function scriptAssetCollection($area) { - if ($area == 'app') - return new AssetCollection([ + if ($area == 'app') { + $collection = new AssetCollection([ new FileAsset('scripts/base/jquery-2.0.2.js'), new FileAsset('scripts/base/jquery-ui.js'), new FileAsset('scripts/base/jquery.cookie.js'), @@ -70,6 +70,19 @@ ]) ]); + if (Config::get('app.debug')) { + $collection->add(new GlobAsset('scripts/debug/*.js')); + + $collection->add(new AssetCollection([ + new GlobAsset('scripts/debug/*.coffee'), + ], [ + new CoffeeScriptFilter(Config::get('app.coffee')) + ])); + } + + return $collection; + } + throw new Exception(); } @@ -84,6 +97,11 @@ new CacheBusterAsset($lastModifiedCollection->getLastModified()) ], [new \Assetic\Filter\LessFilter('node')]); + if (Config::get('app.debug')) { + $css->add(new FileAsset('styles/profiler.less')); + $css->add(new FileAsset('styles/prettify.css')); + } + return $css; } diff --git a/app/models/Entities/Album.php b/app/models/Entities/Album.php index 90377d96..79953ab8 100644 --- a/app/models/Entities/Album.php +++ b/app/models/Entities/Album.php @@ -143,7 +143,7 @@ } public function getFilesize($format) { - $tracks = $this->tracks()->get(); + $tracks = $this->tracks; if (!count($tracks)) return 0; diff --git a/app/models/Entities/Playlist.php b/app/models/Entities/Playlist.php index e3ae234e..73472ddf 100644 --- a/app/models/Entities/Playlist.php +++ b/app/models/Entities/Playlist.php @@ -4,6 +4,7 @@ use Helpers; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; + use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\URL; use Traits\SlugTrait; @@ -78,7 +79,7 @@ return [ 'id' => $playlist->id, - 'track_count' => $playlist->tracks->count(), + 'track_count' => $playlist->tracks()->count(), 'title' => $playlist->title, 'slug' => $playlist->slug, 'created_at' => $playlist->created_at, @@ -149,7 +150,7 @@ } public function getFilesize($format) { - $tracks = $this->tracks()->get(); + $tracks = $this->tracks; if (!count($tracks)) return 0; @@ -178,6 +179,6 @@ } private function getCacheKey($key) { - return 'album-' . $this->id . '-' . $key; + return 'playlist-' . $this->id . '-' . $key; } } \ No newline at end of file diff --git a/app/models/Entities/ProfileRequest.php b/app/models/Entities/ProfileRequest.php new file mode 100644 index 00000000..a2c36f15 --- /dev/null +++ b/app/models/Entities/ProfileRequest.php @@ -0,0 +1,53 @@ +_data = json_decode($data); + return $req; + } + + public static function create() { + $req = new ProfileRequest(); + $req->_id = uniqid(); + return $req; + } + + private function __construct() { + $this->_data = ['log' => []]; + } + + public function toArray() { + return $this->_data; + } + + public function toString() { + return json_encode($this->_data); + } + + public function getId() { + return $this->_id; + } + + public function after($request, $response) { + $this->_data['queries'] = DB::getQueryLog(); + } + + public function log($level, $message, $context) { + $this->_data['log'][] = [ + 'level' => $level, + 'message' => $message + ]; + } + } \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 2ff8a27f..688b30ca 100644 --- a/app/routes.php +++ b/app/routes.php @@ -11,6 +11,10 @@ | */ + if (Config::get('app.debug')) { + Route::get('/api/web/profiler/{id}', 'Api\Web\ProfilerController@getRequest'); + } + Route::get('/dashboard', 'TracksController@getIndex'); Route::get('/tracks', 'TracksController@getIndex'); Route::get('/tracks/popular', 'TracksController@getIndex'); diff --git a/public/scripts/app/controllers/artist-content.coffee b/public/scripts/app/controllers/artist-content.coffee index 4583ec3f..3ff4f489 100644 --- a/public/scripts/app/controllers/artist-content.coffee +++ b/public/scripts/app/controllers/artist-content.coffee @@ -1,7 +1,7 @@ window.pfm.preloaders['artist-content'] = [ 'artists', '$state' (artists, $state) -> - $.when.all [artists.fetch($state.params.slug, true), artists.fetchContent($state.params.slug, true)] + $.when.all [artists.fetch($state.params.slug), artists.fetchContent($state.params.slug, true)] ] angular.module('ponyfm').controller "artist-content", [ diff --git a/public/scripts/app/controllers/artist-favourites.coffee b/public/scripts/app/controllers/artist-favourites.coffee index 04a78aa0..9e86b72b 100644 --- a/public/scripts/app/controllers/artist-favourites.coffee +++ b/public/scripts/app/controllers/artist-favourites.coffee @@ -1,7 +1,7 @@ window.pfm.preloaders['artist-favourites'] = [ 'artists', '$state' (artists, $state) -> - artists.fetchFavourites $state.params.slug, true + $.when.all [artists.fetch($state.params.slug), artists.fetchFavourites($state.params.slug, true)] ] angular.module('ponyfm').controller "artist-favourites", [ diff --git a/public/scripts/app/services/tracks.coffee b/public/scripts/app/services/tracks.coffee index 3c94cbd5..a30e212e 100644 --- a/public/scripts/app/services/tracks.coffee +++ b/public/scripts/app/services/tracks.coffee @@ -86,12 +86,13 @@ angular.module('ponyfm').factory('tracks', [ toFilterString: -> parts = [] _.each @availableFilters, (filter, name) => + filterName = filter.name if filter.type == 'single' return if @filters[name].query == '' - parts.push(name + '-' + @filters[name].query) + parts.push(filterName + '-' + @filters[name].query) else return if @filters[name].selectedArray.length == 0 - parts.push(name + '-' + _.map(@filters[name].selectedArray, (f) -> f.id).join '-') + parts.push(filterName + '-' + _.map(@filters[name].selectedArray, (f) -> f.id).join '-') return parts.join '!' @@ -101,17 +102,26 @@ angular.module('ponyfm').factory('tracks', [ @resetFilters() filters = (str || "").split '!' - for filter in filters - parts = filter.split '-' - name = parts[0] - return if !@availableFilters[name] + for queryFilter in filters + parts = queryFilter.split '-' + queryName = parts[0] - if @availableFilters[name].type == 'single' - filterToSet = _.find @availableFilters[name].values, (f) -> f.query == parts[1] - filterToSet = (_.find @availableFilters[name].values, (f) -> f.isDefault) if filterToSet == null - @setFilter name, filterToSet + filterName = null + filter = null + + for name,f of @availableFilters + continue if f.name != queryName + filterName = name + filter = f + + return if !filter + + if filter.type == 'single' + filterToSet = _.find filter.values, (f) -> f.query == parts[1] + filterToSet = (_.find filter.values, (f) -> f.isDefault) if filterToSet == null + @setFilter filterName, filterToSet else - @toggleListFilter name, id for id in _.rest parts, 1 + @toggleListFilter filterName, id for id in _.rest parts, 1 fetch: () -> return @cachedDef if @cachedDef @@ -159,6 +169,7 @@ angular.module('ponyfm').factory('tracks', [ filterDef = new $.Deferred() self.filters.isVocal = type: 'single' + name: 'vocal' values: [ {title: 'Either', query: '', isDefault: true, filter: ''}, {title: 'Yes', query: 'yes', isDefault: false, filter: 'is_vocal=true'}, @@ -167,25 +178,29 @@ angular.module('ponyfm').factory('tracks', [ self.filters.sort = type: 'single' + name: 'sort' values: [ {title: 'Latest', query: '', isDefault: true, filter: 'order=created_at,desc'}, - {title: 'Most Played', query: 'play_count', isDefault: false, filter: 'order=play_count,desc'}, - {title: 'Most Downloaded', query: 'download_count', isDefault: false, filter: 'order=download_count,desc'}, - {title: 'Most Favourited', query: 'favourite_count', isDefault: false, filter: 'order=favourite_count,desc'} + {title: 'Most Played', query: 'plays', isDefault: false, filter: 'order=play_count,desc'}, + {title: 'Most Downloaded', query: 'downloads', isDefault: false, filter: 'order=download_count,desc'}, + {title: 'Most Favourited', query: 'favourites', isDefault: false, filter: 'order=favourite_count,desc'} ] self.filters.genres = type: 'list' + name: 'genres' values: [] filterName: 'genres' self.filters.trackTypes = type: 'list' + name: 'types' values: [] filterName: 'types' self.filters.showSongs = type: 'list' + name: 'songs' values: [] filterName: 'songs' diff --git a/public/scripts/debug/prettify.js b/public/scripts/debug/prettify.js new file mode 100644 index 00000000..eef5ad7e --- /dev/null +++ b/public/scripts/debug/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p").appendTo document.body +$toolbar = $('
').appendTo $profiler + +$('') + .click (e) -> + e.preventDefault() + $(document.body).toggleClass 'profiler-open' + .appendTo $toolbar + +$('Clear') + .click (e) -> + e.preventDefault() + $profiler.find('.requests').empty() + .appendTo $toolbar + +$requestItems = $("
    ").appendTo $profiler + +appendRequest = (method, url, req) -> + $requestItem = $("
  • ") + $requestHeader = ($("

    ")).appendTo $requestItem + ($("").text method).appendTo $requestHeader + ($("").text url).appendTo $requestHeader + + $logItems = $("
      ").appendTo $requestItem + + for logItem in req.request.log + $liItem = $("
    • ") + + $("

      ") + .html(logItem.message) + .click () -> + $(this).toggleClass 'open' + .appendTo($liItem) + + $("
      ").appendTo $liItem + $liItem.appendTo $logItems + + for query in req.request.queries + $liItem = $("
    • ") + ($("").text query.time).appendTo $liItem + + $("

      ") + .html(prettyPrintOne(query.query, 'lang-sql')) + .click () -> + $(this).toggleClass 'open' + .appendTo($liItem) + + $("
      ").appendTo $liItem + $liItem.appendTo $logItems + + $requestItem.appendTo $requestItems + $requestItems.animate {scrollTop: $requestItems[0].scrollHeight}, 300 + +oldOpen = XMLHttpRequest.prototype.open + +XMLHttpRequest.prototype.open = (method, url) -> + intercept = => + return if this.readyState != 4 + return if !this.getResponseHeader('X-Request-Id') + id = this.getResponseHeader('X-Request-Id') + + $.getJSON('/api/web/profiler/' + id).done (res) -> appendRequest method, url, res + + (this.addEventListener "readystatechange", intercept, false) if url.indexOf('/api/web/profiler/') == -1 + oldOpen.apply this, arguments \ No newline at end of file diff --git a/public/styles/Prettify.css b/public/styles/Prettify.css new file mode 100644 index 00000000..972b74d9 --- /dev/null +++ b/public/styles/Prettify.css @@ -0,0 +1,11 @@ +.prettyprint .str { color: #65B042; } /* string - green */ +.prettyprint .kwd { color: #E28964; } /* keyword - dark pink */ +.prettyprint .com { color: #AEAEAE; font-style: italic; } /* comment - gray */ +.prettyprint .typ { color: #89bdff; } /* type - light blue */ +.prettyprint .lit { color: #3387CC; } /* literal - blue */ +.prettyprint .pun { color: #fff; } /* punctuation - white */ +.prettyprint .pln { color: #fff; } /* plaintext - white */ +.prettyprint .tag { color: #89bdff; } /* html/xml tag - light blue */ +.prettyprint .atn { color: #bdb76b; } /* html/xml attribute name - khaki */ +.prettyprint .atv { color: #65B042; } /* html/xml attribute value - green */ +.prettyprint .dec { color: #3387CC; } /* decimal - blue */ diff --git a/public/styles/body.less b/public/styles/body.less index 71d9ae02..5d91d034 100644 --- a/public/styles/body.less +++ b/public/styles/body.less @@ -9,7 +9,7 @@ margin: 0px; margin-bottom: 5px; font-size: 15pt; - color: @pfm-purple; + color: #C2889C; line-height: normal; overflow: hidden; font-weight: normal; diff --git a/public/styles/components.less b/public/styles/components.less index f1378786..5d795d72 100644 --- a/public/styles/components.less +++ b/public/styles/components.less @@ -120,7 +120,7 @@ cursor: default; background: #eee; color: #000; - border-bottom: 4px solid @pfm-purple; + border-bottom: 4px solid #C1889E; margin-bottom: -4px; } } @@ -363,7 +363,7 @@ html { } li.active a { - background: @pfm-purple; + background: #C1889E; color: #fff; } } diff --git a/public/styles/profiler.less b/public/styles/profiler.less new file mode 100644 index 00000000..9de8ba48 --- /dev/null +++ b/public/styles/profiler.less @@ -0,0 +1,158 @@ +html { + .profiler-open { + header, .site-body { + margin-right: 403px; + } + + .profiler { + width: 400px; + height: 100%; + background: rgba(0, 0, 0, .85); + border-left: 3px solid #111; + + .buttons { + background: #222; + + .clear-button { + display: block; + } + + .open-button { + i:before { + content: "\f077"; + } + } + } + + .requests { + display: block; + } + } + } +} + +.profiler { + position: fixed; + top: 0px; + right: 0px; + height: auto; + z-index: 1000; + + font-size: 8pt; + color: #fff; + + .buttons { + padding: 5px; + overflow: hidden; + + > a { + float: right; + padding: 2px 10px; + background: #e67e22; + color: #fff; + + &:hover { + text-decoration: none; + } + } + + .clear-button { + margin-right: 5px; + display: none; + } + } + + ul { + list-style: none; + padding: 0px; + margin: 0px; + + li { + margin: 0px; + padding: 0px; + line-height: normal; + } + } + + .requests { + display: none; + position: absolute; + top: 33px; + bottom: 0px; + left: 0px; + right: 0px; + overflow-y: auto; + + > li { + + h3, h4 { + padding: 5px; + margin: 0px; + line-height: normal; + font-size: 8pt; + font-weight: normal; + } + + h3 { + background: #34495e; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + .method { + display: block; + float: right; + background: #95a5a6; + padding: 5px; + margin: -5px; + min-width: 50px; + } + } + + h4 { + padding: 5px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + cursor: pointer; + + &.open { + white-space: pre-wrap; + text-overflow: clip; + overflow: visible; + background: #111; + } + + &.log-info { + background: #7f8c8d; + } + + &.log-warning { + background: #d35400; + } + + &.log-error { + background: #c0392b; + } + } + + .time { + float: right; + padding: 5px; + background: #2c3e50; + display: block; + margin-left: 5px; + } + + li { + position: relative; + font-family: 'Consolas'; + + .clear { + clear: both; + } + } + } + } +} \ No newline at end of file diff --git a/public/styles/tracks.less b/public/styles/tracks.less index 08e2006e..dbc3fdfe 100644 --- a/public/styles/tracks.less +++ b/public/styles/tracks.less @@ -138,7 +138,7 @@ padding: 3px; strong { - color: darken(@pfm-purple, 25%); + color: #C1889E; } }