All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.imunity.console.views.maintenance.audit_log.AuditLogView Maven / Gradle / Ivy

There is a newer version: 4.0.3
Show newest version
/*
 * Copyright (c) 2021 Bixbit - Krzysztof Benedyczak. All rights reserved.
 * See LICENCE.txt file for licensing information.
 */

package io.imunity.console.views.maintenance.audit_log;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.ColumnTextAlign;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridSortOrder;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.dataview.GridListDataView;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.provider.SortDirection;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.Route;
import io.imunity.console.ConsoleMenu;
import io.imunity.console.views.ConsoleViewComponent;
import io.imunity.vaadin.elements.*;
import jakarta.annotation.security.PermitAll;
import org.apache.logging.log4j.Logger;
import pl.edu.icm.unity.base.audit.AuditEventAction;
import pl.edu.icm.unity.base.audit.AuditEventType;
import pl.edu.icm.unity.base.entity.Entity;
import pl.edu.icm.unity.base.entity.EntityParam;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.group.GroupMembership;
import pl.edu.icm.unity.base.message.MessageSource;
import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.AuditEventManagement;
import pl.edu.icm.unity.engine.api.EntityManagement;
import pl.edu.icm.unity.engine.api.identity.UnknownIdentityException;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

import static com.vaadin.flow.component.icon.VaadinIcon.SEARCH;
import static io.imunity.vaadin.elements.CSSVars.BIG_MARGIN;
import static java.lang.String.join;
import static java.util.Objects.nonNull;

@PermitAll
@Breadcrumb(key = "WebConsoleMenu.maintenance.auditLog", parent = "WebConsoleMenu.maintenance")
@Route(value = "/audit-log", layout = ConsoleMenu.class)
public class AuditLogView extends ConsoleViewComponent
{
	private final static Logger log = Log.getLogger(Log.U_SERVER_WEB, AuditLogView.class);
	private final static int DEFAULT_LIMIT = 10000;
	private final static String TIMESTAMP = "timestamp";
	private final static DatePicker.DatePickerI18n DATETIME_FORMAT_SHORT_PATTERN = new DatePicker.DatePickerI18n();
	static
	{
		DATETIME_FORMAT_SHORT_PATTERN.setDateFormat("yyyy-MM-dd");
	}

	private final MessageSource msg;
	private final AuditEventManagement eventManagement;
	private final EntityManagement entityMan;
	private final EntityManagement idsMan;
	private final IdentityFormatter identityFormatter;
	private final NotificationPresenter notificationPresenter;

	private final H4 titleLabel = new H4();
	private final H4 diabledMsg = new H4();
	private final MultiSelectComboBox typeFilter = new MultiSelectComboBox<>();
	private final MultiSelectComboBox actionFilter = new MultiSelectComboBox<>();
	private final MultiSelectComboBox tagsFilter = new MultiSelectComboBox<>();
	private final ComboBox limitFilter = new ComboBox<>();
	private final TextField searchFilter = new TextField();
	private final DateTimePicker fromFilter = new DateTimePicker();
	private final DateTimePicker untilFilter = new DateTimePicker();

	private Grid auditEventsGrid;

	AuditLogView(MessageSource msg, AuditEventManagement eventManagement, EntityManagement entityMan,
				 EntityManagement idsMan, NotificationPresenter notificationPresenter, IdentityFormatter identityFormatter)
	{
		this.msg = msg;
		this.eventManagement = eventManagement;
		this.entityMan = entityMan;
		this.idsMan = idsMan;
		this.identityFormatter = identityFormatter;
		this.notificationPresenter = notificationPresenter;
		enter();
	}

