This is an automated email from the ASF dual-hosted git repository.

dhavalrajpara pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new b31611f7d RANGER-5271 : Last login date and time on Ranger Home page 
(#632)
b31611f7d is described below

commit b31611f7de263bbc426e65f2967478cc75b57d1a
Author: Dhaval Rajpara <[email protected]>
AuthorDate: Wed Sep 3 12:01:15 2025 +0530

    RANGER-5271 : Last login date and time on Ranger Home page (#632)
---
 .../src/main/java/org/apache/ranger/biz/SessionMgr.java | 12 ++++++++++++
 .../src/main/java/org/apache/ranger/biz/UserMgr.java    |  1 +
 .../java/org/apache/ranger/db/XXAuthSessionDao.java     | 17 +++++++++++++++++
 .../main/java/org/apache/ranger/view/VXPortalUser.java  | 14 ++++++++++++++
 .../src/main/resources/META-INF/jpa_named_queries.xml   |  5 +++++
 .../src/main/webapp/react-webapp/src/styles/style.css   |  2 +-
 .../src/main/webapp/react-webapp/src/views/Layout.jsx   | 11 +++++++++++
 .../webapp/react-webapp/src/views/SideBar/SideBar.jsx   |  2 +-
 8 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java 
b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
index 68874e86c..ae5e339a3 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
@@ -475,6 +475,18 @@ public synchronized void 
refreshPermissionsIfNeeded(UserSessionBase userSession)
         }
     }
 
