- The INBUXA palette on warm surfaces, softer cards, buttons and inputs, and self-hosted Inter and Space Grotesk. - Each section's icon sits on a colored tile, colored by what the section is about. - The sidebar gets a guide line for sub-pages and a labeled Management / Settings / Account switch, and folds to a rail of tiles (remembered). - Every page has a header with its section's tile. - Fields and pages with no label of their own are spelled out in words (defaultCertificateId becomes Default certificate ID). - The dashboard greets you, and its stat cards wear colored tiles. - Unavailable live numbers are a calm note, not an error. - The cat appears in empty lists and while loading. - Chart colors work again: they were hex values wrapped in hsl().
71 lines
1.5 KiB
TypeScript
71 lines
1.5 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
/** Words that stay in capitals, or in their own casing, when a name is spelled out. */
|
|
const SPECIAL: Record<string, string> = {
|
|
id: 'ID',
|
|
ids: 'IDs',
|
|
url: 'URL',
|
|
urls: 'URLs',
|
|
uri: 'URI',
|
|
uris: 'URIs',
|
|
tls: 'TLS',
|
|
dns: 'DNS',
|
|
mx: 'MX',
|
|
ip: 'IP',
|
|
ips: 'IPs',
|
|
api: 'API',
|
|
http: 'HTTP',
|
|
https: 'HTTPS',
|
|
smtp: 'SMTP',
|
|
imap: 'IMAP',
|
|
pop3: 'POP3',
|
|
jmap: 'JMAP',
|
|
dkim: 'DKIM',
|
|
spf: 'SPF',
|
|
dmarc: 'DMARC',
|
|
arc: 'ARC',
|
|
acme: 'ACME',
|
|
ldap: 'LDAP',
|
|
sql: 'SQL',
|
|
ttl: 'TTL',
|
|
oauth: 'OAuth',
|
|
oidc: 'OIDC',
|
|
sni: 'SNI',
|
|
mta: 'MTA',
|
|
dav: 'DAV',
|
|
cal: 'Cal',
|
|
ai: 'AI',
|
|
llm: 'LLM',
|
|
otp: 'OTP',
|
|
totp: 'TOTP',
|
|
s3: 'S3',
|
|
};
|
|
|
|
/**
|
|
* `defaultCertificateId` → "Default certificate ID", `x:SystemSettings` →
|
|
* "System settings". For names the server gives no label of its own: a
|
|
* person reads words, not identifiers.
|
|
*/
|
|
export function humanize(name: string): string {
|
|
const bare = name.replace(/^x:/, '').split('/').pop() ?? name;
|
|
const words = bare
|
|
.replace(/[_-]+/g, ' ')
|
|
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
.trim()
|
|
.split(/\s+/)
|
|
.filter(Boolean);
|
|
return words
|
|
.map((w, i) => {
|
|
const special = SPECIAL[w.toLowerCase()];
|
|
if (special) return special;
|
|
const lower = w.toLowerCase();
|
|
return i === 0 ? lower[0].toUpperCase() + lower.slice(1) : lower;
|
|
})
|
|
.join(' ');
|
|
}
|