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 @@

Worker resources

}); } - $('#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

- + Window - + Emitted - + Transferred - + Complete latency (ms) - + Acked - + Failed @@ -146,32 +146,32 @@

Output stats ({{windowHint}})

- + Stream - + Emitted - + Transferred - + Complete latency (ms) - + Acked - + Failed @@ -197,52 +197,52 @@

Executors ({{windowHint}})

- + Id - + Uptime - + Host - + Port - + Actions - + Emitted - + Transferred - + Complete latency (ms) - + Acked - + Failed @@ -273,7 +273,7 @@

Profiling and Debugging

- + Status / Timeout (Minutes) @@ -320,42 +320,42 @@

Bolt stats

- + Window - + Emitted - + Transferred - + Execute latency (ms) - + Executed - + Process latency (ms) - + Acked - + Failed @@ -383,37 +383,37 @@

Input stats ({{windowHint}})

- + Component - + Stream - + Execute latency (ms) - + Executed - + Process latency (ms) - + Acked - + Failed @@ -440,17 +440,17 @@

Output stats ({{windowHint}})

- + Stream - + Emitted - + Transferred @@ -473,67 +473,67 @@

Executors ({{windowHint}})

- + Id - + Uptime - + Host - + Port - + Debug - + Emitted - + Transferred - + Capacity (last 10m) - + Execute latency (ms) - + Executed - + Process latency (ms) - + Acked - + Failed @@ -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 @@

Topology resources

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 @@

Topology resources

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 ''; } @@ -459,7 +507,7 @@ var makeWorkerStatsTable = function (response, elId, parentId, type) { toggleComponents(elId); }); - $(elId + ' [data-toggle="tooltip"]').tooltip(); + $(elId + ' [data-bs-toggle="tooltip"]').tooltip(); }; function renderToggleComponents(div, targetTable) { @@ -615,7 +663,7 @@ var makeOwnerSummaryTable = function(response, elId, parentId) { columns: columns, }); - $(elId + ' [data-toggle="tooltip"]').tooltip(); + $(elId + ' [data-bs-toggle="tooltip"]').tooltip(); }; function getPageRenderedTimestamp(eId) { diff --git a/storm-webapp/src/main/webapp/js/visualization.js b/storm-webapp/src/main/webapp/js/visualization.js index 4253b254c01..baaf92c4656 100644 --- a/storm-webapp/src/main/webapp/js/visualization.js +++ b/storm-webapp/src/main/webapp/js/visualization.js @@ -111,11 +111,51 @@ function parseResponse(json) { } } +function getThemeOptions() { + var isDark = document.documentElement.getAttribute('data-bs-theme') === 'dark'; + if (isDark) { + return { + edges: { + color: { color: '#8b949e', highlight: '#58a6ff', hover: '#58a6ff' }, + font: { color: '#c9d1d9' }, + shadow: { enabled: true, color: 'rgba(255,255,255,0.1)', size: 10, x: 5, y: 5 } + }, + nodes: { + font: { color: '#c9d1d9' }, + shadow: { enabled: true, color: 'rgba(255,255,255,0.1)', size: 10, x: 5, y: 5 } + } + }; + } + return { + edges: { + color: { color: '#848484', highlight: '#2B7CE9', hover: '#2B7CE9' }, + font: { color: '#343434' }, + shadow: { enabled: true, color: 'rgba(0,0,0,0.5)', size: 10, x: 5, y: 5 } + }, + nodes: { + font: { color: '#343434' }, + shadow: { enabled: true, color: 'rgba(0,0,0,0.5)', size: 10, x: 5, y: 5 } + } + }; +} + +function applyThemeToNetwork() { + if (visNS.network) { + visNS.network.setOptions(getThemeOptions()); + } +} + function createNetwork() { var data = { "nodes": visNS.nodes, "edges": visNS.edges }; + + // Merge theme-dependent colors into options + var themeOpts = getThemeOptions(); + $.extend(true, visNS.options.edges, themeOpts.edges); + $.extend(true, visNS.options.nodes, themeOpts.nodes); + // Create network visNS.network = new vis.Network(visNS.networkContainer, data, visNS.options); @@ -129,6 +169,14 @@ function createNetwork() { // Then disable layout visNS.network.setOptions({layout: {hierarchical: false } }); + + // Watch for theme changes and update network colors + new MutationObserver(function() { + applyThemeToNetwork(); + }).observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-bs-theme'] + }); } function handleClickEvent(params) {