diff --git a/core/src/main/resources/org/apache/spark/ui/static/table.js b/core/src/main/resources/org/apache/spark/ui/static/table.js index b3aa85f64c5d3..1e99b13f7ccb9 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/table.js +++ b/core/src/main/resources/org/apache/spark/ui/static/table.js @@ -117,3 +117,29 @@ function collapseTableAndButton(thisName, table) { } } /* eslint-enable no-unused-vars */ + +// Event delegation for thread dump page (CSP-compliant) +$(function() { + // toggleThreadStackTrace on row click + $(document).on("click", "tr.accordion-heading[data-thread-id]", function() { + toggleThreadStackTrace($(this).data("thread-id"), false); + }); + + // expandAll / collapseAll + $(document).on("click", "[data-action=expandAllThreadStackTrace]", function() { + expandAllThreadStackTrace(true); + }); + $(document).on("click", "[data-action=collapseAllThreadStackTrace]", function() { + collapseAllThreadStackTrace($(this).data("toggle-button") !== false); + }); + + // onMouseOverAndOut + $(document).on("mouseenter mouseleave", "tr.accordion-heading[data-thread-id]", function() { + onMouseOverAndOut($(this).data("thread-id")); + }); + + // onSearchStringChange + $(document).on("input", "[data-search-input]", function() { + onSearchStringChange(); + }); +}); diff --git a/core/src/main/resources/org/apache/spark/ui/static/utils.js b/core/src/main/resources/org/apache/spark/ui/static/utils.js index 2d4123bc75ab8..ca5fbdc6b80c8 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/utils.js +++ b/core/src/main/resources/org/apache/spark/ui/static/utils.js @@ -237,7 +237,7 @@ function getBaseURI() { function detailsUINode(isMultiline, message) { if (isMultiline) { - const span = '+details'; + const span = '+details'; const pre = '
' + message + '
'; const div = ''; return span + div; diff --git a/core/src/main/resources/org/apache/spark/ui/static/webui.js b/core/src/main/resources/org/apache/spark/ui/static/webui.js index 4c7cf8c8ea90a..6d20c16ffaf4e 100644 --- a/core/src/main/resources/org/apache/spark/ui/static/webui.js +++ b/core/src/main/resources/org/apache/spark/ui/static/webui.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* global $ */ +/* global $, collapseTableAndButton, loadMore, loadNew, toggleDagViz, togglePlanViz, clickPhysicalPlanDetails */ /* eslint-disable no-unused-vars */ var uiRoot = ""; var appBasePath = ""; @@ -111,3 +111,53 @@ $(function() { $(this).toggleClass("description-input-full"); }); }); + +// Event delegation for CSP-compliant inline event handler replacement. +$(function() { + // collapseTable / collapseTableAndButton + $(document).on("click", "[data-collapse-name]", function() { + var name = $(this).data("collapse-name"); + var table = $(this).data("collapse-table"); + if ($(this).data("collapse-button")) { + collapseTableAndButton(name, table); + } else { + collapseTable(name, table); + } + }); + + // toggle details (stage-details, stacktrace-details, expand-details) + $(document).on("click", "[data-toggle-details]", function() { + var selector = $(this).data("toggle-details"); + this.parentNode.querySelector(selector).classList.toggle("collapsed"); + }); + + // toggle sub-execution list (tr two siblings away from parent tr) + $(document).on("click", "[data-toggle-sub-execution]", function() { + $(this).closest("tr").nextAll("tr.sub-execution-list").first().toggleClass("collapsed"); + }); + + // kill links with confirmation + $(document).on("click", "a.kill-link[data-kill-message]", function(e) { + if (!window.confirm($(this).data("kill-message"))) { + e.preventDefault(); + } else if ($(this).closest("form").length > 0) { + e.preventDefault(); + $(this).closest("form").submit(); + } + }); + + // loadMore / loadNew buttons + $(document).on("click", ".log-more-btn", function() { loadMore(); }); + $(document).on("click", ".log-new-btn", function() { loadNew(); }); + + // toggleDagViz + $(document).on("click", ".expand-dag-viz[data-forjob]", function() { + toggleDagViz($(this).data("forjob")); + }); + + // togglePlanViz / clickPhysicalPlanDetails + $(document).on("click", "[data-action=togglePlanViz]", function() { togglePlanViz(); }); + $(document).on("click", "[data-action=clickPhysicalPlanDetails]", function() { + clickPhysicalPlanDetails(); + }); +}); diff --git a/core/src/main/scala/org/apache/spark/deploy/history/HistoryPage.scala b/core/src/main/scala/org/apache/spark/deploy/history/HistoryPage.scala index 4eeddd7cc7098..f0131ba0081f4 100644 --- a/core/src/main/scala/org/apache/spark/deploy/history/HistoryPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/history/HistoryPage.scala @@ -22,7 +22,7 @@ import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest import org.apache.spark.status.api.v1.ApplicationInfo -import org.apache.spark.ui.{UIUtils, WebUIPage} +import org.apache.spark.ui.{CspNonce, UIUtils, WebUIPage} import org.apache.spark.ui.UIUtils.formatImportJavaScript private[history] class HistoryPage(parent: HistoryServer) extends WebUIPage("") { @@ -74,7 +74,8 @@ private[history] class HistoryPage(parent: HistoryServer) extends WebUIPage("") request, "/static/dataTables.rowsGroup.js")}> ++ ++ - ++
+ ++ +
} else if (requestedIncomplete) {

No incomplete applications found!

} else if (eventLogsUnderProcessCount > 0) { diff --git a/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala index 3507ebd101aec..c7f02ea0ab36f 100644 --- a/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/history/LogPage.scala @@ -24,7 +24,7 @@ import jakarta.servlet.http.HttpServletRequest import org.apache.spark.SparkConf import org.apache.spark.deploy.Utils.{getLog, DEFAULT_BYTES} import org.apache.spark.internal.Logging -import org.apache.spark.ui.{UIUtils, WebUIPage} +import org.apache.spark.ui.{CspNonce, UIUtils, WebUIPage} private[history] class LogPage(conf: SparkConf) extends WebUIPage("logPage") with Logging { def render(request: HttpServletRequest): Seq[Node] = { @@ -42,12 +42,12 @@ private[history] class LogPage(conf: SparkConf) extends WebUIPage("logPage") wit val moreButton = - val newButton = - @@ -71,7 +71,7 @@ private[history] class LogPage(conf: SparkConf) extends WebUIPage("logPage") wit {alert}
{newButton}
- + UIUtils.basicSparkPage(request, content, logType + " log page for history server") diff --git a/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala b/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala index 1a46688022341..5483d30de70d7 100644 --- a/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala @@ -116,7 +116,8 @@ private[ui] class ApplicationPage(parent: MasterWebUI) extends WebUIPage("app")
+ data-collapse-name="collapse-aggregated-executors" + data-collapse-table="aggregated-executors">

Executor Summary ({allExecutors.length}) @@ -128,8 +129,8 @@ private[ui] class ApplicationPage(parent: MasterWebUI) extends WebUIPage("app") { if (removedExecutors.nonEmpty) { + data-collapse-name="collapse-aggregated-removedExecutors" + data-collapse-table="aggregated-removedExecutors">

Removed Executors ({removedExecutors.length}) diff --git a/core/src/main/scala/org/apache/spark/deploy/master/ui/EnvironmentPage.scala b/core/src/main/scala/org/apache/spark/deploy/master/ui/EnvironmentPage.scala index 977f8cfae75ef..396e33e7a6752 100644 --- a/core/src/main/scala/org/apache/spark/deploy/master/ui/EnvironmentPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/master/ui/EnvironmentPage.scala @@ -66,8 +66,8 @@ private[ui] class EnvironmentPage(

+ data-collapse-name="collapse-aggregated-runtimeInformation" + data-collapse-table="aggregated-runtimeInformation">

Runtime Information @@ -77,8 +77,8 @@ private[ui] class EnvironmentPage( {runtimeInformationTable}

+ data-collapse-name="collapse-aggregated-sparkProperties" + data-collapse-table="aggregated-sparkProperties">

Spark Properties @@ -88,8 +88,8 @@ private[ui] class EnvironmentPage( {sparkPropertiesTable} + data-collapse-name="collapse-aggregated-hadoopProperties" + data-collapse-table="aggregated-hadoopProperties">

Hadoop Properties @@ -99,8 +99,8 @@ private[ui] class EnvironmentPage( {hadoopPropertiesTable} + data-collapse-name="collapse-aggregated-systemProperties" + data-collapse-table="aggregated-systemProperties">

System Properties @@ -110,8 +110,8 @@ private[ui] class EnvironmentPage( {systemPropertiesTable} + data-collapse-name="collapse-aggregated-metricsProperties" + data-collapse-table="aggregated-metricsProperties">

Metrics Properties @@ -121,8 +121,8 @@ private[ui] class EnvironmentPage( {metricsPropertiesTable} + data-collapse-name="collapse-aggregated-classpathEntries" + data-collapse-table="aggregated-classpathEntries">

Classpath Entries @@ -132,8 +132,8 @@ private[ui] class EnvironmentPage( {classpathEntriesTable} + data-collapse-name="collapse-aggregated-environmentVariables" + data-collapse-table="aggregated-environmentVariables">

Environment Variables diff --git a/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala index 6d3ff1906d17b..7d2613ad5fa35 100644 --- a/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/master/ui/LogPage.scala @@ -23,7 +23,7 @@ import jakarta.servlet.http.HttpServletRequest import org.apache.spark.deploy.Utils.{getLog, DEFAULT_BYTES} import org.apache.spark.internal.Logging -import org.apache.spark.ui.{UIUtils, WebUIPage} +import org.apache.spark.ui.{CspNonce, UIUtils, WebUIPage} private[ui] class LogPage(parent: MasterWebUI) extends WebUIPage("logPage") with Logging { def render(request: HttpServletRequest): Seq[Node] = { @@ -41,12 +41,12 @@ private[ui] class LogPage(parent: MasterWebUI) extends WebUIPage("logPage") with val moreButton = - val newButton = - @@ -70,7 +70,7 @@ private[ui] class LogPage(parent: MasterWebUI) extends WebUIPage("logPage") with {alert}
{newButton}
- + UIUtils.basicSparkPage(request, content, logType + " log page for master") diff --git a/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterPage.scala b/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterPage.scala index a396444ebe9c5..b33fdc4939aca 100644 --- a/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterPage.scala @@ -182,7 +182,8 @@ private[ui] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
+ data-collapse-name="collapse-aggregated-workers" + data-collapse-table="aggregated-workers">

Workers ({workers.length}) @@ -197,7 +198,8 @@ private[ui] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
+ data-collapse-name="collapse-aggregated-activeApps" + data-collapse-table="aggregated-activeApps">

Running Applications ({activeApps.length}) @@ -214,8 +216,8 @@ private[ui] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
+ data-collapse-name="collapse-aggregated-activeDrivers" + data-collapse-table="aggregated-activeDrivers">

Running Drivers ({activeDrivers.length}) @@ -233,8 +235,8 @@ private[ui] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
+ data-collapse-name="collapse-aggregated-completedApps" + data-collapse-table="aggregated-completedApps">

Completed Applications ({completedApps.length}) @@ -252,8 +254,8 @@ private[ui] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
+ data-collapse-name="collapse-aggregated-completedDrivers" + data-collapse-table="aggregated-completedDrivers">

Completed Drivers ({completedDrivers.length}) @@ -300,13 +302,12 @@ private[ui] class MasterPage(parent: MasterWebUI) extends WebUIPage("") { private def appRow(app: ApplicationInfo): Seq[Node] = { val killLink = if (parent.killEnabled && (app.state == ApplicationState.RUNNING || app.state == ApplicationState.WAITING)) { - val confirm = - s"if (window.confirm('Are you sure you want to kill application ${app.id} ?')) " + - "{ this.parentNode.submit(); return true; } else { return false; }"
- (kill) + (kill)
} @@ -350,13 +351,12 @@ private[ui] class MasterPage(parent: MasterWebUI) extends WebUIPage("") { val killLink = if (parent.killEnabled && (driver.state == DriverState.RUNNING || driver.state == DriverState.SUBMITTED)) { - val confirm = - s"if (window.confirm('Are you sure you want to kill driver ${driver.id} ?')) " + - "{ this.parentNode.submit(); return true; } else { return false; }"
- (kill) + (kill)
} diff --git a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala index bdc143be91b94..06fe1f841613c 100644 --- a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala @@ -25,7 +25,7 @@ import jakarta.servlet.http.HttpServletRequest import org.apache.spark.internal.Logging import org.apache.spark.internal.LogKeys.{LOG_TYPE, PATH} -import org.apache.spark.ui.{UIUtils, WebUIPage} +import org.apache.spark.ui.{CspNonce, UIUtils, WebUIPage} import org.apache.spark.util.Utils import org.apache.spark.util.logging.RollingFileAppender @@ -91,12 +91,12 @@ private[ui] class LogPage(parent: WorkerWebUI) extends WebUIPage("logPage") with val moreButton = - val newButton = - @@ -120,7 +120,7 @@ private[ui] class LogPage(parent: WorkerWebUI) extends WebUIPage("logPage") with {alert}
{newButton}

- +
UIUtils.basicSparkPage(request, content, logType + " log page for " + pageName) diff --git a/core/src/main/scala/org/apache/spark/deploy/worker/ui/WorkerPage.scala b/core/src/main/scala/org/apache/spark/deploy/worker/ui/WorkerPage.scala index bfc402811451f..bf55a588d548e 100644 --- a/core/src/main/scala/org/apache/spark/deploy/worker/ui/WorkerPage.scala +++ b/core/src/main/scala/org/apache/spark/deploy/worker/ui/WorkerPage.scala @@ -101,8 +101,8 @@ private[ui] class WorkerPage(parent: WorkerWebUI) extends WebUIPage("") {
+ data-collapse-name="collapse-aggregated-runningExecutors" + data-collapse-table="aggregated-runningExecutors">

Running Executors ({runningExecutors.size}) @@ -114,8 +114,8 @@ private[ui] class WorkerPage(parent: WorkerWebUI) extends WebUIPage("") { { if (runningDrivers.nonEmpty) { + data-collapse-name="collapse-aggregated-runningDrivers" + data-collapse-table="aggregated-runningDrivers">

Running Drivers ({runningDrivers.size}) @@ -129,8 +129,8 @@ private[ui] class WorkerPage(parent: WorkerWebUI) extends WebUIPage("") { { if (finishedExecutors.nonEmpty) { + data-collapse-name="collapse-aggregated-finishedExecutors" + data-collapse-table="aggregated-finishedExecutors">

Finished Executors ({finishedExecutors.size}) @@ -144,8 +144,8 @@ private[ui] class WorkerPage(parent: WorkerWebUI) extends WebUIPage("") { { if (finishedDrivers.nonEmpty) { + data-collapse-name="collapse-aggregated-finishedDrivers" + data-collapse-table="aggregated-finishedDrivers">

Finished Drivers ({finishedDrivers.size}) diff --git a/core/src/main/scala/org/apache/spark/ui/CspNonce.scala b/core/src/main/scala/org/apache/spark/ui/CspNonce.scala new file mode 100644 index 0000000000000..97d8b811bbd52 --- /dev/null +++ b/core/src/main/scala/org/apache/spark/ui/CspNonce.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package org.apache.spark.ui + +import java.util.UUID + +/** + * Thread-local storage for CSP nonce values. + * + * A nonce is generated per request in [[HttpSecurityFilter]] and used by UI pages + * to mark inline scripts and styles as trusted. + */ +private[spark] object CspNonce { + + private val nonce = new ThreadLocal[String] + + /** Generate a new nonce and store it in the current thread. */ + def generate(): String = { + val value = UUID.randomUUID().toString + nonce.set(value) + value + } + + /** Get the nonce for the current request. */ + def get: String = nonce.get() + + /** Remove the nonce after the request is complete. */ + def clear(): Unit = nonce.remove() +} diff --git a/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala b/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala index dca85b53178ad..294e9491c04df 100644 --- a/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/DriverLogPage.scala @@ -56,12 +56,12 @@ private[ui] class DriverLogPage( val moreButton = - val newButton = - @@ -85,7 +85,7 @@ private[ui] class DriverLogPage( {alert}
{newButton}

- +
UIUtils.headerSparkPage(request, "Logs", content, parent) diff --git a/core/src/main/scala/org/apache/spark/ui/GraphUIData.scala b/core/src/main/scala/org/apache/spark/ui/GraphUIData.scala index 9dcbb9d3c3297..215f0f0472699 100644 --- a/core/src/main/scala/org/apache/spark/ui/GraphUIData.scala +++ b/core/src/main/scala/org/apache/spark/ui/GraphUIData.scala @@ -177,6 +177,6 @@ private[spark] class JsCollector(req: HttpServletRequest) { | ${statements.mkString("\n")} |});""".stripMargin - + } } diff --git a/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala b/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala index 2c5a74d2e6c6c..a7e0bb683dd06 100644 --- a/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala +++ b/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala @@ -48,48 +48,57 @@ private class HttpSecurityFilter( val hreq = req.asInstanceOf[HttpServletRequest] val hres = res.asInstanceOf[HttpServletResponse] hres.setHeader("Cache-Control", "no-cache, no-store, must-revalidate") - hres.setHeader("Content-Security-Policy", "default-src 'self'") - - val requestUser = hreq.getRemoteUser() - - // The doAs parameter allows proxy servers (e.g. Knox) to impersonate other users. For - // that to be allowed, the authenticated user needs to be an admin. - val effectiveUser = Option(hreq.getParameter("doAs")) - .map { proxy => - if (requestUser != proxy && !securityMgr.checkAdminPermissions(requestUser)) { - hres.sendError(HttpServletResponse.SC_FORBIDDEN, - s"User $requestUser is not allowed to impersonate others.") - return + + val cspNonce = CspNonce.generate() + try { + hres.setHeader("Content-Security-Policy", + s"default-src 'self'; script-src 'self' 'nonce-$cspNonce'; " + + s"style-src 'self' 'unsafe-inline'; img-src 'self' data:; " + + s"object-src 'none'; base-uri 'self';") + + val requestUser = hreq.getRemoteUser() + + // The doAs parameter allows proxy servers (e.g. Knox) to impersonate other users. For + // that to be allowed, the authenticated user needs to be an admin. + val effectiveUser = Option(hreq.getParameter("doAs")) + .map { proxy => + if (requestUser != proxy && !securityMgr.checkAdminPermissions(requestUser)) { + hres.sendError(HttpServletResponse.SC_FORBIDDEN, + s"User $requestUser is not allowed to impersonate others.") + return + } + proxy } - proxy + .getOrElse(requestUser) + + if (!securityMgr.checkUIViewPermissions(effectiveUser)) { + hres.sendError(HttpServletResponse.SC_FORBIDDEN, + s"User $effectiveUser is not authorized to access this page.") + return } - .getOrElse(requestUser) - if (!securityMgr.checkUIViewPermissions(effectiveUser)) { - hres.sendError(HttpServletResponse.SC_FORBIDDEN, - s"User $effectiveUser is not authorized to access this page.") - return - } + // SPARK-10589 avoid frame-related click-jacking vulnerability, using X-Frame-Options + // (see http://tools.ietf.org/html/rfc7034). By default allow framing only from the + // same origin, but allow framing for a specific named URI. + // Example: spark.ui.allowFramingFrom = https://example.com/ + val xFrameOptionsValue = conf.getOption("spark.ui.allowFramingFrom") + .map { uri => s"ALLOW-FROM $uri" } + .getOrElse("SAMEORIGIN") + + hres.setHeader("X-Frame-Options", xFrameOptionsValue) + hres.setHeader("X-XSS-Protection", conf.get(UI_X_XSS_PROTECTION)) + if (conf.get(UI_X_CONTENT_TYPE_OPTIONS)) { + hres.setHeader("X-Content-Type-Options", "nosniff") + } + if (hreq.getScheme() == "https") { + conf.get(UI_STRICT_TRANSPORT_SECURITY).foreach( + hres.setHeader("Strict-Transport-Security", _)) + } - // SPARK-10589 avoid frame-related click-jacking vulnerability, using X-Frame-Options - // (see http://tools.ietf.org/html/rfc7034). By default allow framing only from the - // same origin, but allow framing for a specific named URI. - // Example: spark.ui.allowFramingFrom = https://example.com/ - val xFrameOptionsValue = conf.getOption("spark.ui.allowFramingFrom") - .map { uri => s"ALLOW-FROM $uri" } - .getOrElse("SAMEORIGIN") - - hres.setHeader("X-Frame-Options", xFrameOptionsValue) - hres.setHeader("X-XSS-Protection", conf.get(UI_X_XSS_PROTECTION)) - if (conf.get(UI_X_CONTENT_TYPE_OPTIONS)) { - hres.setHeader("X-Content-Type-Options", "nosniff") - } - if (hreq.getScheme() == "https") { - conf.get(UI_STRICT_TRANSPORT_SECURITY).foreach( - hres.setHeader("Strict-Transport-Security", _)) + chain.doFilter(new XssSafeRequest(hreq, effectiveUser), res) + } finally { + CspNonce.clear() } - - chain.doFilter(new XssSafeRequest(hreq, effectiveUser), res) } } diff --git a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala index 35b00daefdb5f..e84adbe710cb3 100644 --- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala +++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala @@ -232,7 +232,7 @@ private[spark] object UIUtils extends Logging { - + } def vizHeaderNodes(request: HttpServletRequest): Seq[Node] = { @@ -283,7 +283,7 @@ private[spark] object UIUtils extends Logging { {commonHeaderNodes(request)} - + {if (showVisualization) vizHeaderNodes(request) else Seq.empty} {if (useDataTables) dataTablesHeaderNodes(request) else Seq.empty} + class="expand-dag-viz" data-forjob={forJob.toString}> @@ -703,7 +703,7 @@ private[spark] object UIUtils extends Logging { def detailsUINode(isMultiline: Boolean, message: String): Seq[Node] = { if (isMultiline) { // scalastyle:off - +details ++ 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 df504e4cc8ef2..7994f6983ba69 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 @@ -93,8 +93,8 @@ private[ui] class EnvironmentPage( val content = + data-collapse-name="collapse-aggregated-runtimeInformation" + data-collapse-table="aggregated-runtimeInformation">

Runtime Information @@ -104,8 +104,8 @@ private[ui] class EnvironmentPage( {runtimeInformationTable}

+ data-collapse-name="collapse-aggregated-sparkProperties" + data-collapse-table="aggregated-sparkProperties">

Spark Properties @@ -115,8 +115,8 @@ private[ui] class EnvironmentPage( {sparkPropertiesTable}

+ data-collapse-name="collapse-aggregated-execResourceProfileInformation" + data-collapse-table="aggregated-execResourceProfileInformation">

Resource Profiles @@ -126,8 +126,8 @@ private[ui] class EnvironmentPage( {resourceProfileInformationTable}

+ data-collapse-name="collapse-aggregated-hadoopProperties" + data-collapse-table="aggregated-hadoopProperties">

Hadoop Properties @@ -137,8 +137,8 @@ private[ui] class EnvironmentPage( {hadoopPropertiesTable}

+ data-collapse-name="collapse-aggregated-systemProperties" + data-collapse-table="aggregated-systemProperties">

System Properties @@ -148,8 +148,8 @@ private[ui] class EnvironmentPage( {systemPropertiesTable}

+ data-collapse-name="collapse-aggregated-metricsProperties" + data-collapse-table="aggregated-metricsProperties">

Metrics Properties @@ -159,8 +159,8 @@ private[ui] class EnvironmentPage( {metricsPropertiesTable}

+ data-collapse-name="collapse-aggregated-classpathEntries" + data-collapse-table="aggregated-classpathEntries">

Classpath Entries diff --git a/core/src/main/scala/org/apache/spark/ui/exec/ExecutorThreadDumpPage.scala b/core/src/main/scala/org/apache/spark/ui/exec/ExecutorThreadDumpPage.scala index fa10c8937144c..000a88c0ac534 100644 --- a/core/src/main/scala/org/apache/spark/ui/exec/ExecutorThreadDumpPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/exec/ExecutorThreadDumpPage.scala @@ -24,7 +24,7 @@ import jakarta.servlet.http.HttpServletRequest import org.apache.spark.SparkContext import org.apache.spark.internal.config.UI.UI_FLAMEGRAPH_ENABLED import org.apache.spark.status.api.v1.ThreadStackTrace -import org.apache.spark.ui.{SparkUITab, UIUtils, WebUIPage} +import org.apache.spark.ui.{CspNonce, SparkUITab, UIUtils, WebUIPage} import org.apache.spark.ui.UIUtils.{formatImportJavaScript, prependBaseUri} import org.apache.spark.ui.flamegraph.FlamegraphNode @@ -59,9 +59,7 @@ private[ui] class ExecutorThreadDumpPage( val heldLocks = (synchronizers ++ monitors).mkString(", ") + data-thread-id={threadId.toString}> {threadId} {thread.threadName} {thread.threadState} @@ -83,22 +81,25 @@ private[ui] class ExecutorThreadDumpPage( { // scalastyle:off

- +

Thread Stack Trace

- Expand All - Collapse All + Expand All + Collapse All Download
- +
@@ -108,10 +109,10 @@ private[ui] class ExecutorThreadDumpPage( } - - - - + + +
Thread IDThread NameThread State + Thread IDThread NameThread State Thread Locks @@ -149,7 +150,7 @@ private[ui] class ExecutorThreadDumpPage( - + } @@ -158,7 +159,9 @@ private[ui] class ExecutorThreadDumpPage( private def threadDumpSummary(threadDump: Array[ThreadStackTrace]): Seq[Node] = { val totalCount = threadDump.length
- +

Thread Dump Summary: { totalCount } diff --git a/core/src/main/scala/org/apache/spark/ui/exec/ExecutorsTab.scala b/core/src/main/scala/org/apache/spark/ui/exec/ExecutorsTab.scala index db18b02f23d83..5b09fc6eb04a6 100644 --- a/core/src/main/scala/org/apache/spark/ui/exec/ExecutorsTab.scala +++ b/core/src/main/scala/org/apache/spark/ui/exec/ExecutorsTab.scala @@ -22,7 +22,7 @@ import scala.xml.{Node, Unparsed} import jakarta.servlet.http.HttpServletRequest import org.apache.spark.internal.config.UI._ -import org.apache.spark.ui.{SparkUI, SparkUITab, UIUtils, WebUIPage} +import org.apache.spark.ui.{CspNonce, SparkUI, SparkUITab, UIUtils, WebUIPage} private[ui] class ExecutorsTab(parent: SparkUI) extends SparkUITab(parent, "executors") { @@ -70,7 +70,7 @@ private[ui] class ExecutorsPage( ++ ++ - + } UIUtils.headerSparkPage(request, "Executors", content, parent, useDataTables = true) diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala index 4d65aba31021d..a1a9fff53f64a 100644 --- a/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala @@ -235,7 +235,7 @@ private[ui] class AllJobsPage(parent: JobsTab, store: AppStatusStore) extends We

++ - @@ -380,7 +380,8 @@ private[ui] class AllJobsPage(parent: JobsTab, store: AppStatusStore) extends We if (shouldShowActiveJobs) { content ++= + data-collapse-name="collapse-aggregated-activeJobs" + data-collapse-table="aggregated-activeJobs">

Active Jobs ({activeJobs.size}) @@ -393,7 +394,8 @@ private[ui] class AllJobsPage(parent: JobsTab, store: AppStatusStore) extends We if (shouldShowCompletedJobs) { content ++= + data-collapse-name="collapse-aggregated-completedJobs" + data-collapse-table="aggregated-completedJobs">

Completed Jobs ({completedJobNumStr}) @@ -406,7 +408,8 @@ private[ui] class AllJobsPage(parent: JobsTab, store: AppStatusStore) extends We if (shouldShowFailedJobs) { content ++= + data-collapse-name="collapse-aggregated-failedJobs" + data-collapse-table="aggregated-failedJobs">

Failed Jobs ({failedJobs.size}) @@ -573,19 +576,11 @@ private[ui] class JobPagedTable( val job = jobTableRow.jobData val killLink = if (killEnabled) { - val confirm = - s"if (window.confirm('Are you sure you want to kill job ${job.jobId} ?')) " + - "{ this.parentNode.submit(); return true; } else { return false; }" // SPARK-6846 this should be POST-only but YARN AM won't proxy POST - /* - val killLinkUri = s"$basePathUri/jobs/job/kill/" -
- - (kill) -
- */ val killLinkUri = s"$basePath/jobs/job/kill/?id=${job.jobId}" - (kill) + (kill) } else { Seq.empty } diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala index ae83371796275..165358fd98647 100644 --- a/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala @@ -58,7 +58,8 @@ private[ui] class AllStagesPage(parent: StagesTab) extends WebUIPage("") { val poolsDescription = if (parent.isFairScheduler) { + data-collapse-name="collapse-aggregated-poolTable" + data-collapse-table="aggregated-poolTable">

Fair Scheduler Pools ({pools.size}) @@ -146,8 +147,8 @@ private[ui] class AllStagesPage(parent: StagesTab) extends WebUIPage("") { val classSuffix = s"${statusName(status).capitalize}Stages" + data-collapse-name={s"collapse-aggregated-all$classSuffix"} + data-collapse-table={s"aggregated-all$classSuffix"}>

{headerDescription(status)} Stages ({summaryContent(appSummary, status, size)}) diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala index 793e65f44ba90..2f5e3a8e098b9 100644 --- a/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala @@ -215,7 +215,7 @@ private[ui] class JobPage(parent: JobsTab, store: AppStatusStore) extends WebUIP ++ - @@ -459,7 +459,8 @@ private[ui] class JobPage(parent: JobsTab, store: AppStatusStore) extends WebUIP if (shouldShowActiveStages) { content ++= + data-collapse-name="collapse-aggregated-activeStages" + data-collapse-table="aggregated-activeStages">

Active Stages ({activeStages.size}) @@ -472,8 +473,8 @@ private[ui] class JobPage(parent: JobsTab, store: AppStatusStore) extends WebUIP if (shouldShowPendingStages) { content ++= + data-collapse-name="collapse-aggregated-pendingOrSkippedStages" + data-collapse-table="aggregated-pendingOrSkippedStages">

Pending Stages ({pendingOrSkippedStages.size}) @@ -486,8 +487,8 @@ private[ui] class JobPage(parent: JobsTab, store: AppStatusStore) extends WebUIP if (shouldShowCompletedStages) { content ++= + data-collapse-name="collapse-aggregated-completedStages" + data-collapse-table="aggregated-completedStages">

Completed Stages ({completedStages.size}) @@ -500,8 +501,8 @@ private[ui] class JobPage(parent: JobsTab, store: AppStatusStore) extends WebUIP if (shouldShowSkippedStages) { content ++= + data-collapse-name="collapse-aggregated-pendingOrSkippedStages" + data-collapse-table="aggregated-pendingOrSkippedStages">

Skipped Stages ({pendingOrSkippedStages.size}) @@ -514,7 +515,8 @@ private[ui] class JobPage(parent: JobsTab, store: AppStatusStore) extends WebUIP if (shouldShowFailedStages) { content ++= + data-collapse-name="collapse-aggregated-failedStages" + data-collapse-table="aggregated-failedStages">

Failed Stages ({failedStages.size}) diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/PoolPage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/PoolPage.scala index 15592adc57cea..85dbed0aa41a2 100644 --- a/core/src/main/scala/org/apache/spark/ui/jobs/PoolPage.scala +++ b/core/src/main/scala/org/apache/spark/ui/jobs/PoolPage.scala @@ -51,8 +51,8 @@ private[ui] class PoolPage(parent: StagesTab) extends WebUIPage("pool") { if (activeStages.nonEmpty) { content ++= + data-collapse-name="collapse-aggregated-poolActiveStages" + data-collapse-table="aggregated-poolActiveStages">

Active Stages ({activeStages.size}) diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala index 4fa7c81496904..9d246a4147146 100644 --- a/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala +++ b/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala @@ -240,7 +240,7 @@ private[ui] class StagePage(parent: StagesTab, store: AppStatusStore) extends We - + UIUtils.headerSparkPage(request, stageHeader, content, parent, showVisualization = true, useDataTables = true) @@ -447,7 +447,7 @@ private[ui] class StagePage(parent: StagesTab, store: AppStatusStore) extends We {TIMELINE_LEGEND} ++ - val summary: NodeSeq =