* 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>
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.
Learn more about the following use cases with Mattermost:
Other useful resources:
- Download and Install Mattermost - Install, setup, and configure your own Mattermost instance.
- Product documentation - Learn how to run a Mattermost instance and take advantage of all the features.
- Developer documentation - Contribute code to Mattermost or build an integration via APIs, Webhooks, slash commands, Apps, and plugins.
Table of contents
- Install Mattermost
- Native mobile and desktop apps
- Get security bulletins
- Get involved
- Learn more
- License
- Get the latest news
- Contributing
Install Mattermost
- Download and Install Mattermost Self-Hosted - Deploy a Mattermost Self-hosted instance in minutes via Docker, Ubuntu, or tar.
- Get started in the cloud to try Mattermost today.
- Developer machine setup - Follow this guide if you want to write code for Mattermost.
Other install guides:
- Deploy Mattermost on Docker
- Mattermost Omnibus
- Install Mattermost from Tar
- Ubuntu 20.04 LTS
- Kubernetes
- Helm
- More server 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 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.
Get involved
- Contribute to Mattermost
- Find "Help Wanted" projects
- Join Developer Discussion on a Mattermost server for contributors
- Get Help With Mattermost
Learn more
- API options - webhooks, slash commands, drivers, and web service
- See who's using Mattermost
- Browse over 700 Mattermost integrations
License
See the LICENSE file for license rights and limitations.
Get the latest news
- X - Follow Mattermost on X, formerly Twitter.
- Blog - Get the latest updates from the Mattermost blog.
- Facebook - Follow Mattermost on Facebook.
- LinkedIn - Follow Mattermost on LinkedIn.
- Email - Subscribe to our newsletter (1 or 2 per month).
- Mattermost - Join the ~contributors channel on the Mattermost Community Server.
- IRC - Join the #matterbridge channel on Freenode (thanks to matterircd).
- YouTube - Subscribe to Mattermost.
Contributing
Please see CONTRIBUTING.md. Join the Mattermost Contributors server to join community discussions about contributions, development, and more.




