Implement a filter for Log Table (part 1)

This commit is contained in:
Jan Böhmer 2022-09-11 18:45:31 +02:00
parent 017b0f717e
commit c7f5c23374
12 changed files with 444 additions and 23 deletions

View file

@ -42,6 +42,7 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use App\DataTables\Filters\LogFilter;
use App\DataTables\LogDataTable; use App\DataTables\LogDataTable;
use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractDBElement;
use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\LogSystem\AbstractLogEntry;
@ -49,6 +50,7 @@ use App\Entity\LogSystem\CollectionElementDeleted;
use App\Entity\LogSystem\ElementCreatedLogEntry; use App\Entity\LogSystem\ElementCreatedLogEntry;
use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementDeletedLogEntry;
use App\Entity\LogSystem\ElementEditedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry;
use App\Form\Filters\LogFilterType;
use App\Services\LogSystem\EventUndoHelper; use App\Services\LogSystem\EventUndoHelper;
use App\Services\LogSystem\TimeTravel; use App\Services\LogSystem\TimeTravel;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -86,7 +88,17 @@ class LogController extends AbstractController
{ {
$this->denyAccessUnlessGranted('@system.show_logs'); $this->denyAccessUnlessGranted('@system.show_logs');
$table = $dataTable->createFromType(LogDataTable::class) $formRequest = clone $request;
$formRequest->setMethod('GET');
$filter = new LogFilter();
$filterForm = $this->createForm(LogFilterType::class, $filter, ['method' => 'GET']);
$filterForm->handleRequest($formRequest);
$table = $dataTable->createFromType(LogDataTable::class, [
'filter' => $filter,
])
->handleRequest($request); ->handleRequest($request);
if ($table->isCallback()) { if ($table->isCallback()) {
@ -95,6 +107,7 @@ class LogController extends AbstractController
return $this->render('LogSystem/log_list.html.twig', [ return $this->render('LogSystem/log_list.html.twig', [
'datatable' => $table, 'datatable' => $table,
'filterForm' => $filterForm->createView(),
]); ]);
} }

View file

@ -6,6 +6,7 @@ use App\DataTables\Filters\Constraints\BooleanConstraint;
use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint;
use App\DataTables\Filters\Constraints\EntityConstraint; use App\DataTables\Filters\Constraints\EntityConstraint;
use App\DataTables\Filters\Constraints\InstanceOfConstraint; use App\DataTables\Filters\Constraints\InstanceOfConstraint;
use App\DataTables\Filters\Constraints\IntConstraint;
use App\DataTables\Filters\Constraints\NumberConstraint; use App\DataTables\Filters\Constraints\NumberConstraint;
use App\DataTables\Filters\Constraints\TextConstraint; use App\DataTables\Filters\Constraints\TextConstraint;
use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\AttachmentType;
@ -40,7 +41,7 @@ class AttachmentFilter implements FilterInterface
public function __construct(NodesListBuilder $nodesListBuilder) public function __construct(NodesListBuilder $nodesListBuilder)
{ {
$this->dbId = new NumberConstraint('attachment.id'); $this->dbId = new IntConstraint('attachment.id');
$this->name = new TextConstraint('attachment.name'); $this->name = new TextConstraint('attachment.name');
$this->targetType = new InstanceOfConstraint('attachment'); $this->targetType = new InstanceOfConstraint('attachment');
$this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, 'attachment.attachment_type'); $this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, 'attachment.attachment_type');

View file

@ -9,7 +9,7 @@ class ChoiceConstraint extends AbstractConstraint
public const ALLOWED_OPERATOR_VALUES = ['ANY', 'NONE']; public const ALLOWED_OPERATOR_VALUES = ['ANY', 'NONE'];
/** /**
* @var string[] The values to compare to * @var string[]|int[] The values to compare to
*/ */
protected $value; protected $value;
@ -19,7 +19,7 @@ class ChoiceConstraint extends AbstractConstraint
protected $operator; protected $operator;
/** /**
* @return string[] * @return string[]|int[]
*/ */
public function getValue(): array public function getValue(): array
{ {
@ -27,7 +27,7 @@ class ChoiceConstraint extends AbstractConstraint
} }
/** /**
* @param string[] $value * @param string[]|int[] $value
* @return ChoiceConstraint * @return ChoiceConstraint
*/ */
public function setValue(array $value): ChoiceConstraint public function setValue(array $value): ChoiceConstraint

View file

@ -36,18 +36,22 @@ class EntityConstraint extends AbstractConstraint
protected $value; protected $value;
/** /**
* @param NodesListBuilder $nodesListBuilder * @param NodesListBuilder|null $nodesListBuilder
* @param class-string<T> $class * @param class-string $class
* @param string $property * @param string $property
* @param string|null $identifier * @param string|null $identifier
* @param $value * @param null $value
* @param string $operator * @param string $operator
*/ */
public function __construct(NodesListBuilder $nodesListBuilder, string $class, string $property, string $identifier = null, $value = null, string $operator = '') public function __construct(?NodesListBuilder $nodesListBuilder, string $class, string $property, string $identifier = null, $value = null, string $operator = '')
{ {
$this->nodesListBuilder = $nodesListBuilder; $this->nodesListBuilder = $nodesListBuilder;
$this->class = $class; $this->class = $class;
if ($nodesListBuilder === null && $this->isStructural()) {
throw new \InvalidArgumentException('NodesListBuilder must be provided for structural entities');
}
parent::__construct($property, $identifier); parent::__construct($property, $identifier);
$this->value = $value; $this->value = $value;
$this->operator = $operator; $this->operator = $operator;

View file

@ -0,0 +1,110 @@
<?php
namespace App\DataTables\Filters;
use App\DataTables\Filters\Constraints\ChoiceConstraint;
use App\DataTables\Filters\Constraints\DateTimeConstraint;
use App\DataTables\Filters\Constraints\EntityConstraint;
use App\DataTables\Filters\Constraints\InstanceOfConstraint;
use App\DataTables\Filters\Constraints\IntConstraint;
use App\DataTables\Filters\Constraints\NumberConstraint;
use App\Entity\UserSystem\User;
use Doctrine\ORM\QueryBuilder;
class LogFilter implements FilterInterface
{
use CompoundFilterTrait;
/** @var DateTimeConstraint */
protected $timestamp;
/** @var IntConstraint */
protected $dbId;
/** @var ChoiceConstraint */
protected $level;
/** @var InstanceOfConstraint */
protected $eventType;
/** @var ChoiceConstraint */
protected $targetType;
/** @var IntConstraint */
protected $targetId;
/** @var EntityConstraint */
protected $user;
public function __construct()
{
$this->timestamp = new DateTimeConstraint('log.timestamp');
$this->dbId = new IntConstraint('log.id');
$this->level = new ChoiceConstraint('log.level');
$this->eventType = new InstanceOfConstraint('log');
$this->user = new EntityConstraint(null, User::class, 'log.user');
$this->targetType = new ChoiceConstraint('log.target_type');
$this->targetId = new IntConstraint('log.target_id');
}
public function apply(QueryBuilder $queryBuilder): void
{
$this->applyAllChildFilters($queryBuilder);
}
/**
* @return DateTimeConstraint
*/
public function getTimestamp(): DateTimeConstraint
{
return $this->timestamp;
}
/**
* @return IntConstraint|NumberConstraint
*/
public function getDbId()
{
return $this->dbId;
}
/**
* @return ChoiceConstraint
*/
public function getLevel(): ChoiceConstraint
{
return $this->level;
}
/**
* @return InstanceOfConstraint
*/
public function getEventType(): InstanceOfConstraint
{
return $this->eventType;
}
/**
* @return ChoiceConstraint
*/
public function getTargetType(): ChoiceConstraint
{
return $this->targetType;
}
/**
* @return IntConstraint
*/
public function getTargetId(): IntConstraint
{
return $this->targetId;
}
public function getUser(): EntityConstraint
{
return $this->user;
}
}

View file

@ -47,6 +47,8 @@ use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\LogEntryExtraColumn; use App\DataTables\Column\LogEntryExtraColumn;
use App\DataTables\Column\LogEntryTargetColumn; use App\DataTables\Column\LogEntryTargetColumn;
use App\DataTables\Column\RevertLogColumn; use App\DataTables\Column\RevertLogColumn;
use App\DataTables\Filters\AttachmentFilter;
use App\DataTables\Filters\LogFilter;
use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractDBElement;
use App\Entity\Contracts\TimeTravelInterface; use App\Entity\Contracts\TimeTravelInterface;
use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\LogSystem\AbstractLogEntry;
@ -61,6 +63,7 @@ use App\Services\ElementTypeNameGenerator;
use App\Services\EntityURLGenerator; use App\Services\EntityURLGenerator;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter; use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTable;
@ -97,10 +100,12 @@ class LogDataTable implements DataTableTypeInterface
$optionsResolver->setDefaults([ $optionsResolver->setDefaults([
'mode' => 'system_log', 'mode' => 'system_log',
'filter_elements' => [], 'filter_elements' => [],
'filter' => null,
]); ]);
$optionsResolver->setAllowedTypes('filter_elements', ['array', 'object']); $optionsResolver->setAllowedTypes('filter_elements', ['array', 'object']);
$optionsResolver->setAllowedTypes('mode', 'string'); $optionsResolver->setAllowedTypes('mode', 'string');
$optionsResolver->setAllowedTypes('filter', LogFilter::class);
$optionsResolver->setNormalizer('filter_elements', static function (Options $options, $value) { $optionsResolver->setNormalizer('filter_elements', static function (Options $options, $value) {
if (!is_array($value)) { if (!is_array($value)) {
@ -192,8 +197,8 @@ class LogDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('log.level'), 'label' => $this->translator->trans('log.level'),
'visible' => 'system_log' === $options['mode'], 'visible' => 'system_log' === $options['mode'],
'propertyPath' => 'levelString', 'propertyPath' => 'levelString',
'render' => static function (string $value, AbstractLogEntry $context) { 'render' => function (string $value, AbstractLogEntry $context) {
return $value; return $this->translator->trans('log.level.'.$value);
}, },
]); ]);
@ -271,9 +276,24 @@ class LogDataTable implements DataTableTypeInterface
'query' => function (QueryBuilder $builder) use ($options): void { 'query' => function (QueryBuilder $builder) use ($options): void {
$this->getQuery($builder, $options); $this->getQuery($builder, $options);
}, },
'criteria' => [
function (QueryBuilder $builder) use ($options): void {
$this->buildCriteria($builder, $options);
},
new SearchCriteriaProvider(),
],
]); ]);
} }
private function buildCriteria(QueryBuilder $builder, array $options): void
{
if (!empty($options['filter'])) {
$filter = $options['filter'];
$filter->apply($builder);
}
}
protected function getQuery(QueryBuilder $builder, array $options): void protected function getQuery(QueryBuilder $builder, array $options): void
{ {
$builder->distinct()->select('log') $builder->distinct()->select('log')
@ -281,6 +301,7 @@ class LogDataTable implements DataTableTypeInterface
->from(AbstractLogEntry::class, 'log') ->from(AbstractLogEntry::class, 'log')
->leftJoin('log.user', 'user'); ->leftJoin('log.user', 'user');
/* Do this here as we don't want to show up the global count of all log entries in the footer line, with these modes */
if ('last_activity' === $options['mode']) { if ('last_activity' === $options['mode']) {
$builder->where('log INSTANCE OF '.ElementCreatedLogEntry::class) $builder->where('log INSTANCE OF '.ElementCreatedLogEntry::class)
->orWhere('log INSTANCE OF '.ElementDeletedLogEntry::class) ->orWhere('log INSTANCE OF '.ElementDeletedLogEntry::class)

View file

@ -447,6 +447,6 @@ abstract class AbstractLogEntry extends AbstractDBElement
} }
} }
throw new InvalidArgumentException('No target ID for this class is existing!'); throw new InvalidArgumentException('No target ID for this class is existing! (Class: '.$class.')');
} }
} }

View file

@ -23,7 +23,7 @@ use App\Form\Filters\Constraints\BooleanConstraintType;
use App\Form\Filters\Constraints\DateTimeConstraintType; use App\Form\Filters\Constraints\DateTimeConstraintType;
use App\Form\Filters\Constraints\InstanceOfConstraintType; use App\Form\Filters\Constraints\InstanceOfConstraintType;
use App\Form\Filters\Constraints\NumberConstraintType; use App\Form\Filters\Constraints\NumberConstraintType;
use App\Form\Filters\Constraints\StructuralEntityConstraintType; use App\Form\Filters\Constraints\UserEntityConstraintType;
use App\Form\Filters\Constraints\TextConstraintType; use App\Form\Filters\Constraints\TextConstraintType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ResetType; use Symfony\Component\Form\Extension\Core\Type\ResetType;
@ -73,7 +73,7 @@ class AttachmentFilterType extends AbstractType
] ]
]); ]);
$builder->add('attachmentType', StructuralEntityConstraintType::class, [ $builder->add('attachmentType', UserEntityConstraintType::class, [
'label' => 'attachment.attachment_type', 'label' => 'attachment.attachment_type',
'entity_class' => AttachmentType::class 'entity_class' => AttachmentType::class
]); ]);

View file

@ -0,0 +1,153 @@
<?php
namespace App\Form\Filters;
use App\DataTables\Filters\LogFilter;
use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Devices\Device;
use App\Entity\Devices\DevicePart;
use App\Entity\LabelSystem\LabelProfile;
use App\Entity\LogSystem\AbstractLogEntry;
use App\Entity\LogSystem\CollectionElementDeleted;
use App\Entity\LogSystem\DatabaseUpdatedLogEntry;
use App\Entity\LogSystem\ElementCreatedLogEntry;
use App\Entity\LogSystem\ElementDeletedLogEntry;
use App\Entity\LogSystem\ElementEditedLogEntry;
use App\Entity\LogSystem\InstockChangedLogEntry;
use App\Entity\LogSystem\SecurityEventLogEntry;
use App\Entity\LogSystem\UserLoginLogEntry;
use App\Entity\LogSystem\UserLogoutLogEntry;
use App\Entity\LogSystem\UserNotAllowedLogEntry;
use App\Entity\Parameters\AbstractParameter;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Currency;
use App\Entity\PriceInformations\Orderdetail;
use App\Entity\PriceInformations\Pricedetail;
use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\User;
use App\Form\Filters\Constraints\ChoiceConstraintType;
use App\Form\Filters\Constraints\DateTimeConstraintType;
use App\Form\Filters\Constraints\InstanceOfConstraintType;
use App\Form\Filters\Constraints\NumberConstraintType;
use App\Form\Filters\Constraints\StructuralEntityConstraintType;
use App\Form\Filters\Constraints\UserEntityConstraintType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ResetType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class LogFilterType extends AbstractType
{
protected const LEVEL_CHOICES = [
'log.level.debug' => AbstractLogEntry::LEVEL_DEBUG,
'log.level.info' => AbstractLogEntry::LEVEL_INFO,
'log.level.notice' => AbstractLogEntry::LEVEL_NOTICE,
'log.level.warning' => AbstractLogEntry::LEVEL_WARNING,
'log.level.error' => AbstractLogEntry::LEVEL_ERROR,
'log.level.critical' => AbstractLogEntry::LEVEL_CRITICAL,
'log.level.alert' => AbstractLogEntry::LEVEL_ALERT,
'log.level.emergency' => AbstractLogEntry::LEVEL_EMERGENCY,
];
protected const TARGET_TYPE_CHOICES = [
'log.type.collection_element_deleted' => CollectionElementDeleted::class,
'log.type.database_updated' => DatabaseUpdatedLogEntry::class,
'log.type.element_created' => ElementCreatedLogEntry::class,
'log.type.element_deleted' => ElementDeletedLogEntry::class,
'log.type.element_edited' => ElementEditedLogEntry::class,
'log.type.security' => SecurityEventLogEntry::class,
'log.type.user_login' => UserLoginLogEntry::class,
'log.type.user_logout' => UserLogoutLogEntry::class,
'log.type.user_not_allowed' => UserNotAllowedLogEntry::class,
//Legacy entries
'log.type.instock_changed' => InstockChangedLogEntry::class,
];
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'compound' => true,
'data_class' => LogFilter::class,
'csrf_protection' => false,
]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('dbId', NumberConstraintType::class, [
'label' => 'part.filter.dbId',
'min' => 1,
'step' => 1,
]);
$builder->add('timestamp', DateTimeConstraintType::class, [
'label' => 'log.timestamp',
]);
$builder->add('level', ChoiceConstraintType::class, [
'label' => 'log.level',
'choices' => self::LEVEL_CHOICES,
]);
$builder->add('eventType', InstanceOfConstraintType::class, [
'label' => 'log.type',
'choices' => self::TARGET_TYPE_CHOICES
]);
$builder->add('user', StructuralEntityConstraintType::class, [
'label' => 'log.user',
'entity_class' => User::class,
]);
$builder->add('targetType', ChoiceConstraintType::class, [
'label' => 'log.target_type',
'choices' => [
'user.label' => AbstractLogEntry::targetTypeClassToID(User::class),
'attachment.label' => AbstractLogEntry::targetTypeClassToID(Attachment::class),
'attachment_type.label' => AbstractLogEntry::targetTypeClassToID(AttachmentType::class),
'category.label' => AbstractLogEntry::targetTypeClassToID(Category::class),
'device.label' => AbstractLogEntry::targetTypeClassToID(Device::class),
'device_part.label' => AbstractLogEntry::targetTypeClassToID(DevicePart::class),
'footprint.label' => AbstractLogEntry::targetTypeClassToID(Footprint::class),
'group.label' => AbstractLogEntry::targetTypeClassToID(Group::class),
'manufacturer.label' => AbstractLogEntry::targetTypeClassToID(Manufacturer::class),
'part.label' => AbstractLogEntry::targetTypeClassToID(Part::class),
'storelocation.label' => AbstractLogEntry::targetTypeClassToID(Storelocation::class),
'supplier.label' => AbstractLogEntry::targetTypeClassToID(Supplier::class),
'part_lot.label' => AbstractLogEntry::targetTypeClassToID(PartLot::class),
'currency.label' => AbstractLogEntry::targetTypeClassToID(Currency::class),
'orderdetail.label' => AbstractLogEntry::targetTypeClassToID(Orderdetail::class),
'pricedetail.label' => AbstractLogEntry::targetTypeClassToID(Pricedetail::class),
'measurement_unit.label' => AbstractLogEntry::targetTypeClassToID(MeasurementUnit::class),
'parameter.label' => AbstractLogEntry::targetTypeClassToID(AbstractParameter::class),
'label_profile.label' => AbstractLogEntry::targetTypeClassToID(LabelProfile::class),
]
]);
$builder->add('targetId', NumberConstraintType::class, [
'label' => 'log.target_id',
'min' => 1,
'step' => 1,
]);
$builder->add('submit', SubmitType::class, [
'label' => 'filter.submit',
]);
$builder->add('discard', ResetType::class, [
'label' => 'filter.discard',
]);
}
}

