Administration menu

Hello.

I’m having a try on JMIX (currently using Cuba): I was surprised to see that, by default, the admin menu is almost empty. Reading the forum, I saw how to add the Entity Inspector, but I still don’t know how to display and use the Scheduled Tasks and the User Sessions Log that I find so useful.

Any advice on how to make these things enabled ?

Many thanks.

O.

Hi.
We made the decision to stop support for our scheduled tasks engine and use https://www.quartz-scheduler.org instead.

Use https://github.com/Haulmont/jmix-sessions add-on as an alternative to User Sessions Log.

Full list of add-ons you can find here

Regards,
Natalia

Thanks.
Is there an example of using Quartz with Jmix available ?
Just a Hello world example, to see the creation of the Job would be enough.
The documentation is very light on this particular context.

Thanks.

Is there alternative add-on for CUBA Administration “Server Log” ?

OK I finally succeeded to set it up.

Any clues on how I can get the users’ session logs with this new jmix ?

Thanks in advance

Look at the authentication events. You can register login/logout events of your users, for example by defining an entity with a browse screen and creating such entities in the authentication event listeners.

Regards,
Konstantin

1 Like

Thanks @krivopustov, this helps.
However, I have a last question: this doesn’t manage logout by time out, or logout by closing the browser window (as it was handled in CUBA). Is there a way to keep control on that and not keep ghost logins?
Have a nice day.
O.

You can use the standard HttpSessionListener to catch when HTTP session is destroyed.

Below is a solution that creates a log record on user login and updates the record when the user logs out or the session expires.

The log item entity:

@JmixEntity
@Table(name = "USER_AUTHENTICATION_LOG")
@Entity
public class UserAuthenticationLog {
    @JmixGeneratedValue
    @Column(name = "ID", nullable = false)
    @Id
    private UUID id;

    @Column(name = "VERSION", nullable = false)
    @Version
    private Integer version;

    @Column(name = "USERNAME", nullable = false)
    @NotNull
    private String username;

    @Column(name = "LOGGED_IN")
    private LocalDateTime loggedIn;

    @Column(name = "LOGGED_OUT")
    private LocalDateTime loggedOut;

    @Column(name = "SESSION_ID")
    private String sessionId;

    // getters and setters
}

The listener bean:

@Component
public class AuthenticationEventListener implements HttpSessionListener {

    @Autowired
    private UnconstrainedDataManager dataManager;

    @EventListener
    public void onInteractiveAuthenticationSuccess(InteractiveAuthenticationSuccessEvent event) {
        UserAuthenticationLog logItem = dataManager.create(UserAuthenticationLog.class);
        User user = (User) event.getAuthentication().getPrincipal();
        logItem.setUsername(user.getUsername());
        logItem.setLoggedIn(LocalDateTime.now());
        // Jmix provides session ID in ClientDetails class
        logItem.setSessionId(((ClientDetails) event.getAuthentication().getDetails()).getSessionId());
        dataManager.save(logItem);
    }

    @EventListener
    public void onLogoutSuccess(LogoutSuccessEvent event) {
        // we use session ID to find matching login record
        String sessionId = ((ClientDetails) event.getAuthentication().getDetails()).getSessionId();
        UserAuthenticationLog logItem = getLogOptional(sessionId)
                .orElseGet(() -> {
                    UserAuthenticationLog newLogItem = dataManager.create(UserAuthenticationLog.class);
                    User user = (User) event.getAuthentication().getPrincipal();
                    newLogItem.setUsername(user.getUsername());
                    return newLogItem;
                });

        if (logItem.getLoggedOut() == null) {
            logItem.setLoggedOut(LocalDateTime.now());
            dataManager.save(logItem);
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // we use session ID to find matching login record
        getLogOptional(se.getSession().getId())
                .ifPresent(logItem -> {
                    if (logItem.getLoggedOut() == null) {
                        logItem.setLoggedOut(LocalDateTime.now());
                        dataManager.save(logItem);
                    }
                });
    }

    private Optional<UserAuthenticationLog> getLogOptional(String sessionId) {
        return dataManager.load(UserAuthenticationLog.class)
                .query("e.sessionId = ?1", sessionId)
                .optional();
    }
}

To test the session expiration in UI, use the following property:

# 2 minutes 
jmix.ui.http-session-expiration-timeout-sec = 120

Regards,
Konstantin

1 Like

Hello,

I am trying the solution which you have provided. Most of the cases are working perfectly but sometimes i get the exception bellow:

java.lang.ClassCastException: class org.springframework.security.web.authentication.WebAuthenticationDetails cannot be cast to class io.jmix.core.security.ClientDetails (org.springframework.security.web.authentication.WebAuthenticationDetails and io.jmix.core.security.ClientDetails are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader @e2144e4)

Do you have any idea why I get this?

Thank you,
Ilias

Hi Ilias,
Perhaps you can investigate the reason if if you check the real class of event.getAuthentication().getDetails() and log its content instead of getting sessionId.

Hello,
Thank you for your reply.
I found out that when a user is logging in with remember me enabled (Not the first time while he had checked remember me but after closing the browser and open it again), event.getAuthentication().getDetails() is instance of WebAuthenticationDetails and not ClientDetails.
I made it work by retrieving sessionID from request.

1 Like