Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions hadoop-ozone/dev-support/checks/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ REPORT_FILE="$REPORT_DIR/summary.txt"

declare -i rc

if [[ -d "${BASE_DIR}/ozone-ui/src" ]]; then
if [[ -d "${BASE_DIR}/ozone-ui" ]]; then
(
cd "${BASE_DIR}/ozone-ui/src" || exit 1
cd "${BASE_DIR}/ozone-ui" || exit 1
# Install dependencies if node_modules doesn't exist
if [[ ! -d "node_modules" ]]; then
pnpm install --frozen-lockfile
Expand Down Expand Up @@ -57,7 +57,7 @@ if [[ -d "${BASE_DIR}/ozone-ui/src" ]]; then
fi
else
rc=0
echo "ozone-ui/src not found. Skipping UI lint."
echo "ozone-ui not found. Skipping UI lint."
fi

ERROR_PATTERN="error"
Expand Down
File renamed without changes.
192 changes: 128 additions & 64 deletions ozone-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,99 +17,163 @@

# Ozone UI Monorepo

This monorepo contains three React applications for Apache Ozone UI components:
A Vite + React 18 + TypeScript monorepo (managed with **pnpm workspaces**) that
hosts the Apache Ozone web applications and a shared component library. Styling
is built on **Ant Design v5** themed with the Ozone UI design tokens.

## Applications
## Applications & packages

- **Recon** - Data node management and monitoring
- **SCM** - Storage Container Manager interface
- **OM** - Ozone Manager interface
| Package | Directory | Description |
| ----------------------- | ----------------- | ---------------------------------------------- |
| `@ozone-ui/shared` | `packages/shared` | Design system: theme + reusable components |
| `@ozone-ui/ozone-recon` | `packages/recon` | Recon – datanode management and monitoring |
| `@ozone-ui/ozone-scm` | `packages/scm` | SCM – Storage Container Manager interface |
| `@ozone-ui/ozone-om` | `packages/om` | OM – Ozone Manager interface |

## Structure
## Folder layout

```
hadoop-ui/src/
├── packages/
│ ├── shared/ # Shared components and utilities
│ ├── recon/ # Recon application
│ ├── scm/ # SCM application
│ └── om/ # OM application
├── package.json # Root package.json with workspace configuration
└── pnpm-workspace.yaml # PNPM workspace configuration
ozone-ui/ # pnpm workspace root
├── README.md
├── package.json # Root scripts + shared dev dependencies
├── pnpm-workspace.yaml # Workspace globs (./packages/**)
├── pnpm-lock.yaml
├── tsconfig.json # Base TS config, extended by each package
├── vite.config.shared.ts # Shared Vite config helpers for the apps
├── eslint.config.js # Flat ESLint config
└── packages/
├── shared/ # @ozone-ui/shared (design system)
│ └── src/
│ ├── theme/ # tokens, Ant Design theme, ThemeProvider
│ ├── components/ # Sidebar, UtilityBar, PageHeader, Card, ...
│ ├── utils/ # menuUtils, ...
│ └── index.ts # Public entry point (barrel)
├── recon/ # @ozone-ui/ozone-recon (Vite app)
├── scm/ # @ozone-ui/ozone-scm (Vite app)
└── om/ # @ozone-ui/ozone-om (Vite app)
```

## Development
## Prerequisites

### Prerequisites
- Node.js `>= 20` (Node 20 LTS recommended)
- pnpm `>= 8.15.7` (`corepack enable` provides the pinned version)

- Node.js >= 20.0.0 (Node 20 LTS recommended)
- PNPM >= 8.0.0

### Installation
## Install

```bash
# Install all dependencies
pnpm install
cd ozone-ui
pnpm install # installs all workspace dependencies
```

### Development
## Develop

The `shared` package is consumed as a built artifact, so build it once (and
after any change to it) before/while running an app:

```bash
# Start development server for a specific app
pnpm dev:recon
pnpm dev:scm
pnpm dev:om
cd ozone-ui

pnpm build:shared # compile @ozone-ui/shared -> packages/shared/dist

# Build shared components (run this first if you make changes to shared)
pnpm build:shared
pnpm dev:recon # start the Recon app dev server
pnpm dev:scm # start the SCM app dev server
pnpm dev:om # start the OM app dev server
```

### Building
## Build

```bash
# Build all applications
pnpm build
cd ozone-ui

# Build specific application
pnpm build:recon
pnpm build # build shared, then all three apps
pnpm build:recon # build a single app
pnpm build:scm
pnpm build:om

# Build only shared components
pnpm build:shared
pnpm build:shared # build only the shared library
```

Build outputs are placed in:
- `build/recon/` - Recon application build
- `build/scm/` - SCM application build
- `build/om/` - OM application build
Application build output is written to `build/{recon,scm,om}/`.

### Clean
## Lint & clean

```bash
# Clean all build artifacts and node_modules
pnpm clean
pnpm lint # ESLint across the workspace
pnpm clean # remove build/ and all dist/ + node_modules
pnpm clean:cache # clear Vite caches
pnpm clean:all # clean + clean:cache
```

## Architecture

### Shared Components

The `@hadoop-ui/shared` package contains:

- **Components**: Reusable React components (e.g., Sidebar)
- **Utils**: Shared utility functions (e.g., menu utilities)
- **Types**: TypeScript type definitions

### Individual Applications

Each application (`recon`, `scm`, `om`) is a standalone Vite + React + TypeScript application that can import from the shared package.

## Technology Stack
## Using the design system

Each app mounts the theme once near its root (already wired in
`packages/{om,scm,recon}/src/main.tsx`), then consumes shared components and
tokens from `@ozone-ui/shared`:

```tsx
import {
ThemeProvider,
AppLayout,
Sidebar,
PageHeader,
Card,
KeyValuePair,
Chip,
} from '@ozone-ui/shared';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';

export default function App() {
return (
<ThemeProvider>
<AppLayout sider={<Sidebar setHeader={() => {}} />}>
<PageHeader title="Datanodes" subtitle="12 healthy" />
<Card
title="Instance details"
emphasis="elevated"
collapsible
extra={<Chip color="green" variant="dot">Healthy</Chip>}
>
<KeyValuePair label="Hostname" value="dn-01.ozone.local" />
<KeyValuePair label="UUID" value="a1b2c3" copyable />
</Card>
</AppLayout>
</ThemeProvider>
);
}
```

- **Build Tool**: Vite
### What's in `@ozone-ui/shared`

- **`theme/`**
- `colors`, `semanticColors`, `textStyles`, `fontFamilies`, `spacing`,
`radius` — design tokens (source of truth for colour and typography).
- `ozoneTheme` — an Ant Design v5 `ThemeConfig` derived from the tokens.
- `ThemeProvider` — wraps `ConfigProvider` with the theme and accepts optional
per-app `themeOverrides`.
- **`components/`** (derived from the components recurring across the mockups)
- `UtilityBar` — global top bar (leading/title, centre, actions).
- `Sidebar` — collapsible navigation rail.
- `AppLayout` — page shell (sider + header + content).
- `PageHeader` — page title with breadcrumb, subtitle and actions.
- `Card` — surface with `outlined`/`elevated`/`filled` emphasis and an
optional `collapsible` header.
- `KeyValuePair` — label/value pair (vertical or horizontal, optional link/copy).
- `Chip` — pill: `full`/`dot` variant, `standard`/`small` size, colour and
`selected`/`closable` states.
- `Alert` — inline status banner (info/success/warning/error).
- `TextLink` — themed inline link with optional external affordance.
- `IconButton` — square icon-only button with accessible label + tooltip.
- `Icon` — inline-SVG icon set (`currentColor`, tree-shakeable).

Prefer the tokens/theme over hard-coded colours or font sizes so re-theming
stays centralised.

## Technology stack

- **Build**: Vite 6 (apps), `tsc` (shared library)
- **Framework**: React 18
- **Language**: TypeScript
- **UI Library**: Ant Design v5
- **Package Manager**: PNPM (with workspaces)
- **Monorepo**: PNPM Workspaces
- **Language**: TypeScript 5.6
- **UI**: Ant Design v5, themed with the Ozone UI design tokens
- **Fonts**: Roboto / Roboto Mono (`@fontsource/roboto`), Plus Jakarta Sans (app titles)
- **Package manager / monorepo**: pnpm workspaces
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const compat = new FlatCompat({

module.exports = [
{
ignores: ['dist', 'build', 'node_modules'],
ignores: ['**/dist/**', '**/build/**', '**/node_modules/**'],
},
...compat.config({
env: {
Expand Down
20 changes: 10 additions & 10 deletions ozone-ui/src/package.json → ozone-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"packageManager": "pnpm@8.15.7",
"name": "hadoop-ui",
"name": "ozone-ui",
"version": "1.0.0",
"description": "The home of HDDS UI code.",
"scripts": {
"build": "pnpm run build:shared && pnpm run build:all",
"build:shared": "pnpm --filter @hadoop-ui/shared run build",
"build:all": "pnpm --filter @hadoop-ui/ozone-recon --filter @hadoop-ui/ozone-scm --filter @hadoop-ui/ozone-om run build",
"build:recon": "pnpm --filter @hadoop-ui/ozone-recon run build",
"build:scm": "pnpm --filter @hadoop-ui/ozone-scm run build",
"build:om": "pnpm --filter @hadoop-ui/ozone-om run build",
"dev:recon": "pnpm --filter @hadoop-ui/ozone-recon run dev",
"dev:scm": "pnpm --filter @hadoop-ui/ozone-scm run dev",
"dev:om": "pnpm --filter @hadoop-ui/ozone-om run dev",
"build:shared": "pnpm --filter @ozone-ui/shared run build",
"build:all": "pnpm --filter @ozone-ui/ozone-recon --filter @ozone-ui/ozone-scm --filter @ozone-ui/ozone-om run build",
"build:recon": "pnpm --filter @ozone-ui/ozone-recon run build",
"build:scm": "pnpm --filter @ozone-ui/ozone-scm run build",
"build:om": "pnpm --filter @ozone-ui/ozone-om run build",
"dev:recon": "pnpm --filter @ozone-ui/ozone-recon run dev",
"dev:scm": "pnpm --filter @ozone-ui/ozone-scm run dev",
"dev:om": "pnpm --filter @ozone-ui/ozone-om run dev",
"clean": "rm -rf build && pnpm -r exec rm -rf dist node_modules",
"clean:cache": "rm -rf node_modules/.vite && pnpm -r exec rm -rf node_modules/.vite",
"clean:all": "pnpm run clean && pnpm run clean:cache",
Expand Down Expand Up @@ -51,7 +51,7 @@
"prettier": "^3.8.1",
"typescript": "~5.6.2",
"typescript-eslint": "8.54.0",
"vite": "^5.4.10",
"vite": "^6.4.3",
"vite-plugin-svgr": "4.5.0",
"vite-tsconfig-paths": "^3.6.0",
"vitest": "^1.6.1"
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@hadoop-ui/ozone-om",
"name": "@ozone-ui/ozone-om",
"private": true,
"version": "1.0.0",
"scripts": {
Expand All @@ -8,7 +8,7 @@
"lint": "eslint ."
},
"dependencies": {
"@hadoop-ui/shared": "workspace:*",
"@ozone-ui/shared": "workspace:*",
"@ant-design/icons": "^5.6.1",
"@fontsource/roboto": "^4.5.8",
"ag-charts-community": "^7.3.0",
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@
*/
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { ThemeProvider } from '@ozone-ui/shared';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import App from './App';
import './index.css';

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<ThemeProvider>
<App />
</ThemeProvider>
</StrictMode>
);
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default defineConfig({
],
build: {
target: 'es2015',
outDir: '../../../build/om',
outDir: '../../build/om',
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@hadoop-ui/ozone-scm",
"name": "@ozone-ui/ozone-recon",
"private": true,
"version": "1.0.0",
"scripts": {
Expand All @@ -8,7 +8,7 @@
"lint": "eslint ."
},
"dependencies": {
"@hadoop-ui/shared": "workspace:*",
"@ozone-ui/shared": "workspace:*",
"@ant-design/icons": "^5.6.1",
"@fontsource/roboto": "^4.5.8",
"ag-charts-community": "^7.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,24 @@
* limitations under the License.
*/
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import { Button } from 'antd';
import './App.css';

function App() {
const [count, setCount] = useState(0);

return (
<>
<div>
<a href="https://vite.dev" target="_blank" rel="noreferrer">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="App">
<h1>Ozone Recon</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
<Button type="primary" onClick={() => setCount((count) => count + 1)}>
Count is {count}
</Button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</>
</div>
);
}

Expand Down
Loading
Loading