Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions frontend/goquery-ui/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
npm-debug.log
dist
.git
.gitignore
.DS_Store
9 changes: 9 additions & 0 deletions frontend/goquery-ui/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Global Query API base URL for development
GQ_API_BASE_URL=http://localhost:8145

# Comma-separated list of host resolver options shown in Settings
# Read by webpack dev server to generate /env.js as window.__ENV__.HOST_RESOLVER_TYPES
HOST_RESOLVER_TYPES=string,gethosts

# Enable Server-Sent Events by default on load
SSE_ON_LOAD=true
5 changes: 5 additions & 0 deletions frontend/goquery-ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
global-query_*_openapi.yaml

# deps and distribution
node_modules/
dist/
7 changes: 7 additions & 0 deletions frontend/goquery-ui/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}
43 changes: 43 additions & 0 deletions frontend/goquery-ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ---- build ----

FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
RUN --mount=type=cache,target=/home/nonroot/.npm npm ci --no-audit --no-fund
COPY . .
RUN npm run build

# ---- runtime (Caddy) ----

FROM caddy:2-alpine AS caddy-builder

# Use Alpine as the base for better security and fewer vulnerabilities
FROM alpine:3.19

# Install ca-certificates for HTTPS support
RUN apk add --no-cache ca-certificates

# Copy Caddy binary from the builder image
COPY --from=caddy-builder /usr/bin/caddy /usr/bin/caddy

# Create a non-root user for running Caddy
RUN addgroup -g 1001 caddy && \
adduser -D -u 1001 -G caddy -h /var/lib/caddy -s /sbin/nologin caddy
RUN mkdir -p /var/run/env /opt/app/www /etc/caddy /data /config && \
chown -R caddy:caddy /var/run/env /opt/app/www /etc/caddy /data /config

# static files go to a read-only dir
COPY --from=build --chown=caddy:caddy /app/dist /opt/app/www
## ensure protocol map is present as a static asset as well
COPY --from=build --chown=caddy:caddy /app/src/api/proto-map.json /opt/app/www/proto-map.json

# caddy config
COPY --chown=caddy:caddy deploy/Caddyfile /etc/caddy/Caddyfile

# Switch to non-root user
USER caddy

EXPOSE 5137

# default command
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]
119 changes: 119 additions & 0 deletions frontend/goquery-ui/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Simple Makefile for goquery-ui local development
# Focus: install deps, build bundle, watch, serve, docker setup

# configurable vars
NPM ?= npm
PORT ?= 8000
DIST_DIR := dist
WEBPACK := node_modules/.bin/webpack
# optional lock file (any one). If none exists we still allow install.
LOCKFILE := $(firstword $(wildcard package-lock.json npm-shrinkwrap.json pnpm-lock.yaml yarn.lock))
# OpenAPI spec version (can be overridden: `make VERSION=4.1.19 types`)
VERSION ?= 4.1.18
OPENAPI_SPEC := global-query_$(VERSION)_openapi.yaml
GENERATED_TYPES := src/api/generated.ts

# default target
.DEFAULT_GOAL := help

.PHONY: help
help:
@echo "Available targets:"
@echo " make install - install node dependencies"
@echo " make build - build bundle -> $(DIST_DIR)/bundle.js"
@echo " make watch - incremental rebuild (webpack --watch)"
@echo " make serve - serve static files on http://localhost:$(PORT) (python simple server)"
@echo " make dev - build then instructions to run watch+serve in 2 shells"
@echo " make clean - remove dist directory"
@echo " make format - format TypeScript/JavaScript files with prettier"
@echo " make generate-index - create a minimal index.html if missing"
@echo " make types - fetch OpenAPI $(VERSION) and generate TS types ($(GENERATED_TYPES))"
@echo " make regen - install + types + build"
@echo " make docker-dev - docker compose dev profile (hot reload)"
@echo " make docker-stop - stop docker compose dev profile"
@echo " make docker-logs - view docker compose dev logs"
@echo " make docker-prod - docker compose prod-like profile (Caddy)"
@echo " make docker-build - build Caddy image locally"

# install: re-run if package.json or chosen lockfile changes
node_modules: package.json $(LOCKFILE)
$(NPM) install
@touch node_modules

.PHONY: install
install: node_modules

# fetch OpenAPI spec from GitHub releases if missing or version changed
$(OPENAPI_SPEC):
@echo "Fetching OpenAPI spec version $(VERSION) ..."
curl -fsSL -o "$@" "https://github.com/els0r/goProbe/releases/download/v$(VERSION)/global-query_$(VERSION)_openapi.yaml"
@echo "Saved $@"

# openapi type generation (uses local dev dependency openapi-typescript)
$(GENERATED_TYPES): $(OPENAPI_SPEC) | install
$(NPM) exec -- openapi-typescript "$(OPENAPI_SPEC)" --output "$(GENERATED_TYPES)"

.PHONY: types
types: $(GENERATED_TYPES)

.PHONY: regen
regen: install types build

# generate a minimal index.html (idempotent)
index.html:
@[ -f index.html ] || echo '<!doctype html>\n<html>\n<head><meta charset="utf-8"><title>Goquery UI</title></head>\n<body>\n<div id="root"></div>\n<script src="dist/bundle.js"></script>\n</body>\n</html>' > index.html

.PHONY: generate-index
generate-index: index.html
@echo "index.html present"

.PHONY: build
build: install index.html
$(NPM) run build

.PHONY: watch
watch: install
$(WEBPACK) --watch

# simple static file server (CTRL+C to stop)
.PHONY: serve
serve: build
python3 -m http.server $(PORT)

