Add CI/CD pipeline, feedback modal, Atlas qualys_id fallback, and health endpoint

- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
  environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
  prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
  feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
  fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
This commit is contained in:
Jordan Ramos
2026-05-08 12:47:39 -06:00
parent 86fdd084ac
commit de2c5f245e
14 changed files with 1049 additions and 66 deletions

View File

@@ -7,23 +7,37 @@
# 2. lint — run linters / static checks
# 3. test — run backend (Jest) and frontend (react-scripts) tests
# 4. build — produce the production frontend bundle
# 5. deploy — restart services on the local machine (manual trigger)
# 5. deploy — deploy to staging (local) or production (SSH to 71.85.90.6)
# 6. verify — post-deploy health checks
#
# Executor: shell (runs directly on dashboard-dev using system Node.js)
# Uses cache (not artifacts) for node_modules to avoid upload size limits.
# Environments:
# staging — dashboard-dev:3100 (auto-deploy on main/master)
# production — 71.85.90.6:3001 (manual trigger, requires staging verification)
#
# Executor: shell (runs on dashboard-dev using system Node.js)
# =============================================================================
# ---------------------------------------------------------------------------
# Global cache — persists node_modules between pipeline runs on this runner
# Variables
# ---------------------------------------------------------------------------
variables:
PROD_HOST: "71.85.90.6"
PROD_USER: "root"
PROD_DIR: "/home/cve-dashboard"
STAGING_DIR: "/home/cve-dashboard-staging"
# ---------------------------------------------------------------------------
# Global cache — persists node_modules between pipeline runs
# ---------------------------------------------------------------------------
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- frontend/node_modules/
policy: pull
# ---------------------------------------------------------------------------
# Stages run in order; jobs within a stage run in parallel
# Stages
# ---------------------------------------------------------------------------
stages:
- install
@@ -31,6 +45,7 @@ stages:
- test
- build
- deploy
- verify
# =============================================================================
# STAGE 1: Install dependencies
@@ -39,13 +54,22 @@ stages:
install-backend:
stage: install
script:
- npm install
- npm ci --prefer-offline
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull-push
install-frontend:
stage: install
script:
- cd frontend
- npm install
- cd frontend && npm ci --prefer-offline
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- frontend/node_modules/
policy: pull-push
# =============================================================================
# STAGE 2: Lint / static analysis
@@ -54,10 +78,19 @@ install-frontend:
lint-frontend:
stage: lint
script:
- cd frontend
- npm install
- npx eslint src/ --max-warnings 0
allow_failure: true # non-blocking until the team cleans up existing warnings
- cd frontend && npx eslint src/ --max-warnings 0
needs:
- install-frontend
lint-backend:
stage: lint
script:
- node -c backend/server.js
- node -c backend/routes/*.js
- node -c backend/helpers/*.js
- node -c backend/middleware/*.js
needs:
- install-backend
# =============================================================================
# STAGE 3: Tests
@@ -66,56 +99,196 @@ lint-frontend:
test-backend:
stage: test
script:
- npm install
- npx jest --ci --forceExit --detectOpenHandles backend/__tests__/
- npx jest --ci --forceExit backend/__tests__/
timeout: 5 minutes
needs:
- install-backend
test-frontend:
stage: test
script:
- cd frontend
- npm install
- CI=true npx react-scripts test --watchAll=false --ci --forceExit
- cd frontend && CI=true npx react-scripts test --watchAll=false --ci --forceExit
timeout: 5 minutes
allow_failure: true # 2 test suites have pre-existing ESM/env issues — fix separately
needs:
- install-frontend
# =============================================================================
# STAGE 4: Build the production frontend bundle
# STAGE 4: Build
# =============================================================================
build-frontend:
stage: build
script:
- cd frontend
- npm install
- CI=false REACT_APP_API_BASE=/api REACT_APP_API_HOST="" npm run build
- cd frontend && CI=false REACT_APP_API_BASE=/api REACT_APP_API_HOST="" npm run build
artifacts:
paths:
- frontend/build/
expire_in: 7 days
needs:
- test-frontend
- lint-frontend
# =============================================================================
# STAGE 5: Deploy
# =============================================================================
# Since the runner IS the app server (dashboard-dev), deploy just restarts
# the services locally. No SSH needed.
#
# Manual trigger only, and only from the main/master branch.
# =============================================================================
deploy:
# ---------------------------------------------------------------------------
# Staging — auto-deploys on main/master to dashboard-dev:3100
# ---------------------------------------------------------------------------
deploy-staging:
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
when: on_success
environment:
name: staging
url: http://localhost:3100
script:
- echo "Deploying to staging (dashboard-dev:3100)..."
# Ensure staging directory exists
- mkdir -p ${STAGING_DIR}
# Sync code (exclude .git, node_modules, uploads, logs)
- rsync -a --delete
--exclude='.git'
--exclude='node_modules'
--exclude='frontend/node_modules'
--exclude='frontend/build'
--exclude='backend/uploads'
--exclude='*.log'
--exclude='*.db'
--exclude='.env'
${CI_PROJECT_DIR}/ ${STAGING_DIR}/
# Copy built frontend
- cp -r ${CI_PROJECT_DIR}/frontend/build ${STAGING_DIR}/frontend/build
# Install deps in staging
- cd ${STAGING_DIR} && npm ci --prefer-offline
- cd ${STAGING_DIR}/frontend && npm ci --prefer-offline
# Ensure staging .env exists
- |
if [ ! -f "${STAGING_DIR}/backend/.env" ]; then
cp ${CI_PROJECT_DIR}/backend/.env ${STAGING_DIR}/backend/.env
sed -i 's/^PORT=.*/PORT=3100/' ${STAGING_DIR}/backend/.env
grep -q "^PORT=" ${STAGING_DIR}/backend/.env || echo "PORT=3100" >> ${STAGING_DIR}/backend/.env
fi
# Restart staging service
- sudo systemctl restart cve-backend-staging || sudo systemctl start cve-backend-staging || true
- echo "Staging deploy complete."
needs:
- build-frontend
- test-backend
# ---------------------------------------------------------------------------
# Production — manual trigger, SSH to 71.85.90.6
# ---------------------------------------------------------------------------
deploy-production:
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
when: manual
environment:
name: production
url: http://71.85.90.6:3001
script:
- echo "Deploying on dashboard-dev..."
- cd /home/cve-dashboard
- git pull origin ${CI_COMMIT_BRANCH}
- npm install
- cd frontend && npm install && npm run build && cd ..
- ./stop-servers.sh || true
- ./start-servers.sh
- echo "Deploy complete."
- echo "Deploying to production (${PROD_HOST})..."
# Record current commit on prod for rollback
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git rev-parse HEAD 2>/dev/null || echo none" > /tmp/prod-prev-commit
- echo "Previous production commit:$(cat /tmp/prod-prev-commit)"
# Sync code to production (exclude local-only files)
- rsync -az --delete
--exclude='.git'
--exclude='node_modules'
--exclude='frontend/node_modules'
--exclude='frontend/build'
--exclude='backend/uploads'
--exclude='*.log'
--exclude='*.db'
--exclude='.env'
--exclude='.compliance-staging'
${CI_PROJECT_DIR}/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/
# Copy built frontend
- rsync -az ${CI_PROJECT_DIR}/frontend/build/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/frontend/build/
# Install deps on production
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
- ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/frontend && npm ci --prefer-offline"
# Restart services — install systemd unit if not present
- ssh ${PROD_USER}@${PROD_HOST} "test -f /etc/systemd/system/cve-backend.service" || scp ${CI_PROJECT_DIR}/deploy/cve-backend-production.service ${PROD_USER}@${PROD_HOST}:/etc/systemd/system/cve-backend.service
- ssh ${PROD_USER}@${PROD_HOST} "systemctl daemon-reload && systemctl enable cve-backend && systemctl restart cve-backend"
- echo "Production deploy complete."
needs:
- build-frontend
- test-backend
- verify-staging
# =============================================================================
# STAGE 6: Post-deploy verification
# =============================================================================
# ---------------------------------------------------------------------------
# Staging health check
# ---------------------------------------------------------------------------
verify-staging:
stage: verify
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
when: on_success
script:
- echo "Verifying staging..."
- sleep 3
- |
for i in 1 2 3 4 5; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3100/api/health 2>/dev/null || echo "000")
if [ "$STATUS" = "200" ]; then
echo "Staging health check passed (attempt $i)"
break
fi
echo "Staging not ready (status: $STATUS), retrying... (attempt $i/5)"
sleep 3
done
if [ "$STATUS" != "200" ]; then
echo "FAILED: Staging health check failed after 5 attempts"
exit 1
fi
- echo "Staging verification passed."
needs:
- deploy-staging
# ---------------------------------------------------------------------------
# Production health check — rolls back on failure
# ---------------------------------------------------------------------------
verify-production:
stage: verify
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
when: on_success
script:
- echo "Verifying production..."
- sleep 3
- |
for i in 1 2 3 4 5 6 7 8 9 10; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://${PROD_HOST}:3001/api/health 2>/dev/null || echo "000")
if [ "$STATUS" = "200" ]; then
echo "Production health check passed (attempt $i)"
break
fi
echo "Production not ready (status: $STATUS), retrying... (attempt $i/10)"
sleep 3
done
if [ "$STATUS" != "200" ]; then
echo "FAILED: Production health check failed — initiating rollback"
PREV_COMMIT=$(cat /tmp/prod-prev-commit 2>/dev/null || echo "")
if [ -n "$PREV_COMMIT" ] && [ "$PREV_COMMIT" != "none" ]; then
echo "Rolling back to $PREV_COMMIT..."
# Re-sync the previous version
ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git checkout ${PREV_COMMIT} --force 2>/dev/null" || true
ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline"
ssh ${PROD_USER}@${PROD_HOST} "systemctl restart cve-backend"
echo "Rollback complete. Verify manually."
else
echo "No previous commit recorded — manual intervention required."
fi
exit 1
fi
- echo "Production verification passed."
needs:
- deploy-production
allow_failure: false