Add processor heartbeat management and improve health check functionality
All checks were successful
Publish FaceAI Container / publish (push) Successful in 3m7s

- Introduced processor heartbeat configuration in environment variables and Docker setup.
- Implemented heartbeat publishing in the processor worker.
- Enhanced health check endpoint to include processor availability status.
- Updated frontend components to handle processor unavailability messages.
- Added legacy return functionality in the upload panel.
This commit is contained in:
MaddoScientisto 2026-04-19 11:50:11 +02:00
commit 87d9238795
14 changed files with 292 additions and 23 deletions

View file

@ -44,6 +44,7 @@ const copy = {
invalidImage: 'Seleziona un file immagine valido.',
pollError: 'Impossibile leggere lo stato della ricerca.',
searchFailed: 'La ricerca non è andata a buon fine.',
processorUnavailable: 'Il motore FaceAI non è disponibile in questo momento. Riprova tra poco.',
redirectError: 'Impossibile generare il link di ritorno.',
chooseSelfie: 'Seleziona un selfie prima di avviare la ricerca.',
raceDataUnavailable: 'I dati FaceAI non sono disponibili per questa gara.',
@ -94,6 +95,7 @@ const copy = {
invalidImage: 'Select a valid image file.',
pollError: 'Unable to read the search status.',
searchFailed: 'The search failed.',
processorUnavailable: 'The FaceAI processor is temporarily unavailable. Please try again shortly.',
redirectError: 'Unable to build the return link.',
chooseSelfie: 'Choose a selfie before starting the search.',
raceDataUnavailable: 'FaceAI data is not available for this race.',
@ -110,6 +112,7 @@ const knownServerMessages = {
'FaceAI is not available for this race.': 'unavailableDefault',
'Unable to read search status.': 'pollError',
'The search failed.': 'searchFailed',
'FaceAI processor is temporarily unavailable. Please try again shortly.': 'processorUnavailable',
'Unable to build return URL.': 'redirectError',
'Unable to create the search.': 'searchCreateError',
'Choose a selfie before starting the search.': 'chooseSelfie'
@ -122,6 +125,18 @@ function isInvalidRaceAvailability(availability) {
return availability?.reasonCode === 'RACE_DIRECTORY_NOT_FOUND' || availability?.reasonCode === 'MISSING_RACE_STORAGE';
}
function buildLegacyReturnUrl(url) {
if (!url) {
return legacyHomeUrl;
}
try {
return new URL(url, window.location.href).toString();
} catch {
return legacyHomeUrl;
}
}
export function useFaceAiHome() {
const session = ref(null);
const loading = ref(true);
@ -408,9 +423,10 @@ export function useFaceAiHome() {
async function pollSearch(searchId) {
const response = await fetch(`/api/searches/${searchId}`, { credentials: 'include' });
if (!response.ok) {
errorMessage.value = t('pollError');
const payload = await response.json().catch(() => ({}));
errorMessage.value = localizeServerMessage(payload.error, 'pollError');
isSubmitting.value = false;
logFaceAiDebug('Search polling failed', { searchId, status: response.status });
logFaceAiDebug('Search polling failed', { searchId, status: response.status, payload });
return;
}
@ -493,6 +509,12 @@ export function useFaceAiHome() {
pollSearch(payload.id);
}
function returnToLegacy() {
const returnUrl = buildLegacyReturnUrl(session.value?.returnUrl);
logFaceAiDebug('Returning to legacy race page', { returnUrl });
window.location.replace(returnUrl);
}
onMounted(loadSession);
onBeforeUnmount(() => {
if (pollTimer) {
@ -523,6 +545,7 @@ export function useFaceAiHome() {
onFileChange,
openFilePicker,
redirectUrl,
returnToLegacy,
selectedFile,
selectedFileSizeLabel,
session,