This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new 60ad1522fd [Cherry-pick to branch-1.3] [#12588] improvement(web2-ui):
Expose UI session timeout settings through server configuration (#12621)
(#12780)
60ad1522fd is described below
commit 60ad1522fd767a13212663da2769563f97439437
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Sep 1 20:15:19 2026 +0800
[Cherry-pick to branch-1.3] [#12588] improvement(web2-ui): Expose UI
session timeout settings through server configuration (#12621) (#12780)
**Cherry-pick Information:**
- Original commit: cc5f05eef75d9e8fed51692816f67a9477d2fab8
- Target branch: `branch-1.3`
- Status: ✅ Clean cherry-pick (no conflicts)
Co-authored-by: Qian Xia <[email protected]>
---
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 8336246d6a..c1ebfeee90 100644
--- a/conf/gravitino.conf.template
+++ b/conf/gravitino.conf.template
@@ -41,6 +41,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
@@ -82,8 +93,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
+ )
+ }
+}