diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ff0201..95426e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ --- develop --- +* issue#444: Brighten Dark theme red row markers while preserving panel header colors * issue#442: Keep Carrot theme status indicators legible on coral details titlebars and light table rows * issue#440: Give Cacti theme severity indicators contrasting colors for dark details titlebars and light table rows * issue: Fix the dashboard page not scrolling with the mouse wheel over chart panels: charts were sized from the whole card (header included) and overflowed the card body, turning it into a nested scroller that captured the wheel; charts now keep their server-sized height (which also stops pie/treemap charts growing on every window resize) and card bodies no longer scroll vertically diff --git a/README.md b/README.md index 6e94e33..9e37ef2 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,11 @@ The Carrot fixture checks status markers on light rows and a contrasting details badge that works with existing coral titlebars. See [Carrot theme validation](docs/carrot-theme.md) for screenshots and tests. +![Dark theme dashboard with seeded measurements and alerts](images/dark-theme-dashboard.png) + +The Dark fixture includes a visible red row marker for the failed device. +See [Dark theme validation](docs/dark-theme.md) for screenshots and tests. + ## Requirements - Cacti 1.2.17 or later (the `compat` value in the `INFO` file). The plugin is diff --git a/css/dark.css b/css/dark.css index 3dc8482..bfc723d 100644 --- a/css/dark.css +++ b/css/dark.css @@ -80,3 +80,8 @@ .color_grey, .color_grey i { color: black !important; } + +/* Row alerts need a brighter red than the header's white-text surface. */ +.inpa_sq.color_red { + background: #ff6b6b !important; +} diff --git a/docs/dark-theme.md b/docs/dark-theme.md new file mode 100644 index 0000000..03a6a4c --- /dev/null +++ b/docs/dark-theme.md @@ -0,0 +1,44 @@ +# Dark theme validation + +Dark's red row marker uses a brighter red against dark table rows. The panel +header retains its darker red background and white text. Existing details +status dots remain unchanged. + +## Seeded browser E2E fixture + +The screenshots render the actual plugin and core Dark stylesheets with +synthetic devices, ping times, poller totals, and warning/error messages. +They do not show a live Cacti installation or production monitoring data. + +![Dark dashboard with populated response times and alert panels](../images/dark-theme-dashboard.png) + +The E2E test opens and closes the real jQuery UI details dialog and checks +that its alert data is visible. + +![Dark details dialog with synthetic warning and error messages](../images/dark-theme-status.png) + +## Reproduce + +Use the setup in [Cacti theme validation](cacti-theme.md). With `CACTI_ROOT` +pointing to the adjacent core checkout, run from the plugin root: + +```sh +NODE_PATH="$CACTI_ROOT/tests/e2e/node_modules" mise exec node@22 -- node \ + "$CACTI_ROOT/tests/e2e/node_modules/@playwright/test/cli.js" test \ + --config tests/e2e/playwright.config.cjs +``` + +Set `UPDATE_THEME_SCREENSHOTS=1` to regenerate the screenshots. The suite +checks all four status colors on odd, even, and page surfaces and includes +linear-gradient endpoints in its contrast calculation. + +After committing, measure the changed CSS lines: + +```sh +mise exec python@3.12 -- python "$CACTI_ROOT/tests/tools/report_changed_coverage.py" \ + --base fix/carrot-theme-status-contrast --language css --minimum 100 \ + --browser-root tests/e2e/test-results +``` + +Use `origin/develop` after the parent branch merges. This measures changed CSS +coverage, not whole-plugin coverage or testing against a live installation. diff --git a/images/dark-theme-dashboard.png b/images/dark-theme-dashboard.png new file mode 100644 index 0000000..e493b00 Binary files /dev/null and b/images/dark-theme-dashboard.png differ diff --git a/images/dark-theme-status.png b/images/dark-theme-status.png new file mode 100644 index 0000000..3a2c995 Binary files /dev/null and b/images/dark-theme-status.png differ diff --git a/tests/e2e/cacti-theme.spec.cjs b/tests/e2e/cacti-theme.spec.cjs index b8aeb34..85a5ea0 100644 --- a/tests/e2e/cacti-theme.spec.cjs +++ b/tests/e2e/cacti-theme.spec.cjs @@ -11,10 +11,10 @@ async function installCss(page, file) { await page.addStyleTag({ url: 'http://cacti-theme.test/' + path }); } -for (const theme of ['cacti', 'carrot']) { +for (const theme of ['cacti', 'carrot', 'dark']) { test(`${theme} seeded dashboard displays measurements and opens alert details`, async ({ page }, info) => { await page.setViewportSize({ width: 1140, height: 520 }); - await page.setContent(readFileSync(resolve(root, 'tests/fixtures/Ui/cacti-theme.html'), 'utf8').replaceAll('Cacti theme', `${theme === 'carrot' ? 'Carrot' : 'Cacti'} theme`)); + await page.setContent(readFileSync(resolve(root, 'tests/fixtures/Ui/cacti-theme.html'), 'utf8').replaceAll('Cacti theme', `${theme[0].toUpperCase() + theme.slice(1)} theme`)); for (const file of [resolve(core, `include/themes/${theme}/jquery-ui.css`), resolve(core, `include/themes/${theme}/main.css`), resolve(core, 'include/fa/css/all.css'), resolve(root, 'includes/cards/cards.css'), resolve(root, 'css/common.css'), resolve(root, `css/${theme}.css`)]) { await installCss(page, file); } @@ -65,7 +65,7 @@ test.afterEach(async ({ page }, info) => { writeFileSync(info.outputPath('changed-coverage.json'), JSON.stringify(coverage)); }); -for (const theme of ['cacti', 'carrot']) { +for (const theme of ['cacti', 'carrot', 'dark']) { for (const row of ['odd', 'even', '']) { test(`${theme} severity indicators contrast with titlebars and ${row || 'white'} rows`, async ({ page }) => { await page.setContent('
' + ['green', 'red', 'yellow', 'grey'].map(color => @@ -85,17 +85,26 @@ for (const theme of ['cacti', 'carrot']) { v /= 255; return v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4; }).reduce((sum, v, i) => sum + v * [.2126, .7152, .0722][i], 0); + const over = (fg, bg) => bg.map((v, i) => fg[i] * fg[3] + v * (1 - fg[3])); return elements.map(element => { const square = element.id.startsWith('square-'); - let bg = [255, 255, 255]; + let surfaces = [[255, 255, 255]]; const ancestors = []; for (let e = square ? element.parentElement : element; e; e = e.parentElement) ancestors.unshift(e); for (const e of ancestors) { const c = rgba(getComputedStyle(e).backgroundColor); - bg = bg.map((v, i) => c[i] * c[3] + v * (1 - c[3])); + surfaces = surfaces.map(bg => over(c, bg)); + const image = getComputedStyle(e).backgroundImage; + if (image.startsWith('linear-gradient(')) { + const stops = (image.match(/rgba?\([^)]+\)/g) || []).map(rgba); + surfaces = surfaces.flatMap(bg => stops.map(stop => over(stop, bg))); + } } - const style = getComputedStyle(element), foreground = lum(rgba(square ? style.backgroundColor : style.color)), surface = lum(bg); - return { id: element.id, ratio: (Math.max(foreground, surface) + .05) / (Math.min(foreground, surface) + .05) }; + const style = getComputedStyle(element), color = rgba(square ? style.backgroundColor : style.color); + return { id: element.id, ratio: Math.min(...surfaces.map(bg => { + const foreground = lum(over(color, bg)), surface = lum(bg); + return (Math.max(foreground, surface) + .05) / (Math.min(foreground, surface) + .05); + })) }; }); }); expect(indicators).toHaveLength(8);