diff --git a/.emergent/emergent.yml b/.emergent/emergent.yml new file mode 100644 index 0000000000000000000000000000000000000000..788fc27735d9b2a62ed0f02a00861e687ffaa1ee --- /dev/null +++ b/.emergent/emergent.yml @@ -0,0 +1,3 @@ +{ + "env_image_name": "fastapi_react_mongo_shadcn_base_image_cloud_arm:release-17042026-1" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..267ee1f97607da65c7dee6f00eb20716268673d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# IDE and editors +.idea/ +.vscode/ + +# Dependencies +node_modules/ +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# Testing +/coverage + +# Next.js +/.next/ +/out/ +next-env.d.ts +*.tsbuildinfo + +# Production builds +/build +dist/ +dist + +# Environment files (comprehensive coverage) + +*token.json* +*credentials.json* + +# Logs and debug files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* +dump.rdb + +# System files +.DS_Store +*.pem + +# Python +__pycache__/ +*pyc* +venv/ +.venv/ + +# Development tools +chainlit.md +.chainlit +.ipynb_checkpoints/ +.ac + +# Deployment +.vercel + +# Data and databases +agenthub/agents/youtube/db + +# Archive files and large assets +**/*.zip +**/*.tar.gz +**/*.tar +**/*.tgz +*.pack +*.deb +*.dylib + +# Build caches +.cache/ + +memory/test_credentials.md + +# Mobile development +android-sdk/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3786c8adf4662171d66ebd14aca44ae1b071d91b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Here are your Instructions diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..42a9b3216e1dbb7d9d50853196221e979b9af546 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,27 @@ +fastapi==0.110.1 +uvicorn==0.25.0 +boto3>=1.34.129 +requests-oauthlib>=2.0.0 +cryptography>=42.0.8 +python-dotenv>=1.0.1 +pymongo==4.5.0 +pydantic>=2.6.4 +email-validator>=2.2.0 +pyjwt>=2.10.1 +bcrypt==4.1.3 +passlib>=1.7.4 +tzdata>=2024.2 +motor==3.3.1 +pytest>=8.0.0 +black>=24.1.1 +isort>=5.13.2 +flake8>=7.0.0 +mypy>=1.8.0 +python-jose>=3.3.0 +requests>=2.31.0 +pandas>=2.2.0 +numpy>=1.26.0 +python-multipart>=0.0.9 +jq>=1.6.0 +typer>=0.9.0 +emergentintegrations==0.1.0 diff --git a/backend/server.py b/backend/server.py new file mode 100644 index 0000000000000000000000000000000000000000..9ed97afb76c55f2e25be29af00eaa8736d914e91 --- /dev/null +++ b/backend/server.py @@ -0,0 +1,89 @@ +from fastapi import FastAPI, APIRouter +from dotenv import load_dotenv +from starlette.middleware.cors import CORSMiddleware +from motor.motor_asyncio import AsyncIOMotorClient +import os +import logging +from pathlib import Path +from pydantic import BaseModel, Field, ConfigDict +from typing import List +import uuid +from datetime import datetime, timezone + + +ROOT_DIR = Path(__file__).parent +load_dotenv(ROOT_DIR / '.env') + +# MongoDB connection +mongo_url = os.environ['MONGO_URL'] +client = AsyncIOMotorClient(mongo_url) +db = client[os.environ['DB_NAME']] + +# Create the main app without a prefix +app = FastAPI() + +# Create a router with the /api prefix +api_router = APIRouter(prefix="/api") + + +# Define Models +class StatusCheck(BaseModel): + model_config = ConfigDict(extra="ignore") # Ignore MongoDB's _id field + + id: str = Field(default_factory=lambda: str(uuid.uuid4())) + client_name: str + timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) + +class StatusCheckCreate(BaseModel): + client_name: str + +# Add your routes to the router instead of directly to app +@api_router.get("/") +async def root(): + return {"message": "Hello World"} + +@api_router.post("/status", response_model=StatusCheck) +async def create_status_check(input: StatusCheckCreate): + status_dict = input.model_dump() + status_obj = StatusCheck(**status_dict) + + # Convert to dict and serialize datetime to ISO string for MongoDB + doc = status_obj.model_dump() + doc['timestamp'] = doc['timestamp'].isoformat() + + _ = await db.status_checks.insert_one(doc) + return status_obj + +@api_router.get("/status", response_model=List[StatusCheck]) +async def get_status_checks(): + # Exclude MongoDB's _id field from the query results + status_checks = await db.status_checks.find({}, {"_id": 0}).to_list(1000) + + # Convert ISO string timestamps back to datetime objects + for check in status_checks: + if isinstance(check['timestamp'], str): + check['timestamp'] = datetime.fromisoformat(check['timestamp']) + + return status_checks + +# Include the router in the main app +app.include_router(api_router) + +app.add_middleware( + CORSMiddleware, + allow_credentials=True, + allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','), + allow_methods=["*"], + allow_headers=["*"], +) + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +@app.on_event("shutdown") +async def shutdown_db_client(): + client.close() \ No newline at end of file diff --git a/design_guidelines.json b/design_guidelines.json new file mode 100644 index 0000000000000000000000000000000000000000..4790cb99a7bfcfcae840d53e3aa718708ab0eab2 --- /dev/null +++ b/design_guidelines.json @@ -0,0 +1,106 @@ +{ + "project_name": "ForgeSight", + "domain": "Manufacturing / AI Quality Control / Developer Tool", + "theme": "Dark", + "archetype": "Archetype 4 (Swiss & High-Contrast) - Technical Brutalist variant", + "mood": "Precise, authoritative, industrial, hardware-optimized, slightly brutalist", + "colors": { + "background_base": "#0A0A0A", + "background_surface": "#141416", + "primary_accent": "#ED1C24", + "primary_hover": "#FF3B30", + "border_default": "rgba(255,255,255,0.1)", + "text_primary": "#FFFFFF", + "text_secondary": "#A1A1AA", + "status": { + "pass": "#10B981", + "warn": "#F59E0B", + "fail": "#ED1C24" + } + }, + "typography": { + "headings": { + "family": "Chivo", + "weights": [ + "300", + "900" + ], + "classes_h1": "text-4xl sm:text-5xl lg:text-6xl font-black tracking-tighter leading-none text-white", + "classes_h2": "text-2xl sm:text-3xl lg:text-4xl font-bold tracking-tight text-white", + "classes_h3": "text-xl sm:text-2xl font-semibold text-white" + }, + "body": { + "family": "IBM Plex Sans", + "weights": [ + "400", + "500" + ], + "classes": "text-base leading-relaxed tracking-normal text-zinc-300" + }, + "mono": { + "family": "JetBrains Mono", + "weights": [ + "400", + "700" + ], + "classes": "text-sm tracking-tight font-mono text-zinc-300" + }, + "labels": { + "classes": "text-xs font-mono uppercase tracking-[0.2em] text-zinc-400" + } + }, + "layout_and_spacing": { + "philosophy": "Industrial-HMI meets Linear. Dense information with purposeful whitespace and strict grid alignments.", + "bento_grid": { + "dashboard_mode": "Control Room Grid (Tight gaps: gap-4 or gap-6, col-span logic for metrics vs feeds)", + "marketing_mode": "Tetris Grid (Asymmetric, wide gaps: gap-8, col-span-full for hero elements)" + }, + "spacing_scale": "Tailwind standard (p-4, p-6, p-8). Cards must use internal p-6. Footers use py-24." + }, + "surfaces": { + "dashboards": { + "style": "Grid Borders (Technical Look)", + "rules": "Expose the skeleton. border-collapse layouts, 1px subtle borders (border-white/10), flat backgrounds, no soft shadows." + }, + "agent_console": { + "style": "Retro-Futurism / Terminal HMI", + "rules": "Deep black #000000 background, neon red borders for active states, monospace fonts, CSS repeating-linear-gradient for subtle scanlines." + }, + "navigation": { + "style": "Solid / Linear", + "rules": "Solid #0A0A0A background with hard 1px bottom border (border-white/10). Pill-style route switcher." + } + }, + "components_strategy": { + "html_tailwind": "Use for Marketing/Hero sections (custom pure HTML/Tailwind) for maximum asymmetry.", + "shadcn_ui": "Use heavily customized Shadcn for App/Dashboard interactions (Tabs, Dropdowns, Tables, Cards). Override soft corners to max rounded-sm (2px) or rounded-none.", + "buttons": "Flat, angular. Primary CTA uses AMD Red background, white text. No rounded-full (use rounded-none or rounded-sm). Hover state must transition background to lighter red (#FF3B30).", + "animations": "Minimalist micro-interactions. Blinking cursor for agent streaming. Smooth fade-ins for bento cards." + }, + "media_and_images": { + "hero_inspection": { + "url": "https://static.prod-images.emergentagent.com/jobs/d5829a2e-bc03-4880-adcd-73acc809a3bd/images/184a8bf32b150669152ea3aa72546730d8caad845b1b8eb0233eeb35e4255eeb.png", + "description": "Macro shot of industrial inspection. Use as background for the main hero section with a bg-black/60 overlay for text contrast." + }, + "blueprint_architecture": { + "url": "https://static.prod-images.emergentagent.com/jobs/d5829a2e-bc03-4880-adcd-73acc809a3bd/images/7251062dc0e36ea4218374b05cc959bc4e6c55a2cf4789a8a2cbc38db6392916.png", + "description": "Abstract 3D AMD MI300X chip architecture. Use in the 'Deployment Blueprint' section." + }, + "factory_feed": { + "url": "https://images.unsplash.com/photo-1720036237334-9263cd28c3d4?crop=entropy&cs=srgb&fm=jpg&ixid=M3w3NTY2Njl8MHwxfHNlYXJjaHwyfHxpbmR1c3RyaWFsJTIwbWFudWZhY3R1cmluZyUyMGFzc2VtYmx5JTIwbGluZSUyMGRhcmt8ZW58MHx8fHwxNzc3NjQ5MzQzfDA&ixlib=rb-4.1.0&q=85", + "description": "Background for Defect Feed empty states or generic manufacturing imagery." + } + }, + "icons_and_testing": { + "icon_library": "lucide-react (Default, fine for technical interfaces)", + "testing": "All interactive elements MUST have data-testid attributes (e.g. data-testid='upload-inspection-button')." + }, + "instructions_to_main_agent": [ + "Build the app as a hybrid: Marketing Hero at the top, followed by the Dashboard Console below.", + "Do NOT use the color teal, purple, or generic SaaS blue anywhere.", + "Enforce the 'Grid Borders' surface look for the dashboard parts to make it feel like an industrial HMI.", + "For the Agent Console, implement a typing/streaming effect with JetBrains Mono to simulate live inference.", + "Use data-testid on every interactive UI piece.", + "In the Blueprint diagram section, use standard CSS grid to layer the visual elements over the blueprint_architecture image." + ] +} \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4d29575de80483b005c29bfcac5061cd2f45313e --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000000000000000000000000000000000000..58beeaccd87e230076cab531b8f418f40b6d1aeb --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in your browser. + +The page will reload when you make changes.\ +You may also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can't go back!** + +If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. + +You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `npm run build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000000000000000000000000000000000000..ebf7e6edf3e341acbdb9b72a6d828d4837d504de --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": false, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/frontend/craco.config.js b/frontend/craco.config.js new file mode 100644 index 0000000000000000000000000000000000000000..ddbeea8880b5976f3ae5fd480e120aebd195e166 --- /dev/null +++ b/frontend/craco.config.js @@ -0,0 +1,100 @@ +// craco.config.js +const path = require("path"); +require("dotenv").config(); + +// Check if we're in development/preview mode (not production build) +// Craco sets NODE_ENV=development for start, NODE_ENV=production for build +const isDevServer = process.env.NODE_ENV !== "production"; + +// Environment variable overrides +const config = { + enableHealthCheck: process.env.ENABLE_HEALTH_CHECK === "true", +}; + +// Conditionally load health check modules only if enabled +let WebpackHealthPlugin; +let setupHealthEndpoints; +let healthPluginInstance; + +if (config.enableHealthCheck) { + WebpackHealthPlugin = require("./plugins/health-check/webpack-health-plugin"); + setupHealthEndpoints = require("./plugins/health-check/health-endpoints"); + healthPluginInstance = new WebpackHealthPlugin(); +} + +let webpackConfig = { + eslint: { + configure: { + extends: ["plugin:react-hooks/recommended"], + rules: { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + }, + }, + }, + webpack: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + configure: (webpackConfig) => { + + // Add ignored patterns to reduce watched directories + webpackConfig.watchOptions = { + ...webpackConfig.watchOptions, + ignored: [ + '**/node_modules/**', + '**/.git/**', + '**/build/**', + '**/dist/**', + '**/coverage/**', + '**/public/**', + ], + }; + + // Add health check plugin to webpack if enabled + if (config.enableHealthCheck && healthPluginInstance) { + webpackConfig.plugins.push(healthPluginInstance); + } + return webpackConfig; + }, + }, +}; + +webpackConfig.devServer = (devServerConfig) => { + // Add health check endpoints if enabled + if (config.enableHealthCheck && setupHealthEndpoints && healthPluginInstance) { + const originalSetupMiddlewares = devServerConfig.setupMiddlewares; + + devServerConfig.setupMiddlewares = (middlewares, devServer) => { + // Call original setup if exists + if (originalSetupMiddlewares) { + middlewares = originalSetupMiddlewares(middlewares, devServer); + } + + // Setup health endpoints + setupHealthEndpoints(devServer, healthPluginInstance); + + return middlewares; + }; + } + + return devServerConfig; +}; + +// Wrap with visual edits (automatically adds babel plugin, dev server, and overlay in dev mode) +if (isDevServer) { + try { + const { withVisualEdits } = require("@emergentbase/visual-edits/craco"); + webpackConfig = withVisualEdits(webpackConfig); + } catch (err) { + if (err.code === 'MODULE_NOT_FOUND' && err.message.includes('@emergentbase/visual-edits/craco')) { + console.warn( + "[visual-edits] @emergentbase/visual-edits not installed — visual editing disabled." + ); + } else { + throw err; + } + } +} + +module.exports = webpackConfig; diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..822d8e42f9d704812ecedfbbad596044b650a257 --- /dev/null +++ b/frontend/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"] +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000000000000000000000000000000000000..e3ca27fdd965b2b00f7f6116610aaef74e1eebb4 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,91 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "dependencies": { + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-accordion": "^1.2.8", + "@radix-ui/react-alert-dialog": "^1.1.11", + "@radix-ui/react-aspect-ratio": "^1.1.4", + "@radix-ui/react-avatar": "^1.1.7", + "@radix-ui/react-checkbox": "^1.2.3", + "@radix-ui/react-collapsible": "^1.1.8", + "@radix-ui/react-context-menu": "^2.2.12", + "@radix-ui/react-dialog": "^1.1.11", + "@radix-ui/react-dropdown-menu": "^2.1.12", + "@radix-ui/react-hover-card": "^1.1.11", + "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-menubar": "^1.1.12", + "@radix-ui/react-navigation-menu": "^1.2.10", + "@radix-ui/react-popover": "^1.1.11", + "@radix-ui/react-progress": "^1.1.4", + "@radix-ui/react-radio-group": "^1.3.4", + "@radix-ui/react-scroll-area": "^1.2.6", + "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-slider": "^1.3.2", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-switch": "^1.2.2", + "@radix-ui/react-tabs": "^1.1.9", + "@radix-ui/react-toast": "^1.2.11", + "@radix-ui/react-toggle": "^1.1.6", + "@radix-ui/react-toggle-group": "^1.1.7", + "@radix-ui/react-tooltip": "^1.2.4", + "axios": "^1.8.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "cra-template": "1.2.0", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.507.0", + "next-themes": "^0.4.6", + "react": "^19.0.0", + "react-day-picker": "8.10.1", + "react-dom": "^19.0.0", + "react-hook-form": "^7.56.2", + "react-resizable-panels": "^3.0.1", + "react-router-dom": "^7.5.1", + "react-scripts": "5.0.1", + "recharts": "^3.6.0", + "sonner": "^2.0.3", + "tailwind-merge": "^3.2.0", + "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.2", + "zod": "^3.24.4" + }, + "scripts": { + "start": "craco start", + "build": "craco build", + "test": "craco test" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@craco/craco": "^7.1.0", + "@emergentbase/visual-edits": "https://assets.emergent.sh/npm/emergentbase-visual-edits-1.0.8.tgz", + "@eslint/js": "9.23.0", + "autoprefixer": "^10.4.20", + "eslint": "9.23.0", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-jsx-a11y": "6.10.2", + "eslint-plugin-react": "7.37.4", + "eslint-plugin-react-hooks": "5.2.0", + "globals": "15.15.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/frontend/plugins/health-check/health-endpoints.js b/frontend/plugins/health-check/health-endpoints.js new file mode 100644 index 0000000000000000000000000000000000000000..5af66cfc6dabdc40d2264b0cecf00de470084e6c --- /dev/null +++ b/frontend/plugins/health-check/health-endpoints.js @@ -0,0 +1,213 @@ +// health-endpoints.js +// API endpoints for health checks and monitoring + +const os = require('os'); + +const SERVER_START_TIME = Date.now(); + +/** + * Setup health check endpoints on the dev server + * @param {Object} devServer - Webpack dev server instance + * @param {Object} healthPlugin - Instance of WebpackHealthPlugin + */ +function setupHealthEndpoints(devServer, healthPlugin) { + if (!devServer || !devServer.app) { + console.warn('[Health Check] Dev server not available, skipping health endpoints'); + return; + } + + if (!healthPlugin) { + console.warn('[Health Check] Health plugin not provided, skipping health endpoints'); + return; + } + + console.log('[Health Check] Setting up health endpoints...'); + + // ==================================================================== + // GET /health - Detailed health status (JSON) + // ==================================================================== + devServer.app.get("/health", (req, res) => { + const webpackStatus = healthPlugin.getStatus(); + const uptime = Date.now() - SERVER_START_TIME; + const memUsage = process.memoryUsage(); + + res.json({ + status: webpackStatus.isHealthy ? 'healthy' : 'unhealthy', + timestamp: new Date().toISOString(), + uptime: { + seconds: Math.floor(uptime / 1000), + formatted: formatDuration(uptime), + }, + webpack: { + state: webpackStatus.state, + isHealthy: webpackStatus.isHealthy, + hasCompiled: webpackStatus.hasCompiled, + errors: webpackStatus.errorCount, + warnings: webpackStatus.warningCount, + lastCompileTime: webpackStatus.lastCompileTime + ? new Date(webpackStatus.lastCompileTime).toISOString() + : null, + lastSuccessTime: webpackStatus.lastSuccessTime + ? new Date(webpackStatus.lastSuccessTime).toISOString() + : null, + compileDuration: webpackStatus.compileDuration + ? `${webpackStatus.compileDuration}ms` + : null, + totalCompiles: webpackStatus.totalCompiles, + firstCompileTime: webpackStatus.firstCompileTime + ? new Date(webpackStatus.firstCompileTime).toISOString() + : null, + }, + server: { + nodeVersion: process.version, + platform: os.platform(), + arch: os.arch(), + cpus: os.cpus().length, + memory: { + heapUsed: formatBytes(memUsage.heapUsed), + heapTotal: formatBytes(memUsage.heapTotal), + rss: formatBytes(memUsage.rss), + external: formatBytes(memUsage.external), + }, + systemMemory: { + total: formatBytes(os.totalmem()), + free: formatBytes(os.freemem()), + used: formatBytes(os.totalmem() - os.freemem()), + }, + }, + environment: process.env.NODE_ENV || 'development', + }); + }); + + // ==================================================================== + // GET /health/simple - Simple text response (OK/COMPILING/ERROR) + // ==================================================================== + devServer.app.get("/health/simple", (req, res) => { + const webpackStatus = healthPlugin.getSimpleStatus(); + + if (webpackStatus.state === 'success') { + res.status(200).send('OK'); + } else if (webpackStatus.state === 'compiling') { + res.status(200).send('COMPILING'); + } else if (webpackStatus.state === 'idle') { + res.status(200).send('IDLE'); + } else { + res.status(503).send('ERROR'); + } + }); + + // ==================================================================== + // GET /health/ready - Readiness check (Kubernetes/load balancer) + // ==================================================================== + devServer.app.get("/health/ready", (req, res) => { + const webpackStatus = healthPlugin.getSimpleStatus(); + + if (webpackStatus.state === 'success') { + res.status(200).json({ + ready: true, + state: webpackStatus.state, + }); + } else { + res.status(503).json({ + ready: false, + state: webpackStatus.state, + reason: webpackStatus.state === 'compiling' + ? 'Compilation in progress' + : 'Compilation failed', + }); + } + }); + + // ==================================================================== + // GET /health/live - Liveness check (Kubernetes) + // ==================================================================== + devServer.app.get("/health/live", (req, res) => { + res.status(200).json({ + alive: true, + timestamp: new Date().toISOString(), + }); + }); + + // ==================================================================== + // GET /health/errors - Get current errors and warnings + // ==================================================================== + devServer.app.get("/health/errors", (req, res) => { + const webpackStatus = healthPlugin.getStatus(); + + res.json({ + errorCount: webpackStatus.errorCount, + warningCount: webpackStatus.warningCount, + errors: webpackStatus.errors, + warnings: webpackStatus.warnings, + state: webpackStatus.state, + }); + }); + + // ==================================================================== + // GET /health/stats - Compilation statistics + // ==================================================================== + devServer.app.get("/health/stats", (req, res) => { + const webpackStatus = healthPlugin.getStatus(); + const uptime = Date.now() - SERVER_START_TIME; + + res.json({ + totalCompiles: webpackStatus.totalCompiles, + averageCompileTime: webpackStatus.totalCompiles > 0 + ? `${Math.round(uptime / webpackStatus.totalCompiles)}ms` + : null, + lastCompileDuration: webpackStatus.compileDuration + ? `${webpackStatus.compileDuration}ms` + : null, + firstCompileTime: webpackStatus.firstCompileTime + ? new Date(webpackStatus.firstCompileTime).toISOString() + : null, + serverUptime: formatDuration(uptime), + }); + }); + + console.log('[Health Check] ✓ Health endpoints ready:'); + console.log(' • GET /health - Detailed status'); + console.log(' • GET /health/simple - Simple OK/ERROR'); + console.log(' • GET /health/ready - Readiness check'); + console.log(' • GET /health/live - Liveness check'); + console.log(' • GET /health/errors - Error details'); + console.log(' • GET /health/stats - Statistics'); +} + +// ==================================================================== +// Helper Functions +// ==================================================================== + +/** + * Format bytes to human-readable string + * @param {number} bytes + * @returns {string} + */ +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; +} + +/** + * Format duration to human-readable string + * @param {number} ms - Duration in milliseconds + * @returns {string} + */ +function formatDuration(ms) { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + if (hours > 0) { + return `${hours}h ${minutes % 60}m ${seconds % 60}s`; + } else if (minutes > 0) { + return `${minutes}m ${seconds % 60}s`; + } else { + return `${seconds}s`; + } +} + +module.exports = setupHealthEndpoints; diff --git a/frontend/plugins/health-check/webpack-health-plugin.js b/frontend/plugins/health-check/webpack-health-plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..4efbce72ef278f9bb2db8e27bcde4b01c7c68776 --- /dev/null +++ b/frontend/plugins/health-check/webpack-health-plugin.js @@ -0,0 +1,120 @@ +// webpack-health-plugin.js +// Webpack plugin that tracks compilation state and health metrics + +class WebpackHealthPlugin { + constructor() { + this.status = { + state: 'idle', // idle, compiling, success, failed + errors: [], + warnings: [], + lastCompileTime: null, + lastSuccessTime: null, + compileDuration: 0, + totalCompiles: 0, + firstCompileTime: null, + }; + } + + apply(compiler) { + const pluginName = 'WebpackHealthPlugin'; + + // Hook: Compilation started + compiler.hooks.compile.tap(pluginName, () => { + const now = Date.now(); + this.status.state = 'compiling'; + this.status.lastCompileTime = now; + + if (!this.status.firstCompileTime) { + this.status.firstCompileTime = now; + } + }); + + // Hook: Compilation completed + compiler.hooks.done.tap(pluginName, (stats) => { + const info = stats.toJson({ + all: false, + errors: true, + warnings: true, + }); + + this.status.totalCompiles++; + this.status.compileDuration = Date.now() - this.status.lastCompileTime; + + if (stats.hasErrors()) { + this.status.state = 'failed'; + this.status.errors = info.errors.map(err => ({ + message: err.message || String(err), + stack: err.stack, + moduleName: err.moduleName, + loc: err.loc, + })); + } else { + this.status.state = 'success'; + this.status.lastSuccessTime = Date.now(); + this.status.errors = []; + } + + if (stats.hasWarnings()) { + this.status.warnings = info.warnings.map(warn => ({ + message: warn.message || String(warn), + moduleName: warn.moduleName, + loc: warn.loc, + })); + } else { + this.status.warnings = []; + } + }); + + // Hook: Compilation failed + compiler.hooks.failed.tap(pluginName, (error) => { + this.status.state = 'failed'; + this.status.errors = [{ + message: error.message, + stack: error.stack, + }]; + this.status.compileDuration = Date.now() - this.status.lastCompileTime; + }); + + // Hook: Invalid (file changed, recompiling) + compiler.hooks.invalid.tap(pluginName, () => { + this.status.state = 'compiling'; + }); + } + + getStatus() { + return { + ...this.status, + // Add computed fields + isHealthy: this.status.state === 'success', + errorCount: this.status.errors.length, + warningCount: this.status.warnings.length, + hasCompiled: this.status.totalCompiles > 0, + }; + } + + // Get simplified status for quick checks + getSimpleStatus() { + return { + state: this.status.state, + isHealthy: this.status.state === 'success', + errorCount: this.status.errors.length, + warningCount: this.status.warnings.length, + }; + } + + // Reset statistics (useful for testing) + reset() { + this.status = { + state: 'idle', + errors: [], + warnings: [], + lastCompileTime: null, + lastSuccessTime: null, + compileDuration: 0, + totalCompiles: 0, + firstCompileTime: null, + }; + } +} + +module.exports = WebpackHealthPlugin; diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..33ad091d26d8a9dc95ebdf616e217d985ec215b8 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..427ea43b33806c327610bde0eccca5e4eed4e7b4 --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,158 @@ + + + + + + + + + + + + + Emergent | Fullstack App + + + + + +
+ + + + + + +

+ Made with Emergent +

+
+ + + diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000000000000000000000000000000000000..6bfdb4eae6eb72def093ae4b7de1a601088a7bd4 --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,34 @@ +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #0f0f10; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/frontend/src/App.js b/frontend/src/App.js new file mode 100644 index 0000000000000000000000000000000000000000..3ece752ec477029d40862188437fd1b671e4d118 --- /dev/null +++ b/frontend/src/App.js @@ -0,0 +1,54 @@ +import { useEffect } from "react"; +import "@/App.css"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import axios from "axios"; + +const BACKEND_URL = process.env.REACT_APP_BACKEND_URL; +const API = `${BACKEND_URL}/api`; + +const Home = () => { + const helloWorldApi = async () => { + try { + const response = await axios.get(`${API}/`); + console.log(response.data.message); + } catch (e) { + console.error(e, `errored out requesting / api`); + } + }; + + useEffect(() => { + helloWorldApi(); + }, []); + + return ( +
+
+ + + +

Building something incredible ~!

+
+
+ ); +}; + +function App() { + return ( +
+ + + }> + } /> + + + +
+ ); +} + +export default App; diff --git a/frontend/src/components/ui/accordion.jsx b/frontend/src/components/ui/accordion.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1c4416a4a852c758ce4056b302fb6b2e9cf24118 --- /dev/null +++ b/frontend/src/components/ui/accordion.jsx @@ -0,0 +1,41 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props}> + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/frontend/src/components/ui/alert-dialog.jsx b/frontend/src/components/ui/alert-dialog.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a4174f3675cce4b9835ec9acb878b6d0578134e7 --- /dev/null +++ b/frontend/src/components/ui/alert-dialog.jsx @@ -0,0 +1,97 @@ +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/frontend/src/components/ui/alert.jsx b/frontend/src/components/ui/alert.jsx new file mode 100644 index 0000000000000000000000000000000000000000..28597e84ffa7ba757fb992076bc5340fd9d567a0 --- /dev/null +++ b/frontend/src/components/ui/alert.jsx @@ -0,0 +1,47 @@ +import * as React from "react" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/frontend/src/components/ui/aspect-ratio.jsx b/frontend/src/components/ui/aspect-ratio.jsx new file mode 100644 index 0000000000000000000000000000000000000000..c4abbf37f217c715a0eaade7f45ac78600df419f --- /dev/null +++ b/frontend/src/components/ui/aspect-ratio.jsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/frontend/src/components/ui/avatar.jsx b/frontend/src/components/ui/avatar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9a2f853246a39519572367f45336371dc24d80ea --- /dev/null +++ b/frontend/src/components/ui/avatar.jsx @@ -0,0 +1,33 @@ +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/frontend/src/components/ui/badge.jsx b/frontend/src/components/ui/badge.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a687ebad024768896157580c5884e8c6c0c0f502 --- /dev/null +++ b/frontend/src/components/ui/badge.jsx @@ -0,0 +1,34 @@ +import * as React from "react" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + ...props +}) { + return (
); +} + +export { Badge, badgeVariants } diff --git a/frontend/src/components/ui/breadcrumb.jsx b/frontend/src/components/ui/breadcrumb.jsx new file mode 100644 index 0000000000000000000000000000000000000000..2588f36ddf5e0a0d5367d44033d43fd17db3a66b --- /dev/null +++ b/frontend/src/components/ui/breadcrumb.jsx @@ -0,0 +1,92 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef( + ({ ...props }, ref) =>