Lightweight SPA for managing Beemesh clusters. Designed for Cloudflare Pages hosting.
- Vite + React - Fast, lightweight build
- TypeScript - Type safety
- Tailwind CSS - Utility-first styling
- Google OIDC (PKCE) - Secure auth without secrets
┌─────────────────────┐ ┌─────────────────────┐
│ Cloudflare Pages │ │ Machineplane │
│ (Static SPA) │◄───────►│ (REST API) │
│ │ JWT │ │
│ - React UI │ │ - Validates token │
│ - Google OAuth │ │ - K8s-compatible │
└─────────────────────┘ └─────────────────────┘
# Install dependencies
npm install
# Start mock API server
npm run mock
# Start dev server (separate terminal)
npm run devOpen http://localhost:3000 and use "Dev Login" (no Google OAuth needed).
Create OAuth credentials at Google Cloud Console:
- Create OAuth 2.0 Client ID (Web application)
- Add authorized redirect URI:
https://your-portal.pages.dev/auth/callback - Copy the Client ID
# Build
npm run build
# Deploy via Cloudflare dashboard or CLI
npx wrangler pages deploy distSet environment variables in Cloudflare Pages:
VITE_GOOGLE_CLIENT_ID- Your Google OAuth Client IDVITE_API_URL- Machineplane API endpoint
Machineplane must allow requests from your portal domain:
Access-Control-Allow-Origin: https://your-portal.pages.dev
Access-Control-Allow-Headers: Authorization, Content-Type
- No secrets in SPA - Uses PKCE flow (public client)
- JWT validation - Machineplane validates Google ID tokens
- HTTPS only - Cloudflare enforces TLS
- Security headers - CSP, X-Frame-Options via
_headers
portal/
├── public/
│ ├── _headers # Cloudflare security headers
│ ├── _redirects # SPA routing
│ └── beemesh.svg # Logo
├── src/
│ ├── api/
│ │ └── client.ts # API client with Bearer auth
│ ├── auth/
│ │ ├── AuthContext.tsx
│ │ ├── config.ts
│ │ ├── google.ts # OIDC implementation
│ │ └── pkce.ts # PKCE utilities
│ ├── components/
│ ├── pages/
│ ├── App.tsx
│ └── main.tsx
├── .env.example
├── package.json
└── vite.config.ts