feat: migrate to Vite + React Router and implement Carbon Design System dashboard#215
Conversation
- Removed Babel configuration file (.babelrc) as it is no longer needed. - Added ESLint configuration file (.eslintrc.json) extending Next.js core web vitals. - Updated .gitignore to exclude the .next directory. - Introduced jsconfig.json for path mapping in the project. - Created next.config.js for Next.js configuration with strict mode and SASS options. - Updated package.json and package-lock.json to include new dependencies for Next.js and Carbon Design System. - Deleted legacy React app files (app.js, index.html, route.js) and replaced with new structure using Next.js. - Added new components for dashboard visualization and cost tracking. - Introduced global styles and layout for the application. - Added JSON data file for asset visualization. This commit transitions the project to a Next.js framework, enhancing the structure and modernizing the codebase. Signed-off-by: Vishy <maxvishy02@gmail.com>
✅ Deploy Preview for opencost-ui ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Signed-off-by: Vishy <maxvishy02@gmail.com>
Signed-off-by: Vishy <maxvishy02@gmail.com>
|
@cursor review |
There was a problem hiding this comment.
Pull request overview
This PR migrates opencost-ui from a Parcel + React Router app to a Next.js 13 (App Router) application and introduces a new Carbon Design System-based dashboard experience, including chart/widgets and an assets visualization backed by OpenCost assets data (with demo fallbacks).
Changes:
- Replaces legacy Parcel entrypoints/routing with Next.js App Router structure (
src/app/*) and Next config files. - Adds Carbon-based dashboard components (dashboard list/view/builder, scoped filters, charts, assets visualization).
- Removes legacy pages/routes and comments out large portions of the old Material UI implementation (effectively deprecating it).
Reviewed changes
Copilot reviewed 58 out of 63 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| src/app/page.js | New Carbon dashboard landing page + dashboard selection flow. |
| src/app/layout.js | Next.js root layout + metadata + font setup. |
| src/app/globals.scss | Global styling for the Next.js app + dashboard utility classes. |
| src/components/dashboard-context.js | Client-side dashboard state/context provider. |
| src/components/dashboard-view.js | Dashboard view page (widgets grid, filter toggle, edit mode). |
| src/components/dashboard-builder.js | Dashboard widget layout/builder UI. |
| src/components/create-dashboard-modal.js | Modal flow for creating a new dashboard. |
| src/components/scoped-views.js | Scoped view/filter UI. |
| src/components/cost-summary-cards.js | Summary metric cards widget. |
| src/components/cost-allocation-chart.js | Carbon stacked bar chart widget (sample data). |
| src/components/cost-by-service-chart.js | Carbon area chart widget (sample data). |
| src/components/external-services-chart-widget.js | External services widget with tabs + chart/table (sample data). |
| src/components/assets-visualization.js | Assets visualization widget fetching public/ss.json / OpenCost API / demo data. |
| src/lib/assets-api.js | Asset response parsing + fetch helper. |
| src/css/index.css | Updated legacy stylesheet (appears to be leftover post-migration). |
| netlify.toml | Adds Netlify Next.js build configuration/plugin. |
| next.config.js | Next.js config + sass options. |
| jsconfig.json | Adds @/* path mapping for imports. |
| package.json | Swaps Parcel/MUI dependencies for Next.js + Carbon + Sass tooling. |
| .eslintrc.json | Adds Next.js ESLint config preset. |
| .gitignore | Ignores .next build artifacts. |
| src/app.js | Removes Parcel ReactDOM bootstrap entrypoint. |
| src/index.html | Removes Parcel HTML entrypoint. |
| src/route.js | Removes React Router routing setup. |
| src/pages/Allocations.js | Removes legacy page (allocation report). |
| src/pages/CloudCosts.js | Removes legacy page (cloud cost report). |
| src/pages/ExternalCosts.js | Removes legacy page (external costs report). |
| src/tempPages/Allocations.js | Adds commented-out legacy page snapshot (unused). |
| src/tempPages/CloudCosts.js | Adds commented-out legacy page snapshot (unused). |
| src/tempPages/ExternalCosts.js | Adds commented-out legacy page snapshot (unused). |
| .babelrc | Removes legacy Babel config. |
| src/components/Page.js | Comments out legacy layout component. |
| src/components/Header.js | Comments out legacy header component. |
| src/components/Footer.js | Comments out legacy footer component. |
| src/components/Warnings.js | Comments out legacy warnings component. |
| src/components/Subtitle.js | Comments out legacy subtitle component. |
| src/components/SelectWindow.js | Comments out legacy date range control. |
| src/components/Nav/SidebarNav.js | Comments out legacy sidebar nav. |
| src/components/Nav/NavItem.js | Comments out legacy nav item. |
| src/components/Controls/index.js | Comments out legacy controls wrapper. |
| src/components/Controls/Edit.js | Comments out legacy edit controls. |
| src/components/Controls/Download.js | Comments out legacy download control. |
| src/components/AllocationChart/index.js | Comments out legacy allocation chart. |
| src/components/AllocationChart/RangeChart.js | Comments out legacy allocation range chart. |
| src/components/AllocationChart/SummaryChart.js | Comments out legacy allocation summary chart. |
| src/components/externalCosts/externalCostsChart.js | Comments out legacy external costs chart wrapper. |
| src/components/externalCosts/rangeChart.js | Comments out legacy external costs range chart. |
| src/components/externalCosts/externalCostsControls.js | Comments out legacy external costs controls. |
| src/components/externalCosts/externalCostsTable.js | Comments out legacy external costs table. |
| src/components/externalCosts/externalCostRow.js | Comments out legacy external costs row. |
| src/components/externalCosts/externalCostDetailModal.js | Comments out legacy external costs detail modal. |
| src/components/cloudCost/tokens.js | Comments out legacy cloud cost tokens. |
| src/components/cloudCost/cloudCost.js | Comments out legacy cloud cost main component. |
| src/components/cloudCost/cloudCostRow.js | Comments out legacy cloud cost row component. |
| src/components/cloudCost/cloudCostDetails.js | Comments out legacy cloud cost details modal. |
| src/components/cloudCost/cloudCostChart/index.js | Comments out legacy cloud cost chart wrapper. |
| src/components/cloudCost/controls/cloudCostEditControls.js | Comments out legacy cloud cost edit controls. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const [selectedAssetType, setSelectedAssetType] = useState(null); | ||
| const [sortBy, setSortBy] = useState('cost'); | ||
| const [assets, setAssets] = useState([]); | ||
| const [isLoading, setIsLoading] = useState(true); | ||
| const [error, setError] = useState(null); |
There was a problem hiding this comment.
setSortBy is never used, which will fail next lint (no-unused-vars). If sorting controls aren’t implemented yet, remove this state; otherwise add UI that updates sortBy.
| import { Button, Tile, OverflowMenu, OverflowMenuItem } from '@carbon/react'; | ||
| import { ArrowLeft, OverflowMenuVertical, Edit, Share, Home, TrashCan, Filter } from '@carbon/icons-react'; | ||
| import DashboardBuilder from './dashboard-builder'; |
There was a problem hiding this comment.
Several imported icons (Share, Home, TrashCan) are not used, which will fail next lint (no-unused-vars). Remove unused imports or add UI that uses them.
| } from '@carbon/react'; | ||
| import { Close } from '@carbon/icons-react'; | ||
| import { useDashboard } from './dashboard-context'; |
There was a problem hiding this comment.
Close is imported but never used, which will fail next lint (no-unused-vars). Remove it, or add a close button that uses this icon.
| // import * as React from "react"; | ||
| // import Page from "../components/Page"; | ||
| // import Header from "../components/Header"; | ||
| // import Footer from "../components/Footer"; | ||
| // import IconButton from "@material-ui/core/IconButton"; |
There was a problem hiding this comment.
This new file contains only commented-out legacy code and isn’t referenced anywhere (no tempPages imports). Keeping large commented blocks in the shipped codebase adds noise and maintenance cost; consider deleting these temp pages or moving the legacy code to git history/docs.
| /* Custom styles for the new dashboard */ | ||
| .dashboard-grid { | ||
| display: grid; | ||
| grid-template-columns: repeat(4, 1fr); | ||
| gap: 1rem; | ||
| width: 100%; | ||
| } |
There was a problem hiding this comment.
This file adds dashboard styling that appears duplicated in src/app/globals.scss, and there are no references to src/css/index.css in the codebase after the Next.js migration. Consider deleting this stylesheet (or moving the remaining needed rules into globals.scss) to avoid dead/duplicated CSS.
| 'use client'; | ||
|
|
||
| import { useState } from 'react'; | ||
| import { Button, Header, HeaderName, Tile, Tag } from '@carbon/react'; |
There was a problem hiding this comment.
Tile is imported but never used, which will fail next lint (no-unused-vars). Remove it from the @carbon/react import.
| import { Button, Header, HeaderName, Tile, Tag } from '@carbon/react'; | |
| import { Button, Header, HeaderName, Tag } from '@carbon/react'; |
| <div | ||
| key={dashboard.id} | ||
| className="card-enhanced" | ||
| onClick={() => setSelectedDashboard(dashboard.id)} | ||
| style={{ |
There was a problem hiding this comment.
This dashboard card is a clickable <div> without keyboard support, which is an accessibility issue. Use a semantic interactive element (e.g., Button, Link, or add role="button", tabIndex={0}, and keyboard handlers).
| publish = ".next" | ||
|
|
||
| [[plugins]] | ||
| package = "@netlify/plugin-nextjs" No newline at end of file |
There was a problem hiding this comment.
netlify.toml config references @netlify/plugin-nextjs, but the dependency isn’t present in package.json. Add it to devDependencies (or remove the plugin stanza) to avoid Netlify build failures.
| publish = ".next" | |
| [[plugins]] | |
| package = "@netlify/plugin-nextjs" | |
| publish = ".next" |
| import { useState } from 'react'; | ||
| import { Tile, Button, Select, SelectItem, MultiSelect, TextInput, Tag } from '@carbon/react'; | ||
| import { Filter, TrashCan, Save } from '@carbon/icons-react'; |
There was a problem hiding this comment.
MultiSelect is imported but never used, which will fail next lint (no-unused-vars). Remove it from the import list or use it for one of the filters.
| "@carbon/charts": "^1.13.0", | ||
| "@carbon/charts-react": "^1.13.0", | ||
| "@carbon/icons-react": "^11.14.0", | ||
| "@carbon/react": "1.36.0", | ||
| "next": "13.4.9", | ||
| "react": "18.2.0", |
There was a problem hiding this comment.
PR description says @carbon/react v1.35.0, but package.json pins @carbon/react to 1.36.0. Please update the PR description or align the version so the metadata matches what’s being shipped.
|
I'm really liking the carbon look! Couple of quick thoughts:
|
Signed-off-by: Vishy <maxvishy02@gmail.com>
- Updated Node version in .nvmrc to 20. - Enhanced netlify.toml with environment variables for NODE_VERSION and VITE_BASE_API_URL. - Refactored cost allocation chart and summary cards components to support additional props for aggregation and filtering. - Improved dashboard builder and context to accommodate new widget configurations and filters. - Updated API client to use environment variable for base URL. These changes improve the flexibility and maintainability of the cost allocation features. Signed-off-by: Vishy <maxvishy02@gmail.com>
|
|
||
| RUN npx parcel build src/index.html --public-url ${UI_PATH} | ||
| ADD . ./ | ||
| RUN npm run build |
There was a problem hiding this comment.
add npm run build legacy underneath this
| onClick={() => setSelectedWidget(widget)} | ||
| > | ||
| <div> | ||
| <h3 style={{ fontSize: "1rem", fontWeight: "600", marginBottom: "0.5rem" }}>{widget.title}</h3> |
| Cloud infrastructure costs over time | ||
| </p> | ||
| <CostByServiceChart /> | ||
| </Tile> |
There was a problem hiding this comment.
split out into components, add to registry by name? would allow easier contribution of widgets
There was a problem hiding this comment.
bonus - add editor into registry? to get widget inputs easier when building widgets
- Updated NGINX configuration to use dynamic root paths based on LEGACY_MODE. - Modified Docker entrypoint to set document root for legacy and default UIs. - Enhanced Dockerfile to build both standard and legacy UIs, ensuring proper output verification. - Added new build script in package.json for building both UIs in one command. - Improved cost allocation chart component with additional props for better data handling. These changes improve the application's flexibility in serving different UI versions and enhance the overall build process. Signed-off-by: Vishy <maxvishy02@gmail.com>
- Removed the unnecessary `// @ts-nocheck` directive from SelectWindow.jsx to improve code quality and maintainability. This change enhances the clarity of the code and ensures TypeScript checks are applied where applicable.
…PI client URL resolution - Added support for using mock data in the AllocationService when the backend is unavailable, improving resilience during development. - Refactored the API client to dynamically resolve the base URL from environment variables, enhancing flexibility for different deployment environments. - Removed unused components and files to streamline the codebase. These changes improve the application's robustness and maintainability. Signed-off-by: Vishy <maxvishy02@gmail.com>
- Added new cloud cost widgets for displaying cloud service spend and utilization. - Updated dashboard builder to include options for cloud costs table and chart. - Introduced cloud filters in scoped views for better data aggregation and currency selection. - Refactored widget renderer to accommodate new cloud cost components. These changes improve the dashboard's functionality and user experience by providing more detailed insights into cloud costs. Signed-off-by: Vishy <maxvishy02@gmail.com>
…services - Implemented caching mechanism in AllocationService and CloudCostService to improve data retrieval efficiency. - Added deduplication for in-flight requests to prevent redundant API calls. - Refactored data fetching logic to utilize cached results, reducing load times and enhancing performance. - Updated CostAllocationChart component to manage raw data separately, improving clarity and maintainability. These changes significantly enhance the application's performance and user experience by optimizing data handling and reducing unnecessary API requests. Signed-off-by: Vishy <maxvishy02@gmail.com>
Signed-off-by: Vishy <maxvishy02@gmail.com>
…er functionality - Introduced FilterableWidgetHeader component for improved filter management across cost-related components. - Updated CloudCostTableWidget, CloudCostWidget, CostAllocationChart, CostAllocationTable, and CostSummaryCards to utilize the new filterable header for better user experience. - Implemented shared filter context for allocation and cloud cost components, allowing for consistent filter application across related widgets. - Enhanced component props to support titles and descriptions for better context. Signed-off-by: Vishy <maxvishy02@gmail.com>
… and sharing capabilities - Added grid size selection for new widgets in the DashboardBuilder component, allowing users to customize widget width. - Implemented sharing functionality in the DashboardView component, enabling users to copy a shareable link for dashboards. - Updated DashboardList to handle shared dashboard imports, providing a modal for users to confirm importing shared configurations. - Enhanced overall layout and styling for better user experience across dashboard components. Signed-off-by: Vishy <maxvishy02@gmail.com>
- Added Tailwind CSS and its Vite plugin to the project for improved styling capabilities. - Updated various components to utilize Tailwind classes for consistent styling and responsive design. - Refactored layout and styling in the DashboardBuilder, DashboardView, and widget components to enhance user experience. - Included Tailwind CSS configuration and stylesheets in the project. Signed-off-by: Vishy <maxvishy02@gmail.com>
Signed-off-by: Vishy <maxvishy02@gmail.com>
…ard components Signed-off-by: Vishy <maxvishy02@gmail.com>
|
I’ve asked Copilot to review the code again, since a lot has changed. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 99 out of 109 changed files in this pull request and generated 11 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| FROM node:20-alpine AS builder | ||
| WORKDIR /opt/ui | ||
| ADD package*.json ./ | ||
| RUN npm install | ||
| ADD src /opt/ui/src | ||
|
|
||
| ARG ui_path=/ | ||
| ENV UI_PATH=${ui_path} | ||
|
|
||
| RUN npx parcel build src/index.html --public-url ${UI_PATH} | ||
| ADD . ./ | ||
| # Save first build outside build/ so build:legacy (which overwrites build/) doesn't remove it | ||
| RUN npm run build && cp -r build/client /opt/standard | ||
| RUN npm run build:legacy |
There was a problem hiding this comment.
The image build runs npm run build/build:legacy without setting VITE_BASE_API_URL, so the resulting bundle will use whatever default is compiled in (currently the demo endpoint). If this image is intended for self-hosting, pass VITE_BASE_API_URL (e.g. /model) as a build arg/env before the build steps so the UI targets the in-container nginx proxy by default.
There was a problem hiding this comment.
@maxvishy I think we do want this, right? or will we default to local service by default via some other mechanism?
There was a problem hiding this comment.
We already default to local via VITE_BASE_API_URL || "/model" + nginx /model proxy, so behavior is correct now.
- Added currency selection to allocation filters, cost allocation chart, and cost allocation table components for improved flexibility. - Updated value formatting in charts and tables to utilize the selected currency, enhancing user experience and data presentation. - Refactored related components to ensure consistent currency handling across the application. Signed-off-by: Vishy <maxvishy02@gmail.com>
Signed-off-by: Vishy <maxvishy02@gmail.com>
| // link: { | ||
| // cursor: "pointer", | ||
| // }, | ||
| // }); |
| // @ts-nocheck | ||
| import * as React from "react"; | ||
| // import { makeStyles } from "@material-ui/styles"; | ||
| // // import { makeStyles } from "@material-ui/styles"; |
| --rm \ | ||
| --platform "linux/amd64" \ | ||
| -f 'Dockerfile.cross' \ | ||
| -f 'Dockerfile' \ |
There was a problem hiding this comment.
so, here you are now moving to an in-container compilation of the app, vs using the CI to build the app then bring it into the container. What was your thinking behind this change?
There was a problem hiding this comment.
That said, I see the benefit of the previous approach being faster.
Let me revert it
| if [ -n "$BASE_URL_OVERRIDE" ]; then | ||
| echo "running with BASE_URL=${BASE_URL_OVERRIDE}" | ||
| sed -i "s^{PLACEHOLDER_BASE_URL}^$BASE_URL_OVERRIDE^g" /var/www/*.js | ||
| find "$WWW_ROOT" -name "*.js" -exec sed -i "s^{PLACEHOLDER_BASE_URL}^$BASE_URL_OVERRIDE^g" {} \; 2>/dev/null || true |
There was a problem hiding this comment.
to you really want || true? wont that swallow errors?
| FROM node:20-alpine AS builder | ||
| WORKDIR /opt/ui | ||
| ADD package*.json ./ | ||
| RUN npm install | ||
| ADD src /opt/ui/src | ||
|
|
||
| ARG ui_path=/ | ||
| ENV UI_PATH=${ui_path} | ||
|
|
||
| RUN npx parcel build src/index.html --public-url ${UI_PATH} | ||
| ADD . ./ | ||
| # Save first build outside build/ so build:legacy (which overwrites build/) doesn't remove it | ||
| RUN npm run build && cp -r build/client /opt/standard | ||
| RUN npm run build:legacy |
There was a problem hiding this comment.
@maxvishy I think we do want this, right? or will we default to local service by default via some other mechanism?
| @@ -1,3 +1,4 @@ | |||
| // @ts-nocheck | |||
There was a problem hiding this comment.
probably should avoid turning off type checking
| } from "./scoped-views"; | ||
|
|
||
| function formatUtilPct(v: number | null): string { | ||
| return v === null ? "N/A" : `${v}%`; |
There was a problem hiding this comment.
Should we use the Intl NumberFormat here?
| if (windowName === "" && checkCustomWindow(window)) { | ||
| windowName = toVerboseTimeRange(window) ?? window; | ||
| } | ||
| if (windowName === "") windowName = window; |
There was a problem hiding this comment.
Should we be running eslint/prettier? Some of the formatting seems a little off
| }, [chartData, currency]); | ||
|
|
||
| if (loading) { | ||
| return <div className="h-[400px] flex items-center justify-center text-[#8d8d8d]">Loading...</div>; |
There was a problem hiding this comment.
I think Carbon has some loading/skeleton states that might be worth exploring
| @@ -0,0 +1,38 @@ | |||
| import { | |||
There was a problem hiding this comment.
Any way we could grab these from carbon?
- Changed BASE_API_URL to VITE_BASE_API_URL in .env.example for better compatibility with Vite. - Updated NGINX configuration to use dynamic UI_PATH for serving the application, improving routing flexibility. - Refactored Docker entrypoint script to replace placeholders in JavaScript files, enhancing deployment customization. - Added new environment variable UI_PATH in Dockerfile for better configuration management. - Updated package dependencies to include new versions of @carbon/colors and @mui/styles for improved styling capabilities. Signed-off-by: Vishy <maxvishy02@gmail.com>
- Added support for drilldown filters and aggregate options in allocation filters context, cost allocation chart, and cost allocation table components. - Updated state management to handle local and shared drilldown filters effectively. - Refactored filter setting functions to reset drilldown states when filters are updated, improving user experience and consistency across components. Signed-off-by: Vishy <maxvishy02@gmail.com>
Signed-off-by: Vishy <maxvishy02@gmail.com>


Migrate to Vite + React Router and implement Carbon Design System dashboard
What does this PR change?
This PR completely modernizes the
opencost-uiproject by migrating from a Parcel-based build system to Next.js 13 (App Router) and implementing a new dashboard using IBM's Carbon Design System.Major Changes:
Build System & Framework:
next.config.js,jsconfig.json,.eslintrc.json).gitignoreto exclude Next.js build artifacts (.next/)UI Framework Migration:
@carbon/reactv1.35.0)@carbon/charts-reactv1.13.0) for data visualization@carbon/icons-reactv11.14.0)New Components:
Data Layer:
assets-api.jsutility for parsing OpenCost API responsespublic/ss.json) for development and testingApplication Structure:
src/app.js,src/index.html,src/route.js)src/app/page.js,src/app/layout.js)src/app/globals.scssfor global styles with Carbon design tokenssrc/components/directorysrc/lib/directoryDependencies:
Does this PR relate to any other PRs?
How will this PR impact users?
Positive Impacts:
Breaking Changes:
Does this PR address any GitHub or Zendesk issues?
How was this PR tested?
Local Development Testing:
ss.json) and verified fallback to demo dataBrowser Compatibility:
Build & Production:
npm run dev- Development server runs successfullynpm run build- Production build completes without errorsnpm run lint- ESLint passes with Next.js configDoes this PR require changes to documentation?
Yes, the following documentation should be updated:
Have you labeled this PR and its corresponding Issue as "next release" if it should be part of the next OpenCost release? If not, why not?