2 Commits
7 changed files with 54 additions and 9 deletions

No files matched your search

+12 -1
View File
@@ -19,7 +19,14 @@ Ollama does not need to run on the same host — `OLLAMA_URL` can point to any r
| Port | Service |
|------|---------|
| `8000` | Proxy endpoint (OpenAI API) |
| `8001` | Admin API + web interface (do not expose) |
| `8001` | Admin API + web interface |
Port 8001 must be exposed because the container serves the admin interface directly on this port. To restrict access to the local machine, bind it to `127.0.0.1` — this makes the port reachable only from the host, not from the network:
```
ports:
- "127.0.0.1:8001:8001"
```
## Environment Variables
@@ -46,6 +53,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://host.docker.internal:11434 # or http://<ip>:11434
@@ -71,6 +79,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://host.docker.internal:11434 # or http://<ip>:11434
@@ -115,6 +124,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://ollama:11434
@@ -147,6 +157,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://ollama:11434
+12 -1
View File
@@ -19,7 +19,14 @@ Ollama muss dabei nicht auf demselben Host laufen — `OLLAMA_URL` kann auf jede
| Port | Dienst |
|------|--------|
| `8000` | Proxy-Endpunkt (OpenAI-API) |
| `8001` | Admin-API + Web-Oberfläche (nicht exponieren) |
| `8001` | Admin-API + Web-Oberfläche |
Port 8001 muss exposed werden, da der Container die Admin-Oberfläche selbst auf diesem Port ausliefert. Um den Zugriff auf den lokalen Rechner zu beschränken, die Portbindung auf `127.0.0.1` setzen — so ist der Port nur vom Host erreichbar, nicht aus dem Netzwerk:
```
ports:
- "127.0.0.1:8001:8001"
```
## Umgebungsvariablen
@@ -46,6 +53,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://host.docker.internal:11434 # oder http://<ip>:11434
@@ -71,6 +79,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://host.docker.internal:11434 # oder http://<ip>:11434
@@ -115,6 +124,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://ollama:11434
@@ -147,6 +157,7 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
- "127.0.0.1:8001:8001"
environment:
ADMIN_PASSWORD: changeme
OLLAMA_URL: http://ollama:11434
+2
View File
@@ -47,6 +47,8 @@ async def read_api_keys(
item.tokens_used_month = usage.tokens_used_month or 0
item.requests_today = usage.requests_today or 0
item.requests_month = usage.requests_month or 0
item.daily_reset_at = usage.daily_reset_at
item.monthly_reset_at = usage.monthly_reset_at
result.append(item)
return result
+2
View File
@@ -58,6 +58,8 @@ class APIKeyWithUsage(APIKey):
tokens_used_month: int = 0
requests_today: int = 0
requests_month: int = 0
daily_reset_at: Optional[datetime] = None
monthly_reset_at: Optional[datetime] = None
class Config:
from_attributes = True
+1
View File
@@ -5,6 +5,7 @@ services:
env_file: .env
ports:
- "${PROXY_PORT:-8000}:${PROXY_PORT:-8000}"
- "127.0.0.1:8001:8001"
volumes:
- ./backend/test.db:/app/backend/test.db
- ./backend/logs:/app/backend/logs
+18 -7
View File
@@ -11,17 +11,28 @@ function authHeaders(token) {
const fmtK = (n) => { const k = n / 1000; return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`; };
function QuotaBar({ used, limit, isToken = false }) {
if (limit == null) return <span className="quota-unlimited">∞</span>;
function QuotaBar({ used, limit, isToken = false, since = null }) {
const fmt = isToken ? fmtK : (n) => n.toLocaleString('de-DE');
const sinceLabel = since
? new Date(since).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' })
: null;
if (limit == null) return (
<div className="quota-cell">
<span className="quota-unlimited">∞</span>
{sinceLabel && <span className="quota-since">seit {sinceLabel}</span>}
</div>
);
const pct = Math.min(100, (used / limit) * 100);
const color = pct >= 90 ? '#e74c3c' : pct >= 70 ? '#e67e22' : '#27ae60';
const fmt = isToken ? fmtK : (n) => n.toLocaleString('de-DE');
return (
<div className="quota-cell">
<span className="quota-label">{fmt(used)} / {fmt(limit)}</span>
<div className="progress-bar">
<div className="progress-fill" style={{ width: `${pct}%`, backgroundColor: color }} />
</div>
{sinceLabel && <span className="quota-since">seit {sinceLabel}</span>}
</div>
);
}
@@ -400,10 +411,10 @@ function App() {
<td>{key.name}</td>
<td>{displayKey(key.key_prefix)}</td>
<td>{key.expires_at ? new Date(key.expires_at).toLocaleDateString('de-DE', { timeZone: 'Europe/Berlin' }) : '∞'}</td>
<td><QuotaBar used={key.tokens_used_today} limit={key.daily_tokens} isToken /></td>
<td><QuotaBar used={key.tokens_used_month} limit={key.monthly_tokens} isToken /></td>
<td><QuotaBar used={key.requests_today} limit={key.daily_requests} /></td>
<td><QuotaBar used={key.requests_month} limit={key.monthly_requests} /></td>
<td><QuotaBar used={key.tokens_used_today} limit={key.daily_tokens} isToken since={key.daily_reset_at} /></td>
<td><QuotaBar used={key.tokens_used_month} limit={key.monthly_tokens} isToken since={key.monthly_reset_at} /></td>
<td><QuotaBar used={key.requests_today} limit={key.daily_requests} since={key.daily_reset_at} /></td>
<td><QuotaBar used={key.requests_month} limit={key.monthly_requests} since={key.monthly_reset_at} /></td>
<td className="action-cell">
<button className="btn-icon btn-icon-edit" data-tooltip="Bearbeiten" onClick={() => handleEdit(key)}>✏</button>
{key.is_active ? (
+7
View File
@@ -246,6 +246,13 @@ tr:hover {
font-size: 14px;
}
.quota-since {
display: block;
font-size: 10px;
color: #aaa;
margin-top: 2px;
}
.progress-bar {
height: 4px;
background: #e2e8f0;