82b042b9b2 Redesign and refactor system console logs view (#35569)
* Overhaul Server Logs admin view for better sysadmin experience

Rewrites the Server Logs view from the ground up to address major
usability issues: truncated messages, wasted screen space, no visual
hierarchy, no live updates, and painful navigation on busy servers.

Key changes:
- Rewrite all components as functional React (hooks) replacing class components
- Replace rigid DataGrid table with purpose-built log viewer layout
- Color-coded log level badges (error=red, warn=amber, info=blue, debug=gray)
- Row background tinting for error/warn entries
- Inline row expansion replacing modal-only "Full Log event" workflow
- Compact timestamps (HH:MM:SS.mmm) with full timestamp on hover
- Log level quick-filter toggle buttons with counts (like browser DevTools)
- Configurable page size (50/100/200/500, default 200)
- Polling-based live tail with configurable interval (2s/5s/10s/30s)
- Time range presets (5m/15m/1h/24h) using existing API date params
- Search highlight with match count display
- Keyboard navigation (j/k, Enter expand, e jump to error, / search)
- Text wrapping and compact/comfortable density toggles
- Preferences persisted to localStorage
- Empty state with clear-filters action
- Warn-level color coding added to plain text view
- Updated tests for new component structure

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Update package-lock.json after dependency installation

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix four UX issues in server logs view

1. Pagination buttons no longer shift - page indicator has fixed min-width
2. Keyboard shortcuts shown via hover tooltip on ? button instead of
   raw unicode in footer
3. Empty toolbar buttons replaced with text labels (Compact/Comfortable,
   Wrap/No wrap) since compass-icons lacks line-spacing/wrap icons
4. Top banner buttons properly spaced with vertical separators between
   format toggle, time presets, live tail, and action buttons

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix remaining UX issues in server logs view

1. Remove Compact/Comfortable density toggle - always use compact mode
2. Fix pagination button shifting - footer-info and page-indicator have
   fixed min-widths, left group keeps all pagination controls together
3. Replace invisible ? tooltip with proper keyboard shortcut hints
   rendered as styled kbd elements in the footer
4. Restyle Reload/Download buttons as small tertiary buttons with icons
   (icon-refresh, icon-download-outline) to match the rest of the toolbar

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Redesign server logs toolbar for a cleaner, more professional layout

Move all toolbar controls into LogViewer component with two-row layout:
actions row (time presets, live tail, reload, download) and filter row
(search, level pills, wrap toggle). Rewrite SCSS to match new structure
with unified action-btn style, proper toolbar grouping, and per-level
color-coded pill states.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix time filter using local time instead of UTC

The formatDate helper used local time methods (getHours, etc.) but
appended a +00:00 UTC offset, causing the date range to be shifted
by the user's timezone offset. Use UTC methods to match the offset.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Enhance log detail view with copy buttons and admin deep links

- Add inline copy-to-clipboard buttons on ID fields, caller, URL, and
  other copyable values
- Deep link user_id, team_id, and channel_id to their admin console
  pages (only when value matches Mattermost 26-char ID pattern)
- Split detail view into core fields section and extra fields section
  with a visual separator for better hierarchy
- Add "Copy log line" action alongside existing "Copy JSON"
- Style copy buttons as subtle icons that appear inline with values

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix plain text toggle not fetching logs on format switch

onLogFormatToggle only set the isPlainLogs state but never triggered
a data fetch, so switching to plain text showed an empty view and
switching back to JSON showed stale data. Now explicitly fetch the
appropriate log format on toggle.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Rewrite plain text log view with toolbar and better UX

Rewrite PlainLogList as a functional component with:
- Toolbar with Reload, Download, Wrap toggle, and Copy All buttons
  matching the JSON viewer's design language
- Proper footer with page info, chevron pagination buttons, and a
  "Scroll to bottom" button that appears when you scroll up
- Smart follow-tail: auto-scrolls on new data, disengages when you
  scroll up manually, re-engages when you scroll back to bottom
- Color-coded error/warn lines with hover highlight
- Theme-aware styling (no more hardcoded white background)
- Dedicated SCSS file instead of relying on legacy admin-console styles

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix null crash in PlainLogList when logs prop is null

Redux state can be null before the plain logs fetch resolves, causing
a TypeError on .length access. Default to empty array.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Add line numbers, syntax highlighting, and fix wrap in plain text view

- Fix wrap adding newlines: changed from white-space pre-wrap on the
  line div to wrapping only the inner text span, and removed padding
  that caused visual offset
- Add non-selectable line numbers via CSS ::before pseudo-element with
  user-select: none, so Ctrl+A copies only log text
- Add line number toggle button in toolbar
- Add JSON/JSONL syntax highlighting: keys in blue, strings in green,
  numbers/bools in amber, brackets dimmed
- Add plain log format highlighting: timestamp dimmed, level tags
  color-coded (error=red, warn=amber, info=blue, debug=gray),
  key=value pairs with colored keys
- Memoize highlighted lines to avoid re-rendering on scroll/toggles

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix syntax highlighting contrast and page info unicode escape

- Replace CSS variable colors with hardcoded high-contrast values for
  syntax highlighting (GitHub-style palette): keys #0550ae, strings
  #0a3069, numbers #953800, error #cf222e, warn #9a6700
- Fix \u00B7 rendering literally in page footer by using the actual ·
  character in the defaultMessage string

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Add sort order toggle and improved pagination to plain text view

- Fix line number button being shorter than siblings by adding a
  text label ("Lines")
- Add newest-first/oldest-first sort toggle (client-side reverse)
  with sort-ascending/sort-descending icons
- Replace simple prev/next with full pagination: first page, prev,
  clickable page number, next buttons
- Click the page number to open a "Go to page" input with Enter to
  navigate and Escape to dismiss
- Line numbers stay correct when reversed (count down from total)
- Pass goToPage callback from parent logs.tsx

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix missing icons for sort toggle and first-page button

Replace non-existent compass icons with available alternatives:
- sort-ascending/sort-descending -> arrow-up/arrow-down
- page-first -> « (double chevron character) with styled span
These icons were not in the @mattermost/compass-icons set.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix all lint errors and add PlainLogList tests

- Fix 29 ESLint errors: unused imports, prop placement, regex wrapping,
  mixed operators, ExternalLink usage, nested ternaries, consistent-return
- Fix 47 Stylelint property order errors in both SCSS files
- Add 17 tests for PlainLogList component covering rendering, pagination,
  toolbar toggles, go-to-page, and empty/loading states

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Address code review findings

- Extract shared LogObjectWithAdditionalInfo type into types.ts
- Remove unused onFiltersChange prop from LogList and dead code in logs.tsx
- Fix potential interval stacking in useLogPolling visibility handler
- Add error handling for navigator.clipboard calls (non-HTTPS fallback)
- Fix highlightSearchTerm to highlight all occurrences, not just first
- Remove unused compact prop from LogRow (was always hardcoded true)
- Translate hardcoded strings in CopyButton and DetailValue with useIntl
- Fix time preset buttons to use intl.formatMessage for i18n

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix recursive clipboard call, LogObject reference, and stylelint errors

- Fix copyToClipboard calling itself instead of navigator.clipboard
- Move import to before function declaration
- Replace unimported LogObject with LogObjectWithAdditionalInfo
- Fix CSS property order in _admin-console.scss

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix duplicate i18n message IDs with different defaultMessages

Rename admin.logs.loading and admin.logs.noLogs in plain log view
to admin.logs.plain.loading and admin.logs.plain.noLogs to avoid
conflicts with the JSON log view which uses different default text.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Persist log format preference and rename JSON to Structured

- Remember the user's Structured/Plain text choice in localStorage
  so it persists across page navigations and sessions
- Rename "JSON" label to "Structured" since the view now offers
  filtering, live tail, keyboard nav, and expandable detail rows
- Only applies when the server supports both formats (toggle visible)

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix review findings: accessibility, type safety, polling, and theme support

- Add aria-labels to icon-only pagination and clear-preset buttons
- Reset page/expandedIndex/focusedIndex when logs dataset changes
- Fix extraFields filter to use != null instead of truthiness check
- Guard handleKeyDown from bubbling up from interactive descendants
- Refactor copyToClipboard to return Promise, set success only on resolve
- Fix live-tail with time presets by recomputing date range dynamically
- Fix duplicate reload on mount with hasMountedRef guard
- Fix LogObjectWithAdditionalInfo index signature to use unknown
- Add immediate fetch when polling enables
- Guard against duplicate intervals in visibility change handler
- Use ICU number formatting in i18n strings
- Replace hardcoded colors with theme-aware CSS variables
- Extract isPlainChar helper for readability
- Show copy failure feedback in plain log view

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Replace custom JSON highlighter with highlight.js

Use the project's existing highlight.js dependency and SyntaxHighlighting
utility for JSON syntax highlighting in the plain log viewer. This:

- Removes ~120 lines of custom JSON tokenizer (highlightJson, findStringEnd,
  isPlainChar) and 5 custom CSS classes
- Uses async highlight.js with the user's selected code theme (github,
  monokai, solarized-dark, solarized-light) for consistent styling
