Skip to content

feat(activity): per-miner bulk action detail modal [frontend]#60

Merged
rongxin-liu merged 22 commits intomainfrom
feat/22-activity-log-per-miner-detail-frontend
Apr 25, 2026
Merged

feat(activity): per-miner bulk action detail modal [frontend]#60
rongxin-liu merged 22 commits intomainfrom
feat/22-activity-log-per-miner-detail-frontend

Conversation

@rongxin-liu
Copy link
Copy Markdown
Contributor

@rongxin-liu rongxin-liu commented Apr 23, 2026

Problem

Bulk actions appear in the activity log as a single row with no record of which miners succeeded or failed. Operators have no way to drill into per-miner outcomes after a partial bulk failure. The backend (PR #25) added the batch_id field on ActivityEntry and the GetCommandBatchDeviceResults RPC; PR #71 added device name, IP, and MAC to each result row. This PR wires both into the frontend.

What ships

Frontend only. Requires backend PRs #25 and #71. Closes #22.

Screenshot 2026-04-24 at 17 03 38
  • Click-to-open detail modal for every activity row. Replaces the previous chevron-based inline expand. Clicking any activity row opens a modal showing a summary (event type, timestamp, scope, user, result with StatusCircle indicator) and, for batch actions, the succeeded/failed counts and a scrollable per-device results table.
  • Per-miner device info in the results table. Each device row shows the miner name (with "Unknown device" fallback for historical rows), IP address, and MAC address as secondary text, using the fields added in PR patch(activity backend): record device name/IP/MAC on per-miner bulk action detail #71.
  • Batch row grouping with correct count. When a batch completes, the backend writes both an initiated row (reboot) and a completed row (reboot.completed). The table collapses them into one visible entry (the completed row). The displayed count uses grouped.length so it always matches the number of visible rows. In-progress batches (no completion yet) still show the initiated row. The word "completed" is stripped from descriptions since the outcome summary already conveys completion.
  • Graceful edge-case handling. details_pruned shows "Per-miner details are no longer available." In-progress batches show "Results will appear as devices complete." Truncated responses show a count notice.
  • Terminal-only fetch caching. Batch results are only marked as "fetched" when the response status is finished or detailsPruned. In-progress batches will refetch on modal reopen so the UI stays current. Inflight dedup prevents concurrent requests for the same batch.
  • Keyboard-accessible activity rows. Rows have role="button", tabIndex={0}, and onKeyDown (Enter/Space) for screen reader and keyboard users, matching the shared List component pattern.
  • Timestamps include seconds. formatActivityTimestamp now uses toLocaleString and outputs MM/DD/YY, h:mm:ss AM/PM for audit precision.

Where to review

Data fetching

  • client/src/protoFleet/api/useCommandBatchDeviceResults.ts — custom hook with useState-based cache (keyed by batchId), useRef<Set> inflight dedup, fetchedRef that only records terminal responses (status === "finished" or detailsPruned), and handleAuthErrors / getErrorMessage error handling. Returns { fetch, getResult }.

Detail modal

  • client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx — two sections:
    • Summary — key-value rows (Event, Timestamp, Scope, User, Result with StatusCircle, optional Error) plus batch counts (Succeeded / Failed) when available.
    • BatchDeviceResults — renders loading, error, pruned, pending, and data states. Device table columns: Miner (name + IP/MAC secondary text), Status (colored text), Message, Time. Derives isPending internally from data.status.

Table + grouping

  • client/src/protoFleet/features/activity/components/ActivityTable.tsx — CSS grid layout (grid-cols-[1fr_12rem_10rem_13rem]). Key pieces:
    • groupActivities() — collects completed batch IDs in one pass, then filters out initiated rows whose batch already has a completed counterpart.
    • Activity count shows grouped.length so it always matches visible rows regardless of pagination state.
    • Stable handleDismiss via useCallback; every row is clickable with keyboard support.

Shared utilities

  • client/src/protoFleet/features/activity/utils/eventType.tsisCompletedEvent() and baseEventType() helpers. baseEventType uses isCompletedEvent guard + slice to strip only a trailing .completed suffix.
  • client/src/protoFleet/features/activity/utils/activityIcons.tsxgetActivityIcon uses baseEventType fallback so .completed events resolve to the correct icon.

Timestamp formatting

  • client/src/shared/utils/formatTimestamp.tsformatActivityTimestamp simplified to a single toLocaleString("en-US", ...) call with seconds included.

Test plan

  • Trigger a bulk command (reboot, blink LED, etc.) on a group of miners
  • Verify the activity log collapses the initiated + completed events into one row
  • Verify the displayed activity count matches the number of visible rows
  • Click any activity row — verify the modal opens with correct summary fields
  • For a batch row, verify Succeeded/Failed counts and per-device table appear
  • Verify device name, IP, and MAC display correctly in the miner column
  • Verify the device table scrolls independently (modal itself should not scroll)
  • Close and reopen a finished batch — verify no duplicate network request fires
  • Open a batch while it is still in progress, close, wait for it to finish, reopen — verify updated results appear
  • Verify non-batch rows (login, collection CRUD) show summary-only modal (no device table)
  • Verify details_pruned state shows the "no longer available" message
  • Verify in-progress batch shows "Results will appear as devices complete"
  • Verify truncated batch shows "Showing first N of M devices" notice
  • Verify "Load more" pagination still works on the activity page
  • Verify timestamps show seconds (e.g. "04/24/26, 1:25:09 PM")
  • Tab to an activity row and press Enter or Space — verify the modal opens

@github-actions github-actions Bot added javascript Pull requests that update javascript code client labels Apr 23, 2026
@rongxin-liu rongxin-liu changed the title feat(22): activity log per-miner detail — frontend feat(activity): per-miner success/failure bulk actions detail [frontend] Apr 23, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 23, 2026

🔐 Codex Security Review

Note: This is an automated security-focused code review generated by Codex.
It should be used as a supplementary check alongside human review.
False positives are possible - use your judgment.

Scope summary

  • Reviewed pull request diff only (8b5dc3e74f5411140a8a68799d6269f9e56bfb0c...5566afcfdf5f5d74a0589a1d160d297164b8e25d, exact PR three-dot diff)
  • Model: gpt-5.4

💡 Click "edited" above to see previous reviews for this PR.


Review Summary

Overall Risk: MEDIUM

Findings

[MEDIUM] Activity detail modal can accumulate and render large batch payloads without any bound

  • Category: Reliability
  • Location: client/src/protoFleet/api/useCommandBatchDeviceResults.ts:23, client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx:154
  • Description: The new hook keeps every GetCommandBatchDeviceResultsResponse in cache/fetchedRef for the life of the page and never evicts old batches or aborts stale requests. The modal then renders the full deviceResults array directly into the DOM. The backing RPC is capped server-side at 5,000 per-device rows, so opening several large activity entries can permanently retain large response objects and repeatedly force 5,000-row table renders.
  • Impact: On larger fleets, an operator can degrade or freeze the browser just by drilling into a handful of large command batches. Because requests are not canceled on modal close, rapid open/close cycles also keep expensive RPCs running after the UI no longer needs them.
  • Recommendation: Keep only the active batch or use a bounded/LRU cache, abort in-flight fetches when the modal closes or the batch changes, and paginate or virtualize deviceResults instead of rendering the full response at once.

[LOW] Client-side grouping now under-reports the activity count shown to operators

  • Category: Frontend
  • Location: client/src/protoFleet/features/activity/components/ActivityTable.tsx:19, client/src/protoFleet/features/activity/components/ActivityTable.tsx:45, client/src/protoFleet/features/activity/pages/ActivityPage.tsx:172
  • Description: useActivity still fetches the server totalCount, but the new table no longer receives it and instead shows grouped.length after collapsing initiated/completed rows on the client. Because that collapse happens after pagination, the header can now report far fewer “activities” than actually match the filter or remain on later pages.
  • Impact: The audit view can tell operators there are only the currently rendered grouped rows even though the backend still has more matching entries. That makes the page inconsistent with Load more and with the export button, and it understates the size of recent command activity during incident review.
  • Recommendation: Keep displaying a server-derived total, or move the initiated/completed collapsing to the backend so pagination and counts are computed from the same grouped dataset.

Notes

  • I did not find an auth, SQL injection, command injection, cryptostealing/pool-hijack, or protobuf wire-compatibility regression in the reviewed hunks.
  • The new batch-results RPC path is org-scoped on the server side, and persisted per-device error strings are sanitized before they reach this UI.
  • I could not run frontend build/test verification here because client/node_modules is absent and network access is restricted.

Generated by Codex Security Review |
Triggered by: @rongxin-liu |
Review workflow run

Base automatically changed from feat/22-activity-log-per-miner-detail-backend to main April 23, 2026 05:57
rongxin-liu and others added 3 commits April 23, 2026 01:59
…ils component

Data-fetching hook with in-memory cache and inflight dedup for the
GetCommandBatchDeviceResults RPC, plus a detail component that
renders loading/error/pruned/data states with a per-device result
table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strip the .completed suffix as a fallback lookup so completed batch
events reuse the same icon as their initiation counterpart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the shared List component with a custom grid layout that
groups initiated+completed batch events into a single row, adds
chevron toggle for batch rows, and lazy-fetches per-device results
via ActivityBatchDetails on expand.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rongxin-liu rongxin-liu force-pushed the feat/22-activity-log-per-miner-detail-frontend branch from 10b8d3b to 86c5f32 Compare April 23, 2026 05:59
…ce info

Switch from chevron-based inline expand to a click-to-open modal for
activity detail. Every activity row is now clickable, showing a summary
(event, timestamp, scope, user, result) and — for batch actions — the
per-miner device results table with name, IP, and MAC from PR #71.

- Add ActivityDetailModal with summary section + StatusCircle indicator
- Remove ActivityBatchDetails (superseded by modal)
- Collapse initiated + completed batch rows; fix count mismatch
- Strip "completed" from descriptions in the list view
- Extract isCompletedEvent/baseEventType to shared eventType utility
- Add fetchedRef to useCommandBatchDeviceResults to skip redundant RPCs
- Stabilize onDismiss callback and drop derived isPending prop

Made-with: Cursor
@rongxin-liu rongxin-liu changed the title feat(activity): per-miner success/failure bulk actions detail [frontend] feat(activity): per-miner bulk action detail modal [frontend] Apr 24, 2026
@rongxin-liu rongxin-liu marked this pull request as ready for review April 24, 2026 17:58
@rongxin-liu rongxin-liu requested a review from a team as a code owner April 24, 2026 17:58
Copilot AI review requested due to automatic review settings April 24, 2026 17:58
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6d954003c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/api/useCommandBatchDeviceResults.ts Outdated
Comment thread client/src/protoFleet/features/activity/components/ActivityTable.tsx Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds per-activity-row drill-down via a modal in ProtoFleet’s Activity page, including per-miner outcomes for bulk command batches using the backend’s new batch_id and GetCommandBatchDeviceResults RPC.

Changes:

  • Replaces the Activity log’s inline expand with a click-to-open ActivityDetailModal, including a per-device results table for batch activities.
  • Collapses initiated + completed batch activity rows into a single visible entry and updates icon/event-type handling for .completed events.
  • Introduces a frontend hook to fetch/cache batch device results and updates activity timestamps to include seconds.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
client/src/shared/utils/formatTimestamp.ts Updates activity timestamp formatting to include seconds.
client/src/protoFleet/features/activity/utils/eventType.ts Adds helpers for detecting/normalizing .completed event types.
client/src/protoFleet/features/activity/utils/activityIcons.tsx Falls back to base event type when resolving icons for completed events.
client/src/protoFleet/features/activity/components/ActivityTable.tsx Implements row grouping for batch initiated/completed events and switches to clickable-row layout + modal opening.
client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Adds the activity detail modal UI, including batch per-device results rendering.
client/src/protoFleet/api/useCommandBatchDeviceResults.ts Adds a hook to fetch and cache per-batch per-device results with inflight dedup.

Comment thread client/src/protoFleet/features/activity/components/ActivityTable.tsx Outdated
Comment thread client/src/protoFleet/api/useCommandBatchDeviceResults.ts
Comment thread client/src/protoFleet/api/useCommandBatchDeviceResults.ts Outdated
Comment thread client/src/protoFleet/features/activity/utils/eventType.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44c1e0b252

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

- Only mark batch results as "fetched" when status is terminal
  (finished or detailsPruned), so in-progress batches refetch on reopen
- Show grouped.length for activity count instead of mixing server-side
  total with local hidden count, which was inaccurate during pagination
- Add role="button", tabIndex, and onKeyDown to activity rows for
  keyboard and screen reader accessibility
- Harden baseEventType to strip only a trailing .completed suffix
  using slice instead of replace

Made-with: Cursor
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f96a31a269

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
- Add pollIntervalMs and activeBatchId options to the batch results
  hook, following useFleetCounts/useComponentErrors convention
- Auto-stop polling when batch reaches terminal state (finished or
  detailsPruned) by deriving isTerminal from cache
- Suppress loading spinner on background poll refetches
- Show "In progress" with pending StatusCircle for non-terminal
  batches instead of the false "Success" from the initiated entry
- Use POLL_INTERVAL_MS from shared polling constants

Made-with: Cursor
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86d3dd3d52

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
The custom grid layout dropped the data-testid attributes that the
shared List component previously provided. Add list-row, type, scope,
and user test IDs to restore activity E2E test selectors.

Made-with: Cursor
… entry

When a user opens an in-progress batch, the entry snapshot has
result="success". After polling detects the batch finished with
failures, isFailed stayed false because it read the stale snapshot.
Now derives failure from batchData.failureCount when terminal batch
data is available.

Made-with: Cursor
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aadcb7cd9e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
…per-miner-detail-frontend

Made-with: Cursor

# Conflicts:
#	client/src/protoFleet/features/activity/components/ActivityTable.tsx
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da026f0602

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
Comment thread client/src/protoFleet/api/useCommandBatchDeviceResults.ts
- Fall back to entry.result for failure detection when detailsPruned
  is true, since retention removes the failureCount aggregates
- Show deviceIdentifier as fallback miner label instead of generic
  "Unknown device" so historical rows remain distinguishable
- Convert 5 && expressions to ternaries for react/jsx-no-leaked-render

Made-with: Cursor
- Include the initial loading state (batchData is null) in the
  batchInProgress check so the modal shows "In progress" instead of
  a brief false "Success" flash while the first fetch is in flight
- Preserve existing cached batch data on polling errors instead of
  wiping it with null, so transient failures don't regress the modal

Made-with: Cursor
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 880f9e3100

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
Only show the error-only view when there is no cached data. When
previous poll data exists, continue rendering the device results
so transient failures don't hide already-loaded outcomes.

Made-with: Cursor
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2b5b5adfe

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/activity/components/ActivityDetailModal.tsx Outdated
…is missing

Only treat missing batch data as in-progress for initiated (non-
completed) entries. Completed activity entries already carry a
terminal result, so showing "In progress" during an RPC failure
misreports the audit outcome.

Made-with: Cursor
Comment thread client/src/shared/utils/formatTimestamp.ts Outdated
@rongxin-liu rongxin-liu force-pushed the feat/22-activity-log-per-miner-detail-frontend branch from 090e7b4 to 5566afc Compare April 25, 2026 23:02
@rongxin-liu rongxin-liu merged commit 5bf1ee7 into main Apr 25, 2026
171 of 209 checks passed
@rongxin-liu rongxin-liu deleted the feat/22-activity-log-per-miner-detail-frontend branch April 25, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity log should capture per-miner success/failure detail for bulk actions

3 participants