fix: set UTF-8 charset headers in server and add production deployment checklist
Build Curio HMS / build-and-deploy (push) Successful in 46s
Details
Build Curio HMS / build-and-deploy (push) Successful in 46s
Details
This commit is contained in:
parent
5f2862c8a2
commit
336d485926
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Curio HMS — Production deploy checklist
|
||||||
|
|
||||||
|
Target: [https://curio.applaude.net/dashboard.html](https://curio.applaude.net/dashboard.html)
|
||||||
|
|
||||||
|
## Pre-deploy (local)
|
||||||
|
|
||||||
|
- [ ] Confirm dashboard files are saved as **UTF-8** (no `₹` / `ðŸ` in source).
|
||||||
|
- [ ] Smoke-test locally:
|
||||||
|
```powershell
|
||||||
|
cd c:\Users\sunde\proxmox\curaflow-hms
|
||||||
|
node backend/server.js
|
||||||
|
```
|
||||||
|
Open `http://localhost:3000/dashboard.html` — sidebar must not overlap the queue table; emojis and ₹ must render correctly.
|
||||||
|
- [ ] Bump cache-bust query strings if you changed static assets (`dashboard.css?v=`, `dashboard.js?v=` in `dashboard.html`).
|
||||||
|
|
||||||
|
## Deploy (CI → registry → Kubernetes)
|
||||||
|
|
||||||
|
This repo builds on **push** via `.gitea/workflows/build.yaml` (Kaniko → `192.168.8.250:5000/curio:latest`).
|
||||||
|
|
||||||
|
1. **Commit and push** all changes (including `backend/server.js` UTF-8 headers and dashboard UI fixes).
|
||||||
|
2. **Wait for the Gitea Actions build** to finish successfully.
|
||||||
|
3. **Roll out Kubernetes** (pick one):
|
||||||
|
```bash
|
||||||
|
# Restart pods to pull :latest
|
||||||
|
kubectl rollout restart deployment/curio-app -n curio
|
||||||
|
|
||||||
|
# Or patch redeploy annotation (see k8s/app.yaml)
|
||||||
|
kubectl apply -f k8s/app.yaml -n curio
|
||||||
|
kubectl rollout status deployment/curio-app -n curio
|
||||||
|
```
|
||||||
|
4. **Confirm pods are new**:
|
||||||
|
```bash
|
||||||
|
kubectl get pods -n curio -l app=curio-app
|
||||||
|
kubectl logs -n curio -l app=curio-app --tail=50
|
||||||
|
```
|
||||||
|
|
||||||
|
## Post-deploy verification
|
||||||
|
|
||||||
|
Open production in a **private/incognito** window (avoids stale cache).
|
||||||
|
|
||||||
|
| Check | Expected |
|
||||||
|
|-------|----------|
|
||||||
|
| Nav labels | 👥 Queue, 💊 Pharmacy (not `ðŸ` / `₹`) |
|
||||||
|
| Sidebar layout | Sidebar on the left; main content not covered |
|
||||||
|
| Overview stats | `↑ 4 since last hour` (not `â†'`) |
|
||||||
|
| Doctor workspace | Log in as Doctor → **Queue** → **Start Recording** visible |
|
||||||
|
| Response headers | DevTools → Network → `dashboard.html` → `Content-Type: text/html; charset=utf-8` |
|
||||||
|
| Recording | Chrome/Edge, allow microphone; button toggles to **Pause Scribe** |
|
||||||
|
|
||||||
|
### Quick header check (PowerShell)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
(Invoke-WebRequest -Uri "https://curio.applaude.net/dashboard.html" -Method Head).Headers["Content-Type"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Should include `charset=utf-8`.
|
||||||
|
|
||||||
|
### Doctor role (for Zen / recording UI)
|
||||||
|
|
||||||
|
In browser console on production:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
localStorage.setItem('userRole', 'DOCTOR');
|
||||||
|
location.reload();
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open **Queue** — clinical workspace with SOAP notes and **Start Recording**.
|
||||||
|
|
||||||
|
## If encoding is still broken after deploy
|
||||||
|
|
||||||
|
1. **Files on disk** — Re-save `dashboard.html` (and other HTML) as UTF-8 in the editor; redeploy.
|
||||||
|
2. **Reverse proxy** — Ensure nginx/ingress does not force `charset=iso-8859-1` on static files.
|
||||||
|
3. **CDN/cache** — Purge edge cache for `*.html`, `*.css`, `*.js`.
|
||||||
|
4. **Hard refresh** — `Ctrl+Shift+R` on the dashboard.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If you tag images per release, redeploy the previous tag instead of :latest
|
||||||
|
kubectl set image deployment/curio-app curio=192.168.8.250:5000/curio:<previous-tag> -n curio
|
||||||
|
kubectl rollout status deployment/curio-app -n curio
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files that must ship for the dashboard UX fix
|
||||||
|
|
||||||
|
- `dashboard.html`
|
||||||
|
- `dashboard.css`
|
||||||
|
- `dashboard.js`
|
||||||
|
- `globals.css`
|
||||||
|
- `settings.css`
|
||||||
|
- `backend/server.js` (UTF-8 `Content-Type` headers)
|
||||||
|
|
@ -8,38 +8,48 @@ const app = express();
|
||||||
const port = 3000;
|
const port = 3000;
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
|
const staticRoot = path.join(__dirname, '../');
|
||||||
|
|
||||||
|
/** UTF-8 Content-Type for text assets (fixes emoji/₹ mojibake when proxy omits charset). */
|
||||||
|
function setUtf8ContentType(res, filePath) {
|
||||||
|
if (filePath.endsWith('.html')) {
|
||||||
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.css')) {
|
||||||
|
res.setHeader('Content-Type', 'text/css; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.js')) {
|
||||||
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.json')) {
|
||||||
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNoCacheHeaders(res) {
|
||||||
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||||
|
res.setHeader('Pragma', 'no-cache');
|
||||||
|
res.setHeader('Expires', '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendHtmlPage(res, filename) {
|
||||||
|
setNoCacheHeaders(res);
|
||||||
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
res.sendFile(path.join(staticRoot, filename));
|
||||||
|
}
|
||||||
|
|
||||||
// Serve static files
|
// Serve static files
|
||||||
app.use(express.static(path.join(__dirname, '../'), {
|
app.use(express.static(staticRoot, {
|
||||||
setHeaders: (res, path) => {
|
setHeaders: (res, filePath) => {
|
||||||
if (path.endsWith('.html')) {
|
setUtf8ContentType(res, filePath);
|
||||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
if (filePath.endsWith('.html')) {
|
||||||
res.setHeader('Pragma', 'no-cache');
|
setNoCacheHeaders(res);
|
||||||
res.setHeader('Expires', '0');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Primary Routes
|
// Primary Routes
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => sendHtmlPage(res, 'login.html'));
|
||||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
app.get('/admin', (req, res) => sendHtmlPage(res, 'admin.html'));
|
||||||
res.setHeader('Pragma', 'no-cache');
|
app.get('/dashboard', (req, res) => sendHtmlPage(res, 'dashboard.html'));
|
||||||
res.setHeader('Expires', '0');
|
|
||||||
res.sendFile(path.join(__dirname, '../login.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/admin', (req, res) => {
|
|
||||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
||||||
res.setHeader('Pragma', 'no-cache');
|
|
||||||
res.setHeader('Expires', '0');
|
|
||||||
res.sendFile(path.join(__dirname, '../admin.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/dashboard', (req, res) => {
|
|
||||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
||||||
res.setHeader('Pragma', 'no-cache');
|
|
||||||
res.setHeader('Expires', '0');
|
|
||||||
res.sendFile(path.join(__dirname, '../dashboard.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Multi-tenant Config API
|
// Multi-tenant Config API
|
||||||
app.get('/api/config/:tenantId', (req, res) => {
|
app.get('/api/config/:tenantId', (req, res) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue