diff --git a/.gitignore b/.gitignore
index 74af52e04e9..8487c1cfc02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,3 +68,4 @@ install-shade.txt
# Frontend build (storm-webapp)
node_modules/
storm-webapp/node/
+tmp
\ No newline at end of file
diff --git a/LICENSE-binary b/LICENSE-binary
index ab6da93d3bb..27eeb446b31 100644
--- a/LICENSE-binary
+++ b/LICENSE-binary
@@ -522,10 +522,10 @@ THE SOFTWARE.
-----------------------------------------------------------------------
-For Bootstrap 3.3.1 (bundled via npm package "bootstrap" into storm-webapp webpack output)
+For Bootstrap 5.3.8 (bundled via npm package "bootstrap" into storm-webapp webpack output)
-Bootstrap v3.3.1 (http://getbootstrap.com)
-Copyright 2011-2014 Twitter, Inc.
+Bootstrap v5.3.8 (https://getbootstrap.com)
+Copyright 2011-2025 The Bootstrap Authors.
Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
-----------------------------------------------------------------------
diff --git a/storm-webapp/cypress/e2e/dark-theme.cy.js b/storm-webapp/cypress/e2e/dark-theme.cy.js
new file mode 100644
index 00000000000..6a5d8ca3349
--- /dev/null
+++ b/storm-webapp/cypress/e2e/dark-theme.cy.js
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+describe('Storm UI - Dark Theme', () => {
+
+ it('applies light theme when cookie is set to light', () => {
+ cy.setCookie('stormTheme', 'light');
+ cy.visit('/');
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'light');
+ });
+
+ it('applies dark theme when cookie is set to dark', () => {
+ cy.setCookie('stormTheme', 'dark');
+ cy.visit('/');
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+ });
+
+ it('dark theme gives body a dark background color', () => {
+ cy.setCookie('stormTheme', 'dark');
+ cy.visit('/');
+ cy.get('body').should(($body) => {
+ const bg = window.getComputedStyle($body[0]).backgroundColor;
+ // Bootstrap 5.3 dark theme uses #212529 = rgb(33, 37, 41)
+ expect(bg).to.equal('rgb(33, 37, 41)');
+ });
+ });
+
+ it('light theme gives body a white/light background color', () => {
+ cy.setCookie('stormTheme', 'light');
+ cy.visit('/');
+ cy.get('body').should(($body) => {
+ const bg = window.getComputedStyle($body[0]).backgroundColor;
+ // Bootstrap 5.3 light theme uses #fff = rgb(255, 255, 255)
+ expect(bg).to.equal('rgb(255, 255, 255)');
+ });
+ });
+
+ it('toggle button switches theme from light to dark', () => {
+ cy.setCookie('stormTheme', 'light');
+ cy.visit('/');
+ cy.get('#theme-toggle-btn', { timeout: 5000 })
+ .should('have.value', 'Dark Mode');
+
+ cy.get('#theme-toggle-btn').click();
+
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+ cy.get('#theme-toggle-btn').should('have.value', 'Light Mode');
+ });
+
+ it('toggle button switches theme from dark to light', () => {
+ cy.setCookie('stormTheme', 'dark');
+ cy.visit('/');
+ // Wait for the user template to render and the label to be updated
+ cy.get('#theme-toggle-btn', { timeout: 5000 }).should('exist');
+ // The $(function(){}) handler updates the label after template render
+ cy.wait(500);
+ cy.get('#theme-toggle-btn').should('have.value', 'Light Mode');
+
+ cy.get('#theme-toggle-btn').click();
+
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'light');
+ cy.get('#theme-toggle-btn').should('have.value', 'Dark Mode');
+ });
+
+ it('persists theme preference across page loads', () => {
+ cy.setCookie('stormTheme', 'light');
+ cy.visit('/');
+ cy.get('#theme-toggle-btn', { timeout: 5000 }).click(); // → dark
+
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+
+ cy.reload();
+
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+ });
+
+ it('dark theme works on topology page', () => {
+ cy.setCookie('stormTheme', 'dark');
+ cy.visit('/topology.html?id=word-count-1-1234567890');
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+ });
+
+ it('dark theme works on visualize page', () => {
+ cy.setCookie('stormTheme', 'dark');
+ cy.visit('/visualize.html?id=word-count-1-1234567890&sys=false');
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+ });
+});
diff --git a/storm-webapp/cypress/e2e/flux-page.cy.js b/storm-webapp/cypress/e2e/flux-page.cy.js
index 1ac46bf9c34..d215edb2ebf 100644
--- a/storm-webapp/cypress/e2e/flux-page.cy.js
+++ b/storm-webapp/cypress/e2e/flux-page.cy.js
@@ -62,3 +62,16 @@ describe('Storm UI - Flux Viewer Page', () => {
cy.get('#cy canvas').should('exist');
});
});
+
+describe('Storm UI - Flux Viewer (Dark Mode)', () => {
+ it('respects dark theme from cookie', () => {
+ cy.setCookie('stormTheme', 'dark');
+ cy.visit('/flux.html');
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+ cy.get('body').should(($body) => {
+ const bg = window.getComputedStyle($body[0]).backgroundColor;
+ expect(bg).to.equal('rgb(33, 37, 41)');
+ });
+ });
+});
diff --git a/storm-webapp/cypress/e2e/visualize-page.cy.js b/storm-webapp/cypress/e2e/visualize-page.cy.js
index d1d7c86d0f4..6e466bf66e6 100644
--- a/storm-webapp/cypress/e2e/visualize-page.cy.js
+++ b/storm-webapp/cypress/e2e/visualize-page.cy.js
@@ -62,3 +62,50 @@ describe('Storm UI - Topology Visualization Page', () => {
});
});
});
+
+describe('Storm UI - Topology Visualization (Dark Mode)', () => {
+ beforeEach(() => {
+ cy.setCookie('stormTheme', 'dark');
+ cy.intercept('GET', '/api/v1/topology/*/visualization*').as('vizApi');
+ cy.visit('/visualize.html?id=word-count-1-1234567890&sys=true');
+ cy.wait('@vizApi');
+ });
+
+ it('applies dark theme to the page', () => {
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+ });
+
+ it('gives the network container a dark background', () => {
+ cy.get('#mynetwork').should(($el) => {
+ const bg = window.getComputedStyle($el[0]).backgroundColor;
+ expect(bg).to.equal('rgb(33, 37, 41)');
+ });
+ });
+
+ it('renders nodes and streams in dark mode without errors', () => {
+ cy.window().then((win) => {
+ expect(win.visNS).to.exist;
+ expect(win.visNS.nodes.length).to.be.greaterThan(0);
+ });
+ cy.get('#available-streams li', { timeout: 5000 })
+ .should('have.length.greaterThan', 0);
+ });
+
+ it('switching to light mode gives network container a light background', () => {
+ // Start in dark mode (from beforeEach), then switch to light
+ cy.document().its('documentElement')
+ .should('have.attr', 'data-bs-theme', 'dark');
+
+ // Simulate theme toggle by setting the attribute directly
+ cy.document().then((doc) => {
+ doc.documentElement.setAttribute('data-bs-theme', 'light');
+ });
+
+ cy.get('#mynetwork').should(($el) => {
+ const bg = window.getComputedStyle($el[0]).backgroundColor;
+ // Must be white (light mode), NOT dark
+ expect(bg).to.equal('rgb(255, 255, 255)');
+ });
+ });
+});
diff --git a/storm-webapp/package-lock.json b/storm-webapp/package-lock.json
index 483e9cd3a91..f8620a5a90b 100644
--- a/storm-webapp/package-lock.json
+++ b/storm-webapp/package-lock.json
@@ -8,12 +8,12 @@
"name": "storm-webapp-ui",
"version": "2.8.5",
"dependencies": {
- "bootstrap": "3.3.1",
+ "bootstrap": "5.3.8",
"cytoscape": "3.33.1",
"cytoscape-dagre": "2.5.0",
"dagre": "0.8.5",
"datatables.net": "2.3.7",
- "datatables.net-bs": "2.3.7",
+ "datatables.net-bs5": "2.3.7",
"datatables.net-dt": "2.3.7",
"domurl": "2.3.4",
"esprima": "4.0.1",
@@ -249,6 +249,17 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@sinclair/typebox": {
"version": "0.34.48",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
@@ -956,13 +967,22 @@
"license": "ISC"
},
"node_modules/bootstrap": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.1.tgz",
- "integrity": "sha512-y2aXwH7MhmTaoROnycbChnWK6Ue+VBUb7B5K4qe2mqjxFQ8f+XXnyouVV2rxxs9zCVqqifJKDLBc+AwBKfLEgA==",
- "deprecated": "This version of Bootstrap is no longer supported. Please upgrade to the latest version.",
+ "version": "5.3.8",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
+ "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
"license": "MIT",
- "engines": {
- "node": "~0.10.1"
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
}
},
"node_modules/browserslist": {
@@ -1809,10 +1829,10 @@
"jquery": ">=1.7"
}
},
- "node_modules/datatables.net-bs": {
+ "node_modules/datatables.net-bs5": {
"version": "2.3.7",
- "resolved": "https://registry.npmjs.org/datatables.net-bs/-/datatables.net-bs-2.3.7.tgz",
- "integrity": "sha512-tNnoD/iOVDyXPKVJ2nmpv3w6VeT1QLqUTjMdIBORBdoisAztn41TCGhdj8rQKfCrtBEQq3Z6eL99pcaQPnetnA==",
+ "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.3.7.tgz",
+ "integrity": "sha512-RiCEMpMXDBeMDwjSrMpmcXDU6mibRMuOn7Wk7k3SlOfLEY3FQHO7S2m+K7teXYeaNlCLyjJMU+6BUUwlBCpLFw==",
"license": "MIT",
"dependencies": {
"datatables.net": "2.3.7",
diff --git a/storm-webapp/package.json b/storm-webapp/package.json
index dcd2fa2da65..c3a9233dbc1 100644
--- a/storm-webapp/package.json
+++ b/storm-webapp/package.json
@@ -12,12 +12,12 @@
"test:e2e": "start-server-and-test test:server http://localhost:3088 cypress:run"
},
"dependencies": {
- "bootstrap": "3.3.1",
+ "bootstrap": "5.3.8",
"cytoscape": "3.33.1",
"cytoscape-dagre": "2.5.0",
"dagre": "0.8.5",
"datatables.net": "2.3.7",
- "datatables.net-bs": "2.3.7",
+ "datatables.net-bs5": "2.3.7",
"datatables.net-dt": "2.3.7",
"domurl": "2.3.4",
"esprima": "4.0.1",
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html
index 927d2fcb0c0..4aacd974402 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/component.html
@@ -17,6 +17,7 @@
-->
+
Storm UI
@@ -189,7 +190,7 @@
var uiUser = $("#ui-user");
getStatic("/templates/user-template.html", function(template) {
uiUser.append(Mustache.render($(template).filter("#user-template").html(),response));
- $('#ui-user [data-toggle="tooltip"]').tooltip()
+ $('#ui-user [data-bs-toggle="tooltip"]').tooltip()
});
var topologyUrl = "/api/v1/topology/"+topologyId;
@@ -357,13 +358,13 @@
errorCells[i].style.whiteSpace = "pre";
}
});
- $('#component-summary [data-toggle="tooltip"]').tooltip();
- $('#component-actions [data-toggle="tooltip"]').tooltip();
- $('#component-stats-detail [data-toggle="tooltip"]').tooltip();
- $('#component-input-stats [data-toggle="tooltip"]').tooltip();
- $('#component-output-stats [data-toggle="tooltip"]').tooltip();
- $('#component-executor-stats [data-toggle="tooltip"]').tooltip();
- $('#component-errors [data-toggle="tooltip"]').tooltip();
+ $('#component-summary [data-bs-toggle="tooltip"]').tooltip();
+ $('#component-actions [data-bs-toggle="tooltip"]').tooltip();
+ $('#component-stats-detail [data-bs-toggle="tooltip"]').tooltip();
+ $('#component-input-stats [data-bs-toggle="tooltip"]').tooltip();
+ $('#component-output-stats [data-bs-toggle="tooltip"]').tooltip();
+ $('#component-executor-stats [data-bs-toggle="tooltip"]').tooltip();
+ $('#component-errors [data-bs-toggle="tooltip"]').tooltip();
});
});
});
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/deep_search_result.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/deep_search_result.html
index 5c2cd9bcaef..be117fc7879 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/deep_search_result.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/deep_search_result.html
@@ -16,6 +16,7 @@
limitations under the License.
-->
+
Storm UI
@@ -100,7 +101,7 @@
displayKey: 'value',
source: findIds
});
- $('#search-form [data-toggle="tooltip"]').tooltip();
+ $('#search-form [data-bs-toggle="tooltip"]').tooltip();
});
});
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/flux.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/flux.html
index f272b33d225..05417add051 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/flux.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/flux.html
@@ -17,6 +17,7 @@
limitations under the License.
-->
+
Storm Flux Viewer
+
Storm UI
@@ -130,14 +131,14 @@ Nimbus Configuration
getStatic("/templates/user-template.html", function(template) {
jsError(function() {
uiUser.append(Mustache.render($(template).filter("#user-template").html(),response));
- $('#ui-user [data-toggle="tooltip"]').tooltip()
+ $('#ui-user [data-bs-toggle="tooltip"]').tooltip()
});
});
jsError(function() {
clusterSummary.append(Mustache.render($(indexTemplate).filter("#cluster-summary-template").html(),response));
- $('#cluster-summary [data-toggle="tooltip"]').tooltip();
- $('#cluster-summary [data-toggle="toggle"]').each(function(index) {
+ $('#cluster-summary [data-bs-toggle="tooltip"]').tooltip();
+ $('#cluster-summary [data-storm-toggle="details"]').each(function(index) {
var details = $(this).find('[toggle="details"]')
details.hide()
$(this).find('[toggle="link"]').on("click", function() {
@@ -148,12 +149,12 @@ Nimbus Configuration
jsError(function() {
clusterResources.append(Mustache.render($(indexTemplate).filter("#cluster-resources-template").html(),response));
- $('#cluster-resources [data-toggle="tooltip"]').tooltip();
+ $('#cluster-resources [data-bs-toggle="tooltip"]').tooltip();
});
jsError(function() {
var displayResource = response["schedulerDisplayResource"];
- $('#cluster-resources [data-toggle="tooltip"]').tooltip();
+ $('#cluster-resources [data-bs-toggle="tooltip"]').tooltip();
if (!displayResource){
$('#cluster-resources-header').hide();
$('#cluster-resources').hide();
@@ -171,7 +172,7 @@ Nimbus Configuration
{type: "time-str", targets: [4]}
]
});
- $('#nimbus-summary [data-toggle="tooltip"]').tooltip();
+ $('#nimbus-summary [data-bs-toggle="tooltip"]').tooltip();
});
});
@@ -192,7 +193,7 @@ Nimbus Configuration
{type: "time-str", targets: [3]}
]
});
- $('#topology-summary [data-toggle="tooltip"]').tooltip();
+ $('#topology-summary [data-bs-toggle="tooltip"]').tooltip();
});
});
@@ -207,7 +208,7 @@ Nimbus Configuration
{type: "time-str", targets: [2]}
]
});
- $('#supervisor-summary [data-toggle="tooltip"]').tooltip();
+ $('#supervisor-summary [data-bs-toggle="tooltip"]').tooltip();
});
});
@@ -217,7 +218,7 @@ Nimbus Configuration
$('#nimbus-configuration-table td').jsonFormatter();
//key, value
dtAutoPage("#nimbus-configuration-table", {});
- $('#nimbus-configuration [data-toggle="tooltip"]').tooltip();
+ $('#nimbus-configuration [data-bs-toggle="tooltip"]').tooltip();
});
});
});
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer.html
index fb6078104cd..461a9f17483 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer.html
@@ -17,6 +17,7 @@
limitations under the License.
-->
+
Storm Logviewer
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer_search.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer_search.html
index 888fc5e6d13..9fcb979e607 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer_search.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/logviewer_search.html
@@ -16,6 +16,7 @@
limitations under the License.
-->
+
Storm UI
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html
index 68511b68524..0b039deb7fe 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/owner.html
@@ -18,6 +18,7 @@
limitations under the License.
-->
+
Storm UI
@@ -143,12 +144,12 @@ Owner Topologies
//totalReqOnHeapMem,totalReqOffHeapMem,totalReqMem,totalReqCpu,totalAssignedOnHeapMem,totalAssignedOffHeapMem,totalAssignedMem,totalAssignedCpu
ownerResourceUsage.append(
Mustache.render($(template).filter("#owner-resource-usage-template").html(), response));
- $('#owner-resource-usage-summary [data-toggle="tooltip"]').tooltip();
+ $('#owner-resource-usage-summary [data-bs-toggle="tooltip"]').tooltip();
if (response["cpuGuarantee"] != "N/A" || response["memoryGuarantee"] != "N/A") {
ownerResourceGuarantee.append(
Mustache.render($(template).filter("#owner-resource-guarantee-template").html(), response));
- $('#owner-resource-guarantee-summary [data-toggle="tooltip"]').tooltip();
+ $('#owner-resource-guarantee-summary [data-bs-toggle="tooltip"]').tooltip();
$('#mem-guarantee-util').html(getResourceGuaranteeRemainingFormat("display", response["memoryGuaranteeRemaining"]));
@@ -174,7 +175,7 @@ Owner Topologies
$(".warning").hide();
}
- $('#owner-resource-usage-summary [data-toggle="tooltip"]').tooltip();
+ $('#owner-resource-usage-summary [data-bs-toggle="tooltip"]').tooltip();
topologySummary.append(
Mustache.render($(template).filter("#owner-topology-summary-template").html(), response));
@@ -203,7 +204,7 @@ Owner Topologies
}]
});
}
- $('#topology-summary [data-toggle="tooltip"]').tooltip();
+ $('#topology-summary [data-bs-toggle="tooltip"]').tooltip();
});
});
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/search_result.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/search_result.html
index 7a6b75f7bdf..096134ce26e 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/search_result.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/search_result.html
@@ -16,6 +16,7 @@
limitations under the License.
-->
+
Storm UI
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html
index 429e62e9c42..dabd21e481d 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/supervisor.html
@@ -18,6 +18,7 @@
+
Storm UI
@@ -133,7 +134,7 @@
});
}
- $('#supervisor-summary-table [data-toggle="tooltip"]').tooltip();
+ $('#supervisor-summary-table [data-bs-toggle="tooltip"]').tooltip();
workerStats.append(Mustache.render($(template).filter("#worker-stats-template").html(),response));
makeSupervisorWorkerStatsTable(response, '#worker-stats-table', '#worker-resources');
});
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/component-page-template.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/component-page-template.html
index 3862a031c7b..b93a1362073 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/component-page-template.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/component-page-template.html
@@ -20,43 +20,43 @@ Component summary
|
-
+
Id
|
-
+
Topology
|
-
+
Executors
|
-
+
Tasks
|
{{#schedulerDisplayResource}}
-
+
Requested On-heap Memory
|
-
+
Requested Off-heap Memory
|
-
+
Requested CPU
|
-
+
Requested Generic Resources
|
@@ -64,7 +64,7 @@ Component summary
{{/schedulerDisplayResource}}
{{#eventLogLink}}
-
+
Debug
|
@@ -95,32 +95,32 @@ Spout stats
@@ -146,32 +146,32 @@ Output stats ({{windowHint}})
@@ -197,52 +197,52 @@ Executors ({{windowHint}})
@@ -273,7 +273,7 @@ Profiling and Debugging
@@ -320,42 +320,42 @@ Bolt stats
@@ -383,37 +383,37 @@ Input stats ({{windowHint}})
@@ -440,17 +440,17 @@ Output stats ({{windowHint}})
@@ -473,67 +473,67 @@ Executors ({{windowHint}})
@@ -591,12 +591,12 @@ Errors
Component actions
{{#loggersDisabled}}
-
+
{{/loggersDisabled}}
-
+
{{#loggersDisabled}}
{{/loggersDisabled}}
-
+
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/deep-search-result-page-template.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/deep-search-result-page-template.html
index e5146d8946e..f0fa4afdb02 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/deep-search-result-page-template.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/templates/deep-search-result-page-template.html
@@ -44,19 +44,19 @@
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html
index 453485f01b6..a17cd638a0c 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/topology.html
@@ -18,6 +18,7 @@
+
Storm UI
@@ -281,7 +282,7 @@
var uiUser = $("#ui-user");
getStatic("/templates/user-template.html", function(template) {
uiUser.append(Mustache.render($(template).filter("#user-template").html(),response));
- $('#ui-user [data-toggle="tooltip"]').tooltip();
+ $('#ui-user [data-bs-toggle="tooltip"]').tooltip();
});
var topologySummary = $("#topology-summary");
@@ -429,13 +430,13 @@
errorTime[i].title = "Elapsed Time Since Error: " + time;
}
}
- $('#topology-summary [data-toggle="tooltip"]').tooltip();
- $('#topology-stats [data-toggle="tooltip"]').tooltip();
- $('#spout-stats [data-toggle="tooltip"]').tooltip();
- $('#bolt-stats [data-toggle="tooltip"]').tooltip();
- $('#topology-configuration [data-toggle="tooltip"]').tooltip();
- $('#topology-actions [data-toggle="tooltip"]').tooltip();
- $('#topology-visualization [data-toggle="tooltip"]').tooltip();
+ $('#topology-summary [data-bs-toggle="tooltip"]').tooltip();
+ $('#topology-stats [data-bs-toggle="tooltip"]').tooltip();
+ $('#spout-stats [data-bs-toggle="tooltip"]').tooltip();
+ $('#bolt-stats [data-bs-toggle="tooltip"]').tooltip();
+ $('#topology-configuration [data-bs-toggle="tooltip"]').tooltip();
+ $('#topology-actions [data-bs-toggle="tooltip"]').tooltip();
+ $('#topology-visualization [data-bs-toggle="tooltip"]').tooltip();
var lagUrl = "/api/v1/topology/"+topologyId+"/lag";
$.getJSON(lagUrl,function(lagResponse,status,jqXHR) {
diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/visualize.html b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/visualize.html
index 52e1e5707b4..2a8dedf71a9 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/visualize.html
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/WEB-INF/visualize.html
@@ -17,6 +17,7 @@
limitations under the License.
-->
+
Topology Visualization
diff --git a/storm-webapp/src/main/webapp/css/style.css b/storm-webapp/src/main/webapp/css/style.css
index b8c5c618162..fb7cb130df4 100644
--- a/storm-webapp/src/main/webapp/css/style.css
+++ b/storm-webapp/src/main/webapp/css/style.css
@@ -39,18 +39,12 @@
width:5em;
}
-body,
-.dataTables_wrapper label,
-div.dataTables_info[role="status"],
-.btn-default
-{
- color: #6d6d6d;
-}
+/* Let Bootstrap 5 theme handle body/text/link colors via data-bs-theme */
table.dataTable thead th,
table.dataTable.no-footer
{
- border-color: #d5d5d5;
+ border-color: var(--bs-border-color);
}
.table > tbody > tr > td {
@@ -64,15 +58,7 @@ table.dataTable {
td {
border-bottom-style: solid;
border-bottom-width: 1px;
- border-color: #d5d5d5;
-}
-
-a {
- color: #0069d6;
-}
-
-a:hover {
- color: #0050a3;
+ border-color: var(--bs-border-color);
}
h1
@@ -82,7 +68,6 @@ h1
h2
{
- color: #404040;
font-weight: bold;
}
@@ -141,14 +126,14 @@ PRE.jsonFormatter-codeContainer {
}
div#visualization {
- color: #333;
+ color: var(--bs-body-color);
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 14px;
line-height: 1.42857;
}
div#visualization a, div#visualization a:visited {
- color: blue;
+ color: var(--bs-link-color);
}
div#visualization ul {
@@ -260,4 +245,55 @@ div#visualization summary {
.resource-guarantee-remaining-negative {
color: red;
font-weight: bold;
-}
\ No newline at end of file
+}
+
+/* --- Dark mode overrides for Storm-specific styles --- */
+/* Bootstrap 5 handles body, text, link, border, and heading colors
+ automatically via data-bs-theme="dark". Only Storm-specific components
+ need explicit dark mode overrides below. */
+
+/* DataTables uses :root.dark for its dark theme, but Bootstrap 5 uses
+ [data-bs-theme="dark"]. Bridge the two so DataTables respects BS5 dark mode. */
+[data-bs-theme="dark"] {
+ --dt-html-background: #212529;
+ --dt-row-background: #212529;
+ --dt-row-stripe-background: #2c3034;
+ --dt-column-ordering-background: rgba(255, 255, 255, 0.02);
+ --dt-foreground-color: #c9d1d9;
+ --dt-border-color: #30363d;
+}
+
+[data-bs-theme="dark"] table.dataTable thead .sorting_asc,
+[data-bs-theme="dark"] table.dataTable thead .sorting_desc {
+ background-color: rgba(56, 139, 253, 0.15);
+}
+
+[data-bs-theme="dark"] PRE.jsonFormatter-codeContainer .jsonFormatter-propertyName {
+ color: #ff7b72;
+}
+
+[data-bs-theme="dark"] PRE.jsonFormatter-codeContainer .jsonFormatter-string {
+ color: #7ee787;
+}
+
+[data-bs-theme="dark"] PRE.jsonFormatter-codeContainer .jsonFormatter-number {
+ color: #d2a8ff;
+}
+
+[data-bs-theme="dark"] PRE.jsonFormatter-codeContainer .jsonFormatter-objectBrace {
+ color: #7ee787;
+}
+
+[data-bs-theme="dark"] PRE.jsonFormatter-codeContainer .jsonFormatter-arrayBrace {
+ color: #79c0ff;
+}
+
+[data-bs-theme="dark"] div#visualization #streams_slideout_inner,
+[data-bs-theme="dark"] div#visualization #bolt_slideout_inner {
+ background: #2d5a1e;
+}
+
+#mynetwork {
+ background-color: var(--bs-body-bg, #fff);
+}
+
diff --git a/storm-webapp/src/main/webapp/js/flux-entry.js b/storm-webapp/src/main/webapp/js/flux-entry.js
index 01ca5fa7cd7..642dbe146b1 100644
--- a/storm-webapp/src/main/webapp/js/flux-entry.js
+++ b/storm-webapp/src/main/webapp/js/flux-entry.js
@@ -19,6 +19,10 @@
* Webpack entry point for the Flux topology viewer page (flux.html).
*/
+// --- CSS ---
+require('bootstrap/dist/css/bootstrap.min.css');
+require('../css/style.css');
+
// --- jQuery ---
var $ = require('jquery');
window.$ = $;
diff --git a/storm-webapp/src/main/webapp/js/main-entry.js b/storm-webapp/src/main/webapp/js/main-entry.js
index d4968c893a9..3ed49b0bb16 100644
--- a/storm-webapp/src/main/webapp/js/main-entry.js
+++ b/storm-webapp/src/main/webapp/js/main-entry.js
@@ -27,7 +27,7 @@
// --- CSS ---
require('bootstrap/dist/css/bootstrap.min.css');
require('datatables.net-dt/css/dataTables.dataTables.css');
-require('datatables.net-bs/css/dataTables.bootstrap.css');
+require('datatables.net-bs5/css/dataTables.bootstrap5.css');
require('../lib/jsonFormatter.css');
require('../css/style.css');
@@ -66,7 +66,7 @@ if (!$.type) {
// --- DataTables + Bootstrap 3 integration ---
require('datatables.net');
-require('datatables.net-bs');
+require('datatables.net-bs5');
// --- Mustache + jQuery integration ---
var Mustache = require('mustache');
@@ -120,9 +120,26 @@ require('../lib/jsonFormatter.js');
// --- npm libraries ---
require('typeahead.js');
-require('bootstrap');
var moment = require('moment');
window.moment = moment;
+// --- Bootstrap 5 (vanilla JS, no jQuery plugin) ---
+var bootstrap = require('bootstrap/dist/js/bootstrap.bundle.js');
+window.bootstrap = bootstrap;
+
+// --- Bootstrap 5 tooltip shim ---
+// The inline HTML scripts call $('[data-toggle="tooltip"]').tooltip() or
+// $('[data-bs-toggle="tooltip"]').tooltip(). Bootstrap 5 uses vanilla JS,
+// so we shim the jQuery .tooltip() method.
+$.fn.tooltip = $.fn.tooltip || function () {
+ return this.each(function () {
+ // Support both BS3 data-toggle and BS5 data-bs-toggle attributes
+ if (this.getAttribute('data-bs-toggle') === 'tooltip' ||
+ this.getAttribute('data-toggle') === 'tooltip') {
+ new bootstrap.Tooltip(this);
+ }
+ });
+};
+
// --- Custom Storm UI code ---
require('./script');
diff --git a/storm-webapp/src/main/webapp/js/script.js b/storm-webapp/src/main/webapp/js/script.js
index 1a2ba463206..cea402a67ae 100644
--- a/storm-webapp/src/main/webapp/js/script.js
+++ b/storm-webapp/src/main/webapp/js/script.js
@@ -15,8 +15,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// --- Dark/light theme support ---
+// Uses document.cookie directly so it works before $.cookies shim is loaded
+// and also on pages that don't use the main entry point.
+(function() {
+ function getCookie(name) {
+ var match = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'));
+ return match ? decodeURIComponent(match[1]) : null;
+ }
+ function setCookie(name, value) {
+ var d = new Date();
+ d.setFullYear(d.getFullYear() + 1);
+ document.cookie = name + '=' + encodeURIComponent(value) + ';path=/;expires=' + d.toUTCString();
+ }
+ var saved = getCookie('stormTheme');
+ if (!saved) {
+ saved = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
+ }
+ document.documentElement.setAttribute('data-bs-theme', saved);
+
+ window.toggleTheme = function() {
+ var current = document.documentElement.getAttribute('data-bs-theme') || 'light';
+ var next = (current === 'dark') ? 'light' : 'dark';
+ document.documentElement.setAttribute('data-bs-theme', next);
+ setCookie('stormTheme', next);
+ var btn = document.getElementById('theme-toggle-btn');
+ if (btn) {
+ btn.value = (next === 'dark') ? 'Light Mode' : 'Dark Mode';
+ }
+ };
+})();
+
+// Update the theme toggle button label whenever it appears in the DOM.
+// The button is rendered asynchronously by Mustache templates loaded via AJAX,
+// so we use a MutationObserver to catch it.
+function updateThemeButtonLabel() {
+ var current = document.documentElement.getAttribute('data-bs-theme') || 'light';
+ var btn = document.getElementById('theme-toggle-btn');
+ if (btn) {
+ btn.value = (current === 'dark') ? 'Light Mode' : 'Dark Mode';
+ }
+}
+
+(function() {
+ var observer = new MutationObserver(function() { updateThemeButtonLabel(); });
+ observer.observe(document.documentElement, { childList: true, subtree: true });
+})();
+
$(function () {
$(".js-only").show();
+ updateThemeButtonLabel();
});
//Add in custom sorting for some data types
@@ -191,7 +239,7 @@ function confirmAction(id, name, action, param, defaultParamValue, paramText, ac
}
$(function () {
- $('[data-toggle="tooltip"]').tooltip()
+ $('[data-bs-toggle="tooltip"]').tooltip()
})
function formatConfigData(data) {
@@ -222,9 +270,9 @@ function formatErrorTimeSecs(response){
function renderToggleSys(div) {
var sys = $.cookies.get("sys") || false;
if(sys) {
- div.append("");
+ div.append("");
} else {
- div.append("");
+ div.append("");
}
}
@@ -303,7 +351,7 @@ var formatComponents = function (row) {
var result = '';
Object.keys(row.componentNumTasks || {}).sort().forEach (function (component){
var numTasks = row.componentNumTasks[component];
- result += '';
result += component;
result += '' + numTasks + '';
@@ -377,7 +425,7 @@ var makeWorkerStatsTable = function (response, elId, parentId, type) {
if (type == 'display') {
// show a button to toggle the component row
- return '