mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-18 13:54:43 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0edc84c997 | |||
| 0519ed040b | |||
| 80fedc103a | |||
| 5cefd8f8d5 | |||
| 75537a8570 | |||
| bc13706311 | |||
| 3711c84ebe | |||
| 8e79c03d88 | |||
| 9419ed9883 | |||
| 982a335d3f | |||
| 362a6e2ceb |
@@ -0,0 +1,92 @@
|
|||||||
|
name: Docker Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
tags: ["v*.*.*"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
# github.repository as <account>/<repo>
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push-frontend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3.0.0
|
||||||
|
|
||||||
|
- name: Log into registry ${{ env.REGISTRY }}
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3.0.0
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5.0.0
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v5.0.0
|
||||||
|
with:
|
||||||
|
context: ./frontend
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
build-and-push-backend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3.0.0
|
||||||
|
|
||||||
|
- name: Log into registry ${{ env.REGISTRY }}
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3.0.0
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5.0.0
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v5.0.0
|
||||||
|
with:
|
||||||
|
context: ./backend
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
<h1 align="center">🛰️ S H A D O W B R O K E R</h1>
|
<h1 align="center">🛰️ S H A D O W B R O K E R</h1>
|
||||||
<p align="center"><strong>Global Threat Intercept — Real-Time Geospatial Intelligence Platform</strong></p>
|
<p align="center"><strong>Global Threat Intercept — Real-Time Geospatial Intelligence Platform</strong></p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<code>TOP SECRET // SI TK // NOFORN</code>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|

|
||||||
**ShadowBroker** is a real-time, full-spectrum geospatial intelligence dashboard that aggregates live data from dozens of open-source intelligence (OSINT) feeds and renders them on a unified dark-ops map interface. It tracks aircraft, ships, satellites, earthquakes, conflict zones, CCTV networks, GPS jamming, and breaking geopolitical events — all updating in real time.
|
**ShadowBroker** is a real-time, full-spectrum geospatial intelligence dashboard that aggregates live data from dozens of open-source intelligence (OSINT) feeds and renders them on a unified dark-ops map interface. It tracks aircraft, ships, satellites, earthquakes, conflict zones, CCTV networks, GPS jamming, and breaking geopolitical events — all updating in real time.
|
||||||
|
|
||||||
Built with **Next.js**, **MapLibre GL**, **FastAPI**, and **Python**, it's designed for analysts, researchers, and enthusiasts who want a single-pane-of-glass view of global activity.
|
Built with **Next.js**, **MapLibre GL**, **FastAPI**, and **Python**, it's designed for analysts, researchers, and enthusiasts who want a single-pane-of-glass view of global activity.
|
||||||
@@ -83,33 +83,34 @@ Built with **Next.js**, **MapLibre GL**, **FastAPI**, and **Python**, it's desig
|
|||||||
## 🏗️ Architecture
|
## 🏗️ Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────────────────────────────┐
|
┌────────────────────────────────────────────────────────┐
|
||||||
│ FRONTEND (Next.js) │
|
│ FRONTEND (Next.js) │
|
||||||
│ │
|
│ │
|
||||||
│ ┌─────────────┐ ┌──────────┐ ┌─────────────────┐ │
|
│ ┌─────────────┐ ┌──────────┐ ┌───────────────┐ │
|
||||||
│ │ MapLibre GL │ │ NewsFeed │ │ Control Panels │ │
|
│ │ MapLibre GL │ │ NewsFeed │ │ Control Panels│ │
|
||||||
│ │ 2D WebGL │ │ SIGINT │ │ Layers/Filters │ │
|
│ │ 2D WebGL │ │ SIGINT │ │ Layers/Filters│ │
|
||||||
│ │ Map Render │ │ Intel │ │ Markets/Radio │ │
|
│ │ Map Render │ │ Intel │ │ Markets/Radio │ │
|
||||||
│ └──────┬──────┘ └────┬─────┘ └────────┬────────┘ │
|
│ └──────┬──────┘ └────┬─────┘ └───────┬───────┘ │
|
||||||
│ └──────────────┼─────────────────┘ │
|
│ └────────────────┼──────────────────┘ │
|
||||||
│ │ REST API (15s fast / 60s slow│
|
│ │ REST API (15s / 60s) │
|
||||||
├────────────────────────┼─────────────────────────────┤
|
├──────────────────────────┼─────────────────────────────┤
|
||||||
│ BACKEND (FastAPI) │
|
│ BACKEND (FastAPI) │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ ┌─────────────────────┼─────────────────────────┐ │
|
│ ┌───────────────────────┼──────────────────────────┐ │
|
||||||
│ │ Data Fetcher (Scheduler) │ │
|
│ │ Data Fetcher (Scheduler) │ │
|
||||||
│ │ ┌──────────┬──────────┬──────────┬─────────┐ │ │
|
│ │ │ │
|
||||||
│ │ │ OpenSky │ adsb.lol │ N2YO │ USGS │ │ │
|
│ │ ┌──────────┬──────────┬──────────┬───────────┐ │ │
|
||||||
│ │ │ Flights │ Military │ Sats │ Quakes │ │ │
|
│ │ │ OpenSky │ adsb.lol │ N2YO │ USGS │ │ │
|
||||||
│ │ ├──────────┼──────────┼──────────┼─────────┤ │ │
|
│ │ │ Flights │ Military │ Sats │ Quakes │ │ │
|
||||||
│ │ │ AIS WS │ Carrier │ GDELT │ CCTV │ │ │
|
│ │ ├──────────┼──────────┼──────────┼───────────┤ │ │
|
||||||
│ │ │ Ships │ Tracker │ Conflict │ Cameras │ │ │
|
│ │ │ AIS WS │ Carrier │ GDELT │ CCTV │ │ │
|
||||||
│ │ ├──────────┼──────────┼──────────┼─────────┤ │ │
|
│ │ │ Ships │ Tracker │ Conflict │ Cameras │ │ │
|
||||||
│ │ │ DeepState│ RSS │ Region │ GPS │ │ │
|
│ │ ├──────────┼──────────┼──────────┼───────────┤ │ │
|
||||||
│ │ │ Frontline│ Intel │ Dossier │ Jamming │ │ │
|
│ │ │ DeepState│ RSS │ Region │ GPS │ │ │
|
||||||
│ │ └──────────┴──────────┴──────────┴─────────┘ │ │
|
│ │ │ Frontline│ Intel │ Dossier │ Jamming │ │ │
|
||||||
│ └───────────────────────────────────────────────┘ │
|
│ │ └──────────┴──────────┴──────────┴───────────┘ │ │
|
||||||
└──────────────────────────────────────────────────────┘
|
│ └──────────────────────────────────────────────────┘ │
|
||||||
|
└────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -138,7 +139,68 @@ Built with **Next.js**, **MapLibre GL**, **FastAPI**, and **Python**, it's desig
|
|||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### 🐳 Docker Setup (Recommended for Self-Hosting)
|
||||||
|
|
||||||
|
You can run the dashboard easily using the pre-built Docker images hosted on GitHub Container Registry (GHCR).
|
||||||
|
|
||||||
|
1. Create a `docker-compose.yml` file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
image: ghcr.io/<your-username>/live-risk-dashboard-backend:main
|
||||||
|
container_name: shadowbroker-backend
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- AISSTREAM_API_KEY=${AISSTREAM_API_KEY}
|
||||||
|
- N2YO_API_KEY=${N2YO_API_KEY}
|
||||||
|
# Add other required environment variables here
|
||||||
|
volumes:
|
||||||
|
- backend_data:/app/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: ghcr.io/<your-username>/live-risk-dashboard-frontend:main
|
||||||
|
container_name: shadowbroker-frontend
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
backend_data:
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Create a `.env` file in the same directory with your API keys.
|
||||||
|
2. Run `docker-compose up -d`.
|
||||||
|
3. Access the dashboard at `http://localhost:3000`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📦 Quick Start (No Code Required)
|
||||||
|
|
||||||
|
If you just want to run the dashboard without dealing with terminal commands:
|
||||||
|
|
||||||
|
1. Go to the **[Releases](../../releases)** tab on the right side of this GitHub page.
|
||||||
|
2. Download the `ShadowBroker_v0.2.zip` file.
|
||||||
|
3. Extract the folder to your computer.
|
||||||
|
4. **Windows:** Double-click `start.bat`.
|
||||||
|
**Mac/Linux:** Open terminal, type `chmod +x start.sh`, and run `./start.sh`.
|
||||||
|
5. It will automatically install everything and launch the dashboard!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 💻 Developer Setup
|
||||||
|
|
||||||
|
If you want to modify the code or run from source:
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
- **Node.js** 18+ and **npm**
|
- **Node.js** 18+ and **npm**
|
||||||
- **Python** 3.10+ with `pip`
|
- **Python** 3.10+ with `pip`
|
||||||
@@ -275,8 +337,8 @@ AISSTREAM_API_KEY=your_aisstream_key # Maritime vessel tracking
|
|||||||
N2YO_API_KEY=your_n2yo_key # Satellite position data
|
N2YO_API_KEY=your_n2yo_key # Satellite position data
|
||||||
|
|
||||||
# Optional (enhances data quality)
|
# Optional (enhances data quality)
|
||||||
OPENSKY_USERNAME=your_opensky_user # Higher rate limits for flight data
|
OPENSKY_CLIENT_ID=your_opensky_client_id # Higher rate limits for flight data
|
||||||
OPENSKY_PASSWORD=your_opensky_pass
|
OPENSKY_CLIENT_SECRET=your_opensky_secret
|
||||||
LTA_ACCOUNT_KEY=your_lta_key # Singapore CCTV cameras
|
LTA_ACCOUNT_KEY=your_lta_key # Singapore CCTV cameras
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ba57965389036194d6dd60e6de33d2e1e1bbf20b
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
cctv.db
|
||||||
|
*.json
|
||||||
|
*.txt
|
||||||
|
!requirements.txt
|
||||||
@@ -9,6 +9,13 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Create a non-root user for security
|
||||||
|
RUN adduser --system --uid 1001 backenduser \
|
||||||
|
&& chown -R backenduser /app
|
||||||
|
|
||||||
|
# Switch to the non-root user
|
||||||
|
USER backenduser
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
83ba61e7af89c1dc7b4d9b972e08d3edf3493966
|
5d33551b09405e7e252c6a11f080a6c9eca50f6b
|
||||||
@@ -73,7 +73,7 @@ class OpenSkyClient:
|
|||||||
# User provided credentials
|
# User provided credentials
|
||||||
opensky_client = OpenSkyClient(
|
opensky_client = OpenSkyClient(
|
||||||
client_id=os.environ.get("OPENSKY_CLIENT_ID", "vancecook-api-client"),
|
client_id=os.environ.get("OPENSKY_CLIENT_ID", "vancecook-api-client"),
|
||||||
client_secret=os.environ.get("OPENSKY_CLIENT_SECRET", "0hWKkj1lpZItUMo2usi4YUjqu8I8YOoD")
|
client_secret=os.environ.get("OPENSKY_CLIENT_SECRET", "YOUR_OPENSKY_SECRET")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Throttling and caching for OpenSky to observe the 400 req/day limit
|
# Throttling and caching for OpenSky to observe the 400 req/day limit
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ hex_prefix = hex_code[-2:]
|
|||||||
# Test 1: adsb.fi trace_full
|
# Test 1: adsb.fi trace_full
|
||||||
url1 = f"https://globe.adsb.fi/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json"
|
url1 = f"https://globe.adsb.fi/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json"
|
||||||
print(f"URL1: {url1}")
|
print(f"URL1: {url1}")
|
||||||
r = subprocess.run(["curl.exe", "-s", "--max-time", "10", url1], capture_output=True, text=True, timeout=15)
|
r = subprocess.run(["curl", "-s", "--max-time", "10", url1], capture_output=True, text=True, timeout=15)
|
||||||
if r.stdout.strip().startswith("{"):
|
if r.stdout.strip().startswith("{"):
|
||||||
data = json.loads(r.stdout)
|
data = json.loads(r.stdout)
|
||||||
print(f"SUCCESS! Keys: {list(data.keys())}")
|
print(f"SUCCESS! Keys: {list(data.keys())}")
|
||||||
@@ -28,7 +28,7 @@ else:
|
|||||||
# Test 2: adsb.lol hex lookup
|
# Test 2: adsb.lol hex lookup
|
||||||
url2 = f"https://api.adsb.lol/v2/hex/{hex_code}"
|
url2 = f"https://api.adsb.lol/v2/hex/{hex_code}"
|
||||||
print(f"\nURL2: {url2}")
|
print(f"\nURL2: {url2}")
|
||||||
r2 = subprocess.run(["curl.exe", "-s", "--max-time", "10", url2], capture_output=True, text=True, timeout=15)
|
r2 = subprocess.run(["curl", "-s", "--max-time", "10", url2], capture_output=True, text=True, timeout=15)
|
||||||
if r2.stdout.strip().startswith("{"):
|
if r2.stdout.strip().startswith("{"):
|
||||||
data = json.loads(r2.stdout)
|
data = json.loads(r2.stdout)
|
||||||
if 'ac' in data and data['ac']:
|
if 'ac' in data and data['ac']:
|
||||||
@@ -41,13 +41,13 @@ else:
|
|||||||
# Test 3: Try adsb.lol trace
|
# Test 3: Try adsb.lol trace
|
||||||
url3 = f"https://api.adsb.lol/trace/{hex_code}"
|
url3 = f"https://api.adsb.lol/trace/{hex_code}"
|
||||||
print(f"\nURL3: {url3}")
|
print(f"\nURL3: {url3}")
|
||||||
r3 = subprocess.run(["curl.exe", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "10", url3], capture_output=True, text=True, timeout=15)
|
r3 = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "10", url3], capture_output=True, text=True, timeout=15)
|
||||||
print(f"HTTP status: {r3.stdout}")
|
print(f"HTTP status: {r3.stdout}")
|
||||||
|
|
||||||
# Test 4: Try globe.adsb.lol format
|
# Test 4: Try globe.adsb.lol format
|
||||||
url4 = f"https://globe.adsb.lol/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json"
|
url4 = f"https://globe.adsb.lol/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json"
|
||||||
print(f"\nURL4: {url4}")
|
print(f"\nURL4: {url4}")
|
||||||
r4 = subprocess.run(["curl.exe", "-s", "--max-time", "10", url4], capture_output=True, text=True, timeout=15)
|
r4 = subprocess.run(["curl", "-s", "--max-time", "10", url4], capture_output=True, text=True, timeout=15)
|
||||||
if r4.stdout.strip().startswith("{"):
|
if r4.stdout.strip().startswith("{"):
|
||||||
data = json.loads(r4.stdout)
|
data = json.loads(r4.stdout)
|
||||||
print(f"SUCCESS! Keys: {list(data.keys())}")
|
print(f"SUCCESS! Keys: {list(data.keys())}")
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
zip_name = 'ShadowBroker_v0.1.zip'
|
||||||
|
|
||||||
|
if os.path.exists(zip_name):
|
||||||
|
try:
|
||||||
|
os.remove(zip_name)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to delete old zip: {e}")
|
||||||
|
|
||||||
|
def add_dir(zipf, dir_path, excludes):
|
||||||
|
for root, dirs, files in os.walk(dir_path):
|
||||||
|
dirs[:] = [d for d in dirs if d not in excludes]
|
||||||
|
for f in files:
|
||||||
|
file_path = os.path.join(root, f)
|
||||||
|
zipf.write(file_path, arcname=file_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||||
|
print("Zipping backend...")
|
||||||
|
add_dir(zipf, 'backend', {'venv', '__pycache__'})
|
||||||
|
|
||||||
|
print("Zipping frontend...")
|
||||||
|
add_dir(zipf, 'frontend', {'node_modules', '.next'})
|
||||||
|
|
||||||
|
print("Zipping root files...")
|
||||||
|
zipf.write('docker-compose.yml')
|
||||||
|
zipf.write('start.bat')
|
||||||
|
zipf.write('start.sh')
|
||||||
|
zipf.write('README.md')
|
||||||
|
|
||||||
|
final_size = os.path.getsize(zip_name) / (1024 * 1024)
|
||||||
|
print(f"\n✅ SUCCESS! Created {zip_name}. Final size: {final_size:.2f} MB")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ ERROR creating zip: {e}")
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
README.md
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*
|
||||||
|
eslint.config.mjs
|
||||||
|
postcss.config.mjs
|
||||||
|
tailwind.config.ts
|
||||||
+30
-11
@@ -1,19 +1,38 @@
|
|||||||
FROM node:18-alpine
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
# Copy source code
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
# Expose port
|
FROM base AS runner
|
||||||
EXPOSE 3000
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV production
|
||||||
# Next.js telemetry disable
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
# Start development server
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
CMD ["npm", "run", "dev:frontend"]
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
RUN mkdir .next
|
||||||
|
RUN chown nextjs:nodejs .next
|
||||||
|
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import type { NextConfig } from "next";
|
|||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
transpilePackages: ['react-map-gl', 'mapbox-gl', 'maplibre-gl'],
|
transpilePackages: ['react-map-gl', 'mapbox-gl', 'maplibre-gl'],
|
||||||
|
output: "standalone",
|
||||||
|
typescript: {
|
||||||
|
ignoreBuildErrors: true,
|
||||||
|
},
|
||||||
|
eslint: {
|
||||||
|
ignoreDuringBuilds: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently --names \"NEXT,API\" --prefix-colors \"cyan,yellow\" \"next dev\" \"cd ../backend && venv\\Scripts\\python.exe main.py\"",
|
"dev": "concurrently \"npm run dev:frontend\" \"npm run dev:backend\"",
|
||||||
"dev:frontend": "next dev",
|
"dev:frontend": "next dev",
|
||||||
"dev:backend": "cd ../backend && venv\\Scripts\\python.exe main.py",
|
"dev:backend": "cd ../backend && python -m uvicorn main:app --reload",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mapbox/point-geometry": "^1.1.0",
|
||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/mapbox-gl": "^3.4.1",
|
"@types/mapbox-gl": "^3.4.1",
|
||||||
"framer-motion": "^12.34.3",
|
"framer-motion": "^12.34.3",
|
||||||
|
|||||||
@@ -95,14 +95,7 @@ export default function CesiumViewer({ data, activeLayers, activeFilters, effect
|
|||||||
viewerRef.current.camera.setView({
|
viewerRef.current.camera.setView({
|
||||||
destination: Cesium.Cartesian3.fromDegrees(-95.0, 39.0, 20000000.0)
|
destination: Cesium.Cartesian3.fromDegrees(-95.0, 39.0, 20000000.0)
|
||||||
});
|
});
|
||||||
// Add TomTom Traffic Flow Layer (Raster)
|
// Add Google Photorealistic 3D Tiles if available, otherwise fallback to base
|
||||||
const trafficProvider = new Cesium.UrlTemplateImageryProvider({
|
|
||||||
url: "https://api.tomtom.com/traffic/map/4/tile/flow/relative/{z}/{x}/{y}.png?key=uLndz1KvLDpwetYRwoiw8wKwk9i23sWG",
|
|
||||||
credit: ""
|
|
||||||
});
|
|
||||||
viewerRef.current.trafficLayer = viewerRef.current.imageryLayers.addImageryProvider(trafficProvider);
|
|
||||||
viewerRef.current.trafficLayer.show = false; // default off
|
|
||||||
|
|
||||||
// ── Primitive Collections for Fast Rendering ──
|
// ── Primitive Collections for Fast Rendering ──
|
||||||
const Cesium2 = (window as any).Cesium;
|
const Cesium2 = (window as any).Cesium;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "======================================================="
|
||||||
|
echo " S H A D O W B R O K E R - macOS / Linux Start "
|
||||||
|
echo "======================================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for Node.js
|
||||||
|
if ! command -v npm &> /dev/null; then
|
||||||
|
echo "[!] ERROR: npm is not installed. Please install Node.js (https://nodejs.org/)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for Python
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo "[!] ERROR: python3 is not installed. Please install Python 3.10+ (https://python.org/)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[*] Setting up Backend Environment..."
|
||||||
|
cd backend
|
||||||
|
if [ ! -d "venv" ]; then
|
||||||
|
echo "[*] Creating Python Virtual Environment..."
|
||||||
|
python3 -m venv venv
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[*] Installing Backend dependencies..."
|
||||||
|
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||||
|
# In case someone runs this in Git Bash on Windows
|
||||||
|
source venv/Scripts/activate
|
||||||
|
else
|
||||||
|
source venv/bin/activate
|
||||||
|
fi
|
||||||
|
pip install -r requirements.txt
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo "[*] Setting up Frontend Environment..."
|
||||||
|
cd frontend
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo "[*] Installing Frontend dependencies..."
|
||||||
|
npm install
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "======================================================="
|
||||||
|
echo " 🚀 Starting Services... "
|
||||||
|
echo " Dashboard will be available at: http://localhost:3000"
|
||||||
|
echo " Keep this window open! Note: Initial load takes ~10s "
|
||||||
|
echo "======================================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Start both services (npm run dev automatically calls the python backend on Mac/Linux if scripts are configured cross-platform)
|
||||||
|
npm run dev
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
def create_clean_zip():
|
||||||
|
zip_name = 'ShadowBroker_v0.2.zip'
|
||||||
|
exclude_dirs = {'.git', 'node_modules', 'venv', '.next', '__pycache__'}
|
||||||
|
exclude_files = {zip_name, 'zip_repo.py', '.env', '.env.local'}
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||||
|
for root, dirs, files in os.walk('.'):
|
||||||
|
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
||||||
|
for file in files:
|
||||||
|
if file in exclude_files:
|
||||||
|
continue
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
arcname = os.path.relpath(file_path, '.')
|
||||||
|
zipf.write(file_path, arcname)
|
||||||
|
print(f"Created {zip_name} successfully!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
create_clean_zip()
|
||||||
Reference in New Issue
Block a user