mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Added a console command to view the event log.
This commit is contained in:
parent
4ddc9251d1
commit
3178dcbb6c
4 changed files with 315 additions and 65 deletions
160
src/Command/ShowEventLogCommand.php
Normal file
160
src/Command/ShowEventLogCommand.php
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\Base\NamedDBElement;
|
||||||
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
use App\Services\ElementTypeNameGenerator;
|
||||||
|
use App\Services\LogSystem\LogEntryExtraFormatter;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
class ShowEventLogCommand extends Command
|
||||||
|
{
|
||||||
|
protected static $defaultName = 'app:show-logs';
|
||||||
|
protected $entityManager;
|
||||||
|
protected $translator;
|
||||||
|
protected $elementTypeNameGenerator;
|
||||||
|
protected $repo;
|
||||||
|
protected $formatter;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager,
|
||||||
|
TranslatorInterface $translator, ElementTypeNameGenerator $elementTypeNameGenerator, LogEntryExtraFormatter $formatter)
|
||||||
|
{
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
||||||
|
$this->formatter = $formatter;
|
||||||
|
|
||||||
|
$this->repo = $this->entityManager->getRepository(AbstractLogEntry::class);
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setDescription('List the last event log entries.')
|
||||||
|
->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50 )
|
||||||
|
->addOption('oldest_first', null, InputOption::VALUE_NONE,'Show older entries first.')
|
||||||
|
->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'Which page should be shown?', 1)
|
||||||
|
->addOption('onePage', null, InputOption::VALUE_NONE, 'Show only one page (dont ask to go to next).')
|
||||||
|
->addOption('showExtra', 'x', InputOption::VALUE_NONE, 'Show a column with the extra data.');
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$onePage = $input->getOption('onePage');
|
||||||
|
|
||||||
|
$desc = $input->getOption('oldest_first');
|
||||||
|
$limit = $input->getOption('count');
|
||||||
|
$page = $input->getOption('page');
|
||||||
|
$showExtra = $input->getOption('showExtra');
|
||||||
|
|
||||||
|
$total_count = $this->repo->count([]);
|
||||||
|
$max_page = ceil($total_count / $limit);
|
||||||
|
|
||||||
|
if ($page > $max_page) {
|
||||||
|
$io->error("There is no page $page! The maximum page is $max_page.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->note("There are a total of $total_count log entries in the DB.");
|
||||||
|
|
||||||
|
$continue = true;
|
||||||
|
while ($continue && $page <= $max_page) {
|
||||||
|
$this->showPage($output, $desc, $limit, $page, $max_page, $showExtra);
|
||||||
|
|
||||||
|
if ($onePage) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$continue = $io->confirm('Do you want to show the next page?');
|
||||||
|
$page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function showPage(OutputInterface $output, bool $desc, int $limit, int $page, int $max_page, bool $showExtra): void
|
||||||
|
{
|
||||||
|
$sorting = $desc ? 'ASC' : 'DESC';
|
||||||
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
|
/** @var AbstractLogEntry[] $entries */
|
||||||
|
$entries = $this->repo->getLogsOrderedByTimestamp($sorting, $limit, $offset);
|
||||||
|
|
||||||
|
$table = new Table($output);
|
||||||
|
$table->setHeaderTitle("Page $page / $max_page");
|
||||||
|
$headers = ['ID', 'Timestamp', 'Type', 'User', 'Target Type', 'Target'];
|
||||||
|
if ($showExtra) {
|
||||||
|
$headers[] = 'Extra data';
|
||||||
|
}
|
||||||
|
$table->setHeaders($headers);
|
||||||
|
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$this->addTableRow($table, $entry, $showExtra);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addTableRow(Table $table, AbstractLogEntry $entry, bool $showExtra): void
|
||||||
|
{
|
||||||
|
$target = $this->repo->getTargetElement($entry);
|
||||||
|
$target_name = "";
|
||||||
|
if ($target instanceof NamedDBElement) {
|
||||||
|
$target_name = $target->getName() . ' <info>(' . $target->getID() . ')</info>';
|
||||||
|
} elseif ($entry->getTargetID()) {
|
||||||
|
$target_name = '<info>(' . $entry->getTargetID() . ')</info>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$target_class = "";
|
||||||
|
if ($entry->getTargetClass() !== null) {
|
||||||
|
$target_class = $this->elementTypeNameGenerator->getLocalizedTypeLabel($entry->getTargetClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = [
|
||||||
|
$entry->getID(),
|
||||||
|
$entry->getTimestamp()->format('Y-m-d H:i:s'),
|
||||||
|
$entry->getType(),
|
||||||
|
$entry->getUser()->getFullName(true),
|
||||||
|
$target_class,
|
||||||
|
$target_name
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($showExtra) {
|
||||||
|
$row[] = $this->formatter->formatConsole($entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->addRow($row);
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,16 +31,19 @@ use App\Entity\LogSystem\InstockChangedLogEntry;
|
||||||
use App\Entity\LogSystem\UserLoginLogEntry;
|
use App\Entity\LogSystem\UserLoginLogEntry;
|
||||||
use App\Entity\LogSystem\UserLogoutLogEntry;
|
use App\Entity\LogSystem\UserLogoutLogEntry;
|
||||||
use App\Entity\LogSystem\UserNotAllowedLogEntry;
|
use App\Entity\LogSystem\UserNotAllowedLogEntry;
|
||||||
|
use App\Services\LogSystem\LogEntryExtraFormatter;
|
||||||
use Omines\DataTablesBundle\Column\AbstractColumn;
|
use Omines\DataTablesBundle\Column\AbstractColumn;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
class LogEntryExtraColumn extends AbstractColumn
|
class LogEntryExtraColumn extends AbstractColumn
|
||||||
{
|
{
|
||||||
protected $translator;
|
protected $translator;
|
||||||
|
protected $formatter;
|
||||||
|
|
||||||
public function __construct(TranslatorInterface $translator)
|
public function __construct(TranslatorInterface $translator, LogEntryExtraFormatter $formatter)
|
||||||
{
|
{
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
|
$this->formatter = $formatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,69 +56,6 @@ class LogEntryExtraColumn extends AbstractColumn
|
||||||
|
|
||||||
public function render($value, $context)
|
public function render($value, $context)
|
||||||
{
|
{
|
||||||
if ($context instanceof UserLoginLogEntry || $context instanceof UserLogoutLogEntry) {
|
return $this->formatter->format($context);
|
||||||
return sprintf(
|
|
||||||
"<i>%s</i>: %s",
|
|
||||||
$this->translator->trans('log.user_login.ip'),
|
|
||||||
htmlspecialchars($context->getIPAddress())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($context instanceof ExceptionLogEntry) {
|
|
||||||
return sprintf(
|
|
||||||
'<i>%s</i> %s:%d : %s',
|
|
||||||
htmlspecialchars($context->getExceptionClass()),
|
|
||||||
htmlspecialchars($context->getFile()),
|
|
||||||
$context->getLine(),
|
|
||||||
htmlspecialchars($context->getMessage())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($context instanceof DatabaseUpdatedLogEntry) {
|
|
||||||
return sprintf(
|
|
||||||
'<i>%s</i> %s <i class="fas fa-long-arrow-alt-right"></i> %s',
|
|
||||||
$this->translator->trans($context->isSuccessful() ? 'log.database_updated.success' : 'log.database_updated.failure'),
|
|
||||||
$context->getOldVersion(),
|
|
||||||
$context->getNewVersion()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) {
|
|
||||||
return sprintf(
|
|
||||||
'<i>%s</i>: %s',
|
|
||||||
$this->translator->trans('log.element_created.original_instock'),
|
|
||||||
$context->getCreationInstockValue()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($context instanceof ElementDeletedLogEntry) {
|
|
||||||
return sprintf(
|
|
||||||
'<i>%s</i>: %s',
|
|
||||||
$this->translator->trans('log.element_deleted.old_name'),
|
|
||||||
$context->getOldName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($context instanceof ElementEditedLogEntry && !empty($context->getMessage())) {
|
|
||||||
return htmlspecialchars($context->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($context instanceof InstockChangedLogEntry) {
|
|
||||||
return sprintf(
|
|
||||||
'<i>%s</i>; %s <i class="fas fa-long-arrow-alt-right"></i> %s (%s); %s: %s',
|
|
||||||
$this->translator->trans($context->isWithdrawal() ? 'log.instock_changed.withdrawal' : 'log.instock_changed.added'),
|
|
||||||
$context->getOldInstock(),
|
|
||||||
$context->getNewInstock(),
|
|
||||||
(!$context->isWithdrawal() ? '+' : '-') . $context->getDifference(true),
|
|
||||||
$this->translator->trans('log.instock_changed.comment'),
|
|
||||||
htmlspecialchars($context->getComment())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($context instanceof UserNotAllowedLogEntry) {
|
|
||||||
return htmlspecialchars($context->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -55,6 +55,18 @@ class LogEntryRepository extends EntityRepository
|
||||||
return $this->findBy(['element' => $element], ['timestamp' => $order], $limit, $offset);
|
return $this->findBy(['element' => $element], ['timestamp' => $order], $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last log entries ordered by timestamp
|
||||||
|
* @param string $order
|
||||||
|
* @param null $limit
|
||||||
|
* @param null $offset
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getLogsOrderedByTimestamp($order = 'DESC', $limit = null, $offset = null)
|
||||||
|
{
|
||||||
|
return $this->findBy([], ['timestamp' => $order], $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the target element associated with the logentry.
|
* Gets the target element associated with the logentry.
|
||||||
* @param AbstractLogEntry $logEntry
|
* @param AbstractLogEntry $logEntry
|
||||||
|
|
138
src/Services/LogSystem/LogEntryExtraFormatter.php
Normal file
138
src/Services/LogSystem/LogEntryExtraFormatter.php
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\LogSystem;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
use App\Entity\LogSystem\DatabaseUpdatedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ElementCreatedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ElementDeletedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ElementEditedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ExceptionLogEntry;
|
||||||
|
use App\Entity\LogSystem\InstockChangedLogEntry;
|
||||||
|
use App\Entity\LogSystem\UserLoginLogEntry;
|
||||||
|
use App\Entity\LogSystem\UserLogoutLogEntry;
|
||||||
|
use App\Entity\LogSystem\UserNotAllowedLogEntry;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the Extra field of a log entry in a user readible form.
|
||||||
|
* @package App\Services\LogSystem
|
||||||
|
*/
|
||||||
|
class LogEntryExtraFormatter
|
||||||
|
{
|
||||||
|
protected $translator;
|
||||||
|
|
||||||
|
public function __construct(TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->translator = $translator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an user viewable representation of the extra data in a log entry, styled for console output.
|
||||||
|
* @param AbstractLogEntry $logEntry
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function formatConsole(AbstractLogEntry $logEntry): string
|
||||||
|
{
|
||||||
|
$tmp = $this->format($logEntry);
|
||||||
|
|
||||||
|
//Just a simple tweak to make the console output more pretty.
|
||||||
|
$search = ['<i>', '</i>', '<b>', '</b>', ' <i class="fas fa-long-arrow-alt-right">'];
|
||||||
|
$replace = ['<info>', '</info>', '<error>', '</error>', '→'];
|
||||||
|
|
||||||
|
return str_replace($search, $replace, $tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a HTML formatted string containing a user viewable form of the Extra data
|
||||||
|
* @param AbstractLogEntry $context
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function format(AbstractLogEntry $context): string
|
||||||
|
{
|
||||||
|
if ($context instanceof UserLoginLogEntry || $context instanceof UserLogoutLogEntry) {
|
||||||
|
return sprintf(
|
||||||
|
"<i>%s</i>: %s",
|
||||||
|
$this->translator->trans('log.user_login.ip'),
|
||||||
|
htmlspecialchars($context->getIPAddress())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context instanceof ExceptionLogEntry) {
|
||||||
|
return sprintf(
|
||||||
|
'<i>%s</i> %s:%d : %s',
|
||||||
|
htmlspecialchars($context->getExceptionClass()),
|
||||||
|
htmlspecialchars($context->getFile()),
|
||||||
|
$context->getLine(),
|
||||||
|
htmlspecialchars($context->getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context instanceof DatabaseUpdatedLogEntry) {
|
||||||
|
return sprintf(
|
||||||
|
'<i>%s</i> %s <i class="fas fa-long-arrow-alt-right"></i> %s',
|
||||||
|
$this->translator->trans($context->isSuccessful() ? 'log.database_updated.success' : 'log.database_updated.failure'),
|
||||||
|
$context->getOldVersion(),
|
||||||
|
$context->getNewVersion()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) {
|
||||||
|
return sprintf(
|
||||||
|
'<i>%s</i>: %s',
|
||||||
|
$this->translator->trans('log.element_created.original_instock'),
|
||||||
|
$context->getCreationInstockValue()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context instanceof ElementDeletedLogEntry) {
|
||||||
|
return sprintf(
|
||||||
|
'<i>%s</i>: %s',
|
||||||
|
$this->translator->trans('log.element_deleted.old_name'),
|
||||||
|
$context->getOldName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context instanceof ElementEditedLogEntry && !empty($context->getMessage())) {
|
||||||
|
return htmlspecialchars($context->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context instanceof InstockChangedLogEntry) {
|
||||||
|
return sprintf(
|
||||||
|
'<i>%s</i>; %s <i class="fas fa-long-arrow-alt-right"></i> %s (%s); %s: %s',
|
||||||
|
$this->translator->trans($context->isWithdrawal() ? 'log.instock_changed.withdrawal' : 'log.instock_changed.added'),
|
||||||
|
$context->getOldInstock(),
|
||||||
|
$context->getNewInstock(),
|
||||||
|
(!$context->isWithdrawal() ? '+' : '-') . $context->getDifference(true),
|
||||||
|
$this->translator->trans('log.instock_changed.comment'),
|
||||||
|
htmlspecialchars($context->getComment())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context instanceof UserNotAllowedLogEntry) {
|
||||||
|
return htmlspecialchars($context->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue