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]

Reply via email to