- Renders JSON lines with dangerouslySetInnerHTML inside .hljs container
- Shows sanitized HTML as initial fallback while highlight.js loads the
  JSON grammar asynchronously
- Keeps custom highlightPlainLog for Mattermost's [LEVEL] key=value format

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix hljs background override in plain log viewer

The highlight.js theme CSS sets background on .hljs elements (typically
white), causing JSON log lines to have a white background instead of
inheriting from the log viewer. Override to transparent.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Update webapp/channels/src/i18n/en.json

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add visible focus indicator to search input in log viewer

The search input had outline: none with no replacement focus style,
making it invisible to keyboard users. Add :focus-within on the
wrapper to show a border + box-shadow ring matching the theme's
button-bg color when the input is focused.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Use React Router Link for admin deep links in log row

Replace plain <a href> with React Router <Link to> for admin console
deep links (user_id, team_id, channel_id). This ensures links work
correctly on subpath deployments where the app is served from e.g.
/mattermost, since React Router automatically prepends the basename.
Also gives client-side navigation instead of full page reloads.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix follow-tail to honor newestFirst sort order in plain log viewer

When newestFirst is true, the tail (newest entries) is at the top of
the scroll container. Updated three locations:
- Auto-scroll useEffect: scrolls to top when newestFirst, bottom otherwise
- handleScroll: detects proximity to top as "at tail" when newestFirst
- Scroll-to-tail button: scrolls to top and shows up arrow when newestFirst

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Refactor copyToClipboard to guard navigator.clipboard and propagate errors

