Deploy chat_web_app via server-infra (node-static) #15
@@ -0,0 +1,37 @@
|
||||
name: Deploy Beta
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
runs-on: self-hosted
|
||||
defaults:
|
||||
run:
|
||||
working-directory: llm-fe
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run unit tests
|
||||
run: npm run test:ci
|
||||
|
||||
deploy-beta:
|
||||
needs: unit-tests
|
||||
runs-on: self-hosted
|
||||
env:
|
||||
SERVER_INFRA_ROOT: /home/westfarn/Documents/repos/server-infra
|
||||
steps:
|
||||
- name: Deploy chat_web_app beta
|
||||
run: |
|
||||
"$SERVER_INFRA_ROOT/scripts/deploy.sh" \
|
||||
--app chat_web_app \
|
||||
--env beta \
|
||||
--ref "${{ gitea.sha }}"
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Deploy Prod
|
||||
|
||||
# Runs after Unit Tests completes on master. Direct pushes only (not PRs).
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Unit Tests]
|
||||
types: [completed]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: gitea.event.workflow_run.conclusion == 'success' && gitea.event.workflow_run.event == 'push'
|
||||
runs-on: self-hosted
|
||||
env:
|
||||
SERVER_INFRA_ROOT: /home/westfarn/Documents/repos/server-infra
|
||||
steps:
|
||||
- name: Deploy chat_web_app prod
|
||||
run: |
|
||||
"$SERVER_INFRA_ROOT/scripts/deploy.sh" \
|
||||
--app chat_web_app \
|
||||
--env prod \
|
||||
--ref "${{ gitea.event.workflow_run.head_sha }}"
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: self-hosted
|
||||
defaults:
|
||||
run:
|
||||
working-directory: llm-fe
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run unit tests
|
||||
run: npm run test:ci
|
||||
@@ -0,0 +1,167 @@
|
||||
# Chat Web App (`chat_web_app`)
|
||||
|
||||
Frontend for [chat.aimloperations.com](https://chat.aimloperations.com) — a React +
|
||||
TypeScript single-page app for the AI ML Operations chat product.
|
||||
|
||||
Companion backend: [chat_backend](https://git.aimloperations.com/ai_ml_operations/chat_backend)
|
||||
|
||||
Production serve is **node-static** (not Docker). Hosts build with npm into a
|
||||
document root; the shared **web-static** nginx container from
|
||||
[server-infra](https://git.aimloperations.com/ai_ml_operations/server-infra) serves
|
||||
those assets. Same pattern as `dta_webapp`.
|
||||
|
||||
## Stack
|
||||
|
||||
- **React 18**, **TypeScript**, **Create React App** (`react-scripts` 5)
|
||||
- **Material UI (MUI) 5** + Emotion for components and theming
|
||||
- **React Router 6** for routing
|
||||
- **Axios** for REST against `chat_backend`
|
||||
- **WebSocket** for live chat (`REACT_APP_BACKEND_WS_API_BASE_URL`)
|
||||
- **Jest** + React Testing Library for unit tests
|
||||
|
||||
## Repository layout
|
||||
|
||||
```
|
||||
chat_web_app/
|
||||
├── README.md
|
||||
├── .gitea/workflows/ # Unit tests + deploy-prod / deploy-beta
|
||||
└── llm-fe/ # CRA application (npm root)
|
||||
├── package.json
|
||||
├── .env.development # Local dev API/WS URLs
|
||||
├── .env.beta
|
||||
├── .env.production
|
||||
├── public/
|
||||
└── src/
|
||||
├── axiosApi.js # Axios instances (JWT + CSRF)
|
||||
├── App.tsx
|
||||
├── index.tsx
|
||||
└── llm-fe/
|
||||
├── pages/ # Route-level views
|
||||
├── components/ # Shared & feature UI
|
||||
├── contexts/ # Auth, account, WebSocket, theme
|
||||
└── ui-kit/ # MD* design-system wrappers
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js 20** (matches CI)
|
||||
- **npm** (lockfile: `llm-fe/package-lock.json`)
|
||||
- Running [chat_backend](https://git.aimloperations.com/ai_ml_operations/chat_backend)
|
||||
locally for full chat/auth (default in `.env.development`: port **8001**)
|
||||
|
||||
## Local setup
|
||||
|
||||
### 1. Clone and install
|
||||
|
||||
```bash
|
||||
git clone ssh://git@git.aimloperations.com:30009/ai_ml_operations/chat_web_app.git
|
||||
cd chat_web_app/llm-fe
|
||||
npm ci
|
||||
```
|
||||
|
||||
### 2. Configure environment
|
||||
|
||||
CRA loads mode-specific env files. Variables must be prefixed with `REACT_APP_`
|
||||
to be visible in the browser bundle.
|
||||
|
||||
| File | Used when | Backend |
|
||||
|------|-----------|---------|
|
||||
| `.env.development` | `npm start` | `http://127.0.0.1:8001` |
|
||||
| `.env.production` | `npm run build` / `build:prod` | `https://chatbackend.aimloperations.com` |
|
||||
| `.env.beta` | `npm run build:beta` | `https://beta.chatbackend.aimloperations.com` |
|
||||
|
||||
Required keys (already set in the committed env files):
|
||||
|
||||
```env
|
||||
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
|
||||
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/
|
||||
```
|
||||
|
||||
These are **baked into the JS at build time**. There is no host secret file for
|
||||
this frontend (unlike Django apps under `~/Documents/secrets/`). Change the
|
||||
committed `.env.*` files if API domains change, then redeploy.
|
||||
|
||||
Optional local overrides (gitignored): `.env.local`, `.env.development.local`,
|
||||
`.env.production.local`.
|
||||
|
||||
### 3. Start the backend
|
||||
|
||||
From the `chat_backend` repo (adjust to however that service is run locally):
|
||||
|
||||
```bash
|
||||
# example — follow chat_backend README for the current command
|
||||
cd chat_backend
|
||||
# run Django on the port matching .env.development (8001)
|
||||
```
|
||||
|
||||
### 4. Start the dev server
|
||||
|
||||
```bash
|
||||
cd llm-fe
|
||||
npm start
|
||||
```
|
||||
|
||||
Open **http://localhost:3000**.
|
||||
|
||||
## npm scripts
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `npm start` | CRA dev server (port 3000) |
|
||||
| `npm run build` | Production build into `llm-fe/build/` |
|
||||
| `npm run build:prod` | Prod build + copy to `/var/www/prod.chat.aimloperations/html` |
|
||||
| `npm run build:beta` | Beta env build + copy to `/var/www/beta.chat.aimloperations/html` |
|
||||
| `npm test` | Jest watch mode |
|
||||
| `npm run test:ci` | Single CI run (`CI=true`, no watch) |
|
||||
|
||||
`build:prod` / `build:beta` are intended for deploy hosts (or a machine that can
|
||||
write those `/var/www/...` paths). Paths must match
|
||||
`server-infra` `app_catalog.chat_web_app.webroot_pattern`:
|
||||
|
||||
```text
|
||||
/var/www/{env}.chat.aimloperations/html
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
cd llm-fe
|
||||
npm test # watch
|
||||
npm run test:ci # one-shot (matches Gitea Unit Tests)
|
||||
```
|
||||
|
||||
## CI / deployment
|
||||
|
||||
Gitea Actions workflows in `.gitea/workflows/`:
|
||||
|
||||
| Workflow | Trigger | Behavior |
|
||||
|----------|---------|----------|
|
||||
| `unit-tests.yml` | push/PR to `master` | `npm ci` + `npm run test:ci` in `llm-fe/` |
|
||||
| `deploy-prod.yml` | after green **Unit Tests** on `master` **push** | `server-infra/scripts/deploy.sh --app chat_web_app --env prod --ref <sha>` |
|
||||
| `deploy-beta.yml` | `workflow_dispatch` | tests, then deploy `--env beta` |
|
||||
|
||||
Deploy flow (on each webserver listed in `host_apps`):
|
||||
|
||||
1. Git checkout at the pinned ref under `/opt/apps/src/chat_web_app_<env>`
|
||||
2. `npm ci` in `llm-fe/`
|
||||
3. `npm run build:<env>` → writes the static tree under `/var/www/...`
|
||||
4. **web-static** nginx serves that root on the host port (prod **8082**, beta **8083** if enabled)
|
||||
|
||||
Manual deploy from the control node:
|
||||
|
||||
```bash
|
||||
~/Documents/repos/server-infra/scripts/deploy.sh \
|
||||
--app chat_web_app --env prod --ref <sha>
|
||||
```
|
||||
|
||||
NPM (not managed by Ansible) should balance public chat domains to:
|
||||
|
||||
- `adama:8082` + `roslin:8082` + `ai-server-4080:8082` (prod)
|
||||
|
||||
See [server-infra IMPLEMENTATION.md](https://git.aimloperations.com/ai_ml_operations/server-infra).
|
||||
|
||||
## Related repos
|
||||
|
||||
- **Backend:** [chat_backend](https://git.aimloperations.com/ai_ml_operations/chat_backend) — Django API + WebSocket
|
||||
- **Infra:** [server-infra](https://git.aimloperations.com/ai_ml_operations/server-infra) — `app_catalog`, `host_apps`, `deploy.sh`, web-static
|
||||
- **Pattern reference:** [dta_webapp](https://git.aimloperations.com/Ditch_The_Agent/dta_webapp) — node-static frontend deploy
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
REACT_APP_BACKEND_REST_API_BASE_URL=https://beta.chatbackend.aimloperations.com/api/
|
||||
REACT_APP_BACKEND_WS_API_BASE_URL=wss://beta.chatbackend.aimloperations.com/ws/chat_again/
|
||||
@@ -40,6 +40,8 @@
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=development react-scripts start",
|
||||
"build": "NODE_ENV=production react-scripts build",
|
||||
"build:prod": "NODE_ENV=production react-scripts build && mkdir -p /var/www/prod.chat.aimloperations/html && cp -r ./build/* /var/www/prod.chat.aimloperations/html/",
|
||||
"build:beta": "bash -c 'set -a; source .env.beta; set +a; NODE_ENV=production react-scripts build' && mkdir -p /var/www/beta.chat.aimloperations/html && cp -r ./build/* /var/www/beta.chat.aimloperations/html/",
|
||||
"test": "NODE_ENV=development react-scripts test",
|
||||
"test:ci": "CI=true NODE_ENV=development react-scripts test --watchAll=false --coverage=false",
|
||||
"eject": "react-scripts eject"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import axios from "axios";
|
||||
const Cookies = require("js-cookie");
|
||||
|
||||
const baseURL = "http://localhost:8011/api/";
|
||||
//const baseURL = 'https://chatbackend.aimloperations.com/api/';
|
||||
//const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL;
|
||||
const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL;
|
||||
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL: baseURL,
|
||||
|
||||
@@ -70,11 +70,7 @@ function WebSocketProvider({ children }) {
|
||||
useEffect(() => {
|
||||
/* WS initialization and cleanup */
|
||||
if (account) {
|
||||
ws.current = new WebSocket(`ws://localhost:8011/ws/chat_again/`);
|
||||
//ws.current = new WebSocket(`ws://localhost:8011/ws/conditional_chat/`);
|
||||
//ws.current = new WebSocket('wss://chatbackend.aimloperations.com/ws/chat_again/')
|
||||
//ws.current = new WebSocket('wss://chatbackend.aimloperations.com/ws/conditional_chat/')
|
||||
//ws.current = process.env.REACT_APP_BACKEND_WS_API_BASE_URL;
|
||||
ws.current = new WebSocket(process.env.REACT_APP_BACKEND_WS_API_BASE_URL);
|
||||
|
||||
ws.current.onopen = () => {
|
||||
setSocket(ws.current);
|
||||
|
||||
Reference in New Issue
Block a user