Merge pull request #10309 from fabrixxm/feature/advanced-logsview
Display structured logs in admin
This commit is contained in:
commit
632d1024f7
14 changed files with 1315 additions and 31 deletions
33
view/js/module/admin/logs/view.js
Normal file
33
view/js/module/admin/logs/view.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
(function(){
|
||||
function log_show_details(elm) {
|
||||
const id = elm.id;
|
||||
var hidden = true;
|
||||
document
|
||||
.querySelectorAll('[data-id="' + id + '"]')
|
||||
.forEach(edetails => {
|
||||
hidden = edetails.classList.toggle('hidden');
|
||||
});
|
||||
document
|
||||
.querySelectorAll('[aria-expanded="true"]')
|
||||
.forEach(eexpanded => {
|
||||
eexpanded.setAttribute('aria-expanded', false);
|
||||
});
|
||||
|
||||
if (!hidden) {
|
||||
elm.setAttribute('aria-expanded', true);
|
||||
}
|
||||
}
|
||||
|
||||
document
|
||||
.querySelectorAll('.log-event')
|
||||
.forEach(elm => {
|
||||
elm.addEventListener("click", evt => {
|
||||
log_show_details(evt.currentTarget);
|
||||
});
|
||||
elm.addEventListener("keydown", evt => {
|
||||
if (evt.keyCode == 13 || evt.keyCode == 32) {
|
||||
log_show_details(evt.currentTarget);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -1,6 +1,78 @@
|
|||
<div id="adminpage">
|
||||
<h1>{{$title}} - {{$page}}</h1>
|
||||
|
||||
<h3>{{$logname}}</h3>
|
||||
<div style="width:100%; height:400px; overflow: auto; "><pre>{{$data}}</pre></div>
|
||||
|
||||
<h2>{{$logname}}</h2>
|
||||
{{if $error }}
|
||||
<div id="admin-error-message-wrapper" class="alert alert-warning">
|
||||
<p>{{$error nofilter}}</p>
|
||||
</div>
|
||||
{{else}}
|
||||
<form>
|
||||
<p>
|
||||
<input type="search" name="q" value="{{$q}}" placeholder="search"></input>
|
||||
<input type="Submit" value="search">
|
||||
<a href="/admin/logs/view">clear</a>
|
||||
</p>
|
||||
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>
|
||||
<select name="level" onchange="this.form.submit()">
|
||||
{{foreach $filtersvalues.level as $v }}
|
||||
<option {{if $filters.level == $v}}selected{{/if}} value="{{$v}}">
|
||||
{{if $v == ""}}Level{{/if}}
|
||||
{{$v}}
|
||||
</option>
|
||||
{{/foreach}}
|
||||
</select>
|
||||
</th>
|
||||
<th>
|
||||
<select name="context" onchange="this.form.submit()">
|
||||
{{foreach $filtersvalues.context as $v }}
|
||||
<option {{if $filters.context == $v}}selected{{/if}} value="{{$v}}">
|
||||
{{if $v == ""}}Context{{/if}}
|
||||
{{$v}}
|
||||
</option>
|
||||
{{/foreach}}
|
||||
</select>
|
||||
</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{foreach $data as $row}}
|
||||
<tr id="ev-{{$row->id}}" class="log-event"
|
||||
role="button" tabIndex="0"
|
||||
aria-label="View details" aria-haspopup="true" aria-expanded="false"
|
||||
style="cursor:pointer;"
|
||||
title="Click to view details">
|
||||
<td>{{$row->date}}</td>
|
||||
<td>{{$row->level}}</td>
|
||||
<td>{{$row->context}}</td>
|
||||
<td>{{$row->message}}</td>
|
||||
</tr>
|
||||
<tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Data</th></tr>
|
||||
{{foreach $row->getData() as $k=>$v}}
|
||||
<tr class="hidden" data-id="ev-{{$row->id}}">
|
||||
<th>{{$k}}</th>
|
||||
<td colspan="3">
|
||||
<pre>{{$v nofilter}}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
{{/foreach}}
|
||||
<tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Source</th></tr>
|
||||
{{foreach $row->getSource() as $k=>$v}}
|
||||
<tr class="hidden" data-id="ev-{{$row->id}}">
|
||||
<th>{{$k}}</th>
|
||||
<td colspan="3">{{$v}}</td>
|
||||
</tr>
|
||||
{{/foreach}}
|
||||
{{/foreach}}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -84,6 +84,27 @@ blockquote {
|
|||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* details tag
|
||||
*/
|
||||
details {
|
||||
padding: .5em .5em 0;
|
||||
}
|
||||
details details {
|
||||
padding-left: .5em;
|
||||
}
|
||||
details summary {
|
||||
font-weight: bold;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/**
|
||||
* clickable table rows
|
||||
*/
|
||||
.table > tbody > td[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* mobile aside
|
||||
*/
|
||||
|
|
98
view/theme/frio/js/module/admin/logs/view.js
Normal file
98
view/theme/frio/js/module/admin/logs/view.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
$(function(){
|
||||
|
||||
/* column filter */
|
||||
$("a[data-filter]").on("click", function(ev) {
|
||||
var filter = this.dataset.filter;
|
||||
var value = this.dataset.filterValue;
|
||||
var re = RegExp(filter+"=[a-z]*");
|
||||
var newhref = location.href;
|
||||
if (!location.href.indexOf("?") < 0) {
|
||||
newhref = location.href + "?" + filter + "=" + value;
|
||||
} else if (location.href.match(re)) {
|
||||
newhref = location.href.replace(RegExp(filter+"=[a-z]*"), filter+"="+value);
|
||||
} else {
|
||||
newhref = location.href + "&" + filter + "=" + value;
|
||||
}
|
||||
location.href = newhref;
|
||||
return false;
|
||||
});
|
||||
|
||||
/* log details dialog */
|
||||
$(".log-event").on("click", function(ev) {
|
||||
show_details_for_element(ev.currentTarget);
|
||||
});
|
||||
$(".log-event").on("keydown", function(ev) {
|
||||
if (ev.keyCode == 13 || ev.keyCode == 32) {
|
||||
show_details_for_element(ev.currentTarget);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$("[data-previous").on("click", function(ev){
|
||||
var currentid = document.getElementById("logdetail").dataset.rowId;
|
||||
var $elm = $("#" + currentid).prev();
|
||||
if ($elm.length == 0) return;
|
||||
show_details_for_element($elm[0]);
|
||||
});
|
||||
|
||||
$("[data-next").on("click", function(ev){
|
||||
var currentid = document.getElementById("logdetail").dataset.rowId;
|
||||
var $elm = $("#" + currentid).next();
|
||||
if ($elm.length == 0) return;
|
||||
show_details_for_element($elm[0]);
|
||||
});
|
||||
|
||||
|
||||
const $modal = $("#logdetail");
|
||||
|
||||
$modal.on("hidden.bs.modal", function(ev){
|
||||
document
|
||||
.querySelectorAll('[aria-expanded="true"]')
|
||||
.forEach(elm => elm.setAttribute("aria-expanded", false))
|
||||
});
|
||||
|
||||
function show_details_for_element(element) {
|
||||
$modal[0].dataset.rowId = element.id;
|
||||
|
||||
var tr = $modal.find(".main-data tbody tr")[0];
|
||||
tr.innerHTML = element.innerHTML;
|
||||
|
||||
var data = JSON.parse(element.dataset.source);
|
||||
$modal.find(".source-data td").each(function(i,elm){
|
||||
var k = elm.dataset.value;
|
||||
elm.innerText = data[k];
|
||||
});
|
||||
|
||||
var elm = $modal.find(".event-data")[0];
|
||||
elm.innerHTML = "";
|
||||
var data = element.dataset.data;
|
||||
if (data !== "") {
|
||||
elm.innerHTML = "<h3>Data</h3>";
|
||||
data = JSON.parse(data);
|
||||
elm.innerHTML += recursive_details("", data);
|
||||
}
|
||||
|
||||
$("[data-previous").prop("disabled", $(element).prev().length == 0);
|
||||
$("[data-next").prop("disabled", $(element).next().length == 0);
|
||||
|
||||
$modal.modal({})
|
||||
element.setAttribute("aria-expanded", true);
|
||||
}
|
||||
|
||||
function recursive_details(s, data, lev=0) {
|
||||
for(var k in data) {
|
||||
if (data.hasOwnProperty(k)) {
|
||||
var v = data[k];
|
||||
var open = lev > 1 ? "" : "open";
|
||||
s += "<details " + open + "><summary>" + k + "</summary>";
|
||||
if (typeof v === 'object' && v !== null) {
|
||||
s = recursive_details(s, v, lev+1);
|
||||
} else {
|
||||
s += $("<pre>").text(v)[0].outerHTML;
|
||||
}
|
||||
s += "</details>";
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
});
|
138
view/theme/frio/templates/admin/logs/view.tpl
Executable file
138
view/theme/frio/templates/admin/logs/view.tpl
Executable file
|
@ -0,0 +1,138 @@
|
|||
<div id="adminpage">
|
||||
<h1>{{$title}} - {{$page}}</h1>
|
||||
|
||||
<h2>{{$logname}}</h2>
|
||||
{{if $error }}
|
||||
<div id="admin-error-message-wrapper" class="alert alert-warning">
|
||||
<p>{{$error nofilter}}</p>
|
||||
</div>
|
||||
{{else}}
|
||||
<form method="get" class="row">
|
||||
<div class="col-xs-10">
|
||||
<div class="form-group form-group-search">
|
||||
<input accesskey="s" id="nav-search-input-field" class="form-control form-search"
|
||||
type="text" name="q" data-toggle="tooltip" title="Search in logs"
|
||||
placeholder="Search" value="{{$q}}">
|
||||
<button class="btn btn-default btn-sm form-button-search"
|
||||
type="submit">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="xol-xs-2">
|
||||
<a href="/admin/logs/view" class="btn btn-default">Show all</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th class="dropdown">
|
||||
<a class="dropdown-toggle text-nowrap" type="button" id="level" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Level {{if $filters.level}}({{$filters.level}}){{/if}}<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="level">
|
||||
{{foreach $filtersvalues.level as $v }}
|
||||
<li {{if $filters.level == $v}}class="active"{{/if}}>
|
||||
<a href="/admin/logs/view?level={{$v}}" data-filter="level" data-filter-value="{{$v}}">
|
||||
{{if $v == ""}}ALL{{/if}}{{$v}}
|
||||
</a>
|
||||
</li>
|
||||
{{/foreach}}
|
||||
</ul>
|
||||
</th>
|
||||
<th class="dropdown">
|
||||
<a class="dropdown-toggle text-nowrap" type="button" id="context" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Context {{if $filters.context}}({{$filters.context}}){{/if}}<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="context">
|
||||
{{foreach $filtersvalues.context as $v }}
|
||||
<li {{if $filters.context == $v}}class="active"{{/if}}>
|
||||
<a href="/admin/logs/view?context={{$v}}" data-filter="context" data-filter-value="{{$v}}">
|
||||
{{if $v == ""}}ALL{{/if}}{{$v}}
|
||||
</a>
|
||||
</li>
|
||||
{{/foreach}}
|
||||
</ul>
|
||||
</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{foreach $data as $row}}
|
||||
<tr id="ev-{{$row->id}}" class="log-event"
|
||||
role="button" tabIndex="0"
|
||||
aria-label="View details" aria-haspopup="true" aria-expanded="false"
|
||||
data-data="{{$row->data}}" data-source="{{$row->source}}">
|
||||
<td>{{$row->date}}</td>
|
||||
<td class="
|
||||
{{if $row->level == "CRITICAL"}}bg-danger
|
||||
{{elseif $row->level == "ERROR"}}bg-danger
|
||||
{{elseif $row->level == "WARNING"}}bg-warinig
|
||||
{{elseif $row->level == "NOTICE"}}bg-info
|
||||
{{elseif $row->level == "DEBUG"}}text-muted
|
||||
{{/if}}
|
||||
">{{$row->level}}</td>
|
||||
<td>{{$row->context}}</td>
|
||||
<td style="width:80%">{{$row->message}}</td>
|
||||
</tr>
|
||||
{{/foreach}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div id="logdetail" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" style="width:90%" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Event details</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table main-data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Level</th>
|
||||
<th>Context</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr></tr></tbody>
|
||||
</table>
|
||||
<table class="table source-data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File</th>
|
||||
<th>Line</th>
|
||||
<th>Function</th>
|
||||
<th>UID</th>
|
||||
<th>Process ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td data-value="file"></td>
|
||||
<td data-value="line"></td>
|
||||
<td data-value="function" style="width:70%"></td>
|
||||
<td data-value="uid"></td>
|
||||
<td data-value="process_id"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<div class="event-source">
|
||||
</div>
|
||||
<div class="event-data">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-previous><</button>
|
||||
<button type="button" class="btn btn-default" data-next>></button>
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
Loading…
Add table
Add a link
Reference in a new issue