notifications: add low disk space notification

This commit is contained in:
Girish Ramakrishnan
2025-05-07 13:15:08 +02:00
parent 5489237f11
commit 0cf0c7a27c
9 changed files with 73 additions and 18 deletions
+1
View File
@@ -2940,4 +2940,5 @@
* sendmail: requiresValidCertificate option for using mail server domain
* mail: update haraka to 3.1.0
* sshfs: implement rm via ssh
* notification: low disk notification
+2 -1
View File
@@ -1159,7 +1159,8 @@
"appUp": "App is back online",
"appDown": "App is down",
"rebootRequired": "Server reboot required",
"cloudronUpdateFailed": "Cloudron Update Failed"
"cloudronUpdateFailed": "Cloudron Update Failed",
"diskSpace": "Low Disk Space"
},
"settingsDialog": {
"description": "Manage your personal notification preferences here. Cloudron will send an email for the selected events to your primary email address."
+3 -1
View File
@@ -1201,7 +1201,9 @@
"title": "Herstel {{ app }}",
"restoreAction": "Herstel",
"warning": "Alle gegevens tussen nu en de laatst bekende backup zullen onherstelbaar verloren gaan. Het is aanbevolen om eerst handmatig een backup te maken van de gegevens vóór het herstellen.",
"description": "Hierdoor zal deze app worden hersteld met de gegevens van {{ creationTime }}."
"description": "Hierdoor zal deze app worden hersteld met de gegevens van {{ creationTime }}.",
"cloneAction": "Kloon",
"cloneActionOverwrite": "Kloon en overschrijf DNS"
},
"cloneDialog": {
"title": "Kloon {{ app }}",
+12 -9
View File
@@ -368,7 +368,7 @@
"body": "Письмо отправлено на {{ email }}"
},
"exposedLdap": {
"title": "Сервер LDAP",
"title": "Поставщик LDAP",
"ipRestriction": {
"description": "Ограничьте доступ к серверу каталогов только для определённого круга IP-адресов и диапазонов. Строки, начинающиеся с <code>#</code>, будут считаться комментарием.",
"placeholder": "IP-адреса или подсети, разделённые строками",
@@ -545,7 +545,7 @@
"updates": {
"info": {
"customAppUpdateInfo": "Для сторонних приложений автообновления недоступны.",
"updateAvailableAction": "Доступно обновление",
"updateAvailableAction": "Доступно Обновление",
"title": "Данные приложения",
"description": "Название и версия приложения",
"appId": "ID приложения",
@@ -707,7 +707,7 @@
"running": "Запущено",
"stopped": "Остановлено",
"notResponding": "Не отвечает",
"updateAvailable": "Доступно обновление"
"updateAvailable": "Доступно Обновление"
},
"display": {
"tags": "Метки",
@@ -839,7 +839,9 @@
"title": "Восстановить {{ app }}",
"restoreAction": "Восстановить",
"description": "Данное действие восстановит данные приложения от {{ creationTime }}.",
"warning": "Любые данные, созданные между настоящим моментом и последней известной резервной копией будут безвозвратно утеряны. Рекомендуем создать резервную копию текущих данных перед восстановлением."
"warning": "Любые данные, созданные между настоящим моментом и последней известной резервной копией будут безвозвратно утеряны. Рекомендуем создать резервную копию текущих данных перед восстановлением.",
"cloneAction": "Клонировать",
"cloneActionOverwrite": "Клонировать и перезаписать DNS"
},
"cloneDialog": {
"title": "Клонировать {{ app }}",
@@ -890,7 +892,8 @@
"archiveDialog": {
"title": "Архивирование {{app}}",
"description": "Это действие удалит приложение и поместит его последнюю резервную копию от {{date}} в Архив."
}
},
"updateAvailableTooltip": "Доступно обновление"
},
"backups": {
"location": {
@@ -1049,7 +1052,7 @@
"title": "Подвал",
"description": "Используйте разметку Markdown для стилизации подвала.",
"subscriptionRequired": "Настройка подвала доступна только в платной подписке.",
"setupSubscriptionNow": "Настроить подписку"
"setupSubscriptionNow": "Оформить подписку"
},
"changeLogo": {
"title": "Выбрать изображение Cloudron"
@@ -1273,7 +1276,7 @@
"showLogsAction": "Показать логи",
"changeScheduleAction": "Изменить расписание",
"checkForUpdatesAction": "Проверить обновления",
"updateAvailableAction": "Обновление доступно",
"updateAvailableAction": "Доступно Обновление",
"version": "Версия платформы",
"stopUpdateAction": "Остановить обновление",
"description": "Обновления платформы и приложений применяются автоматически, на основании Расписания в <a href=\"/#/settings\">Системной часовой зоне</a>.",
@@ -1284,7 +1287,7 @@
"title": "Частный реестр Docker",
"description": "Cloudron может извлекать образ и устанавливать <a href=\"{{ customAppsLink }}\" target=\"_blank\">сторонние приложения</a> из частного реестра Docker.",
"subscriptionRequired": "Данная функция доступна только в платной подписке.",
"setupSubscriptionAction": "Настроить подписку",
"setupSubscriptionAction": "Оформить подписку",
"server": "Адрес сервера",
"username": "Имя пользователя",
"configureAction": "Настроить реестр",
@@ -2020,7 +2023,7 @@
"newClient": "Новый Клиент",
"empty": "Клиенты не найдены"
},
"title": "Поставщик OpenID Сonnect",
"title": "Поставщик OpenID",
"description": "Cloudron может выступать в качестве поставщика OpenID connect для внутренних приложений и внешних сервисов.",
"editClientDialog": {
"title": "Редактировать клиента {{ client }}"
@@ -32,6 +32,11 @@
<input type="checkbox" ng-model="settings.config.certificateRenewalFailed"> {{ 'notifications.settings.certificateRenewalFailed' | tr }}
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="settings.config.diskSpace"> {{ 'notifications.settings.diskSpace' | tr }}
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="settings.config.cloudronUpdateFailed"> {{ 'notifications.settings.cloudronUpdateFailed' | tr }}
@@ -0,0 +1,9 @@
Dear Cloudron Admin,
<%- message %>
Powered by https://cloudron.io
Don't want such mails? Change your notification preferences at <%= notificationsUrl %>
Sent at: <%= new Date().toUTCString() %>
+17
View File
@@ -13,6 +13,7 @@ exports = module.exports = {
oomEvent,
rebootRequired,
boxUpdateError,
lowDiskSpace,
sendTestMail,
@@ -279,6 +280,22 @@ async function rebootRequired(mailTo) {
await sendMail(mailOptions);
}
async function lowDiskSpace(mailTo, message) {
assert.strictEqual(typeof mailTo, 'string');
assert.strictEqual(typeof message, 'string');
const mailConfig = await getMailConfig();
const mailOptions = {
from: mailConfig.notificationFrom,
to: mailTo,
subject: `[${mailConfig.cloudronName}] Server is running low on disk space`,
text: render('low_disk_space-text.ejs', { message, notificationsUrl: mailConfig.notificationsUrl })
};
await sendMail(mailOptions);
}
async function boxUpdateError(mailTo, message) {
assert.strictEqual(typeof mailTo, 'string');
assert.strictEqual(typeof message, 'string');
+17 -3
View File
@@ -287,12 +287,26 @@ async function rebootRequired() {
}
}
async function onPin(type) {
async function lowDiskSpace(message) {
assert.strictEqual(typeof message, 'string');
const admins = await users.getAdmins();
for (const admin of admins) {
if (admin.notificationConfig.includes(exports.TYPE_DISK_SPACE)) {
await mailer.lowDiskSpace(admin.email, message);
}
}
}
async function onPin(type, message) {
assert.strictEqual(typeof type, 'string'); // TYPE_
assert.strictEqual(typeof message, 'string');
switch (type) {
case exports.TYPE_REBOOT:
return await rebootRequired();
case exports.TYPE_DISK_SPACE:
return await lowDiskSpace(message);
default:
break;
}
@@ -306,7 +320,7 @@ async function pin(type, title, message, options) {
const result = await getByType(type, options.context || '');
if (!result) {
await onPin(type);
await onPin(type, message);
return await add(type, title, message, { eventId: null, context: options.context || '' });
}
@@ -314,7 +328,7 @@ async function pin(type, title, message, options) {
const isUpdateType = type === exports.TYPE_BOX_UPDATE || type === exports.TYPE_MANUAL_APP_UPDATE_NEEDED;
const acknowledged = (isUpdateType && result.message === message) ? result.acknowledged : false;
if (result.acknowledged && !acknowledged) await onPin(type);
if (result.acknowledged && !acknowledged) await onPin(type, message);
await update(result, { title, message, acknowledged, creationTime: new Date() });
return result.id;
+7 -4
View File
@@ -189,15 +189,18 @@ async function checkDiskSpace() {
const filesystem = filesystems[fsPath];
if (filesystem.contents.length === 0) continue; // ignore if nothing interesting here
if (filesystem.available <= (1.25 * 1024 * 1024 * 1024)) { // 1.5G
markdownMessage += `* ${filesystem.filesystem} is at ${filesystem.capacity*100}% capacity.\n`;
if (filesystem.capacity >= 0.90) { // > 90%
const prettyUsed = df.prettyBytes(filesystem.used),
prettyAvailable = df.prettyBytes(filesystem.available),
prettySize = df.prettySize(filesystem.size);
markdownMessage += `* ${filesystem.filesystem}(${filesystem.type}) is at ${filesystem.capacity*100}% capacity. Used: ${prettyUsed} Available: ${prettyAvailable} Size: ${prettySize}\n`;
}
}
debug(`checkDiskSpace: disk space checked. out of space: ${markdownMessage || 'no'}`);
debug(`checkDiskSpace: disk space checked. low disk space: ${markdownMessage || 'no'}`);
if (markdownMessage) {
const finalMessage = `One or more file systems are running out of space. Please increase the disk size at the earliest.\n\n${markdownMessage}`;
const finalMessage = `One or more file systems are running low on space. Please increase the disk size at the earliest.\n\n${markdownMessage}`;
await notifications.pin(notifications.TYPE_DISK_SPACE, 'Server is running out of disk space', finalMessage, {});
} else {
await notifications.unpin(notifications.TYPE_DISK_SPACE, {});