Promote develop → main (2026-07-25 22:22 UTC) #16
@@ -36,7 +36,7 @@ class LicenseParams(BaseModel):
|
|||||||
token_budget_usd: Optional[float] = None
|
token_budget_usd: Optional[float] = None
|
||||||
credits_total: Optional[int] = None
|
credits_total: Optional[int] = None
|
||||||
cost_per_credit: Optional[float] = 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
|
unlimited_budget: bool = False
|
||||||
# 'monthly' = Kontingent wird zum Monatswechsel neu gefuellt (der Monitor
|
# 'monthly' = Kontingent wird zum Monatswechsel neu gefuellt (der Monitor
|
||||||
# setzt den Verbrauch traege zurueck), 'total' = gilt fuer die Laufzeit.
|
# setzt den Verbrauch traege zurueck), 'total' = gilt fuer die Laufzeit.
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ async def update_budget(license_id: int, data: dict, request: Request, admin=Dep
|
|||||||
pct = int(data["budget_warning_percent"])
|
pct = int(data["budget_warning_percent"])
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
raise HTTPException(status_code=400, detail="budget_warning_percent muss eine Zahl sein")
|
raise HTTPException(status_code=400, detail="budget_warning_percent muss eine Zahl sein")
|
||||||
if not 1 <= pct <= 100:
|
if not 0 <= pct <= 100:
|
||||||
raise HTTPException(status_code=400, detail="budget_warning_percent muss zwischen 1 und 100 liegen")
|
raise HTTPException(status_code=400, detail="budget_warning_percent muss zwischen 0 und 100 liegen (0 = Warnung aus)")
|
||||||
data["budget_warning_percent"] = pct
|
data["budget_warning_percent"] = pct
|
||||||
if "max_users" in data:
|
if "max_users" in data:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -263,8 +263,8 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="editWarnPercent">Warnschwelle (%)</label>
|
<label for="editWarnPercent">Warnschwelle (%, 0 = aus)</label>
|
||||||
<input type="number" id="editWarnPercent" min="1" max="100" placeholder="80">
|
<input type="number" id="editWarnPercent" min="0" max="100" placeholder="80">
|
||||||
</div>
|
</div>
|
||||||
<details class="adv-section">
|
<details class="adv-section">
|
||||||
<summary>
|
<summary>
|
||||||
@@ -625,8 +625,8 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="newOrgLicWarnPercent">Warnschwelle (%)</label>
|
<label for="newOrgLicWarnPercent">Warnschwelle (%, 0 = aus)</label>
|
||||||
<input type="number" id="newOrgLicWarnPercent" value="80" min="1" max="100">
|
<input type="number" id="newOrgLicWarnPercent" value="80" min="0" max="100">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="newOrgLicCostPerCredit">Kosten pro Credit (USD)</label>
|
<label for="newOrgLicCostPerCredit">Kosten pro Credit (USD)</label>
|
||||||
@@ -724,8 +724,8 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="newLicWarnPercent">Warnschwelle (%)</label>
|
<label for="newLicWarnPercent">Warnschwelle (%, 0 = aus)</label>
|
||||||
<input type="number" id="newLicWarnPercent" value="80" min="1" max="100">
|
<input type="number" id="newLicWarnPercent" value="80" min="0" max="100">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="newLicCostPerCredit">Kosten pro Credit (USD)</label>
|
<label for="newLicCostPerCredit">Kosten pro Credit (USD)</label>
|
||||||
@@ -1162,7 +1162,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/a11y.js?v=20260725a"></script>
|
<script src="/static/js/a11y.js?v=20260725a"></script>
|
||||||
<script src="/static/js/app.js?v=20260725j"></script>
|
<script src="/static/js/app.js?v=20260725k"></script>
|
||||||
<script src="/static/js/sources.js?v=20260522x2"></script>
|
<script src="/static/js/sources.js?v=20260522x2"></script>
|
||||||
<script src="/static/js/x-scraper.js?v=20260522a"></script>
|
<script src="/static/js/x-scraper.js?v=20260522a"></script>
|
||||||
<script src="/static/js/source-health.js?v=20260509l"></script>
|
<script src="/static/js/source-health.js?v=20260509l"></script>
|
||||||
|
|||||||
@@ -561,7 +561,6 @@ async function loadOrgLicenses(orgId) {
|
|||||||
<td>
|
<td>
|
||||||
${l.status === "active" ? `
|
${l.status === "active" ? `
|
||||||
<button class="btn btn-secondary btn-small" onclick="extendLicense(${l.id})">Verlängern</button>
|
<button class="btn btn-secondary btn-small" onclick="extendLicense(${l.id})">Verlängern</button>
|
||||||
<button class="btn btn-danger btn-small" onclick="confirmRevokeLicense(${l.id})">Widerrufen</button>
|
|
||||||
` : ""}
|
` : ""}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -602,20 +601,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function confirmRevokeLicense(licId) {
|
// Der Widerrufen-Knopf ist bewusst entfernt. Eine neue Lizenz widerruft die
|
||||||
showConfirm(
|
// alte automatisch, und einen Kunden abschalten erledigt der Org-Status-
|
||||||
"Lizenz widerrufen",
|
// Umschalter in der Kopfzeile. Der API-Endpoint /revoke bleibt bestehen.
|
||||||
"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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Expiring Licenses (global view) ---
|
// --- Expiring Licenses (global view) ---
|
||||||
async function loadExpiringLicenses() {
|
async function loadExpiringLicenses() {
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ def test_license_params_credits_defaults():
|
|||||||
assert p.budget_warning_percent == 80
|
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():
|
def test_license_params_period_pattern():
|
||||||
LicenseParams(license_type="trial", credits_period="monthly")
|
LicenseParams(license_type="trial", credits_period="monthly")
|
||||||
LicenseParams(license_type="trial", credits_period="total")
|
LicenseParams(license_type="trial", credits_period="total")
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren