Files
flvx/.entire/metadata/ses_332fc6806ffeDArHciRt5Ctxja/summary.txt
T
sagit de9ab51def feat: node management enhancements and UX improvements (#297)
* feat(nodes): add renewal cycle and auto-advance scheduling

- Add renewal_cycle field to nodes for tracking paid vs free cycles
- Implement auto-advance scheduling when renewal is processed
- Add migration test for renewal_cycle column
- Update dashboard to show renewal cycle count
- Add renewal status display on node detail page

* fix(frontend): show rule count in compact view mode for forward pages

* style(frontend): format code and improve node card layout

Entire-Checkpoint: 08b760b76538

* fix: restore renewal.ts from main to resolve build errors
2026-03-09 10:01:55 +08:00

230 lines
12 KiB
Plaintext

Concise audit for issue #276, focused on `vite-frontend/src/pages` and `src/api`.
a) What already exists
1. Dashboard traffic auto-refresh
- Dashboard data is loaded via async API calls in a hook, not by page reload:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/dashboard/use-dashboard-data.ts`
- `useDashboardData()` calls `getUserPackageInfo()` and `getAnnouncement()`.
- The 24h traffic chart is already rendered from `statisticsFlows`:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/dashboard.tsx`
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/dashboard/components/flow-chart-card.tsx`
2. Node status real-time updates
- This is the strongest existing area.
- WebSocket realtime hook already exists:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node/use-node-realtime.ts`
- `useNodeRealtime()`
- Offline debounce exists:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node/use-node-offline-timers.ts`
- Node page consumes websocket messages and updates local state without reload:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node.tsx`
- `handleWebSocketMessage()`
- `loadNodes()`
- Realtime also includes system info updates and upgrade progress updates.
3. Form submissions without full page reload
- Broadly already implemented across main CRUD pages using async handlers + modal close + state refresh.
- Examples:
- Nodes: `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node.tsx`
- `handleSubmit()`
- Tunnels: `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/tunnel.tsx`
- `handleSubmit()`
- Forwards: `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/forward.tsx`
- `handleSubmit()`
- Users: `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/user.tsx`
- `handleSubmitUser()`
- I did not find page reload patterns in these CRUD pages (`location.reload` / `window.location.reload` absent there).
4. Local/partial list refresh after CRUD/toggle
- Some local optimistic/partial updates already exist:
- Node delete updates local state directly:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node.tsx`
- `confirmDelete()`
- Node edit updates local item directly:
- `handleSubmit()` edit branch
- Forward service toggle uses optimistic local update + rollback on failure:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/forward.tsx`
- `handleServiceToggle()`
- Sorting persists locally and updates item `inx` in-memory after success:
- Node `handleDragEnd()`
- Tunnel `handleDragEnd()`
- Forward `handleDragEnd()`
5. Batch operation progress UI
- Node upgrade has actual per-node progress UI:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node.tsx`
- `upgradeProgress` state
- websocket `type === "upgrade_progress"` handling in `handleWebSocketMessage()`
- `<Progress />` render around lines ~1307+
- Forward batch actions are factored and return structured outcomes:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/forward/batch-actions.ts`
6. Search/filter instant response
- Instant client-side filtering already exists in:
- Nodes: `sortedNodes` memo filters by `searchKeyword`
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node.tsx`
- Tunnels: `sortedTunnels`
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/tunnel.tsx`
- Forwards: `orderedForwards`
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/forward.tsx`
- Forward also has instant client-side filter dropdowns (`filterUserId`, `filterTunnelId`).
b) What is missing or partial
1. Dashboard traffic auto-refresh: missing
- `useDashboardData()` only fetches once on mount.
- No polling, websocket, refetch trigger, focus refresh, or manual refresh hook.
- So “AJAX no-refresh” exists only for initial load, not ongoing refresh.
2. Node realtime: partial but good
- Realtime status/system-info exists only for nodes.
- It reconnects with limited retries (5 attempts) and then stops.
- `loadNodes()` initially sets `systemInfo: null`, so a full reload can temporarily wipe metrics until WS catches up again.
- No obvious fallback polling if websocket dies permanently.
3. Form submission no full reload: mostly done
- CRUD forms are already async and modal-driven.
- But post-submit refresh style is inconsistent:
- some do local patch,
- many do full page-level list refetch (`loadData()`, `loadUsers()`, `loadNodes()`).
4. Local/partial list refresh after CRUD/toggle: partial/inconsistent
- Forward toggle is good and local.
- Node edit/delete are relatively local.
- But many operations still refetch the whole page dataset:
- Tunnel create/update/delete -> `loadData()`
- Forward create/update/delete -> `loadData()`
- User create/update/delete -> `loadUsers()`
- User tunnel assignment/update/remove -> `loadUserTunnels()`
- This is AJAX and avoids full browser reload, but not “partial/local refresh” in the stronger UX sense.
5. Batch operation progress UI: largely missing outside node upgrade
- Tunnel batch delete/redeploy: only loading spinner/toast + full reload, no per-item progress/result panel.
- Forward batch actions: summarized via toast and optional refresh, but no visible progress timeline/progress list.
- User batch tunnel assignment: no progress UI beyond loading state.
6. Search/filter instant response: mixed
- Nodes/tunnels/forwards are instant client-side.
- Users are not fully instant:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/user.tsx`
- `loadUsers()` is server-driven and tied to `useEffect([pagination.current, pagination.size, searchKeyword])`
- Enter key also manually calls `handleSearch()`
- So user search is AJAX but not purely local/instant in the same way.
c) Specific file paths and functions to touch for an implementation plan
Primary targets
1. Dashboard auto-refresh
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/dashboard/use-dashboard-data.ts`
- `useDashboardData()`
- extract `loadPackageData()` / `loadAnnouncement()` into stable callbacks
- add polling or visibility/focus-based refetch
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/dashboard.tsx`
- if adding manual refresh indicator/button, this is the page entry
- API source already exists:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/api/index.ts`
- `getUserPackageInfo()`, `getAnnouncement()`
2. Node realtime hardening / fallback
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node/use-node-realtime.ts`
- `useNodeRealtime()`
- retry policy, reconnect strategy, possible fallback refresh trigger
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node.tsx`
- `handleWebSocketMessage()`
- `loadNodes()`
- `handleSubmit()`
- `confirmDelete()`
- `handleBatchDelete()`
3. Replace whole-list reloads with local/partial updates
- Tunnels:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/tunnel.tsx`
- `loadData()`
- `handleSubmit()`
- `confirmDelete()`
- `handleBatchDelete()`
- `handleBatchRedeploy()`
- Forwards:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/forward.tsx`
- `loadData()`
- `handleSubmit()`
- `confirmDelete()`
- `handleServiceToggle()`
- `handleBatchDelete()`
- `handleBatchToggleService()`
- `handleBatchRedeploy()`
- `handleBatchChangeTunnel()`
- Users:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/user.tsx`
- `loadUsers()`
- `handleSubmitUser()`
- `handleConfirmDelete()`
- `loadUserTunnels()`
- `handleBatchAssignTunnel()`
- `handleUpdateTunnel()`
- `handleConfirmRemoveTunnel()`
- `handleConfirmResetFlow()`
- `handleConfirmResetTunnelFlow()`
4. Batch progress/result UX
- Nodes already have a model to copy:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node.tsx`
- `upgradeProgress`
- websocket `upgrade_progress`
- Forward batch abstraction:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/forward/batch-actions.ts`
- likely extend return shape if backend can expose richer progress/events
- API endpoints involved:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/api/index.ts`
- `batchDeleteForwards()`
- `batchPauseForwards()`
- `batchResumeForwards()`
- `batchRedeployForwards()`
- `batchChangeTunnel()`
- `batchDeleteTunnels()`
- `batchRedeployTunnels()`
- `batchDeleteNodes()`
- `batchUpgradeNodes()`
5. Search/filter behavior normalization
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/user.tsx`
- `loadUsers()`
- `handleSearch()`
- search input around lines ~718+
- Nodes/tunnels/forwards already have memo-based client filtering and are the pattern to follow.
d) Notable risks / edge cases
1. Dashboard polling could spam toasts
- `checkExpirationNotifications()` runs inside package load flow.
- If polling is added naively, expiration toasts could repeat or localStorage notification keys could churn unexpectedly.
2. Dashboard chart semantics
- `statisticsFlows` appears to be a snapshot list, not a stream API.
- Frequent polling may redraw aggressively and may need dedupe/staleness handling.
3. Node websocket auth/security
- Realtime URL sends token in query string (`secret=${getToken()}`) in:
- `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/quiet-canyon/vite-frontend/src/pages/node/use-node-realtime.ts`
- Any changes should be careful not to break backend expectations.
4. Realtime/local state merge complexity
- `loadNodes()` normalizes nodes and clears `systemInfo`.
- If partial refetch is introduced, merging fetched node metadata with live websocket state must avoid wiping online metrics/progress.
5. Batch APIs may not provide granular progress
- Current batch helpers mostly return summary counts, not per-item progress.
- True progress UI may require backend support, not only frontend work.
6. Forward toggle consistency
- `handleServiceToggle()` optimistically flips `serviceRunning` and later patches `status`.
- If backend returns partial failure or delayed actual state, UI may drift until a refresh.
7. User search behavior currently depends on backend pagination/search
- Moving to fully instant local filtering may conflict with server-side pagination semantics and large datasets.
Bottom line
- Already present: async modal CRUD, no browser reloads, strong node realtime, optimistic forward toggle, instant search/filter on node/tunnel/forward pages.
- Missing/partial for #276: dashboard traffic auto-refresh, consistent partial/local state updates after CRUD, batch progress UI outside node upgrade, and more unified instant-search UX on the user page.