Shell: collapsible rail, DNS/Server groups with pins, always-on header with breadcrumbs and server status, Ctrl/Cmd+K command palette. Cache: left column is a lazily expanded tree of every zone, right column the selected node's records; both columns are ReUI Frames. Styles: dark variant follows data-theme (field and outline fills), 32 px selects, 36 px segmented strip, 40 px caption table heads, 56 px rows, 20/24/32 page padding. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
324 lines
15 KiB
CSS
324 lines
15 KiB
CSS
/* Logs. Two very different sub-screens: a two-column file viewer and a table with
|
|
fifteen filters. The table classes are the same as in Cache, Allowed and
|
|
Blocked. */
|
|
|
|
.wrap { composes: screen from '../../ui/SectionHeader.module.css'; }
|
|
|
|
.acts { display: flex; gap: var(--s-4); flex-wrap: wrap; justify-content: flex-end; align-items: center; }
|
|
|
|
/* ---- View Logs ---- */
|
|
|
|
/*
|
|
Master and detail, both at once, and at 390 too.
|
|
|
|
At 1440 it is `250px` for the list and the rest for the viewer. On narrowing it
|
|
**does not turn into navigation**: they stack and both stay present. And it is a
|
|
decision, not a convenience: a paginated collection would be worth turning into
|
|
list→detail, but here there are **three files**, so sending the user to another
|
|
screen to see one and back to see another costs more than it saves.
|
|
|
|
Each panel answers for itself: the list can fail without the viewer hearing about
|
|
it, and the other way round. That is why there is no "screen" state, there are
|
|
two.
|
|
|
|
The breakpoint is 720, the same one `SectionHeader`, `Segmented` and `PanelForm`
|
|
already use: below that, 250 px of list leave the viewer with less than half the
|
|
screen and a log line stops fitting anywhere.
|
|
*/
|
|
.split { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: var(--s-10); align-items: start; }
|
|
|
|
@media (max-width: 720px) {
|
|
.split { grid-template-columns: minmax(0, 1fr); }
|
|
}
|
|
|
|
/*
|
|
The window's height, handed down to the reader (see ViewLogs.tsx). The Shell's body
|
|
is a flex column for a screen marked `data-fills-window`; from there each step takes
|
|
the rest: the screen, the split under the header, the reader panel (stretched, while
|
|
the file list keeps its own height), its body, and `Raw`'s `fill` box at the end.
|
|
*/
|
|
.viewLogs { composes: wrap; flex: 1 1 auto; }
|
|
.viewLogs .split { flex: 1 1 auto; grid-template-rows: minmax(0, 1fr); }
|
|
.reader { align-self: stretch; display: flex; flex-direction: column; }
|
|
.readerBody { flex: 1 1 auto; display: flex; flex-direction: column; }
|
|
|
|
/* Stacked under the file list: 70 % of the window, see ViewLogs.tsx. */
|
|
@media (max-width: 720px) {
|
|
.viewLogs .split { grid-template-rows: auto; }
|
|
.readerBody { flex: none; height: 70dvh; }
|
|
}
|
|
|
|
/* The time of an entry, one step below its message: see LogText.tsx. */
|
|
.logStamp { color: var(--faint); }
|
|
/* A hundred lines of the file, laid out only near the screen: see LogText.tsx. */
|
|
.logChunk { display: block; content-visibility: auto; contain-intrinsic-block-size: auto 100lh; }
|
|
/* A run of zero bytes, laid out zero wide and on one line: see LogText.tsx. `clip`
|
|
and not `hidden`: Chromium leaves what `hidden` cuts out of a selection, and the
|
|
run is the file's own text, copied with the rest. */
|
|
.logNul { display: inline-block; inline-size: 0; overflow: clip; white-space: pre; vertical-align: top; }
|
|
|
|
.pbList { padding: var(--s-5); }
|
|
|
|
.logfiles { display: flex; flex-direction: column; gap: var(--s-1); }
|
|
.logfile { composes: row from '../../ui/list.module.css'; justify-content: space-between; }
|
|
.logfile .name { font-family: var(--font-mono); }
|
|
.sz { composes: annotationQuiet from '../../ui/text.module.css'; }
|
|
/* ---- Query Logs ---- */
|
|
|
|
.fs { composes: panel from '../../ui/Panel.module.css'; margin-bottom: var(--s-9); }
|
|
/* The box is a panel, so its title is a panel's (`ui/Panel`, `.title`): one step
|
|
above the field labels under it, not another row of small capitals. */
|
|
.fsTitle {
|
|
composes: title from '../../ui/Panel.module.css';
|
|
display: block; width: 100%; padding: var(--s-8) var(--s-10);
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
/*
|
|
The filters are laid out as GROUPS SIDE BY SIDE, not as one row per group.
|
|
|
|
They used to be five full-width rows (Source, Mode, Query, Response, Paging), each
|
|
with its name in a 110 px column on the left and its fields in a single line on
|
|
the right. Every row used less than half of its width, and the five stacked made
|
|
the panel so tall that at 1440x980 the results started at y=842, with one row on
|
|
screen: the table, which is what the screen is for, was below the fold.
|
|
|
|
Now each group carries its name ABOVE its fields and the groups flow side by side,
|
|
wrapping as the width runs out: two bands at 1440 instead of five rows (Source,
|
|
Mode and Paging; then Query and Response), so the table starts at y=629. Nothing
|
|
is collapsed or hidden at this width: the same fifteen controls, the same labels and
|
|
options. Below 960 the whole panel folds instead (see `.fsToggle` below).
|
|
Two things moved: `Live Update` went to the screen's header (QueryLogs.tsx says
|
|
why), and Paging now follows Mode in the document, which is also the tab order.
|
|
The grouping is the one decided below (the MODE group keeps its four fields and
|
|
its edge together).
|
|
*/
|
|
.fsIn {
|
|
display: flex; flex-wrap: wrap; align-items: flex-start;
|
|
gap: var(--s-9) var(--s-10);
|
|
padding: var(--s-8) var(--s-10) var(--s-10);
|
|
}
|
|
.group { display: flex; flex-direction: column; gap: var(--s-4); min-width: 0; max-width: 100%; }
|
|
/* Every head is as tall as the `While Live Update` pill that appears in Mode's,
|
|
so the fields of the groups in one band line up and the pill appearing does
|
|
not move anything. */
|
|
.groupLabel {
|
|
display: flex; align-items: center; gap: var(--s-6) var(--s-8); flex-wrap: wrap;
|
|
min-height: var(--s-11);
|
|
font-size: var(--t-1); font-weight: 600; letter-spacing: 0.09em;
|
|
color: var(--mute);
|
|
}
|
|
.groupCtl { display: flex; align-items: flex-end; gap: var(--s-7); flex-wrap: wrap; }
|
|
.field { display: flex; flex-direction: column; gap: var(--s-3); width: var(--ql-field, auto); }
|
|
/* On a phone each filter takes the whole line instead of its desktop width: the
|
|
same column for all fifteen, and no field left half as wide as its neighbour. */
|
|
@media (max-width: 560px) {
|
|
.group, .groupCtl, .field { width: 100%; }
|
|
}
|
|
.field label { font-size: var(--t-2); color: var(--mute); font-weight: 550; }
|
|
.field input, .field select { width: 100%; }
|
|
|
|
/*
|
|
Folded, below 960 (FOLD_QUERY in QueryLogs.tsx says what and why).
|
|
|
|
The title becomes the toggle and the whole title row is its target, so the button
|
|
carries the padding the heading had. Closed, the row loses its rule: under it there
|
|
is either the list of what is set, which brings its own, or the panel's edge.
|
|
*/
|
|
.fsIn[hidden] { display: none; }
|
|
.fsTitleFold { padding: 0; }
|
|
.fsTitleClosed { border-bottom: 0; }
|
|
.fsToggle {
|
|
display: flex; align-items: center; gap: var(--s-5);
|
|
width: 100%; padding: var(--s-8) var(--s-10);
|
|
border: 0; background: none; color: inherit; font: inherit; text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
/* The panel clips what overflows it, the focus ring included: drawn inside. */
|
|
.fsToggle:focus-visible { outline-offset: calc(-1 * var(--s-1)); }
|
|
.chevron { composes: chevron from '../../ui/Details.module.css'; color: var(--mute); }
|
|
[aria-expanded='true'] > .chevron { transform: rotate(90deg); }
|
|
/* What is set, by the fields' own labels: label quiet, value as typed. */
|
|
.fsSet {
|
|
display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-9);
|
|
margin: 0; padding: var(--s-7) var(--s-10) var(--s-8);
|
|
border-top: 1px solid var(--line);
|
|
font-size: var(--t-3); color: var(--ink);
|
|
}
|
|
.fsSetItem { min-width: 0; overflow-wrap: break-word; }
|
|
.fsSetLabel { color: var(--mute); }
|
|
|
|
/* The count is `ui/count.module.css`'s: see there for why. */
|
|
.count { composes: bar above from '../../ui/count.module.css'; }
|
|
.mono { composes: mono from '../../ui/text.module.css'; }
|
|
.nowrap { white-space: nowrap; }
|
|
/*
|
|
The Query Logs table: one line per row.
|
|
|
|
Every cell keeps to one line. Domain, Answer and the client address are cut with
|
|
an ellipsis and carry the whole value in their text and `title` (`Clip` in
|
|
QueryLogs.tsx); the rest are short by nature. Before, those three broke anywhere,
|
|
the timestamp took two lines, and a row with a long answer reached 200 px: at
|
|
1440x980 one row fitted on the first screen.
|
|
|
|
Answer is the elastic column: `max-width: 0` takes it out of the table's minimum
|
|
width and `width: 100%` hands it whatever the other columns leave, so a long list
|
|
of addresses never pushes the row menu out of the panel. Its minimum keeps a
|
|
readable piece of it where the table no longer fits and scrolls inside its panel.
|
|
Domain is cut at a fixed width instead, wide enough for 89 of 100 names on a page
|
|
of the showcase traffic. Written as `table.` to outweigh ui/Table's
|
|
`.table thead th` and `.table td`, which weigh the same as one class.
|
|
*/
|
|
table.queryTable thead th { white-space: normal; padding-left: var(--s-3); padding-right: var(--s-3); }
|
|
table.queryTable td { white-space: nowrap; padding-left: var(--s-3); padding-right: var(--s-3); }
|
|
table.queryTable td:first-child { padding-left: var(--s-7); }
|
|
table.queryTable td.answer { width: 100%; max-width: 0; min-width: 16ch; }
|
|
.clipDomain, .clipAnswer, .clipClient {
|
|
display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.clipDomain { max-width: 30ch; }
|
|
.clipClient { max-width: 24ch; }
|
|
/* Narrower windows: Domain gives up width first, and the timestamp breaks
|
|
between date and time (`Stamp`), so the table still fits at 1366 and needs
|
|
less of a scroll inside its panel below that. */
|
|
@media (max-width: 1439px) {
|
|
.clipDomain { max-width: 22ch; }
|
|
table.queryTable td.answer { min-width: 12ch; }
|
|
}
|
|
@media (max-width: 1365px) {
|
|
table.queryTable td.stamp { white-space: normal; }
|
|
}
|
|
/* When the rows stack (`stack` in ui/Table: the table no longer fits its panel,
|
|
which at 1024 and 768 it does not), each cell is as wide as its value and the
|
|
widths above stand down: the timestamp on one line, Domain and Answer cut only
|
|
where the row ends, and each value on its label's line. */
|
|
table.queryTable[data-stacked] td:first-child { padding-left: 0; }
|
|
table.queryTable[data-stacked] td.stamp { white-space: nowrap; }
|
|
/* Answer: its label and its one cut line side by side, the line taking the rest
|
|
of the row (a flex item shrinks; an inline box beside a label would not). */
|
|
table.queryTable[data-stacked] td.answer { display: flex; align-items: baseline; width: auto; max-width: 100%; min-width: 0; }
|
|
table.queryTable[data-stacked] .clipAnswer { min-width: 0; }
|
|
table.queryTable[data-stacked] .clipDomain,
|
|
table.queryTable[data-stacked] .clipClient { display: inline-block; max-width: 100%; vertical-align: bottom; }
|
|
table.queryTable[data-stacked] .rtt { display: inline; }
|
|
|
|
/*
|
|
Upstream's row colour code (logs.js:452-508), with the SAME mapping, drawn as a pill.
|
|
|
|
`rowClass` still decides one category per row from the RCODE and, within it, the
|
|
response type, exactly as upstream does. What changed is where it is painted.
|
|
Upstream lays it over the whole row; here it went as a 30 % background over every
|
|
cell, and on a page of mostly Cached rows the table read as one block of colour,
|
|
so the exception (a Blocked row) hardly stood out.
|
|
|
|
Now the row is neutral and the category sets ONE custom property, `--ql-tone`,
|
|
which the pill around the Response Type reads: the same 30 % over the panel the
|
|
whole row used to carry, so every measurement of `dev/row-colours.mjs` still
|
|
holds as it is (ΔE00 between pairs as a background, ink at 6.5:1 over the
|
|
lightest one).
|
|
|
|
The colour is the ROW's, not the cell's: an `NxDomain` row whose type is
|
|
`Recursive` gets the NX Domain colour on a pill that reads `Recursive`, exactly
|
|
as the whole row did before. That is why the legend still says "row colours".
|
|
|
|
**A pill and not an edge**: the edge belongs to the amber, and it means "this is
|
|
yours to change". And the colour is still reinforcement, never the only channel:
|
|
`RCODE` and `Response Type` are written in their cells, and the legend names the
|
|
seven conditions. One pair (`Server Failure`/`Cached` at 8.5) is below the
|
|
collision threshold, see `tokens.css`.
|
|
|
|
The custom property is called `--ql-tone` so that it cannot shadow a token: no
|
|
name in `tokens.css` starts with `--ql-`.
|
|
*/
|
|
.rServerFailure { --ql-tone: var(--ch-fail); }
|
|
.rBlocked { --ql-tone: var(--ch-block); }
|
|
.rNxDomain { --ql-tone: var(--ch-nx); }
|
|
.rRefused { --ql-tone: var(--ch-refuse); }
|
|
.rAuthoritative { --ql-tone: var(--ch-auth); }
|
|
.rRecursive { --ql-tone: var(--ch-rec); }
|
|
.rCached { --ql-tone: var(--ch-cache); }
|
|
|
|
/* A row with no category gets a pill with no fill, so the response type keeps
|
|
the same place on every row. */
|
|
.tone {
|
|
display: inline-block;
|
|
padding: var(--s-1) var(--s-5);
|
|
border-radius: var(--r-1);
|
|
background: color-mix(in srgb, var(--ql-tone, transparent) 30%, transparent);
|
|
white-space: nowrap;
|
|
}
|
|
.rtt { font-size: var(--t-2); color: var(--faint-on-tint); padding-left: var(--s-5); }
|
|
|
|
/*
|
|
The legend of the seven, and it is ALWAYS complete.
|
|
|
|
The design rule is explicit: every label that comes from the server carries its
|
|
legend entry. Here there were seven colours and none: the user saw coloured rows
|
|
and nothing explaining them.
|
|
|
|
Complete, and not "the ones on this page": a legend that changes with the data
|
|
shows six colours on one page and four on the next, and stops being a vocabulary
|
|
to become a summary of what is in front of you.
|
|
*/
|
|
.legend {
|
|
display: flex; flex-wrap: wrap; align-items: center;
|
|
gap: var(--s-5) var(--s-9);
|
|
padding: var(--s-7) var(--s-9);
|
|
margin-bottom: var(--s-7);
|
|
border: 1px solid var(--line); border-radius: var(--r-3); background: var(--pan);
|
|
}
|
|
.legendTitle {
|
|
font-size: var(--t-1); font-weight: 600; letter-spacing: 0.09em;
|
|
color: var(--mute);
|
|
}
|
|
.legendItem { display: inline-flex; align-items: center; gap: var(--s-5); font-size: var(--t-2); }
|
|
/* Each entry is the pill itself, the same one the Response Type cell carries,
|
|
followed by the condition that paints it. */
|
|
.legendItem .tone { font-weight: 550; color: var(--ink); }
|
|
.legendWhen { color: var(--faint); }
|
|
/* Folded (below 960): the seven pills stay, their conditions wait behind the
|
|
title, which is the toggle. */
|
|
.legendFolded .legendWhen { display: none; }
|
|
.legendToggle {
|
|
composes: hitArea from '../../ui/interaction.module.css';
|
|
display: inline-flex; align-items: center; gap: var(--s-4);
|
|
padding: 0; border: 0; background: none; font-family: inherit; cursor: pointer;
|
|
}
|
|
|
|
/* Centred on purpose: it is a bar filter, not a settings row. But the minimum
|
|
height is the same as `ui/Check.module.css`'s. */
|
|
/* `.check` went in the redesign: it was a hand copy of `ui/Check` with eight px of gap
|
|
instead of nine, and `uniformity.js` counted it as a separate signature of the
|
|
same checkbox. Query Logs uses the primitive now. */
|
|
|
|
|
|
|
|
/*
|
|
The group a MODE switches off: the edge, once for all four.
|
|
|
|
It is the same master-switch signal as `Settings` with the polarity inverted:
|
|
there the control is off because its master is OFF and the pill says `Needs …`;
|
|
here it is off because `Live Update` is ON and it says `While …`. The mechanics do
|
|
not change because what matters is the same: **the user can undo it themselves**,
|
|
and that is what the amber means.
|
|
|
|
It composes from `Form.module.css` so that it is THE SAME edge and not another one
|
|
like it: an inner shadow and not a border, because a border eats two pixels of the
|
|
box and shifts the content, as measured in `Settings`, where it gave away a third
|
|
grid signature that existed only on that screen.
|
|
|
|
It goes on the GROUP and not on each field because all four switch off together:
|
|
four pills saying the same thing are noise, and the `Settings` rule was already
|
|
one pill per group.
|
|
*/
|
|
/* The edge needs room to its left, and it takes it from the gap BEFORE the group
|
|
(the negative margin cancels the padding): a group that grew when the mode
|
|
switched on would push its neighbours to another band. */
|
|
/* The switch in the header: centred on the buttons beside it. */
|
|
.live { display: inline-flex; align-items: center; padding-left: var(--s-5); }
|
|
|
|
.byMode {
|
|
composes: dependent from '../../ui/Form.module.css';
|
|
padding-left: var(--s-8);
|
|
margin-left: calc(-1 * var(--s-8));
|
|
}
|