diff --git a/app.css b/app.css index b914951..3976e1f 100644 --- a/app.css +++ b/app.css @@ -173,28 +173,32 @@ fieldset { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-d .kv dt { color: var(--ink-2); } .kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; } -/* health: public addresses, then one tile per check */ +/* health: public addresses on the left, one row per check on the right */ +.hcbody { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 12px; margin-top: 16px; } .hchead { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; } .hchead > span { font-size: 13px; color: var(--ink-2); } .hchead > span.bad { color: var(--bad-ink); font-weight: 500; } -.hcaddrs { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; margin-top: 16px; } -.hcaddr { background: var(--ground); border-radius: 10px; padding: 14px 16px; min-width: 0; } +.hcaddrs { display: flex; flex-direction: column; gap: 12px; } +.hcaddr { flex: 1; display: flex; flex-direction: column; justify-content: center; background: var(--ground); border-radius: 10px; padding: 14px 18px; min-width: 0; } .hcaddr .l { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-2); } .hcaddr .v { margin-top: 4px; font-size: 15px; font-weight: 500; overflow-wrap: anywhere; } -.hcaddr .v.mono { font-size: 20px; } +.hcaddr .v.mono { font-size: 22px; } .hcaddr .n { font-family: var(--sans); font-size: 12px; font-weight: 400; color: var(--ink-2); } .hcaddr.bad { background: #fdf6f5; box-shadow: inset 0 0 0 1px #e6b3b0; } .hcaddr.bad .v { color: var(--bad-ink); } -.hctiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; margin-top: 12px; } -.hctile { border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; min-width: 0; } -.hctile .l { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-2); } -.hctile .l > span:first-child { flex: 1; min-width: 0; } -.hctile .s { font-size: 15px; font-weight: 500; } -.hctile .r { font-size: 11.5px; color: var(--ink-3); overflow-wrap: anywhere; } -.hctile .p { font-size: 12.5px; color: var(--bad-ink); overflow-wrap: anywhere; } -.hctile.bad { border-color: #e6b3b0; background: #fdf6f5; } -.hctile.bad .s { color: var(--bad-ink); } -@media (max-width: 640px) { .hctiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } } +.hclist { border: 1px solid var(--line); border-radius: 10px; min-width: 0; } +.hcrow { display: grid; grid-template-columns: 8px minmax(0, 190px) minmax(0, 1fr); gap: 2px 14px; align-items: baseline; padding: 11px 16px; border-top: 1px solid var(--line-2); } +.hcrow:first-child { border-top: 0; } +.hcrow > .dot { align-self: center; } +.hcrow .l { font-size: 13px; color: var(--ink-2); } +.hcrow .v { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; min-width: 0; } +.hcrow .s { font-weight: 500; } +.hcrow .r { font-size: 12px; color: var(--ink-3); overflow-wrap: anywhere; } +.hcrow .p { grid-column: 2 / -1; font-size: 12.5px; color: var(--bad-ink); overflow-wrap: anywhere; } +.hcrow.bad { background: #fdf6f5; } +.hcrow.bad .s { color: var(--bad-ink); } +@media (max-width: 1000px) { .hcbody { grid-template-columns: minmax(0, 1fr); } } +@media (max-width: 640px) { .hcrow { grid-template-columns: 8px minmax(0, 1fr); } .hcrow .v { grid-column: 2; } } /* activity */ .ev { display: flex; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line-2); font-size: 13px; } diff --git a/app.js b/app.js index ebfcde5..f4994b4 100644 --- a/app.js +++ b/app.js @@ -1511,9 +1511,9 @@ const DNS_PRESETS = [['Quad9', '9.9.9.9, 149.112.112.112']]; // healthParts turns the server's checks into the Health card: the public - // address per IP family (from its uplink and public address checks), then - // one tile per other check with a plain-word status, the raw setting and, - // when it fails, what is wrong. + // address per IP family (from its uplink and public address checks) and, + // beside them, one row per other check with a plain-word status, the raw + // setting and, when it fails, what is wrong. function healthParts(checks) { const by = Object.fromEntries(checks.map((c) => [c.name, c])); const addrs = []; @@ -1563,14 +1563,14 @@ return h('section', { class: 'card', 'aria-labelledby': 'hc' }, h('div', { class: 'hchead' }, h('h2', { id: 'hc' }, 'Health'), h('span', { class: hp.failing ? 'bad' : null }, hp.failing ? hp.failing + ' of ' + hp.total + ' checks failing' : 'All ' + hp.total + ' checks pass')), + h('div', { class: 'hcbody' }, hp.addrs.length ? h('div', { class: 'hcaddrs' }, hp.addrs.map((a) => h('div', { class: a.ok ? 'hcaddr' : 'hcaddr bad' }, h('div', { class: 'l' }, dot(a.ok), a.label), h('div', { class: a.mono ? 'v mono' : 'v' }, a.value, a.note ? h('span', { class: 'n' }, ' ' + a.note) : null)))) : null, - h('div', { class: 'hctiles' }, hp.tiles.map((t) => h('div', { class: t.ok ? 'hctile' : 'hctile bad', title: t.title || null }, - h('div', { class: 'l' }, h('span', null, t.label), dot(t.ok)), - h('div', { class: 's' }, t.status), - t.raw ? h('div', { class: 'r mono' }, t.raw) : null, - t.problem ? h('div', { class: 'p' }, t.problem) : null)))); + h('div', { class: 'hclist' }, hp.tiles.map((t) => h('div', { class: t.ok ? 'hcrow' : 'hcrow bad', title: t.title || null }, + dot(t.ok), h('span', { class: 'l' }, t.label), + h('span', { class: 'v' }, h('span', { class: 's' }, t.status), t.raw ? h('span', { class: 'r mono' }, t.raw) : null), + t.problem ? h('div', { class: 'p' }, t.problem) : null))))); } async function viewServer(wrap) {