v0.5.3: dark-mode branding preview + unified button spacing
UI polish: - Settings → Branding: each logo swatch now previews on a background matching where the mark lands (light page / dark page / dark PXE screen) regardless of the current page theme, so the Dark slot reads as dark even while viewing Settings in light mode. - Site-wide button spacing: add one rule (`.card .body > button`) giving every primary card action button the same gap above it, and drop the ad-hoc per-button inline margins (14/16/6px) so the look is uniform. Fixes the Hosts → "Bind MAC to target" button butting against the form. (Boot-menu highlight intentionally unchanged — a rotating-RGB highlight isn't possible in iPXE's static single-draw menu; deferred to a future custom-renderer effort.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a906c47f53
commit
4a17bcb242
Generated
+8
-8
@@ -2251,7 +2251,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openpxe"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -2273,7 +2273,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-core"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -2299,7 +2299,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-dhcp-proxy"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -2313,7 +2313,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-http-api"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -2349,7 +2349,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-ipxe-assets"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"openpxe-core",
|
||||
"rust-embed",
|
||||
@@ -2359,7 +2359,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-iso-store"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bcrypt",
|
||||
@@ -2386,7 +2386,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-tftp"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -2400,7 +2400,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-webui"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.95"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -304,6 +304,14 @@ button.ghost { background: transparent; color: var(--fg); border: 1px solid var(
|
||||
button.ghost:hover { background: var(--bg-panel-2); color: var(--fg); }
|
||||
button.danger { background: transparent; color: var(--err); border: 1px solid color-mix(in srgb, var(--err) 35%, transparent); }
|
||||
button.danger:hover { background: color-mix(in srgb, var(--err) 14%, transparent); color: var(--err); }
|
||||
/* v0.5.3: unified spacing for a card's primary action button(s). Any
|
||||
button that sits as a direct child of a card body (Save, Bind, Add,
|
||||
Launch, …) gets the same gap above it so it never butts against the
|
||||
form. Inline buttons inside table rows / toolbars / logo slots /
|
||||
modal action bars are nested deeper, so the `>` keeps them untouched.
|
||||
Adjacent action buttons on one row (e.g. Save + Send test) share the
|
||||
margin and stay aligned. */
|
||||
.card .body > button { margin-top: 16px; }
|
||||
|
||||
label.field {
|
||||
display: grid; gap: 4px; margin-bottom: 14px;
|
||||
|
||||
+13
-9
@@ -904,7 +904,7 @@
|
||||
protoSelect.addEventListener('change', syncProto);
|
||||
|
||||
// One add button; dispatches to the selected protocol's endpoint.
|
||||
const addShare = el('button', {style:'margin-top:14px', onclick: async () => {
|
||||
const addShare = el('button', {onclick: async () => {
|
||||
if (protoSelect.value === 'smb') {
|
||||
if (!smbServer.value || !smbShare.value) {
|
||||
shareMsg.replaceChildren(document.createTextNode('Server and share name are required.'));
|
||||
@@ -1411,7 +1411,7 @@
|
||||
// beneath the input row instead of butting against the password
|
||||
// fields. Mirrors the `Save SSO settings` button below for visual
|
||||
// parity between the two settings cards.
|
||||
const accountSave = el('button', {style:'margin-top:6px', onclick: async () => {
|
||||
const accountSave = el('button', {onclick: async () => {
|
||||
accountMsg.textContent = ''; accountMsg.className = 'msg';
|
||||
if (!currentPw.value) {
|
||||
accountMsg.textContent = 'Current password is required.';
|
||||
@@ -1549,7 +1549,7 @@
|
||||
};
|
||||
ssoMode.onchange = refreshSsoFields;
|
||||
const ssoMsg = el('div', {class:'msg', style:'margin-top:8px'});
|
||||
const ssoSave = el('button', {style:'margin-top:16px', onclick: async () => {
|
||||
const ssoSave = el('button', {onclick: async () => {
|
||||
ssoMsg.textContent = ''; ssoMsg.className = 'msg';
|
||||
const payload = {
|
||||
enabled: ssoEnabled.checked,
|
||||
@@ -1630,13 +1630,17 @@
|
||||
const logoMsg = el('div', {class:'msg', style:'margin-top:10px'});
|
||||
const bust = '?v=' + Date.now(); // bust the preview cache after a change
|
||||
const brandingPresence = status.branding || { light:false, dark:false, client:false };
|
||||
// Each swatch previews on a background matching where the mark
|
||||
// lands (light page / dark page / dark PXE screen), independent of
|
||||
// the operator's current page theme — so the Dark slot always reads
|
||||
// as dark even while viewing Settings in light mode.
|
||||
const slotDefs = [
|
||||
{ slot:'light', title:'Light mode', preview:'/assets/logo.svg?theme=light' + '&' + bust.slice(1),
|
||||
hint:'Shown on light-theme pages.', accept:'image/svg+xml,image/png,image/jpeg,image/webp,image/gif' },
|
||||
swatchBg:'#f4f5f7', hint:'Shown on light-theme pages.', accept:'image/svg+xml,image/png,image/jpeg,image/webp,image/gif' },
|
||||
{ slot:'dark', title:'Dark mode', preview:'/assets/logo.svg?theme=dark' + '&' + bust.slice(1),
|
||||
hint:'Shown on dark-theme pages.', accept:'image/svg+xml,image/png,image/jpeg,image/webp,image/gif' },
|
||||
swatchBg:'#0e1014', hint:'Shown on dark-theme pages.', accept:'image/svg+xml,image/png,image/jpeg,image/webp,image/gif' },
|
||||
{ slot:'client', title:'Client', preview:'/branding/pxe-logo' + bust,
|
||||
hint:'Above the PXE boot menu.', accept:'image/png,image/jpeg,image/webp,image/gif' },
|
||||
swatchBg:'#0e1014', hint:'Above the PXE boot menu.', accept:'image/png,image/jpeg,image/webp,image/gif' },
|
||||
];
|
||||
const slotCol = (def) => {
|
||||
const set = !!brandingPresence[def.slot];
|
||||
@@ -1662,7 +1666,7 @@
|
||||
el('span', {class:'name'}, def.title),
|
||||
set ? el('span', {class:'tag ok'}, 'set') : el('span', {class:'tag'}, 'default'),
|
||||
]),
|
||||
el('div', {class:'swatch', style: def.slot === 'light' ? 'background:#f4f5f7' : ''},
|
||||
el('div', {class:'swatch', style:'background:' + def.swatchBg},
|
||||
el('img', {src: def.preview, alt: def.title + ' logo'})),
|
||||
el('div', {class:'logo-slot-hint'}, def.hint),
|
||||
el('div', {style:'display:flex;gap:6px;flex-wrap:wrap'}, [
|
||||
@@ -1795,13 +1799,13 @@
|
||||
smtp_implicit_tls: sTls.checked,
|
||||
});
|
||||
|
||||
const saveBtn = el('button', {style:'margin-top:16px', onclick: async () => {
|
||||
const saveBtn = el('button', {onclick: async () => {
|
||||
nMsg.textContent = 'Saving…'; nMsg.className = 'msg';
|
||||
const r = await putJSON('/api/notify', collectNotify());
|
||||
if (r.ok) { nMsg.textContent = 'Saved.'; nMsg.className = 'msg ok'; render('settings'); }
|
||||
else { nMsg.textContent = 'Save failed: ' + (await r.text()); nMsg.className = 'msg err'; }
|
||||
}}, 'Save notification settings');
|
||||
const testBtn = el('button', {class:'ghost', style:'margin-top:16px;margin-left:8px',
|
||||
const testBtn = el('button', {class:'ghost', style:'margin-left:8px',
|
||||
onclick: async () => {
|
||||
nMsg.textContent = 'Sending test…'; nMsg.className = 'msg';
|
||||
// Save first so the test uses exactly what's on screen.
|
||||
|
||||
Reference in New Issue
Block a user