Fix review points
- Fix headers hierarchy - Improve accessibility: - set mouse pointer - make rows focusable - open on key press - add tooltip with "title" - add role and aria attributes - Rename `ParsedLog` to `ParsedLogLine` - Add docs to `ReversedFileReader`'s implementation of `Iterator`'s methods - Add docs to `ParsedLogIterator`'s implementation of `Iterator`'s methods - Remove unnecessary comment - Add more test for parsing log lines and fix some edge cases - Fix function name in snake-case to camelCase - Remove `DIRECTORY_SEPARATOR`
This commit is contained in:
parent
5520f100b2
commit
7f695197aa
|
@ -22,10 +22,10 @@
|
||||||
namespace Friendica\Model\Log;
|
namespace Friendica\Model\Log;
|
||||||
|
|
||||||
use Friendica\Util\ReversedFileReader;
|
use Friendica\Util\ReversedFileReader;
|
||||||
use Friendica\Object\Log\ParsedLog;
|
use Friendica\Object\Log\ParsedLogLine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An iterator which returns `\Friendica\Objec\Log\ParsedLog` instances
|
* An iterator which returns `\Friendica\Objec\Log\ParsedLogLine` instances
|
||||||
*
|
*
|
||||||
* Uses `\Friendica\Util\ReversedFileReader` to fetch log lines
|
* Uses `\Friendica\Util\ReversedFileReader` to fetch log lines
|
||||||
* from newest to oldest.
|
* from newest to oldest.
|
||||||
|
@ -35,7 +35,7 @@ class ParsedLogIterator implements \Iterator
|
||||||
/** @var \Iterator */
|
/** @var \Iterator */
|
||||||
private $reader;
|
private $reader;
|
||||||
|
|
||||||
/** @var ParsedLog current iterator value*/
|
/** @var ParsedLogLine current iterator value*/
|
||||||
private $value = null;
|
private $value = null;
|
||||||
|
|
||||||
/** @var int max number of lines to read */
|
/** @var int max number of lines to read */
|
||||||
|
@ -100,19 +100,19 @@ class ParsedLogIterator implements \Iterator
|
||||||
* Check if parsed log line match filters.
|
* Check if parsed log line match filters.
|
||||||
* Always match if no filters are set.
|
* Always match if no filters are set.
|
||||||
*
|
*
|
||||||
* @param ParsedLog $parsedlog
|
* @param ParsedLogLine $parsedlogline
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function filter($parsedlog)
|
private function filter($parsedlogline)
|
||||||
{
|
{
|
||||||
$match = true;
|
$match = true;
|
||||||
foreach ($this->filters as $filter => $filtervalue) {
|
foreach ($this->filters as $filter => $filtervalue) {
|
||||||
switch ($filter) {
|
switch ($filter) {
|
||||||
case "level":
|
case "level":
|
||||||
$match = $match && ($parsedlog->level == strtoupper($filtervalue));
|
$match = $match && ($parsedlogline->level == strtoupper($filtervalue));
|
||||||
break;
|
break;
|
||||||
case "context":
|
case "context":
|
||||||
$match = $match && ($parsedlog->context == $filtervalue);
|
$match = $match && ($parsedlogline->context == $filtervalue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,13 +123,13 @@ class ParsedLogIterator implements \Iterator
|
||||||
* Check if parsed log line match search.
|
* Check if parsed log line match search.
|
||||||
* Always match if no search query is set.
|
* Always match if no search query is set.
|
||||||
*
|
*
|
||||||
* @param ParsedLog $parsedlog
|
* @param ParsedLogLine $parsedlogline
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function search($parsedlog)
|
private function search($parsedlogline)
|
||||||
{
|
{
|
||||||
if ($this->search != "") {
|
if ($this->search != "") {
|
||||||
return strstr($parsedlog->logline, $this->search) !== false;
|
return strstr($parsedlogline->logline, $this->search) !== false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -138,8 +138,8 @@ class ParsedLogIterator implements \Iterator
|
||||||
* Read a line from reader and parse.
|
* Read a line from reader and parse.
|
||||||
* Returns null if limit is reached or the reader is invalid.
|
* Returns null if limit is reached or the reader is invalid.
|
||||||
*
|
*
|
||||||
* @param ParsedLog $parsedlog
|
* @param ParsedLogLine $parsedlogline
|
||||||
* @return ?ParsedLog
|
* @return ?ParsedLogLine
|
||||||
*/
|
*/
|
||||||
private function read()
|
private function read()
|
||||||
{
|
{
|
||||||
|
@ -149,22 +149,34 @@ class ParsedLogIterator implements \Iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
$line = $this->reader->current();
|
$line = $this->reader->current();
|
||||||
return new ParsedLog($this->reader->key(), $line);
|
return new ParsedLogLine($this->reader->key(), $line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch next parsed log line which match with filters or search and
|
||||||
|
* set it as current iterator value.
|
||||||
|
*
|
||||||
|
* @see Iterator::next()
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function next()
|
public function next()
|
||||||
{
|
{
|
||||||
$parsed = $this->read();
|
$parsed = $this->read();
|
||||||
|
|
||||||
// if read() has not retuned none and
|
|
||||||
// the line don't match filters or search
|
|
||||||
// read the next line
|
|
||||||
while (is_null($parsed) == false && !($this->filter($parsed) && $this->search($parsed))) {
|
while (is_null($parsed) == false && !($this->filter($parsed) && $this->search($parsed))) {
|
||||||
$parsed = $this->read();
|
$parsed = $this->read();
|
||||||
}
|
}
|
||||||
$this->value = $parsed;
|
$this->value = $parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind the iterator to the first matching log line
|
||||||
|
*
|
||||||
|
* @see Iterator::rewind()
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function rewind()
|
public function rewind()
|
||||||
{
|
{
|
||||||
$this->value = null;
|
$this->value = null;
|
||||||
|
@ -172,16 +184,35 @@ class ParsedLogIterator implements \Iterator
|
||||||
$this->next();
|
$this->next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current parsed log line number
|
||||||
|
*
|
||||||
|
* @see Iterator::key()
|
||||||
|
* @see ReversedFileReader::key()
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
public function key()
|
public function key()
|
||||||
{
|
{
|
||||||
return $this->reader->key();
|
return $this->reader->key();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current iterator value
|
||||||
|
*
|
||||||
|
* @see Iterator::current()
|
||||||
|
* @return ?ParsedLogLing
|
||||||
|
*/
|
||||||
public function current()
|
public function current()
|
||||||
{
|
{
|
||||||
return $this->value;
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if current iterator value is valid, that is, not null
|
||||||
|
*
|
||||||
|
* @see Iterator::valid()
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
public function valid()
|
public function valid()
|
||||||
{
|
{
|
||||||
return ! is_null($this->value);
|
return ! is_null($this->value);
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace Friendica\Object\Log;
|
||||||
/**
|
/**
|
||||||
* Parse a log line and offer some utility methods
|
* Parse a log line and offer some utility methods
|
||||||
*/
|
*/
|
||||||
class ParsedLog
|
class ParsedLogLine
|
||||||
{
|
{
|
||||||
const REGEXP = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^ ]*) (\w+) \[(\w*)\]: (.*)/';
|
const REGEXP = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^ ]*) (\w+) \[(\w*)\]: (.*)/';
|
||||||
|
|
||||||
|
@ -64,16 +64,23 @@ class ParsedLog
|
||||||
|
|
||||||
private function parse($logline)
|
private function parse($logline)
|
||||||
{
|
{
|
||||||
|
$this->logline = $logline;
|
||||||
|
|
||||||
// if data is empty is serialized as '[]'. To ease the parsing
|
// if data is empty is serialized as '[]'. To ease the parsing
|
||||||
// let's replace it with '{""}'. It will be replaced by null later
|
// let's replace it with '{""}'. It will be replaced by null later
|
||||||
$logline = str_replace(' [] - {', ' {""} - {', $logline);
|
$logline = str_replace(' [] - {', ' {""} - {', $logline);
|
||||||
|
|
||||||
|
|
||||||
|
if (strstr($logline, ' - {') === false) {
|
||||||
|
// the log line is not well formed
|
||||||
|
$jsonsource = null;
|
||||||
|
} else {
|
||||||
// here we hope that there will not be the string ' - {' inside the $jsonsource value
|
// here we hope that there will not be the string ' - {' inside the $jsonsource value
|
||||||
list($logline, $jsonsource) = explode(' - {', $logline);
|
list($logline, $jsonsource) = explode(' - {', $logline);
|
||||||
$jsonsource = '{' . $jsonsource;
|
$jsonsource = '{' . $jsonsource;
|
||||||
|
}
|
||||||
|
|
||||||
$jsondata = null;
|
$jsondata = null;
|
||||||
|
|
||||||
if (strpos($logline, '{"') > 0) {
|
if (strpos($logline, '{"') > 0) {
|
||||||
list($logline, $jsondata) = explode('{"', $logline, 2);
|
list($logline, $jsondata) = explode('{"', $logline, 2);
|
||||||
|
|
||||||
|
@ -82,15 +89,20 @@ class ParsedLog
|
||||||
|
|
||||||
preg_match(self::REGEXP, $logline, $matches);
|
preg_match(self::REGEXP, $logline, $matches);
|
||||||
|
|
||||||
|
if (count($matches) == 0) {
|
||||||
|
// regexp not matching
|
||||||
|
$this->message = $this->logline;
|
||||||
|
} else {
|
||||||
$this->date = $matches[1];
|
$this->date = $matches[1];
|
||||||
$this->context = $matches[2];
|
$this->context = $matches[2];
|
||||||
$this->level = $matches[3];
|
$this->level = $matches[3];
|
||||||
$this->message = trim($matches[4]);
|
$this->message = $matches[4];
|
||||||
$this->data = $jsondata == '{""}' ? null : $jsondata;
|
$this->data = $jsondata == '{""}' ? null : $jsondata;
|
||||||
$this->source = $jsonsource;
|
$this->source = $jsonsource;
|
||||||
$this->try_fix_json();
|
$this->tryfixjson();
|
||||||
|
}
|
||||||
|
|
||||||
$this->logline = $logline;
|
$this->message = trim($this->message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,7 +113,7 @@ class ParsedLog
|
||||||
* This method try to parse the found json and if it fails, search for next '{'
|
* This method try to parse the found json and if it fails, search for next '{'
|
||||||
* in json data and retry
|
* in json data and retry
|
||||||
*/
|
*/
|
||||||
private function try_fix_json()
|
private function tryfixjson()
|
||||||
{
|
{
|
||||||
if (is_null($this->data) || $this->data == '') {
|
if (is_null($this->data) || $this->data == '') {
|
||||||
return;
|
return;
|
||||||
|
@ -112,10 +124,15 @@ class ParsedLog
|
||||||
// try to find next { in $str and move string before to 'message'
|
// try to find next { in $str and move string before to 'message'
|
||||||
|
|
||||||
$pos = strpos($this->data, '{', 1);
|
$pos = strpos($this->data, '{', 1);
|
||||||
|
if ($pos === false) {
|
||||||
|
$this->message .= $this->data;
|
||||||
|
$this->data = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->message .= substr($this->data, 0, $pos);
|
$this->message .= substr($this->data, 0, $pos);
|
||||||
$this->data = substr($this->data, $pos);
|
$this->data = substr($this->data, $pos);
|
||||||
$this->try_fix_json();
|
$this->tryfixjson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +141,7 @@ class ParsedLog
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data()
|
public function getData()
|
||||||
{
|
{
|
||||||
$data = json_decode($this->data, true);
|
$data = json_decode($this->data, true);
|
||||||
if ($data) {
|
if ($data) {
|
||||||
|
@ -140,7 +157,7 @@ class ParsedLog
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_source()
|
public function getSource()
|
||||||
{
|
{
|
||||||
return json_decode($this->source, true);
|
return json_decode($this->source, true);
|
||||||
}
|
}
|
|
@ -70,6 +70,11 @@ class ReversedFileReader implements \Iterator
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read $size bytes behind last position
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
private function _read($size)
|
private function _read($size)
|
||||||
{
|
{
|
||||||
$this->pos -= $size;
|
$this->pos -= $size;
|
||||||
|
@ -77,6 +82,12 @@ class ReversedFileReader implements \Iterator
|
||||||
return fread($this->fh, $size);
|
return fread($this->fh, $size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read next line from end of file
|
||||||
|
* Return null if no lines are left to read
|
||||||
|
*
|
||||||
|
* @return ?string
|
||||||
|
*/
|
||||||
private function _readline()
|
private function _readline()
|
||||||
{
|
{
|
||||||
$buffer = & $this->buffer;
|
$buffer = & $this->buffer;
|
||||||
|
@ -91,12 +102,24 @@ class ReversedFileReader implements \Iterator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch next line from end and set it as current iterator value.
|
||||||
|
*
|
||||||
|
* @see Iterator::next()
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function next()
|
public function next()
|
||||||
{
|
{
|
||||||
++$this->key;
|
++$this->key;
|
||||||
$this->value = $this->_readline();
|
$this->value = $this->_readline();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind iterator to the first line at the end of file
|
||||||
|
*
|
||||||
|
* @see Iterator::rewind()
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function rewind()
|
public function rewind()
|
||||||
{
|
{
|
||||||
if ($this->filesize > 0) {
|
if ($this->filesize > 0) {
|
||||||
|
@ -108,16 +131,34 @@ class ReversedFileReader implements \Iterator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current line number, starting from zero at the end of file
|
||||||
|
*
|
||||||
|
* @see Iterator::key()
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
public function key()
|
public function key()
|
||||||
{
|
{
|
||||||
return $this->key;
|
return $this->key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current line
|
||||||
|
*
|
||||||
|
* @see Iterator::current()
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function current()
|
public function current()
|
||||||
{
|
{
|
||||||
return $this->value;
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if current iterator value is valid, that is, we readed all lines in files
|
||||||
|
*
|
||||||
|
* @see Iterator::valid()
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
public function valid()
|
public function valid()
|
||||||
{
|
{
|
||||||
return ! is_null($this->value);
|
return ! is_null($this->value);
|
||||||
|
|
|
@ -42,12 +42,7 @@ class ParsedLogIteratorTest extends TestCase
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$logfile = dirname(__DIR__) . DIRECTORY_SEPARATOR .
|
$logfile = dirname(__DIR__) . '/../../datasets/log/friendica.log.txt';
|
||||||
'..' . DIRECTORY_SEPARATOR .
|
|
||||||
'..' . DIRECTORY_SEPARATOR .
|
|
||||||
'datasets' . DIRECTORY_SEPARATOR .
|
|
||||||
'log' . DIRECTORY_SEPARATOR .
|
|
||||||
'friendica.log.txt';
|
|
||||||
|
|
||||||
$reader = new ReversedFileReader();
|
$reader = new ReversedFileReader();
|
||||||
$this->pli = new ParsedLogIterator($reader);
|
$this->pli = new ParsedLogIterator($reader);
|
||||||
|
|
|
@ -21,17 +21,17 @@
|
||||||
|
|
||||||
namespace Friendica\Test\src\Object\Log;
|
namespace Friendica\Test\src\Object\Log;
|
||||||
|
|
||||||
use Friendica\Object\Log\ParsedLog;
|
use Friendica\Object\Log\ParsedLogLine;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log parser testing class
|
* Log parser testing class
|
||||||
*/
|
*/
|
||||||
class ParsedLogTest extends TestCase
|
class ParsedLogLineTest extends TestCase
|
||||||
{
|
{
|
||||||
public static function do_log_line($logline, $expected_data)
|
public static function do_log_line($logline, $expected_data)
|
||||||
{
|
{
|
||||||
$parsed = new ParsedLog(0, $logline);
|
$parsed = new ParsedLogLine(0, $logline);
|
||||||
foreach ($expected_data as $k => $v) {
|
foreach ($expected_data as $k => $v) {
|
||||||
self::assertSame($parsed->$k, $v, '"'.$k.'" does not match expectation');
|
self::assertSame($parsed->$k, $v, '"'.$k.'" does not match expectation');
|
||||||
}
|
}
|
||||||
|
@ -90,4 +90,94 @@ class ParsedLogTest extends TestCase
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test non conforming log line
|
||||||
|
*/
|
||||||
|
public function testNonConformingLogLine()
|
||||||
|
{
|
||||||
|
self::do_log_line(
|
||||||
|
'this log line is not formatted as expected',
|
||||||
|
[
|
||||||
|
'date' => null,
|
||||||
|
'context' => null,
|
||||||
|
'level' => null,
|
||||||
|
'message' => 'this log line is not formatted as expected',
|
||||||
|
'data' => null,
|
||||||
|
'source' => null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test missing source
|
||||||
|
*/
|
||||||
|
public function testMissingSource()
|
||||||
|
{
|
||||||
|
self::do_log_line(
|
||||||
|
'2021-05-24T15:30:01Z worker [NOTICE]: Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10 {"worker_id":"ece8fc8","worker_cmd":"Cron"}',
|
||||||
|
[
|
||||||
|
'date' => '2021-05-24T15:30:01Z',
|
||||||
|
'context' => 'worker',
|
||||||
|
'level' => 'NOTICE',
|
||||||
|
'message' => 'Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10',
|
||||||
|
'data' => '{"worker_id":"ece8fc8","worker_cmd":"Cron"}',
|
||||||
|
'source' => null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test missing data
|
||||||
|
*/
|
||||||
|
public function testMissingData()
|
||||||
|
{
|
||||||
|
self::do_log_line(
|
||||||
|
'2021-05-24T15:30:01Z worker [NOTICE]: Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10 - {"file":"Worker.php","line":786,"function":"tooMuchWorkers","uid":"364d3c","process_id":20754}',
|
||||||
|
[
|
||||||
|
'date' => '2021-05-24T15:30:01Z',
|
||||||
|
'context' => 'worker',
|
||||||
|
'level' => 'NOTICE',
|
||||||
|
'message' => 'Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10',
|
||||||
|
'data' => null,
|
||||||
|
'source' => '{"file":"Worker.php","line":786,"function":"tooMuchWorkers","uid":"364d3c","process_id":20754}',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test missing data and source
|
||||||
|
*/
|
||||||
|
public function testMissingDataAndSource()
|
||||||
|
{
|
||||||
|
self::do_log_line(
|
||||||
|
'2021-05-24T15:30:01Z worker [NOTICE]: Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10',
|
||||||
|
[
|
||||||
|
'date' => '2021-05-24T15:30:01Z',
|
||||||
|
'context' => 'worker',
|
||||||
|
'level' => 'NOTICE',
|
||||||
|
'message' => 'Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10',
|
||||||
|
'data' => null,
|
||||||
|
'source' => null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test missing source and invalid data
|
||||||
|
*/
|
||||||
|
public function testMissingSourceAndInvalidData()
|
||||||
|
{
|
||||||
|
self::do_log_line(
|
||||||
|
'2021-05-24T15:30:01Z worker [NOTICE]: Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10 {"invalidjson {really',
|
||||||
|
[
|
||||||
|
'date' => '2021-05-24T15:30:01Z',
|
||||||
|
'context' => 'worker',
|
||||||
|
'level' => 'NOTICE',
|
||||||
|
'message' => 'Load: 0.01/20 - processes: 0/1/6 (0:0, 30:1) - maximum: 10/10 {"invalidjson {really',
|
||||||
|
'data' => null,
|
||||||
|
'source' => null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,33 @@
|
||||||
function log_show_details(id) {
|
(function(){
|
||||||
|
function log_show_details(elm) {
|
||||||
|
const id = elm.id;
|
||||||
|
var hidden = true;
|
||||||
document
|
document
|
||||||
.querySelectorAll('[data-id="' + id + '"]')
|
.querySelectorAll('[data-id="' + id + '"]')
|
||||||
.forEach(elm => {
|
.forEach(edetails => {
|
||||||
elm.classList.toggle('hidden')
|
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,7 +1,7 @@
|
||||||
<div id="adminpage">
|
<div id="adminpage">
|
||||||
<h1>{{$title}} - {{$page}}</h1>
|
<h1>{{$title}} - {{$page}}</h1>
|
||||||
|
|
||||||
<h3>{{$logname}}</h3>
|
<h2>{{$logname}}</h2>
|
||||||
{{if $error }}
|
{{if $error }}
|
||||||
<div id="admin-error-message-wrapper" class="alert alert-warning">
|
<div id="admin-error-message-wrapper" class="alert alert-warning">
|
||||||
<p>{{$error nofilter}}</p>
|
<p>{{$error nofilter}}</p>
|
||||||
|
@ -44,14 +44,18 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{foreach $data as $row}}
|
{{foreach $data as $row}}
|
||||||
<tr id="ev-{{$row->id}}" onClick="log_show_details('ev-{{$row->id}}')">
|
<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->date}}</td>
|
||||||
<td>{{$row->level}}</td>
|
<td>{{$row->level}}</td>
|
||||||
<td>{{$row->context}}</td>
|
<td>{{$row->context}}</td>
|
||||||
<td>{{$row->message}}</td>
|
<td>{{$row->message}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Data</th></tr>
|
<tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Data</th></tr>
|
||||||
{{foreach $row->get_data() as $k=>$v}}
|
{{foreach $row->getData() as $k=>$v}}
|
||||||
<tr class="hidden" data-id="ev-{{$row->id}}">
|
<tr class="hidden" data-id="ev-{{$row->id}}">
|
||||||
<th>{{$k}}</th>
|
<th>{{$k}}</th>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
|
@ -60,7 +64,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
{{/foreach}}
|
{{/foreach}}
|
||||||
<tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Source</th></tr>
|
<tr class="hidden" data-id="ev-{{$row->id}}"><th colspan="4">Source</th></tr>
|
||||||
{{foreach $row->get_source() as $k=>$v}}
|
{{foreach $row->getSource() as $k=>$v}}
|
||||||
<tr class="hidden" data-id="ev-{{$row->id}}">
|
<tr class="hidden" data-id="ev-{{$row->id}}">
|
||||||
<th>{{$k}}</th>
|
<th>{{$k}}</th>
|
||||||
<td colspan="3">{{$v}}</td>
|
<td colspan="3">{{$v}}</td>
|
||||||
|
|
|
@ -98,6 +98,13 @@ details summary {
|
||||||
display: list-item;
|
display: list-item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clickable table rows
|
||||||
|
*/
|
||||||
|
.table > tbody > td[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mobile aside
|
* mobile aside
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,6 +21,12 @@ $(function(){
|
||||||
$(".log-event").on("click", function(ev) {
|
$(".log-event").on("click", function(ev) {
|
||||||
show_details_for_element(ev.currentTarget);
|
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){
|
$("[data-previous").on("click", function(ev){
|
||||||
var currentid = document.getElementById("logdetail").dataset.rowId;
|
var currentid = document.getElementById("logdetail").dataset.rowId;
|
||||||
|
@ -37,9 +43,15 @@ $(function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function show_details_for_element(element) {
|
const $modal = $("#logdetail");
|
||||||
var $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;
|
$modal[0].dataset.rowId = element.id;
|
||||||
|
|
||||||
var tr = $modal.find(".main-data tbody tr")[0];
|
var tr = $modal.find(".main-data tbody tr")[0];
|
||||||
|
@ -64,6 +76,7 @@ $(function(){
|
||||||
$("[data-next").prop("disabled", $(element).next().length == 0);
|
$("[data-next").prop("disabled", $(element).next().length == 0);
|
||||||
|
|
||||||
$modal.modal({})
|
$modal.modal({})
|
||||||
|
element.setAttribute("aria-expanded", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function recursive_details(s, data, lev=0) {
|
function recursive_details(s, data, lev=0) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div id="adminpage">
|
<div id="adminpage">
|
||||||
<h1>{{$title}} - {{$page}}</h1>
|
<h1>{{$title}} - {{$page}}</h1>
|
||||||
|
|
||||||
<h3>{{$logname}}</h3>
|
<h2>{{$logname}}</h2>
|
||||||
{{if $error }}
|
{{if $error }}
|
||||||
<div id="admin-error-message-wrapper" class="alert alert-warning">
|
<div id="admin-error-message-wrapper" class="alert alert-warning">
|
||||||
<p>{{$error nofilter}}</p>
|
<p>{{$error nofilter}}</p>
|
||||||
|
@ -60,6 +60,8 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{{foreach $data as $row}}
|
{{foreach $data as $row}}
|
||||||
<tr id="ev-{{$row->id}}" class="log-event"
|
<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}}">
|
data-data="{{$row->data}}" data-source="{{$row->source}}">
|
||||||
<td>{{$row->date}}</td>
|
<td>{{$row->date}}</td>
|
||||||
<td class="
|
<td class="
|
||||||
|
|
Loading…
Reference in New Issue
Block a user