From d887aa28b9c33ce4e2ea2b85e6e97fa3f8b98a31 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Tue, 28 Apr 2026 16:20:31 +0100 Subject: [PATCH 01/15] add get and post silences --- src/murfey/server/api/session_info.py | 44 ++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 64bde37fa..37371246c 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -2,8 +2,9 @@ from logging import getLogger from pathlib import Path from typing import Dict, List, Optional +import requests -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Depends, Request, HTTPException from fastapi.responses import FileResponse from pydantic import BaseModel from sqlmodel import select @@ -58,6 +59,47 @@ tags=["Session Info: General"], ) +alertmanager_url = "https://murfey-alertmanager.diamond.ac.uk" + +@router.get("/silences/{microscope}") +def get_silences(microscope: str): + try: + silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") + return str(silences.json()) + except: + HTTPException(status_code=silences.status_code, detail=silences.json()) + +@router.post("/silences/{microscope}") +def get_silences(microscope: str, end_time: datetime ): + #for testing + # microscope = 'm00' + + start_time = datetime.now().astimezone().isoformat() + end_time = end_time.astimezone().isoformat() + silence_json = { + "matchers":[ + { + "name": "microscope", + "value": microscope, + "isRegex": False + }], + "createdBy": "dvu71330", + "annotations":{"description": "Test"}, + "comment": "test", + "status": {"state": "active"}, + "startsAt": str(start_time), + "endsAt": str(end_time) + } + print(silence_json) + try: + alertmanager_response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) + print(alertmanager_response) + except: + raise HTTPException(status_code=500, detail= "Error creating silence") + if alertmanager_response.status_code == 200: + return str(alertmanager_response.json()) + else: + raise HTTPException(status_code=alertmanager_response.status_code, detail=alertmanager_response.json()) @router.get("/health/") def health_check(db=ispyb_db): From b2ed2caf763751eed0710ce28effb90d2f1cdacb Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Fri, 1 May 2026 15:58:18 +0100 Subject: [PATCH 02/15] add delete silence endpoint --- src/murfey/server/api/session_info.py | 28 ++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 37371246c..282f3acf5 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -1,4 +1,5 @@ from datetime import datetime +import json from logging import getLogger from pathlib import Path from typing import Dict, List, Optional @@ -65,14 +66,14 @@ def get_silences(microscope: str): try: silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") - return str(silences.json()) + return (silences.json()) except: HTTPException(status_code=silences.status_code, detail=silences.json()) @router.post("/silences/{microscope}") -def get_silences(microscope: str, end_time: datetime ): +def create_silence(microscope: str, end_time: datetime ): #for testing - # microscope = 'm00' + microscope = 'm00' start_time = datetime.now().astimezone().isoformat() end_time = end_time.astimezone().isoformat() @@ -90,10 +91,8 @@ def get_silences(microscope: str, end_time: datetime ): "startsAt": str(start_time), "endsAt": str(end_time) } - print(silence_json) try: alertmanager_response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) - print(alertmanager_response) except: raise HTTPException(status_code=500, detail= "Error creating silence") if alertmanager_response.status_code == 200: @@ -101,6 +100,25 @@ def get_silences(microscope: str, end_time: datetime ): else: raise HTTPException(status_code=alertmanager_response.status_code, detail=alertmanager_response.json()) +@router.delete("/silences/{microscope}") +def delete_silences(microscope: str): + #for testing + microscope='m00' + + silences = get_silences(microscope) + + ids = [] #for testing + for silence in silences: + if silence['status']['state'] == 'active': + id = silence['id'] + print(id) + try: + response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") + print(response) + except: + raise HTTPException(status_code=400, detail="error deleting silence") + return "Silences Deleted" + @router.get("/health/") def health_check(db=ispyb_db): conn = db.connection() From 05343885f0854856191a4180f863f8520e88ad8d Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Wed, 6 May 2026 17:49:56 +0100 Subject: [PATCH 03/15] filter for active silences in get silence --- src/murfey/server/api/session_info.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 282f3acf5..38dce3242 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -66,7 +66,12 @@ def get_silences(microscope: str): try: silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") - return (silences.json()) + active_silences = [] + for silence in silences.json(): + if silence['status']['state'] == 'active': + active_silences.append(silence) + print(active_silences) + return (active_silences) except: HTTPException(status_code=silences.status_code, detail=silences.json()) @@ -96,7 +101,7 @@ def create_silence(microscope: str, end_time: datetime ): except: raise HTTPException(status_code=500, detail= "Error creating silence") if alertmanager_response.status_code == 200: - return str(alertmanager_response.json()) + return alertmanager_response.json() else: raise HTTPException(status_code=alertmanager_response.status_code, detail=alertmanager_response.json()) @@ -109,14 +114,12 @@ def delete_silences(microscope: str): ids = [] #for testing for silence in silences: - if silence['status']['state'] == 'active': - id = silence['id'] - print(id) - try: - response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") - print(response) - except: - raise HTTPException(status_code=400, detail="error deleting silence") + id = silence['id'] + try: + response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") + print(response) + except: + raise HTTPException(status_code=400, detail="error deleting silence") return "Silences Deleted" @router.get("/health/") From e9e741dd60b511f27e5504466a553a58a3d03b0b Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Tue, 12 May 2026 13:12:23 +0100 Subject: [PATCH 04/15] Remove try-excepts and raise exceptions and minor other changes --- src/murfey/server/api/session_info.py | 52 +++++++++------------------ 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 38dce3242..364654cc0 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -5,8 +5,8 @@ from typing import Dict, List, Optional import requests -from fastapi import APIRouter, Depends, Request, HTTPException -from fastapi.responses import FileResponse +from fastapi import APIRouter, Depends, Request +from fastapi.responses import FileResponse, JSONResponse from pydantic import BaseModel from sqlmodel import select @@ -64,22 +64,15 @@ @router.get("/silences/{microscope}") def get_silences(microscope: str): - try: - silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") - active_silences = [] - for silence in silences.json(): - if silence['status']['state'] == 'active': - active_silences.append(silence) - print(active_silences) - return (active_silences) - except: - HTTPException(status_code=silences.status_code, detail=silences.json()) + silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") + active_silences = [] + for silence in silences.json(): + if silence['status']['state'] == 'active': + active_silences.append(silence) + return (active_silences) @router.post("/silences/{microscope}") def create_silence(microscope: str, end_time: datetime ): - #for testing - microscope = 'm00' - start_time = datetime.now().astimezone().isoformat() end_time = end_time.astimezone().isoformat() silence_json = { @@ -89,38 +82,25 @@ def create_silence(microscope: str, end_time: datetime ): "value": microscope, "isRegex": False }], - "createdBy": "dvu71330", + "createdBy": "murfey", "annotations":{"description": "Test"}, - "comment": "test", + "comment": "silence created from murfey", "status": {"state": "active"}, "startsAt": str(start_time), "endsAt": str(end_time) } - try: - alertmanager_response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) - except: - raise HTTPException(status_code=500, detail= "Error creating silence") - if alertmanager_response.status_code == 200: - return alertmanager_response.json() - else: - raise HTTPException(status_code=alertmanager_response.status_code, detail=alertmanager_response.json()) + response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) + return JSONResponse(status_code=response.status_code, content=response.json()) @router.delete("/silences/{microscope}") def delete_silences(microscope: str): - #for testing - microscope='m00' - silences = get_silences(microscope) - - ids = [] #for testing + if len(silences) == 0: + return None for silence in silences: id = silence['id'] - try: - response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") - print(response) - except: - raise HTTPException(status_code=400, detail="error deleting silence") - return "Silences Deleted" + response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") + return response #returns final response in loop @router.get("/health/") def health_check(db=ispyb_db): From bcab1f9d6c153fd48dcf33ceee7148581bad061d Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Tue, 12 May 2026 13:27:40 +0100 Subject: [PATCH 05/15] move silences code to end of file --- src/murfey/server/api/session_info.py | 83 ++++++++++++++------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 364654cc0..cc4101b56 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -60,47 +60,7 @@ tags=["Session Info: General"], ) -alertmanager_url = "https://murfey-alertmanager.diamond.ac.uk" - -@router.get("/silences/{microscope}") -def get_silences(microscope: str): - silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") - active_silences = [] - for silence in silences.json(): - if silence['status']['state'] == 'active': - active_silences.append(silence) - return (active_silences) - -@router.post("/silences/{microscope}") -def create_silence(microscope: str, end_time: datetime ): - start_time = datetime.now().astimezone().isoformat() - end_time = end_time.astimezone().isoformat() - silence_json = { - "matchers":[ - { - "name": "microscope", - "value": microscope, - "isRegex": False - }], - "createdBy": "murfey", - "annotations":{"description": "Test"}, - "comment": "silence created from murfey", - "status": {"state": "active"}, - "startsAt": str(start_time), - "endsAt": str(end_time) - } - response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) - return JSONResponse(status_code=response.status_code, content=response.json()) -@router.delete("/silences/{microscope}") -def delete_silences(microscope: str): - silences = get_silences(microscope) - if len(silences) == 0: - return None - for silence in silences: - id = silence['id'] - response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") - return response #returns final response in loop @router.get("/health/") def health_check(db=ispyb_db): @@ -513,3 +473,46 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur visit_name=visit_name, session_id=session_id, tiff_path=tiff_path, db=db ) return FileResponse(path=tiff_file) if isinstance(tiff_file, Path) else tiff_file + +#Methods for turning alerts on and off +alertmanager_url = "https://murfey-alertmanager.diamond.ac.uk" + +@router.get("/silences/{microscope}") +def get_silences(microscope: str): + silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") + active_silences = [] + for silence in silences.json(): + if silence['status']['state'] == 'active': + active_silences.append(silence) + return (active_silences) + +@router.post("/silences/{microscope}") +def create_silence(microscope: str, end_time: datetime ): + start_time = datetime.now().astimezone().isoformat() + end_time = end_time.astimezone().isoformat() + silence_json = { + "matchers":[ + { + "name": "microscope", + "value": microscope, + "isRegex": False + }], + "createdBy": "murfey", + "annotations":{"description": "Test"}, + "comment": "silence created from murfey", + "status": {"state": "active"}, + "startsAt": str(start_time), + "endsAt": str(end_time) + } + response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) + return JSONResponse(status_code=response.status_code, content=response.json()) #return a response with same data and code as from alertmanager + +@router.delete("/silences/{microscope}") #delete all silences for given microscope +def delete_silences(microscope: str): + silences = get_silences(microscope) + if len(silences) == 0: + return None + for silence in silences: + id = silence['id'] + response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") + return response #returns final response in loop \ No newline at end of file From 43684594afdaffe836f109a64038c880579959ad Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Tue, 12 May 2026 14:41:57 +0100 Subject: [PATCH 06/15] sanitise inputs from frontend --- src/murfey/server/api/session_info.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index cc4101b56..3533bc7a5 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -1,5 +1,4 @@ from datetime import datetime -import json from logging import getLogger from pathlib import Path from typing import Dict, List, Optional @@ -61,7 +60,6 @@ ) - @router.get("/health/") def health_check(db=ispyb_db): conn = db.connection() @@ -476,9 +474,11 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur #Methods for turning alerts on and off alertmanager_url = "https://murfey-alertmanager.diamond.ac.uk" +alertmanager_url = sanitise(alertmanager_url) @router.get("/silences/{microscope}") def get_silences(microscope: str): + microscope = sanitise(microscope) silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") active_silences = [] for silence in silences.json(): @@ -488,6 +488,7 @@ def get_silences(microscope: str): @router.post("/silences/{microscope}") def create_silence(microscope: str, end_time: datetime ): + microscope = sanitise(microscope) start_time = datetime.now().astimezone().isoformat() end_time = end_time.astimezone().isoformat() silence_json = { @@ -509,10 +510,11 @@ def create_silence(microscope: str, end_time: datetime ): @router.delete("/silences/{microscope}") #delete all silences for given microscope def delete_silences(microscope: str): + microscope = sanitise(microscope) silences = get_silences(microscope) if len(silences) == 0: return None for silence in silences: id = silence['id'] response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") - return response #returns final response in loop \ No newline at end of file + return response #returns final response in loop From 5348931f4e0ec5b6ffa168bf598cc4fc43e60d3f Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Tue, 12 May 2026 14:50:19 +0100 Subject: [PATCH 07/15] sanitise and rename microscope --- src/murfey/server/api/session_info.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 3533bc7a5..047f2dc74 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -478,8 +478,8 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur @router.get("/silences/{microscope}") def get_silences(microscope: str): - microscope = sanitise(microscope) - silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope}") + microscope_sanitised = sanitise(microscope) + silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope_sanitised}") active_silences = [] for silence in silences.json(): if silence['status']['state'] == 'active': @@ -510,8 +510,8 @@ def create_silence(microscope: str, end_time: datetime ): @router.delete("/silences/{microscope}") #delete all silences for given microscope def delete_silences(microscope: str): - microscope = sanitise(microscope) - silences = get_silences(microscope) + microscope_sanitised = sanitise(microscope) + silences = get_silences(microscope_sanitised) if len(silences) == 0: return None for silence in silences: From 9b87f38887e5c78c7a19fdab892d642e33464ba2 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Tue, 12 May 2026 15:41:51 +0100 Subject: [PATCH 08/15] validate microscope permissions for alerts changed 'microscope' to 'instrument_name' for consistency. Alert silencing endpoints instrument_name type is now MurfeyInstrumentName for validation. --- src/murfey/server/api/session_info.py | 29 ++++++++++++++------------- src/murfey/util/config.py | 3 +++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 047f2dc74..423fec74f 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -473,29 +473,29 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur return FileResponse(path=tiff_file) if isinstance(tiff_file, Path) else tiff_file #Methods for turning alerts on and off -alertmanager_url = "https://murfey-alertmanager.diamond.ac.uk" -alertmanager_url = sanitise(alertmanager_url) -@router.get("/silences/{microscope}") -def get_silences(microscope: str): - microscope_sanitised = sanitise(microscope) - silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={microscope_sanitised}") +@router.get("/silences/{instrument_name}") +def get_silences(instrument_name: MurfeyInstrumentName): + machine_config = machine_info_by_instrument(instrument_name) + alertmanager_url = machine_config.alertmanager_url + silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={instrument_name}") active_silences = [] for silence in silences.json(): if silence['status']['state'] == 'active': active_silences.append(silence) return (active_silences) -@router.post("/silences/{microscope}") -def create_silence(microscope: str, end_time: datetime ): - microscope = sanitise(microscope) +@router.post("/silences/{instrument_name}") +def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime ): + machine_config = machine_info_by_instrument(instrument_name) + alertmanager_url = machine_config.alertmanager_url start_time = datetime.now().astimezone().isoformat() end_time = end_time.astimezone().isoformat() silence_json = { "matchers":[ { "name": "microscope", - "value": microscope, + "value": instrument_name, "isRegex": False }], "createdBy": "murfey", @@ -508,10 +508,11 @@ def create_silence(microscope: str, end_time: datetime ): response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) return JSONResponse(status_code=response.status_code, content=response.json()) #return a response with same data and code as from alertmanager -@router.delete("/silences/{microscope}") #delete all silences for given microscope -def delete_silences(microscope: str): - microscope_sanitised = sanitise(microscope) - silences = get_silences(microscope_sanitised) +@router.delete("/silences/{instrument_name}") #delete all silences for given microscope +def delete_silences(instrument_name: MurfeyInstrumentName): + machine_config = machine_info_by_instrument(instrument_name) + alertmanager_url = machine_config.alertmanager_url + silences = get_silences(instrument_name) if len(silences) == 0: return None for silence in silences: diff --git a/src/murfey/util/config.py b/src/murfey/util/config.py index 11e8db570..e60f07a75 100644 --- a/src/murfey/util/config.py +++ b/src/murfey/util/config.py @@ -118,6 +118,9 @@ class MachineConfig(BaseModel): # type: ignore # Pydantic BaseModel settings model_config = ConfigDict(extra="allow") + # Alertmanager URL + alertmanager_url: str = "https://murfey-alertmanager.diamond.ac.uk" + @field_validator("calibrations", mode="before") @classmethod def validate_calibration_data( From 87440faabce29ffa644f3b35e810004274e820a2 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Tue, 12 May 2026 16:28:39 +0100 Subject: [PATCH 09/15] sanitise instrument name inline --- src/murfey/server/api/session_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 423fec74f..d648a5fcc 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -478,7 +478,7 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur def get_silences(instrument_name: MurfeyInstrumentName): machine_config = machine_info_by_instrument(instrument_name) alertmanager_url = machine_config.alertmanager_url - silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={instrument_name}") + silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}") active_silences = [] for silence in silences.json(): if silence['status']['state'] == 'active': From dff9443fc5b2934ccafc584d1955a86273a44c65 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Wed, 13 May 2026 09:58:22 +0100 Subject: [PATCH 10/15] check microscope is in machineconfigs --- src/murfey/server/api/session_info.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index d648a5fcc..95c05629b 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -476,7 +476,11 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur @router.get("/silences/{instrument_name}") def get_silences(instrument_name: MurfeyInstrumentName): - machine_config = machine_info_by_instrument(instrument_name) + all_configs = get_machine_config() + microscopes = list(all_configs.keys()) + if (instrument_name not in microscopes): + return None + machine_config = all_configs[instrument_name] alertmanager_url = machine_config.alertmanager_url silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}") active_silences = [] From df1d2bd577d548030496dd91e8ddce609365d555 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Wed, 13 May 2026 10:23:47 +0100 Subject: [PATCH 11/15] formatted using pre-commit checks --- src/murfey/server/api/session_info.py | 55 +++++++++++++++------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 95c05629b..4e60177e9 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -2,8 +2,8 @@ from logging import getLogger from pathlib import Path from typing import Dict, List, Optional -import requests +import requests from fastapi import APIRouter, Depends, Request from fastapi.responses import FileResponse, JSONResponse from pydantic import BaseModel @@ -472,47 +472,54 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur ) return FileResponse(path=tiff_file) if isinstance(tiff_file, Path) else tiff_file -#Methods for turning alerts on and off + +# Methods for turning alerts on and off + @router.get("/silences/{instrument_name}") def get_silences(instrument_name: MurfeyInstrumentName): all_configs = get_machine_config() microscopes = list(all_configs.keys()) - if (instrument_name not in microscopes): + if instrument_name not in microscopes: return None machine_config = all_configs[instrument_name] alertmanager_url = machine_config.alertmanager_url - silences = requests.get(f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}") + silences = requests.get( + f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}" + ) active_silences = [] for silence in silences.json(): - if silence['status']['state'] == 'active': + if silence["status"]["state"] == "active": active_silences.append(silence) - return (active_silences) + return active_silences + @router.post("/silences/{instrument_name}") -def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime ): +def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): machine_config = machine_info_by_instrument(instrument_name) alertmanager_url = machine_config.alertmanager_url start_time = datetime.now().astimezone().isoformat() - end_time = end_time.astimezone().isoformat() + end_time_str = end_time.astimezone().isoformat() silence_json = { - "matchers":[ - { - "name": "microscope", - "value": instrument_name, - "isRegex": False - }], - "createdBy": "murfey", - "annotations":{"description": "Test"}, - "comment": "silence created from murfey", - "status": {"state": "active"}, - "startsAt": str(start_time), - "endsAt": str(end_time) + "matchers": [ + {"name": "microscope", "value": instrument_name, "isRegex": False} + ], + "createdBy": "murfey", + "annotations": {"description": "Test"}, + "comment": "silence created from murfey", + "status": {"state": "active"}, + "startsAt": str(start_time), + "endsAt": str(end_time_str), } response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) - return JSONResponse(status_code=response.status_code, content=response.json()) #return a response with same data and code as from alertmanager + return JSONResponse( + status_code=response.status_code, content=response.json() + ) # return a response with same data and code as from alertmanager + -@router.delete("/silences/{instrument_name}") #delete all silences for given microscope +@router.delete( + "/silences/{instrument_name}" +) # delete all silences for given microscope def delete_silences(instrument_name: MurfeyInstrumentName): machine_config = machine_info_by_instrument(instrument_name) alertmanager_url = machine_config.alertmanager_url @@ -520,6 +527,6 @@ def delete_silences(instrument_name: MurfeyInstrumentName): if len(silences) == 0: return None for silence in silences: - id = silence['id'] + id = silence["id"] response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") - return response #returns final response in loop + return response # returns final response in loop From 9d4ea453e1990d3e22a3202fe5512148d42b26d2 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Wed, 13 May 2026 10:49:25 +0100 Subject: [PATCH 12/15] add routes to route_manifest --- src/murfey/server/api/session_info.py | 118 +++++++++++++------------- src/murfey/util/route_manifest.yaml | 21 +++++ 2 files changed, 80 insertions(+), 59 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 4e60177e9..df2f5847a 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -277,6 +277,65 @@ def update_current_gain_ref( db.commit() +# Methods for turning alerts on and off + + +@router.get("/silences/{instrument_name}") +def get_silences(instrument_name: MurfeyInstrumentName): + all_configs = get_machine_config() + microscopes = list(all_configs.keys()) + if instrument_name not in microscopes: + return None + machine_config = all_configs[instrument_name] + alertmanager_url = machine_config.alertmanager_url + silences = requests.get( + f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}" + ) + active_silences = [] + for silence in silences.json(): + if silence["status"]["state"] == "active": + active_silences.append(silence) + return active_silences + + +@router.post("/silences/{instrument_name}") +def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): + machine_config = machine_info_by_instrument(instrument_name) + alertmanager_url = machine_config.alertmanager_url + start_time = datetime.now().astimezone().isoformat() + end_time_str = end_time.astimezone().isoformat() + silence_json = { + "matchers": [ + {"name": "microscope", "value": instrument_name, "isRegex": False} + ], + "createdBy": "murfey", + "annotations": {"description": "Test"}, + "comment": "silence created from murfey", + "status": {"state": "active"}, + "startsAt": str(start_time), + "endsAt": str(end_time_str), + } + response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) + return JSONResponse( + status_code=response.status_code, content=response.json() + ) # return a response with same data and code as from alertmanager + + +@router.delete( + "/silences/{instrument_name}" +) # delete all silences for given microscope +def delete_silences(instrument_name: MurfeyInstrumentName): + machine_config = machine_info_by_instrument(instrument_name) + alertmanager_url = machine_config.alertmanager_url + silences = get_silences(instrument_name) + if len(silences) == 0: + return None + for silence in silences: + id = silence["id"] + response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") + return response # returns final response in loop + + spa_router = APIRouter( prefix="/session_info/spa", dependencies=[Depends(validate_token)], @@ -471,62 +530,3 @@ async def get_tiff_file(visit_name: str, session_id: int, tiff_path: str, db=mur visit_name=visit_name, session_id=session_id, tiff_path=tiff_path, db=db ) return FileResponse(path=tiff_file) if isinstance(tiff_file, Path) else tiff_file - - -# Methods for turning alerts on and off - - -@router.get("/silences/{instrument_name}") -def get_silences(instrument_name: MurfeyInstrumentName): - all_configs = get_machine_config() - microscopes = list(all_configs.keys()) - if instrument_name not in microscopes: - return None - machine_config = all_configs[instrument_name] - alertmanager_url = machine_config.alertmanager_url - silences = requests.get( - f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}" - ) - active_silences = [] - for silence in silences.json(): - if silence["status"]["state"] == "active": - active_silences.append(silence) - return active_silences - - -@router.post("/silences/{instrument_name}") -def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): - machine_config = machine_info_by_instrument(instrument_name) - alertmanager_url = machine_config.alertmanager_url - start_time = datetime.now().astimezone().isoformat() - end_time_str = end_time.astimezone().isoformat() - silence_json = { - "matchers": [ - {"name": "microscope", "value": instrument_name, "isRegex": False} - ], - "createdBy": "murfey", - "annotations": {"description": "Test"}, - "comment": "silence created from murfey", - "status": {"state": "active"}, - "startsAt": str(start_time), - "endsAt": str(end_time_str), - } - response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) - return JSONResponse( - status_code=response.status_code, content=response.json() - ) # return a response with same data and code as from alertmanager - - -@router.delete( - "/silences/{instrument_name}" -) # delete all silences for given microscope -def delete_silences(instrument_name: MurfeyInstrumentName): - machine_config = machine_info_by_instrument(instrument_name) - alertmanager_url = machine_config.alertmanager_url - silences = get_silences(instrument_name) - if len(silences) == 0: - return None - for silence in silences: - id = silence["id"] - response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") - return response # returns final response in loop diff --git a/src/murfey/util/route_manifest.yaml b/src/murfey/util/route_manifest.yaml index 10ace7fd3..276fa5f9b 100644 --- a/src/murfey/util/route_manifest.yaml +++ b/src/murfey/util/route_manifest.yaml @@ -1153,6 +1153,27 @@ murfey.server.api.session_info.router: type: int methods: - PUT + - path: /session_info/silences/{instrument_name} + function: get_silences + path_params: + - name: instrument_name + type: str + methods: + - GET + - path: /session_info/silences/{instrument_name} + function: create_silence + path_params: + - name: instrument_name + type: str + methods: + - POST + - path: /session_info/silences/{instrument_name} + function: delete_silences + path_params: + - name: instrument_name + type: str + methods: + - DELETE murfey.server.api.session_info.spa_router: - path: /session_info/spa/sessions/{session_id}/spa_processing_parameters function: get_spa_proc_param_details From e264d98037a57e6ec5af2f3697a96b5f908d402d Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Wed, 13 May 2026 14:10:44 +0100 Subject: [PATCH 13/15] add logs, move url, change alert description --- src/murfey/server/api/session_info.py | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index df2f5847a..12fc8692e 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -277,7 +277,7 @@ def update_current_gain_ref( db.commit() -# Methods for turning alerts on and off +# Endpoints for turning alerts on and off @router.get("/silences/{instrument_name}") @@ -288,11 +288,18 @@ def get_silences(instrument_name: MurfeyInstrumentName): return None machine_config = all_configs[instrument_name] alertmanager_url = machine_config.alertmanager_url - silences = requests.get( + if alertmanager_url == "": + logger.warning(f"alertmanager_url not set for {sanitise(instrument_name)}") + return None + response = requests.get( f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}" ) + if response.status_code != 200: + logger.warning( + f"Tried to get silences for {sanitise(instrument_name)}, but received status {response.status_code} from alertmanager API" + ) active_silences = [] - for silence in silences.json(): + for silence in response.json(): if silence["status"]["state"] == "active": active_silences.append(silence) return active_silences @@ -302,6 +309,9 @@ def get_silences(instrument_name: MurfeyInstrumentName): def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): machine_config = machine_info_by_instrument(instrument_name) alertmanager_url = machine_config.alertmanager_url + if alertmanager_url == "": + logger.warning(f"alertmanager_url not set for {sanitise(instrument_name)}") + return None start_time = datetime.now().astimezone().isoformat() end_time_str = end_time.astimezone().isoformat() silence_json = { @@ -309,13 +319,17 @@ def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): {"name": "microscope", "value": instrument_name, "isRegex": False} ], "createdBy": "murfey", - "annotations": {"description": "Test"}, + "annotations": {"description": f"Silence for alerts on {instrument_name}"}, "comment": "silence created from murfey", "status": {"state": "active"}, "startsAt": str(start_time), "endsAt": str(end_time_str), } response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) + if response.status_code != 200: + logger.warning( + f"Tried to create silence for {sanitise(instrument_name)}, but received status {response.status_code} from alertmanager API" + ) return JSONResponse( status_code=response.status_code, content=response.json() ) # return a response with same data and code as from alertmanager @@ -327,12 +341,19 @@ def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): def delete_silences(instrument_name: MurfeyInstrumentName): machine_config = machine_info_by_instrument(instrument_name) alertmanager_url = machine_config.alertmanager_url + if alertmanager_url == "": + logger.warning(f"alertmanager_url not set for {sanitise(instrument_name)}") + return None silences = get_silences(instrument_name) if len(silences) == 0: return None for silence in silences: id = silence["id"] response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") + if response.status_code != 200: + logger.warning( + f"Tried to delete silence {id} for {sanitise(instrument_name)}, but received status {response.status_code} from alertmanager API" + ) return response # returns final response in loop From ddc086fadb0e081f2776a9375d22d8dbf9ceb5f7 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Wed, 13 May 2026 16:21:07 +0100 Subject: [PATCH 14/15] ensuring we get microscope from config --- src/murfey/server/api/session_info.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index 12fc8692e..fb7e439a9 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -283,17 +283,18 @@ def update_current_gain_ref( @router.get("/silences/{instrument_name}") def get_silences(instrument_name: MurfeyInstrumentName): all_configs = get_machine_config() - microscopes = list(all_configs.keys()) - if instrument_name not in microscopes: + valid_instrument_names = list(all_configs.keys()) + if instrument_name in valid_instrument_names: + index = valid_instrument_names.index(instrument_name) + query_params = f"filter=microscope={sanitise(valid_instrument_names[index])}" + else: return None machine_config = all_configs[instrument_name] alertmanager_url = machine_config.alertmanager_url if alertmanager_url == "": logger.warning(f"alertmanager_url not set for {sanitise(instrument_name)}") return None - response = requests.get( - f"{alertmanager_url}/api/v2/silences?filter=microscope={sanitise(instrument_name)}" - ) + response = requests.get(f"{alertmanager_url}/api/v2/silences?{query_params}") if response.status_code != 200: logger.warning( f"Tried to get silences for {sanitise(instrument_name)}, but received status {response.status_code} from alertmanager API" From c8ba61cdf9a8026ad8a5e1e0edbca773f05ccb78 Mon Sep 17 00:00:00 2001 From: Abigail Yates Date: Thu, 14 May 2026 15:59:35 +0100 Subject: [PATCH 15/15] change return types and update config --- src/murfey/server/api/session_info.py | 31 ++++++++++++++++----------- src/murfey/util/config.py | 4 +--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/murfey/server/api/session_info.py b/src/murfey/server/api/session_info.py index fb7e439a9..63bbbaa52 100644 --- a/src/murfey/server/api/session_info.py +++ b/src/murfey/server/api/session_info.py @@ -288,12 +288,15 @@ def get_silences(instrument_name: MurfeyInstrumentName): index = valid_instrument_names.index(instrument_name) query_params = f"filter=microscope={sanitise(valid_instrument_names[index])}" else: - return None + logger.warning( + f"invalid query parameters for get alerts on {sanitise(instrument_name)}" + ) + return [] machine_config = all_configs[instrument_name] alertmanager_url = machine_config.alertmanager_url - if alertmanager_url == "": + if not alertmanager_url: logger.warning(f"alertmanager_url not set for {sanitise(instrument_name)}") - return None + return [] response = requests.get(f"{alertmanager_url}/api/v2/silences?{query_params}") if response.status_code != 200: logger.warning( @@ -307,12 +310,16 @@ def get_silences(instrument_name: MurfeyInstrumentName): @router.post("/silences/{instrument_name}") -def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): +def create_silence( + instrument_name: MurfeyInstrumentName, end_time: datetime +) -> JSONResponse: machine_config = machine_info_by_instrument(instrument_name) alertmanager_url = machine_config.alertmanager_url - if alertmanager_url == "": + if not alertmanager_url: logger.warning(f"alertmanager_url not set for {sanitise(instrument_name)}") - return None + return JSONResponse( + status_code=500, content="URL not set. Could not turn off alerts" + ) start_time = datetime.now().astimezone().isoformat() end_time_str = end_time.astimezone().isoformat() silence_json = { @@ -329,7 +336,7 @@ def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): response = requests.post(f"{alertmanager_url}/api/v2/silences", json=silence_json) if response.status_code != 200: logger.warning( - f"Tried to create silence for {sanitise(instrument_name)}, but received status {response.status_code} from alertmanager API" + f"Create silence for {sanitise(instrument_name)} received status {response.status_code} from alertmanager API" ) return JSONResponse( status_code=response.status_code, content=response.json() @@ -342,18 +349,18 @@ def create_silence(instrument_name: MurfeyInstrumentName, end_time: datetime): def delete_silences(instrument_name: MurfeyInstrumentName): machine_config = machine_info_by_instrument(instrument_name) alertmanager_url = machine_config.alertmanager_url - if alertmanager_url == "": + if not alertmanager_url: logger.warning(f"alertmanager_url not set for {sanitise(instrument_name)}") - return None + return JSONResponse(status_code=500, content="URL not set") silences = get_silences(instrument_name) - if len(silences) == 0: - return None + if not silences: + return {"success": False} for silence in silences: id = silence["id"] response = requests.delete(f"{alertmanager_url}/api/v2/silence/{id}") if response.status_code != 200: logger.warning( - f"Tried to delete silence {id} for {sanitise(instrument_name)}, but received status {response.status_code} from alertmanager API" + f"Delete silence {id} for {sanitise(instrument_name)} received status {response.status_code} from alertmanager API" ) return response # returns final response in loop diff --git a/src/murfey/util/config.py b/src/murfey/util/config.py index e60f07a75..2331bdde1 100644 --- a/src/murfey/util/config.py +++ b/src/murfey/util/config.py @@ -109,6 +109,7 @@ class MachineConfig(BaseModel): # type: ignore frontend_url: str = "http://localhost:3000" instrument_server_url: str = "http://localhost:8001" smartem_api_url: str = "" + alertmanager_url: str = "" # Messaging queues failure_queue: str = "" @@ -118,9 +119,6 @@ class MachineConfig(BaseModel): # type: ignore # Pydantic BaseModel settings model_config = ConfigDict(extra="allow") - # Alertmanager URL - alertmanager_url: str = "https://murfey-alertmanager.diamond.ac.uk" - @field_validator("calibrations", mode="before") @classmethod def validate_calibration_data(