- Check for navigator.clipboard availability before calling writeText,
  rejecting the promise if unavailable instead of silently swallowing
- CopyButton now awaits the promise before showing success feedback
- All copy handlers (handleCopyJson, handleCopyLine, handleKeyDown 'c')
  now have .catch() handlers so unhandled rejections don't occur

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Clamp page index and clear stale selection on dataset changes

Previously the reset effect always set page to 0 when logs changed,
losing the user's position on reloads and live-tail updates. Now:
- Filter/sort changes still reset to page 0
- Dataset changes (reload, live-tail) clamp page to valid range
- handlePageSizeChange also clears focusedIndex (was missing)

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Guard navigator.clipboard in handleCopyAll to prevent TypeError

Check navigator.clipboard?.writeText exists before calling it. In
non-secure contexts where the Clipboard API is unavailable, show the
failure state immediately instead of throwing a synchronous TypeError.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Preserve wall-clock time in formatTimestampShort

Extract the time substring directly from the raw timestamp string via
regex before attempting Date parsing. This avoids toISOString() converting
to UTC and losing the original timezone offset from the server logs.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix "1 lines" pluralization in plain log viewer page info

Use ICU plural syntax so the message reads "1 line" (singular) and
"N lines" (plural) instead of always showing "lines".

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Align header column widths with row layout in log viewer

Header widths were mismatched with row columns:
- header-level: 40px → 32px (matches LogRow level badge)
- header-timestamp: 100px → 92px (matches LogRow timestamp)
- header-caller: 200px → 216px (192px caller + 8px gap + 16px expand indicator)

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Prevent overlapping poll fetches in useLogPolling

Added an isPollingRef guard so tick() returns immediately if a previous
fetch is still in-flight. Applied to both the main polling effect and
the visibility-resume branch to prevent concurrent fetches when
fetchLogs takes longer than intervalMs.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* fix linter errors

* Fix post-merge lint and E2E fallout from master

- useMemo(loadPrefs, []) -> inline arrow, for react-hooks/use-memo which
  landed with the ESLint 9 upgrade (#37039).
- Restore the trailing comma in logs.tsx dropped while resolving the
  conflict, for @stylistic/comma-dangle (#36770).
- Update the MM-T908 Cypress spec, which still asserted on .LogTable and
  the old banner reload button; both were replaced by the LogViewer
  markup on this branch.

* Remove CSS orphaned by the log viewer rewrite

.log__panel, .banner-buttons (incl. the __separator this branch added)
.LogTable and .plain-log-list no longer match anything: the class
components that rendered them were replaced by LogViewer/PlainLogViewer,
which carry their own flex sizing in log_list.scss / plain_log_list.scss.

* Add unit tests for LogRow and useLogPolling

The log viewer rewrite added log_row.tsx and use_log_polling.ts without
tests, while its sibling plain_log_list.tsx got a dedicated suite.

LogRow (51 tests): expand/collapse by click and Enter/Space, the `c` copy
shortcut, the copy-line format, search highlighting, extra field
rendering with admin console deep links, level classes, and the
stopPropagation that keeps the copy controls from toggling the row.

useLogPolling (21 tests): the immediate fetch on enable, interval
restarts, the re-entrancy guard, and the visibilitychange pause/resume.

The advance() helper flushes microtasks before advancing the fake
timers, otherwise the re-entrancy guard swallows the next tick and
several tests pass for the wrong reason. The resume test asserts on
jest.getTimerCount() rather than call counts, because counting fetches
cannot distinguish one interval from two.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Harden the server logs live-tail polling

A failed poll rejected out of the interval callback with nothing to
catch it, so any 500 or network blip from /api/v4/logs during live tail
surfaced as an unhandled promise rejection in the admin's browser.
Catch it and let the next tick recover.

Also collapse the two copies of the tick closure into one, so the
visibilitychange resume path cannot drift from the main effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Address review feedback on the server logs viewer

Restore the search debounce (PR review): the rewrite filtered on every
keystroke with the input value owned by the parent, so typing was gated
on a pass over every fetched log entry. /logs/query sends no pagination,
so the server returns up to LogsPerPageDefault (10000) entries per node.
The input value is now local to LogList and the term is debounced
upwards at 200ms, matching the interval the class component used.
Clearing bypasses the debounce so it feels immediate.

Fix the grid semantics (PR review): rows carried role='row' with plain
spans as children, the column header sat outside the role='grid'
container with no columnheader roles, and the empty state was a
non-row child. Drop to role='list'/'listitem', which matches the
row-level arrow key navigation that is actually implemented, and move
the disclosure semantics onto the row summary so aria-expanded sits on
a role that supports it. The keyboard navigation now focuses
.LogRow__main, and the empty state and refreshing indicator moved out
of the list so it only owns list items.

Also stop background polls from touching the loading state, and hoist
the repeated date formatting and filter constants.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Extract the remaining hardcoded strings in the logs viewer

The rewrite left literal strings in the level filter pills, the plain
text viewer controls and several titles. Move them to defineMessages so
they are translatable and extractable, and sync en.json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep the expanded log row stable across reloads

The expanded row was tracked by its index in the visible page, so a
reload or a live-tail poll either collapsed it or moved the expansion to
whatever log now sat at that index. Track it by content instead, and
give repeated identical lines distinct React keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Remove the stale User/Token ID lookup hint from the logs page

The banner told admins to look up a User ID or Token ID by pasting it
into the User Management search filter. That search is the user report
query, whose searchable columns are Username, FirstName, LastName,
Nickname and Email, so neither kind of ID matches anything.

It worked until the new User Management screen replaced the old page in
03f71c9c84 ("[MM-54296] Admin Reporting for Users"), which dropped the
getUserByTokenOrId lookup behind the search box. The hint has pointed at
missing behaviour ever since.

The user_id, team_id and channel_id fields in an expanded log entry now
link straight to their admin console pages, which covers the useful half
of the advice. Token IDs have no path yet; that needs its own change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop j/k log navigation and fix caller column clipping

Arrow keys already cover row navigation, so the j/k vim bindings were
redundant; the hint bar now shows the arrow keys instead.

The caller column was rigidly fixed-width, so on narrower viewports it
(and the expand indicator after it) got clipped by the list's
overflow-x: hidden with no visual indication. It now shrinks in favor
of the message column, and truncates from the start so the useful
file:line suffix stays visible; a title attribute exposes the full
path on hover.

* Add regression test for stable expanded row; dedupe plain log sort tooltip

Covers the content-keyed expanded-row behavior with a test that
rerenders with an equal-but-new logs array, matching what a reload or
live-tail poll hands down. Also lifts the plain log view's sort
tooltip strings into defineMessages so they aren't redefined on every
render.

* Fix review findings on the logs view

- Advance the live tail "last updated" label with its own clock, so the
  elapsed seconds actually move between polls instead of being pinned to
  "just now"
- Give the plain viewer's go-to-page input an accessible name
- Disable the log spinner, live-tail pulse and row expand animations under
  prefers-reduced-motion
- Dedupe the time preset range calculation into a helper
- Assert the log list owns only list items by comparing against its children,
  which a semantic <li> would have slipped past
- Repoint the Cypress spec at the log format toggle: it still asserted on the
  banner this branch removed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add Playwright coverage for the server logs page

Covers what the RTL suite cannot: that entries written to the real server log
reach the page, that live tail polls them in without a manual reload, that the
log format choice survives a reload, and that time presets go out in the layout
the server parses.

The date format assertion is deliberately strict. isLogFilteredByDate silently
falls back to a zero time on an unparseable date_from rather than rejecting it,
so a format change would return 200 with a wider result set and no test would
notice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fix plain log line numbering and expose the Lines toggle state

The plain logs API pages backwards from the newest line, so numbering by
page offset made older pages count higher than newer ones: the gutter
ascended within a page but descended across them. A single page cannot know
its position in the file, so number lines relative to the page instead.

Also add aria-pressed to the Lines button, whose label does not change with
its state. The sort and wrap buttons already carry their state in their
label, so the attribute would contradict them there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Expand only the clicked log row

The row identity used for expansion was timestamp|caller|msg, which is not
unique: concurrent HTTP requests log the same message from the same caller
inside the same millisecond, so clicking one row expanded every entry that
shared those three fields. Key on the whole entry, and fall back to the
row's disambiguated key so byte-identical entries stay independent too.

While here, read the visible rows through a ref in the row callbacks. They
depended on an array rebuilt every render, so their identity changed every
time and memo(LogRow) never held - every row re-rendered on each parent
render, including the once-a-second live tail clock.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Assert which row expanded in the identical entries test

Counting the expanded rows passed even when the wrong one opened, which is
the failure the test is named for. The entries carry no distinguishing
content, so check the details by row position instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep the log selection on the same entry across reloads

The focused row was tracked by its position in the visible page, so any
poll or reload that shifted the dataset left the highlight pinned to a
screen position while the entry it belonged to moved underneath it. Track
it by content key instead, the way the expanded row already is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Cover newest-first polling and DOM focus in the selection tests

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Offer 5s, 10s, 30s and 60s as live-tail intervals

Drop the 2s option, which polls the logs API harder than the page needs,
and add 60s for a slow tail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Test the log list filters, sorting, pagination and preferences

Covers the logic that only lived in LogList: the level pills including the
solo and last-level-on rules, the sort toggle, paging and page size with the
clamp that runs when a poll shrinks the dataset, the stored preferences
including malformed values, and the e and / shortcuts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Order the server logs spec imports the way the linter expects

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Default server logs to newest-first and hide decorative icons from a11y tree

Oldest-first with page 0 shown by default meant a newly written entry
could land past the visible page once the log file had more than 200
lines, which is why live tail and the e2e assertions on a freshly
written marker were failing. Default to newest-first instead. Also add
aria-hidden to decorative icons; one of them (the poll-interval
chevron) was leaking into its button's accessible name and breaking an
anchored-regex Playwright locator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Align server logs page with Mattermost admin console design

Rebuild the log viewer chrome on the shared admin console primitives instead
of a bespoke design system:

- Render the viewer inside .admin-console__container so it reads as the same
  white card the other admin console list pages use.
- Replace the custom .LogViewer__action-btn family with btn btn-tertiary /
  btn-quaternary / btn-primary / btn-icon.
- Replace the search field with the shared Input widget, and the level pills,
  time presets, poll interval and log format radios with Menu.Container
  selectors matching the System Users filter row.
- Match the column header and footer metrics of .adminConsoleListTable, and
  move keyboard shortcut hints from a kbd strip into a tooltip.
- Drop the -apple-system font override so the chrome inherits Open Sans, and
  keep monospace for log content only.
- Use the design tokens for radii, elevation and level colors.

Co-authored-by: ben.schumacher <ben.schumacher@mattermost.com>

* Fold live tail into one selector and update tests for the new markup

The live tail on/off toggle and its poll interval were two link-styled buttons
sitting loose in the filter row; they are now a single Menu selector matching
Levels, Duration and Log format, where picking an interval is what starts
polling. Also gives the core detail fields their own column widths so the
level chip does not leave a gap.

Co-authored-by: ben.schumacher <ben.schumacher@mattermost.com>

* Lead the server logs filter row with the live tail selector

Live tail decides whether the list is a snapshot or a stream, so it now comes
first, ahead of the controls that narrow what the stream contains. Its elapsed
status moves up with it and reserves its width, so the ticking seconds cannot
nudge the filters beside it.

Co-authored-by: ben.schumacher <ben.schumacher@mattermost.com>

* Reorder server logs filter row: search, levels, live tail, duration, log format

Moves the live tail selector after the levels menu so the filter row reads
search -> levels -> live tail -> duration -> log format, and drops the
"Updated Xs ago" live tail indicator along with its now-dead polling clock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix unused intl lint error in logs.tsx

useIntl's only remaining use was formatting the removed live-tail
"last updated" text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Count live intervals directly in the useLogPolling resume test

React 19's async act() leaves a pending timer behind, so jest.getTimerCount()
no longer equals the number of polling intervals.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Match the log detail copy buttons and monospace the detail values

Both copy actions use btn-quaternary with the copy icon; icon-code-json
does not exist in compass-icons. Timestamp and message in the expanded
details now use the monospace font like caller.

* Keep same-millisecond log entries in order when sorting

Ties now fall back to file order, so newest-first reverses entries that
share a timestamp instead of leaving them oldest-first. Timestamps are
rewritten to ISO 8601 before parsing since Safari rejects the server
format.

* Show server logs shortcuts as a key list and drop the next-error shortcut

The shortcuts tooltip was one long sentence; it now lists each shortcut
next to its key caps, and documents Esc. E / Shift+E are removed since
few would discover them and the Levels filter covers finding errors.

* Wrap the expanded log row instead of repeating its message

The expanded details repeated the row's message and level chip. The row
now wraps its message while expanded, and the details keep only the full
timestamp, caller and extra fields. Finishing a text selection inside the
row no longer toggles it.

* Reuse ShortcutSequence for the server logs keyboard hints

* Drop the 5 second live tail interval and default to 30 seconds

Every poll is a full logs query that scans the log file on every cluster
node, so a 5 second interval puts sustained load on the server.

* Derive server log fetches from state and run them in order

Every fetch now goes through one reload that reads the current format,
page, time preset and levels, triggered by an effect on those values:

- the enabled levels are sent with the query again, so filtering by level
  isn't limited to the newest 10000 lines
- switching back to structured logs recomputes the time preset's range
- fetches are queued, and a queued fetch replaced by a newer one is
  skipped, so a slow response can't overwrite a newer one
- the search filter moved into LogList so the footer total counts every
  loaded entry

* Format nested log fields, end presets at server time, trim comments

- nested objects in extra fields render, search and key as JSON instead
  of [object Object]
- time presets no longer send an end date, so the server's clock ends
  the range and browser clock skew can't drop the newest entries
- row keys are memoized and unchanged entries keep their object, so
  memo(LogRow) skips them after a poll
- multi-line and narrating comments in server_logs cut to one line

* Show the user behind user ID fields in the server logs view

User ID fields in an expanded log row now show the avatar and @username
next to the ID, and the admin console link moves from the ID to the user.

* Share one clipboard hook in the server logs view and drop dead code

- useClipboardCopy replaces the copy handlers in LogRow and PlainLogList,
  and falls back to the shared copyToClipboard where the Clipboard API is
  missing (plain HTTP), where copying used to fail
- useLogPolling no longer tracks lastUpdated, which nothing read
- full_log_event_modal is no longer imported anywhere

* Fix server logs live tail e2e test after removing last-updated indicator

The "Updated Xs ago" span was dropped from the filter row, so the test
now checks the live tail selector's value instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: describe redesigned server logs filters and live tail

Rename the JSON log format option to Structured, replace the full log
event modal reference with inline row expansion, and document the
duration filter and live tail added by the server logs redesign.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Expect an open-ended date_to in the server logs time range e2e test

Presets now send only date_from so the server ends the range at its own
clock; the test still required a formatted date_to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-21 10:08:38 +02:00
2026-10-04 12:35:53 +08:00
2018-05-30 10:23:25 -04:00

Mattermost logo

Mattermost is an open core, self-hosted collaboration platform that offers chat, workflow automation, voice calling, screen sharing, and AI integration. This repo is the primary source for core development on the Mattermost platform; it's written in Go and React, runs as a single Linux binary, and relies on PostgreSQL. A new compiled version is released under an MIT license every month on the 16th.

Deploy Mattermost on-premises, or try it for free in the cloud.

mattermost user interface

Learn more about the following use cases with Mattermost:

Other useful resources:

Table of contents

Install Mattermost

Other install guides:

Native mobile and desktop apps

In addition to the web interface, you can also download Mattermost clients for Android, iOS, Windows PC, macOS, and Linux.

Get Mattermost on Google Play Get Mattermost on the App Store Get Mattermost on Windows PC Get Mattermost on Mac OSX Get Mattermost on Linux

Get security bulletins

Receive notifications of critical security updates. The sophistication of online attackers is perpetually increasing. If you're deploying Mattermost it's highly recommended you subscribe to the Mattermost Security Bulletin mailing list for updates on critical security releases.

Subscribe here

Get involved

Learn more

License

See the LICENSE file for license rights and limitations.

Get the latest news

Contributing

Small Image

Please see CONTRIBUTING.md. Join the Mattermost Contributors server to join community discussions about contributions, development, and more.

S
Description
Mattermost is an open source platform for secure collaboration across the entire software development lifecycle..
Readme AGPL-3.0, Apache-2.0
2.6 GiB
0 Stars 3 Watchers 0 Forks
Languages
TypeScript 48.9%
Go 41.8%
JavaScript 5.1%
SCSS 2.3%
HTML 1.4%
Other 0.3%