View file

@ -15,7 +15,7 @@ use App\Form\Filters\Constraints\ChoiceConstraintType;
use App\Form\Filters\Constraints\DateTimeConstraintType; use App\Form\Filters\Constraints\DateTimeConstraintType;
use App\Form\Filters\Constraints\NumberConstraintType; use App\Form\Filters\Constraints\NumberConstraintType;
use App\Form\Filters\Constraints\ParameterConstraintType; use App\Form\Filters\Constraints\ParameterConstraintType;
use App\Form\Filters\Constraints\StructuralEntityConstraintType; use App\Form\Filters\Constraints\UserEntityConstraintType;
use App\Form\Filters\Constraints\TagsConstraintType; use App\Form\Filters\Constraints\TagsConstraintType;
use App\Form\Filters\Constraints\TextConstraintType; use App\Form\Filters\Constraints\TextConstraintType;
use Svg\Tag\Text; use Svg\Tag\Text;
@ -54,12 +54,12 @@ class PartFilterType extends AbstractType
'label' => 'part.edit.description', 'label' => 'part.edit.description',
]); ]);
$builder->add('category', StructuralEntityConstraintType::class, [ $builder->add('category', UserEntityConstraintType::class, [
'label' => 'part.edit.category', 'label' => 'part.edit.category',
'entity_class' => Category::class 'entity_class' => Category::class
]); ]);
$builder->add('footprint', StructuralEntityConstraintType::class, [ $builder->add('footprint', UserEntityConstraintType::class, [
'label' => 'part.edit.footprint', 'label' => 'part.edit.footprint',
'entity_class' => Footprint::class 'entity_class' => Footprint::class
]); ]);
@ -96,7 +96,7 @@ class PartFilterType extends AbstractType
'min' => 0, 'min' => 0,
]); ]);
$builder->add('measurementUnit', StructuralEntityConstraintType::class, [ $builder->add('measurementUnit', UserEntityConstraintType::class, [
'label' => 'part.edit.partUnit', 'label' => 'part.edit.partUnit',
'entity_class' => MeasurementUnit::class 'entity_class' => MeasurementUnit::class
]); ]);
@ -114,7 +114,7 @@ class PartFilterType extends AbstractType
* Manufacturer tab * Manufacturer tab
*/ */
$builder->add('manufacturer', StructuralEntityConstraintType::class, [ $builder->add('manufacturer', UserEntityConstraintType::class, [
'label' => 'part.edit.manufacturer.label', 'label' => 'part.edit.manufacturer.label',
'entity_class' => Manufacturer::class 'entity_class' => Manufacturer::class
]); ]);
@ -145,7 +145,7 @@ class PartFilterType extends AbstractType
* Purchasee informations * Purchasee informations
*/ */
$builder->add('supplier', StructuralEntityConstraintType::class, [ $builder->add('supplier', UserEntityConstraintType::class, [
'label' => 'supplier.label', 'label' => 'supplier.label',
'entity_class' => Manufacturer::class 'entity_class' => Manufacturer::class
]); ]);
@ -163,7 +163,7 @@ class PartFilterType extends AbstractType
/* /*
* Stocks tabs * Stocks tabs
*/ */
$builder->add('storelocation', StructuralEntityConstraintType::class, [ $builder->add('storelocation', UserEntityConstraintType::class, [
'label' => 'storelocation.label', 'label' => 'storelocation.label',
'entity_class' => Storelocation::class 'entity_class' => Storelocation::class
]); ]);
@ -210,7 +210,7 @@ class PartFilterType extends AbstractType
'min' => 0, 'min' => 0,
]); ]);
$builder->add('attachmentType', StructuralEntityConstraintType::class, [ $builder->add('attachmentType', UserEntityConstraintType::class, [
'label' => 'attachment.attachment_type', 'label' => 'attachment.attachment_type',
'entity_class' => AttachmentType::class 'entity_class' => AttachmentType::class
]); ]);

