[glm-grade=A] fix(notifications): un-gate 7 dead emitters + repair legacy 4-arg send shape (DC-094)
Two silent-death defects in one class: 1. Seven emitters absent from DEFAULT events, so the send() gate (config.events[canonical] !== true) dropped them on every install: ssl-cert-expiry, dns-propagation, drift-detected, dependency-restart-complete/-failed, recipeRemoved, workflow. All now default ON; dependency-restart spellings fold onto one canonical toggle; recipeRemoved aliases to recipe-removed in both the manager and route alias maps. 2. Nine call sites used a legacy 4-arg send(event, title, message, type) against the 3-arg signature: the message string landed in the type slot (Discord embed color fell back to info-blue, history.type wrong) and providers received the TITLE as the body - deploy-failure notifications carried no error text at all. Fixed at source (9 sites) plus a type-guarded shim in send() for external legacy callers. Also: explicit data.title now flows to ntfy Title header, email subject, Discord embed title, and history; settings UI gains 9 event toggles (separate Backup Complete/Failed) with defaults-on semantics. Tests: +24 (new DC-094 suite: defaults, gate pass-through, alias folding send-time and load-time, stored-config inheritance, shim body/title/ color/subject/history, type-guard, 3-arg no-regression); 4 assertions in bundled-workflows-health-check updated from the old 4-arg mock contract to the canonical shape (same behavior asserted). Full suite 116 suites / 2706 tests green. Judge: GLM-5.3 cold read via delegate_task (deleg_8a7cedd0), grade A, zero blockers; 2 polish items (Backups toggle conflation, shim type-guard) folded into this commit. Verdict URN: urn:ump:uyipjjwdjqjy3alvceqxvlucrymd7hnsben5udpp2bqycoh3l2la
This commit is contained in:
@@ -419,7 +419,10 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
|
||||
const notificationMessage = usedExisting
|
||||
? `**${template.name}** configured using existing container.\nURL: ${serviceUrl}`
|
||||
: `**${template.name}** has been deployed successfully.\nURL: ${serviceUrl}`;
|
||||
ctx.notification.send('deploymentSuccess', usedExisting ? 'Configuration Complete' : 'Deployment Successful', notificationMessage, 'success');
|
||||
ctx.notification.send('deploymentSuccess', {
|
||||
title: usedExisting ? 'Configuration Complete' : 'Deployment Successful',
|
||||
text: notificationMessage
|
||||
}, 'success');
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
@@ -427,7 +430,10 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
|
||||
const msg = error?.message || String(error || 'Unknown error');
|
||||
log.error('deploy', error, null, { note: 'Deployment failed', appId });
|
||||
const template = ctx.APP_TEMPLATES[appId];
|
||||
try { ctx.notification.send('deploymentFailed', 'Deployment Failed', `Failed to deploy **${template?.name || appId}**.\nError: ${msg}`, 'error'); } catch (_) {}
|
||||
try { ctx.notification.send('deploymentFailed', {
|
||||
title: 'Deployment Failed',
|
||||
text: `Failed to deploy **${template?.name || appId}**.\nError: ${msg}`
|
||||
}, 'error'); } catch (_) {}
|
||||
errorResponse(res, 500, ctx.safeErrorMessage(error));
|
||||
}
|
||||
}, 'apps-deploy'));
|
||||
|
||||
@@ -478,8 +478,10 @@ module.exports = function(ctx) {
|
||||
if (anyConfigured) {
|
||||
notification.send(
|
||||
'deploymentSuccess',
|
||||
'Arr Stack Auto-Connected',
|
||||
`Overseerr configured: ${Object.entries(configResults).filter(([k,v]) => v === 'configured').map(([k]) => k).join(', ')}`,
|
||||
{
|
||||
title: 'Arr Stack Auto-Connected',
|
||||
text: `Overseerr configured: ${Object.entries(configResults).filter(([k,v]) => v === 'configured').map(([k]) => k).join(', ')}`
|
||||
},
|
||||
'success'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -313,8 +313,10 @@ module.exports = function({ credentialManager, servicesStateManager, fetchT, asy
|
||||
if (succeeded > 0) {
|
||||
ctx.notification.send(
|
||||
'deploymentSuccess',
|
||||
'Smart Arr Connect Complete',
|
||||
`${succeeded}/${steps.length} steps completed successfully`,
|
||||
{
|
||||
title: 'Smart Arr Connect Complete',
|
||||
text: `${succeeded}/${steps.length} steps completed successfully`
|
||||
},
|
||||
'success'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -203,6 +203,11 @@ module.exports = function({ notification, asyncHandler, ok }) {
|
||||
backupComplete: 'backup-complete',
|
||||
backupFailed: 'backup-failed',
|
||||
autoRestart: 'auto-restart',
|
||||
// DC-094: same additions as the manager's EVENT_ALIASES — keep the
|
||||
// two maps in sync so a key saved here is the key send() gates on.
|
||||
recipeRemoved: 'recipe-removed',
|
||||
'dependency-restart-complete': 'dependency-restart',
|
||||
'dependency-restart-failed': 'dependency-restart',
|
||||
};
|
||||
const folded = {};
|
||||
for (const [k, v] of Object.entries(events)) {
|
||||
@@ -264,7 +269,7 @@ module.exports = function({ notification, asyncHandler, ok }) {
|
||||
res.json({ success: result.success, provider, error: result.error });
|
||||
} else {
|
||||
// Test all enabled providers
|
||||
const result = await notification.send('test', 'Test Notification', 'This is a test notification from DashCaddy.', 'info');
|
||||
const result = await notification.send('test', { title: 'Test Notification', text: 'This is a test notification from DashCaddy.' }, 'info');
|
||||
ok(res, { ...result });
|
||||
}
|
||||
}, 'notifications-test'));
|
||||
|
||||
@@ -142,10 +142,10 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
|
||||
setupInstructions: recipe.setupInstructions
|
||||
};
|
||||
|
||||
ctx.notification.send('deploymentSuccess', 'Recipe Deployed',
|
||||
`**${recipe.name}** recipe deployed (${deployedComponents.length} components).`,
|
||||
'success'
|
||||
);
|
||||
ctx.notification.send('deploymentSuccess', {
|
||||
title: 'Recipe Deployed',
|
||||
text: `**${recipe.name}** recipe deployed (${deployedComponents.length} components).`
|
||||
}, 'success');
|
||||
|
||||
ok(res, response);
|
||||
} catch (error) {
|
||||
@@ -175,9 +175,10 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
|
||||
}
|
||||
}
|
||||
|
||||
ctx.notification.send('deploymentFailed', 'Recipe Failed',
|
||||
`Failed to deploy **${recipe.name}**: ${error.message}`, 'error'
|
||||
);
|
||||
ctx.notification.send('deploymentFailed', {
|
||||
title: 'Recipe Failed',
|
||||
text: `Failed to deploy **${recipe.name}**: ${error.message}`
|
||||
}, 'error');
|
||||
|
||||
// Error automatically handled by middleware
|
||||
}
|
||||
|
||||
@@ -273,10 +273,10 @@ module.exports = function({ servicesStateManager, asyncHandler, log, docker, not
|
||||
}
|
||||
}
|
||||
|
||||
ctx.notification.send('recipeRemoved', 'Recipe Removed',
|
||||
`Removed **${recipeId}** recipe (${results.filter(r => r.status === 'removed').length} containers).`,
|
||||
'info'
|
||||
);
|
||||
ctx.notification.send('recipeRemoved', {
|
||||
title: 'Recipe Removed',
|
||||
text: `Removed **${recipeId}** recipe (${results.filter(r => r.status === 'removed').length} containers).`
|
||||
}, 'info');
|
||||
|
||||
log.info('recipe', 'Recipe removed', { recipeId, results });
|
||||
ok(res, { recipeId, results });
|
||||
|
||||
Reference in New Issue
Block a user