ajout du type distance parcourue
This commit is contained in:
parent
69af91ad84
commit
8f36fa8fd3
7 changed files with 3541 additions and 64 deletions
|
|
@ -15,7 +15,7 @@ def parse_date(value: str) -> datetime:
|
|||
return datetime.strptime(value, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def load_input_value(inputs_file: str, item_key: str) -> str:
|
||||
def load_input_item(inputs_file: str, item_key: str) -> dict:
|
||||
path = Path(inputs_file)
|
||||
if not path.exists():
|
||||
raise ValueError(f"inputs file introuvable: {inputs_file}")
|
||||
|
|
@ -30,10 +30,13 @@ def load_input_value(inputs_file: str, item_key: str) -> str:
|
|||
item = data[item_key]
|
||||
if not isinstance(item, dict):
|
||||
raise ValueError(f"entrée invalide pour item_key: {item_key}")
|
||||
return item
|
||||
|
||||
|
||||
def load_input_value(inputs_file: str, item_key: str) -> str:
|
||||
item = load_input_item(inputs_file, item_key)
|
||||
if "value" not in item:
|
||||
raise ValueError(f"champ 'value' absent pour item_key: {item_key}")
|
||||
|
||||
return str(item["value"]).strip()
|
||||
|
||||
|
||||
|
|
@ -63,21 +66,50 @@ def main() -> int:
|
|||
args = ap.parse_args()
|
||||
|
||||
try:
|
||||
if args.source_type != "manual_date":
|
||||
exit_with(2, f"CRITICAL - source_type non supporté: {args.source_type}")
|
||||
if args.probe_type in {"elapsed_time", "days_until_due"}:
|
||||
if args.source_type != "manual_date":
|
||||
exit_with(2, f"CRITICAL - source_type non supporté: {args.source_type}")
|
||||
if args.unit != "days":
|
||||
exit_with(2, f"CRITICAL - unité non supportée: {args.unit}")
|
||||
|
||||
if args.unit != "days":
|
||||
exit_with(2, f"CRITICAL - unité non supportée: {args.unit}")
|
||||
if args.inputs_file and args.item_key:
|
||||
source_value = load_input_value(args.inputs_file, args.item_key)
|
||||
elif args.source_value:
|
||||
source_value = args.source_value
|
||||
else:
|
||||
raise ValueError("aucune source fournie: inputs-file/item-key ou source-value requis")
|
||||
|
||||
dt = parse_date(source_value)
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
elif args.probe_type == "elapsed_distance":
|
||||
if args.source_type != "manual_counter":
|
||||
exit_with(2, f"CRITICAL - source_type non supporté: {args.source_type}")
|
||||
if args.unit != "km":
|
||||
exit_with(2, f"CRITICAL - unité non supportée: {args.unit}")
|
||||
|
||||
if args.inputs_file and args.item_key:
|
||||
item = load_input_item(args.inputs_file, args.item_key)
|
||||
current_value = float(str(item.get("value", "")).strip())
|
||||
reference_value = float(str(item.get("reference_value", "")).strip())
|
||||
else:
|
||||
raise ValueError("manual_counter exige inputs-file et item-key")
|
||||
|
||||
metric = current_value - reference_value
|
||||
|
||||
if args.unknown_lt is not None and metric < args.unknown_lt:
|
||||
exit_with(3, f"UNKNOWN - {args.label}: écart compteur invalide ({metric:.0f} km)")
|
||||
if args.critical_gte is not None and metric >= args.critical_gte:
|
||||
exit_with(2, f"CRITICAL - {args.label}: {metric:.0f} km")
|
||||
if args.warning_gte is not None and metric >= args.warning_gte:
|
||||
exit_with(1, f"WARNING - {args.label}: {metric:.0f} km")
|
||||
if args.ok_gte is not None and metric >= args.ok_gte:
|
||||
exit_with(0, f"OK - {args.label}: {metric:.0f} km")
|
||||
|
||||
exit_with(2, f"CRITICAL - {args.label}: seuils incohérents")
|
||||
|
||||
if args.inputs_file and args.item_key:
|
||||
source_value = load_input_value(args.inputs_file, args.item_key)
|
||||
elif args.source_value:
|
||||
source_value = args.source_value
|
||||
else:
|
||||
raise ValueError("aucune source fournie: inputs-file/item-key ou source-value requis")
|
||||
|
||||
dt = parse_date(source_value)
|
||||
now = datetime.now(timezone.utc)
|
||||
exit_with(2, f"CRITICAL - probe_type non supporté: {args.probe_type}")
|
||||
|
||||
if args.probe_type == "elapsed_time":
|
||||
metric = (now - dt).total_seconds() / 86400.0
|
||||
|
|
@ -107,9 +139,6 @@ def main() -> int:
|
|||
|
||||
exit_with(2, f"CRITICAL - {args.label}: seuils incohérents")
|
||||
|
||||
else:
|
||||
exit_with(2, f"CRITICAL - probe_type non supporté: {args.probe_type}")
|
||||
|
||||
except Exception as exc:
|
||||
if args.on_error == "critical":
|
||||
exit_with(2, f"CRITICAL - erreur sonde: {exc}")
|
||||
|
|
|
|||
76
domains.yaml
76
domains.yaml
|
|
@ -1415,18 +1415,94 @@ domains:
|
|||
- name: verification-huile-moteur
|
||||
date: 2026-04-01
|
||||
notes: Vérification de l'huile moteur
|
||||
probe:
|
||||
type: elapsed_distance
|
||||
source:
|
||||
type: manual_counter
|
||||
inputs_file: /opt/life-noc/data/inputs/voiture.yaml
|
||||
item_key: verification-huile-moteur
|
||||
metric:
|
||||
unit: km
|
||||
thresholds:
|
||||
unknown_lt: 0
|
||||
ok_gte: 0
|
||||
warning_gte: 8000
|
||||
critical_gte: 10000
|
||||
policy:
|
||||
on_error: unknown
|
||||
ui:
|
||||
form_mode: counter_pair
|
||||
allow_complete: false
|
||||
allow_manual_edit: true
|
||||
- name: inspection-freins
|
||||
date: 2026-05-01
|
||||
notes: Inspection des freins
|
||||
probe:
|
||||
type: elapsed_distance
|
||||
source:
|
||||
type: manual_counter
|
||||
inputs_file: /opt/life-noc/data/inputs/voiture.yaml
|
||||
item_key: inspection-freins
|
||||
metric:
|
||||
unit: km
|
||||
thresholds:
|
||||
unknown_lt: 0
|
||||
ok_gte: 0
|
||||
warning_gte: 15000
|
||||
critical_gte: 20000
|
||||
policy:
|
||||
on_error: unknown
|
||||
ui:
|
||||
form_mode: counter_pair
|
||||
allow_complete: false
|
||||
allow_manual_edit: true
|
||||
- name: verification-pneus
|
||||
date: 2026-04-15
|
||||
notes: Vérification usure, pression et permutation des pneus
|
||||
probe:
|
||||
type: elapsed_distance
|
||||
source:
|
||||
type: manual_counter
|
||||
inputs_file: /opt/life-noc/data/inputs/voiture.yaml
|
||||
item_key: verification-pneus
|
||||
metric:
|
||||
unit: km
|
||||
thresholds:
|
||||
unknown_lt: 0
|
||||
ok_gte: 0
|
||||
warning_gte: 10000
|
||||
critical_gte: 12000
|
||||
policy:
|
||||
on_error: unknown
|
||||
ui:
|
||||
form_mode: counter_pair
|
||||
allow_complete: false
|
||||
allow_manual_edit: true
|
||||
- name: ajustement-valves
|
||||
date: 2026-08-01
|
||||
notes: Vérification et ajustement des valves Honda Accord
|
||||
- name: verification-timing-belt
|
||||
date: 2027-01-01
|
||||
notes: Vérification de la courroie de distribution et pompe à eau
|
||||
probe:
|
||||
type: elapsed_distance
|
||||
source:
|
||||
type: manual_counter
|
||||
inputs_file: /opt/life-noc/data/inputs/voiture.yaml
|
||||
item_key: verification-timing-belt
|
||||
metric:
|
||||
unit: km
|
||||
thresholds:
|
||||
unknown_lt: 0
|
||||
ok_gte: 0
|
||||
warning_gte: 150000
|
||||
critical_gte: 168000
|
||||
policy:
|
||||
on_error: unknown
|
||||
ui:
|
||||
form_mode: counter_pair
|
||||
allow_complete: false
|
||||
allow_manual_edit: true
|
||||
- name: verification-batterie-voiture
|
||||
date: 2026-10-01
|
||||
notes: Vérification de l'état de la batterie automobile
|
||||
|
|
|
|||
3234
domains.yaml.orig
Normal file
3234
domains.yaml.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -5,33 +5,60 @@
|
|||
*/
|
||||
|
||||
apply Service "voiture-verification-huile-moteur" {
|
||||
import "service_echeance"
|
||||
vars.date_echeance = "2026-04-01"
|
||||
vars.mock_state = "OK"
|
||||
vars.mock_message = "Sous contrôle"
|
||||
import "service_life_noc_probe"
|
||||
vars.life_noc_probe_type = "elapsed_distance"
|
||||
vars.life_noc_source_type = "manual_counter"
|
||||
vars.life_noc_source_value = ""
|
||||
vars.life_noc_inputs_file = "/opt/life-noc/data/inputs/voiture.yaml"
|
||||
vars.life_noc_item_key = "verification-huile-moteur"
|
||||
vars.life_noc_metric_unit = "km"
|
||||
vars.life_noc_label = "verification-huile-moteur"
|
||||
groups = [ "VOITURE" ]
|
||||
vars.life_noc_threshold_unknown_lt = "0"
|
||||
vars.life_noc_threshold_ok_gte = "0"
|
||||
vars.life_noc_threshold_warning_gte = "8000"
|
||||
vars.life_noc_threshold_critical_gte = "10000"
|
||||
vars.life_noc_on_error = "unknown"
|
||||
notes = "Vérification de l'huile moteur"
|
||||
vars.instructions_url = "/life-noc/item/voiture/verification-huile-moteur"
|
||||
assign where host.name == "life-noc"
|
||||
}
|
||||
|
||||
apply Service "voiture-inspection-freins" {
|
||||
import "service_echeance"
|
||||
vars.date_echeance = "2026-05-01"
|
||||
vars.mock_state = "OK"
|
||||
vars.mock_message = "Sous contrôle"
|
||||
import "service_life_noc_probe"
|
||||
vars.life_noc_probe_type = "elapsed_distance"
|
||||
vars.life_noc_source_type = "manual_counter"
|
||||
vars.life_noc_source_value = ""
|
||||
vars.life_noc_inputs_file = "/opt/life-noc/data/inputs/voiture.yaml"
|
||||
vars.life_noc_item_key = "inspection-freins"
|
||||
vars.life_noc_metric_unit = "km"
|
||||
vars.life_noc_label = "inspection-freins"
|
||||
groups = [ "VOITURE" ]
|
||||
vars.life_noc_threshold_unknown_lt = "0"
|
||||
vars.life_noc_threshold_ok_gte = "0"
|
||||
vars.life_noc_threshold_warning_gte = "15000"
|
||||
vars.life_noc_threshold_critical_gte = "20000"
|
||||
vars.life_noc_on_error = "unknown"
|
||||
notes = "Inspection des freins"
|
||||
vars.instructions_url = "/life-noc/item/voiture/inspection-freins"
|
||||
assign where host.name == "life-noc"
|
||||
}
|
||||
|
||||
apply Service "voiture-verification-pneus" {
|
||||
import "service_echeance"
|
||||
vars.date_echeance = "2026-04-15"
|
||||
vars.mock_state = "OK"
|
||||
vars.mock_message = "Sous contrôle"
|
||||
import "service_life_noc_probe"
|
||||
vars.life_noc_probe_type = "elapsed_distance"
|
||||
vars.life_noc_source_type = "manual_counter"
|
||||
vars.life_noc_source_value = ""
|
||||
vars.life_noc_inputs_file = "/opt/life-noc/data/inputs/voiture.yaml"
|
||||
vars.life_noc_item_key = "verification-pneus"
|
||||
vars.life_noc_metric_unit = "km"
|
||||
vars.life_noc_label = "verification-pneus"
|
||||
groups = [ "VOITURE" ]
|
||||
vars.life_noc_threshold_unknown_lt = "0"
|
||||
vars.life_noc_threshold_ok_gte = "0"
|
||||
vars.life_noc_threshold_warning_gte = "10000"
|
||||
vars.life_noc_threshold_critical_gte = "12000"
|
||||
vars.life_noc_on_error = "unknown"
|
||||
notes = "Vérification usure, pression et permutation des pneus"
|
||||
vars.instructions_url = "/life-noc/item/voiture/verification-pneus"
|
||||
assign where host.name == "life-noc"
|
||||
|
|
@ -49,11 +76,20 @@ apply Service "voiture-ajustement-valves" {
|
|||
}
|
||||
|
||||
apply Service "voiture-verification-timing-belt" {
|
||||
import "service_echeance"
|
||||
vars.date_echeance = "2027-01-01"
|
||||
vars.mock_state = "OK"
|
||||
vars.mock_message = "Sous contrôle"
|
||||
import "service_life_noc_probe"
|
||||
vars.life_noc_probe_type = "elapsed_distance"
|
||||
vars.life_noc_source_type = "manual_counter"
|
||||
vars.life_noc_source_value = ""
|
||||
vars.life_noc_inputs_file = "/opt/life-noc/data/inputs/voiture.yaml"
|
||||
vars.life_noc_item_key = "verification-timing-belt"
|
||||
vars.life_noc_metric_unit = "km"
|
||||
vars.life_noc_label = "verification-timing-belt"
|
||||
groups = [ "VOITURE" ]
|
||||
vars.life_noc_threshold_unknown_lt = "0"
|
||||
vars.life_noc_threshold_ok_gte = "0"
|
||||
vars.life_noc_threshold_warning_gte = "150000"
|
||||
vars.life_noc_threshold_critical_gte = "168000"
|
||||
vars.life_noc_on_error = "unknown"
|
||||
notes = "Vérification de la courroie de distribution et pompe à eau"
|
||||
vars.instructions_url = "/life-noc/item/voiture/verification-timing-belt"
|
||||
assign where host.name == "life-noc"
|
||||
|
|
|
|||
|
|
@ -67,6 +67,18 @@ def set_item(domain: str, item_key: str, value: str, origin: str = "manual") ->
|
|||
return data[item_key]
|
||||
|
||||
|
||||
def set_counter_pair(domain: str, item_key: str, current_value: str, reference_value: str, origin: str = "manual") -> dict:
|
||||
data = load_domain(domain)
|
||||
data[item_key] = {
|
||||
"value": current_value,
|
||||
"reference_value": reference_value,
|
||||
"captured_at": utc_now_iso(),
|
||||
"origin": origin,
|
||||
}
|
||||
save_domain(domain, data)
|
||||
return data[item_key]
|
||||
|
||||
|
||||
def complete_item(domain: str, item_key: str, origin: str = "manual") -> dict:
|
||||
return set_item(domain, item_key, utc_today_date(), origin=origin)
|
||||
|
||||
|
|
@ -106,30 +118,33 @@ def compute_state_for_item(domain: str, item_key: str) -> str:
|
|||
source = probe.get("source", {})
|
||||
thresholds = probe.get("thresholds", {})
|
||||
metric = probe.get("metric", {})
|
||||
|
||||
if str(source.get("type", "")).strip() != "manual_date":
|
||||
return "CRITICAL"
|
||||
if str(metric.get("unit", "")).strip() != "days":
|
||||
return "CRITICAL"
|
||||
source_type = str(source.get("type", "")).strip()
|
||||
metric_unit = str(metric.get("unit", "")).strip()
|
||||
|
||||
item = get_item(domain, item_key)
|
||||
value = str(item.get("value", "")).strip()
|
||||
dt = datetime.strptime(value, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
if probe_type == "elapsed_time":
|
||||
days = (now - dt).total_seconds() / 86400.0
|
||||
if "critical_gte" in thresholds and days >= float(thresholds["critical_gte"]):
|
||||
if probe_type in {"elapsed_time", "days_until_due"}:
|
||||
if source_type != "manual_date":
|
||||
return "CRITICAL"
|
||||
if metric_unit != "days":
|
||||
return "CRITICAL"
|
||||
|
||||
value = str(item.get("value", "")).strip()
|
||||
dt = datetime.strptime(value, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
if probe_type == "elapsed_time":
|
||||
days = (now - dt).total_seconds() / 86400.0
|
||||
if "critical_gte" in thresholds and days >= float(thresholds["critical_gte"]):
|
||||
return "CRITICAL"
|
||||
if "warning_gte" in thresholds and days >= float(thresholds["warning_gte"]):
|
||||
return "WARNING"
|
||||
if "ok_gte" in thresholds and days >= float(thresholds["ok_gte"]):
|
||||
return "OK"
|
||||
if "unknown_lt" in thresholds and days < float(thresholds["unknown_lt"]):
|
||||
return "UNKNOWN"
|
||||
return "CRITICAL"
|
||||
if "warning_gte" in thresholds and days >= float(thresholds["warning_gte"]):
|
||||
return "WARNING"
|
||||
if "ok_gte" in thresholds and days >= float(thresholds["ok_gte"]):
|
||||
return "OK"
|
||||
if "unknown_lt" in thresholds and days < float(thresholds["unknown_lt"]):
|
||||
return "UNKNOWN"
|
||||
return "CRITICAL"
|
||||
|
||||
if probe_type == "days_until_due":
|
||||
days = (dt - now).total_seconds() / 86400.0
|
||||
if "critical_lt" in thresholds and days < float(thresholds["critical_lt"]):
|
||||
return "CRITICAL"
|
||||
|
|
@ -141,4 +156,24 @@ def compute_state_for_item(domain: str, item_key: str) -> str:
|
|||
return "UNKNOWN"
|
||||
return "CRITICAL"
|
||||
|
||||
if probe_type == "elapsed_distance":
|
||||
if source_type != "manual_counter":
|
||||
return "CRITICAL"
|
||||
if metric_unit != "km":
|
||||
return "CRITICAL"
|
||||
|
||||
current_value = float(str(item.get("value", "")).strip())
|
||||
reference_value = float(str(item.get("reference_value", "")).strip())
|
||||
distance = current_value - reference_value
|
||||
|
||||
if "unknown_lt" in thresholds and distance < float(thresholds["unknown_lt"]):
|
||||
return "UNKNOWN"
|
||||
if "critical_gte" in thresholds and distance >= float(thresholds["critical_gte"]):
|
||||
return "CRITICAL"
|
||||
if "warning_gte" in thresholds and distance >= float(thresholds["warning_gte"]):
|
||||
return "WARNING"
|
||||
if "ok_gte" in thresholds and distance >= float(thresholds["ok_gte"]):
|
||||
return "OK"
|
||||
return "CRITICAL"
|
||||
|
||||
return "CRITICAL"
|
||||
|
|
|
|||
|
|
@ -202,26 +202,36 @@ def validate_probe(item: dict) -> None:
|
|||
raise ValueError("probe doit être un objet")
|
||||
|
||||
probe_type = str(probe.get("type", "")).strip()
|
||||
if probe_type not in {"elapsed_time", "days_until_due"}:
|
||||
raise ValueError("seuls probe.type=elapsed_time et days_until_due sont supportés")
|
||||
if probe_type not in {"elapsed_time", "days_until_due", "elapsed_distance"}:
|
||||
raise ValueError("seuls probe.type=elapsed_time, days_until_due et elapsed_distance sont supportés")
|
||||
|
||||
source = probe.get("source")
|
||||
if not isinstance(source, dict):
|
||||
raise ValueError("probe.source doit être un objet")
|
||||
if str(source.get("type", "")).strip() != "manual_date":
|
||||
raise ValueError("seul probe.source.type=manual_date est supporté en v1")
|
||||
|
||||
source_type = str(source.get("type", "")).strip()
|
||||
has_inline_value = "value" in source
|
||||
has_store_ref = "inputs_file" in source and "item_key" in source
|
||||
|
||||
if not has_inline_value and not has_store_ref:
|
||||
raise ValueError("probe.source.value ou probe.source.inputs_file + item_key requis")
|
||||
if probe_type in {"elapsed_time", "days_until_due"}:
|
||||
if source_type != "manual_date":
|
||||
raise ValueError("seul probe.source.type=manual_date est supporté pour les sondes de date")
|
||||
if not has_inline_value and not has_store_ref:
|
||||
raise ValueError("probe.source.value ou probe.source.inputs_file + item_key requis")
|
||||
elif probe_type == "elapsed_distance":
|
||||
if source_type != "manual_counter":
|
||||
raise ValueError("seul probe.source.type=manual_counter est supporté pour elapsed_distance")
|
||||
if not has_store_ref:
|
||||
raise ValueError("probe.source.inputs_file + item_key requis pour elapsed_distance")
|
||||
|
||||
metric = probe.get("metric")
|
||||
if not isinstance(metric, dict):
|
||||
raise ValueError("probe.metric doit être un objet")
|
||||
if str(metric.get("unit", "")).strip() != "days":
|
||||
raise ValueError("seul probe.metric.unit=days est supporté en v1")
|
||||
metric_unit = str(metric.get("unit", "")).strip()
|
||||
if probe_type in {"elapsed_time", "days_until_due"} and metric_unit != "days":
|
||||
raise ValueError("seul probe.metric.unit=days est supporté pour les sondes de date")
|
||||
if probe_type == "elapsed_distance" and metric_unit != "km":
|
||||
raise ValueError("seul probe.metric.unit=km est supporté pour elapsed_distance")
|
||||
|
||||
thresholds = probe.get("thresholds")
|
||||
if not isinstance(thresholds, dict):
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ from lib.life_noc_inputs import ( # noqa: E402
|
|||
list_items,
|
||||
get_item,
|
||||
set_item,
|
||||
set_counter_pair,
|
||||
complete_item,
|
||||
get_defined_service,
|
||||
compute_state_for_item,
|
||||
|
|
@ -52,6 +53,12 @@ class CompleteInputRequest(BaseModel):
|
|||
origin: str = "manual"
|
||||
|
||||
|
||||
class CounterPairInputRequest(BaseModel):
|
||||
current_value: str
|
||||
reference_value: str
|
||||
origin: str = "manual"
|
||||
|
||||
|
||||
app = FastAPI(title="Life-NOC Input API", version="1.1.0")
|
||||
|
||||
|
||||
|
|
@ -537,16 +544,34 @@ def api_complete_input(domain: str, item_key: str, req: CompleteInputRequest | N
|
|||
return {"domain": domain, "item_key": item_key, **item}
|
||||
|
||||
|
||||
@app.post("/inputs/{domain}/{item_key}/counter")
|
||||
def api_set_counter_pair(domain: str, item_key: str, req: CounterPairInputRequest) -> dict:
|
||||
item = set_counter_pair(
|
||||
domain,
|
||||
item_key,
|
||||
current_value=req.current_value,
|
||||
reference_value=req.reference_value,
|
||||
origin=req.origin or "manual",
|
||||
)
|
||||
return {"domain": domain, "item_key": item_key, **item}
|
||||
|
||||
|
||||
@app.get("/life-noc/item/{domain}/{item_key}", response_class=HTMLResponse)
|
||||
def page_item(domain: str, item_key: str) -> HTMLResponse:
|
||||
try:
|
||||
service = get_defined_service(domain, item_key)
|
||||
current_input = get_item(domain, item_key)
|
||||
state = compute_state_for_item(domain, item_key)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="item introuvable")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
try:
|
||||
current_input = get_item(domain, item_key)
|
||||
except KeyError:
|
||||
current_input = {}
|
||||
|
||||
try:
|
||||
state = compute_state_for_item(domain, item_key)
|
||||
except (KeyError, ValueError):
|
||||
state = "UNKNOWN"
|
||||
|
||||
title = service.get("title") or service.get("display_name") or item_key.replace("-", " ").capitalize()
|
||||
summary = service.get("summary", "")
|
||||
|
|
@ -601,6 +626,25 @@ def page_item(domain: str, item_key: str) -> HTMLResponse:
|
|||
</form>
|
||||
</section>
|
||||
"""
|
||||
elif allow_manual_edit and form_mode == "counter_pair":
|
||||
manual_form_html = f"""
|
||||
<section class="card">
|
||||
<h2>Mettre à jour les compteurs</h2>
|
||||
<p class="muted">Saisir le compteur courant et le compteur au dernier entretien.</p>
|
||||
<form method="post" action="/life-noc/item/{html_escape(domain)}/{html_escape(item_key)}/counter" class="form-grid two">
|
||||
<label>Compteur courant
|
||||
<input type="text" inputmode="decimal" name="current_value" value="{html_escape(current_input.get('value', ''))}" required>
|
||||
</label>
|
||||
<label>Compteur au dernier entretien
|
||||
<input type="text" inputmode="decimal" name="reference_value" value="{html_escape(current_input.get('reference_value', ''))}" required>
|
||||
</label>
|
||||
<input type="hidden" name="origin" value="manual">
|
||||
<div class="actions">
|
||||
<button type="submit">Enregistrer</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
"""
|
||||
|
||||
complete_form_html = ""
|
||||
if allow_complete:
|
||||
|
|
@ -632,6 +676,7 @@ def page_item(domain: str, item_key: str) -> HTMLResponse:
|
|||
<section class="card">
|
||||
<h2>Dernier intrant</h2>
|
||||
<p><strong>Valeur :</strong> {html_escape(current_input.get('value', ''))}</p>
|
||||
{f'<p><strong>Valeur de référence :</strong> {html_escape(current_input.get("reference_value", ""))}</p>' if 'reference_value' in current_input else ''}
|
||||
<p><strong>Capturé le :</strong> {html_escape(current_input.get('captured_at', ''))}</p>
|
||||
<p><strong>Origine :</strong> {html_escape(current_input.get('origin', ''))}</p>
|
||||
</section>
|
||||
|
|
@ -668,6 +713,18 @@ def page_set_item(domain: str, item_key: str, value: str = Form(...), origin: st
|
|||
return RedirectResponse(url=f"/life-noc/item/{domain}/{item_key}", status_code=303)
|
||||
|
||||
|
||||
@app.post("/life-noc/item/{domain}/{item_key}/counter")
|
||||
def page_set_counter(domain: str, item_key: str, current_value: str = Form(...), reference_value: str = Form(...), origin: str = Form(default="manual")) -> RedirectResponse:
|
||||
try:
|
||||
set_counter_pair(domain, item_key, current_value=current_value, reference_value=reference_value, origin=origin)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="item introuvable")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
return RedirectResponse(url=f"/life-noc/item/{domain}/{item_key}", status_code=303)
|
||||
|
||||
|
||||
@app.post("/life-noc/item/{domain}/{item_key}/complete")
|
||||
def page_complete_item(domain: str, item_key: str, origin: str = Form(default="manual")) -> RedirectResponse:
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in a new issue