Javascript plugin to beautify exceptions on the backend event log page (#2035)
This commit is contained in:
parent
c72670e450
commit
38ec5d109a
|
|
@ -0,0 +1,83 @@
|
|||
[data-plugin="exception-beautifier"] {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.markup_exception_container {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.markup_exception_name {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.markup_exception_message {
|
||||
display: block;
|
||||
padding: 15px 0;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.markup_exception_stacktrace_title {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.markup_exception_stacktrace_line {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.markup_exception_stacktrace_line:nth-child(even) {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.markup_exception_stacktrace_line:nth-child(odd) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.markup_exception_stacktrace_line_number {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
margin-right: 15px;
|
||||
text-align: right;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.markup_exception_stacktrace_line_internal {
|
||||
color: #2C3E50;
|
||||
}
|
||||
|
||||
.markup_exception_stacktrace_line_function {
|
||||
display: block;
|
||||
margin-left: 55px;
|
||||
}
|
||||
|
||||
.markup_exception_file {
|
||||
color: #0c7eff;
|
||||
}
|
||||
|
||||
.markup_exception_line_number {
|
||||
color: #af0016;
|
||||
}
|
||||
|
||||
.markup_exception_class {
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.markup_exception_function {
|
||||
font-weight: bold;
|
||||
color: #4d9a98;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.markup_exception_function.--system {
|
||||
color: #3d7a78;
|
||||
}
|
||||
|
||||
.markup_exception_sign {
|
||||
color: #4d9a98;
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Previews class
|
||||
*/
|
||||
|
||||
+function ($) {
|
||||
"use strict";
|
||||
|
||||
var ExceptionBeautifier = function (el, options) {
|
||||
var self = this
|
||||
|
||||
self.$el = $(el)
|
||||
self.options = options || {}
|
||||
|
||||
// Init
|
||||
self.init()
|
||||
}
|
||||
|
||||
ExceptionBeautifier.DEFAULTS = {
|
||||
editor: 'phpstorm'
|
||||
}
|
||||
|
||||
ExceptionBeautifier.REGEX = {
|
||||
phpLine: /^(#[0-9]+)\s+(.+\.php)(?:\(([0-9]+)\))?\s*:(.*)/,
|
||||
internalLine: /^(#[0-9]+)\s+(\[internal function\]\s*:)(.*)/,
|
||||
defaultLine: /^(#[0-9]+)\s*(.*)/,
|
||||
className: /([a-z0-9]+\\[a-z0-9\\]+)/gi,
|
||||
filePath: /((?:[A-Z]:[/\\]|\/)?[\w/\\]+\.php)(\(([0-9]+)\)|:([0-9]+))?/gi,
|
||||
functionCall: /->([^(]+)\((.*)\)/i
|
||||
}
|
||||
|
||||
ExceptionBeautifier.extensions = []
|
||||
|
||||
ExceptionBeautifier.prototype.init = function () {
|
||||
var self = this
|
||||
|
||||
ExceptionBeautifier.extensions.forEach(function (extension) {
|
||||
extension.onInit(self);
|
||||
})
|
||||
|
||||
self.$el.html(self.parseSource())
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.parseSource = function () {
|
||||
var self = this,
|
||||
source = self.$el.text(),
|
||||
markup = {lines: []},
|
||||
start = 0,
|
||||
end,
|
||||
matches
|
||||
|
||||
// We only parse stack trace message. Just transform newline to <br> and format spaces for standard messages.
|
||||
if (source.indexOf('Stack trace:') < 0) {
|
||||
return '<div class="form-control">' + self.formatMessage(source) + '</div>';
|
||||
}
|
||||
|
||||
end = source.indexOf(':', start) + 1
|
||||
markup.name = source.substring(start, end)
|
||||
|
||||
start = end
|
||||
end = source.indexOf('Stack trace:', start)
|
||||
markup.message = source.substring(start, end)
|
||||
|
||||
start = source.indexOf('#', end)
|
||||
while ((end = source.indexOf('#', start + 1)) > 0) {
|
||||
markup.lines.push(self.parseLine(source.substring(start, end)))
|
||||
start = end
|
||||
}
|
||||
|
||||
markup.lines.push(self.parseLine(source.substring(start)))
|
||||
|
||||
source = '<div class="form-control">' +
|
||||
'<span class="markup_exception_name">' + markup.name + '</span>' +
|
||||
'<span class="markup_exception_message">' + self.formatMessage(markup.message) + '</span>' +
|
||||
'</div>' +
|
||||
'<span class="markup_exception_stacktrace_title">Stack trace:</span>'
|
||||
|
||||
markup.lines.forEach(function (line) {
|
||||
source += '<span class="markup_exception_stacktrace_line">' + self.formatLine(line) + '</span>'
|
||||
})
|
||||
|
||||
ExceptionBeautifier.extensions.forEach(function (extension) {
|
||||
extension.onParse(self);
|
||||
})
|
||||
|
||||
return '<div class="markup_exception_container">' + source + '</div>'
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.parseLine = function (str) {
|
||||
var self = this,
|
||||
line = {},
|
||||
matches
|
||||
|
||||
if (matches = str.match(ExceptionBeautifier.REGEX.phpLine)) {
|
||||
line.type = 'phpLine'
|
||||
line.number = $.trim(matches[1])
|
||||
line.file = $.trim(matches[2])
|
||||
line.lineNumber = $.trim(matches[3])
|
||||
line.function = $.trim(matches[4])
|
||||
}
|
||||
else if (matches = str.match(ExceptionBeautifier.REGEX.internalLine)) {
|
||||
line.type = 'internal'
|
||||
line.number = $.trim(matches[1])
|
||||
line.internal = $.trim(matches[2])
|
||||
line.function = $.trim(matches[3])
|
||||
} else if (matches = str.match(ExceptionBeautifier.REGEX.defaultLine)){
|
||||
line.type = 'default'
|
||||
line.number = $.trim(matches[1])
|
||||
line.function = $.trim(matches[2])
|
||||
}
|
||||
|
||||
return line
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.formatLine = function (line) {
|
||||
var self = this
|
||||
|
||||
if (line.function) {
|
||||
line.function = self.formatClasses(line.function)
|
||||
}
|
||||
|
||||
switch (line.type) {
|
||||
case 'phpLine':
|
||||
return '<span class="markup_exception_stacktrace_line_number">' + line.number + '</span>' +
|
||||
self.formatFilePath(line.file, line.lineNumber) +
|
||||
'<span class="markup_exception_line_number">(' + line.lineNumber + ')</span>: ' +
|
||||
'<span class="markup_exception_stacktrace_line_function">' + line.function + '</span>'
|
||||
|
||||
case 'internal':
|
||||
return '<span class="markup_exception_stacktrace_line_number">' + line.number + '</span>' +
|
||||
'<span class="markup_exception_stacktrace_line_internal">' + line.internal + '</span>' +
|
||||
'<span class="markup_exception_stacktrace_line_function">' + line.function + '</span>'
|
||||
|
||||
case 'default':
|
||||
return '<span class="markup_exception_stacktrace_line_number">' + line.number + '</span>' +
|
||||
'<span class="markup_exception_stacktrace_line_function">' + line.function + '</span>'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.formatClasses = function (str) {
|
||||
var self = this
|
||||
|
||||
str = str.replace(ExceptionBeautifier.REGEX.className, '<span class="markup_exception_class">$1</span>')
|
||||
|
||||
str = str.replace(ExceptionBeautifier.REGEX.filePath, self.formatFilePath('$1', '$3$4') +
|
||||
'<span class="markup_exception_line_number">$2</span>')
|
||||
|
||||
if (str.match(/^\s*(call_user_func|spl_autoload_call)/)) {
|
||||
str = str.replace(/^\s*(?:call_user_func|spl_autoload_call)([^(]*)\((.*)\)/,
|
||||
'<span class="markup_exception_function --system">call_user_func$1</span>' +
|
||||
'<span class="markup_exception_sign">(</span>' +
|
||||
'$2' +
|
||||
'<span class="markup_exception_sign">)</span>')
|
||||
} else {
|
||||
str = str.replace(ExceptionBeautifier.REGEX.functionCall,
|
||||
'<span class="markup_exception_sign">-></span>' +
|
||||
'<span class="markup_exception_function">$1</span>' +
|
||||
'<span class="markup_exception_sign">(</span>' +
|
||||
'$2' +
|
||||
'<span class="markup_exception_sign">)</span>')
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.formatFilePath = function (path, line) {
|
||||
var self = this
|
||||
|
||||
return '<span class="markup_exception_file"">' + path + '</span>'
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.formatMessage = function (str) {
|
||||
var self = this
|
||||
|
||||
return self.formatClasses(str)
|
||||
.replace(/^\s+/, '')
|
||||
.replace(/\r|\n|\r\r/g, '<br>')
|
||||
.replace(/\r| {4}/g, ' ')
|
||||
}
|
||||
|
||||
|
||||
// EXCEPTION BEAUTIFIER PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
$.fn.exceptionBeautifier = function (option) {
|
||||
var args = arguments,
|
||||
result
|
||||
|
||||
this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('oc.exceptionBeautifier')
|
||||
var options = $.extend({}, ExceptionBeautifier.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('oc.exceptionBeautifier', (data = new ExceptionBeautifier(this, options)))
|
||||
if (typeof option == 'string') result = data[option].call($this)
|
||||
if (typeof result != 'undefined') return false
|
||||
})
|
||||
|
||||
return result ? result : this
|
||||
}
|
||||
|
||||
$.fn.exceptionBeautifier.Constructor = ExceptionBeautifier
|
||||
|
||||
$(document).render(function () {
|
||||
$('[data-plugin="exception-beautifier"]').exceptionBeautifier()
|
||||
})
|
||||
|
||||
}(window.jQuery)
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Previews class
|
||||
*/
|
||||
|
||||
+function ($) {
|
||||
"use strict";
|
||||
|
||||
var ExceptionBeautifier = $.fn.exceptionBeautifier.Constructor;
|
||||
|
||||
ExceptionBeautifier.EDITORS = {
|
||||
subl: {scheme: 'subl://open?url=file://%file&line=%line', name: 'Sublime (subl://)'},
|
||||
txmt: {scheme: 'txmt://open/?url=file://%file&line=%line', name: 'TextMate (txmt://)'},
|
||||
mvim: {scheme: 'mvim://open/?url=file://%file&line=%line', name: 'MacVim (mvim://)'},
|
||||
phpstorm: {scheme: 'phpstorm://open?file=%file&line=%line', name: 'PhpStorm (phpstorm://)'},
|
||||
editor: {scheme: 'editor://open/?file=%file&line=%line', name: 'Custom (editor://)'}
|
||||
}
|
||||
|
||||
ExceptionBeautifier.REGEX.editor = /idelink:\/\/([^#]+)&([0-9]+)?/
|
||||
|
||||
ExceptionBeautifier.extensions.push({
|
||||
onInit: function (exceptionBeautfier) {
|
||||
exceptionBeautfier.initEditorPopup();
|
||||
},
|
||||
onParse: function (exceptionBeautfier) {
|
||||
exceptionBeautfier.$el.on('click', 'a', function () {
|
||||
exceptionBeautfier.openWithEditor($(this).data('href'));
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
ExceptionBeautifier.prototype.initEditorPopup = function () {
|
||||
var self = this,
|
||||
title = $.oc.lang.get('eventlog.editor.title'),
|
||||
description = $.oc.lang.get('eventlog.editor.description'),
|
||||
openWith = $.oc.lang.get('eventlog.editor.openWith'),
|
||||
saveRememberClose = $.oc.lang.get('eventlog.editor.openAndRemember'),
|
||||
openClose = $.oc.lang.get('eventlog.editor.open')
|
||||
|
||||
self.$el.parent().append(' \
|
||||
<div class="control-popup modal fade" id="exception-link-editor" tabindex="-1" role="dialog"> \
|
||||
<div class="modal-dialog"><div class="modal-content"><div class="modal-header"> \
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> \
|
||||
<h4 class="modal-title">' + title + '</h4> \
|
||||
</div> \
|
||||
<div class="modal-body"><p>' + description + '</p> \
|
||||
<div class="form-group"> \
|
||||
<label class="control-label">' + openWith + ':</label> \
|
||||
<select class="form-control" name="select-exception-link-editor"></select> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="modal-footer"> \
|
||||
<button type="button" class="btn btn-default" data-trigger="editor-open-save" \
|
||||
data-dismiss="modal">' + saveRememberClose + '</button> \
|
||||
<button type="button" class="btn btn-primary" data-trigger="editor-open-close" \
|
||||
data-dismiss="modal">' + openClose + '</button> \
|
||||
</div></div></div></div>')
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.openWithEditor = function (link) {
|
||||
var self = this,
|
||||
matches,
|
||||
open = function (value) {
|
||||
window.open(link.replace(
|
||||
ExceptionBeautifier.REGEX.editor,
|
||||
ExceptionBeautifier.EDITORS[value].scheme
|
||||
.replace(/%file/, matches[1])
|
||||
.replace(/%line/, matches[2])
|
||||
), '_self')
|
||||
}
|
||||
|
||||
if (matches = link.match(ExceptionBeautifier.REGEX.editor)) {
|
||||
if (window.sessionStorage && window.sessionStorage['oc-exception-editor']) {
|
||||
open(window.sessionStorage['oc-exception-editor']);
|
||||
} else {
|
||||
$('#exception-link-editor')
|
||||
.popup({content: $('#exception-link-editor').html()})
|
||||
.on('shown.oc.popup', function (event, source, popup) {
|
||||
var $select = $('select', popup).empty()
|
||||
|
||||
if (!window.sessionStorage) {
|
||||
$('[data-trigger="editor-open-save"]', popup).hide();
|
||||
}
|
||||
|
||||
$('[data-trigger="editor-open-close"]', popup).on('click', function () {
|
||||
open($select.val());
|
||||
})
|
||||
|
||||
$('[data-trigger="editor-open-save"]', popup).on('click', function () {
|
||||
window.sessionStorage['oc-exception-editor'] = $select.val();
|
||||
|
||||
open($select.val());
|
||||
})
|
||||
|
||||
for (var key in ExceptionBeautifier.EDITORS) {
|
||||
$select.append('<option value="' + key + '">' + ExceptionBeautifier.EDITORS[key].name + '</option>')
|
||||
}
|
||||
})
|
||||
.on('hide.oc.popup', function (event, source, popup) {
|
||||
$('[data-trigger]', popup).off('click')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionBeautifier.prototype.formatFilePath = function (path, line) {
|
||||
return '<a class="markup_exception_file" href="javascript:" data-href="idelink://' + path + '&' + line + '">' + path + '</a>'
|
||||
}
|
||||
|
||||
}(window.jQuery)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?php namespace System\Controllers;
|
||||
|
||||
use App;
|
||||
use Str;
|
||||
use Lang;
|
||||
use File;
|
||||
|
|
@ -69,4 +70,17 @@ class EventLogs extends Controller
|
|||
|
||||
return $this->listRefresh();
|
||||
}
|
||||
|
||||
|
||||
public function preview($id)
|
||||
{
|
||||
$this->addCss('/modules/system/assets/css/eventlogs/preview.css', 'core');
|
||||
$this->addJs('/modules/system/assets/js/eventlogs/preview.js', 'core');
|
||||
|
||||
if (in_array(App::environment(), ['dev', 'local'])) {
|
||||
$this->addJs('/modules/system/assets/js/eventlogs/preview.links.js', 'core');
|
||||
}
|
||||
|
||||
return $this->asExtension('FormController')->preview($id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,16 @@ return [
|
|||
'before_placeholder' => 'Before'
|
||||
]
|
||||
],
|
||||
|
||||
'eventlog' => [
|
||||
'editor' => [
|
||||
'title' => 'Select the source code editor to use',
|
||||
'description' => 'Your environnement must be configured to listen to one of these URL schemes.',
|
||||
'openWith' => 'Open with',
|
||||
'openAndRemember' => 'Open & Remember for this session',
|
||||
'open' => 'Open'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ return [
|
|||
'before_placeholder' => 'Avant le',
|
||||
]
|
||||
],
|
||||
|
||||
'eventlog' => [
|
||||
'editor' => [
|
||||
'title' => 'Sélectionnez l’éditeur de code source à utiliser',
|
||||
'description' => 'Votre environnement doit être configuré pour ouvrir l’un des schéma d’URL ci-dessous.',
|
||||
'openWith' => 'Ouvrir avec',
|
||||
'openAndRemember' => 'Ouvrir et enregistrer pour la session',
|
||||
'open' => 'Ouvrir'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,3 +6,5 @@ fields:
|
|||
|
||||
message:
|
||||
type: textarea
|
||||
containerAttributes:
|
||||
data-plugin: exception-beautifier
|
||||
|
|
|
|||
Loading…
Reference in New Issue