Skip to content

feat: v0.1 boilerplate web-ui for testing actions.#70

Open
chrisdoehring wants to merge 1 commit intomainfrom
cd/add-local-dev-ui
Open

feat: v0.1 boilerplate web-ui for testing actions.#70
chrisdoehring wants to merge 1 commit intomainfrom
cd/add-local-dev-ui

Conversation

@chrisdoehring
Copy link
Copy Markdown
Contributor

This pull request introduces a new React-based web UI for the Gundi Integration Actions Execution Service, integrates OpenID Connect authentication, and updates local development documentation and Docker Compose setup to support the new UI. The changes add user-friendly features for action execution and API integration, along with comprehensive documentation and troubleshooting guides for authentication.

Major changes include:

Web UI Addition & Features

  • Added a new React-based web UI in local/web-ui/ with features such as action listing and execution, dynamic forms, real-time results, Gundi API integration, and user authentication/profile management. [1] [2] [3] [4] [5]
  • Provided a production-ready Dockerfile for the web UI, enabling containerized builds and deployment as part of the local development environment.
  • Added .gitignore for the web UI to exclude dependencies, build artifacts, and environment files from version control.

Authentication Integration

  • Implemented OpenID Connect authentication in the web UI, including token management, protected routes, and user profile display. [1] [2]
  • Added detailed setup and troubleshooting documentation for OIDC authentication in AUTHENTICATION.md and TROUBLESHOOTING.md. [1] [2]

Docker Compose & Local Development Enhancements

  • Updated docker-compose.yml to add the web-ui service, configure health checks, and ensure the web UI is built and served automatically. Also improved the FastAPI health check and added platform specification for the pubsub emulator. [1] [2]
  • Updated LOCAL_DEVELOPMENT.md to document the new web UI, authentication setup, and troubleshooting steps for local deployment.

These changes collectively provide a modern, authenticated, and user-friendly interface for managing and executing Gundi integration actions, along with robust developer documentation and improved local development workflows.

@chrisdoehring
Copy link
Copy Markdown
Contributor Author

@marianobrc this is the same code I had previously PR'd from the onyesha repo (minus the Onyesha specific things).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new local React web UI to exercise the Action Runner service and view Gundi API connections, with OpenID Connect (OIDC) authentication support and local Docker Compose/docs updates to run everything together.

Changes:

  • Introduces a React + MUI UI for listing actions, executing actions (including schema-driven forms), and viewing connections.
  • Adds OIDC login/logout flow, token handling helpers, and protected routing.
  • Updates local Docker Compose + local dev docs to include the new web-ui service and troubleshooting guides.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
local/web-ui/start-dev.sh Dev helper script to install deps and run the UI locally.
local/web-ui/src/services/authService.js OIDC client wrapper for login/logout/token/header helpers.
local/web-ui/src/index.js React entrypoint mounting the app.
local/web-ui/src/index.css Base CSS styles.
local/web-ui/src/contexts/ConnectionContext.js Global state for selected connection.
local/web-ui/src/contexts/AuthContext.js React auth state + helpers exposed via context.
local/web-ui/src/config/auth.js Central auth/API configuration values for the UI.
local/web-ui/src/components/SilentCallback.js Route handler for OIDC silent renew callback.
local/web-ui/src/components/ProtectedRoute.js Route guard for authenticated-only content.
local/web-ui/src/components/Login.js Login screen initiating OIDC redirect.
local/web-ui/src/components/Header.js Top navigation + user menu + logout.
local/web-ui/src/components/DynamicForm.js Schema-driven action execution form + load stored config.
local/web-ui/src/components/ConfigViewer.js Connections list + filtering/search + details drawer.
local/web-ui/src/components/Callback.js OIDC redirect callback handler.
local/web-ui/src/components/ActionsList.js Lists actions exposed by the local FastAPI service.
local/web-ui/src/components/ActionExecute.js Action execution page (dynamic form + manual overrides).
local/web-ui/src/App.js App routing + theming + provider wiring.
local/web-ui/public/manifest.json PWA manifest.
local/web-ui/public/index.html UI HTML shell.
local/web-ui/package.json UI dependencies and scripts.
local/web-ui/TROUBLESHOOTING.md OIDC/CORS troubleshooting guide.
local/web-ui/README.md UI overview, usage, and configuration notes.
local/web-ui/Dockerfile Container build for the UI (build + serve static assets).
local/web-ui/AUTHENTICATION.md OIDC setup instructions.
local/web-ui/.gitignore Ignores node/build/env artifacts.
local/docker-compose.yml Adds web-ui service + healthcheck adjustments.
local/LOCAL_DEVELOPMENT.md Documents new UI and authentication in local workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