	public void enter()
	{
		auditEventsGrid = new Grid<>();
		auditEventsGrid.addThemeVariants(GridVariant.LUMO_NO_BORDER);

		initGridColumns();
		VerticalLayout filterLayout = createFiltersLayout();

		titleLabel.addClassName("u-AuditEventsGridTitle");

		diabledMsg.addClassName("u-AuditEventsWarnMsg");
		diabledMsg.setText(msg.getMessage("AuditEventsView.disabledMsg"));
		diabledMsg.setVisible(!eventManagement.isPublisherEnabled());

		getContent().add(new VerticalLayout(diabledMsg, titleLabel, filterLayout, auditEventsGrid));
	}

	private void initGridColumns()
	{
		ColumnToggleMenu columnToggleMenu = new ColumnToggleMenu();

		Grid.Column timestampColumn = auditEventsGrid.addComponentColumn(this::createTimestampWithDetailsArrow)
				.setHeader(getTimestampHeaderLabel())
				.setResizable(true)
				.setSortable(true)
				.setAutoWidth(true);
		timestampColumn.setId(TIMESTAMP);

		Grid.Column typeColumn = auditEventsGrid.addColumn(ae -> ae.getEvent().getType().toString())
				.setHeader(msg.getMessage("AuditEventsView.type"))
				.setResizable(true)
				.setSortable(true)
				.setAutoWidth(true);
		typeColumn.setId("type");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.type"), typeColumn);

		Grid.Column actionColumn = auditEventsGrid.addColumn(ae -> ae.getEvent().getAction().toString())
				.setHeader(msg.getMessage("AuditEventsView.action"))
				.setResizable(true)
				.setSortable(true)
				.setAutoWidth(true);
		actionColumn.setId("action");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.action"), actionColumn);

		Grid.Column nameColumn = auditEventsGrid.addColumn(AuditEventEntry::getName)
				.setHeader(msg.getMessage("AuditEventsView.name"))
				.setResizable(true)
				.setSortable(true)
				.setAutoWidth(true);
		nameColumn.setId("name");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.name"), nameColumn);

		Grid.Column tagsColumn = auditEventsGrid.addColumn(AuditEventEntry::formatTags)
				.setHeader(msg.getMessage("AuditEventsView.tags"))
				.setResizable(true)
				.setSortable(true)
				.setAutoWidth(true);
		tagsColumn.setId("tags");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.tags"), tagsColumn);

		Grid.Column subjectIdColumn = auditEventsGrid.addColumn(AuditEventEntry::getSubjectId)
				.setHeader(msg.getMessage("AuditEventsView.subjectId"))
				.setResizable(true)
				.setAutoWidth(true);
		subjectIdColumn.setVisible(false);
		subjectIdColumn.setId("subjectId");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.subjectId"), subjectIdColumn);

		Grid.Column subjectNameColumn = auditEventsGrid.addColumn(AuditEventEntry::getSubjectName)
				.setHeader(msg.getMessage("AuditEventsView.subjectName"))
				.setResizable(true)
				.setAutoWidth(true);
		subjectNameColumn.setVisible(false);
		subjectNameColumn.setId("subjectName");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.subjectName"), subjectNameColumn);

		Grid.Column subjectEmailColumn = auditEventsGrid.addColumn(AuditEventEntry::getSubjectEmail)
				.setHeader(msg.getMessage("AuditEventsView.subjectEmail"))
				.setResizable(true)
				.setAutoWidth(true);
		subjectEmailColumn.setVisible(false);
		subjectEmailColumn.setId("subjectEmail");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.subjectEmail"), subjectEmailColumn);

		Grid.Column initiatorIdColumn = auditEventsGrid.addColumn(AuditEventEntry::getInitiatorId)
				.setHeader(msg.getMessage("AuditEventsView.initiatorId"))
				.setResizable(true)
				.setAutoWidth(true);
		initiatorIdColumn.setVisible(false);
		initiatorIdColumn.setId("initiatorId");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.initiatorId"), initiatorIdColumn);

		Grid.Column initiatorNameColumn = auditEventsGrid.addColumn(AuditEventEntry::getInitiatorName)
				.setHeader(msg.getMessage("AuditEventsView.initiatorName"))
				.setResizable(true)
				.setAutoWidth(true);
		initiatorNameColumn.setVisible(false);
		initiatorNameColumn.setId("initiatorName");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.initiatorName"), initiatorNameColumn);

		Grid.Column initiatorEmailColumn = auditEventsGrid.addColumn(AuditEventEntry::getInitiatorEmail)
				.setHeader(msg.getMessage("AuditEventsView.initiatorEmail"))
				.setResizable(true)
				.setAutoWidth(true);
		initiatorEmailColumn.setVisible(false);
		initiatorEmailColumn.setId("initiatorEmail");
		columnToggleMenu.addColumn(msg.getMessage("AuditEventsView.initiatorEmail"), initiatorEmailColumn);

		auditEventsGrid.addComponentColumn(this::createActionsButton)
				.setHeader(getActions(columnToggleMenu))
				.setTextAlign(ColumnTextAlign.END);

		auditEventsGrid.setItemDetailsRenderer(new ComponentRenderer<>(this::getDetailsComponent));
		auditEventsGrid.setDetailsVisibleOnClick(false);
		auditEventsGrid.addSortListener(event -> {
			if(auditEventsGrid.getSortOrder().iterator().hasNext())
				auditEventsGrid.getSortOrder().iterator().next().getSorted().getId().filter(id -> id.equals(TIMESTAMP))
						.ifPresent(id -> auditEventsGrid.getDataProvider().refreshAll());
		});
		auditEventsGrid.sort(List.of(new GridSortOrder<>(timestampColumn, SortDirection.DESCENDING)));
		auditEventsGrid.setAllRowsVisible(true);
	}

