log panes: lines wrap under their start instead of scrolling sideways
The owner, 2026-10-16, a phone screenshot of a message's Delivery log:
"На телефоне при просмотре лога, появляются две полосы. Мы это можем
поправить?" - the horizontal scroll bars Chrome on Android drew over a
pre whose mail.log lines were wider than it. As the mockup of 216f366:
sp-log wraps (pre-wrap, overflow-wrap: anywhere) and no longer scrolls;
log_pane writes each line as a block, sp-ll, with a 4ch hanging indent and
its newline inside it, so copied text keeps one line per line. One
partial: the Delivery log of a message, System log, the outbound queue's
listing.
Tests: TestLogPaneIsAPreWithEscapedLines reads the new shape; the page
tests' substrings stand; TestPanelCSSIsTheMockupStylesheet holds panel.css
to the mockup. go vet + go test (26 packages) and make e2e on the test
server for this tree. CHANGELOG [Unreleased] § Fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,14 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Log panes wrap their lines instead of scrolling sideways**: a phone drew
|
||||||
|
two scroll bars over the Delivery log of a message, and the end of a
|
||||||
|
delivery line - `status=` and the reply - was out of sight. A long line
|
||||||
|
wraps, the rest indented under its start; copied text keeps one line per
|
||||||
|
line. The Delivery log, System log and the queue's listing.
|
||||||
|
|
||||||
## [2.9.0] - 2026-10-16
|
## [2.9.0] - 2026-10-16
|
||||||
|
|
||||||
The panel's DNS records read faster: a record DNS already holds as shown
|
The panel's DNS records read faster: a record DNS already holds as shown
|
||||||
|
|||||||
@@ -364,17 +364,19 @@ func TestTimelineLevels(t *testing.T) {
|
|||||||
`<li class="sp-pending"><time>next attempt</time>Still queued</li>`)
|
`<li class="sp-pending"><time>next attempt</time>Still queued</li>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The log is a pre: its lines are separated by a newline of their own, not by
|
// The log is a pre of lines: each a block of its own (sp-ll), which wraps
|
||||||
// the template's layout whitespace, and a line is escaped.
|
// under its start rather than scrolling sideways, ended by a newline inside it
|
||||||
|
// so copied text keeps one line per line - and nothing of the template's
|
||||||
|
// layout whitespace between them. A line is escaped.
|
||||||
func TestLogPaneIsAPreWithEscapedLines(t *testing.T) {
|
func TestLogPaneIsAPreWithEscapedLines(t *testing.T) {
|
||||||
out := partial(t, "log_pane", []LogLine{
|
out := partial(t, "log_pane", []LogLine{
|
||||||
{Time: "13:58:07", Text: "message-id=<a81f0c@alertmanager>"},
|
{Time: "13:58:07", Text: "message-id=<a81f0c@alertmanager>"},
|
||||||
{Time: "13:58:09", Text: "status=deferred", Level: "warn"},
|
{Time: "13:58:09", Text: "status=deferred", Level: "warn"},
|
||||||
{Text: "no time on this one", Level: "error"},
|
{Text: "no time on this one", Level: "error"},
|
||||||
})
|
})
|
||||||
want := `<pre class="sp-log"><span class="sp-d">13:58:07</span> message-id=<a81f0c@alertmanager>` + "\n" +
|
want := `<pre class="sp-log"><span class="sp-ll"><span class="sp-d">13:58:07</span> message-id=<a81f0c@alertmanager>` + "\n</span>" +
|
||||||
`<span class="sp-d">13:58:09</span> <span class="sp-w">status=deferred</span>` + "\n" +
|
`<span class="sp-ll"><span class="sp-d">13:58:09</span> <span class="sp-w">status=deferred</span>` + "\n</span>" +
|
||||||
`<span class="sp-e">no time on this one</span></pre>`
|
`<span class="sp-ll"><span class="sp-e">no time on this one</span>` + "\n</span></pre>"
|
||||||
if out != want {
|
if out != want {
|
||||||
t.Errorf("log_pane =\n%s\nwant\n%s", out, want)
|
t.Errorf("log_pane =\n%s\nwant\n%s", out, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,8 +295,12 @@ details.sp-record[open] .sp-record-toggle span + span { display: inline; }
|
|||||||
/* ---- logs, timeline, help */
|
/* ---- logs, timeline, help */
|
||||||
.sp-log {
|
.sp-log {
|
||||||
margin: 0; padding: 1rem 1.25rem; background: #191c21; color: #d9d5cc; border-radius: 0;
|
margin: 0; padding: 1rem 1.25rem; background: #191c21; color: #d9d5cc; border-radius: 0;
|
||||||
font-family: var(--sp-mono); font-size: var(--sp-fs-s); line-height: 1.6; overflow-x: auto; white-space: pre;
|
font-family: var(--sp-mono); font-size: var(--sp-fs-s); line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
/* One line of a log: long ones wrap inside the pane, the rest of the line
|
||||||
|
indented under its start, so nothing scrolls sideways - a phone drew the
|
||||||
|
scroll bars over the text (owner, 2026-10-16). */
|
||||||
|
.sp-log .sp-ll { display: block; padding-left: 4ch; text-indent: -4ch; }
|
||||||
.sp-log .sp-w { color: #e0b15c; } .sp-log .sp-e { color: #ef8f82; } .sp-log .sp-d { color: #7f8894; }
|
.sp-log .sp-w { color: #e0b15c; } .sp-log .sp-e { color: #ef8f82; } .sp-log .sp-d { color: #7f8894; }
|
||||||
.sp-timeline { list-style: none; margin: 0; padding: 0; }
|
.sp-timeline { list-style: none; margin: 0; padding: 0; }
|
||||||
.sp-timeline li { position: relative; padding: 0 0 1.1rem 1.6rem; border-left: 2px solid var(--sp-rule); margin-left: .4rem; }
|
.sp-timeline li { position: relative; padding: 0 0 1.1rem 1.6rem; border-left: 2px solid var(--sp-rule); margin-left: .4rem; }
|
||||||
|
|||||||
@@ -190,8 +190,8 @@
|
|||||||
{{/* log_pane is a dark block of log lines (input: []LogLine). The time is
|
{{/* log_pane is a dark block of log lines (input: []LogLine). The time is
|
||||||
dimmed, a warning amber, an error red. The newline between lines is part of
|
dimmed, a warning amber, an error red. The newline between lines is part of
|
||||||
the text: the pane is a pre. */}}
|
the text: the pane is a pre. */}}
|
||||||
{{define "log_pane"}}<pre class="sp-log">{{range $i, $l := .}}{{if $i}}
|
{{define "log_pane"}}<pre class="sp-log">{{range .}}<span class="sp-ll">{{if .Time}}<span class="sp-d">{{.Time}}</span> {{end}}{{if eq .Level "warn"}}<span class="sp-w">{{.Text}}</span>{{else if eq .Level "error"}}<span class="sp-e">{{.Text}}</span>{{else}}{{.Text}}{{end}}
|
||||||
{{end}}{{if $l.Time}}<span class="sp-d">{{$l.Time}}</span> {{end}}{{if eq $l.Level "warn"}}<span class="sp-w">{{$l.Text}}</span>{{else if eq $l.Level "error"}}<span class="sp-e">{{$l.Text}}</span>{{else}}{{$l.Text}}{{end}}{{end}}</pre>{{end}}
|
</span>{{end}}</pre>{{end}}
|
||||||
|
|
||||||
{{/* empty_state says what is absent and why that is fine (input: EmptyState). */}}
|
{{/* empty_state says what is absent and why that is fine (input: EmptyState). */}}
|
||||||
{{define "empty_state"}}<div class="sp-empty"><i class="ti {{.Icon}}"></i>{{template "inline" .Text}}</div>{{end}}
|
{{define "empty_state"}}<div class="sp-empty"><i class="ti {{.Icon}}"></i>{{template "inline" .Text}}</div>{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user