this.userManager.events.addAccessTokenExpired(() => {
console.log('Access token expired');
this.signinSilent();
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addAccessTokenExpired calls this.signinSilent() without awaiting or handling rejections. If silent signin fails, this can produce an unhandled promise rejection. Handle the promise (e.g., void ...catch(...)) and decide how the app should react on failure.

Suggested change
this.signinSilent();
void this.signinSilent().catch((error) => {
console.error('Silent signin failed after access token expiration:', error);
});

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +67
### Environment Variables
- `REACT_APP_API_URL` - URL of the FastAPI service (defaults to `http://localhost:8080`)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REACT_APP_API_URL is documented here, but the code currently hardcodes FastAPI URLs (e.g., http://localhost:8080) and does not read this env var. Either update the code to use REACT_APP_API_URL or adjust the docs to reflect the actual configuration mechanism.

Suggested change
### Environment Variables
- `REACT_APP_API_URL` - URL of the FastAPI service (defaults to `http://localhost:8080`)
The FastAPI service URL is currently configured via the React development server proxy
(see the `proxy` setting in `package.json`) and the local Docker Compose setup, which
connects the web UI to the FastAPI service running on port `8080`. The `REACT_APP_API_URL`
environment variable is not used by the current implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25
Drawer,
Slide,
Backdrop
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slide and Backdrop are imported from @mui/material but never used, which adds noise and can trigger lint warnings. Remove unused imports.

Suggested change
Drawer,
Slide,
Backdrop
Drawer

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +32
const fetchActions = async () => {
try {
setLoading(true);
// Use the FastAPI service URL that the browser can access
const response = await axios.get('http://localhost:8080/v1/actions/');
setActions(response.data);
setError(null);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FastAPI calls are hardcoded to http://localhost:8080, which bypasses CRA's proxy setting and makes the UI harder to run in non-localhost setups. Consider centralizing the API base URL (e.g., from process.env.REACT_APP_API_URL) and using relative paths or an axios instance.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +136
const fetchSchema = useCallback(async () => {
try {
setSchemaLoading(true);
const response = await axios.get(`http://localhost:8080/v1/actions/${actionId}/schema`);
setSchema(response.data);
} catch (err) {
console.error('Error fetching schema:', err);
// Don't set error state here, just log it
} finally {
setSchemaLoading(false);
}
}, [actionId]);

// Fetch schema when component mounts
useEffect(() => {
fetchSchema();
}, [fetchSchema]);

const handleInputChange = (field) => (event) => {
setFormData({
...formData,
[field]: event.target.value
});
};

const handleSwitchChange = (field) => (event) => {
setFormData({
...formData,
[field]: event.target.checked
});
};

const handleConfigOverrideChange = (key) => (event) => {
setFormData({
...formData,
config_overrides: {
...formData.config_overrides,
[key]: event.target.value
}
});
};

const handleTabChange = (event, newValue) => {
setTabValue(newValue);
};

const handleSubmit = async (event) => {
event.preventDefault();
setLoading(true);
setError(null);
setResult(null);

try {
// Use the FastAPI service URL that the browser can access
const response = await axios.post('http://localhost:8080/v1/actions/execute', formData);
setResult(response.data);
} catch (err) {
setError(err.response?.data?.detail || 'Failed to execute action');
console.error('Error executing action:', err);
} finally {
setLoading(false);
}
};

const handleDynamicFormSubmit = async (formData) => {
setLoading(true);
setError(null);
setResult(null);

try {
const requestData = {
integration_id: formData.integration_id,
action_id: actionId,
run_in_background: formData.run_in_background,
config_overrides: formData.config_overrides
};

const response = await axios.post('http://localhost:8080/v1/actions/execute', requestData);
setResult(response.data);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action execution and schema fetch are hardcoded to http://localhost:8080. This makes the UI inflexible outside the default local setup and ignores package.json proxy. Prefer a centralized/configurable API base URL (env-driven) and reuse it for all FastAPI requests.

Copilot uses AI. Check for mistakes.
type="number"
label={fieldSchema.title || fieldName}
value={value}
onChange={(e) => handleConfigOverrideChange(fieldName, parseFloat(e.target.value) || '')}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number parsing uses parseFloat(e.target.value) || '', which converts an entered 0 into '' and prevents users from setting a value of zero. Handle the empty-string case explicitly and otherwise parse to a number (and consider Number.isNaN checks).

Suggested change
onChange={(e) => handleConfigOverrideChange(fieldName, parseFloat(e.target.value) || '')}
onChange={(e) => {
const rawValue = e.target.value;
if (rawValue === '') {
handleConfigOverrideChange(fieldName, '');
return;
}
const parsedValue = parseFloat(rawValue);
handleConfigOverrideChange(
fieldName,
Number.isNaN(parsedValue) ? '' : parsedValue
);
}}

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +36
const initializeAuth = async () => {
try {
setIsLoading(true);
setError(null);
await authService.initialize();
setUser(authService.getUser());
setIsAuthenticated(authService.isUserAuthenticated());
} catch (err) {
console.error('Error initializing authentication:', err);
setError(err.message);
} finally {
setIsLoading(false);
}
};

initializeAuth();
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuthProvider initializes auth state once but doesn't subscribe to oidc-client user/token events. As a result, user/isAuthenticated in context can become stale after silent renew, token expiry, or signout from another tab. Consider wiring userManager.events (userLoaded/userUnloaded/accessTokenExpired) to update React state and cleaning up listeners on unmount.

Suggested change
const initializeAuth = async () => {
try {
setIsLoading(true);
setError(null);
await authService.initialize();
setUser(authService.getUser());
setIsAuthenticated(authService.isUserAuthenticated());
} catch (err) {
console.error('Error initializing authentication:', err);
setError(err.message);
} finally {
setIsLoading(false);
}
};
initializeAuth();
let isMounted = true;
const syncAuthStateFromService = () => {
if (!isMounted) {
return;
}
setUser(authService.getUser());
setIsAuthenticated(authService.isUserAuthenticated());
};
const initializeAuth = async () => {
try {
setIsLoading(true);
setError(null);
await authService.initialize();
syncAuthStateFromService();
} catch (err) {
console.error('Error initializing authentication:', err);
setError(err.message);
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
initializeAuth();
const userManager = authService.userManager;
const events = userManager && userManager.events;
if (events) {
const handleUserLoaded = () => {
syncAuthStateFromService();
};
const handleUserUnloaded = () => {
if (!isMounted) {
return;
}
setUser(null);
setIsAuthenticated(false);
};
const handleAccessTokenExpired = () => {
if (!isMounted) {
return;
}
// Conservatively mark the user as signed out when the access token expires.
setUser(null);
setIsAuthenticated(false);
};
events.addUserLoaded(handleUserLoaded);
events.addUserUnloaded(handleUserUnloaded);
events.addAccessTokenExpired(handleAccessTokenExpired);
return () => {
isMounted = false;
events.removeUserLoaded(handleUserLoaded);
events.removeUserUnloaded(handleUserUnloaded);
events.removeAccessTokenExpired(handleAccessTokenExpired);
};
}
return () => {
isMounted = false;
};

Copilot uses AI. Check for mistakes.
const { handleSigninCallback } = useAuth();
const navigate = useNavigate();
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable isLoading.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,30 @@
import React, { useEffect, useRef } from 'react';
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import React.

Suggested change
import React, { useEffect, useRef } from 'react';
import { useEffect, useRef } from 'react';

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25
Drawer,
Slide,
Backdrop
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused imports Backdrop, Slide.

Suggested change
Drawer,
Slide,
Backdrop
Drawer

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants