diff --git a/couchpotato/core/plugins/log/static/log.css b/couchpotato/core/plugins/log/static/log.css index e8797c69..63165128 100644 --- a/couchpotato/core/plugins/log/static/log.css +++ b/couchpotato/core/plugins/log/static/log.css @@ -43,6 +43,19 @@ } } + .page.log .nav li.hint { + text-align: center; + width: 400px; + left: 50%; + margin-left: -200px; + font-style: italic; + font-size: 11px; + position: absolute; + right: 20px; + opacity: .5; + bottom: 5px; + } + .page.log .loading { text-align: center; font-size: 20px; @@ -65,11 +78,18 @@ .page.log .container .time { clear: both; color: lightgrey; - padding: 3px 0; font-size: 10px; - border-top: 1px solid rgba(255, 255, 255, 0.2); + border-top: 1px solid rgba(255, 255, 255, 0.1); position: relative; } + .page.log .container .time.highlight { + background: rgba(255, 255, 255, 0.1); + } + .page.log .container .time span { + padding: 5px 0 3px; + display: inline-block; + vertical-align: middle; + } .page.log [data-filter=INFO] .error, .page.log [data-filter=INFO] .debug, @@ -91,5 +111,69 @@ } .page.log .container .error { color: #FFA4A4; } - .page.log .container .debug { opacity: .4; } + .page.log .container .debug span { opacity: .4; } +.do_report { + position: absolute; + padding: 10px; +} + +.page.log .report { + position: fixed; + width: 100%; + height: 100%; + background: rgba(0,0,0,.7); + left: 0; + top: 0; + z-index: 99999; + font-size: 14px; + font-family: OpenSans,"Helvetica Neue",Helvetica,Arial,Geneva,sans-serif; +} + + .page.log .report .button { + display: inline-block; + margin: 10px 0; + padding: 10px; + } + + .page.log .report .bug { + width: 800px; + height: 80%; + position: absolute; + left: 50%; + top: 50%; + margin: 0 0 0 -400px; + transform: translate(0, -50%); + } + + .page.log .report .bug textarea { + display: block; + width: 100%; + background: #FFF; + padding: 20px; + overflow: auto; + color: #666; + height: 70%; + font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif; + font-size: 12px; + } + +.page.log .container ::-webkit-selection { + background-color: #000; + color: #FFF; +} + +.page.log .container ::-moz-selection { + background-color: #000; + color: #FFF; +} + +.page.log .container ::-ms-selection { + background-color: #000; + color: #FFF; +} + +.page.log .container ::selection { + background-color: #000; + color: #FFF; +} diff --git a/couchpotato/core/plugins/log/static/log.js b/couchpotato/core/plugins/log/static/log.js index 670f5b75..1e18c5b1 100644 --- a/couchpotato/core/plugins/log/static/log.js +++ b/couchpotato/core/plugins/log/static/log.js @@ -7,34 +7,54 @@ Page.Log = new Class({ title: 'Show recent logs.', has_tab: false, - indexAction: function(){ + report_text: '\ +### Steps to reproduce:\n\ +1. ..\n\ +2. ..\n\ +\n\ +### Information:\n\ +Movie(s) I have this with: ...\n\ +Quality of the movie being searched: ...\n\ +Providers I use: ...\n\ +Version of CouchPotato: ...\n\ +Running on: ...\n\ +\n\ +### Logs:\n\ +```\n{issue}```', + + indexAction: function () { var self = this; self.getLogs(0); }, - getLogs: function(nr){ + getLogs: function (nr) { var self = this; - if(self.log) self.log.destroy(); + if (self.log) self.log.destroy(); self.log = new Element('div.container.loading', { - 'text': 'loading...' + 'text': 'loading...', + 'events': { + 'mouseup:relay(.time)': function(e){ + self.showSelectionButton.delay(100, self, e); + } + } }).inject(self.el); Api.request('logging.get', { 'data': { 'nr': nr }, - 'onComplete': function(json){ + 'onComplete': function (json) { self.log.set('text', ''); self.log.adopt(self.createLogElements(json.log)); self.log.removeClass('loading'); var nav = new Element('ul.nav', { 'events': { - 'click:relay(li.select)': function(e, el){ - self.getLogs(parseInt(el.get('text'))-1); + 'click:relay(li.select)': function (e, el) { + self.getLogs(parseInt(el.get('text')) - 1); } } }); @@ -43,7 +63,7 @@ Page.Log = new Class({ new Element('li.filter').grab( new Element('select', { 'events': { - 'change': function(){ + 'change': function () { var type_filter = this.getSelected()[0].get('value'); self.log.set('data-filter', type_filter); self.scrollToBottom(); @@ -60,8 +80,8 @@ Page.Log = new Class({ // Selections for (var i = 0; i <= json.total; i++) { new Element('li', { - 'text': i+1, - 'class': 'select ' + (nr == i ? 'active': '') + 'text': i + 1, + 'class': 'select ' + (nr == i ? 'active' : '') }).inject(nav); } @@ -69,9 +89,9 @@ Page.Log = new Class({ new Element('li.clear', { 'text': 'clear', 'events': { - 'click': function(){ + 'click': function () { Api.request('logging.clear', { - 'onComplete': function(){ + 'onComplete': function () { self.getLogs(0); } }); @@ -80,6 +100,11 @@ Page.Log = new Class({ } }).inject(nav); + // Hint + new Element('li.hint', { + 'text': 'Select multiple lines & report an issue' + }).inject(nav); + // Add to page nav.inject(self.log, 'top'); @@ -89,29 +114,159 @@ Page.Log = new Class({ }, - createLogElements: function(logs){ + createLogElements: function (logs) { - var elements = []; + var elements = []; - logs.each(function(log){ - elements.include(new Element('div', { - 'class': 'time ' + log.type.toLowerCase(), - 'text': log.time - }).adopt( - new Element('span.type', { - 'text': log.type - }), - new Element('span.message', { - 'text': log.message - }) - )) - }); + logs.each(function (log) { + elements.include(new Element('div', { + 'class': 'time ' + log.type.toLowerCase() + }).adopt( + new Element('span', { + 'text': log.time + }), + new Element('span.type', { + 'text': log.type + }), + new Element('span.message', { + 'text': log.message + }) + )) + }); return elements; }, - scrollToBottom: function(){ + scrollToBottom: function () { new Fx.Scroll(window, {'duration': 0}).toBottom(); + }, + + showSelectionButton: function(e){ + var self = this, + selection = self.getSelected(), + start_node = selection.anchorNode, + parent_start = start_node.parentNode.getParent('.time'), + end_node = selection.focusNode.parentNode.getParent('.time'), + text = ''; + + var remove_button = function(){ + self.log.getElements('.highlight').removeClass('highlight'); + if(self.do_report) + self.do_report.destroy(); + document.body.removeEvent('click', remove_button); + }; + remove_button(); + + if(parent_start) + start_node = parent_start; + + var nodes = [start_node], + current_node = start_node; + + while(current_node != end_node){ + current_node = current_node.getNext('.time'); + nodes.include(current_node); + } + nodes.each(function(node, nr){ + node.addClass('highlight'); + node.getElements('span').each(function(span){ + text += self.spaceFill(span.get('text') + ' ', 6); + }); + text += '\n'; + }); + + self.do_report = new Element('a.do_report.button', { + 'text': 'Report issue', + 'styles': { + 'top': e.page.y, + 'left': e.page.x + }, + 'events': { + 'click': function(e){ + (e).stop(); + + self.showReport(text); + } + } + }).inject(document.body); + + setTimeout(function(){ + document.body.addEvent('click', remove_button); + }, 0); + + }, + + showReport: function(text){ + var self = this; + + var overlay = new Element('div.report', { + 'method': 'post', + 'events': { + 'click': function(e){ + overlay.destroy(); + } + } + }).grab( + new Element('div.bug', { + 'events': { + 'click': function(e){ + (e).stopPropagation(); + } + } + }).adopt( + new Element('h1', { + 'text': 'Report a bug' + }), + new Element('span').adopt( + new Element('span', { + 'text': 'Read ' + }), + new Element('a.button', { + 'target': '_blank', + 'text': 'the contributing guide', + 'href': 'https://github.com/RuudBurger/CouchPotatoServer/blob/develop/contributing.md' + }), + new Element('span', { + 'text': ' before posting, then copy the text below' + }) + ), + new Element('textarea', { + 'text': self.report_text.replace('{issue}', text), + 'events': { + 'click': function(){ + this.select(); + } + } + }), + new Element('a.button', { + 'target': '_blank', + 'text': 'Create a new issue on GitHub with the text above', + 'href': 'https://github.com/RuudBurger/CouchPotatoServer/issues/new?body=Paste the text here' + }) + ) + ); + + overlay.inject(self.log); + }, + + getSelected: function(){ + if (window.getSelection) + return window.getSelection(); + else if (document.getSelection) + return document.getSelection(); + else { + var selection = document.selection && document.selection.createRange(); + if (selection.text) + return selection.text; + } + return false; + + }, + + spaceFill: function( number, width ){ + if ( number.toString().length >= width ) + return number; + return ( new Array( width ).join( ' ' ) + number.toString() ).substr( -width ); } });