setup full stack of microservices for refinement
This commit is contained in:
484
README.md
484
README.md
@@ -1,45 +1,123 @@
|
|||||||
# Homelab Stack
|
# Homelab Stack
|
||||||
|
|
||||||
Self-hosted infrastructure stack with GPU support, S3-compatible storage, Git server with container registry, CI/CD, and research tools.
|
Self-hosted infrastructure stack with GPU support, S3-compatible storage, Git server with container registry, CI/CD, data lakehouse, observability, and research tools.
|
||||||
|
|
||||||
## Services
|
## Services
|
||||||
|
|
||||||
|
All services are accessible via Traefik reverse proxy on port 80.
|
||||||
|
|
||||||
| Service | URL | Description |
|
| Service | URL | Description |
|
||||||
|---------|-----|-------------|
|
|---------|-----|-------------|
|
||||||
| Gitea | http://gitea.home.fhirworx.io:3000 | Git server with LFS + container registry |
|
| Dashboard | http://home.fhirworx.io | Homelab command center |
|
||||||
| Woodpecker CI | http://ci.home.fhirworx.io:8000 | CI/CD pipelines |
|
| Traefik | http://traefik.home.fhirworx.io | Reverse proxy dashboard |
|
||||||
| RustFS | http://s3.home.fhirworx.io:9000 | S3-compatible object storage |
|
| Gitea | http://gitea.home.fhirworx.io | Git server with LFS + container registry |
|
||||||
| RustFS Console | http://s3.home.fhirworx.io:9001 | Storage management UI |
|
| Woodpecker CI | http://ci.home.fhirworx.io | CI/CD pipelines |
|
||||||
| Notebooks | http://notebooks.home.fhirworx.io:2718 | GPU-accelerated Jupyter |
|
| RustFS Console | http://minio.home.fhirworx.io | S3 storage management UI |
|
||||||
| Zotero | http://zotero.home.fhirworx.io:8080 | Reference manager with VNC |
|
| RustFS S3 API | http://s3.home.fhirworx.io | S3-compatible object storage |
|
||||||
|
| Notebooks | http://notebooks.home.fhirworx.io | GPU-accelerated Marimo notebooks |
|
||||||
|
| Zotero | http://zotero.home.fhirworx.io | Reference manager with VNC |
|
||||||
|
| Nessie | http://nessie.home.fhirworx.io | Git-like data catalog for Iceberg |
|
||||||
|
| Trino | http://trino.home.fhirworx.io | Distributed SQL query engine |
|
||||||
|
| Grafana | http://grafana.home.fhirworx.io | Observability dashboards |
|
||||||
|
| Prometheus | http://prometheus.home.fhirworx.io | Metrics collection |
|
||||||
|
| Jaeger | http://jaeger.home.fhirworx.io | Distributed tracing |
|
||||||
|
| Loki | http://loki.home.fhirworx.io | Log aggregation |
|
||||||
| PostgreSQL | localhost:5432 | Database (Bitnami hardened) |
|
| PostgreSQL | localhost:5432 | Database (Bitnami hardened) |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ intrastack network │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Traefik :80/:443 │ │
|
||||||
|
│ │ Reverse Proxy + Load Balancer + OTEL Tracing │ │
|
||||||
|
│ └───────────────────────────────┬─────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌─────────────────────────────┼─────────────────────────────┐ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ▼ ▼ ▼ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ Gitea │ │Woodpecker│ │ Notebooks│ │ Zotero │ │ Dashboard│ │
|
||||||
|
│ │ :3000 │ │ :8000 │ │ :2718 │ │ :8080 │ │ :80 │ │
|
||||||
|
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────────┘ └──────────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ └─────────────┼─────────────┘ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ PostgreSQL :5432 │ │
|
||||||
|
│ │ (gitea, woodpecker, nessie) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Data Lakehouse │ │
|
||||||
|
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────────────────┐ │ │
|
||||||
|
│ │ │ Trino │────▶│ Nessie │────▶│ RustFS │ │ │
|
||||||
|
│ │ │ :8080 │ │ :19120 │ │ S3 :9000 | Console :9001 │ │ │
|
||||||
|
│ │ └──────────┘ └──────────┘ │ Buckets: gitea, lakehouse │ │ │
|
||||||
|
│ │ Iceberg REST Catalog └──────────────────────────────┘ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Observability Stack │ │
|
||||||
|
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||||
|
│ │ │ Grafana │────▶│Prometheus│ │ Jaeger │◀────│ Loki │ │ │
|
||||||
|
│ │ │ :3000 │ │ :9090 │ │ :16686 │ │ :3100 │ │ │
|
||||||
|
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
|
||||||
|
│ │ │ ▲ ▲ │ │
|
||||||
|
│ │ └──────── Dashboards ───────────────┴───────────────┘ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Docker with rootless mode
|
- Docker with rootless mode
|
||||||
- NVIDIA GPU with container toolkit
|
- NVIDIA GPU with container toolkit
|
||||||
- Fix for rootless NVIDIA containers:
|
- iptables for port 80 redirect (rootless Docker can't bind privileged ports)
|
||||||
```bash
|
|
||||||
sudo sed -i 's/#no-cgroups = false/no-cgroups = true/' /etc/nvidia-container-runtime/config.toml
|
Fix for rootless NVIDIA containers:
|
||||||
```
|
```bash
|
||||||
|
sudo sed -i 's/#no-cgroups = false/no-cgroups = true/' /etc/nvidia-container-runtime/config.toml
|
||||||
|
```
|
||||||
|
|
||||||
## DNS Setup
|
## DNS Setup
|
||||||
|
|
||||||
Add to `/etc/hosts` (replace with your server's LAN IP):
|
Add to `/etc/hosts` (replace IP with your server's LAN IP):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat << 'EOF' | sudo tee -a /etc/hosts
|
# Homelab Stack
|
||||||
|
192.168.1.192 home.fhirworx.io
|
||||||
# homelab stack
|
192.168.1.192 dashboard.home.fhirworx.io
|
||||||
|
192.168.1.192 traefik.home.fhirworx.io
|
||||||
192.168.1.192 gitea.home.fhirworx.io
|
192.168.1.192 gitea.home.fhirworx.io
|
||||||
192.168.1.192 ci.home.fhirworx.io
|
192.168.1.192 ci.home.fhirworx.io
|
||||||
192.168.1.192 s3.home.fhirworx.io
|
|
||||||
192.168.1.192 notebooks.home.fhirworx.io
|
192.168.1.192 notebooks.home.fhirworx.io
|
||||||
192.168.1.192 zotero.home.fhirworx.io
|
192.168.1.192 zotero.home.fhirworx.io
|
||||||
EOF
|
192.168.1.192 nessie.home.fhirworx.io
|
||||||
|
192.168.1.192 trino.home.fhirworx.io
|
||||||
|
192.168.1.192 minio.home.fhirworx.io
|
||||||
|
192.168.1.192 s3.home.fhirworx.io
|
||||||
|
192.168.1.192 grafana.home.fhirworx.io
|
||||||
|
192.168.1.192 prometheus.home.fhirworx.io
|
||||||
|
192.168.1.192 jaeger.home.fhirworx.io
|
||||||
|
192.168.1.192 loki.home.fhirworx.io
|
||||||
```
|
```
|
||||||
|
|
||||||
Or configure these records on your router/DNS server.
|
## Port 80 Redirect
|
||||||
|
|
||||||
|
Rootless Docker cannot bind to privileged ports. Use iptables to redirect port 80 to Traefik's port 8880:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8880
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -o lo -j REDIRECT --to-port 8880
|
||||||
|
|
||||||
|
# Persist across reboots
|
||||||
|
sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
|
||||||
|
```
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@@ -59,6 +137,11 @@ WOODPECKER_DB_PASSWORD=<woodpecker_db_password>
|
|||||||
WOODPECKER_AGENT_SECRET=<generated_secret>
|
WOODPECKER_AGENT_SECRET=<generated_secret>
|
||||||
WOODPECKER_GITEA_CLIENT=<oauth_client_id>
|
WOODPECKER_GITEA_CLIENT=<oauth_client_id>
|
||||||
WOODPECKER_GITEA_SECRET=<oauth_client_secret>
|
WOODPECKER_GITEA_SECRET=<oauth_client_secret>
|
||||||
|
|
||||||
|
# Nessie Data Lake
|
||||||
|
NESSIE_DB_PASSWORD=<nessie_db_password>
|
||||||
|
NESSIE_S3_ACCESS_KEY=<nessie_s3_user>
|
||||||
|
NESSIE_S3_SECRET_KEY=<nessie_s3_password>
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate agent secret:
|
Generate agent secret:
|
||||||
@@ -69,15 +152,19 @@ openssl rand -hex 32
|
|||||||
### 2. Start Core Services
|
### 2. Start Core Services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d postgres rustfs
|
docker compose up -d postgres rustfs traefik
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Configure RustFS
|
### 3. Configure RustFS
|
||||||
|
|
||||||
1. Access console at http://s3.home.fhirworx.io:9001
|
1. Access console at http://minio.home.fhirworx.io
|
||||||
2. Create user `git` with S3 credentials matching `GITEA_S3_ACCESS_KEY` and `GITEA_S3_SECRET_KEY`
|
2. Create user `git` with S3 credentials matching `GITEA_S3_ACCESS_KEY` and `GITEA_S3_SECRET_KEY`
|
||||||
3. Create buckets: `gitea`, `gitea-lfs`, `gitea-packages`
|
3. Create buckets: `gitea`, `gitea-lfs`, `gitea-packages`
|
||||||
4. Apply IAM policy to `git` user:
|
4. Create user `nessie` with S3 credentials matching `NESSIE_S3_ACCESS_KEY` and `NESSIE_S3_SECRET_KEY`
|
||||||
|
5. Create bucket: `lakehouse`
|
||||||
|
6. Apply IAM policies to users (see below)
|
||||||
|
|
||||||
|
**Gitea IAM Policy:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -115,6 +202,36 @@ docker compose up -d postgres rustfs
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Nessie IAM Policy:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:GetBucketLocation",
|
||||||
|
"s3:ListBucket",
|
||||||
|
"s3:ListBucketMultipartUploads"
|
||||||
|
],
|
||||||
|
"Resource": ["arn:aws:s3:::lakehouse"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:DeleteObject",
|
||||||
|
"s3:ListMultipartUploadParts",
|
||||||
|
"s3:AbortMultipartUpload"
|
||||||
|
],
|
||||||
|
"Resource": ["arn:aws:s3:::lakehouse/*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 4. Configure PostgreSQL
|
### 4. Configure PostgreSQL
|
||||||
|
|
||||||
Create databases and users:
|
Create databases and users:
|
||||||
@@ -127,68 +244,242 @@ docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREA
|
|||||||
# Woodpecker database
|
# Woodpecker database
|
||||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE DATABASE woodpecker;"
|
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE DATABASE woodpecker;"
|
||||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE USER woodpecker WITH PASSWORD '<woodpecker_db_password>'; ALTER DATABASE woodpecker OWNER TO woodpecker;"
|
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE USER woodpecker WITH PASSWORD '<woodpecker_db_password>'; ALTER DATABASE woodpecker OWNER TO woodpecker;"
|
||||||
|
|
||||||
|
# Nessie database
|
||||||
|
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE USER nessie WITH PASSWORD '<nessie_db_password>';"
|
||||||
|
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE DATABASE nessie OWNER nessie;"
|
||||||
|
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE nessie TO nessie;"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Start Gitea
|
### 5. Start All Services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d gitea
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Configure Gitea
|
### 6. Configure Gitea
|
||||||
|
|
||||||
1. Complete initial setup at http://gitea.home.fhirworx.io:3000
|
1. Complete initial setup at http://gitea.home.fhirworx.io
|
||||||
2. Create organization `homelab`
|
2. Create organization `homelab`
|
||||||
3. Create repository `stack`
|
3. Create repository `stack`
|
||||||
4. Generate API token for container registry access
|
4. Generate API token for container registry access
|
||||||
5. Create OAuth2 application for Woodpecker:
|
5. Create OAuth2 application for Woodpecker:
|
||||||
- Name: `Woodpecker CI`
|
- Name: `Woodpecker CI`
|
||||||
- Redirect URI: `http://ci.home.fhirworx.io:8000/authorize`
|
- Redirect URI: `http://ci.home.fhirworx.io/authorize`
|
||||||
- Copy Client ID and Secret to `.env`
|
- Copy Client ID and Secret to `.env`
|
||||||
|
|
||||||
### 7. Start Woodpecker CI
|
### 7. Configure Woodpecker Secrets
|
||||||
|
|
||||||
```bash
|
Add secrets in Woodpecker UI (http://ci.home.fhirworx.io):
|
||||||
docker compose up -d woodpecker-server woodpecker-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
Add secrets in Woodpecker UI (http://ci.home.fhirworx.io:8000):
|
|
||||||
- `registry_user` - Gitea username
|
- `registry_user` - Gitea username
|
||||||
- `registry_pass` - Gitea token
|
- `registry_pass` - Gitea token
|
||||||
|
|
||||||
### 8. SSH and GPG Keys
|
### 8. Verify Services
|
||||||
|
|
||||||
Generate SSH key:
|
|
||||||
```bash
|
```bash
|
||||||
ssh-keygen -t ed25519 -C "user@homelab" -f ~/.ssh/gitea_ed25519 -N ""
|
# Check Traefik routes
|
||||||
|
curl -s http://localhost:8081/api/http/routers | jq -r '.[].name'
|
||||||
|
|
||||||
|
# Check Nessie
|
||||||
|
curl http://nessie.home.fhirworx.io/api/v2/config
|
||||||
|
|
||||||
|
# Check Trino
|
||||||
|
docker exec -it trino trino --execute "SHOW CATALOGS"
|
||||||
|
|
||||||
|
# Check Prometheus targets
|
||||||
|
curl -s http://prometheus.home.fhirworx.io/api/v1/targets | jq '.data.activeTargets[].labels.job'
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate GPG key:
|
## Observability
|
||||||
```bash
|
|
||||||
gpg --batch --gen-key <<EOF
|
The stack includes a complete observability solution:
|
||||||
Key-Type: eddsa
|
|
||||||
Key-Curve: ed25519
|
### Components
|
||||||
Key-Usage: sign
|
|
||||||
Subkey-Type: ecdh
|
| Component | Purpose | Metrics |
|
||||||
Subkey-Curve: cv25519
|
|-----------|---------|---------|
|
||||||
Subkey-Usage: encrypt
|
| **Prometheus** | Metrics collection and storage | Scrapes all services |
|
||||||
Name-Real: <your_name>
|
| **Grafana** | Visualization and dashboards | Homelab Overview, Traefik |
|
||||||
Name-Email: <your_email>
|
| **Jaeger** | Distributed tracing | OTLP collector |
|
||||||
Expire-Date: 2y
|
| **Loki** | Log aggregation | Container logs via Promtail |
|
||||||
%no-protection
|
| **Promtail** | Log shipper | Scrapes Docker container logs |
|
||||||
%commit
|
|
||||||
EOF
|
### Tracing
|
||||||
|
|
||||||
|
Services instrumented with OpenTelemetry:
|
||||||
|
|
||||||
|
| Service | Tracing |
|
||||||
|
|---------|---------|
|
||||||
|
| Traefik | Native OTLP export |
|
||||||
|
| Nessie | Quarkus OTEL |
|
||||||
|
| Trino | Native OTEL |
|
||||||
|
|
||||||
|
All traces flow to Jaeger via OTLP gRPC (port 4317).
|
||||||
|
|
||||||
|
### Dashboards
|
||||||
|
|
||||||
|
Pre-provisioned Grafana dashboards:
|
||||||
|
- **Homelab Overview** - Service health, logs, traces
|
||||||
|
- **Traefik** - Request rates, latencies, errors by router/service
|
||||||
|
|
||||||
|
### Prometheus Targets
|
||||||
|
|
||||||
|
| Job | Endpoint |
|
||||||
|
|-----|----------|
|
||||||
|
| prometheus | localhost:9090 |
|
||||||
|
| traefik | traefik:8080/metrics |
|
||||||
|
| jaeger | jaeger:14269 |
|
||||||
|
| loki | loki:3100 |
|
||||||
|
| nessie | nessie:19120/q/metrics |
|
||||||
|
| trino | trino:8080/v1/status |
|
||||||
|
|
||||||
|
## Data Lakehouse
|
||||||
|
|
||||||
|
The stack includes a complete data lakehouse built on Apache Iceberg:
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
| Component | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| **Nessie** | Git-like version control for data (branches, merges, time travel) |
|
||||||
|
| **Apache Iceberg** | Open table format with ACID transactions, schema evolution |
|
||||||
|
| **Trino** | Distributed SQL query engine with JDBC/ODBC support |
|
||||||
|
| **RustFS** | S3 storage backend (lakehouse bucket) |
|
||||||
|
| **PyIceberg** | Python client for Iceberg tables in notebooks |
|
||||||
|
|
||||||
|
### Query Access
|
||||||
|
|
||||||
|
| Method | Use Case | Connection |
|
||||||
|
|--------|----------|------------|
|
||||||
|
| **Trino CLI** | Ad-hoc SQL queries | `docker exec -it trino trino` |
|
||||||
|
| **Trino JDBC** | External tools (DBeaver, etc.) | `jdbc:trino://trino.home.fhirworx.io/iceberg` |
|
||||||
|
| **Trino Web UI** | Query monitoring | http://trino.home.fhirworx.io |
|
||||||
|
| **PyIceberg** | Python notebooks | Direct to Nessie REST API |
|
||||||
|
| **DuckDB** | Fast local queries | Read Iceberg via PyArrow |
|
||||||
|
|
||||||
|
### Example: Create a Table
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Connect to Trino
|
||||||
|
docker exec -it trino trino
|
||||||
|
|
||||||
|
-- Create a schema
|
||||||
|
CREATE SCHEMA iceberg.analytics;
|
||||||
|
|
||||||
|
-- Create a partitioned table
|
||||||
|
CREATE TABLE iceberg.analytics.events (
|
||||||
|
event_id VARCHAR,
|
||||||
|
event_time TIMESTAMP(6) WITH TIME ZONE,
|
||||||
|
user_id VARCHAR,
|
||||||
|
event_type VARCHAR,
|
||||||
|
properties MAP(VARCHAR, VARCHAR)
|
||||||
|
)
|
||||||
|
WITH (
|
||||||
|
format = 'PARQUET',
|
||||||
|
partitioning = ARRAY['day(event_time)']
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Insert data
|
||||||
|
INSERT INTO iceberg.analytics.events VALUES
|
||||||
|
('evt-001', CURRENT_TIMESTAMP, 'user-1', 'page_view', MAP(ARRAY['page'], ARRAY['/home']));
|
||||||
|
|
||||||
|
-- Query data
|
||||||
|
SELECT * FROM iceberg.analytics.events;
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure Git:
|
## Traefik Reverse Proxy
|
||||||
```bash
|
|
||||||
git config --global user.name "<your_name>"
|
All HTTP services are routed through Traefik using file-based configuration (not Docker labels).
|
||||||
git config --global user.email "<your_email>"
|
|
||||||
git config --global user.signingkey <GPG_KEY_ID>
|
### Configuration
|
||||||
git config --global commit.gpgsign true
|
|
||||||
|
Routes are defined in `traefik/dynamic/services.yml`. To add a new service:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
myservice:
|
||||||
|
rule: "Host(`myservice.home.fhirworx.io`)"
|
||||||
|
service: myservice
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
services:
|
||||||
|
myservice:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://myservice:8080"
|
||||||
```
|
```
|
||||||
|
|
||||||
Add to `~/.ssh/config`:
|
### Features
|
||||||
|
|
||||||
|
- **OTEL Tracing** - All requests traced to Jaeger
|
||||||
|
- **Prometheus Metrics** - Request rates, latencies by router/service
|
||||||
|
- **File Provider** - Routes defined in YAML, hot-reloaded
|
||||||
|
- **Dashboard** - http://traefik.home.fhirworx.io
|
||||||
|
|
||||||
|
### TCP/UDP Routing
|
||||||
|
|
||||||
|
Traefik can also route TCP and UDP traffic. See `traefik/dynamic/` for examples.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
stack/
|
||||||
|
├── compose.yml # Docker Compose configuration
|
||||||
|
├── .env # Environment variables (not in git)
|
||||||
|
├── .woodpecker.yml # CI/CD pipeline
|
||||||
|
├── README.md # This file
|
||||||
|
├── data/ # Shared data directory
|
||||||
|
├── gitea/
|
||||||
|
│ └── custom/
|
||||||
|
│ └── public/assets/css/
|
||||||
|
│ └── theme-nes-sports.css
|
||||||
|
├── grafana/
|
||||||
|
│ ├── provisioning/
|
||||||
|
│ │ ├── dashboards/
|
||||||
|
│ │ │ └── dashboards.yml
|
||||||
|
│ │ └── datasources/
|
||||||
|
│ │ └── datasources.yml
|
||||||
|
│ └── dashboards/
|
||||||
|
│ ├── homelab-overview.json
|
||||||
|
│ └── traefik.json
|
||||||
|
├── loki/
|
||||||
|
│ ├── loki-config.yml
|
||||||
|
│ └── promtail-config.yml
|
||||||
|
├── nginx/
|
||||||
|
│ ├── nginx.conf # Dashboard nginx config
|
||||||
|
│ └── index.html # Dashboard HTML
|
||||||
|
├── notebooks/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── pyproject.toml
|
||||||
|
│ ├── .marimo.toml
|
||||||
|
│ └── retro-arcade.css
|
||||||
|
├── prometheus/
|
||||||
|
│ └── prometheus.yml
|
||||||
|
├── traefik/
|
||||||
|
│ ├── traefik.yml # Static configuration
|
||||||
|
│ └── dynamic/
|
||||||
|
│ ├── middlewares.yml # Rate limiting, security headers
|
||||||
|
│ └── services.yml # All HTTP routes
|
||||||
|
├── trino/
|
||||||
|
│ └── etc/
|
||||||
|
│ ├── config.properties
|
||||||
|
│ ├── jvm.config
|
||||||
|
│ ├── node.properties
|
||||||
|
│ └── catalog/
|
||||||
|
│ └── iceberg.properties
|
||||||
|
├── woodpecker/
|
||||||
|
│ └── custom.css
|
||||||
|
└── zotero/
|
||||||
|
├── Dockerfile
|
||||||
|
└── data/
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSH Access
|
||||||
|
|
||||||
|
Configure Git SSH access in `~/.ssh/config`:
|
||||||
|
|
||||||
```
|
```
|
||||||
Host gitea
|
Host gitea
|
||||||
HostName gitea.home.fhirworx.io
|
HostName gitea.home.fhirworx.io
|
||||||
@@ -198,79 +489,36 @@ Host gitea
|
|||||||
IdentitiesOnly yes
|
IdentitiesOnly yes
|
||||||
```
|
```
|
||||||
|
|
||||||
Add host key:
|
Generate and add SSH key:
|
||||||
```bash
|
```bash
|
||||||
|
ssh-keygen -t ed25519 -C "user@homelab" -f ~/.ssh/gitea_ed25519 -N ""
|
||||||
ssh-keyscan -p 2222 gitea.home.fhirworx.io >> ~/.ssh/known_hosts
|
ssh-keyscan -p 2222 gitea.home.fhirworx.io >> ~/.ssh/known_hosts
|
||||||
```
|
```
|
||||||
|
|
||||||
Add SSH and GPG public keys to Gitea: http://gitea.home.fhirworx.io:3000/user/settings/keys
|
Add public key to Gitea: http://gitea.home.fhirworx.io/user/settings/keys
|
||||||
|
|
||||||
### 9. Deploy Key (for CI/CD)
|
## Container Registry
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh-keygen -t ed25519 -C "deploy@homelab/stack" -f .deploy_key -N ""
|
|
||||||
```
|
|
||||||
|
|
||||||
Add `.deploy_key.pub` to repository deploy keys: http://gitea.home.fhirworx.io:3000/homelab/stack/settings/keys
|
|
||||||
|
|
||||||
### 10. Push to Gitea
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git remote add gitea gitea:homelab/stack.git
|
|
||||||
git push -u gitea main
|
|
||||||
```
|
|
||||||
|
|
||||||
### 11. Container Registry
|
|
||||||
|
|
||||||
Login:
|
Login:
|
||||||
```bash
|
```bash
|
||||||
echo "<GITEA_TOKEN>" | docker login localhost:3000 -u <username> --password-stdin
|
echo "<GITEA_TOKEN>" | docker login gitea.home.fhirworx.io -u <username> --password-stdin
|
||||||
```
|
```
|
||||||
|
|
||||||
Tag and push images:
|
Tag and push:
|
||||||
```bash
|
```bash
|
||||||
docker tag <image>:latest localhost:3000/homelab/<image>:latest
|
docker tag <image>:latest gitea.home.fhirworx.io/homelab/<image>:latest
|
||||||
docker push localhost:3000/homelab/<image>:latest
|
docker push gitea.home.fhirworx.io/homelab/<image>:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### 12. Start All Services
|
## Ports Reference
|
||||||
|
|
||||||
```bash
|
| Port | Service | Notes |
|
||||||
docker compose up -d
|
|------|---------|-------|
|
||||||
```
|
| 80 | Traefik HTTP | iptables redirect to 8880 |
|
||||||
|
| 443 | Traefik HTTPS | iptables redirect to 8443 |
|
||||||
## Container Registry Images
|
| 2222 | Gitea SSH | Direct access |
|
||||||
|
| 4317 | Jaeger OTLP gRPC | Direct access for external traces |
|
||||||
All images are hosted in the Gitea container registry:
|
| 4318 | Jaeger OTLP HTTP | Direct access for external traces |
|
||||||
|
| 5432 | PostgreSQL | Direct access |
|
||||||
- `localhost:3000/homelab/postgresql:latest`
|
| 9000 | RustFS S3 API | Also via s3.home.fhirworx.io |
|
||||||
- `localhost:3000/homelab/rustfs:latest`
|
| 9001 | RustFS Console | Also via minio.home.fhirworx.io |
|
||||||
- `localhost:3000/homelab/gitea:latest-rootless`
|
|
||||||
- `localhost:3000/homelab/woodpecker-server:latest`
|
|
||||||
- `localhost:3000/homelab/woodpecker-agent:latest`
|
|
||||||
- `localhost:3000/homelab/notebooks:latest`
|
|
||||||
- `localhost:3000/homelab/zotero:latest`
|
|
||||||
|
|
||||||
## CI/CD Pipeline
|
|
||||||
|
|
||||||
The `.woodpecker.yml` pipeline:
|
|
||||||
|
|
||||||
1. **validate** - Validates docker compose config
|
|
||||||
2. **build-notebooks** - Builds notebooks image (on changes to `notebooks/`)
|
|
||||||
3. **build-zotero** - Builds zotero image (on changes to `zotero/`)
|
|
||||||
4. **push-images** - Pushes to container registry (on main branch)
|
|
||||||
5. **deploy** - Pulls and restarts services (on main branch)
|
|
||||||
|
|
||||||
## Endpoints
|
|
||||||
|
|
||||||
| Service | URL |
|
|
||||||
|---------|-----|
|
|
||||||
| Gitea Web | http://gitea.home.fhirworx.io:3000 |
|
|
||||||
| Gitea SSH | ssh://git@gitea.home.fhirworx.io:2222 |
|
|
||||||
| Container Registry | http://gitea.home.fhirworx.io:3000/v2/ |
|
|
||||||
| Woodpecker CI | http://ci.home.fhirworx.io:8000 |
|
|
||||||
| RustFS S3 API | http://s3.home.fhirworx.io:9000 |
|
|
||||||
| RustFS Console | http://s3.home.fhirworx.io:9001 |
|
|
||||||
| PostgreSQL | localhost:5432 |
|
|
||||||
| Notebooks | http://notebooks.home.fhirworx.io:2718 |
|
|
||||||
| Zotero | http://zotero.home.fhirworx.io:8080 |
|
|
||||||
|
|||||||
212
compose.yml
212
compose.yml
@@ -4,6 +4,24 @@ networks:
|
|||||||
internal: false
|
internal: false
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:v3.3
|
||||||
|
container_name: traefik
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
ports:
|
||||||
|
- "8880:80"
|
||||||
|
- "8443:443"
|
||||||
|
- "8081:8080"
|
||||||
|
volumes:
|
||||||
|
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
||||||
|
- ./traefik/dynamic:/etc/traefik/dynamic:ro
|
||||||
|
environment:
|
||||||
|
- OTEL_SERVICE_NAME=traefik
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
rustfs:
|
rustfs:
|
||||||
image: localhost:3000/homelab/rustfs:latest
|
image: localhost:3000/homelab/rustfs:latest
|
||||||
container_name: rustfs
|
container_name: rustfs
|
||||||
@@ -40,7 +58,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
gitea:
|
gitea:
|
||||||
image: localhost:3000/homelab/gitea:latest-rootless
|
image: gitea/gitea:1.25.4-rootless
|
||||||
container_name: gitea
|
container_name: gitea
|
||||||
networks:
|
networks:
|
||||||
- intrastack
|
- intrastack
|
||||||
@@ -70,53 +88,53 @@ services:
|
|||||||
- GITEA__packages__MINIO_BUCKET=gitea-packages
|
- GITEA__packages__MINIO_BUCKET=gitea-packages
|
||||||
- GITEA__packages__MINIO_USE_SSL=false
|
- GITEA__packages__MINIO_USE_SSL=false
|
||||||
- GITEA__server__DOMAIN=gitea.home.fhirworx.io
|
- GITEA__server__DOMAIN=gitea.home.fhirworx.io
|
||||||
- GITEA__server__ROOT_URL=http://gitea.home.fhirworx.io:3000/
|
- GITEA__server__ROOT_URL=http://gitea.home.fhirworx.io/
|
||||||
- GITEA__server__SSH_DOMAIN=gitea.home.fhirworx.io
|
- GITEA__server__SSH_DOMAIN=gitea.home.fhirworx.io
|
||||||
- GITEA__webhook__ALLOWED_HOST_LIST=woodpecker-server,ci.home.fhirworx.io,192.168.1.192,172.19.0.0/16
|
- GITEA__webhook__ALLOWED_HOST_LIST=woodpecker-server,ci.home.fhirworx.io,192.168.1.192,172.19.0.0/16
|
||||||
|
- GITEA__ui__THEMES=gitea-auto,gitea-light,gitea-dark,nes-sports
|
||||||
|
- GITEA__ui__DEFAULT_THEME=nes-sports
|
||||||
volumes:
|
volumes:
|
||||||
- gitea_data:/var/lib/gitea
|
- gitea_data:/var/lib/gitea
|
||||||
- gitea_config:/etc/gitea
|
- gitea_config:/etc/gitea
|
||||||
|
- ./gitea/custom:/var/lib/gitea/custom:ro
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
|
||||||
- "2222:2222"
|
- "2222:2222"
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- rustfs
|
- rustfs
|
||||||
security_opt:
|
|
||||||
- no-new-privileges:true
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
woodpecker-server:
|
woodpecker-server:
|
||||||
image: woodpeckerci/woodpecker-server:v3.3.0
|
image: woodpeckerci/woodpecker-server:v3.13.0
|
||||||
container_name: woodpecker-server
|
container_name: woodpecker-server
|
||||||
networks:
|
networks:
|
||||||
- intrastack
|
- intrastack
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "gitea.home.fhirworx.io:192.168.1.192"
|
- "gitea.home.fhirworx.io:192.168.1.192"
|
||||||
environment:
|
environment:
|
||||||
- WOODPECKER_HOST=http://ci.home.fhirworx.io:8000
|
- WOODPECKER_HOST=http://ci.home.fhirworx.io
|
||||||
- WOODPECKER_GRPC_ADDR=:9000
|
- WOODPECKER_GRPC_ADDR=:9000
|
||||||
- WOODPECKER_LOG_LEVEL=debug
|
- WOODPECKER_LOG_LEVEL=debug
|
||||||
- WOODPECKER_ADMIN=kert
|
- WOODPECKER_ADMIN=kert
|
||||||
- WOODPECKER_OPEN=true
|
- WOODPECKER_OPEN=true
|
||||||
- WOODPECKER_GITEA=true
|
- WOODPECKER_GITEA=true
|
||||||
- WOODPECKER_GITEA_URL=http://gitea.home.fhirworx.io:3000
|
- WOODPECKER_GITEA_URL=http://gitea.home.fhirworx.io
|
||||||
- WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
|
- WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
|
||||||
- WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}
|
- WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}
|
||||||
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
|
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
|
||||||
- WOODPECKER_DATABASE_DRIVER=postgres
|
- WOODPECKER_DATABASE_DRIVER=postgres
|
||||||
- WOODPECKER_DATABASE_DATASOURCE=postgres://woodpecker:${WOODPECKER_DB_PASSWORD}@postgres:5432/woodpecker?sslmode=disable
|
- WOODPECKER_DATABASE_DATASOURCE=postgres://woodpecker:${WOODPECKER_DB_PASSWORD}@postgres:5432/woodpecker?sslmode=disable
|
||||||
|
- WOODPECKER_CUSTOM_CSS_FILE=/etc/woodpecker/custom.css
|
||||||
volumes:
|
volumes:
|
||||||
- woodpecker_data:/var/lib/woodpecker
|
- woodpecker_data:/var/lib/woodpecker
|
||||||
ports:
|
- ./woodpecker/custom.css:/etc/woodpecker/custom.css:ro
|
||||||
- "8000:8000"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- gitea
|
- gitea
|
||||||
- postgres
|
- postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
woodpecker-agent:
|
woodpecker-agent:
|
||||||
image: woodpeckerci/woodpecker-agent:v3.3.0
|
image: woodpeckerci/woodpecker-agent:v3.13.0
|
||||||
container_name: woodpecker-agent
|
container_name: woodpecker-agent
|
||||||
networks:
|
networks:
|
||||||
- intrastack
|
- intrastack
|
||||||
@@ -140,8 +158,6 @@ services:
|
|||||||
container_name: notebooks
|
container_name: notebooks
|
||||||
networks:
|
networks:
|
||||||
- intrastack
|
- intrastack
|
||||||
ports:
|
|
||||||
- "2718:2718"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./notebooks:/home/kert/notebooks
|
- ./notebooks:/home/kert/notebooks
|
||||||
- ./data:/home/kert/data
|
- ./data:/home/kert/data
|
||||||
@@ -164,7 +180,6 @@ services:
|
|||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
|
||||||
- "3478:3478"
|
- "3478:3478"
|
||||||
- "3478:3478/udp"
|
- "3478:3478/udp"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -191,6 +206,172 @@ services:
|
|||||||
capabilities: [gpu]
|
capabilities: [gpu]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
nessie:
|
||||||
|
image: ghcr.io/projectnessie/nessie:latest
|
||||||
|
container_name: nessie
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
environment:
|
||||||
|
- NESSIE_VERSION_STORE_TYPE=JDBC
|
||||||
|
- QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://postgres:5432/nessie
|
||||||
|
- QUARKUS_DATASOURCE_USERNAME=nessie
|
||||||
|
- QUARKUS_DATASOURCE_PASSWORD=${NESSIE_DB_PASSWORD}
|
||||||
|
# Iceberg REST Catalog settings
|
||||||
|
- NESSIE_CATALOG_DEFAULT_WAREHOUSE=s3://lakehouse/
|
||||||
|
- NESSIE_CATALOG_SERVICE_S3_DEFAULT_OPTIONS_ENDPOINT=http://rustfs:9000
|
||||||
|
- NESSIE_CATALOG_SERVICE_S3_DEFAULT_OPTIONS_ACCESS_KEY_ID=${NESSIE_S3_ACCESS_KEY}
|
||||||
|
- NESSIE_CATALOG_SERVICE_S3_DEFAULT_OPTIONS_SECRET_ACCESS_KEY=${NESSIE_S3_SECRET_KEY}
|
||||||
|
- NESSIE_CATALOG_SERVICE_S3_DEFAULT_OPTIONS_PATH_STYLE_ACCESS=true
|
||||||
|
- NESSIE_CATALOG_SERVICE_S3_DEFAULT_OPTIONS_REGION=us-east-1
|
||||||
|
# OpenTelemetry tracing
|
||||||
|
- QUARKUS_OTEL_ENABLED=true
|
||||||
|
- QUARKUS_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://jaeger:4317
|
||||||
|
- QUARKUS_OTEL_SERVICE_NAME=nessie
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- rustfs
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
trino:
|
||||||
|
image: trinodb/trino:latest
|
||||||
|
container_name: trino
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
environment:
|
||||||
|
- NESSIE_S3_ACCESS_KEY=${NESSIE_S3_ACCESS_KEY}
|
||||||
|
- NESSIE_S3_SECRET_KEY=${NESSIE_S3_SECRET_KEY}
|
||||||
|
volumes:
|
||||||
|
- ./trino/etc:/etc/trino:ro
|
||||||
|
depends_on:
|
||||||
|
- nessie
|
||||||
|
- rustfs
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: dashboard
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
volumes:
|
||||||
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
- ./nginx/index.html:/usr/share/nginx/html/index.html:ro
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Observability Stack
|
||||||
|
jaeger:
|
||||||
|
image: jaegertracing/all-in-one:latest
|
||||||
|
container_name: jaeger
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
environment:
|
||||||
|
- COLLECTOR_OTLP_ENABLED=true
|
||||||
|
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
|
||||||
|
ports:
|
||||||
|
- "14268:14268" # Jaeger collector HTTP
|
||||||
|
- "14250:14250" # Jaeger collector gRPC
|
||||||
|
- "6831:6831/udp" # Jaeger agent UDP
|
||||||
|
- "4317:4317" # OTLP gRPC
|
||||||
|
- "4318:4318" # OTLP HTTP
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:16686 || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 15s
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
loki:
|
||||||
|
image: grafana/loki:latest
|
||||||
|
container_name: loki
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
volumes:
|
||||||
|
- ./loki/loki-config.yml:/etc/loki/local-config.yaml:ro
|
||||||
|
- loki_data:/loki
|
||||||
|
command: -config.file=/etc/loki/local-config.yaml
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3100/ready || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
promtail:
|
||||||
|
image: grafana/promtail:latest
|
||||||
|
container_name: promtail
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
volumes:
|
||||||
|
- ./loki/promtail-config.yml:/etc/promtail/config.yml:ro
|
||||||
|
- /home/kert/.local/share/docker/containers:/var/lib/docker/containers:ro
|
||||||
|
command: -config.file=/etc/promtail/config.yml
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus:latest
|
||||||
|
container_name: prometheus
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
volumes:
|
||||||
|
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||||
|
- prometheus_data:/prometheus
|
||||||
|
command:
|
||||||
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||||
|
- '--storage.tsdb.path=/prometheus'
|
||||||
|
- '--web.enable-lifecycle'
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:9090/-/healthy || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 15s
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:latest
|
||||||
|
container_name: grafana
|
||||||
|
networks:
|
||||||
|
- intrastack
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_USER=admin
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||||
|
- GF_USERS_ALLOW_SIGN_UP=false
|
||||||
|
- GF_AUTH_ANONYMOUS_ENABLED=true
|
||||||
|
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
|
||||||
|
- GF_SERVER_ROOT_URL=http://grafana.home.fhirworx.io
|
||||||
|
volumes:
|
||||||
|
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||||
|
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
||||||
|
- grafana_data:/var/lib/grafana
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
- jaeger
|
||||||
|
- prometheus
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
rustfs_data:
|
rustfs_data:
|
||||||
@@ -199,3 +380,6 @@ volumes:
|
|||||||
gitea_config:
|
gitea_config:
|
||||||
woodpecker_data:
|
woodpecker_data:
|
||||||
woodpecker_agent_config:
|
woodpecker_agent_config:
|
||||||
|
loki_data:
|
||||||
|
prometheus_data:
|
||||||
|
grafana_data:
|
||||||
|
|||||||
349
gitea/custom/public/assets/css/theme-nes-sports.css
Normal file
349
gitea/custom/public/assets/css/theme-nes-sports.css
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
/* NES SPORTS THEME FOR GITEA - RBI Baseball / Tecmo Super Bowl */
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
||||||
|
|
||||||
|
/* Theme metadata */
|
||||||
|
gitea-theme-meta-info {
|
||||||
|
--theme-display-name: "NES Sports";
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--is-dark-theme: true;
|
||||||
|
|
||||||
|
/* NES Color Palette */
|
||||||
|
--nes-black: #0f0f0f;
|
||||||
|
--nes-dark-gray: #1a1a2e;
|
||||||
|
--nes-gray: #4a4a68;
|
||||||
|
--nes-light-gray: #9090a8;
|
||||||
|
--nes-white: #f0f0f0;
|
||||||
|
|
||||||
|
--nes-dark-blue: #1a1a40;
|
||||||
|
--nes-blue: #2840a0;
|
||||||
|
--nes-mid-blue: #4060c0;
|
||||||
|
--nes-light-blue: #70a0e0;
|
||||||
|
--nes-sky: #90c0f0;
|
||||||
|
|
||||||
|
--nes-red: #d03030;
|
||||||
|
--nes-orange: #e07020;
|
||||||
|
--nes-yellow: #e0b040;
|
||||||
|
--nes-green: #40a040;
|
||||||
|
--nes-light-green: #80d080;
|
||||||
|
--nes-cyan: #40b0c0;
|
||||||
|
--nes-cream: #e8d8c0;
|
||||||
|
|
||||||
|
/* Gitea color mappings */
|
||||||
|
--color-primary: var(--nes-mid-blue);
|
||||||
|
--color-primary-light: var(--nes-light-blue);
|
||||||
|
--color-primary-dark: var(--nes-blue);
|
||||||
|
--color-primary-alpha-10: rgba(64, 96, 192, 0.1);
|
||||||
|
--color-primary-alpha-20: rgba(64, 96, 192, 0.2);
|
||||||
|
--color-primary-alpha-40: rgba(64, 96, 192, 0.4);
|
||||||
|
|
||||||
|
--color-secondary: var(--nes-gray);
|
||||||
|
--color-secondary-light: var(--nes-light-gray);
|
||||||
|
--color-secondary-dark: var(--nes-dark-gray);
|
||||||
|
|
||||||
|
/* Background colors */
|
||||||
|
--color-body: var(--nes-dark-blue);
|
||||||
|
--color-box-body: var(--nes-black);
|
||||||
|
--color-box-header: var(--nes-dark-gray);
|
||||||
|
|
||||||
|
/* Text colors */
|
||||||
|
--color-text: var(--nes-white);
|
||||||
|
--color-text-light: var(--nes-light-gray);
|
||||||
|
--color-text-dark: var(--nes-cream);
|
||||||
|
|
||||||
|
/* Input colors */
|
||||||
|
--color-input-text: var(--nes-white);
|
||||||
|
--color-input-background: var(--nes-dark-gray);
|
||||||
|
--color-input-border: var(--nes-gray);
|
||||||
|
|
||||||
|
/* Status colors */
|
||||||
|
--color-success: var(--nes-green);
|
||||||
|
--color-success-text: var(--nes-white);
|
||||||
|
--color-warning: var(--nes-yellow);
|
||||||
|
--color-warning-text: var(--nes-black);
|
||||||
|
--color-error: var(--nes-red);
|
||||||
|
--color-error-text: var(--nes-white);
|
||||||
|
--color-info: var(--nes-light-blue);
|
||||||
|
--color-info-text: var(--nes-black);
|
||||||
|
|
||||||
|
/* Border */
|
||||||
|
--color-border: var(--nes-gray);
|
||||||
|
|
||||||
|
/* Link colors */
|
||||||
|
--color-link: var(--nes-sky);
|
||||||
|
--color-link-hover: var(--nes-white);
|
||||||
|
|
||||||
|
/* Code */
|
||||||
|
--color-code-bg: var(--nes-dark-gray);
|
||||||
|
--color-code-border: var(--nes-gray);
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--color-shadow: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global styles */
|
||||||
|
body {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
background: linear-gradient(180deg, #0a0a28 0%, #1a1a40 30%, #1a1a40 70%, #0a0a28 100%) !important;
|
||||||
|
background-attachment: fixed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headings */
|
||||||
|
h1, h2, h3, h4, h5, h6,
|
||||||
|
.header-wrapper .header {
|
||||||
|
font-family: 'Press Start 2P', monospace !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: 18px !important; color: var(--nes-light-green) !important; }
|
||||||
|
h2 { font-size: 14px !important; color: var(--nes-sky) !important; }
|
||||||
|
h3 { font-size: 12px !important; color: var(--nes-yellow) !important; }
|
||||||
|
|
||||||
|
/* Navbar */
|
||||||
|
.ui.secondary.menu.navbar,
|
||||||
|
.navbar {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border-bottom: 3px solid var(--nes-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .item {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .item:hover {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
color: var(--nes-sky) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .item.active {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.ui.button,
|
||||||
|
.button {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
transition: all 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.primary.button,
|
||||||
|
.ui.blue.button {
|
||||||
|
background: var(--nes-mid-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-light-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.primary.button:hover,
|
||||||
|
.ui.blue.button:hover {
|
||||||
|
background: var(--nes-light-blue) !important;
|
||||||
|
color: var(--nes-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.green.button {
|
||||||
|
background: var(--nes-green) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.green.button:hover {
|
||||||
|
background: var(--nes-light-green) !important;
|
||||||
|
color: var(--nes-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.red.button {
|
||||||
|
background: var(--nes-red) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Segments and boxes */
|
||||||
|
.ui.segment,
|
||||||
|
.ui.segments,
|
||||||
|
.repository,
|
||||||
|
.box {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.attached.header,
|
||||||
|
.box-header {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
border-bottom: 2px solid var(--nes-gray) !important;
|
||||||
|
font-family: 'Press Start 2P', monospace !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
color: var(--nes-cream) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.ui.table {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.table th {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
font-family: 'Press Start 2P', monospace !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
border: 1px solid var(--nes-mid-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.table td {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
color: var(--nes-cream) !important;
|
||||||
|
border: 1px solid var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.table tr:hover td {
|
||||||
|
background: var(--nes-dark-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
.ui.form input,
|
||||||
|
.ui.form textarea,
|
||||||
|
.ui.form select,
|
||||||
|
.ui.input input {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.form input:focus,
|
||||||
|
.ui.form textarea:focus,
|
||||||
|
.ui.input input:focus {
|
||||||
|
border-color: var(--nes-light-blue) !important;
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Labels */
|
||||||
|
.ui.label {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdowns */
|
||||||
|
.ui.dropdown .menu {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.dropdown .menu .item {
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.dropdown .menu .item:hover {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code and diffs */
|
||||||
|
.code-view,
|
||||||
|
.file-view,
|
||||||
|
.CodeMirror,
|
||||||
|
.highlight {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-file-box .diff-file-header {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Commit graph */
|
||||||
|
.repository .commit-list .commit {
|
||||||
|
border-bottom: 1px solid var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Issues and PRs */
|
||||||
|
.issue-list .item,
|
||||||
|
.pull-list .item {
|
||||||
|
border-bottom: 1px solid var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border-top: 3px solid var(--nes-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.ui.card,
|
||||||
|
.ui.cards .card {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Messages */
|
||||||
|
.ui.message {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.positive.message,
|
||||||
|
.ui.success.message {
|
||||||
|
background: rgba(64, 160, 64, 0.2) !important;
|
||||||
|
border: 2px solid var(--nes-green) !important;
|
||||||
|
color: var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.negative.message,
|
||||||
|
.ui.error.message {
|
||||||
|
background: rgba(208, 48, 48, 0.2) !important;
|
||||||
|
border: 2px solid var(--nes-red) !important;
|
||||||
|
color: var(--nes-red) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.warning.message {
|
||||||
|
background: rgba(224, 176, 64, 0.2) !important;
|
||||||
|
border: 2px solid var(--nes-yellow) !important;
|
||||||
|
color: var(--nes-yellow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.info.message {
|
||||||
|
background: rgba(112, 160, 224, 0.2) !important;
|
||||||
|
border: 2px solid var(--nes-light-blue) !important;
|
||||||
|
color: var(--nes-light-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbars */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--nes-dark-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--nes-gray);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--nes-light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection */
|
||||||
|
::selection {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
a {
|
||||||
|
color: var(--nes-sky) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
22
grafana/custom.ini
Normal file
22
grafana/custom.ini
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[server]
|
||||||
|
root_url = http://localhost:3001
|
||||||
|
|
||||||
|
[security]
|
||||||
|
admin_user = admin
|
||||||
|
admin_password = admin
|
||||||
|
|
||||||
|
[users]
|
||||||
|
allow_sign_up = false
|
||||||
|
|
||||||
|
[auth.anonymous]
|
||||||
|
enabled = true
|
||||||
|
org_role = Viewer
|
||||||
|
|
||||||
|
[dashboards]
|
||||||
|
default_home_dashboard_path = /var/lib/grafana/dashboards/homelab-overview.json
|
||||||
|
|
||||||
|
[unified_alerting]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[feature_toggles]
|
||||||
|
enable = traceqlEditor tempoServiceGraph traceQLStreaming
|
||||||
356
grafana/dashboards/homelab-overview.json
Normal file
356
grafana/dashboards/homelab-overview.json
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"dedupStrategy": "none",
|
||||||
|
"enableLogDetails": true,
|
||||||
|
"prettifyLogMessage": false,
|
||||||
|
"showCommonLabels": false,
|
||||||
|
"showLabels": false,
|
||||||
|
"showTime": true,
|
||||||
|
"sortOrder": "Descending",
|
||||||
|
"wrapLogMessage": false
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"editorMode": "builder",
|
||||||
|
"expr": "{job=\"containerlogs\"} |= ``",
|
||||||
|
"queryType": "range",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "All Container Logs",
|
||||||
|
"type": "logs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"dedupStrategy": "none",
|
||||||
|
"enableLogDetails": true,
|
||||||
|
"prettifyLogMessage": false,
|
||||||
|
"showCommonLabels": false,
|
||||||
|
"showLabels": true,
|
||||||
|
"showTime": true,
|
||||||
|
"sortOrder": "Descending",
|
||||||
|
"wrapLogMessage": false
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "{container_name=~\"gitea|woodpecker.*\"} |= ``",
|
||||||
|
"queryType": "range",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Git & CI Logs",
|
||||||
|
"type": "logs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"dedupStrategy": "none",
|
||||||
|
"enableLogDetails": true,
|
||||||
|
"prettifyLogMessage": false,
|
||||||
|
"showCommonLabels": false,
|
||||||
|
"showLabels": true,
|
||||||
|
"showTime": true,
|
||||||
|
"sortOrder": "Descending",
|
||||||
|
"wrapLogMessage": false
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "{container_name=~\"nessie|trino\"} |= ``",
|
||||||
|
"queryType": "range",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Data Lake Logs",
|
||||||
|
"type": "logs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"dedupStrategy": "none",
|
||||||
|
"enableLogDetails": true,
|
||||||
|
"prettifyLogMessage": false,
|
||||||
|
"showCommonLabels": false,
|
||||||
|
"showLabels": true,
|
||||||
|
"showTime": true,
|
||||||
|
"sortOrder": "Descending",
|
||||||
|
"wrapLogMessage": false
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "{container_name=~\"postgres|rustfs\"} |= ``",
|
||||||
|
"queryType": "range",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Storage Logs",
|
||||||
|
"type": "logs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"dedupStrategy": "none",
|
||||||
|
"enableLogDetails": true,
|
||||||
|
"prettifyLogMessage": false,
|
||||||
|
"showCommonLabels": false,
|
||||||
|
"showLabels": true,
|
||||||
|
"showTime": true,
|
||||||
|
"sortOrder": "Descending",
|
||||||
|
"wrapLogMessage": false
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "{job=\"containerlogs\"} |~ \"error|Error|ERROR|exception|Exception|EXCEPTION|failed|Failed|FAILED\"",
|
||||||
|
"queryType": "range",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Errors Across All Services",
|
||||||
|
"type": "logs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "bars",
|
||||||
|
"fillOpacity": 50,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "normal"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 6,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "loki"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum by (container_name) (count_over_time({job=\"containerlogs\"} [1m]))",
|
||||||
|
"queryType": "range",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Log Volume by Container",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "jaeger",
|
||||||
|
"uid": "jaeger"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 22
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "jaeger",
|
||||||
|
"uid": "jaeger"
|
||||||
|
},
|
||||||
|
"queryType": "search",
|
||||||
|
"refId": "A",
|
||||||
|
"service": "$service",
|
||||||
|
"limit": 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Recent Traces",
|
||||||
|
"type": "traces"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"tags": ["homelab", "observability"],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"selected": true,
|
||||||
|
"text": "nessie",
|
||||||
|
"value": "nessie"
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "jaeger",
|
||||||
|
"uid": "jaeger"
|
||||||
|
},
|
||||||
|
"definition": "services",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": false,
|
||||||
|
"label": "Service",
|
||||||
|
"multi": false,
|
||||||
|
"name": "service",
|
||||||
|
"options": [],
|
||||||
|
"query": "services",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 1,
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-1h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Homelab Overview",
|
||||||
|
"uid": "homelab-overview",
|
||||||
|
"version": 1,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
652
grafana/dashboards/traefik.json
Normal file
652
grafana/dashboards/traefik.json
Normal file
@@ -0,0 +1,652 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 1,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
|
||||||
|
"id": 100,
|
||||||
|
"title": "Overview",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "reqps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 1 },
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": ["lastNotNull"],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_entrypoint_requests_total[5m]))",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Requests / sec",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 1 },
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": ["lastNotNull"],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(traefik_open_connections)",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Open Connections",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "thresholds" },
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 0.01 },
|
||||||
|
{ "color": "red", "value": 0.05 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percentunit"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 1 },
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": ["lastNotNull"],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_entrypoint_requests_total{code=~\"5..\"}[5m])) / sum(rate(traefik_entrypoint_requests_total[5m])) or vector(0)",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Error Rate (5xx)",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 1 },
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": ["lastNotNull"],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(traefik_entrypoint_request_duration_seconds_bucket[5m])) by (le))",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "P95 Latency",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 },
|
||||||
|
"id": 101,
|
||||||
|
"title": "Requests",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "none" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "reqps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 6 },
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_entrypoint_requests_total[5m])) by (entrypoint)",
|
||||||
|
"legendFormat": "{{entrypoint}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Requests by Entrypoint",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "normal" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "reqps"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byRegexp", "options": "2.." }, "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }] },
|
||||||
|
{ "matcher": { "id": "byRegexp", "options": "3.." }, "properties": [{ "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } }] },
|
||||||
|
{ "matcher": { "id": "byRegexp", "options": "4.." }, "properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }] },
|
||||||
|
{ "matcher": { "id": "byRegexp", "options": "5.." }, "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 6 },
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_entrypoint_requests_total[5m])) by (code)",
|
||||||
|
"legendFormat": "{{code}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Requests by Status Code",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 14 },
|
||||||
|
"id": 102,
|
||||||
|
"title": "Services",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "none" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "reqps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 15 },
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "right", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_service_requests_total[5m])) by (service)",
|
||||||
|
"legendFormat": "{{service}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Requests by Service",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "none" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 15 },
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "right", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(traefik_service_request_duration_seconds_bucket[5m])) by (le, service))",
|
||||||
|
"legendFormat": "{{service}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "P95 Latency by Service",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 23 },
|
||||||
|
"id": 103,
|
||||||
|
"title": "Routers",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "none" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "reqps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 24 },
|
||||||
|
"id": 9,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "right", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_router_requests_total[5m])) by (router)",
|
||||||
|
"legendFormat": "{{router}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Requests by Router",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "none" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 24 },
|
||||||
|
"id": 10,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "right", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(traefik_router_request_duration_seconds_bucket[5m])) by (le, router))",
|
||||||
|
"legendFormat": "{{router}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "P95 Latency by Router",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 32 },
|
||||||
|
"id": 104,
|
||||||
|
"title": "Bandwidth",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "none" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "Bps"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 33 },
|
||||||
|
"id": 11,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_entrypoint_requests_bytes_total[5m])) by (entrypoint)",
|
||||||
|
"legendFormat": "{{entrypoint}} in",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(rate(traefik_entrypoint_responses_bytes_total[5m])) by (entrypoint)",
|
||||||
|
"legendFormat": "{{entrypoint}} out",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Bandwidth by Entrypoint",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": { "mode": "palette-classic" },
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 20,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "smooth",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": { "type": "linear" },
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": { "group": "A", "mode": "none" },
|
||||||
|
"thresholdsStyle": { "mode": "off" }
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [{ "color": "green", "value": null }]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 33 },
|
||||||
|
"id": 12,
|
||||||
|
"options": {
|
||||||
|
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true },
|
||||||
|
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||||
|
"expr": "sum(traefik_open_connections) by (entrypoint, protocol)",
|
||||||
|
"legendFormat": "{{entrypoint}} ({{protocol}})",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Open Connections",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "30s",
|
||||||
|
"schemaVersion": 38,
|
||||||
|
"tags": ["traefik", "proxy"],
|
||||||
|
"templating": { "list": [] },
|
||||||
|
"time": { "from": "now-1h", "to": "now" },
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Traefik",
|
||||||
|
"uid": "traefik",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
13
grafana/provisioning/dashboards/dashboards.yml
Normal file
13
grafana/provisioning/dashboards/dashboards.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: 'Homelab Dashboards'
|
||||||
|
orgId: 1
|
||||||
|
folder: 'Homelab'
|
||||||
|
folderUid: 'homelab'
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
updateIntervalSeconds: 30
|
||||||
|
allowUiUpdates: true
|
||||||
|
options:
|
||||||
|
path: /var/lib/grafana/dashboards
|
||||||
32
grafana/provisioning/datasources/datasources.yml
Normal file
32
grafana/provisioning/datasources/datasources.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
deleteDatasources:
|
||||||
|
- name: Loki
|
||||||
|
orgId: 1
|
||||||
|
- name: Prometheus
|
||||||
|
orgId: 1
|
||||||
|
- name: Jaeger
|
||||||
|
orgId: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: Loki
|
||||||
|
type: loki
|
||||||
|
uid: loki
|
||||||
|
access: proxy
|
||||||
|
url: http://loki:3100
|
||||||
|
isDefault: true
|
||||||
|
editable: true
|
||||||
|
|
||||||
|
- name: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
uid: prometheus
|
||||||
|
access: proxy
|
||||||
|
url: http://prometheus:9090
|
||||||
|
editable: true
|
||||||
|
|
||||||
|
- name: Jaeger
|
||||||
|
type: jaeger
|
||||||
|
uid: jaeger
|
||||||
|
access: proxy
|
||||||
|
url: http://jaeger:16686
|
||||||
|
editable: true
|
||||||
46
loki/loki-config.yml
Normal file
46
loki/loki-config.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
auth_enabled: false
|
||||||
|
|
||||||
|
server:
|
||||||
|
http_listen_port: 3100
|
||||||
|
grpc_listen_port: 9096
|
||||||
|
|
||||||
|
common:
|
||||||
|
instance_addr: 127.0.0.1
|
||||||
|
path_prefix: /loki
|
||||||
|
storage:
|
||||||
|
filesystem:
|
||||||
|
chunks_directory: /loki/chunks
|
||||||
|
rules_directory: /loki/rules
|
||||||
|
replication_factor: 1
|
||||||
|
ring:
|
||||||
|
kvstore:
|
||||||
|
store: inmemory
|
||||||
|
|
||||||
|
query_range:
|
||||||
|
results_cache:
|
||||||
|
cache:
|
||||||
|
embedded_cache:
|
||||||
|
enabled: true
|
||||||
|
max_size_mb: 100
|
||||||
|
|
||||||
|
schema_config:
|
||||||
|
configs:
|
||||||
|
- from: 2020-10-24
|
||||||
|
store: tsdb
|
||||||
|
object_store: filesystem
|
||||||
|
schema: v13
|
||||||
|
index:
|
||||||
|
prefix: index_
|
||||||
|
period: 24h
|
||||||
|
|
||||||
|
ruler:
|
||||||
|
alertmanager_url: http://localhost:9093
|
||||||
|
|
||||||
|
limits_config:
|
||||||
|
reject_old_samples: true
|
||||||
|
reject_old_samples_max_age: 168h
|
||||||
|
ingestion_rate_mb: 16
|
||||||
|
ingestion_burst_size_mb: 24
|
||||||
|
|
||||||
|
analytics:
|
||||||
|
reporting_enabled: false
|
||||||
38
loki/promtail-config.yml
Normal file
38
loki/promtail-config.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
server:
|
||||||
|
http_listen_port: 9080
|
||||||
|
grpc_listen_port: 0
|
||||||
|
|
||||||
|
positions:
|
||||||
|
filename: /tmp/positions.yaml
|
||||||
|
|
||||||
|
clients:
|
||||||
|
- url: http://loki:3100/loki/api/v1/push
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
# Scrape Docker container logs
|
||||||
|
- job_name: containers
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- localhost
|
||||||
|
labels:
|
||||||
|
job: containerlogs
|
||||||
|
__path__: /var/lib/docker/containers/*/*log
|
||||||
|
|
||||||
|
pipeline_stages:
|
||||||
|
- json:
|
||||||
|
expressions:
|
||||||
|
output: log
|
||||||
|
stream: stream
|
||||||
|
attrs:
|
||||||
|
- json:
|
||||||
|
expressions:
|
||||||
|
tag:
|
||||||
|
source: attrs
|
||||||
|
- regex:
|
||||||
|
expression: (?P<container_name>(?:[a-zA-Z0-9][a-zA-Z0-9_.-]+))
|
||||||
|
source: tag
|
||||||
|
- labels:
|
||||||
|
stream:
|
||||||
|
container_name:
|
||||||
|
- output:
|
||||||
|
source: output
|
||||||
471
nginx/index.html
Normal file
471
nginx/index.html
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>HOMELAB COMMAND CENTER</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Press Start 2P', monospace;
|
||||||
|
background: #0a0a0a;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CRT scanline effect */
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 0, 0, 0.15),
|
||||||
|
rgba(0, 0, 0, 0.15) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 2px
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animated starfield background */
|
||||||
|
.stars {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star {
|
||||||
|
position: absolute;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: twinkle 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes twinkle {
|
||||||
|
0%, 100% { opacity: 0.3; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #00ff00;
|
||||||
|
text-shadow:
|
||||||
|
0 0 10px #00ff00,
|
||||||
|
0 0 20px #00ff00,
|
||||||
|
0 0 40px #00ff00,
|
||||||
|
4px 4px 0 #003300;
|
||||||
|
animation: glow 1.5s ease-in-out infinite alternate;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
from { text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00, 0 0 40px #00ff00, 4px 4px 0 #003300; }
|
||||||
|
to { text-shadow: 0 0 20px #00ff00, 0 0 40px #00ff00, 0 0 60px #00ff00, 4px 4px 0 #003300; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: #ff00ff;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
margin-top: 15px;
|
||||||
|
text-shadow: 0 0 10px #ff00ff;
|
||||||
|
animation: blink 1s step-end infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 40px;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-item {
|
||||||
|
color: #ffff00;
|
||||||
|
text-shadow: 0 0 5px #ffff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 25px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile {
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||||
|
border: 4px solid;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 25px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile:hover {
|
||||||
|
transform: translateY(-5px) scale(1.02);
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Service-specific colors */
|
||||||
|
.tile.storage { border-color: #ff6b6b; }
|
||||||
|
.tile.storage .icon { color: #ff6b6b; text-shadow: 0 0 20px #ff6b6b; }
|
||||||
|
.tile.storage:hover { background: linear-gradient(135deg, #2a1a1a 0%, #1e1616 100%); }
|
||||||
|
|
||||||
|
.tile.database { border-color: #4ecdc4; }
|
||||||
|
.tile.database .icon { color: #4ecdc4; text-shadow: 0 0 20px #4ecdc4; }
|
||||||
|
.tile.database:hover { background: linear-gradient(135deg, #1a2a2a 0%, #162121 100%); }
|
||||||
|
|
||||||
|
.tile.git { border-color: #f7b731; }
|
||||||
|
.tile.git .icon { color: #f7b731; text-shadow: 0 0 20px #f7b731; }
|
||||||
|
.tile.git:hover { background: linear-gradient(135deg, #2a2a1a 0%, #212116 100%); }
|
||||||
|
|
||||||
|
.tile.ci { border-color: #a55eea; }
|
||||||
|
.tile.ci .icon { color: #a55eea; text-shadow: 0 0 20px #a55eea; }
|
||||||
|
.tile.ci:hover { background: linear-gradient(135deg, #2a1a2a 0%, #211621 100%); }
|
||||||
|
|
||||||
|
.tile.notebook { border-color: #26de81; }
|
||||||
|
.tile.notebook .icon { color: #26de81; text-shadow: 0 0 20px #26de81; }
|
||||||
|
.tile.notebook:hover { background: linear-gradient(135deg, #1a2a1a 0%, #162116 100%); }
|
||||||
|
|
||||||
|
.tile.research { border-color: #fd79a8; }
|
||||||
|
.tile.research .icon { color: #fd79a8; text-shadow: 0 0 20px #fd79a8; }
|
||||||
|
.tile.research:hover { background: linear-gradient(135deg, #2a1a22 0%, #21161a 100%); }
|
||||||
|
|
||||||
|
.tile.catalog { border-color: #00cec9; }
|
||||||
|
.tile.catalog .icon { color: #00cec9; text-shadow: 0 0 20px #00cec9; }
|
||||||
|
.tile.catalog:hover { background: linear-gradient(135deg, #1a2a2a 0%, #162121 100%); }
|
||||||
|
|
||||||
|
.tile.query { border-color: #6c5ce7; }
|
||||||
|
.tile.query .icon { color: #6c5ce7; text-shadow: 0 0 20px #6c5ce7; }
|
||||||
|
.tile.query:hover { background: linear-gradient(135deg, #1a1a2a 0%, #161621 100%); }
|
||||||
|
|
||||||
|
.tile.observability { border-color: #e17055; }
|
||||||
|
.tile.observability .icon { color: #e17055; text-shadow: 0 0 20px #e17055; }
|
||||||
|
.tile.observability:hover { background: linear-gradient(135deg, #2a1a1a 0%, #211616 100%); }
|
||||||
|
|
||||||
|
.tile.metrics { border-color: #fdcb6e; }
|
||||||
|
.tile.metrics .icon { color: #fdcb6e; text-shadow: 0 0 20px #fdcb6e; }
|
||||||
|
.tile.metrics:hover { background: linear-gradient(135deg, #2a2a1a 0%, #212116 100%); }
|
||||||
|
|
||||||
|
.tile.logs { border-color: #74b9ff; }
|
||||||
|
.tile.logs .icon { color: #74b9ff; text-shadow: 0 0 20px #74b9ff; }
|
||||||
|
.tile.logs:hover { background: linear-gradient(135deg, #1a1a2a 0%, #161621 100%); }
|
||||||
|
|
||||||
|
.tile.proxy { border-color: #55efc4; }
|
||||||
|
.tile.proxy .icon { color: #55efc4; text-shadow: 0 0 20px #55efc4; }
|
||||||
|
.tile.proxy:hover { background: linear-gradient(135deg, #1a2a2a 0%, #162121 100%); }
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-title {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-shadow: 2px 2px 0 #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-desc {
|
||||||
|
font-size: 0.5rem;
|
||||||
|
color: #888;
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-port {
|
||||||
|
font-size: 0.45rem;
|
||||||
|
color: #00ff00;
|
||||||
|
background: rgba(0, 255, 0, 0.1);
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #00ff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #00ff00;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
box-shadow: 0 0 10px #00ff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
50% { transform: scale(1.2); opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.pixel-border {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
currentColor 0px,
|
||||||
|
currentColor 4px,
|
||||||
|
transparent 4px,
|
||||||
|
transparent 8px
|
||||||
|
);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #444;
|
||||||
|
font-size: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer span {
|
||||||
|
color: #ff0000;
|
||||||
|
animation: heart 1s infinite;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes heart {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.3); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Power-up badges */
|
||||||
|
.badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
left: 20px;
|
||||||
|
background: #ff0000;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.4rem;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: bounce 0.5s ease infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
from { transform: translateY(0); }
|
||||||
|
to { transform: translateY(-3px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.new { background: #ff0000; }
|
||||||
|
.badge.gpu { background: #ff6b00; }
|
||||||
|
.badge.data { background: #0066ff; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="stars" id="stars"></div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>HOMELAB</h1>
|
||||||
|
<p class="subtitle">[ PRESS START TO CONTINUE ]</p>
|
||||||
|
<div class="score-bar">
|
||||||
|
<span class="score-item">SERVICES: 12</span>
|
||||||
|
<span class="score-item">STATUS: ONLINE</span>
|
||||||
|
<span class="score-item">LEVEL: MAX</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<a href="http://traefik.home.fhirworx.io" target="_blank" class="tile proxy">
|
||||||
|
<span class="badge new">NEW</span>
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">🔗</span>
|
||||||
|
<h2 class="tile-title">TRAEFIK</h2>
|
||||||
|
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
||||||
|
<span class="tile-port">traefik.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #55efc4;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://minio.home.fhirworx.io" target="_blank" class="tile storage">
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">💾</span>
|
||||||
|
<h2 class="tile-title">RUSTFS</h2>
|
||||||
|
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
||||||
|
<span class="tile-port">minio.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #ff6b6b;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://gitea.home.fhirworx.io" target="_blank" class="tile git">
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">🐙</span>
|
||||||
|
<h2 class="tile-title">GITEA</h2>
|
||||||
|
<p class="tile-desc">Self-hosted Git service with packages & CI</p>
|
||||||
|
<span class="tile-port">gitea.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #f7b731;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://ci.home.fhirworx.io" target="_blank" class="tile ci">
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">🔨</span>
|
||||||
|
<h2 class="tile-title">WOODPECKER</h2>
|
||||||
|
<p class="tile-desc">Simple yet powerful CI/CD engine</p>
|
||||||
|
<span class="tile-port">ci.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #a55eea;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://notebooks.home.fhirworx.io" target="_blank" class="tile notebook">
|
||||||
|
<span class="badge gpu">GPU</span>
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">📑</span>
|
||||||
|
<h2 class="tile-title">NOTEBOOKS</h2>
|
||||||
|
<p class="tile-desc">Marimo reactive notebooks with CUDA</p>
|
||||||
|
<span class="tile-port">notebooks.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #26de81;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://zotero.home.fhirworx.io" target="_blank" class="tile research">
|
||||||
|
<span class="badge gpu">GPU</span>
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">📚</span>
|
||||||
|
<h2 class="tile-title">ZOTERO</h2>
|
||||||
|
<p class="tile-desc">Research reference manager in the cloud</p>
|
||||||
|
<span class="tile-port">zotero.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #fd79a8;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://nessie.home.fhirworx.io/api/v2/config" target="_blank" class="tile catalog">
|
||||||
|
<span class="badge data">DATA</span>
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">🌳</span>
|
||||||
|
<h2 class="tile-title">NESSIE</h2>
|
||||||
|
<p class="tile-desc">Git-like versioned data catalog for Iceberg</p>
|
||||||
|
<span class="tile-port">nessie.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #00cec9;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://trino.home.fhirworx.io" target="_blank" class="tile query">
|
||||||
|
<span class="badge data">DATA</span>
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">🔍</span>
|
||||||
|
<h2 class="tile-title">TRINO</h2>
|
||||||
|
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
||||||
|
<span class="tile-port">trino.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #6c5ce7;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://grafana.home.fhirworx.io" target="_blank" class="tile observability">
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">📊</span>
|
||||||
|
<h2 class="tile-title">GRAFANA</h2>
|
||||||
|
<p class="tile-desc">Observability dashboards for logs and traces</p>
|
||||||
|
<span class="tile-port">grafana.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #e17055;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://jaeger.home.fhirworx.io" target="_blank" class="tile metrics">
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">👁</span>
|
||||||
|
<h2 class="tile-title">JAEGER</h2>
|
||||||
|
<p class="tile-desc">Distributed tracing for microservices</p>
|
||||||
|
<span class="tile-port">jaeger.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #fdcb6e;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://prometheus.home.fhirworx.io" target="_blank" class="tile logs">
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">📈</span>
|
||||||
|
<h2 class="tile-title">PROMETHEUS</h2>
|
||||||
|
<p class="tile-desc">Metrics collection and alerting</p>
|
||||||
|
<span class="tile-port">prometheus.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #74b9ff;"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="http://loki.home.fhirworx.io/ready" target="_blank" class="tile database">
|
||||||
|
<span class="status"></span>
|
||||||
|
<span class="icon">📋</span>
|
||||||
|
<h2 class="tile-title">LOKI</h2>
|
||||||
|
<p class="tile-desc">Log aggregation system by Grafana</p>
|
||||||
|
<span class="tile-port">loki.home.fhirworx.io</span>
|
||||||
|
<div class="pixel-border" style="color: #4ecdc4;"></div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
MADE WITH <span>♥</span> FOR THE HOMELAB
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Generate starfield
|
||||||
|
const starsContainer = document.getElementById('stars');
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const star = document.createElement('div');
|
||||||
|
star.className = 'star';
|
||||||
|
star.style.left = Math.random() * 100 + '%';
|
||||||
|
star.style.top = Math.random() * 100 + '%';
|
||||||
|
star.style.width = Math.random() * 3 + 1 + 'px';
|
||||||
|
star.style.height = star.style.width;
|
||||||
|
star.style.animationDelay = Math.random() * 2 + 's';
|
||||||
|
starsContainer.appendChild(star);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click sound effect (visual feedback)
|
||||||
|
document.querySelectorAll('.tile').forEach(tile => {
|
||||||
|
tile.addEventListener('click', function(e) {
|
||||||
|
this.style.transform = 'scale(0.95)';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.style.transform = '';
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
nginx/nginx.conf
Normal file
20
nginx/nginx.conf
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
notebooks/.marimo.toml
Normal file
3
notebooks/.marimo.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[display]
|
||||||
|
theme = "dark"
|
||||||
|
custom_css = ["/home/kert/.config/marimo/retro-arcade.css"]
|
||||||
@@ -37,8 +37,15 @@ RUN uv python install ${PYTHON_VERSION} \
|
|||||||
&& uv init workspace --python ${PYTHON_VERSION} \
|
&& uv init workspace --python ${PYTHON_VERSION} \
|
||||||
&& cd workspace \
|
&& cd workspace \
|
||||||
&& uv add "marimo[recommended]" polars cudf-polars-cu12 pandas numpy pyarrow \
|
&& uv add "marimo[recommended]" polars cudf-polars-cu12 pandas numpy pyarrow \
|
||||||
|
"pyiceberg[s3,pyarrow]>=0.7.0" "duckdb>=1.0.0" "narwhals>=1.0.0" "trino>=0.328.0" \
|
||||||
|
vega_datasets pyzotero \
|
||||||
--extra-index-url https://pypi.nvidia.com
|
--extra-index-url https://pypi.nvidia.com
|
||||||
|
|
||||||
|
# Create marimo config directory and config
|
||||||
|
RUN mkdir -p /home/${USERNAME}/.config/marimo
|
||||||
|
COPY --chown=${USER_UID}:${USER_GID} .marimo.toml /home/${USERNAME}/.config/marimo/marimo.toml
|
||||||
|
COPY --chown=${USER_UID}:${USER_GID} retro-arcade.css /home/${USERNAME}/.config/marimo/retro-arcade.css
|
||||||
|
|
||||||
EXPOSE 2718
|
EXPOSE 2718
|
||||||
|
|
||||||
CMD ["uv", "run", "--project", "/home/kert/workspace", \
|
CMD ["uv", "run", "--project", "/home/kert/workspace", \
|
||||||
|
|||||||
444
notebooks/retro-arcade.css
Normal file
444
notebooks/retro-arcade.css
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
/* NES SPORTS THEME - RBI Baseball / Tecmo Super Bowl */
|
||||||
|
/* Uses marimo's native CSS variable system for proper theming */
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
FONT CONFIGURATION
|
||||||
|
============================================ */
|
||||||
|
:root {
|
||||||
|
--marimo-monospace-font: 'IBM Plex Mono', 'Courier New', monospace;
|
||||||
|
--marimo-text-font: 'IBM Plex Mono', 'Courier New', monospace;
|
||||||
|
--marimo-heading-font: 'Press Start 2P', 'IBM Plex Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
NES COLOR PALETTE
|
||||||
|
============================================ */
|
||||||
|
:root {
|
||||||
|
/* Core NES colors */
|
||||||
|
--nes-black: #0f0f0f;
|
||||||
|
--nes-dark-gray: #1a1a2e;
|
||||||
|
--nes-gray: #4a4a68;
|
||||||
|
--nes-light-gray: #9090a8;
|
||||||
|
--nes-white: #f0f0f0;
|
||||||
|
|
||||||
|
/* NES blues (the classic Tecmo/RBI look) */
|
||||||
|
--nes-dark-blue: #1a1a40;
|
||||||
|
--nes-blue: #2840a0;
|
||||||
|
--nes-mid-blue: #4060c0;
|
||||||
|
--nes-light-blue: #70a0e0;
|
||||||
|
--nes-sky: #90c0f0;
|
||||||
|
|
||||||
|
/* Accent colors */
|
||||||
|
--nes-red: #d03030;
|
||||||
|
--nes-orange: #e07020;
|
||||||
|
--nes-yellow: #e0b040;
|
||||||
|
--nes-green: #40a040;
|
||||||
|
--nes-light-green: #80d080;
|
||||||
|
--nes-cyan: #40b0c0;
|
||||||
|
--nes-cream: #e8d8c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
MARIMO THEME VARIABLES (Dark Mode)
|
||||||
|
============================================ */
|
||||||
|
:root {
|
||||||
|
/* Base colors */
|
||||||
|
--background: var(--nes-dark-blue);
|
||||||
|
--foreground: var(--nes-white);
|
||||||
|
|
||||||
|
/* Muted/secondary text */
|
||||||
|
--muted: var(--nes-dark-gray);
|
||||||
|
--muted-foreground: var(--nes-light-gray);
|
||||||
|
|
||||||
|
/* Popover/dropdown backgrounds */
|
||||||
|
--popover: var(--nes-dark-gray);
|
||||||
|
--popover-foreground: var(--nes-white);
|
||||||
|
|
||||||
|
/* Cards (cells) */
|
||||||
|
--card: var(--nes-black);
|
||||||
|
--card-foreground: var(--nes-white);
|
||||||
|
|
||||||
|
/* Borders and inputs */
|
||||||
|
--border: var(--nes-gray);
|
||||||
|
--input: var(--nes-dark-gray);
|
||||||
|
|
||||||
|
/* Primary action color */
|
||||||
|
--primary: var(--nes-mid-blue);
|
||||||
|
--primary-foreground: var(--nes-white);
|
||||||
|
|
||||||
|
/* Secondary elements */
|
||||||
|
--secondary: var(--nes-gray);
|
||||||
|
--secondary-foreground: var(--nes-white);
|
||||||
|
|
||||||
|
/* Accent/highlight */
|
||||||
|
--accent: var(--nes-light-blue);
|
||||||
|
--accent-foreground: var(--nes-black);
|
||||||
|
|
||||||
|
/* Focus ring */
|
||||||
|
--ring: var(--nes-light-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SEMANTIC COLORS
|
||||||
|
============================================ */
|
||||||
|
:root {
|
||||||
|
/* Error states */
|
||||||
|
--destructive: var(--nes-red);
|
||||||
|
--destructive-foreground: var(--nes-white);
|
||||||
|
--error: var(--nes-red);
|
||||||
|
--error-foreground: var(--nes-white);
|
||||||
|
|
||||||
|
/* Success states */
|
||||||
|
--success: var(--nes-green);
|
||||||
|
--success-foreground: var(--nes-white);
|
||||||
|
|
||||||
|
/* Action buttons (run, etc) */
|
||||||
|
--action: var(--nes-green);
|
||||||
|
--action-hover: var(--nes-light-green);
|
||||||
|
--action-foreground: var(--nes-black);
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
--link: var(--nes-sky);
|
||||||
|
--link-visited: var(--nes-light-blue);
|
||||||
|
|
||||||
|
/* Stale indicator */
|
||||||
|
--stale: rgba(224, 176, 64, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SHADOWS (subtle, NES-style)
|
||||||
|
============================================ */
|
||||||
|
:root {
|
||||||
|
--base-shadow: rgba(0, 0, 0, 0.4);
|
||||||
|
--base-shadow-darker: rgba(0, 0, 0, 0.6);
|
||||||
|
--base-shadow-opacity: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
BACKGROUND GRADIENT (Tecmo field style)
|
||||||
|
============================================ */
|
||||||
|
:root {
|
||||||
|
--background-image: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
#0a0a28 0%,
|
||||||
|
#1a1a40 30%,
|
||||||
|
#1a1a40 70%,
|
||||||
|
#0a0a28 100%
|
||||||
|
);
|
||||||
|
--background-repeat: no-repeat;
|
||||||
|
--background-attachment: fixed;
|
||||||
|
--background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-image: var(--background-image) !important;
|
||||||
|
background-repeat: var(--background-repeat) !important;
|
||||||
|
background-attachment: var(--background-attachment) !important;
|
||||||
|
background-size: var(--background-size) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
CODE EDITOR - CODEMIRROR STYLING
|
||||||
|
============================================ */
|
||||||
|
.cm-editor {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-content {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-line {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-gutters {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
border-right: 2px solid var(--nes-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-lineNumbers .cm-gutterElement {
|
||||||
|
color: var(--nes-light-gray) !important;
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syntax highlighting */
|
||||||
|
.cm-keyword, .tok-keyword { color: var(--nes-sky) !important; }
|
||||||
|
.cm-string, .tok-string { color: var(--nes-light-green) !important; }
|
||||||
|
.cm-number, .tok-number { color: var(--nes-yellow) !important; }
|
||||||
|
.cm-comment, .tok-comment { color: var(--nes-light-gray) !important; font-style: italic !important; }
|
||||||
|
.cm-function, .tok-function { color: var(--nes-cream) !important; }
|
||||||
|
.cm-variableName, .tok-variableName { color: var(--nes-white) !important; }
|
||||||
|
.cm-operator, .tok-operator { color: var(--nes-cyan) !important; }
|
||||||
|
.cm-propertyName, .tok-propertyName { color: var(--nes-light-blue) !important; }
|
||||||
|
.tok-standard, .cm-builtin { color: var(--nes-orange) !important; }
|
||||||
|
.tok-definition, .cm-def { color: var(--nes-sky) !important; }
|
||||||
|
.tok-typeName { color: var(--nes-yellow) !important; }
|
||||||
|
.tok-bool { color: var(--nes-orange) !important; }
|
||||||
|
|
||||||
|
/* Cursor */
|
||||||
|
.cm-cursor {
|
||||||
|
border-left: 2px solid var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection */
|
||||||
|
.cm-selectionBackground,
|
||||||
|
::selection {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active line */
|
||||||
|
.cm-activeLine {
|
||||||
|
background: rgba(64, 96, 192, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
HEADINGS (Press Start 2P for that NES feel)
|
||||||
|
============================================ */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--marimo-heading-font) !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
|
line-height: 1.8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 18px !important;
|
||||||
|
color: var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 14px !important;
|
||||||
|
color: var(--nes-sky) !important;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 12px !important;
|
||||||
|
color: var(--nes-yellow) !important;
|
||||||
|
}
|
||||||
|
h4, h5, h6 {
|
||||||
|
font-size: 11px !important;
|
||||||
|
color: var(--nes-cream) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
TABLES (Scoreboard style)
|
||||||
|
============================================ */
|
||||||
|
table {
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
font-family: var(--marimo-heading-font) !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
padding: 10px 12px !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 1px !important;
|
||||||
|
border: 1px solid var(--nes-mid-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
color: var(--nes-cream) !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
border: 1px solid var(--nes-dark-gray) !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) td {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover td {
|
||||||
|
background: var(--nes-dark-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
BUTTONS (NES menu style)
|
||||||
|
============================================ */
|
||||||
|
button,
|
||||||
|
[role="button"],
|
||||||
|
.btn {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
transition: all 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Primary/action buttons should be visible */
|
||||||
|
button[data-variant="primary"],
|
||||||
|
button.primary,
|
||||||
|
[data-action="run"] {
|
||||||
|
background: var(--nes-green) !important;
|
||||||
|
color: var(--nes-black) !important;
|
||||||
|
border: 2px solid var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[data-variant="primary"]:hover,
|
||||||
|
button.primary:hover,
|
||||||
|
[data-action="run"]:hover {
|
||||||
|
background: var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
INPUTS AND FORMS
|
||||||
|
============================================ */
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
textarea:focus,
|
||||||
|
select:focus {
|
||||||
|
border-color: var(--nes-light-blue) !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(112, 160, 224, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
CODE BLOCKS (in markdown)
|
||||||
|
============================================ */
|
||||||
|
pre, code {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
color: var(--nes-light-green) !important;
|
||||||
|
padding: 2px 6px !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
font-size: 0.9em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: transparent !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
LINKS
|
||||||
|
============================================ */
|
||||||
|
a {
|
||||||
|
color: var(--nes-sky) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: color 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: var(--nes-light-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SCROLLBARS
|
||||||
|
============================================ */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--nes-dark-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--nes-gray);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 2px solid var(--nes-dark-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--nes-light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
MISC UI REFINEMENTS
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Tooltips */
|
||||||
|
[role="tooltip"],
|
||||||
|
[data-state="open"][data-side] {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown menus */
|
||||||
|
[role="menu"],
|
||||||
|
[role="listbox"] {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="menuitem"]:hover,
|
||||||
|
[role="option"]:hover,
|
||||||
|
[role="menuitem"][data-highlighted],
|
||||||
|
[role="option"][data-highlighted] {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges and pills */
|
||||||
|
[class*="badge"],
|
||||||
|
[class*="pill"],
|
||||||
|
[class*="tag"] {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus visible for accessibility */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--nes-light-blue) !important;
|
||||||
|
outline-offset: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
CELL OUTPUT AREA
|
||||||
|
============================================ */
|
||||||
|
[data-testid="outputs"],
|
||||||
|
.output-area {
|
||||||
|
background: rgba(26, 26, 64, 0.5) !important;
|
||||||
|
border-top: 2px solid var(--nes-gray) !important;
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DataFrame outputs */
|
||||||
|
[class*="dataframe"] {
|
||||||
|
font-family: var(--marimo-monospace-font) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plot/chart containers */
|
||||||
|
[class*="plot"],
|
||||||
|
[class*="chart"],
|
||||||
|
svg {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
449
notebooks/sample.py
Normal file
449
notebooks/sample.py
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
# ruff: noqa
|
||||||
|
|
||||||
|
import marimo
|
||||||
|
|
||||||
|
__generated_with = "0.9.14"
|
||||||
|
app = marimo.App(
|
||||||
|
layout_file="layouts/sample.slides.json")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
# Sample Markdown Block
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Header 1
|
||||||
|
## Header 2
|
||||||
|
### Header 3
|
||||||
|
#### Header 4
|
||||||
|
##### Header 5
|
||||||
|
###### Header 6
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Bold** | *Italic:* | ~~Strikethrough~~ | `Inline Code`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> "The only way to do great work is to love what you do."
|
||||||
|
> — *Steve Jobs*
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
---
|
||||||
|
|
||||||
|
1. Item One
|
||||||
|
2. Item Two
|
||||||
|
3. Item Three
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- Bullet One
|
||||||
|
- Bullet Two
|
||||||
|
- Bullet Three
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
1. Main Item
|
||||||
|
- Sub Item
|
||||||
|
- Sub-sub Item
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Marimo](https://marimo.io/)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
"""
|
||||||
|
| Syntax | Description | Example |
|
||||||
|
|-------------|-------------|-------------------|
|
||||||
|
| **Header** | Row Title | Content Here |
|
||||||
|
| **Row 1** | First Cell | `Some code` |
|
||||||
|
| **Row 2** | Second Cell | *Italicized text* |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
def greet(name: str) -> str:
|
||||||
|
\"""Return a greeting message.\"""
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Here is a sentence with a footnote reference[^1].
|
||||||
|
|
||||||
|
[^1]: This is the footnote content.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
- Inline: $E = mc^2$
|
||||||
|
- Block:
|
||||||
|
|
||||||
|
$$
|
||||||
|
f(x) = \\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx
|
||||||
|
$$
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(r"""# Incomplete Reference of Different Args' Use""")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
## `bg-muted` & `text-muted-foreground`
|
||||||
|
|
||||||
|
- hover on `fullscreen` and `cell action` buttons at the right of each cell
|
||||||
|
- The banner of collapsed cells
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
## `bg-popover` & `text-popover-foreground`
|
||||||
|
|
||||||
|
- `Snippets` side panel
|
||||||
|
- `Menu` items: `Share`, `Download`, `Helper panel`, `Present as`
|
||||||
|
- The popover snippets when you enter `:` in markdown cell
|
||||||
|
- The popover snippets when you enter code in python cell
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
## `bg-card` & `text-card-foreground`
|
||||||
|
|
||||||
|
- even rows of markdown table
|
||||||
|
|
||||||
|
| Syntax | Description | Example |
|
||||||
|
|-------------|-------------|-------------------|
|
||||||
|
| **Header** | Row Title | Content Here |
|
||||||
|
| **Row 1** | First Cell | `Some code` |
|
||||||
|
| **Row 2** | Second Cell | *Italicized text* |
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
## `bg-primary & text-primary-foreground`
|
||||||
|
|
||||||
|
- `machine stats` on the right bottom of notebook
|
||||||
|
- the header of `Documentation` panel
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md("""## `bg-secondary & text-secondary-foreground`""")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
## `bg-accent` & `text-accent-foreground`
|
||||||
|
|
||||||
|
- Floating Outline on the right center of the notebook
|
||||||
|
- Side panels on the left of the notebook
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
## `destructive` & `destructive-foreground`
|
||||||
|
|
||||||
|
- delete button in each cell
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
r"""
|
||||||
|
## other args
|
||||||
|
|
||||||
|
- `border`: borders of cells, buttons, etc.
|
||||||
|
- `input`: markdown table separator line between header and content
|
||||||
|
- ...
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(r"""# Marimo UI Components""")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.hstack(
|
||||||
|
[
|
||||||
|
mo.ui.button(
|
||||||
|
label="Danger",
|
||||||
|
kind="danger",
|
||||||
|
),
|
||||||
|
mo.ui.button(label="Info", kind="info"),
|
||||||
|
mo.ui.button(
|
||||||
|
label="Neutral",
|
||||||
|
kind="neutral",
|
||||||
|
),
|
||||||
|
mo.ui.button(
|
||||||
|
label="Success",
|
||||||
|
kind="success",
|
||||||
|
),
|
||||||
|
mo.ui.button(
|
||||||
|
label="Warn",
|
||||||
|
kind="warn",
|
||||||
|
),
|
||||||
|
mo.ui.button(
|
||||||
|
label="Alert",
|
||||||
|
kind="alert",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
justify="space-around",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.chat(model="")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.checkbox()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.code_editor()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(data, mo) -> None:
|
||||||
|
mo.ui.dataframe(data.cars())
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(data, mo) -> None:
|
||||||
|
mo.ui.data_explorer(data.cars())
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.date(
|
||||||
|
value="2022-06-01",
|
||||||
|
start="2022-01-01",
|
||||||
|
stop="2022-12-31",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.dropdown(
|
||||||
|
options={"one": 1, "two": 2, "three": 3},
|
||||||
|
value="one",
|
||||||
|
label="pick a number",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.file(kind="area")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.file_browser()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.md(
|
||||||
|
"""
|
||||||
|
**Your form.**
|
||||||
|
|
||||||
|
{name}
|
||||||
|
|
||||||
|
{date}
|
||||||
|
"""
|
||||||
|
).batch(
|
||||||
|
name=mo.ui.text(label="name"),
|
||||||
|
date=mo.ui.date(label="date"),
|
||||||
|
).form(show_clear_button=True, bordered=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.microphone()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.multiselect(options=["a", "b", "c"], label="choose some options")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.number(start=1, stop=10, step=2)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.radio(
|
||||||
|
options={"one": 1, "two": 2, "three": 3},
|
||||||
|
value="one",
|
||||||
|
label="pick a number",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.range_slider(start=1, stop=10, step=2, value=[2, 6], show_value=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.refresh(
|
||||||
|
options=["1m", "5m 30s", "10m"],
|
||||||
|
default_interval="10m",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.switch()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(data, mo) -> None:
|
||||||
|
mo.ui.table(
|
||||||
|
data.cars(),
|
||||||
|
show_column_summaries=True,
|
||||||
|
selection="multi",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo):
|
||||||
|
tab1 = mo.vstack(
|
||||||
|
[
|
||||||
|
mo.ui.slider(1, 10),
|
||||||
|
mo.ui.text(),
|
||||||
|
mo.ui.date(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
tab2 = mo.md("You can show arbitrary content in a tab.")
|
||||||
|
|
||||||
|
tabs = mo.ui.tabs({"Heading 1": tab1, "Heading 2": tab2})
|
||||||
|
tabs
|
||||||
|
return tab1, tab2, tabs
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.hstack(
|
||||||
|
[
|
||||||
|
mo.ui.text(value="Hello, Text", kind="text"),
|
||||||
|
mo.ui.text(value="Hello, Password", kind="password"),
|
||||||
|
mo.ui.text(value="Hello, Email", kind="email"),
|
||||||
|
mo.ui.text(value="Hello, URL", kind="url"),
|
||||||
|
],
|
||||||
|
justify="space-around",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.ui.text_area()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.accordion(
|
||||||
|
{
|
||||||
|
"Door 1": mo.md("Nothing!"),
|
||||||
|
"Door 2": mo.md("Nothing!"),
|
||||||
|
"Door 3": mo.md(
|
||||||
|
""
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.carousel(
|
||||||
|
[
|
||||||
|
mo.md("# Introduction"),
|
||||||
|
"By the marimo team",
|
||||||
|
mo.md("## What is marimo?"),
|
||||||
|
mo.md(""),
|
||||||
|
mo.md("## Questions?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __(mo) -> None:
|
||||||
|
mo.vstack(
|
||||||
|
[
|
||||||
|
mo.callout("This is a info callout", kind="info"),
|
||||||
|
mo.callout("This is a neutral callout", kind="neutral"),
|
||||||
|
mo.callout("This is a warn callout", kind="warn"),
|
||||||
|
mo.callout("This is a danger callout", kind="danger"),
|
||||||
|
mo.callout("This is a success callout", kind="success"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __():
|
||||||
|
from vega_datasets import data
|
||||||
|
|
||||||
|
return (data,)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def __():
|
||||||
|
import marimo as mo
|
||||||
|
|
||||||
|
return (mo,)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
37
prometheus/prometheus.yml
Normal file
37
prometheus/prometheus.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
evaluation_interval: 15s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
# Prometheus self-monitoring
|
||||||
|
- job_name: 'prometheus'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:9090']
|
||||||
|
|
||||||
|
# Jaeger metrics
|
||||||
|
- job_name: 'jaeger'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['jaeger:14269']
|
||||||
|
|
||||||
|
# Loki metrics
|
||||||
|
- job_name: 'loki'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['loki:3100']
|
||||||
|
|
||||||
|
# Trino metrics (if enabled)
|
||||||
|
- job_name: 'trino'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['trino:8080']
|
||||||
|
metrics_path: /v1/status
|
||||||
|
|
||||||
|
# Nessie metrics
|
||||||
|
- job_name: 'nessie'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['nessie:19120']
|
||||||
|
metrics_path: /q/metrics
|
||||||
|
|
||||||
|
# Traefik metrics
|
||||||
|
- job_name: 'traefik'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['traefik:8080']
|
||||||
|
metrics_path: /metrics
|
||||||
15
traefik/dynamic/middlewares.yml
Normal file
15
traefik/dynamic/middlewares.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Dynamic Traefik Configuration - Middlewares
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
# Security headers
|
||||||
|
secure-headers:
|
||||||
|
headers:
|
||||||
|
frameDeny: true
|
||||||
|
browserXssFilter: true
|
||||||
|
contentTypeNosniff: true
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
rate-limit:
|
||||||
|
rateLimit:
|
||||||
|
average: 100
|
||||||
|
burst: 50
|
||||||
168
traefik/dynamic/services.yml
Normal file
168
traefik/dynamic/services.yml
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# Dynamic Traefik Configuration - Services
|
||||||
|
# All services defined manually instead of Docker provider
|
||||||
|
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
# Dashboard
|
||||||
|
dashboard:
|
||||||
|
rule: "Host(`dashboard.home.fhirworx.io`) || Host(`home.fhirworx.io`)"
|
||||||
|
service: dashboard
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Gitea
|
||||||
|
gitea:
|
||||||
|
rule: "Host(`gitea.home.fhirworx.io`)"
|
||||||
|
service: gitea
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Woodpecker CI
|
||||||
|
woodpecker:
|
||||||
|
rule: "Host(`ci.home.fhirworx.io`)"
|
||||||
|
service: woodpecker
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Notebooks
|
||||||
|
notebooks:
|
||||||
|
rule: "Host(`notebooks.home.fhirworx.io`)"
|
||||||
|
service: notebooks
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Zotero
|
||||||
|
zotero:
|
||||||
|
rule: "Host(`zotero.home.fhirworx.io`)"
|
||||||
|
service: zotero
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Nessie
|
||||||
|
nessie:
|
||||||
|
rule: "Host(`nessie.home.fhirworx.io`)"
|
||||||
|
service: nessie
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Trino
|
||||||
|
trino:
|
||||||
|
rule: "Host(`trino.home.fhirworx.io`)"
|
||||||
|
service: trino
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# RustFS S3 API
|
||||||
|
rustfs-api:
|
||||||
|
rule: "Host(`s3.home.fhirworx.io`)"
|
||||||
|
service: rustfs-api
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# RustFS Console
|
||||||
|
rustfs-console:
|
||||||
|
rule: "Host(`minio.home.fhirworx.io`)"
|
||||||
|
service: rustfs-console
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Grafana
|
||||||
|
grafana:
|
||||||
|
rule: "Host(`grafana.home.fhirworx.io`)"
|
||||||
|
service: grafana
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Prometheus
|
||||||
|
prometheus:
|
||||||
|
rule: "Host(`prometheus.home.fhirworx.io`)"
|
||||||
|
service: prometheus
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Jaeger
|
||||||
|
jaeger:
|
||||||
|
rule: "Host(`jaeger.home.fhirworx.io`)"
|
||||||
|
service: jaeger
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Loki
|
||||||
|
loki:
|
||||||
|
rule: "Host(`loki.home.fhirworx.io`)"
|
||||||
|
service: loki
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
# Traefik Dashboard
|
||||||
|
traefik:
|
||||||
|
rule: "Host(`traefik.home.fhirworx.io`)"
|
||||||
|
service: api@internal
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
services:
|
||||||
|
dashboard:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://dashboard:80"
|
||||||
|
|
||||||
|
gitea:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://gitea:3000"
|
||||||
|
|
||||||
|
woodpecker:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://woodpecker-server:8000"
|
||||||
|
|
||||||
|
notebooks:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://notebooks:2718"
|
||||||
|
|
||||||
|
zotero:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://zotero:8080"
|
||||||
|
|
||||||
|
nessie:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://nessie:19120"
|
||||||
|
|
||||||
|
trino:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://trino:8080"
|
||||||
|
|
||||||
|
rustfs-api:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://rustfs:9000"
|
||||||
|
|
||||||
|
rustfs-console:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://rustfs:9001"
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://grafana:3000"
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://prometheus:9090"
|
||||||
|
|
||||||
|
jaeger:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://jaeger:16686"
|
||||||
|
|
||||||
|
loki:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://loki:3100"
|
||||||
34
traefik/traefik.yml
Normal file
34
traefik/traefik.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Traefik Static Configuration
|
||||||
|
api:
|
||||||
|
dashboard: true
|
||||||
|
insecure: true
|
||||||
|
|
||||||
|
# Enable Prometheus metrics
|
||||||
|
metrics:
|
||||||
|
prometheus:
|
||||||
|
addEntryPointsLabels: true
|
||||||
|
addRoutersLabels: true
|
||||||
|
addServicesLabels: true
|
||||||
|
|
||||||
|
entryPoints:
|
||||||
|
web:
|
||||||
|
address: ":80"
|
||||||
|
websecure:
|
||||||
|
address: ":443"
|
||||||
|
|
||||||
|
providers:
|
||||||
|
file:
|
||||||
|
directory: /etc/traefik/dynamic
|
||||||
|
watch: true
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: INFO
|
||||||
|
|
||||||
|
accessLog: {}
|
||||||
|
|
||||||
|
# Enable OpenTelemetry tracing
|
||||||
|
tracing:
|
||||||
|
otlp:
|
||||||
|
grpc:
|
||||||
|
endpoint: jaeger:4317
|
||||||
|
insecure: true
|
||||||
11
trino/etc/catalog/iceberg.properties
Normal file
11
trino/etc/catalog/iceberg.properties
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
connector.name=iceberg
|
||||||
|
iceberg.catalog.type=nessie
|
||||||
|
iceberg.nessie-catalog.uri=http://nessie:19120/api/v2
|
||||||
|
iceberg.nessie-catalog.ref=main
|
||||||
|
iceberg.nessie-catalog.default-warehouse-dir=s3://lakehouse/
|
||||||
|
fs.native-s3.enabled=true
|
||||||
|
s3.endpoint=http://rustfs:9000
|
||||||
|
s3.path-style-access=true
|
||||||
|
s3.region=us-east-1
|
||||||
|
s3.aws-access-key=${ENV:NESSIE_S3_ACCESS_KEY}
|
||||||
|
s3.aws-secret-key=${ENV:NESSIE_S3_SECRET_KEY}
|
||||||
8
trino/etc/config.properties
Normal file
8
trino/etc/config.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
coordinator=true
|
||||||
|
node-scheduler.include-coordinator=true
|
||||||
|
http-server.http.port=8080
|
||||||
|
discovery.uri=http://localhost:8080
|
||||||
|
|
||||||
|
# OpenTelemetry tracing
|
||||||
|
tracing.enabled=true
|
||||||
|
tracing.exporter.endpoint=http://jaeger:4317
|
||||||
10
trino/etc/jvm.config
Normal file
10
trino/etc/jvm.config
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-server
|
||||||
|
-Xmx4G
|
||||||
|
-XX:InitialRAMPercentage=80
|
||||||
|
-XX:MaxRAMPercentage=80
|
||||||
|
-XX:+UseG1GC
|
||||||
|
-XX:G1HeapRegionSize=32M
|
||||||
|
-XX:+ExplicitGCInvokesConcurrent
|
||||||
|
-XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
-XX:+ExitOnOutOfMemoryError
|
||||||
|
-Djdk.attach.allowAttachSelf=true
|
||||||
3
trino/etc/node.properties
Normal file
3
trino/etc/node.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node.environment=production
|
||||||
|
node.id=trino-coordinator
|
||||||
|
node.data-dir=/data/trino
|
||||||
381
woodpecker/custom.css
Normal file
381
woodpecker/custom.css
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
/* NES SPORTS THEME FOR WOODPECKER CI - RBI Baseball / Tecmo Super Bowl */
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* NES Color Palette */
|
||||||
|
--nes-black: #0f0f0f;
|
||||||
|
--nes-dark-gray: #1a1a2e;
|
||||||
|
--nes-gray: #4a4a68;
|
||||||
|
--nes-light-gray: #9090a8;
|
||||||
|
--nes-white: #f0f0f0;
|
||||||
|
|
||||||
|
--nes-dark-blue: #1a1a40;
|
||||||
|
--nes-blue: #2840a0;
|
||||||
|
--nes-mid-blue: #4060c0;
|
||||||
|
--nes-light-blue: #70a0e0;
|
||||||
|
--nes-sky: #90c0f0;
|
||||||
|
|
||||||
|
--nes-red: #d03030;
|
||||||
|
--nes-orange: #e07020;
|
||||||
|
--nes-yellow: #e0b040;
|
||||||
|
--nes-green: #40a040;
|
||||||
|
--nes-light-green: #80d080;
|
||||||
|
--nes-cyan: #40b0c0;
|
||||||
|
--nes-cream: #e8d8c0;
|
||||||
|
|
||||||
|
/* Woodpecker color overrides */
|
||||||
|
--color-wp-primary-100: var(--nes-sky);
|
||||||
|
--color-wp-primary-200: var(--nes-light-blue);
|
||||||
|
--color-wp-primary-300: var(--nes-mid-blue);
|
||||||
|
--color-wp-primary-400: var(--nes-blue);
|
||||||
|
--color-wp-primary-500: var(--nes-dark-blue);
|
||||||
|
|
||||||
|
--color-wp-success-100: var(--nes-light-green);
|
||||||
|
--color-wp-success-200: var(--nes-green);
|
||||||
|
|
||||||
|
--color-wp-error-100: #f08080;
|
||||||
|
--color-wp-error-200: var(--nes-red);
|
||||||
|
|
||||||
|
--color-wp-warning-100: var(--nes-yellow);
|
||||||
|
--color-wp-warning-200: var(--nes-orange);
|
||||||
|
|
||||||
|
--color-wp-gray-100: var(--nes-white);
|
||||||
|
--color-wp-gray-200: var(--nes-light-gray);
|
||||||
|
--color-wp-gray-300: var(--nes-gray);
|
||||||
|
--color-wp-gray-400: var(--nes-dark-gray);
|
||||||
|
--color-wp-gray-500: var(--nes-dark-blue);
|
||||||
|
--color-wp-gray-600: var(--nes-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global styles */
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
background: linear-gradient(180deg, #0a0a28 0%, #1a1a40 30%, #1a1a40 70%, #0a0a28 100%) !important;
|
||||||
|
background-attachment: fixed !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headings */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: 'Press Start 2P', monospace !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
|
line-height: 1.8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: 18px !important; color: var(--nes-light-green) !important; }
|
||||||
|
h2 { font-size: 14px !important; color: var(--nes-sky) !important; }
|
||||||
|
h3 { font-size: 12px !important; color: var(--nes-yellow) !important; }
|
||||||
|
|
||||||
|
/* Header/Navbar */
|
||||||
|
header,
|
||||||
|
nav,
|
||||||
|
.navbar,
|
||||||
|
[class*="header"],
|
||||||
|
[class*="Header"] {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border-bottom: 3px solid var(--nes-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
aside,
|
||||||
|
[class*="sidebar"],
|
||||||
|
[class*="Sidebar"] {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border-right: 3px solid var(--nes-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main content area */
|
||||||
|
main,
|
||||||
|
[class*="content"],
|
||||||
|
[class*="Content"] {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards and panels */
|
||||||
|
[class*="card"],
|
||||||
|
[class*="Card"],
|
||||||
|
[class*="panel"],
|
||||||
|
[class*="Panel"],
|
||||||
|
[class*="box"],
|
||||||
|
[class*="Box"] {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
button,
|
||||||
|
[class*="btn"],
|
||||||
|
[class*="Btn"],
|
||||||
|
[class*="button"],
|
||||||
|
[class*="Button"] {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
transition: all 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Primary buttons */
|
||||||
|
[class*="primary"],
|
||||||
|
[class*="Primary"],
|
||||||
|
button[type="submit"] {
|
||||||
|
background: var(--nes-mid-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-light-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="primary"]:hover,
|
||||||
|
[class*="Primary"]:hover,
|
||||||
|
button[type="submit"]:hover {
|
||||||
|
background: var(--nes-light-blue) !important;
|
||||||
|
color: var(--nes-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Success/green buttons */
|
||||||
|
[class*="success"],
|
||||||
|
[class*="Success"],
|
||||||
|
[class*="green"],
|
||||||
|
[class*="Green"] {
|
||||||
|
background: var(--nes-green) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="success"]:hover,
|
||||||
|
[class*="Success"]:hover {
|
||||||
|
background: var(--nes-light-green) !important;
|
||||||
|
color: var(--nes-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Danger/red buttons */
|
||||||
|
[class*="danger"],
|
||||||
|
[class*="Danger"],
|
||||||
|
[class*="error"],
|
||||||
|
[class*="Error"],
|
||||||
|
[class*="red"],
|
||||||
|
[class*="Red"] {
|
||||||
|
background: var(--nes-red) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
textarea:focus,
|
||||||
|
select:focus {
|
||||||
|
border-color: var(--nes-light-blue) !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(112, 160, 224, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
table {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
font-family: 'Press Start 2P', monospace !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
padding: 10px 12px !important;
|
||||||
|
border: 1px solid var(--nes-mid-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
color: var(--nes-cream) !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
border: 1px solid var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) td {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover td {
|
||||||
|
background: var(--nes-dark-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pipeline/build status colors */
|
||||||
|
[class*="running"],
|
||||||
|
[class*="Running"],
|
||||||
|
[class*="pending"],
|
||||||
|
[class*="Pending"] {
|
||||||
|
color: var(--nes-yellow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="success"],
|
||||||
|
[class*="Success"],
|
||||||
|
[class*="passed"],
|
||||||
|
[class*="Passed"] {
|
||||||
|
color: var(--nes-light-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="failure"],
|
||||||
|
[class*="Failure"],
|
||||||
|
[class*="failed"],
|
||||||
|
[class*="Failed"],
|
||||||
|
[class*="error"],
|
||||||
|
[class*="Error"] {
|
||||||
|
color: var(--nes-red) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build logs */
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
[class*="log"],
|
||||||
|
[class*="Log"],
|
||||||
|
[class*="console"],
|
||||||
|
[class*="Console"] {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
color: var(--nes-cream) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
code {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
color: var(--nes-light-green) !important;
|
||||||
|
padding: 2px 6px !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
a {
|
||||||
|
color: var(--nes-sky) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdowns */
|
||||||
|
[class*="dropdown"],
|
||||||
|
[class*="Dropdown"],
|
||||||
|
[class*="menu"],
|
||||||
|
[class*="Menu"],
|
||||||
|
[class*="popover"],
|
||||||
|
[class*="Popover"] {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="dropdown"] li:hover,
|
||||||
|
[class*="menu"] li:hover,
|
||||||
|
[role="menuitem"]:hover,
|
||||||
|
[role="option"]:hover {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges and tags */
|
||||||
|
[class*="badge"],
|
||||||
|
[class*="Badge"],
|
||||||
|
[class*="tag"],
|
||||||
|
[class*="Tag"],
|
||||||
|
[class*="label"],
|
||||||
|
[class*="Label"] {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltips */
|
||||||
|
[role="tooltip"],
|
||||||
|
[class*="tooltip"],
|
||||||
|
[class*="Tooltip"] {
|
||||||
|
font-family: 'IBM Plex Mono', monospace !important;
|
||||||
|
background: var(--nes-black) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress bars */
|
||||||
|
progress,
|
||||||
|
[class*="progress"],
|
||||||
|
[class*="Progress"] {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-webkit-progress-bar {
|
||||||
|
background: var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-webkit-progress-value {
|
||||||
|
background: var(--nes-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbars */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--nes-dark-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--nes-gray);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--nes-light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection */
|
||||||
|
::selection {
|
||||||
|
background: var(--nes-blue) !important;
|
||||||
|
color: var(--nes-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icons - ensure visibility */
|
||||||
|
svg,
|
||||||
|
[class*="icon"],
|
||||||
|
[class*="Icon"] {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avatar images */
|
||||||
|
img[class*="avatar"],
|
||||||
|
[class*="avatar"] img {
|
||||||
|
border: 2px solid var(--nes-gray) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Repo/pipeline list items */
|
||||||
|
[class*="item"],
|
||||||
|
[class*="Item"],
|
||||||
|
[class*="list-item"],
|
||||||
|
[class*="ListItem"] {
|
||||||
|
border-bottom: 1px solid var(--nes-dark-gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="item"]:hover,
|
||||||
|
[class*="Item"]:hover {
|
||||||
|
background: rgba(64, 96, 192, 0.1) !important;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user