View file

@ -3,5 +3,52 @@
{% block title %}{% trans %}log.list.title{% endtrans %}{% endblock %} {% block title %}{% trans %}log.list.title{% endtrans %}{% endblock %}
{% block content %} {% block content %}
<div class="accordion mb-3" id="listAccordion">
<div class="accordion-item">
<div class="accordion-header">
<button class="accordion-button collapsed py-2" data-bs-toggle="collapse" data-bs-target="#searchInfo" disabled>
<i class="fa-solid fa-binoculars fa-fw"></i>
{% trans %}log.list.title{% endtrans %}
</button>
</div>
<div id="searchInfo" class="accordion-collapse collapse" data-bs-parent="#listAccordion">
<div class="accordion-body">
</div>
</div>
</div>
<div class="accordion-item">
<div class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#filterFormCollapse" aria-expanded="false" aria-controls="filterFormCollapse"><i class="fa-solid fa-filter fa-fw"></i> {% trans %}filter.title{% endtrans %}</button>
</div>
<div id="filterFormCollapse" class="accordion-collapse collapse" data-bs-parent="#listAccordion">
<div class="accordion-body">
{{ form_start(filterForm, {"attr": {"data-controller": "helpers--form-cleanup", "data-action": "helpers--form-cleanup#submit"}}) }}
{{ form_row(filterForm.dbId) }}
{{ form_row(filterForm.timestamp) }}
{{ form_row(filterForm.eventType) }}
{{ form_row(filterForm.level) }}
{{ form_row(filterForm.targetType) }}
{{ form_row(filterForm.targetId) }}
{{ form_row(filterForm.submit) }}
{{ form_row(filterForm.discard) }}
<div class="row mb-3">
<div class="col-sm-9 offset-sm-3">
<button type="button" class="btn btn-danger" {{ stimulus_action('helpers/form_cleanup', 'clearAll') }}>{% trans %}filter.clear_filters{% endtrans %}</button>
</div>
</div>
{{ form_end(filterForm) }}
</div>
</div>
</div>
</div>
{% include "LogSystem/_log_table.html.twig" %} {% include "LogSystem/_log_table.html.twig" %}
{% endblock %} {% endblock %}

