Dockerized full-stack app for searching Elasticsearch skill indices. Decoupled 3-service architecture with a Neo-Brutalist UI.
| Service | Tech | Port |
|---|---|---|
| elasticsearch | Elasticsearch 9.4.3 (single-node) | 9200 |
| backend | Python 3.12 + FastAPI + elasticsearch-py 9.x | 8000 |
| frontend | React 18 + Vite + TailwindCSS (nginx) | 5173 |
frontend→backend: HTTP via host port (Vite build-timeVITE_API_BASE=http://localhost:8000)backend→elasticsearch: Docker internal network (skillnetbridge) athttp://elasticsearch:9200elasticsearchsecurity disabled (xpack.security.enabled=false)
| Endpoint | Method | Purpose |
|---|---|---|
/api/indices |
GET | List all non-system indices (cat.indices, excludes . prefixed) |
/api/fields/{index_name} |
GET | Return text-type fields from index mapping |
/api/search |
POST | Free-text multi_match with fuzziness + highlight |
/api/search/advanced |
POST | Bool query (must/should/must_not) with highlight |
Search payloads accept index, fields[], and query terms. Results include _score and <mark> highlighted description snippets.
App.jsx # State management, API calls, layout
├── SearchControls.jsx # Index dropdown (with ↻ refresh), field checkboxes
├── FreeTextSearch.jsx # Textarea + Search button
├── AdvancedSearch.jsx # 3-column must/should/must_not inputs
├── ResultsArea.jsx # Results grid wrapper
│ └── ResultCard.jsx # Card: name, repo, downloads, stars, score, highlighted desc
- Palette: Strict monochrome (black/white), yellow (
bg-yellow-300) for highlight marks - Borders:
border-4 border-blackon all containers - Shadows: Hard solid
shadow-brutal(4px 4px 0 0 black) - Typography:
font-black, uppercase headings, bold labels - Corners: No border-radius (sharp edges)
- Buttons: Black bg + white text when active, translate on hover/press for tactile feel
- Cards: White bg, thick border, score badge in top-right corner (black bg)
findskill/
├── docker-compose.yml # Orchestrates 3 services + skillnet + esdata volume
├── .env # STACK_VERSION, CLUSTER_NAME, LICENSE, ES_PORT, MEM_LIMIT
├── backend/
│ ├── Dockerfile # python:3.12-slim, pip install, uvicorn
│ ├── main.py # FastAPI app (4 endpoints)
│ └── requirements.txt # fastapi, uvicorn, elasticsearch>=9.0.0, pydantic
└── frontend/
├── Dockerfile # Multi-stage: node:20 build → nginx:alpine serve
├── nginx.conf # SPA fallback (try_files → index.html)
├── package.json # React 18, Vite 5, TailwindCSS 3
├── vite.config.js
├── tailwind.config.js # Custom shadow-brutal utilities
├── index.html
└── src/
├── main.jsx
├── index.css # @tailwind directives
├── App.jsx
└── components/
- User selects index → frontend calls
GET /api/fields/{index}→ checkboxes populate - User types query + clicks Search → frontend POSTs to
/api/searchor/api/search/advanced - Backend builds ES query with highlight, returns
{total, results[]} - Frontend renders ResultCard grid with
_scorebadges and yellow<mark>snippets