.PHONY: dev
dev: build
@echo "DEPRECATED: preferably use \"make docker-dev\""
@echo "Run these in two terminals for live development:" \
"\n Terminal 1: make watch" \
"\n Terminal 2: make serve" \
"\nThen open http://localhost:$(PORT)"

.PHONY: clean
clean:
rm -rf $(DIST_DIR) $(GENERATED_TYPES)

.PHONY: format
format: install
$(NPM) exec -- prettier --write "src/**/*.{js,jsx,ts,tsx}"

.PHONY: docker-dev
docker-dev:
@echo "Starting Docker development server with hot reload..."
@echo "Access the application at http://localhost:5173"
docker compose --profile dev up --build

.PHONY: docker-stop
docker-stop:
docker compose --profile dev down

.PHONY: docker-logs
docker-logs:
docker compose --profile dev logs -f

.PHONY: docker-prod
docker-prod: types
docker compose --profile prod up --build

.PHONY: docker-build
docker-build: types
docker build -t goquery-ui:caddy-local .
122 changes: 122 additions & 0 deletions frontend/goquery-ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Goquery UI — Network Usage Explorer

Lightweight table + graph UI for exploring network flow data via the Global Query API.

## Prerequisites

- Node.js 18+
- npm 9+

## Install

```bash
npm ci
```

## Develop

Run with Docker Compose from this folder.

### Dev (hot reload via webpack-dev-server)

```bash
docker compose --profile dev up
```

Open <http://localhost:5173>

### Without Docker

Start webpack in watch mode and open `index.html` in a local static server (or your browser directly):

```bash
npm run dev
```

The bundle is emitted to `dist/` and `index.html` loads it.

## Build

```bash
npm run build
```

## Deployment

### Prod-like (Caddy serves built SPA with runtime env.js)

```bash
docker compose --profile prod up --build
```

Open <http://localhost:8080>

Optional .env (next to docker-compose.yml) to override runtime values:

```bash
GQ_API_BASE_URL=http://localhost:8081
HOST_RESOLVER_TYPES=dns,cache,local
SSE_ON_LOAD=true
```

Makefile shortcuts:

```bash
make docker-dev # same as compose dev profile
make docker-prod # same as compose prod profile
make docker-build # build Caddy image locally
```

## Using the UI

- Time range: quick presets (5m…30d) or set exact From/To.
- Hosts Query: free text filter (sets `query_hosts`).
- Interfaces: comma-separated list (sets `ifaces`).
- Attributes: choose which columns to group by (leave blank for all).
- Condition: free text filter (ANDs expressions like `proto=TCP and (dport=80 or dport=443)`).
- Sorting / Limit: choose metric and direction; limit applies to non-time queries.
- Run: executes the query and renders results.

### Interactions

- Graph tab
- Click an IP: opens service breakdown (proto/dport) with in/out and totals.
- Click an interface: opens services for that host/interface (scoped by host_id + iface).
- Click a host: shows per-interface totals for the host.
- Table tab
- Click a row: opens a temporal drilldown (attributes = time) scoped by row’s host_id + iface and filtered by sip/dip/dport/proto.
- Press Enter: opens temporal drilldown for the first row when no panel is open.

### Panels

- Escape closes any open panel; clicking outside also closes it.
- Unidirectional slots/cards are highlighted in red.
- Zero values are suppressed (no `0 B` / `0 pkts`).
- Temporal drilldown:
- Header shows `HOST — IFACE` with total Bytes/Packets.
- Only attributes visible in the table are shown in the header row.
- Consecutive timestamps on the same day show only HH:MM:SS (timezone suffix hidden).

## Saved Views & Export

- Save view: stores current params in localStorage. Load from the dropdown.
- Export CSV: exports the current table with selected columns and totals.

## Troubleshooting

- If the editor reports a spurious import error for a newly added view file, run a fresh typecheck:

```bash
npm run typecheck
```

- If the Global Query API changes, regenerate client types:

```bash
npm run generate:types
```

## Notes

- For development without a server, open `index.html` directly; API calls must resolve against your environment-provided endpoint if required by `client.ts`.
- This UI is a standalone package under `applications/network-observability/src/goquery-ui`.
53 changes: 53 additions & 0 deletions frontend/goquery-ui/deploy/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
# We're behind an Ingress; don't manage TLS here
auto_https off
admin off
}

:5137 {
root * /opt/app/www
encode zstd gzip

# Security headers (tune CSP to your domains!)
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy no-referrer-when-downgrade
Cross-Origin-Opener-Policy same-origin
Cross-Origin-Resource-Policy same-origin
Cross-Origin-Embedder-Policy require-corp
Permissions-Policy "geolocation=(), microphone=(), camera=()"
Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; connect-src 'self' http: https:; frame-ancestors 'none';"
}

# Cache policies
@assets {
path *.js *.css *.woff *.woff2 *.ttf *.otf *.eot
}
header @assets Cache-Control "public, max-age=31536000, immutable"

@images {
path *.png *.jpg *.jpeg *.gif *.webp *.svg
}
header @images Cache-Control "public, max-age=604800"

# runtime env.js (no-store) served from a writable volume
@envjs path /env.js
header @envjs Cache-Control "no-store"
header @envjs Content-Type "application/javascript"
route @envjs {
file_server {
root /var/run/env
}
}

# Serve static files first
file_server

# SPA fallback: if request isn't an existing file AND not env.js, serve index.html
@notFile {
not file
not path /env.js
}
rewrite @notFile /index.html
}
Loading
Loading