	private Span getTimestampHeaderLabel()
	{
		Span label = new Span(msg.getMessage("AuditEventsView.timestamp"));
		label.getStyle().set("margin-left", BIG_MARGIN.value());
		return label;
	}

	private HorizontalLayout createTimestampWithDetailsArrow(AuditEventEntry eventEntry)
	{
		Span label = new Span(eventEntry.formatTimestamp());
		Icon openIcon = VaadinIcon.ANGLE_RIGHT.create();
		Icon closeIcon = VaadinIcon.ANGLE_DOWN.create();
		openIcon.setVisible(!auditEventsGrid.isDetailsVisible(eventEntry));
		closeIcon.setVisible(auditEventsGrid.isDetailsVisible(eventEntry));
		openIcon.addClickListener(e -> auditEventsGrid.setDetailsVisible(eventEntry, true));
		closeIcon.addClickListener(e -> auditEventsGrid.setDetailsVisible(eventEntry, false));
		return new HorizontalLayout(openIcon, closeIcon, label);
	}

	private HorizontalLayout getActions(ColumnToggleMenu columnToggleMenu)
	{
		HorizontalLayout horizontalLayout = new HorizontalLayout(new Span(msg.getMessage("actions")), columnToggleMenu.getTarget());
		horizontalLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
		return horizontalLayout;
	}