View file

@ -9699,5 +9699,77 @@ Element 3</target>
<target>Associated element type</target> <target>Associated element type</target>
</segment> </segment>
</unit> </unit>
<unit id="nUZamS5" name="log.level.debug">
<segment>
<source>log.level.debug</source>
<target>Debug</target>
</segment>
</unit>
<unit id="ZPxm2Ee" name="log.level.info">
<segment>
<source>log.level.info</source>
<target>Info</target>
</segment>
</unit>
<unit id="NVXjDhG" name="log.level.notice">
<segment>
<source>log.level.notice</source>
<target>Notice</target>
</segment>
</unit>
<unit id="9ObjWuR" name="log.level.warning">
<segment>
<source>log.level.warning</source>
<target>Warning</target>
</segment>
</unit>
<unit id="WxMwuLP" name="log.level.error">
<segment>
<source>log.level.error</source>
<target>Error</target>
</segment>
</unit>
<unit id="idch78J" name="log.level.critical">
<segment>
<source>log.level.critical</source>
<target>Critical</target>
</segment>
</unit>
<unit id="jehoQTd" name="log.level.alert">
<segment>
<source>log.level.alert</source>
<target>Alert</target>
</segment>
</unit>
<unit id="XvKEDM0" name="log.level.emergency">
<segment>
<source>log.level.emergency</source>
<target>Emergency</target>
</segment>
</unit>
<unit id="sEvRe10" name="log.type.security">
<segment>
<source>log.type.security</source>
<target>Security related event</target>
</segment>
</unit>
<unit id="gJDmvym" name="log.type.instock_changed">
<segment>
<source>log.type.instock_changed</source>
<target>[LEGACY] Instock changed</target>
</segment>
</unit>
<unit id="4Od2HUk" name="device_part.label">
<segment>
<source>device_part.label</source>
<target>Device part</target>
</segment>
</unit>
<unit id="lJZCHHM" name="log.target_id">
<segment>
<source>log.target_id</source>
<target>Target element ID</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>