namespace Friendica\Model\Log;
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
* from newest to oldest.
/** @var \Iterator */
private $reader;
- /** @var ParsedLog current iterator value*/
+ /** @var ParsedLogLine current iterator value*/
private $value = null;
/** @var int max number of lines to read */
* Check if parsed log line match filters.
* Always match if no filters are set.
*
- * @param ParsedLog $parsedlog
+ * @param ParsedLogLine $parsedlogline
* @return bool
*/
- private function filter($parsedlog)
+ private function filter($parsedlogline)
{
$match = true;
foreach ($this->filters as $filter => $filtervalue) {
switch ($filter) {
case "level":
- $match = $match && ($parsedlog->level == strtoupper($filtervalue));
+ $match = $match && ($parsedlogline->level == strtoupper($filtervalue));
break;
case "context":
- $match = $match && ($parsedlog->context == $filtervalue);
+ $match = $match && ($parsedlogline->context == $filtervalue);
break;
}
}
* Check if parsed log line match search.
* Always match if no search query is set.
*
- * @param ParsedLog $parsedlog
+ * @param ParsedLogLine $parsedlogline
* @return bool
*/
- private function search($parsedlog)
+ private function search($parsedlogline)
{
if ($this->search != "") {
- return strstr($parsedlog->logline, $this->search) !== false;
+ return strstr($parsedlogline->logline, $this->search) !== false;
}
return true;
}
* Read a line from reader and parse.
* Returns null if limit is reached or the reader is invalid.
*
- * @param ParsedLog $parsedlog
- * @return ?ParsedLog
+ * @param ParsedLogLine $parsedlogline
+ * @return ?ParsedLogLine
*/
private function read()
{
}
$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()
{
$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))) {
$parsed = $this->read();
}
$this->value = $parsed;
}
+
+ /**
+ * Rewind the iterator to the first matching log line
+ *
+ * @see Iterator::rewind()
+ * @return void
+ */
public function rewind()
{
$this->value = null;
$this->next();
}
+ /**
+ * Return current parsed log line number
+ *
+ * @see Iterator::key()
+ * @see ReversedFileReader::key()
+ * @return int
+ */
public function key()
{
return $this->reader->key();
}
+ /**
+ * Return current iterator value
+ *
+ * @see Iterator::current()
+ * @return ?ParsedLogLing
+ */
public function current()
{
return $this->value;
}
+ /**
+ * Checks if current iterator value is valid, that is, not null
+ *
+ * @see Iterator::valid()
+ * @return bool
+ */
public function valid()
{
return ! is_null($this->value);
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2021, Friendica
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Object\Log;
-
-/**
- * Parse a log line and offer some utility methods
- */
-class ParsedLog
-{
- const REGEXP = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^ ]*) (\w+) \[(\w*)\]: (.*)/';
-
- /** @var int */
- public $id = 0;
-
- /** @var string */
- public $date = null;
-
- /** @var string */
- public $context = null;
-
- /** @var string */
- public $level = null;
-
- /** @var string */
- public $message = null;
-
- /** @var string */
- public $data = null;
-
- /** @var string */
- public $source = null;
-
- /** @var string */
- public $logline;
-
- /**
- * @param int line id
- * @param string $logline Source log line to parse
- */
- public function __construct(int $id, string $logline)
- {
- $this->id = $id;
- $this->parse($logline);
- }
-
- private function parse($logline)
- {
- // if data is empty is serialized as '[]'. To ease the parsing
- // let's replace it with '{""}'. It will be replaced by null later
- $logline = str_replace(' [] - {', ' {""} - {', $logline);
-
- // here we hope that there will not be the string ' - {' inside the $jsonsource value
- list($logline, $jsonsource) = explode(' - {', $logline);
- $jsonsource = '{' . $jsonsource;
-
- $jsondata = null;
-
- if (strpos($logline, '{"') > 0) {
- list($logline, $jsondata) = explode('{"', $logline, 2);
-
- $jsondata = '{"' . $jsondata;
- }
-
- preg_match(self::REGEXP, $logline, $matches);
-
- $this->date = $matches[1];
- $this->context = $matches[2];
- $this->level = $matches[3];
- $this->message = trim($matches[4]);
- $this->data = $jsondata == '{""}' ? null : $jsondata;
- $this->source = $jsonsource;
- $this->try_fix_json();
-
- $this->logline = $logline;
- }
-
- /**
- * Fix message / data split
- *
- * In log boundary between message and json data is not specified.
- * If message contains '{' the parser thinks there starts the json data.
- * This method try to parse the found json and if it fails, search for next '{'
- * in json data and retry
- */
- private function try_fix_json()
- {
- if (is_null($this->data) || $this->data == '') {
- return;
- }
- try {
- $d = json_decode($this->data, true, 512, JSON_THROW_ON_ERROR);
- } catch (\JsonException $e) {
- // try to find next { in $str and move string before to 'message'
-
- $pos = strpos($this->data, '{', 1);
-
- $this->message .= substr($this->data, 0, $pos);
- $this->data = substr($this->data, $pos);
- $this->try_fix_json();
- }
- }
-
- /**
- * Return decoded `data` as array suitable for template
- *
- * @return array
- */
- public function get_data()
- {
- $data = json_decode($this->data, true);
- if ($data) {
- foreach ($data as $k => $v) {
- $data[$k] = print_r($v, true);
- }
- }
- return $data;
- }
-
- /**
- * Return decoded `source` as array suitable for template
- *
- * @return array
- */
- public function get_source()
- {
- return json_decode($this->source, true);
- }
-}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2021, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Object\Log;
+
+/**
+ * Parse a log line and offer some utility methods
+ */
+class ParsedLogLine
+{
+ const REGEXP = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^ ]*) (\w+) \[(\w*)\]: (.*)/';
+
+ /** @var int */
+ public $id = 0;
+
+ /** @var string */
+ public $date = null;
+
+ /** @var string */
+ public $context = null;
+
+ /** @var string */
+ public $level = null;
+
+ /** @var string */
+ public $message = null;
+
+ /** @var string */
+ public $data = null;
+
+ /** @var string */
+ public $source = null;
+
+ /** @var string */
+ public $logline;
+
+ /**
+ * @param int line id
+ * @param string $logline Source log line to parse
+ */
+ public function __construct(int $id, string $logline)
+ {
+ $this->id = $id;
+ $this->parse($logline);
+ }
+
+ private function parse($logline)
+ {
+ $this->logline = $logline;
+
+ // if data is empty is serialized as '[]'. To ease the parsing
+ // let's replace it with '{""}'. It will be replaced by null later
+ $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
+ list($logline, $jsonsource) = explode(' - {', $logline);
+ $jsonsource = '{' . $jsonsource;
+ }
+
+ $jsondata = null;
+ if (strpos($logline, '{"') > 0) {
+ list($logline, $jsondata) = explode('{"', $logline, 2);
+
+ $jsondata = '{"' . $jsondata;
+ }
+
+ preg_match(self::REGEXP, $logline, $matches);
+
+ if (count($matches) == 0) {
+ // regexp not matching
+ $this->message = $this->logline;
+ } else {
+ $this->date = $matches[1];
+ $this->context = $matches[2];
+ $this->level = $matches[3];
+ $this->message = $matches[4];
+ $this->data = $jsondata == '{""}' ? null : $jsondata;
+ $this->source = $jsonsource;
+ $this->tryfixjson();
+ }
+
+ $this->message = trim($this->message);
+ }
+
+ /**
+ * Fix message / data split
+ *
+ * In log boundary between message and json data is not specified.
+ * If message contains '{' the parser thinks there starts the json data.
+ * This method try to parse the found json and if it fails, search for next '{'
+ * in json data and retry
+ */
+ private function tryfixjson()
+ {
+ if (is_null($this->data) || $this->data == '') {
+ return;
+ }
+ try {
+ $d = json_decode($this->data, true, 512, JSON_THROW_ON_ERROR);
+ } catch (\JsonException $e) {
+ // try to find next { in $str and move string before to 'message'
+
+ $pos = strpos($this->data, '{', 1);
+ if ($pos === false) {
+ $this->message .= $this->data;
+ $this->data = null;
+ return;
+ }
+
+ $this->message .= substr($this->data, 0, $pos);
+ $this->data = substr($this->data, $pos);
+ $this->tryfixjson();
+ }
+ }
+
+ /**
+ * Return decoded `data` as array suitable for template
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ $data = json_decode($this->data, true);
+ if ($data) {
+ foreach ($data as $k => $v) {
+ $data[$k] = print_r($v, true);
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Return decoded `source` as array suitable for template
+ *
+ * @return array
+ */
+ public function getSource()
+ {
+ return json_decode($this->source, true);
+ }
+}
return $this;
}
+ /**
+ * Read $size bytes behind last position
+ *
+ * @return string
+ */
private function _read($size)
{
$this->pos -= $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()
{
$buffer = & $this->buffer;
}
}
+ /**
+ * Fetch next line from end and set it as current iterator value.
+ *
+ * @see Iterator::next()
+ * @return void
+ */
public function next()
{
++$this->key;
$this->value = $this->_readline();
}
+ /**
+ * Rewind iterator to the first line at the end of file
+ *
+ * @see Iterator::rewind()
+ * @return void
+ */
public function rewind()
{
if ($this->filesize > 0) {
}
}
+ /**
+ * Return current line number, starting from zero at the end of file
+ *
+ * @see Iterator::key()
+ * @return int
+ */
public function key()
{
return $this->key;
}
+ /**
+ * Return current line
+ *
+ * @see Iterator::current()
+ * @return string
+ */
public function current()
{
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()
{
return ! is_null($this->value);
protected function setUp(): void
{
- $logfile = dirname(__DIR__) . DIRECTORY_SEPARATOR .
- '..' . DIRECTORY_SEPARATOR .
- '..' . DIRECTORY_SEPARATOR .
- 'datasets' . DIRECTORY_SEPARATOR .
- 'log' . DIRECTORY_SEPARATOR .
- 'friendica.log.txt';
+ $logfile = dirname(__DIR__) . '/../../datasets/log/friendica.log.txt';
$reader = new ReversedFileReader();
$this->pli = new ParsedLogIterator($reader);
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Object\Log;
+
+use Friendica\Object\Log\ParsedLogLine;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Log parser testing class
+ */
+class ParsedLogLineTest extends TestCase
+{
+ public static function do_log_line($logline, $expected_data)
+ {
+ $parsed = new ParsedLogLine(0, $logline);
+ foreach ($expected_data as $k => $v) {
+ self::assertSame($parsed->$k, $v, '"'.$k.'" does not match expectation');
+ }
+ }
+
+ /**
+ * test parsing a generic log line
+ */
+ public function testGenericLogLine()
+ {
+ self::do_log_line(
+ '2021-05-24T15:40:01Z worker [WARNING]: Spool file does does not start with "item-" {"file":".","worker_id":"560c8b6","worker_cmd":"SpoolPost"} - {"file":"SpoolPost.php","line":40,"function":"execute","uid":"fd8c37","process_id":20846}',
+ [
+ 'date' => '2021-05-24T15:40:01Z',
+ 'context' => 'worker',
+ 'level' => 'WARNING',
+ 'message' => 'Spool file does does not start with "item-"',
+ 'data' => '{"file":".","worker_id":"560c8b6","worker_cmd":"SpoolPost"}',
+ 'source' => '{"file":"SpoolPost.php","line":40,"function":"execute","uid":"fd8c37","process_id":20846}',
+ ]
+ );
+ }
+
+ /**
+ * test parsing a log line with empty data
+ */
+ public function testEmptyDataLogLine()
+ {
+ self::do_log_line(
+ '2021-05-24T15:23:58Z index [INFO]: No HTTP_SIGNATURE header [] - {"file":"HTTPSignature.php","line":476,"function":"getSigner","uid":"0a3934","process_id":14826}',
+ [
+ 'date' => '2021-05-24T15:23:58Z',
+ 'context' => 'index',
+ 'level' => 'INFO',
+ 'message' => 'No HTTP_SIGNATURE header',
+ 'data' => null,
+ 'source' => '{"file":"HTTPSignature.php","line":476,"function":"getSigner","uid":"0a3934","process_id":14826}',
+ ]
+ );
+ }
+
+ /**
+ * test parsing a log line with various " - " in it
+ */
+ public function testTrickyDashLogLine()
+ {
+ 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"} - {"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' => '{"worker_id":"ece8fc8","worker_cmd":"Cron"}',
+ 'source' => '{"file":"Worker.php","line":786,"function":"tooMuchWorkers","uid":"364d3c","process_id":20754}',
+ ]
+ );
+ }
+
+ /**
+ * 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,
+ ]
+ );
+ }
+}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Object\Log;
-
-use Friendica\Object\Log\ParsedLog;
-use PHPUnit\Framework\TestCase;
-
-/**
- * Log parser testing class
- */
-class ParsedLogTest extends TestCase
-{
- public static function do_log_line($logline, $expected_data)
- {
- $parsed = new ParsedLog(0, $logline);
- foreach ($expected_data as $k => $v) {
- self::assertSame($parsed->$k, $v, '"'.$k.'" does not match expectation');
- }
- }
-
- /**
- * test parsing a generic log line
- */
- public function testGenericLogLine()
- {
- self::do_log_line(
- '2021-05-24T15:40:01Z worker [WARNING]: Spool file does does not start with "item-" {"file":".","worker_id":"560c8b6","worker_cmd":"SpoolPost"} - {"file":"SpoolPost.php","line":40,"function":"execute","uid":"fd8c37","process_id":20846}',
- [
- 'date' => '2021-05-24T15:40:01Z',
- 'context' => 'worker',
- 'level' => 'WARNING',
- 'message' => 'Spool file does does not start with "item-"',
- 'data' => '{"file":".","worker_id":"560c8b6","worker_cmd":"SpoolPost"}',
- 'source' => '{"file":"SpoolPost.php","line":40,"function":"execute","uid":"fd8c37","process_id":20846}',
- ]
- );
- }
-
- /**
- * test parsing a log line with empty data
- */
- public function testEmptyDataLogLine()
- {
- self::do_log_line(
- '2021-05-24T15:23:58Z index [INFO]: No HTTP_SIGNATURE header [] - {"file":"HTTPSignature.php","line":476,"function":"getSigner","uid":"0a3934","process_id":14826}',
- [
- 'date' => '2021-05-24T15:23:58Z',
- 'context' => 'index',
- 'level' => 'INFO',
- 'message' => 'No HTTP_SIGNATURE header',
- 'data' => null,
- 'source' => '{"file":"HTTPSignature.php","line":476,"function":"getSigner","uid":"0a3934","process_id":14826}',
- ]
- );
- }
-
- /**
- * test parsing a log line with various " - " in it
- */
- public function testTrickyDashLogLine()
- {
- 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"} - {"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' => '{"worker_id":"ece8fc8","worker_cmd":"Cron"}',
- 'source' => '{"file":"Worker.php","line":786,"function":"tooMuchWorkers","uid":"364d3c","process_id":20754}',
- ]
- );
- }
-}
-function log_show_details(id) {
+(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('[data-id="' + id + '"]')
+ .querySelectorAll('.log-event')
.forEach(elm => {
- elm.classList.toggle('hidden')
+ 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);
+ }
+ });
});
-}
+})();
\ No newline at end of file
<div id="adminpage">
<h1>{{$title}} - {{$page}}</h1>
- <h3>{{$logname}}</h3>
+ <h2>{{$logname}}</h2>
{{if $error }}
<div id="admin-error-message-wrapper" class="alert alert-warning">
<p>{{$error nofilter}}</p>
</thead>
<tbody>
{{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->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->get_data() as $k=>$v}}
+ {{foreach $row->getData() as $k=>$v}}
<tr class="hidden" data-id="ev-{{$row->id}}">
<th>{{$k}}</th>
<td colspan="3">
</tr>
{{/foreach}}
<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}}">
<th>{{$k}}</th>
<td colspan="3">{{$v}}</td>
display: list-item;
}
+/**
+ * clickable table rows
+ */
+.table > tbody > td[role="button"] {
+ cursor: pointer;
+}
+
/**
* mobile aside
*/
$(".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;
});
- function show_details_for_element(element) {
- var $modal = $("#logdetail");
+ 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];
$("[data-next").prop("disabled", $(element).next().length == 0);
$modal.modal({})
+ element.setAttribute("aria-expanded", true);
}
function recursive_details(s, data, lev=0) {
<div id="adminpage">
<h1>{{$title}} - {{$page}}</h1>
- <h3>{{$logname}}</h3>
+ <h2>{{$logname}}</h2>
{{if $error }}
<div id="admin-error-message-wrapper" class="alert alert-warning">
<p>{{$error nofilter}}</p>
</thead>
<tbody>
{{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}}">
<td>{{$row->date}}</td>
<td class="