Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ vendor/
bin/*
*.env
*.env*
!monitoring/.env.observability.example

# Frontend
app/coverage/

# Local build output
tools/smoke/target/

# Import script and temp data
scripts/*
allprintings.csv.zip
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build dev persistence clean test test-api test-smoke-rust deploy-ui deploy-server docker-server docker-ui docker
.PHONY: build dev persistence clean test test-api test-smoke-rust monitoring-up monitoring-down deploy-ui deploy-server docker-server docker-ui docker

GOCMD=go
GOBUILD=$(GOCMD) build
Expand Down Expand Up @@ -68,5 +68,11 @@ import-csv:
persistence:
docker-compose -f dev.docker-compose.yml up -d postgres

monitoring-up:
cd monitoring && docker compose --env-file .env.observability up -d

monitoring-down:
cd monitoring && docker compose --env-file .env.observability down

confirm:
@echo -n "Are you sure? [y/N] " && read ans && [ $${ans:-N} = y ]
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,94 @@ The server emits structured JSON logs to stdout.
LOG_LEVEL=debug make run
```

For local stack runs via `dev.docker-compose.yml`, add basic container log controls:

- `web`, `server`, and `postgres` now use the `json-file` logging driver.
- log rotation is set to `10m` per file with `3` files retained.

Read logs per service:

```sh
docker compose -f dev.docker-compose.yml logs -f server
docker compose -f dev.docker-compose.yml logs -f web
docker compose -f dev.docker-compose.yml logs -f postgres
```

## Observability stack (local)

Use this stack to validate that the API is exporting `/prometheus` and that Grafana is wired to it.

1. Ensure API metrics are enabled and token-protected in the API env.

```sh
cd /root/.openclaw/workspace/vedh
sed -i 's/^METRICS_ENABLED=.*/METRICS_ENABLED=true/' .vedh.env
sed -i 's/^METRICS_TOKEN=.*/METRICS_TOKEN=dev-metrics-token/' .vedh.env
grep -q '^METRICS_ENABLED=' .vedh.env || echo 'METRICS_ENABLED=true' >> .vedh.env
grep -q '^METRICS_TOKEN=' .vedh.env || echo 'METRICS_TOKEN=dev-metrics-token' >> .vedh.env
```

2. Configure observability env.

```sh
cd /root/.openclaw/workspace/vedh/monitoring
cp .env.observability.example .env.observability
```

Set these values in `.env.observability`:

- `PROMETHEUS_TARGET` to the API host:port your monitoring stack should scrape
- default example: `host.docker.internal:8081` for the API in `dev.docker-compose.yml`
- `PROMETHEUS_BEARER_TOKEN` to match `METRICS_TOKEN` from `.vedh.env`
- `GRAFANA_ADMIN_USER` / `GRAFANA_ADMIN_PASSWORD` for UI login

3. Start the stack.

```sh
cd /root/.openclaw/workspace/vedh
make monitoring-up
```

If the API stack is already running, restart it after changing metrics env vars:

```sh
docker compose -f dev.docker-compose.yml up -d --force-recreate server
```

4. Verify Prometheus is running and scraping the API.

```sh
curl -sf http://localhost:9090/-/ready
curl -sf http://localhost:9090/api/v1/targets?state=any | grep -q '"job":"vedh-api"' && echo "scrape target configured"
curl -sf "http://localhost:9090/api/v1/query?query=up%7Bjob%3D%22vedh-api%22%7D" | grep -q '"value":\[' && echo "metrics query returned"
```

5. Verify Grafana has the Prometheus datasource and is connected.

```sh
export GRAFANA_ADMIN_USER=admin
export GRAFANA_ADMIN_PASSWORD=admin

curl -s -u "$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASSWORD" \
http://localhost:3000/api/health | grep -q '"database": "ok"' && echo "grafana up"

curl -s -u "$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASSWORD" \
"http://localhost:3000/api/datasources/name/Prometheus" | grep -q '"url":"http://prometheus:9090"' && echo "prometheus datasource configured"
```

Open `http://localhost:3000` and run an Explore query such as:

```txt
up{job="vedh-api"}
```

Stop the monitoring stack when done:

```sh
cd /root/.openclaw/workspace/vedh
make monitoring-down
```

## Stack

- Postgres stores application and card data.
Expand Down
97 changes: 88 additions & 9 deletions app/src/components/Card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,37 +125,91 @@ function onImgError(ev: Event) {
.card-tile {
display: grid;
gap: 0.35rem;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
background: linear-gradient(180deg, rgba(255, 244, 237, 0.08), rgba(26, 22, 21, 0.34));
border: 1px solid rgba(255, 244, 237, 0.11);
border-radius: 14px;
padding: 0.4rem;
cursor: grab;
position: relative;
transition: transform 140ms ease, box-shadow 160ms ease;
overflow: hidden;
box-shadow: 0 10px 24px rgba(21, 12, 9, 0.18);
transition: transform 140ms ease, box-shadow 160ms ease, border-color 140ms ease, background 160ms ease;
}
.card-tile:active { cursor: grabbing; }

.card-tile .label { font-size: 0.8rem; opacity: 0.9; }
.card-tile .label {
font-size: 0.8rem;
opacity: 0.95;
line-height: 1.25;
font-weight: 500;
color: var(--vedh-text);
text-wrap: balance;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
min-height: 2em;
}

.media {
position: relative;
}

.media::after {
content: '';
position: absolute;
inset: auto 0 0 0;
height: 34%;
background: linear-gradient(180deg, rgba(0,0,0,0), rgba(18, 12, 10, 0.38));
pointer-events: none;
}

.media.image .image,
.media.thumb .image {
width: 100%;
aspect-ratio: 0.714;
object-fit: cover;
border-radius: 6px;
border-radius: 10px;
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.28);
transition: transform 180ms ease, filter 180ms ease;
}
.media .image.placeholder {
background: linear-gradient(135deg, rgba(40,40,40,0.9), rgba(22,22,22,0.9));
color: rgba(255,255,255,0.7);
display: grid;
place-items: center;
min-height: 100%;
}

/* tapped rotates the card image only */
.tapped .media .image { transform: rotate(90deg); }
.tapped .media .image {
transform: rotate(90deg) scale(0.92);
transform-origin: center;
}

.tapped::before,
.facedown::before {
content: attr(data-state);
position: absolute;
top: 0.7rem;
right: 0.7rem;
z-index: 3;
padding: 0.2rem 0.42rem;
border-radius: 999px;
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--vedh-text);
background: rgba(17, 13, 12, 0.72);
border: 1px solid rgba(255, 244, 237, 0.16);
}

.tapped { --card-state-label: 'Tapped'; }
.facedown { --card-state-label: 'Face down'; }

.tapped::before { content: 'Tapped'; }
.facedown::before { content: 'Face down'; }

.overlays {
position: absolute;
Expand Down Expand Up @@ -215,12 +269,37 @@ function onImgError(ev: Event) {
}
.card-tile:hover {
transform: translateY(-4px);
box-shadow: 0 8px 22px rgba(0,0,0,0.55);
box-shadow: 0 16px 32px rgba(17, 10, 9, 0.36), 0 0 0 1px rgba(var(--vedh-primary-rgb), 0.12);
border-color: rgba(var(--vedh-primary-rgb), 0.28);
}

.card-tile:hover .image {
transform: scale(1.03);
filter: saturate(1.04) contrast(1.02);
}

.selected {
border-color: rgba(var(--vedh-secondary-rgb), 0.55);
box-shadow: 0 0 0 1px rgba(var(--vedh-secondary-rgb), 0.2), 0 12px 28px rgba(21, 12, 9, 0.22);
}

.highlight {
border-color: rgba(var(--vedh-primary-rgb), 0.58);
box-shadow: 0 0 0 1px rgba(var(--vedh-primary-rgb), 0.22), 0 0 24px rgba(var(--vedh-primary-rgb), 0.16);
}

/* sizes: adjust text and padding, image will fill width of grid cell */
.size-xs { padding: 0.25rem; }
.size-sm { padding: 0.35rem; }
.size-md { padding: 0.4rem; }
.size-lg { padding: 0.5rem; }
</style>

.size-xs .label,
.size-sm .label {
font-size: 0.72rem;
}

.size-lg .label {
font-size: 0.86rem;
}
</style>
Loading
Loading