Conversation
- React 18 SPA with Vite, TypeScript, TailwindCSS v4, React Router v6 - Served at /ui/ via go:embed with SPA fallback routing - Auth: basic auth login, admin detection via /locker probe - Pages: node browser with pagination/filters/sort, node detail with tabbed view (file info, attributes editor, ACL management, indexes, locations), file upload with drag-and-drop and progress, download with pre-auth URL support - Admin pages: server dashboard, storage location inspector, locker view, execution trace controls with download/summary/events analysis - shock-ts client updates: authType (basic/oauth) support, admin methods (locker, locked/*, trace, location info), deleteIndex, trace download/summary/events, corresponding React hooks - New Go endpoints: GET /trace/download, /trace/summary, /trace/events - Multi-stage Dockerfile with Node.js build step - build-ui.sh script for local development Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- API Token section: copyable Authorization header, curl examples for list/upload/download - Preferences: theme toggle (dark/light), default page size persisted to localStorage - Account info: username and admin/user role badge - Server Configuration (admin only): version, uptime, auth methods, anonymous permissions, attribute indexes - Sidebar: added Settings link for all users - NodesPage: reads default page size from settings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an embedded React single-page UI to Shock (served at /ui/ from the Go binary), extends the shock-ts client to support basic auth plus new admin/trace APIs, and wires new server endpoints to support trace download/analysis.
Changes:
- Embed and serve a Vite-built React SPA under
/ui/viago:embedwith SPA fallback routing. - Add trace analysis endpoints (
/trace/download,/trace/summary,/trace/events) backed bygo tool trace, plus client/hooks to consume them. - Introduce a
clients/npm workspace (shock-ts + shock-ui), a build script, and Docker multi-stage build for UI artifacts.
Reviewed changes
Copilot reviewed 64 out of 67 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| shock-server/ui/ui.go | Embedded SPA file server with SPA fallback routing |
| shock-server/ui/dist/.gitkeep | Placeholder to keep embed directory in repo |
| shock-server/trace.go | Adds helpers to locate latest trace and run go tool trace |
| shock-server/main.go | Adds /trace/* endpoints and mounts embedded UI at /ui/ |
| scratchpad.md | Updates scratchpad notes for UI branch and local dev info |
| clients/shock-ui/vite.config.ts | Vite config (base /ui/, aliases, dev proxy) |
| clients/shock-ui/tsconfig.tsbuildinfo | TypeScript incremental build artifact (currently committed) |
| clients/shock-ui/tsconfig.json | TS compiler configuration for the SPA |
| clients/shock-ui/src/vite-env.d.ts | Vite type references |
| clients/shock-ui/src/providers/ShockUIProvider.tsx | Composes React Query + shock-client provider + auth |
| clients/shock-ui/src/providers/AuthProvider.tsx | Basic-auth login state + sessionStorage persistence |
| clients/shock-ui/src/pages/UploadPage.tsx | File upload UI with optional JSON attributes |
| clients/shock-ui/src/pages/SettingsPage.tsx | Settings (theme, page size, token copy helpers) |
| clients/shock-ui/src/pages/NodesPage.tsx | Node browser with filters/sort/pagination |
| clients/shock-ui/src/pages/NodeDetailPage.tsx | Node detail view (download, attributes, ACL, indexes, locations) |
| clients/shock-ui/src/pages/LoginPage.tsx | Login route + redirect when already authenticated |
| clients/shock-ui/src/pages/AdminTracePage.tsx | Admin trace page wrapper |
| clients/shock-ui/src/pages/AdminLockerPage.tsx | Admin locker page wrapper |
| clients/shock-ui/src/pages/AdminLocationsPage.tsx | Admin locations page wrapper |
| clients/shock-ui/src/pages/AdminDashboardPage.tsx | Admin dashboard page wrapper |
| clients/shock-ui/src/main.tsx | React entrypoint |
| clients/shock-ui/src/lib/utils.ts | UI helpers (classnames merge, formatting, basic auth encoding) |
| clients/shock-ui/src/lib/auth.ts | Session storage helpers for auth state |
| clients/shock-ui/src/lib/api-url.ts | Dev vs embedded API base URL selection |
| clients/shock-ui/src/index.css | Tailwind v4 theme tokens and base styles |
| clients/shock-ui/src/hooks/use-is-admin.ts | Convenience hook for admin status |
| clients/shock-ui/src/hooks/use-auth.ts | Auth context + hook |
| clients/shock-ui/src/components/upload/UploadZone.tsx | Drag/drop file selector component |
| clients/shock-ui/src/components/upload/UploadProgress.tsx | Upload progress bar display |
| clients/shock-ui/src/components/ui/tabs.tsx | Custom tabs components |
| clients/shock-ui/src/components/ui/skeleton.tsx | Skeleton loader component |
| clients/shock-ui/src/components/ui/input.tsx | Input component wrapper |
| clients/shock-ui/src/components/ui/card.tsx | Card UI primitives |
| clients/shock-ui/src/components/ui/button.tsx | Button variants via CVA |
| clients/shock-ui/src/components/ui/badge.tsx | Badge variants via CVA |
| clients/shock-ui/src/components/nodes/Pagination.tsx | Pagination controls + page size selector |
| clients/shock-ui/src/components/nodes/NodeTable.tsx | Node list table with actions |
| clients/shock-ui/src/components/nodes/NodeFilters.tsx | Filter parsing + sort controls |
| clients/shock-ui/src/components/nodes/AttributeEditor.tsx | JSON attribute editor with save/reset |
| clients/shock-ui/src/components/layout/Sidebar.tsx | App navigation sidebar (admin links gated) |
| clients/shock-ui/src/components/layout/Header.tsx | Header (server info, theme toggle, logout) |
| clients/shock-ui/src/components/layout/AppShell.tsx | Layout shell for protected routes |
| clients/shock-ui/src/components/indexes/IndexList.tsx | Index CRUD UI |
| clients/shock-ui/src/components/download/PreAuthButton.tsx | Pre-authenticated URL generator + copy |
| clients/shock-ui/src/components/download/DownloadButton.tsx | Direct download via shock client |
| clients/shock-ui/src/components/auth/ProtectedRoute.tsx | Auth/admin route guards |
| clients/shock-ui/src/components/auth/LoginForm.tsx | Login form UI |
| clients/shock-ui/src/components/admin/TraceControls.tsx | Trace start/stop/download + analysis tabs |
| clients/shock-ui/src/components/admin/LockerView.tsx | Locked nodes/files/indexes view |
| clients/shock-ui/src/components/admin/LocationBrowser.tsx | Location inspector with missing/present lists |
| clients/shock-ui/src/components/admin/Dashboard.tsx | Admin dashboard summary cards |
| clients/shock-ui/src/components/acl/AclPanel.tsx | ACL management UI |
| clients/shock-ui/src/components/ErrorBoundary.tsx | Top-level UI error boundary |
| clients/shock-ui/src/App.tsx | Router + code-splitting + protected/admin routing |
| clients/shock-ui/package.json | SPA package manifest (deps + build scripts) |
| clients/shock-ui/index.html | SPA HTML entry |
| clients/shock-ui/.gitignore | Ignores node_modules and dist |
| clients/shock-ts/src/types.ts | Adds authType + admin/location/locker types |
| clients/shock-ts/src/react/provider.tsx | Propagates authType through ShockProvider |
| clients/shock-ts/src/react/index.ts | Exports new admin/index/trace hooks |
| clients/shock-ts/src/react/hooks.ts | Adds admin/index/trace hooks and query keys |
| clients/shock-ts/src/index.ts | Re-exports new types |
| clients/shock-ts/src/client.ts | Adds authType header support + admin APIs + deleteIndex |
| clients/package.json | NPM workspace root for shock-ts + shock-ui |
| build-ui.sh | Builds shock-ts + shock-ui and copies dist for embedding |
| Dockerfile | Adds Node build stage and copies UI dist into embed dir |
| .gitignore | Ignores UI build outputs and node_modules |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| <button | ||
| className={cn( | ||
| "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2", | ||
| ctx.value === value && "bg-background text-foreground shadow", | ||
| className |
There was a problem hiding this comment.
TabsTrigger should set type="button" on the underlying <button>; otherwise, if tabs are used inside a <form>, clicking a tab will submit the form by default.
| {node.file.size > 0 && ( | ||
| <Link to={`/ui/nodes/${node.id}`}> | ||
| <Button variant="ghost" size="icon" title="Download"> | ||
| <Download className="h-3.5 w-3.5" /> | ||
| </Button> |
There was a problem hiding this comment.
This action is labeled "Download" but it only navigates to the node detail route (/ui/nodes/:id) and doesn’t trigger a download. Either wire this button to the actual download behavior (e.g., call downloadFile / link to the ?download endpoint) or rename it to something like "View" to match the navigation.
shock-server/trace.go
Outdated
| cmd := exec.Command(goPath, "tool", "trace", "-d="+mode, traceFilePath) | ||
| out, err := cmd.CombinedOutput() | ||
| if err != nil { | ||
| return out, fmt.Errorf("go tool trace failed: %w\n%s", err, string(out)) | ||
| } |
There was a problem hiding this comment.
CombinedOutput() buffers the entire go tool trace output in memory and the command can run for a long time on large traces. Consider using exec.CommandContext with a reasonable timeout/cancellation and streaming/limiting output size to reduce memory/CPU risk for this admin endpoint.
| if strings.HasPrefix(e.Name(), "trace.") && strings.HasSuffix(e.Name(), ".log") { | ||
| // Skip the currently-active trace file | ||
| if traceOn && traceFile != nil && e.Name() == filepath.Base(traceFile.Name()) { | ||
| continue |
There was a problem hiding this comment.
The check for the currently-active trace file reads traceOn/traceFile without synchronization while /trace/start and /trace/stop can update them concurrently, which can cause data races. Protect these globals with a mutex/atomic (and use the same lock in start/stop and latestTraceFile).
| // Fall back to index.html for SPA routing | ||
| r.URL.Path = "/" | ||
| fileServer.ServeHTTP(w, r) |
There was a problem hiding this comment.
If dist/index.html is missing (e.g., fresh checkout where only .gitkeep exists), falling back to r.URL.Path = "/" can cause http.FileServer to serve a directory listing. Consider checking for index.html and returning 404/500 with a helpful message and/or disabling directory listings.
| @@ -0,0 +1 @@ | |||
| {"root":["./src/app.tsx","./src/main.tsx","./src/components/errorboundary.tsx","./src/components/acl/aclpanel.tsx","./src/components/admin/dashboard.tsx","./src/components/admin/locationbrowser.tsx","./src/components/admin/lockerview.tsx","./src/components/admin/tracecontrols.tsx","./src/components/auth/loginform.tsx","./src/components/auth/protectedroute.tsx","./src/components/download/downloadbutton.tsx","./src/components/download/preauthbutton.tsx","./src/components/indexes/indexlist.tsx","./src/components/layout/appshell.tsx","./src/components/layout/header.tsx","./src/components/layout/sidebar.tsx","./src/components/nodes/attributeeditor.tsx","./src/components/nodes/nodefilters.tsx","./src/components/nodes/nodetable.tsx","./src/components/nodes/pagination.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/upload/uploadprogress.tsx","./src/components/upload/uploadzone.tsx","./src/hooks/use-auth.ts","./src/hooks/use-is-admin.ts","./src/lib/auth.ts","./src/lib/utils.ts","./src/pages/admindashboardpage.tsx","./src/pages/adminlocationspage.tsx","./src/pages/adminlockerpage.tsx","./src/pages/admintracepage.tsx","./src/pages/loginpage.tsx","./src/pages/nodedetailpage.tsx","./src/pages/nodespage.tsx","./src/pages/uploadpage.tsx","./src/providers/authprovider.tsx","./src/providers/shockuiprovider.tsx"],"version":"5.9.3"} No newline at end of file | |||
There was a problem hiding this comment.
This file is a TypeScript incremental build artifact produced by tsc -b and is environment-specific. It shouldn’t be committed; remove it from the repo and add tsconfig.tsbuildinfo (or *.tsbuildinfo) to the appropriate .gitignore.
| try { | ||
| attributes = JSON.parse(attrsText); | ||
| } catch { | ||
| return; // Don't upload if JSON is invalid | ||
| } |
There was a problem hiding this comment.
If the attributes JSON is invalid, this returns early with no user-visible feedback, which can look like the upload button does nothing. Consider storing a parse error in state and rendering an error message near the textarea.
| function TabsList({ children, className }: { children: ReactNode; className?: string }) { | ||
| return ( | ||
| <div className={cn("inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className)}> | ||
| {children} | ||
| </div> |
There was a problem hiding this comment.
The custom tabs UI is missing ARIA roles/attributes and keyboard interaction (e.g., role="tablist"/"tab", aria-selected, roving tabIndex, arrow-key navigation), which hurts accessibility for keyboard and screen-reader users. Consider adding these semantics or using an accessible tabs primitive.
shock-server/main.go
Outdated
| responder.RespondWithData(w, r, "trace stoped") | ||
| }) |
There was a problem hiding this comment.
Response string has a typo ("trace stoped"). Since this text is user-visible (UI uses it), please correct it to "trace stopped" for consistency and clearer UX.
| export function getStoredPageSize(): number { | ||
| const stored = localStorage.getItem(PAGE_SIZE_KEY); | ||
| return stored ? Number(stored) : 25; | ||
| } |
There was a problem hiding this comment.
getStoredPageSize() can return NaN if localStorage contains a non-numeric value (or empty string), which then propagates into query limit values. Consider validating with Number.isFinite and falling back to a sane default (and possibly clamping to allowed options).
Fix issues raised in PR #411 review: add type="button" and ARIA roles to tabs, rename misleading Download button to View, add mutex for trace globals, add 60s timeout to go tool trace, guard against missing index.html in SPA handler, remove committed tsbuildinfo, show JSON parse errors on upload, fix "stoped" typo, validate stored page size. Make SHOCK_PORT configurable in docker-compose.local.yml. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
/ui/viago:embedshock-tsclient with basic auth support, admin methods, and trace download/analysis hooksGET /trace/download,/trace/summary,/trace/eventsfor remote trace analysisUser Features
Admin Features
Infrastructure
shock-server/ui/ui.go—go:embedwith SPA fallback for client-side routingbuild-ui.sh— builds shock-ts + shock-ui and copies dist to embed directoryDockerfilewith Node.js build stepshock-tsandshock-uiTest plan
cd clients/shock-ui && npm run dev— login asadmin:secret, browse nodes, upload file, manage ACLs./build-ui.sh && ./compile-server.sh— verify embedded UI athttp://localhost:7445/ui/docker compose -f docker-compose.local.yml up --build— verify Docker build includes UIuser1:secret) — verify admin sidebar links are hidden🤖 Generated with Claude Code