Skip to content

luci-app-tailscale-community: add sticky header and peer search filter - #9083

Open
Tokisaki-Galaxy wants to merge 1 commit into
openwrt:masterfrom
Tokisaki-Galaxy:tailscale-peer-filter
Open

Tokisaki-Galaxy wants to merge 1 commit into
openwrt:masterfrom
Tokisaki-Galaxy:tailscale-peer-filter

Conversation

@Tokisaki-Galaxy

@Tokisaki-Galaxy Tokisaki-Galaxy commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Description

When many devices are connected to the Tailnet, browsing and managing the device list can be cumbersome. This PR introduces two UI enhancements to improve the device list experience:

  1. Sticky Header and Scrollable Container:

    • Wraps the peer devices table in a scrollable container with a maximum height of 480px.
    • The table header remains sticky (position: sticky; top: 0; z-index: 1) during vertical scrolling.
  2. Peer Search Filter:

    • Adds a search filter input field above the devices table.
    • Allows real-time filtering of peers by hostname, IP address, or OS.
      .
    • Automatically preserves filter keyword, input focus, and cursor position across background status polling updates.

Tested on

  • LuCI master / OpenWrt snapshot
  • Tested with ~100 peer devices on Chrome / Firefox

Checklist

  • This PR is not from my main or master branch, but a separate branch.
  • Each commit has a valid Signed-off-by: tokisaki galaxy <moebest@outlook.jp> row matching the author name and email.
  • Each commit and PR title has a valid <package name>: title first line subject (under 80 characters).

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit checks

  • 441b2a2 "luci-app-tailscale-community: add sticky header and peer search filter": the body doesn't mention the changed callGetStatus() fallback defaults in getRunningStatus() and load() ({ status: 'stopped', peers: {} }). Add a bullet for it, or split it into its own commit.

Generated by Claude Code

const table = E('table', { 'class': 'cbi-table' }, [
E('tr', { 'class': 'cbi-table-header' }, peerTableHeaders.map(header => {
let th_style = 'padding-right: 20px; text-align: left;';
let th_style = 'padding-right: 20px; text-align: left; position: sticky; top: 0; background: inherit; z-index: 1;';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

background: inherit takes the <tr>'s background, which is transparent in most themes, so scrolled rows show through the sticky header. The header cell needs an opaque, theme-aware background (e.g. set by a theme class or CSS variable) rather than inherit.


Generated by Claude Code

Comment thread applications/luci-app-tailscale-community/po/templates/tailscale-community.pot Outdated

@BKPepe BKPepe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall idea makes sense, but I don't think the current implementation is ready yet.

The main issue is that the filter input is still recreated on every 10-second poll via devicesView.replaceChildren(renderDevices(res)). Re-applying its value and focus afterwards does not preserve the DOM/input state and can still interrupt IME composition, text selection, and caret handling. Please render the filter input once and only update the peer rows when polling.

The sticky header also uses background: inherit, which can leave the header transparent and cause scrolled rows to show through. Please use an opaque, theme-aware background instead.

There are a few smaller issues as well: the hard-coded #ddd border is not theme-aware, peerid is unused, and the new POT entry should be regenerated rather than appended manually.

Also, the change to the callGetStatus() fallback from { running: false } to { status: 'stopped', peers: {} } is unrelated to the UI feature and should either be explained in the commit body or split into a separate commit.

@Tokisaki-Galaxy

Copy link
Copy Markdown
Contributor Author

Hi @BKPepe

Thank you for the detailed and constructive review. I have refactored the implementation to address all points:

  1. IME Composition & Caret State:

    • The search filter input and table structure are now instantiated only once during initial mount.
    • On the 10-second polling updates, only the peer rows in <tbody> are re-rendered based on the active query value. This completely eliminates input destruction and preserves IME composition, text selection, and caret positioning.
  2. Sticky Header & Theme Compatibility:

    • Switched sticky header styling to .cbi-section-table-titles with an opaque background-color: var(--background-color, #fff) fallback.
    • Removed hardcoded #ddd border from the container, letting standard LuCI theme styles govern table styling.
  3. Code Cleanup:

    • Removed the unused peerid variable.
    • Reverted unrelated changes to callGetStatus() fallback defaults.
  4. POT Template:

    • Re-ordered the new .pot entries in alphabetical order matching upstream gettext structure.


const thead = E('thead', {}, [
E('tr', { 'class': 'cbi-table-header cbi-section-table-titles', 'style': 'position: sticky; top: 0; z-index: 1;' }, peerTableHeaders.map(header => {
let th_style = 'padding-right: 20px; text-align: left; background-color: var(--background-color, #fff);';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No in-tree theme defines --background-color, so the #fff fallback always applies, and the header stays white in bootstrap's dark mode. Bootstrap exposes [`--background-color-high` at cascade.css:27](https://github.com/openwrt/luci/blob/b7b2264279ba26ac9fdf11b825bf34e001a008e9/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css#L27``) instead.

Suggested change
let th_style = 'padding-right: 20px; text-align: left; background-color: var(--background-color, #fff);';
let th_style = 'padding-right: 20px; text-align: left; background-color: var(--background-color-high, #fff);';

Generated by Claude Code

…iner

- Add search input to filter peer devices by hostname, IP, or OS
- Mount filter input once and only update table body on poll
- Avoid recreating input element to prevent IME composition interruption
- Wrap devices table in a scrollable container with sticky header
- Use theme-aware sticky header styling and remove hardcoded border

Signed-off-by: tokisaki galaxy <moebest@outlook.jp>

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit checks

  • b2f005a "luci-app-tailscale-community: add peer search filter and scroll container" — "remove hardcoded border" and "Avoid recreating input element" describe changes between review rounds; master had no border and no filter input. Describe the change against master only.

Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants