diff --git a/src/models.py b/src/models.py
index 072198c..ae51f2d 100644
--- a/src/models.py
+++ b/src/models.py
@@ -36,7 +36,7 @@ class LicenseParams(BaseModel):
token_budget_usd: Optional[float] = None
credits_total: Optional[int] = None
cost_per_credit: Optional[float] = None
- budget_warning_percent: Optional[int] = Field(default=80, ge=1, le=100)
+ budget_warning_percent: Optional[int] = Field(default=80, ge=0, le=100) # 0 = Warnung aus
unlimited_budget: bool = False
# 'monthly' = Kontingent wird zum Monatswechsel neu gefuellt (der Monitor
# setzt den Verbrauch traege zurueck), 'total' = gilt fuer die Laufzeit.
diff --git a/src/routers/token_usage.py b/src/routers/token_usage.py
index 7996fca..4a19671 100644
--- a/src/routers/token_usage.py
+++ b/src/routers/token_usage.py
@@ -196,8 +196,8 @@ async def update_budget(license_id: int, data: dict, request: Request, admin=Dep
pct = int(data["budget_warning_percent"])
except (TypeError, ValueError):
raise HTTPException(status_code=400, detail="budget_warning_percent muss eine Zahl sein")
- if not 1 <= pct <= 100:
- raise HTTPException(status_code=400, detail="budget_warning_percent muss zwischen 1 und 100 liegen")
+ if not 0 <= pct <= 100:
+ raise HTTPException(status_code=400, detail="budget_warning_percent muss zwischen 0 und 100 liegen (0 = Warnung aus)")
data["budget_warning_percent"] = pct
if "max_users" in data:
try:
diff --git a/src/static/dashboard.html b/src/static/dashboard.html
index 71a9b19..8216a20 100644
--- a/src/static/dashboard.html
+++ b/src/static/dashboard.html
@@ -263,8 +263,8 @@
-
-
+
+
@@ -625,8 +625,8 @@
-
-
+
+
@@ -724,8 +724,8 @@
-
-
+
+
@@ -1162,7 +1162,7 @@
-
+
diff --git a/src/static/js/app.js b/src/static/js/app.js
index 0d6e7cd..6a2321e 100644
--- a/src/static/js/app.js
+++ b/src/static/js/app.js
@@ -561,7 +561,6 @@ async function loadOrgLicenses(orgId) {
${l.status === "active" ? `
-
` : ""}
|
@@ -602,20 +601,9 @@ document.addEventListener("DOMContentLoaded", function () {
});
});
-function confirmRevokeLicense(licId) {
- showConfirm(
- "Lizenz widerrufen",
- "Soll die Lizenz wirklich widerrufen werden? Nutzer können dann nur noch lesen.",
- async () => {
- try {
- await API.put(`/api/licenses/${licId}/revoke`);
- if (currentOrgId) loadOrgLicenses(currentOrgId);
- } catch (err) {
- showToast(err.message, "error");
- }
- }
- );
-}
+// Der Widerrufen-Knopf ist bewusst entfernt. Eine neue Lizenz widerruft die
+// alte automatisch, und einen Kunden abschalten erledigt der Org-Status-
+// Umschalter in der Kopfzeile. Der API-Endpoint /revoke bleibt bestehen.
// --- Expiring Licenses (global view) ---
async function loadExpiringLicenses() {
diff --git a/tests/test_models.py b/tests/test_models.py
index 0da0ca0..05a7d47 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -54,6 +54,13 @@ def test_license_params_credits_defaults():
assert p.budget_warning_percent == 80
+def test_license_params_warnschwelle_null_ist_aus():
+ p = LicenseParams(license_type="trial", budget_warning_percent=0)
+ assert p.budget_warning_percent == 0
+ with pytest.raises(ValidationError):
+ LicenseParams(license_type="trial", budget_warning_percent=101)
+
+
def test_license_params_period_pattern():
LicenseParams(license_type="trial", credits_period="monthly")
LicenseParams(license_type="trial", credits_period="total")