1
0
Fork 0

Merge pull request #10309 from fabrixxm/feature/advanced-logsview

Display structured logs in admin
This commit is contained in:
Hypolite Petovan 2021-08-20 05:24:07 -04:00 committed by GitHub
commit 632d1024f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1315 additions and 31 deletions

View 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);
}
});
});
})();

View file

@ -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>

View file

@ -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
*/

View 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;
}
});

View 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">&times;</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>&lt;</button>
<button type="button" class="btn btn-default" data-next>&gt;</button>
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->