Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move Docker image building out of sbt (sbt-native-packager) and into standalone Dockerfiles for the JVM server, native server, and UI. Update CI to build JARs first then invoke docker buildx directly, enabling multi-arch (amd64/arm64) builds and publishing. Add docker-compose.yaml for running the full stack locally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The legacy stats server on port 9325 served two purposes: a JSON queue metrics API and the old embedded UI. Both are now superseded: the UI by the new Next.js application, and the metrics API was not used by anything. The serveUI code was already broken after the old UI resources were removed. - Delete rest/rest-sqs/src/main/scala/org/elasticmq/rest/stats/ (4 files) - Delete StatisticsDirectivesTest.scala - Remove RestStatisticsConfiguration from ElasticMQServerConfig - Remove optionallyStartRestStatistics from ElasticMQServer - Remove rest-stats block from reference.conf - Remove EXPOSE 9325 from server/Dockerfile and native-server/Dockerfile - Remove port 9325 mapping from docker-compose.yaml - Remove all 9325 mentions from README.md and CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace single cross-compiled buildx job with native builds: - Build JARs once on amd64 and share as artifacts (JVM bytecode is arch-neutral) - Build amd64 images on ubuntu-24.04, arm64 images on ubuntu-24.04-arm (in parallel) - Merge into multi-arch manifests with docker buildx imagetools create Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR replaces the legacy Create React App-based UI (and its dedicated /statistics/* REST server) with a new Next.js-based UI that talks directly to ElasticMQ via the SQS API, and updates Docker/CI to build and run the new UI alongside the server.
Changes:
- Migrates
ui/from CRA + Material UI + Jest tests to a Next.js (App Router) UI using@aws-sdk/client-sqs, Tailwind v4, and server actions. - Removes the Scala
rest-statsHTTP server, configuration, and tests; updates docs accordingly. - Reworks Docker/CI: adds dedicated Dockerfiles (server/native/ui), adds compose for local running, and splits UI CI from sbt.
Reviewed changes
Copilot reviewed 65 out of 77 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/tsconfig.json | Updates TS config for Next.js (App Router, paths, bundler resolution). |
| ui/src/styles/queue.css | Removes legacy queue hover styling (CRA UI removal). |
| ui/src/setupTests.js | Removes Jest DOM setup (legacy UI testing removal). |
| ui/src/services/QueueService.ts | Removes legacy REST-stats axios service. |
| ui/src/services/QueueService.test.ts | Removes legacy service unit tests. |
| ui/src/Queues/RefreshQueuesData.ts | Removes legacy polling hook implementation. |
| ui/src/Queues/QueuesTable.tsx | Removes legacy Material UI queues table. |
| ui/src/Queues/QueuesTable.test.tsx | Removes legacy UI tests for queues table. |
| ui/src/Queues/QueueRowDetails.tsx | Removes legacy row details component. |
| ui/src/Queues/QueueRow.tsx | Removes legacy row component. |
| ui/src/Queues/QueueRow.test.tsx | Removes legacy row component tests. |
| ui/src/Queues/QueueMessageData.ts | Removes legacy UI type definitions. |
| ui/src/NavBar/NavBar.tsx | Removes legacy navbar component. |
| ui/src/Main/Main.tsx | Removes legacy app composition. |
| ui/src/index.js | Removes CRA entrypoint. |
| ui/src/index.css | Removes CRA global styles. |
| ui/src/App.tsx | Removes legacy App component. |
| ui/src/App.test.tsx | Removes legacy App test. |
| ui/README.md | Removes CRA README boilerplate. |
| ui/public/window.svg | Adds Next.js default/static asset. |
| ui/public/vercel.svg | Adds Next.js default/static asset. |
| ui/public/next.svg | Adds Next.js default/static asset. |
| ui/public/manifest.json | Removes CRA PWA manifest. |
| ui/public/index.html | Removes CRA HTML template. |
| ui/public/globe.svg | Adds Next.js default/static asset. |
| ui/public/file.svg | Adds Next.js default/static asset. |
| ui/postcss.config.mjs | Adds Tailwind v4 PostCSS setup. |
| ui/package.json | Switches to Next.js deps/scripts; removes legacy test tooling. |
| ui/next.config.ts | Adds Next config (standalone output). |
| ui/lib/utils.ts | Adds attribute formatting helpers and hidden-attribute list. |
| ui/lib/types.ts | Adds UI/shared types for queues and messages. |
| ui/lib/theme-provider.tsx | Adds client theme context/provider with persisted selection. |
| ui/lib/sqs-client.ts | Adds SQS client configuration + queue name helper. |
| ui/lib/actions.ts | Adds server actions for queue list/details, send/receive/delete, batch send. |
| ui/eslint.config.mjs | Adds ESLint flat config using eslint-config-next. |
| ui/Dockerfile | Adds multi-stage build/runtime Dockerfile for standalone Next output. |
| ui/components/theme-switcher.tsx | Adds theme toggle UI component. |
| ui/components/send-message-modal.tsx | Adds send-message modal (attributes, FIFO fields, trace header). |
| ui/components/receive-messages-panel.tsx | Adds receive/delete UI panel with message inspection. |
| ui/components/queue-list.tsx | Adds queue list client component with polling and refresh UI. |
| ui/components/queue-details.tsx | Adds queue details view (polling, actions, attributes table). |
| ui/components/queue-card.tsx | Adds queue summary card with quick actions. |
| ui/components/page-header.tsx | Adds landing page header with theme control. |
| ui/components/loading-skeleton.tsx | Adds loading skeleton component. |
| ui/components/generate-messages-modal.tsx | Adds message generation/batch send modal. |
| ui/components/error-display.tsx | Adds UI error component with retry. |
| ui/app/queues/[name]/page.tsx | Adds dynamic queue details route page. |
| ui/app/page.tsx | Adds home page rendering queue list. |
| ui/app/layout.tsx | Adds root layout with fonts + ThemeProvider. |
| ui/app/globals.css | Adds global theme variables + Tailwind import. |
| ui/app/favicon.ico | Adds new favicon asset. |
| ui/.gitignore | Updates ignores for Next/Yarn/env/build artifacts. |
| ui/.dockerignore | Adds Docker ignore for UI build context. |
| server/Dockerfile | Adds dedicated JVM server image build (jar + config, non-root user). |
| rest/rest-sqs/src/test/.../StatisticsDirectivesTest.scala | Removes stats REST server test coverage. |
| rest/rest-sqs/src/main/.../ElasticMQServer.scala | Stops wiring up the stats REST server. |
| rest/rest-sqs/src/main/.../ElasticMQServerConfig.scala | Removes rest-stats config section. |
| rest/rest-sqs/src/main/.../StatisticsRestServerBuilder.scala | Removes stats REST server builder implementation. |
| rest/rest-sqs/src/main/.../StatisticsJsonFormat.scala | Removes stats REST JSON formats. |
| rest/rest-sqs/src/main/.../StatisticsDirectives.scala | Removes stats routes + UI-serving directive code. |
| rest/rest-sqs/src/main/.../stats/package.scala | Removes stats package types alias. |
| rest/rest-sqs/src/main/resources/reference.conf | Removes rest-stats reference config block. |
| README.md | Updates local run/UI docs; removes rest-stats references. |
| project/plugins.sbt | Removes sbt-native-packager plugin usage. |
| native-server/Dockerfile | Adds dedicated GraalVM native-image build + minimal runtime image. |
| integration-tests/docker/Dockerfile | Switches logback.xml source path to examples/logback.xml. |
| examples/logback.xml | Adds example logback config (currently DEBUG root). |
| examples/elasticmq.conf | Adds example ElasticMQ config for compose/local runs. |
| docker-compose.yaml | Adds compose to run server + new UI images together. |
| CLAUDE.md | Adds repo documentation and UI/backend dev notes. |
| build.sbt | Removes sbt-managed UI build + docker packaging logic; adjusts aggregates. |
| .gitignore | Adds .data ignore. |
| .github/workflows/ci.yml | Adds ci-ui job; updates docker build steps to use Dockerfiles. |
| .dockerignore | Adds root docker ignore list for smaller contexts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| interface QueuePageProps { | ||
| params: Promise<{ | ||
| name: string; | ||
| }>; | ||
| } | ||
|
|
||
| export default async function QueuePage({ params }: QueuePageProps) { | ||
| const { name } = await params; | ||
| const queueName = decodeURIComponent(name); |
There was a problem hiding this comment.
params in App Router pages is passed as a plain object, not a Promise. Typing it as Promise and await-ing it is misleading and can hide incorrect usage; update the prop type to { params: { name: string } } and remove the unnecessary await.
| The dev server starts at **http://localhost:3000** and expects ElasticMQ running at `http://localhost:9324`. Configure the endpoint in `ui/.env.local` if needed: | ||
|
|
||
| You can start UI via `yarn start` command in the `ui` directory, which will run on localhost:3000 address. | ||
| ```bash | ||
| NEXT_PUBLIC_SQS_ENDPOINT=http://localhost:9324 | ||
| AWS_REGION=elasticmq | ||
| AWS_ACCESS_KEY_ID=x | ||
| AWS_SECRET_ACCESS_KEY=x | ||
| ``` |
There was a problem hiding this comment.
The docs suggest NEXT_PUBLIC_SQS_ENDPOINT, but the UI server-side SQS client reads process.env.SQS_ENDPOINT (and docker-compose also sets SQS_ENDPOINT). Update the README env snippet to match the actual variable name(s) used by the code.
| // Get attributes for each queue | ||
| const queueDataPromises = listResult.QueueUrls.map(async (queueUrl) => { | ||
| const attributesCommand = new GetQueueAttributesCommand({ | ||
| QueueUrl: queueUrl, | ||
| AttributeNames: ['All'], | ||
| }); |
There was a problem hiding this comment.
getQueues() always fetches AttributeNames: ['All'] for every queue. Since the list page only needs the three numeric stats, requesting all attributes increases payload and makes polling much more expensive as queue count grows; consider requesting only the needed attribute names (or splitting list vs details endpoints).
| } catch (error) { | ||
| console.error('Error fetching queues:', error); | ||
| throw new Error('Failed to fetch queues. Make sure ElasticMQ is running on localhost:9324'); | ||
| } |
There was a problem hiding this comment.
getQueues() throws an error mentioning localhost:9324, but the code actually uses process.env.SQS_ENDPOINT (and can point elsewhere, e.g. http://elasticmq:9324 in Docker). Consider making this message endpoint-agnostic or include the configured endpoint value to avoid misleading users.
| } catch (error) { | ||
| console.error('Error fetching queue details:', error); | ||
| throw new Error('Failed to fetch queue details. Make sure ElasticMQ is running on localhost:9324'); | ||
| } |
There was a problem hiding this comment.
getQueueDetails() throws an error mentioning localhost:9324, but the code actually uses process.env.SQS_ENDPOINT (and can point elsewhere). Consider making this message endpoint-agnostic or include the configured endpoint value to avoid misleading users.
| useEffect(() => { | ||
| fetchQueues(); | ||
| const interval = setInterval(() => fetchQueues(), 1000); | ||
| return () => clearInterval(interval); | ||
| }, []); |
There was a problem hiding this comment.
Polling every 1s will trigger getQueues() repeatedly; combined with per-queue GetQueueAttributes calls this can create significant load (N+1 SQS calls per second). Consider increasing the interval, making it configurable, or switching to manual refresh / backoff after errors.
| // System attribute keys that contain Unix timestamps (seconds) | ||
| const TIMESTAMP_ATTRS = ['SentTimestamp', 'ApproximateFirstReceiveTimestamp']; | ||
|
|
||
| function formatSysAttrValue(key: string, value: string): string { | ||
| if (TIMESTAMP_ATTRS.includes(key)) { | ||
| const ms = parseInt(value); | ||
| if (!isNaN(ms)) return new Date(ms).toLocaleString(); | ||
| } |
There was a problem hiding this comment.
Comment says the system attributes are Unix timestamps in seconds, but SentTimestamp / ApproximateFirstReceiveTimestamp are epoch milliseconds in SQS (and the code treats them as ms). Please update the comment to avoid confusion.
| const options = [ | ||
| { value: 'light', label: '☀', title: 'Light' }, | ||
| { value: 'dark', label: '☾', title: 'Dark' }, | ||
| { value: 'auto', label: '⬡', title: 'Auto' }, | ||
| ] as const; | ||
|
|
||
| return ( | ||
| <div | ||
| style={{ | ||
| display: 'flex', | ||
| background: 'var(--card-bg)', | ||
| border: '1px solid var(--card-border)', | ||
| borderRadius: 8, | ||
| padding: 3, | ||
| gap: 2, | ||
| }} | ||
| > | ||
| {options.map(o => { | ||
| const active = mounted && theme === o.value; | ||
| return ( | ||
| <button | ||
| key={o.value} | ||
| onClick={() => setTheme(o.value)} | ||
| title={o.title} | ||
| style={{ | ||
| width: 30, height: 28, | ||
| borderRadius: 5, | ||
| border: 'none', | ||
| cursor: 'pointer', | ||
| fontSize: 13, | ||
| fontWeight: active ? 700 : 400, | ||
| background: active ? 'var(--accent-dim)' : 'transparent', | ||
| color: active ? 'var(--accent)' : 'var(--muted)', | ||
| transition: 'all 150ms ease', | ||
| outline: active ? '1px solid var(--accent)' : 'none', | ||
| outlineOffset: -1, | ||
| }} |
There was a problem hiding this comment.
The theme toggle buttons only render symbols (e.g. ☀, ☾, ⬡) and rely on title for meaning. Add an accessible name (e.g. aria-label / aria-pressed) so screen readers can understand the options and current state.
| "scripts": { | ||
| "start": "react-scripts start", | ||
| "build": "react-scripts build", | ||
| "test": "react-scripts test --env=jest-environment-jsdom-sixteen", | ||
| "test:ci": "CI=true react-scripts test --env=jest-environment-jsdom-sixteen --maxWorkers 2", | ||
| "eject": "react-scripts eject" | ||
| "dev": "next dev", | ||
| "build": "next build", | ||
| "start": "next start", | ||
| "lint": "eslint" | ||
| }, | ||
| "eslintConfig": { | ||
| "extends": [ | ||
| "react-app" | ||
| ] | ||
| "dependencies": { | ||
| "@aws-sdk/client-sqs": "^3.1000.0", | ||
| "next": "16.1.6", | ||
| "react": "19.2.3", | ||
| "react-dom": "19.2.3" | ||
| }, |
There was a problem hiding this comment.
UI test coverage was removed (no test script and no test dependencies), but the PR introduces substantial new UI behavior (queue polling, send/receive/delete, batch generation). Consider adding at least a minimal automated test setup (e.g. Playwright smoke tests or component tests) to prevent regressions.
- Correct NEXT_PUBLIC_SQS_ENDPOINT → SQS_ENDPOINT in CLAUDE.md and README (code already used SQS_ENDPOINT; docs were out of sync) - Replace disabled Cancel with active Stop button while generating; clicking Stop sets abortRef to abort after the current batch completes - Show "Stopped" state in the done screen when generation was aborted Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No description provided.