+    public Date getLastSuccessLoginAuthTimeByUserId(String loginId) {
+        XXAuthSession xXAuthSession = 
daoManager.getXXAuthSession().getLastSuccessLoginAuthSessionByUserId(loginId);
+
+        if (xXAuthSession != null) {
+            return 
authSessionService.populateViewBean(xXAuthSession).getAuthTime();
+        } else {
+            logger.info("Session cleaned up or  User logged in for first 
time");
+        }
+
+        return null;
+    }
+
     protected boolean validateUserSession(UserSessionBase userSession, String 
currentLoginId) {
         if 
(currentLoginId.equalsIgnoreCase(userSession.getXXPortalUser().getLoginId())) {
             return true;
diff --git a/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java 
b/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java
index 0686f37a5..5516ba43e 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java
@@ -648,6 +648,7 @@ public VXPortalUser 
mapXXPortalUserToVXPortalUser(XXPortalUser user, Collection<
             userProfile.setUserRoleList(userRoleList);
         }
 
+        
userProfile.setLastLoginTime(sessionMgr.getLastSuccessLoginAuthTimeByUserId(sess.getLoginId()));
         userProfile.setUserSource(user.getUserSource());
 
         return userProfile;
diff --git 
a/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java 
b/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java
index 3deb41e52..0f8016be7 100644
--- a/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java
+++ b/security-admin/src/main/java/org/apache/ranger/db/XXAuthSessionDao.java
@@ -68,6 +68,23 @@ public List<XXAuthSession> getAuthSessionByUserId(Long 
userId) {
         }
     }
 
+    public XXAuthSession getLastSuccessLoginAuthSessionByUserId(String 
loginId) {
+        if (loginId == null) {
+            return null;
+        }
+        try {
+            List<XXAuthSession> sessions = getEntityManager()
+                    
.createNamedQuery("XXAuthSession.getSuccessAuthSessionsByUserId", tClass)
+                    .setParameter("loginId", loginId)
+                    .setParameter("authStatus", 
XXAuthSession.AUTH_STATUS_SUCCESS)
+                    .setMaxResults(2)
+                    .getResultList();
+            return sessions.size() >= 2 ? sessions.get(1) : null;
+        } catch (NoResultException ignoreNoResultFound) {
+            return null;
+        }
+    }
+
     public long getRecentAuthFailureCountByLoginId(String loginId, int 
timeRangezSecond) {
         Date utcDate             = DateUtil.getUTCDate();
         Date authWindowStartTime = new Date((utcDate != null ? 
utcDate.getTime() : System.currentTimeMillis()) - timeRangezSecond * 1000L);
diff --git 
a/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java 
b/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java
index c9ceeb0c4..f7e100855 100644
--- a/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java
+++ b/security-admin/src/main/java/org/apache/ranger/view/VXPortalUser.java
@@ -23,9 +23,12 @@
 import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.apache.ranger.common.AppConstants;
+import org.apache.ranger.json.JsonDateSerializer;
 
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -90,6 +93,8 @@ public class VXPortalUser extends VXDataObject implements 
java.io.Serializable {
      * sync Source Attribute.
      */
     protected String syncSource;
+    @JsonSerialize(using = JsonDateSerializer.class)
+    protected Date lastLoginTime;
 
     /**
      * Configuration properties.
@@ -318,6 +323,7 @@ public String toString() {
         str += "userRoleList={" + userRoleList + "} ";
         str += "otherAttributes={" + otherAttributes + "} ";
         str += "syncSource={" + syncSource + "} ";
+        str += "lastLoginTime={" + lastLoginTime + "} ";
         str += "}";
         return str;
     }
@@ -385,4 +391,12 @@ public String getSyncSource() {
     public void setSyncSource(final String syncSource) {
         this.syncSource = syncSource;
     }
+
+    public Date getLastLoginTime() {
+        return lastLoginTime;
+    }
+
+    public void setLastLoginTime(Date lastLoginTime) {
+        this.lastLoginTime = lastLoginTime;
+    }
 }
diff --git a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml 
b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
index 8f61e66e3..265479422 100755
--- a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
+++ b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
@@ -48,6 +48,11 @@
                <query>DELETE FROM XXAuthSession obj WHERE obj.id in :ids
                </query>
        </named-query>
+       <named-query name="XXAuthSession.getSuccessAuthSessionsByUserId">
+               <query>SELECT obj FROM XXAuthSession obj WHERE obj.loginId = 
:loginId and obj.authStatus = :authStatus order by obj.id DESC
+               </query>
+       </named-query>
+
 
        <!-- XXPortalUser -->
        <named-query name="XXPortalUser.findByEmailAddress">
diff --git a/security-admin/src/main/webapp/react-webapp/src/styles/style.css 
b/security-admin/src/main/webapp/react-webapp/src/styles/style.css
index 51bc0e071..ea5f33e07 100644
--- a/security-admin/src/main/webapp/react-webapp/src/styles/style.css
+++ b/security-admin/src/main/webapp/react-webapp/src/styles/style.css
@@ -1317,7 +1317,7 @@ header {
 }
 
 .top-scroll {
-  bottom: 3px;
+  bottom: 10px;
   right: 5px;
 }
 
diff --git a/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx 
b/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx
index 1edb38423..d6199b41f 100644
--- a/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/views/Layout.jsx
@@ -40,6 +40,7 @@ import { Loader } from "../components/CommonComponents";
 import { Suspense } from "react";
 import { PathAssociateWithModule } from "../utils/XAEnums";
 import { flatMap, values } from "lodash";
+import dateFormat from "dateformat";
 
 const Layout = () => {
   let location = useLocation();
@@ -94,6 +95,13 @@ const Layout = () => {
     activate();
   };
 
+  const lastLoginInformation = userProfile?.lastLoginTime
+    ? `You last logged in on, ${dateFormat(
+        new Date(userProfile.lastLoginTime),
+        "dddd, mmm dd, yyyy, hh:MM:ss TT"
+      )}`
+    : "";
+
   useEffect(() => {
     const interval = setInterval(() => {
       if (isPrompted()) {
@@ -170,6 +178,9 @@ const Layout = () => {
                 >
                   Licensed under the Apache License, Version 2.0
                 </a>
+                <span className="float-end mb-2 me-4" style={{ color: "#999" 
}}>
+                  {lastLoginInformation}
+                </span>
               </p>
             </div>
           </footer>
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx 
b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx
index d57082026..4006f4e04 100644
--- a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx
@@ -107,7 +107,7 @@ export const SideBar = () => {
 
   const userProps = getUserProfile();
   const loginId = (
-    <span className="login-id user-name">{userProps?.loginId}</span>
+    <span className="login-id user-name p-0">{userProps?.loginId}</span>
   );
 
   let location = useLocation();

Reply via email to