This is an automated email from the ASF dual-hosted git repository.
yao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new e8f281daea0d [SPARK-55834][UI] Use Bootstrap 5 nav-tabs for
Environment page sections
e8f281daea0d is described below
commit e8f281daea0d01b7d43357d2ccbab2ff210d80e5
Author: Kent Yao <[email protected]>
AuthorDate: Thu Mar 5 12:46:13 2026 +0800
[SPARK-55834][UI] Use Bootstrap 5 nav-tabs for Environment page sections
### What changes were proposed in this pull request?
Replace the 7 stacked collapsible sections on the Environment page with
Bootstrap 5 nav-tabs. Each tab displays a badge count showing the number of
entries in that section. The active tab state is persisted in `localStorage` so
it survives page refreshes.
### Why are the changes needed?
The collapsible accordion layout requires users to scroll through all
sections to find the one they need. Nav-tabs provide a cleaner, more navigable
UI where only one section is visible at a time, and badge counts let users see
at a glance how many entries each section contains.
### Does this PR introduce _any_ user-facing change?
Yes. The Environment page now uses tab navigation instead of collapsible
sections.
### How was this patch tested?
- `./dev/scalastyle` — passed
- `./dev/lint-js` — passed
- `./build/sbt core/compile` — passed
### Was this patch authored or co-authored using generative AI tooling?
Yes.
Closes #54623 from yaooqinn/SPARK-55834.
Authored-by: Kent Yao <[email protected]>
Signed-off-by: Kent Yao <[email protected]>
---
.../org/apache/spark/ui/static/environmentpage.js | 17 ++
.../resources/org/apache/spark/ui/static/webui.css | 4 +
.../org/apache/spark/ui/env/EnvironmentPage.scala | 182 ++++++++++-----------
3 files changed, 109 insertions(+), 94 deletions(-)
diff --git
a/core/src/main/resources/org/apache/spark/ui/static/environmentpage.js
b/core/src/main/resources/org/apache/spark/ui/static/environmentpage.js
index 943c120f490a..28df9824c9b2 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/environmentpage.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/environmentpage.js
@@ -18,6 +18,23 @@
/* global $ */
$(document).ready(function(){
+ // Tab state persistence
+ var storedTab = localStorage.getItem("env-active-tab");
+ if (storedTab) {
+ var el = document.getElementById(storedTab);
+ if (el) {
+ bootstrap.Tab.getOrCreateInstance(el).show();
+ }
+ }
+
+ document.querySelectorAll('#envTabs button[data-bs-toggle="pill"]')
+ .forEach(function(tabEl) {
+ tabEl.addEventListener("shown.bs.tab", function(event) {
+ localStorage.setItem("env-active-tab", event.target.id);
+ });
+ });
+
+ // Column filter functionality
$('th').on('click', function(e) {
let inputBox = $(this).find('.env-table-filter-input');
if (inputBox.length === 0) {
diff --git a/core/src/main/resources/org/apache/spark/ui/static/webui.css
b/core/src/main/resources/org/apache/spark/ui/static/webui.css
index 5431310d67d8..1f4d324cf246 100755
--- a/core/src/main/resources/org/apache/spark/ui/static/webui.css
+++ b/core/src/main/resources/org/apache/spark/ui/static/webui.css
@@ -492,3 +492,7 @@ a.downloadbutton {
.offcanvas-resize-handle.resizing {
background-color: rgba(13, 110, 253, 0.3);
}
+
+.tab-content {
+ margin-top: 10px;
+}
diff --git a/core/src/main/scala/org/apache/spark/ui/env/EnvironmentPage.scala
b/core/src/main/scala/org/apache/spark/ui/env/EnvironmentPage.scala
index 56ac8618204d..423475859d59 100644
--- a/core/src/main/scala/org/apache/spark/ui/env/EnvironmentPage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/env/EnvironmentPage.scala
@@ -74,109 +74,103 @@ private[ui] class EnvironmentPage(
val runtimeInformationTable = UIUtils.listingTable(
propertyHeader, jvmRow, jvmInformation.toSeq.sorted, fixedWidth = true,
headerClasses = headerClasses)
+ val sparkProperties = Utils.redact(conf, appEnv.sparkProperties.sorted)
val sparkPropertiesTable = UIUtils.listingTable(propertyHeader,
propertyRow,
- Utils.redact(conf, appEnv.sparkProperties.sorted), fixedWidth = true,
- headerClasses = headerClasses)
+ sparkProperties, fixedWidth = true, headerClasses = headerClasses)
val emptyProperties = collection.Seq.empty[(String, String)]
+ val hadoopProperties =
+ Utils.redact(conf,
Option(appEnv.hadoopProperties).getOrElse(emptyProperties).sorted)
val hadoopPropertiesTable = UIUtils.listingTable(propertyHeader,
propertyRow,
- Utils.redact(conf,
Option(appEnv.hadoopProperties).getOrElse(emptyProperties).sorted),
- fixedWidth = true, headerClasses = headerClasses)
+ hadoopProperties, fixedWidth = true, headerClasses = headerClasses)
+ val systemProperties = Utils.redact(conf, appEnv.systemProperties.sorted)
val systemPropertiesTable = UIUtils.listingTable(propertyHeader,
propertyRow,
- Utils.redact(conf, appEnv.systemProperties.sorted), fixedWidth = true,
- headerClasses = headerClasses)
+ systemProperties, fixedWidth = true, headerClasses = headerClasses)
+ val metricsProperties =
+ Utils.redact(conf,
Option(appEnv.metricsProperties).getOrElse(emptyProperties).sorted)
val metricsPropertiesTable = UIUtils.listingTable(propertyHeader,
propertyRow,
- Utils.redact(conf,
Option(appEnv.metricsProperties).getOrElse(emptyProperties).sorted),
- fixedWidth = true, headerClasses = headerClasses)
+ metricsProperties, fixedWidth = true, headerClasses = headerClasses)
+ val classpathEntries = appEnv.classpathEntries.sorted
val classpathEntriesTable = UIUtils.listingTable(
- classPathHeader, classPathRow, appEnv.classpathEntries.sorted,
fixedWidth = true,
+ classPathHeader, classPathRow, classpathEntries, fixedWidth = true,
headerClasses = headerClasses)
val content =
<span>
- <span class="collapse-table" data-bs-toggle="collapse"
- data-bs-target="#aggregated-runtimeInformation"
- aria-expanded="true" aria-controls="aggregated-runtimeInformation"
- data-collapse-name="collapse-aggregated-runtimeInformation">
- <h4>
- <span class="collapse-table-arrow arrow-open"></span>
- <a>Runtime Information</a>
- </h4>
- </span>
- <div class="collapsible-table collapse show"
id="aggregated-runtimeInformation">
- {runtimeInformationTable}
- </div>
- <span class="collapse-table" data-bs-toggle="collapse"
- data-bs-target="#aggregated-sparkProperties"
- aria-expanded="true" aria-controls="aggregated-sparkProperties"
- data-collapse-name="collapse-aggregated-sparkProperties">
- <h4>
- <span class="collapse-table-arrow arrow-open"></span>
- <a>Spark Properties</a>
- </h4>
- </span>
- <div class="collapsible-table collapse show"
id="aggregated-sparkProperties">
- {sparkPropertiesTable}
- </div>
- <span class="collapse-table" data-bs-toggle="collapse"
- data-bs-target="#aggregated-execResourceProfileInformation"
- aria-expanded="true"
- aria-controls="aggregated-execResourceProfileInformation"
-
data-collapse-name="collapse-aggregated-execResourceProfileInformation">
- <h4>
- <span class="collapse-table-arrow arrow-open"></span>
- <a>Resource Profiles</a>
- </h4>
- </span>
- <div class="collapsible-table collapse show"
- id="aggregated-execResourceProfileInformation">
- {resourceProfileInformationTable}
- </div>
- <span class="collapse-table" data-bs-toggle="collapse"
- data-bs-target="#aggregated-hadoopProperties"
- aria-expanded="false" aria-controls="aggregated-hadoopProperties"
- data-collapse-name="collapse-aggregated-hadoopProperties">
- <h4>
- <span class="collapse-table-arrow arrow-closed"></span>
- <a>Hadoop Properties</a>
- </h4>
- </span>
- <div class="collapsible-table collapse"
id="aggregated-hadoopProperties">
- {hadoopPropertiesTable}
- </div>
- <span class="collapse-table" data-bs-toggle="collapse"
- data-bs-target="#aggregated-systemProperties"
- aria-expanded="false" aria-controls="aggregated-systemProperties"
- data-collapse-name="collapse-aggregated-systemProperties">
- <h4>
- <span class="collapse-table-arrow arrow-closed"></span>
- <a>System Properties</a>
- </h4>
- </span>
- <div class="collapsible-table collapse"
id="aggregated-systemProperties">
- {systemPropertiesTable}
- </div>
- <span class="collapse-table" data-bs-toggle="collapse"
- data-bs-target="#aggregated-metricsProperties"
- aria-expanded="false" aria-controls="aggregated-metricsProperties"
- data-collapse-name="collapse-aggregated-metricsProperties">
- <h4>
- <span class="collapse-table-arrow arrow-closed"></span>
- <a>Metrics Properties</a>
- </h4>
- </span>
- <div class="collapsible-table collapse"
id="aggregated-metricsProperties">
- {metricsPropertiesTable}
- </div>
- <span class="collapse-table" data-bs-toggle="collapse"
- data-bs-target="#aggregated-classpathEntries"
- aria-expanded="false" aria-controls="aggregated-classpathEntries"
- data-collapse-name="collapse-aggregated-classpathEntries">
- <h4>
- <span class="collapse-table-arrow arrow-closed"></span>
- <a>Classpath Entries</a>
- </h4>
- </span>
- <div class="collapsible-table collapse"
id="aggregated-classpathEntries">
- {classpathEntriesTable}
+ <div class="d-flex align-items-start">
+ <div class="nav flex-column nav-pills me-3" id="envTabs"
role="tablist"
+ aria-orientation="vertical" style="min-width: 200px;">
+ <button class="nav-link active text-start" id="runtime-tab"
data-bs-toggle="pill"
+ data-bs-target="#runtime" type="button" role="tab"
+ aria-controls="runtime" aria-selected="true">
+ Runtime Information
+ <span class="badge bg-secondary
ms-1">{jvmInformation.size}</span>
+ </button>
+ <button class="nav-link text-start" id="spark-props-tab"
data-bs-toggle="pill"
+ data-bs-target="#spark-props" type="button" role="tab"
+ aria-controls="spark-props" aria-selected="false">
+ Spark Properties
+ <span class="badge bg-secondary
ms-1">{sparkProperties.size}</span>
+ </button>
+ <button class="nav-link text-start" id="resource-profiles-tab"
data-bs-toggle="pill"
+ data-bs-target="#resource-profiles" type="button"
role="tab"
+ aria-controls="resource-profiles" aria-selected="false">
+ Resource Profiles
+ <span class="badge bg-secondary
ms-1">{resourceProfileInfo.size}</span>
+ </button>
+ <button class="nav-link text-start" id="hadoop-props-tab"
data-bs-toggle="pill"
+ data-bs-target="#hadoop-props" type="button" role="tab"
+ aria-controls="hadoop-props" aria-selected="false">
+ Hadoop Properties
+ <span class="badge bg-secondary
ms-1">{hadoopProperties.size}</span>
+ </button>
+ <button class="nav-link text-start" id="system-props-tab"
data-bs-toggle="pill"
+ data-bs-target="#system-props" type="button" role="tab"
+ aria-controls="system-props" aria-selected="false">
+ System Properties
+ <span class="badge bg-secondary
ms-1">{systemProperties.size}</span>
+ </button>
+ <button class="nav-link text-start" id="metrics-props-tab"
data-bs-toggle="pill"
+ data-bs-target="#metrics-props" type="button" role="tab"
+ aria-controls="metrics-props" aria-selected="false">
+ Metrics Properties
+ <span class="badge bg-secondary
ms-1">{metricsProperties.size}</span>
+ </button>
+ <button class="nav-link text-start" id="classpath-tab"
data-bs-toggle="pill"
+ data-bs-target="#classpath" type="button" role="tab"
+ aria-controls="classpath" aria-selected="false">
+ Classpath Entries
+ <span class="badge bg-secondary
ms-1">{classpathEntries.size}</span>
+ </button>
+ </div>
+ <div class="tab-content flex-fill" id="envTabContent">
+ <div class="tab-pane fade show active" id="runtime" role="tabpanel"
+ aria-labelledby="runtime-tab">
+ {runtimeInformationTable}
+ </div>
+ <div class="tab-pane fade" id="spark-props" role="tabpanel"
+ aria-labelledby="spark-props-tab">
+ {sparkPropertiesTable}
+ </div>
+ <div class="tab-pane fade" id="resource-profiles" role="tabpanel"
+ aria-labelledby="resource-profiles-tab">
+ {resourceProfileInformationTable}
+ </div>
+ <div class="tab-pane fade" id="hadoop-props" role="tabpanel"
+ aria-labelledby="hadoop-props-tab">
+ {hadoopPropertiesTable}
+ </div>
+ <div class="tab-pane fade" id="system-props" role="tabpanel"
+ aria-labelledby="system-props-tab">
+ {systemPropertiesTable}
+ </div>
+ <div class="tab-pane fade" id="metrics-props" role="tabpanel"
+ aria-labelledby="metrics-props-tab">
+ {metricsPropertiesTable}
+ </div>
+ <div class="tab-pane fade" id="classpath" role="tabpanel"
+ aria-labelledby="classpath-tab">
+ {classpathEntriesTable}
+ </div>
+ </div>
</div>
<script src={UIUtils.prependBaseUri(request,
"/static/environmentpage.js")}></script>
</span>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]