Skip to content

Commit 4275ae9

Browse files
authored
OSS Conductor UI V2 (#852)
* OSS Conductor UI V2
1 parent 5e7c282 commit 4275ae9

1,360 files changed

Lines changed: 218354 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ui-next-ci.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: UI v2 CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths:
8+
- "ui-next/**"
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
lint-format-test:
15+
name: Lint, Format & Test
16+
runs-on: ubuntu-latest
17+
defaults:
18+
run:
19+
working-directory: ui-next
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: 22
28+
29+
- name: Setup pnpm
30+
uses: pnpm/action-setup@v4
31+
with:
32+
version: 10.32.0
33+
34+
- name: Get pnpm store directory
35+
id: pnpm-cache
36+
run: echo "store=$(pnpm store path)" >> $GITHUB_OUTPUT
37+
38+
- name: Cache pnpm store
39+
uses: actions/cache@v4
40+
with:
41+
path: ${{ steps.pnpm-cache.outputs.store }}
42+
key: ${{ runner.os }}-pnpm-${{ hashFiles('ui-next/pnpm-lock.yaml') }}
43+
restore-keys: ${{ runner.os }}-pnpm-
44+
45+
- name: Install dependencies
46+
run: pnpm install --frozen-lockfile
47+
48+
- name: Prettier check
49+
run: pnpm prettier:check
50+
51+
- name: Lint
52+
run: pnpm lint
53+
54+
- name: Type check
55+
run: pnpm typecheck
56+
57+
- name: Test
58+
run: pnpm test
59+
60+
- name: Build
61+
run: pnpm build

ui-next/.env

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# OSS Conductor UI – defaults for local dev
2+
3+
# Backend API (Conductor server). Default: local server.
4+
VITE_WF_SERVER=http://localhost:8080
5+
6+
# Optional
7+
# VITE_PUBLIC_URL=/
8+
# GENERATE_SOURCEMAP=false

ui-next/.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Dependencies
2+
node_modules
3+
4+
# Local env (may contain secrets)
5+
.env.local
6+
.env.*.local
7+
8+
# Build output
9+
dist
10+
build
11+
storybook-static
12+
13+
# Test / Playwright
14+
/test-results/
15+
/playwright-report/
16+
/playwright/.cache/
17+
18+
# Turbo
19+
.turbo
20+
21+
# Vite
22+
.vite
23+
24+
# IDE / OS
25+
.DS_Store
26+

ui-next/.npmrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Hoist these packages so they are directly importable without needing explicit
2+
# devDependency declarations for each transitive package.
3+
public-hoist-pattern[]=@mui/*
4+
public-hoist-pattern[]=@use-gesture/*
5+
public-hoist-pattern[]=@eslint/*
6+
public-hoist-pattern[]=globals
7+
public-hoist-pattern[]=monaco-editor

ui-next/.prettierignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
build
2+
storybook-static
3+
/test-results/
4+
/playwright-report/
5+
/playwright/.cache/
6+
tests-examples
7+
playwright
8+
public/context.js
9+
/dist/
10+
pnpm-lock.yaml

ui-next/.prettierrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

ui-next/README.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# Conductor UI v2
2+
3+
The open-source React UI for [Conductor](https://github.com/conductor-oss/conductor). It ships as both a **standalone web application** and an **npm library** that enterprise packages can extend via a plugin system.
4+
5+
## Running locally
6+
7+
### Prerequisites
8+
9+
- Node.js 22+
10+
- [pnpm](https://pnpm.io/) 10.32.0 (`corepack use pnpm@10.32.0`)
11+
- A running Conductor server (default: `http://localhost:8080`)
12+
13+
### Setup
14+
15+
```bash
16+
pnpm install
17+
```
18+
19+
Configure the backend URL in `.env` (see `.env` for defaults):
20+
21+
```bash
22+
VITE_WF_SERVER=http://localhost:8080
23+
```
24+
25+
### Start the dev server
26+
27+
```bash
28+
pnpm dev
29+
```
30+
31+
The app will be available at `http://localhost:1234`.
32+
33+
### Runtime configuration
34+
35+
The app reads runtime config from `public/context.js`, which is loaded at startup (not bundled). Copy the example and edit as needed:
36+
37+
```bash
38+
cp public/context.js.example public/context.js
39+
```
40+
41+
This file sets feature flags (`window.conductor`) and auth config (`window.authConfig`) without requiring a rebuild.
42+
43+
## Available scripts
44+
45+
| Script | Description |
46+
| --------------------- | ------------------------------- |
47+
| `pnpm dev` | Start dev server with HMR |
48+
| `pnpm build` | Build standalone app to `dist/` |
49+
| `pnpm build:lib` | Build npm library to `dist/` |
50+
| `pnpm build:all` | Build both app and library |
51+
| `pnpm lint` | Run ESLint |
52+
| `pnpm lint:fix` | Run ESLint with auto-fix |
53+
| `pnpm prettier:check` | Check formatting |
54+
| `pnpm prettier:write` | Auto-format all files |
55+
| `pnpm typecheck` | Type-check without emitting |
56+
| `pnpm test` | Run unit tests |
57+
| `pnpm test:watch` | Run tests in watch mode |
58+
| `pnpm test:coverage` | Run tests with coverage report |
59+
60+
## Using as an npm library
61+
62+
Install the package:
63+
64+
```bash
65+
npm install conductor-ui
66+
```
67+
68+
Import styles in your app entry point:
69+
70+
```tsx
71+
import "conductor-ui/styles.css"; // component styles
72+
import "conductor-ui/global.css"; // global body/font styles (optional)
73+
```
74+
75+
### Extending with plugins
76+
77+
The plugin system lets you register additional routes, sidebar items, task forms, auth providers, and more without modifying the core package.
78+
79+
```tsx
80+
import { pluginRegistry, App } from "conductor-ui";
81+
82+
// Register a custom sidebar item
83+
pluginRegistry.registerSidebarItem({
84+
position: { target: "root", after: "definitionsSubMenu" },
85+
item: {
86+
id: "myFeature",
87+
title: "My Feature",
88+
icon: <MyIcon />,
89+
linkTo: "/my-feature",
90+
shortcuts: [],
91+
hidden: false,
92+
position: 350,
93+
},
94+
});
95+
96+
// Register a custom route
97+
pluginRegistry.registerRoutes([
98+
{
99+
path: "/my-feature",
100+
element: <MyFeaturePage />,
101+
},
102+
]);
103+
104+
// Render the app
105+
function Root() {
106+
return <App />;
107+
}
108+
```
109+
110+
### Plugin extension points
111+
112+
| Extension | Method | Description |
113+
| --------------- | ------------------------------ | -------------------------------------------------- |
114+
| Routes | `registerRoutes(routes)` | Add authenticated routes |
115+
| Public routes | `registerPublicRoutes(routes)` | Add unauthenticated routes |
116+
| Sidebar items | `registerSidebarItem(reg)` | Inject items into the sidebar |
117+
| Task forms | `registerTaskForm(reg)` | Custom forms for task types in the workflow editor |
118+
| Task menu items | `registerTaskMenuItem(reg)` | Add task types to the "Add Task" menu |
119+
| Auth provider | `registerAuthProvider(reg)` | Replace the auth implementation |
120+
| Search provider | `registerSearchProvider(reg)` | Add results to global search |
121+
122+
### Sidebar item positioning
123+
124+
Sidebar items use numeric positions so plugins can inject between core items without collisions. The core OSS positions are exported for reference:
125+
126+
```tsx
127+
import { CORE_SIDEBAR_POSITIONS } from "conductor-ui";
128+
129+
// CORE_SIDEBAR_POSITIONS.ROOT:
130+
// executionsSubMenu: 100
131+
// runWorkflow: 200
132+
// definitionsSubMenu:300
133+
// helpMenu: 400
134+
// swaggerItem: 500
135+
136+
pluginRegistry.registerSidebarItem({
137+
position: { target: "root" },
138+
item: {
139+
id: "myItem",
140+
position: 350, // between definitionsSubMenu (300) and helpMenu (400)
141+
// ...
142+
},
143+
});
144+
```
145+
146+
## Project structure
147+
148+
```
149+
src/
150+
├── components/ # Shared UI components
151+
│ └── Sidebar/ # Sidebar with plugin-injectable menu
152+
├── pages/ # Route-level page components
153+
├── plugins/ # Plugin registry and fetch utilities
154+
├── shared/ # Auth state machine and context
155+
├── theme/ # MUI theme provider
156+
├── types/ # Shared TypeScript types
157+
└── utils/ # Feature flags, constants, helpers
158+
public/
159+
├── context.js # Runtime config (gitignored, not bundled)
160+
└── context.js.example
161+
```
162+
163+
## Peer dependencies
164+
165+
When consuming as a library, the following must be provided by the host app:
166+
167+
- `react` ^18
168+
- `react-dom` ^18
169+
- `react-router` / `react-router-dom` ^7
170+
- `@mui/material`, `@mui/icons-material`, `@mui/system`, `@mui/x-date-pickers`
171+
- `@emotion/react`, `@emotion/styled`

ui-next/eslint.config.mjs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import eslintReact from "@eslint-react/eslint-plugin";
2+
import js from "@eslint/js";
3+
import vitest from "@vitest/eslint-plugin";
4+
import reactHooks from "eslint-plugin-react-hooks";
5+
import reactRefresh from "eslint-plugin-react-refresh";
6+
import { globalIgnores } from "eslint/config";
7+
import globals from "globals";
8+
import tseslint from "typescript-eslint";
9+
10+
const commonRules = {
11+
"@typescript-eslint/no-explicit-any": "warn",
12+
"@typescript-eslint/no-unused-vars": [
13+
"error",
14+
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
15+
],
16+
// TODO: Remove this and fix types properly
17+
"@typescript-eslint/ban-ts-comment": "warn",
18+
// Prevent direct imports from date-fns and date-fns-tz except in utils/date.ts
19+
"no-restricted-imports": [
20+
"error",
21+
{
22+
patterns: [
23+
{
24+
group: ["date-fns"],
25+
message:
26+
"Direct imports from 'date-fns' are not allowed. Please import from 'src/utils/date' instead.",
27+
},
28+
{
29+
group: ["date-fns-tz"],
30+
message:
31+
"Direct imports from 'date-fns-tz' are not allowed. Please import from 'src/utils/date' instead.",
32+
},
33+
],
34+
},
35+
],
36+
};
37+
38+
const baseConfig = {
39+
extends: [
40+
js.configs.recommended,
41+
tseslint.configs.recommended,
42+
reactHooks.configs["recommended-latest"],
43+
reactRefresh.configs.vite,
44+
eslintReact.configs.recommended,
45+
],
46+
languageOptions: {
47+
ecmaVersion: 2020,
48+
sourceType: "module",
49+
globals: {
50+
...globals.browser,
51+
...globals.node,
52+
},
53+
},
54+
rules: {
55+
"no-undef": "error",
56+
...commonRules,
57+
},
58+
};
59+
60+
export default tseslint.config([
61+
globalIgnores(["dist", "node_modules"]),
62+
63+
// Test files (Vitest + testing globals)
64+
{
65+
files: [
66+
"**/__tests__/**/*.{js,jsx,ts,tsx}",
67+
"**/*.{test,spec}.{js,jsx,ts,tsx}",
68+
],
69+
...baseConfig,
70+
plugins: { vitest, ...baseConfig.plugins },
71+
rules: {
72+
...vitest.configs.recommended.rules,
73+
...commonRules,
74+
},
75+
},
76+
77+
// JSX files (allow PropTypes)
78+
{
79+
files: ["**/*.jsx"],
80+
...baseConfig,
81+
rules: {
82+
...baseConfig.rules,
83+
"react/prop-types": "off",
84+
"@eslint-react/no-prop-types": "off",
85+
},
86+
},
87+
88+
// Non-test files (TS/TSX)
89+
{
90+
files: ["**/*.{js,ts,tsx}"],
91+
ignores: [
92+
"**/__tests__/**/*.{js,jsx,ts,tsx}",
93+
"**/*.{test,spec}.{js,jsx,ts,tsx}",
94+
],
95+
...baseConfig,
96+
},
97+
98+
// Allow date-fns and date-fns-tz imports in utils/date.ts
99+
{
100+
files: ["src/utils/date.ts"],
101+
rules: {
102+
"no-restricted-imports": "off",
103+
},
104+
},
105+
]);

0 commit comments

Comments
 (0)