Service state colours, activity badge colours, readable named colours
This commit is contained in:
@@ -292,4 +292,38 @@ describe('facet chips on a card that shows a health dot', () => {
|
||||
it('offers no chips for a panel with no status rollup', () => {
|
||||
expect(facets({ id: 'x', render: 'list', _data: [{ a: 1 }] })).toEqual([])
|
||||
})
|
||||
|
||||
it('prefers an explicit facets declaration over the status rollup', () => {
|
||||
// Services carry BOTH: a statecolor status dot and a statelabel facet.
|
||||
// Chips must follow the declaration, because a service is Running or
|
||||
// Stopped - "OK" and "Critical" is not how anybody describes one.
|
||||
const services = {
|
||||
id: 'endpoints-services',
|
||||
render: 'list',
|
||||
status: { key: 'statecolor', levels: { green: 'ok', red: 'critical' } },
|
||||
facets: { key: 'statelabel', order: ['Running', 'Stopped', 'Paused'] },
|
||||
_data: [
|
||||
{ statelabel: 'Running', statecolor: 'green' },
|
||||
{ statelabel: 'Stopped', statecolor: 'red' },
|
||||
{ statelabel: 'Paused', statecolor: 'red' },
|
||||
],
|
||||
}
|
||||
const chips = facets(services)
|
||||
expect(chips.map((c) => c.label)).toEqual(['Running', 'Stopped', 'Paused'])
|
||||
expect(chips.map((c) => c.label)).not.toContain('Critical')
|
||||
})
|
||||
|
||||
it('gives an unlisted state its own chip rather than dropping the row', () => {
|
||||
// Windows has transitional states, and an agent can report something the
|
||||
// order list never anticipated. Those rows must stay reachable.
|
||||
const services = {
|
||||
id: 'svc2',
|
||||
render: 'list',
|
||||
facets: { key: 'statelabel', order: ['Running', 'Stopped'] },
|
||||
_data: [{ statelabel: 'Running' }, { statelabel: 'Start Pending' }],
|
||||
}
|
||||
const chips = facets(services)
|
||||
expect(chips.map((c) => c.value)).toEqual(['Running', 'Start Pending'])
|
||||
expect(facetedRows(services, ['Start Pending']).length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,10 +19,33 @@ export const PALETTE = [
|
||||
'#6c757d', // gray
|
||||
]
|
||||
|
||||
// Plugin panels emit colour NAMES, not hex - a services row says 'yellow', an
|
||||
// activity row says 'gray'. Without this the brightness test cannot parse them
|
||||
// and falls through to white, which is why an amber badge rendered white text
|
||||
// on yellow and could not be read. Green, red and grey are dark enough that
|
||||
// white worked by luck, so the fault only showed when amber was introduced.
|
||||
const NAMED_COLORS = {
|
||||
red: '#dc3545',
|
||||
green: '#28a745',
|
||||
yellow: '#ffc107',
|
||||
amber: '#ffc107',
|
||||
orange: '#fd7e14',
|
||||
blue: '#4181ff',
|
||||
grey: '#6c757d',
|
||||
gray: '#6c757d',
|
||||
black: '#000000',
|
||||
white: '#ffffff',
|
||||
}
|
||||
|
||||
export function resolveColor(color) {
|
||||
if (!color || typeof color !== 'string') return color
|
||||
return NAMED_COLORS[color.trim().toLowerCase()] || color
|
||||
}
|
||||
|
||||
// Black or white text for a given background, by perceived brightness.
|
||||
export function readableText(bg) {
|
||||
if (!bg || typeof bg !== 'string') return '#ffffff'
|
||||
let hex = bg.trim().replace('#', '')
|
||||
let hex = resolveColor(bg).trim().replace('#', '')
|
||||
if (hex.length === 3) hex = hex.split('').map(c => c + c).join('')
|
||||
if (hex.length !== 6) return '#ffffff'
|
||||
const r = parseInt(hex.slice(0, 2), 16)
|
||||
@@ -34,6 +57,8 @@ export function readableText(bg) {
|
||||
|
||||
// Style object for a badge/pill from a stored color (with a neutral fallback).
|
||||
export function colorStyle(color, fallback = '#6c757d') {
|
||||
const backgroundColor = color || fallback
|
||||
// Resolve the name for the BACKGROUND too, so the swatch and the text
|
||||
// contrast are computed from the same value.
|
||||
const backgroundColor = resolveColor(color) || fallback
|
||||
return { backgroundColor, color: readableText(backgroundColor) }
|
||||
}
|
||||
|
||||
27
frontend/src/utils/colorStyle.spec.js
Normal file
27
frontend/src/utils/colorStyle.spec.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { readableText, colorStyle } from './colorStyle'
|
||||
|
||||
describe('named colours', () => {
|
||||
// Panels emit names, not hex. Before these resolved, the brightness test
|
||||
// could not parse them and returned white for everything - which is how an
|
||||
// amber badge shipped as white-on-yellow.
|
||||
it('gives yellow dark text, not white', () => {
|
||||
expect(readableText('yellow')).toBe('#1a1a1a')
|
||||
expect(colorStyle('yellow').color).toBe('#1a1a1a')
|
||||
})
|
||||
|
||||
it('still gives dark colours white text', () => {
|
||||
expect(readableText('red')).toBe('#ffffff')
|
||||
expect(readableText('green')).toBe('#ffffff')
|
||||
expect(readableText('gray')).toBe('#ffffff')
|
||||
})
|
||||
|
||||
it('resolves the name for the background as well as the text', () => {
|
||||
expect(colorStyle('yellow').backgroundColor).toBe('#ffc107')
|
||||
})
|
||||
|
||||
it('leaves hex and unknown values alone', () => {
|
||||
expect(colorStyle('#ffffff').color).toBe('#1a1a1a')
|
||||
expect(colorStyle('rebeccapurple').backgroundColor).toBe('rebeccapurple')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user