	private VerticalLayout createFiltersLayout()
	{
		VerticalLayout filterLayout = new VerticalLayout();
		filterLayout.addClassName("u-auditEvents-filterLayout");
		filterLayout.setWidthFull();
		filterLayout.setPadding(false);

		LocalDateTime initialDate = LocalDateTime.now().minusDays(1);
		fromFilter.setValue(initialDate);
		fromFilter.setDatePickerI18n(DATETIME_FORMAT_SHORT_PATTERN);
		fromFilter.setLocale(msg.getLocaleForTimeFormat());
		fromFilter.setLabel(msg.getMessage("AuditEventsView.from"));
		fromFilter.setWidthFull();

		untilFilter.setDatePickerI18n(DATETIME_FORMAT_SHORT_PATTERN);
		untilFilter.setLocale(msg.getLocaleForTimeFormat());
		untilFilter.setLabel(msg.getMessage("AuditEventsView.until"));
		untilFilter.setWidthFull();

		typeFilter.setItems(Arrays.stream(AuditEventType.values()).map(AuditEventType::toString).collect(Collectors.toList()));
		typeFilter.setLabel(msg.getMessage("AuditEventsView.type"));
		typeFilter.setWidthFull();

		actionFilter.setItems(Arrays.stream(AuditEventAction.values()).map(AuditEventAction::toString).collect(Collectors.toList()));
		actionFilter.setLabel(msg.getMessage("AuditEventsView.action"));
		actionFilter.setWidthFull();

		searchFilter.setLabel(msg.getMessage("search"));
		searchFilter.setValueChangeMode(ValueChangeMode.EAGER);
		searchFilter.setWidthFull();

		tagsFilter.setItems(eventManagement.getAllTags().stream().sorted().collect(Collectors.toList()));
		tagsFilter.setLabel(msg.getMessage("AuditEventsView.tags"));
		tagsFilter.setWidthFull();

		limitFilter.setItems(100, 1000, 10000);
		limitFilter.setValue(DEFAULT_LIMIT);
		limitFilter.setLabel(msg.getMessage("AuditEventsView.limit"));
		limitFilter.setWidthFull();

		HorizontalLayout upperLayout = new HorizontalLayout(limitFilter, fromFilter, untilFilter);
		upperLayout.setWidthFull();
		HorizontalLayout lowerLayout = new HorizontalLayout(typeFilter, actionFilter, tagsFilter, searchFilter);
		lowerLayout.setWidthFull();
		filterLayout.add(upperLayout, lowerLayout);
		filterLayout.setSpacing(false);

		GridListDataView auditEventEntryGridListDataView = reloadGrid();
		AuditLogFilter auditLogFilter = new AuditLogFilter(auditEventEntryGridListDataView);

		limitFilter.addValueChangeListener(event ->
		{
			GridListDataView dataView = reloadGrid();
			auditLogFilter.setDataView(dataView);
		});
		fromFilter.addValueChangeListener(event ->
		{
			GridListDataView dataView = reloadGrid();
			auditLogFilter.setDataView(dataView);
		});
		untilFilter.addValueChangeListener(event ->
		{
			GridListDataView dataView = reloadGrid();
			auditLogFilter.setDataView(dataView);
		});
		typeFilter.addValueChangeListener(event -> auditLogFilter.setTypes(event.getValue()));
		actionFilter.addValueChangeListener(event -> auditLogFilter.setActions(event.getValue()));
		searchFilter.addValueChangeListener(event -> auditLogFilter.setSearch(event.getValue()));
		tagsFilter.addValueChangeListener(event -> auditLogFilter.setTags(event.getValue()));

		return filterLayout;
	}

	GridListDataView reloadGrid()
	{
		Collection auditEvents = getAuditEvents();
		GridListDataView auditEventEntryGridListDataView = auditEventsGrid.setItems(auditEvents);
		auditEventEntryGridListDataView.addItemCountChangeListener(event ->
				titleLabel.setText(msg.getMessage("AuditEventsView.gridSummary", event.getItemCount(), auditEvents.size())));
		return auditEventEntryGridListDataView;
	}
	private FormLayout getDetailsComponent(AuditEventEntry ae)
	{
		FormLayout wrapper = new FormLayout();
		wrapper.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
		wrapper.addFormItem(new Span(ae.getFormattedSubject()), new FormLayoutLabel(msg.getMessage("AuditEventsView.subject") + ":"));
		wrapper.addFormItem(new Span(ae.getFormattedInitiator()), new FormLayoutLabel(msg.getMessage("AuditEventsView.initiator") + ":"));

		if (!"".equals(ae.formatDetails()))
			wrapper.addFormItem(new Span(ae.formatDetails()), new FormLayoutLabel(msg.getMessage("AuditEventsView.details") + ":"));

		return wrapper;
	}

	private Collection getAuditEvents()
	{
		try
		{
			Date from = null;
			Date until = null;
			if (nonNull(fromFilter.getValue())) {
				from = Date.from(fromFilter.getValue().atZone(ZoneId.systemDefault()).toInstant());
			}
			if (nonNull(untilFilter.getValue())) {
				until = Date.from(untilFilter.getValue().atZone(ZoneId.systemDefault()).toInstant());
			}
			long now = System.currentTimeMillis();
			SortDirection direction =  auditEventsGrid.getSortOrder().get(0).getSorted().getId().filter(id -> id.equals(TIMESTAMP)).isPresent()
					? auditEventsGrid.getSortOrder().get(0).getDirection()
					: SortDirection.DESCENDING;
			List list = eventManagement.getAuditEvents(from,
							until,
							limitFilter.getValue(),
							TIMESTAMP,
							direction == SortDirection.ASCENDING ? 1 : -1).stream()
					.map(ae -> new AuditEventEntry(msg, ae))
					.collect(Collectors.toList());
			log.debug("AuditEvents retrieval time: {} ms" , System.currentTimeMillis() - now);
			titleLabel.setText(msg.getMessage("AuditEventsView.gridSummary", list.size(), list.size()));
			return list;
		} catch (Exception e)
		{
			notificationPresenter.showError(msg.getMessage("Error"), e.getMessage());
		}

		return Collections.emptyList();
	}

	private Component createActionsButton(AuditEventEntry eventEntry)
	{
		ActionMenu actionMenu = new ActionMenu();

		MenuButton showSubjectDetailsButton = new MenuButton(msg.getMessage("AuditEventsView.showDetails", msg.getMessage("AuditEventsView.subject")), SEARCH);
		MenuItem showSubjectDetailsMenuItem = actionMenu.addItem(showSubjectDetailsButton, e -> showSubjectDetails(eventEntry));
		if(eventEntry.getEvent().getSubject() == null)
			showSubjectDetailsMenuItem.setEnabled(false);

		MenuButton showInitiatorDetailsButton = new MenuButton(msg.getMessage("AuditEventsView.showDetails", msg.getMessage("AuditEventsView.initiator")), SEARCH);
		MenuItem showInitiatorDetailsMenuItem = actionMenu.addItem(showInitiatorDetailsButton, e -> showInitiatorDetails(eventEntry));
		if(eventEntry.getEvent().getInitiator().getEntityId() == 0)
			showInitiatorDetailsMenuItem.setEnabled(false);

		Component target = actionMenu.getTarget();
		target.getElement().getStyle().set("margin-right", BIG_MARGIN.value());
		return target;
	}

	private void showSubjectDetails(AuditEventEntry selection)
	{
		showEntityDetails(selection.getEvent().getSubject().getEntityId());
	}

	private void showInitiatorDetails(AuditEventEntry selection)
	{
		showEntityDetails(selection.getEvent().getInitiator().getEntityId());
	}

	private void showEntityDetails(Long entityId)
	{
		EntityParam param = new EntityParam(entityId);
		Collection groups;
		Entity entity;
		String label;
		try
		{
			entity = idsMan.getEntity(param);
			label = idsMan.getEntityLabel(param);
			groups = entityMan.getGroups(new EntityParam(entityId)).values();
		} catch (UnknownIdentityException e) {
			notificationPresenter.showError("Not found", join(" ", "Cannot display entity details.\nEntity",
					Long.toString(entityId), "was removed from the system."));
			return;
		} catch (EngineException e)
		{
			notificationPresenter.showError("Error", "Cannot display entity details.");
			return;
		}
		FormLayout formLayout = EntityDetailsPanelFactory.create(msg, identityFormatter, entity, label, groups);
		new EntityDetailsDialog(formLayout, msg.getMessage("close")).open();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy