Skip to content

Commit 9948fb6

Browse files
committed
Initial TeslaMate dashboard
0 parents  commit 9948fb6

26 files changed

Lines changed: 4886 additions & 0 deletions

.env.example

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Aliyun image location used by compose.yaml
2+
IMAGE_REGISTRY=registry.cn-shenzhen.aliyuncs.com
3+
IMAGE_NAMESPACE=your-namespace
4+
IMAGE_TAG=latest
5+
6+
# Only Nginx is published to the host.
7+
WEB_PORT=8080
8+
BASIC_AUTH_USER=admin
9+
BASIC_AUTH_PASSWORD=change-this-password
10+
11+
# The Docker network where TeslaMate's PostgreSQL container is reachable.
12+
# Common TeslaMate compose network names are teslamate_default or teslamate_teslamate.
13+
TESLAMATE_DOCKER_NETWORK=teslamate_default
14+
15+
# Use a read-only PostgreSQL role. See deploy/create-readonly-user.sql.
16+
# The host should be the database service/container name inside TESLAMATE_DOCKER_NETWORK.
17+
DATABASE_URL=postgresql://dashboard_readonly:replace-with-a-strong-password@database:5432/teslamate
18+
19+
# Optional API tuning
20+
STATEMENT_TIMEOUT_MS=15000
21+
DB_POOL_MIN_SIZE=1
22+
DB_POOL_MAX_SIZE=5

.github/workflows/publish.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Build and Push Images
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags:
8+
- "v*"
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
packages: write
14+
15+
jobs:
16+
publish:
17+
runs-on: ubuntu-latest
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
include:
22+
- image: tesla-dashboard-api
23+
context: ./backend
24+
- image: tesla-dashboard-web
25+
context: ./frontend
26+
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v4
30+
31+
- name: Set up Docker Buildx
32+
uses: docker/setup-buildx-action@v3
33+
34+
- name: Login to Aliyun Container Registry
35+
uses: docker/login-action@v3
36+
with:
37+
registry: ${{ secrets.ALIYUN_REGISTRY }}
38+
username: ${{ secrets.ALIYUN_USERNAME }}
39+
password: ${{ secrets.ALIYUN_PASSWORD }}
40+
41+
- name: Prepare tags
42+
id: tags
43+
shell: bash
44+
env:
45+
REGISTRY: ${{ secrets.ALIYUN_REGISTRY }}
46+
NAMESPACE: ${{ secrets.ALIYUN_NAMESPACE }}
47+
IMAGE: ${{ matrix.image }}
48+
run: |
49+
set -euo pipefail
50+
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
51+
VERSION="${GITHUB_REF_NAME}"
52+
else
53+
VERSION="${GITHUB_SHA::12}"
54+
fi
55+
56+
{
57+
echo "tags<<EOF"
58+
echo "${REGISTRY}/${NAMESPACE}/${IMAGE}:${VERSION}"
59+
if [[ "${GITHUB_REF_NAME}" == "main" ]]; then
60+
echo "${REGISTRY}/${NAMESPACE}/${IMAGE}:latest"
61+
fi
62+
echo "EOF"
63+
} >> "$GITHUB_OUTPUT"
64+
65+
- name: Build and push
66+
uses: docker/build-push-action@v6
67+
with:
68+
context: ${{ matrix.context }}
69+
push: true
70+
platforms: linux/amd64
71+
tags: ${{ steps.tags.outputs.tags }}
72+
cache-from: type=gha,scope=${{ matrix.image }}
73+
cache-to: type=gha,mode=max,scope=${{ matrix.image }}

.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.env
2+
.env.*
3+
!.env.example
4+
.htpasswd
5+
*.htpasswd
6+
secrets/
7+
private/
8+
9+
*.pem
10+
*.key
11+
*.crt
12+
*.p12
13+
*.pfx
14+
id_rsa*
15+
id_ed25519*
16+
known_hosts
17+
18+
node_modules/
19+
dist/
20+
build/
21+
.vite/
22+
*.tsbuildinfo
23+
24+
__pycache__/
25+
*.py[cod]
26+
.pytest_cache/
27+
.venv/
28+
venv/
29+
30+
*.log
31+
*.bck
32+
*.dump
33+
*.backup
34+
*.bak
35+
*.sql.bz2
36+
*.sql.gz
37+
*.dump.gz
38+
*.backup.gz
39+
teslamate*.sql
40+
41+
.DS_Store

README.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# TeslaMate Dashboard
2+
3+
一个只读 TeslaMate 报表界面。它不依赖 TeslaMate 的 Web UI 或 Grafana,只读取 TeslaMate PostgreSQL 中 `public` schema 的行程、充电、位置、状态、升级等数据;Nginx 负责对外入口和 Basic Auth,后端与数据库都不暴露到宿主机端口。
4+
5+
## 架构
6+
7+
```text
8+
浏览器
9+
|
10+
| Basic Auth
11+
v
12+
Nginx / Frontend -- 内网 HTTP --> FastAPI Backend -- Docker 内网 --> TeslaMate PostgreSQL
13+
```
14+
15+
默认生产部署只发布 `web``WEB_PORT``api` 只在 Docker 内网暴露 `8000`,TeslaMate、Grafana、PostgreSQL 不需要对外开放端口。
16+
17+
## 功能
18+
19+
- 当前车辆状态、电量、额定续航、里程表、温度、胎压、软件版本
20+
- 周期行程统计:里程、时长、最高速度、估算能耗
21+
- 周期充电统计:充电次数、电量、费用、SOC 增量、进行中的充电
22+
- 日趋势、月汇总、电量/续航趋势、在线/睡眠/离线状态占比
23+
- 最近行程、最近充电、常到地点、充电地点
24+
- 移动端优先的单页界面
25+
26+
## 本地用当前备份验证
27+
28+
当前目录里的 `teslamate.bck` 是 PostgreSQL 18 plain SQL dump,可以直接用本地 compose 恢复测试库:
29+
30+
```bash
31+
docker compose version
32+
```
33+
34+
如果提示 `unknown command: docker compose``unknown shorthand flag: 'f' in -f`,说明没有安装 Docker Compose v2 插件。在 Ubuntu 24.04 上安装:
35+
36+
```bash
37+
sudo apt-get update
38+
sudo apt-get install -y docker-compose-v2
39+
```
40+
41+
```bash
42+
sudo docker compose -f compose.local.yaml up --build
43+
```
44+
45+
访问:
46+
47+
```text
48+
http://localhost:8080
49+
用户名:admin
50+
密码:change-me-local
51+
```
52+
53+
首次启动会导入 `teslamate.bck`,需要等待 PostgreSQL 初始化完成。重新导入时先删除本地 volume:
54+
55+
```bash
56+
sudo docker compose -f compose.local.yaml down -v
57+
sudo docker compose -f compose.local.yaml up --build
58+
```
59+
60+
## 生产部署
61+
62+
### 1. 创建只读数据库用户
63+
64+
在 TeslaMate 的 PostgreSQL 数据库里执行 [deploy/create-readonly-user.sql](deploy/create-readonly-user.sql),先替换密码:
65+
66+
```sql
67+
create user dashboard_readonly with password 'replace-with-a-strong-password';
68+
grant connect on database teslamate to dashboard_readonly;
69+
grant usage on schema public to dashboard_readonly;
70+
grant select on all tables in schema public to dashboard_readonly;
71+
grant select on all sequences in schema public to dashboard_readonly;
72+
alter default privileges for role teslamate in schema public
73+
grant select on tables to dashboard_readonly;
74+
alter role dashboard_readonly set default_transaction_read_only = on;
75+
alter role dashboard_readonly set statement_timeout = '15s';
76+
```
77+
78+
不要给 `private` schema 授权。这样后端无法读取 Tesla API token。
79+
80+
### 2. 找到 TeslaMate Docker 网络
81+
82+
在部署机上查看 TeslaMate 所在网络:
83+
84+
```bash
85+
sudo docker network ls | grep -i tesla
86+
```
87+
88+
常见值是 `teslamate_default``DATABASE_URL` 里的数据库 host 要使用该网络内 PostgreSQL 服务名,常见是 `database`
89+
90+
### 3. 配置环境变量
91+
92+
```bash
93+
cp .env.example .env
94+
```
95+
96+
编辑 `.env`
97+
98+
```dotenv
99+
IMAGE_REGISTRY=registry.cn-shenzhen.aliyuncs.com
100+
IMAGE_NAMESPACE=your-namespace
101+
IMAGE_TAG=latest
102+
103+
WEB_PORT=8080
104+
BASIC_AUTH_USER=admin
105+
BASIC_AUTH_PASSWORD=change-this-password
106+
107+
TESLAMATE_DOCKER_NETWORK=teslamate_default
108+
DATABASE_URL=postgresql://dashboard_readonly:replace-with-a-strong-password@database:5432/teslamate
109+
```
110+
111+
### 4. 启动
112+
113+
先确认部署机有 Docker Compose v2:
114+
115+
```bash
116+
docker compose version
117+
```
118+
119+
Ubuntu 24.04 如果缺失 Compose v2:
120+
121+
```bash
122+
sudo apt-get update
123+
sudo apt-get install -y docker-compose-v2
124+
```
125+
126+
```bash
127+
sudo docker compose pull
128+
sudo docker compose up -d
129+
```
130+
131+
之后访问:
132+
133+
```text
134+
http://部署机IP:8080
135+
```
136+
137+
只要外层还有反向代理或公网入口,把它转发到 `WEB_PORT` 即可;不需要暴露 TeslaMate、Grafana 或 PostgreSQL。
138+
139+
## GitHub Actions 推送到阿里云镜像仓库
140+
141+
工作流在 [.github/workflows/publish.yml](.github/workflows/publish.yml)。在 GitHub 仓库的 `Settings -> Secrets and variables -> Actions` 添加:
142+
143+
| Secret | 示例 |
144+
| --- | --- |
145+
| `ALIYUN_REGISTRY` | `registry.cn-shenzhen.aliyuncs.com` |
146+
| `ALIYUN_NAMESPACE` | `your-namespace` |
147+
| `ALIYUN_USERNAME` | 阿里云镜像仓库用户名 |
148+
| `ALIYUN_PASSWORD` | 阿里云镜像仓库密码或访问凭证 |
149+
150+
推送到 `main` 时会构建并推送:
151+
152+
```text
153+
registry.cn-shenzhen.aliyuncs.com/your-namespace/tesla-dashboard-api:<commit-sha>
154+
registry.cn-shenzhen.aliyuncs.com/your-namespace/tesla-dashboard-api:latest
155+
registry.cn-shenzhen.aliyuncs.com/your-namespace/tesla-dashboard-web:<commit-sha>
156+
registry.cn-shenzhen.aliyuncs.com/your-namespace/tesla-dashboard-web:latest
157+
```
158+
159+
推送 `v*` tag 时会额外使用 tag 名作为镜像 tag。
160+
161+
## 直接开发
162+
163+
后端:
164+
165+
```bash
166+
cd backend
167+
python -m venv .venv
168+
. .venv/bin/activate
169+
pip install -r requirements.txt
170+
DATABASE_URL=postgresql://teslamate:teslamate@localhost:5432/teslamate uvicorn app.main:app --reload
171+
```
172+
173+
前端:
174+
175+
```bash
176+
cd frontend
177+
npm install
178+
npm run dev
179+
```
180+
181+
前端开发服务器会把 `/api` 代理到 `http://localhost:8000`

backend/.dockerignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
__pycache__/
2+
*.pyc
3+
.pytest_cache/
4+
.venv/
5+
venv/
6+
.env
7+
Dockerfile

backend/Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM python:3.12-slim
2+
3+
ENV PYTHONDONTWRITEBYTECODE=1 \
4+
PYTHONUNBUFFERED=1 \
5+
PIP_NO_CACHE_DIR=1
6+
7+
WORKDIR /app
8+
9+
COPY requirements.txt .
10+
RUN pip install --no-cache-dir -r requirements.txt
11+
12+
COPY app ./app
13+
14+
EXPOSE 8000
15+
16+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]

backend/app/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""TeslaMate read-only dashboard API."""

backend/app/config.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from __future__ import annotations
2+
3+
import os
4+
from dataclasses import dataclass
5+
from functools import lru_cache
6+
7+
8+
@dataclass(frozen=True)
9+
class Settings:
10+
app_name: str
11+
database_url: str
12+
statement_timeout_ms: int
13+
db_pool_min_size: int
14+
db_pool_max_size: int
15+
cors_origins: tuple[str, ...]
16+
17+
18+
def _int_env(name: str, default: int) -> int:
19+
raw = os.getenv(name)
20+
if raw is None or raw == "":
21+
return default
22+
return int(raw)
23+
24+
25+
def _csv_env(name: str) -> tuple[str, ...]:
26+
raw = os.getenv(name, "")
27+
return tuple(item.strip() for item in raw.split(",") if item.strip())
28+
29+
30+
@lru_cache
31+
def get_settings() -> Settings:
32+
return Settings(
33+
app_name=os.getenv("APP_NAME", "TeslaMate Dashboard API"),
34+
database_url=os.getenv(
35+
"DATABASE_URL",
36+
"postgresql://teslamate:teslamate@database:5432/teslamate",
37+
),
38+
statement_timeout_ms=_int_env("STATEMENT_TIMEOUT_MS", 15000),
39+
db_pool_min_size=_int_env("DB_POOL_MIN_SIZE", 1),
40+
db_pool_max_size=_int_env("DB_POOL_MAX_SIZE", 5),
41+
cors_origins=_csv_env("CORS_ORIGINS"),
42+
)

0 commit comments

Comments
 (0)