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

jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new cc5f05eef7 [#12588] improvement(web2-ui): Expose UI session timeout 
settings through server configuration (#12621)
cc5f05eef7 is described below

commit cc5f05eef75d9e8fed51692816f67a9477d2fab8
Author: Qian Xia <[email protected]>
AuthorDate: Tue Sep 1 16:04:31 2026 +0800

    [#12588] improvement(web2-ui): Expose UI session timeout settings through 
server configuration (#12621)
    
    ### What changes were proposed in this pull request?
    
      - Added three optional server-side UI session timeout configurations:
          - gravitino.ui.sessionIdleTimeoutMs
          - gravitino.ui.sessionMaxDurationMs
          - gravitino.ui.sessionIdleWarningLeadMs
    
    - Added the configurations to conf/gravitino.conf.template with
    commented defaults and descriptions.
      - Updated the UI to resolve values in this order:
          1. /configs API
          2. Corresponding NEXT_PUBLIC_* environment variable
          3. Built-in default value
    
    - Removed gravitino.authorization.serviceAdmins from visibleConfigs; it
    is already exposed automatically by
        the /configs API when authorization is enabled.
    
      ### Why are the changes needed?
    
    Session timeout behavior previously depended only on UI environment
    variables and hardcoded defaults. This
    made deployment-specific configuration inconvenient and required
    rebuilding or separately configuring the UI.
    
    The server configuration now provides a centralized way to configure
    idle timeout, maximum session duration,
      and warning lead time.
    
      Fix: #12588
    
      ### Does this PR introduce any user-facing change?
    
      Yes.
    
    - Adds three optional server configuration keys for UI session
    management.
    - Existing UI environment variables and defaults remain supported as
    fallbacks.
    - gravitino.authorization.serviceAdmins remains available through
    /configs when authorization is enabled; it
        no longer needs to be listed in gravitino.server.visibleConfigs.
    
    - No public REST API shape or authentication API changes are introduced.
    
      ### How was this patch tested?
    
      From web-v2/web:
    
      pnpm test
      pnpm lint
---
 conf/gravitino.conf.template                       | 14 ++++-
 docs/webui-v2.md                                   | 21 ++++++++
 .../web/src/lib/hooks/useAbsoluteSessionTimeout.js | 10 +---
 web-v2/web/src/lib/hooks/useIdleTimeout.js         |  8 +--
 web-v2/web/src/lib/provider/IdleSessionProvider.js | 51 +++++++++---------
 .../web/src/lib/provider/sessionTimeoutConfig.js   | 62 ++++++++++++++++++++++
 6 files changed, 124 insertions(+), 42 deletions(-)

diff --git a/conf/gravitino.conf.template b/conf/gravitino.conf.template
index c9afcc025c..20f0bb51ae 100644
--- a/conf/gravitino.conf.template
+++ b/conf/gravitino.conf.template
@@ -43,6 +43,17 @@ gravitino.server.webserver.threadPoolWorkQueueSize = 100
 gravitino.server.webserver.requestHeaderSize = 131072
 # The response header size of the built-in web server
 gravitino.server.webserver.responseHeaderSize = 131072
+# UI inactivity timeout in milliseconds. The UI environment variable
+# NEXT_PUBLIC_IDLE_TIMEOUT_MS is used when this config is not set.
+# gravitino.ui.sessionIdleTimeoutMs = 900000
+# Maximum UI session duration in milliseconds, regardless of user activity. 
The UI environment
+# variable NEXT_PUBLIC_MAX_SESSION_DURATION_MS is used when this config is not 
set.
+# gravitino.ui.sessionMaxDurationMs = 18000000
+# UI warning countdown duration in milliseconds before the inactivity timeout. 
The UI environment
+# variable NEXT_PUBLIC_IDLE_WARNING_LEAD_MS is used when this config is not 
set.
+# gravitino.ui.sessionIdleWarningLeadMs = 60000
+# Multiple visibleConfigs are split by commas.
+# gravitino.server.visibleConfigs = 
gravitino.ui.sessionIdleTimeoutMs,gravitino.ui.sessionMaxDurationMs,gravitino.ui.sessionIdleWarningLeadMs
 
 # THE CONFIGURATION FOR Gravitino ENTITY STORE
 # The entity store to use, we only supports relational
@@ -88,8 +99,7 @@ gravitino.fetchFile.blockUnsafeRemoteUri = true
 # THE CONFIGURATION FOR authorization
 # Whether Gravitino enable authorization or not
 gravitino.authorization.enable = false
-# The admins of Gravitino service, multiple admins are spitted by comma.
-gravitino.server.visibleConfigs=gravitino.authorization.serviceAdmins
+# The admins of Gravitino service, separated by commas.
 gravitino.authorization.serviceAdmins = anonymous
 
 # THE CONFIGURATION FOR AUXILIARY SERVICE
diff --git a/docs/webui-v2.md b/docs/webui-v2.md
index d45318725c..2e0ccd3a82 100644
--- a/docs/webui-v2.md
+++ b/docs/webui-v2.md
@@ -31,6 +31,27 @@ After changing this value, restart the Gravitino server for 
the change to take e
 <path-to-gravitino>/bin/gravitino.sh restart
 ```
 
+### Session timeout configuration
+
+Web V2 supports server-side configuration of session timeouts. All values are 
in milliseconds:
+
+| Server configuration | UI environment fallback | Default | Description |
+| --- | --- | ---: | --- |
+| `gravitino.ui.sessionIdleTimeoutMs` | `NEXT_PUBLIC_IDLE_TIMEOUT_MS` | 
`900000` | Logs the user out after this period without activity. |
+| `gravitino.ui.sessionMaxDurationMs` | `NEXT_PUBLIC_MAX_SESSION_DURATION_MS` 
| `18000000` | Logs the user out after this total session duration, regardless 
of activity. |
+| `gravitino.ui.sessionIdleWarningLeadMs` | `NEXT_PUBLIC_IDLE_WARNING_LEAD_MS` 
| `60000` | Shows the inactivity warning this long before the idle timeout. |
+
+The UI resolves each value in this order: the `/configs` API, the 
corresponding `NEXT_PUBLIC_*` environment variable, and then the default value. 
To expose server values through `/configs`, configure the keys in 
`gravitino.server.visibleConfigs`:
+
+```properties
+gravitino.ui.sessionIdleTimeoutMs = 900000
+gravitino.ui.sessionMaxDurationMs = 18000000
+gravitino.ui.sessionIdleWarningLeadMs = 60000
+gravitino.server.visibleConfigs = 
gravitino.ui.sessionIdleTimeoutMs,gravitino.ui.sessionMaxDurationMs,gravitino.ui.sessionIdleWarningLeadMs
+```
+
+`gravitino.authorization.serviceAdmins` is exposed automatically by `/configs` 
when authorization is enabled and does not need to be added to 
`gravitino.server.visibleConfigs`.
+
 ## Web V2
 
 The sections below describe the Web V2. This is the default UI; set 
`GRAVITINO_USE_WEB_V2=false` to use the legacy v1 UI.
diff --git a/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js 
b/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js
index 58dab82730..41a6d84946 100644
--- a/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js
+++ b/web-v2/web/src/lib/hooks/useAbsoluteSessionTimeout.js
@@ -30,14 +30,8 @@ const SESSION_START_KEY = 'grtv-session-start'
 
 /**
  * Default maximum session duration: 5 hours in milliseconds.
- * Overridable via NEXT_PUBLIC_MAX_SESSION_DURATION_MS environment variable.
  */
-const DEFAULT_MAX_SESSION_DURATION_MS = (() => {
-  const envVal = process.env.NEXT_PUBLIC_MAX_SESSION_DURATION_MS
-  const parsed = envVal ? Number(envVal) : NaN
-
-  return Number.isFinite(parsed) && parsed > 0 ? parsed : 5 * 60 * 60 * 1000
-})()
+const DEFAULT_MAX_SESSION_DURATION_MS = 5 * 60 * 60 * 1000
 
 /**
  * Custom hook for absolute session duration enforcement.
@@ -54,7 +48,7 @@ const DEFAULT_MAX_SESSION_DURATION_MS = (() => {
  *   When true and no session start exists, the current time is recorded.
  *   When false, the session start is cleared.
  * @param {number} [options.maxDurationMs] - Maximum session duration in 
milliseconds.
- *   Defaults to NEXT_PUBLIC_MAX_SESSION_DURATION_MS env var or 5 hours.
+ *   Defaults to 5 hours.
  * @returns {{ isExpired: boolean, remainingMs: number, clearSession: () => 
void }}
  */
 export function useAbsoluteSessionTimeout({ isAuthenticated, maxDurationMs = 
DEFAULT_MAX_SESSION_DURATION_MS } = {}) {
diff --git a/web-v2/web/src/lib/hooks/useIdleTimeout.js 
b/web-v2/web/src/lib/hooks/useIdleTimeout.js
index dc4246d748..56fb67a2a0 100644
--- a/web-v2/web/src/lib/hooks/useIdleTimeout.js
+++ b/web-v2/web/src/lib/hooks/useIdleTimeout.js
@@ -35,14 +35,8 @@ const DISABLED_SENTINEL = Number.MAX_SAFE_INTEGER
 
 /**
  * Default idle timeout: 15 minutes in milliseconds.
- * Overridable via NEXT_PUBLIC_IDLE_TIMEOUT_MS environment variable.
  */
-const DEFAULT_IDLE_TIMEOUT_MS = (() => {
-  const envVal = process.env.NEXT_PUBLIC_IDLE_TIMEOUT_MS
-  const parsed = envVal ? Number(envVal) : NaN
-
-  return Number.isFinite(parsed) && parsed > 0 ? parsed : 15 * 60 * 1000
-})()
+const DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1000
 
 /**
  * Throttle interval for activity events in milliseconds.
diff --git a/web-v2/web/src/lib/provider/IdleSessionProvider.js 
b/web-v2/web/src/lib/provider/IdleSessionProvider.js
index 0ffdcb8302..974909f2fe 100644
--- a/web-v2/web/src/lib/provider/IdleSessionProvider.js
+++ b/web-v2/web/src/lib/provider/IdleSessionProvider.js
@@ -28,28 +28,19 @@ import { useBroadcastChannel } from 
'@/lib/hooks/useBroadcastChannel'
 import { logoutAction } from '@/lib/store/auth'
 import IdleSessionContext from './IdleSessionContext'
 import IdleWarningModal from '@/components/IdleWarningModal'
+import { resolveSessionTimeouts } from './sessionTimeoutConfig'
 
 /**
  * Default idle timeout: 15 minutes in milliseconds.
- * Overridable via NEXT_PUBLIC_IDLE_TIMEOUT_MS environment variable.
  */
-const DEFAULT_IDLE_TIMEOUT_MS = (() => {
-  const envVal = process.env.NEXT_PUBLIC_IDLE_TIMEOUT_MS
-  const parsed = envVal ? Number(envVal) : NaN
-
-  return Number.isFinite(parsed) && parsed > 0 ? parsed : 15 * 60 * 1000
-})()
+const DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1000
 
 /**
  * Default warning lead time: 60 seconds in milliseconds.
- * Overridable via NEXT_PUBLIC_IDLE_WARNING_LEAD_MS environment variable.
  */
-const DEFAULT_WARNING_LEAD_MS = (() => {
-  const envVal = process.env.NEXT_PUBLIC_IDLE_WARNING_LEAD_MS
-  const parsed = envVal ? Number(envVal) : NaN
+const DEFAULT_WARNING_LEAD_MS = 60 * 1000
 
-  return Number.isFinite(parsed) && parsed > 0 ? parsed : 60 * 1000
-})()
+const DEFAULT_MAX_SESSION_DURATION_MS = 5 * 60 * 60 * 1000
 
 /**
  * Converts milliseconds to seconds, rounded up.
@@ -72,17 +63,26 @@ function msToSeconds(ms) {
  * @param {React.ReactNode} props.children
  * @param {number} [props.idleTimeoutMs] - Idle timeout in milliseconds 
(default: 15 min)
  * @param {number} [props.warningLeadMs] - Warning lead time in milliseconds 
(default: 60s)
+ * @param {number} [props.maxSessionDurationMs] - Maximum session duration 
(default: 5 hours)
  */
 export default function IdleSessionProvider({
   children,
   idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,
-  warningLeadMs = DEFAULT_WARNING_LEAD_MS
+  warningLeadMs = DEFAULT_WARNING_LEAD_MS,
+  maxSessionDurationMs = DEFAULT_MAX_SESSION_DURATION_MS
 }) {
   const router = useRouter()
   const pathname = usePathname()
   const dispatch = useAppDispatch()
   const authType = useAppSelector(state => state.auth.authType)
   const authToken = useAppSelector(state => state.auth.authToken)
+  const systemConfig = useAppSelector(state => state.auth.systemConfig)
+
+  const {
+    idleTimeoutMs: resolvedIdleTimeoutMs,
+    warningLeadMs: resolvedWarningLeadMs,
+    maxSessionDurationMs: resolvedMaxSessionDurationMs
+  } = resolveSessionTimeouts(systemConfig, { idleTimeoutMs, warningLeadMs, 
maxSessionDurationMs })
 
   // Only enable idle timeout when the user is authenticated and not on login 
page.
   // Fall back to persisted token in localStorage to avoid prematurely treating
@@ -98,7 +98,7 @@ export default function IdleSessionProvider({
   const isLoginPage = pathname.endsWith('/login')
 
   const [state, setState] = useState('active')
-  const [warningCountdown, setWarningCountdown] = 
useState(msToSeconds(warningLeadMs))
+  const [warningCountdown, setWarningCountdown] = 
useState(msToSeconds(resolvedWarningLeadMs))
   const loggedOutRef = useRef(false)
   const wasAuthenticatedRef = useRef(isAuthenticated)
 
@@ -107,7 +107,7 @@ export default function IdleSessionProvider({
     resetActivity: resetIdleActivity,
     idleTimeRemaining
   } = useIdleTimeout({
-    idleTimeoutMs: isAuthenticated ? idleTimeoutMs : Number.MAX_SAFE_INTEGER,
+    idleTimeoutMs: isAuthenticated ? resolvedIdleTimeoutMs : 
Number.MAX_SAFE_INTEGER,
 
     // Pause DOM activity detection during warning state so the modal stays
     // visible until the user explicitly clicks "Stay signed in" or "Sign out 
now"
@@ -116,13 +116,14 @@ export default function IdleSessionProvider({
 
   // Absolute session duration: forces logout after a fixed time regardless of 
activity
   const { isExpired: isAbsoluteExpired } = useAbsoluteSessionTimeout({
-    isAuthenticated
+    isAuthenticated,
+    maxDurationMs: resolvedMaxSessionDurationMs
   })
 
   const { sendMessage, onMessage } = useBroadcastChannel()
 
   // Track warning threshold: when idleTimeRemaining drops below 
warningLeadMs, show warning
-  const warningThreshold = warningLeadMs
+  const warningThreshold = resolvedWarningLeadMs
 
   // State machine transitions
   useEffect(() => {
@@ -190,11 +191,11 @@ export default function IdleSessionProvider({
   const handleStaySignedIn = useCallback(() => {
     setState('active')
     resetIdleActivity()
-    setWarningCountdown(msToSeconds(warningLeadMs))
+    setWarningCountdown(msToSeconds(resolvedWarningLeadMs))
 
     // Broadcast stay_signed_in to other tabs so they also dismiss warning and 
stay logged in
     sendMessage({ type: 'stay_signed_in', timestamp: Date.now() })
-  }, [resetIdleActivity, warningLeadMs, sendMessage])
+  }, [resetIdleActivity, resolvedWarningLeadMs, sendMessage])
 
   // Cross-tab message handling (IST-REQ-005)
   useEffect(() => {
@@ -204,14 +205,14 @@ export default function IdleSessionProvider({
         resetIdleActivity()
         if (state === 'warning') {
           setState('active')
-          setWarningCountdown(msToSeconds(warningLeadMs))
+          setWarningCountdown(msToSeconds(resolvedWarningLeadMs))
         }
       } else if (message.type === 'stay_signed_in') {
         // Another tab clicked "Stay signed in" — dismiss warning and stay 
logged in
         resetIdleActivity()
         if (state === 'warning') {
           setState('active')
-          setWarningCountdown(msToSeconds(warningLeadMs))
+          setWarningCountdown(msToSeconds(resolvedWarningLeadMs))
         }
       } else if (message.type === 'logout') {
         // Another tab triggered logout — clear local auth state and redirect
@@ -230,7 +231,7 @@ export default function IdleSessionProvider({
         }
       }
     })
-  }, [onMessage, resetIdleActivity, router, state, warningLeadMs, dispatch])
+  }, [onMessage, resetIdleActivity, router, state, resolvedWarningLeadMs, 
dispatch])
 
   // Broadcast activity to other tabs when this tab has activity (IST-REQ-005)
   // Only broadcast when idleTimeRemaining jumps UP (indicating a timer reset),
@@ -275,7 +276,7 @@ export default function IdleSessionProvider({
       loggedOutRef.current = false
       setState('active')
       resetIdleActivity()
-      setWarningCountdown(msToSeconds(warningLeadMs))
+      setWarningCountdown(msToSeconds(resolvedWarningLeadMs))
     }
 
     // Broadcast authenticated→unauthenticated transition (e.g., manual logout 
from user menu)
@@ -285,7 +286,7 @@ export default function IdleSessionProvider({
     }
 
     wasAuthenticatedRef.current = isAuthenticated
-  }, [isAuthenticated, resetIdleActivity, sendMessage, warningLeadMs])
+  }, [isAuthenticated, resetIdleActivity, sendMessage, resolvedWarningLeadMs])
 
   // Context value
   const contextValue = {
diff --git a/web-v2/web/src/lib/provider/sessionTimeoutConfig.js 
b/web-v2/web/src/lib/provider/sessionTimeoutConfig.js
new file mode 100644
index 0000000000..d325968056
--- /dev/null
+++ b/web-v2/web/src/lib/provider/sessionTimeoutConfig.js
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+const SESSION_IDLE_TIMEOUT_KEY = 'gravitino.ui.sessionIdleTimeoutMs'
+
+const SESSION_MAX_DURATION_KEY = 'gravitino.ui.sessionMaxDurationMs'
+
+const SESSION_IDLE_WARNING_LEAD_KEY = 'gravitino.ui.sessionIdleWarningLeadMs'
+
+function resolveSessionDuration(serverValue, envValue, defaultValue) {
+  const serverDuration = Number(serverValue)
+  if (Number.isFinite(serverDuration) && serverDuration > 0) {
+    return serverDuration
+  }
+
+  const envDuration = Number(envValue)
+
+  return Number.isFinite(envDuration) && envDuration > 0 ? envDuration : 
defaultValue
+}
+
+/**
+ * Resolves all UI session timeout settings.
+ *
+ * @param {Object} systemConfig values returned by the /configs endpoint
+ * @param {Object} defaults built-in fallback values
+ * @returns {{idleTimeoutMs: number, warningLeadMs: number, 
maxSessionDurationMs: number}}
+ */
+export function resolveSessionTimeouts(systemConfig, defaults) {
+  return {
+    idleTimeoutMs: resolveSessionDuration(
+      systemConfig?.[SESSION_IDLE_TIMEOUT_KEY],
+      process.env.NEXT_PUBLIC_IDLE_TIMEOUT_MS,
+      defaults.idleTimeoutMs
+    ),
+    warningLeadMs: resolveSessionDuration(
+      systemConfig?.[SESSION_IDLE_WARNING_LEAD_KEY],
+      process.env.NEXT_PUBLIC_IDLE_WARNING_LEAD_MS,
+      defaults.warningLeadMs
+    ),
+    maxSessionDurationMs: resolveSessionDuration(
+      systemConfig?.[SESSION_MAX_DURATION_KEY],
+      process.env.NEXT_PUBLIC_MAX_SESSION_DURATION_MS,
+      defaults.maxSessionDurationMs
+    )
+  }
+}

Reply via email to