alarms now support roles with different recipients - aesthetic changes on the dashboard
calc: $now - $last_collected_t
every: 10s
warn: $this > ( 5 * $update_every)
- crit: $this > (10 * $update_every)
+ crit: $this > (60 * $update_every)
units: seconds ago
info: number of seconds since the last successful data collection
-
+ to: webmaster
warn: $this > 90
units: %
info: average cpu utilization for the last 5 minutes
+ to: sysadmin
template: 5min_iowait_cpu_pcent
on: system.cpu
warn: $this > 10
units: %
info: average wait I/O for the last 5 minutes
+ to: sysadmin
template: 20min_steal_cpu_pcent
on: system.cpu
warn: $this > 10
units: %
info: average stolen CPU time for the last 20 minutes
+ to: sysadmin
crit: $this > 95
units: %
info: current disk space usage
+ to: sysadmin
# -----------------------------------------------------------------------------
crit: $this > 0 and $this < 24
units: hours
info: estimated time the disk will run out of space, if the system continues to add data with the rate of the last 30 minutes
+ to: sysadmin
# -----------------------------------------------------------------------------
crit: $this > $red
units: %
info: the percentage of time the disk was busy, during the last 10 minutes
+ to: sysadmin
# raise an alarm if the disk backlog
crit: $this > $red
units: ms
info: average of the kernel estimated disk backlog, for the last 10 minutes
+ to: sysadmin
crit: $this < 100
units: entries
info: minimum entries in the random numbers pool (entropy), for the last 30 minutes
+ to: sysadmin
calc: $now - $last_collected_t
every: 10s
warn: $this > ( 5 * $update_every)
- crit: $this > (10 * $update_every)
+ crit: $this > (60 * $update_every)
units: seconds ago
info: number of seconds since the last successful data collection
+ to: dba
# detect if memcached cache is full
crit: $this > 90
units: %
info: current cache memory usage
+ to: dba
# find the rate memcached cache is filling
crit: $this > 0 and $this < 24
units: hours
info: estimated time the cache will run out of space, if the system continues to add data with the rate of the last 30 minutes
+ to: dba
calc: $now - $last_collected_t
every: 10s
warn: $this > ( 5 * $update_every)
- crit: $this > (10 * $update_every)
+ crit: $this > (60 * $update_every)
units: seconds ago
info: number of seconds since the last successful data collection
+ to: domainadmin
crit: $this > 0
units: packets
info: dropped packets in the last 30 minutes
+ to: sysadmin
# check if an interface is having FIFO
crit: $this > 0
units: errors
info: network interface fifo errors in the last 30 minutes
+ to: sysadmin
calc: $now - $last_collected_t
every: 10s
warn: $this > ( 5 * $update_every)
- crit: $this > (10 * $update_every)
+ crit: $this > (60 * $update_every)
units: seconds ago
info: number of seconds since the last successful data collection
+ to: webmaster
# warn: $this > 0
# units: packets
# info: dropped packets in the last 30 minutes
+# to: sysadmin
crit: $this > 90
units: %
info: system RAM usage
+ to: sysadmin
calc: $now - $last_collected_t
every: 10s
warn: $this > ( 5 * $update_every)
- crit: $this > (10 * $update_every)
+ crit: $this > (60 * $update_every)
units: seconds ago
info: number of seconds since the last successful data collection
+ to: dba
calc: $now - $last_collected_t
every: 10s
warn: $this > ( 5 * $update_every)
- crit: $this > (10 * $update_every)
+ crit: $this > (60 * $update_every)
units: seconds ago
info: number of seconds since the last successful data collection
+ to: proxyadmin
crit: $this > 20
units: % of RAM
info: the amount of memory swapped in the last 30 minutes, as a percentage of the system RAM
+ to: sysadmin
alarm: pcent_of_ram_in_swap
on: system.swap
crit: $this > 50
units: % of RAM
info: the swap memory used, as a percentage of the system RAM
+ to: sysadmin
--- /dev/null
+# Configuration for alarms recipients
+
+# netdata alarms have been categorized to allow different roles to receive
+# alarms related to their work.
+
+# this file defines the email addresses for each role. if a role is not
+# defined, the email will be sent to root.
+
+# you can set multiple addresses for each role, like this:
+#
+# recipients[sysadmin]="admin1@example.com, admin2@example.com"
+#
+# it is important to add the comma between email addresses.
+
+# This configuration file is a BASH script itself. The 'recipients' variable is
+# an associative array. So you can use other variables too, like this one:
+
+default_recipient_for_all_roles="root"
+
+
+# -----------------------------------------------------------------------------
+# generic system alarms
+# CPU, disks, entropy, etc
+
+recipients[sysadmin]="${default_recipient_for_all_roles}"
+
+
+# -----------------------------------------------------------------------------
+# DNS related alarms
+
+recipients[domainadmin]="${default_recipient_for_all_roles}"
+
+
+# -----------------------------------------------------------------------------
+# database servers alarms
+# mysql, redis, memcached, etc
+
+recipients[dba]="${default_recipient_for_all_roles}"
+
+
+# -----------------------------------------------------------------------------
+# web servers alarms
+# apache, nginx, etc
+
+recipients[webmaster]="${default_recipient_for_all_roles}"
+
+
+# -----------------------------------------------------------------------------
+# proxy servers alarms
+# apache, nginx, etc
+
+recipients[proxyadmin]="${default_recipient_for_all_roles}"
+
['7cf6402b51e5070f2be3ad6fe059ff89']='charts.d.conf'
['a02d14124b19c635c1426cee2e98bac5']='charts.d.conf'
['ca026d7c779f0a7cb7787713c5be5c47']='charts.d.conf'
+ ['074df527cc70b5f38c0714f08f20e57c']='health.d/apache.conf'
['3848172053221b95279ba9bf789cd4e0']='health.d/apache.conf'
['842b1ad5b89bfa5f421d9c5b72e001a4']='health.d/apache.conf'
+ ['a6d5ce2572bf7a1dce9e545fcd29273e']='health.d/apache.conf'
['084ee72d64760f2641b0720e79c922f3']='health.d/cpu.conf'
+ ['254de8ec49602bea2da3631676d7cfec']='health.d/cpu.conf'
['623771eecb3c277fc728b5304793f93b']='health.d/cpu.conf'
['7596ae54d46ce199ac599429ef753caf']='health.d/cpu.conf'
['fdd11640ba626cc2064c2fe3ea3eee4c']='health.d/cpu.conf'
['a8167dafeac0b66696a1d9b08e815cda']='health.d/disks.conf'
['afdae4646c755ff2d117527fbf761c8e']='health.d/disks.conf'
['cb60badf376d246ad8ec9d3f524db430']='health.d/disks.conf'
+ ['dd8254ef74509a3e38cb2838e30f7e63']='health.d/disks.conf'
['e8ec8046c7007af6ca3e8c51e62c99f8']='health.d/disks.conf'
['2d1d7498c72f4245cf32902c2b7e71e0']='health.d/entropy.conf'
['450667c552ab7a7d8d4a2c214fdacca5']='health.d/entropy.conf'
+ ['5ff1bcaa58695754e2f6980bfe19f579']='health.d/entropy.conf'
['a8feb36776005bf419c90278787a1be8']='health.d/entropy.conf'
['d8dc489e32f7114c6298fce94e86a8ef']='health.d/entropy.conf'
['e449e5582279742496550df14b6fca95']='health.d/entropy.conf'
['45a77ac36ba9f1898144b902de17204b']='health.d/memcached.conf'
+ ['621f10b257a11add5ff5aff41e9662e3']='health.d/memcached.conf'
+ ['7e5fc1644aa7a54f9dbb1bd102521b09']='health.d/memcached.conf'
['b81b8f331161b0d48e03f6fbf6b6d062']='health.d/memcached.conf'
['c1a7e634b5b8aad523a0d115a93379cd']='health.d/memcached.conf'
['f8c30f22df92765e2c0fab3c8174e2fc']='health.d/memcached.conf'
['373c1276dc9e65884ff2b26e1f08afe7']='health.d/named.conf'
+ ['6b39de5d85db45115db236347a6896d4']='health.d/named.conf'
['846ce94bfeeb90c0dc6a89e8d25f1a68']='health.d/named.conf'
['ddda2bb1c88be03b637d3285406f7910']='health.d/named.conf'
['2827de41cf34a91b7a8e4d8724f59668']='health.d/net.conf'
['2ad55a5d1e885cf142849a78d4b00401']='health.d/net.conf'
['43ebb7f224c3b232d8ad044d7e9508b6']='health.d/net.conf'
+ ['cb178b15427274d7def5b14bc4c09441']='health.d/net.conf'
['d11711b3647bc2bdd0292dd7deebbeb1']='health.d/net.conf'
['de02f899a61f21b86adb646940f0bcae']='health.d/net.conf'
+ ['32fde0057c790964f2c743cb3c9aad29']='health.d/nginx.conf'
['64c48f9726ab987baec9c617a9fef7a6']='health.d/nginx.conf'
['7a985528cc9176564640001aa73e3492']='health.d/nginx.conf'
+ ['eb5168f0b516bc982aac45e59da6e52e']='health.d/nginx.conf'
['36fdd55665cf10b0db164c2a0cca5e57']='health.d/qos.conf'
+ ['55608bdd908a3806df1468f6ee318b2b']='health.d/qos.conf'
['80f109ff293ac94222bf3959432751bd']='health.d/qos.conf'
['20be73f473e59bc7de1fe61d53466aba']='health.d/ram.conf'
+ ['a09714b5942cf25a89ec3da1dbc18063']='health.d/ram.conf'
['b7d769ce86a7aebba01315da5c0799e6']='health.d/ram.conf'
['325617412a628e3bc776e3fbb777a2a6']='health.d/redis.conf'
+ ['3634d5eddc46fb0d50cf47f370670c2c']='health.d/redis.conf'
['4f6a5b47a13f5912cc89e9286701dd08']='health.d/redis.conf'
['23ae815aefa221b1929f96752a1f7556']='health.d/squid.conf'
+ ['3cc6255457d4cba881ae0554ae5d9190']='health.d/squid.conf'
['a4a8660728c6afcb528cc6b378897d6b']='health.d/squid.conf'
['ef9916ea144878a9f37cbb6b1b29da10']='health.d/squid.conf'
+ ['8214bb8f4b005aa4691fcd38f7331e8f']='health.d/swap.conf'
['a44899a5795bed2863c1d11aa3e85586']='health.d/swap.conf'
['a7320c6f26191b9599ec3bc4be007a93']='health.d/swap.conf'
['c9b792755de59d842ba95f8c315d94c8']='health.d/swap.conf'
['da29d2ab1ab7b8fda189960c840e5144']='health.d/swap.conf'
+ ['707a63f53f4b32e01d134ae90ba94aad']='health_email_recipients.conf'
['13141998a5d71308d9c119834c27bfd3']='python.d.conf'
['38d1bf04fe9901481dd6febcc0404a86']='python.d.conf'
['4b775fb31342f1478b3773d041a72911']='python.d.conf'
// Grid Current Power Chart
if(d['GriPwr'].value !== null) {
- var id = 'sma_webbox_' + service.name + '.current';
+ var id = 'smawebbox_' + service.name + '.current';
var chart = webbox.charts[id];
if(typeof chart === 'undefined') {
title: service.name + ' Current Grid Power', // the title of the chart
units: d['GriPwr'].unit, // the units of the chart dimensions
family: 'now', // the family of the chart
- context: 'sma_webbox.grid.power', // the context of the chart
+ context: 'smawebbox.grid_power', // the context of the chart
type: netdata.chartTypes.area, // the type of the chart
priority: webbox.base_priority + 1, // the priority relative to others in the same family
update_every: service.update_every, // the expected update frequency of the chart
}
if(d['GriEgyTdy'].value !== null) {
- var id = 'sma_webbox_' + service.name + '.today';
+ var id = 'smawebbox_' + service.name + '.today';
var chart = webbox.charts[id];
if(typeof chart === 'undefined') {
title: service.name + ' Today Grid Power', // the title of the chart
units: d['GriEgyTdy'].unit, // the units of the chart dimensions
family: 'today', // the family of the chart
- context: 'sma_webbox.grid.power.today', // the context of the chart
+ context: 'smawebbox.grid_power_today', // the context of the chart
type: netdata.chartTypes.area, // the type of the chart
priority: webbox.base_priority + 2, // the priority relative to others in the same family
update_every: service.update_every, // the expected update frequency of the chart
}
if(d['GriEgyTot'].value !== null) {
- var id = 'sma_webbox_' + service.name + '.total';
+ var id = 'smawebbox_' + service.name + '.total';
var chart = webbox.charts[id];
if(typeof chart === 'undefined') {
title: service.name + ' Total Grid Power', // the title of the chart
units: d['GriEgyTot'].unit, // the units of the chart dimensions
family: 'total', // the family of the chart
- context: 'sma_webbox.grid.power.total', // the context of the chart
+ context: 'smawebbox.grid_power_total', // the context of the chart
type: netdata.chartTypes.area, // the type of the chart
priority: webbox.base_priority + 3, // the priority relative to others in the same family
update_every: service.update_every, // the expected update frequency of the chart
echo >&2 "I cannot send emails - there is no sendmail command available."
fi
+default_recipient_for_all_roles="root"
+declare -A recipients=()
+if [ -f "${NETDATA_CONFIG_DIR}/health_email_recipients.conf" ]
+ then
+ source "${NETDATA_CONFIG_DIR}/health_email_recipients.conf"
+fi
+
sendmail_from_pipe() {
"${sendmail}" -t
fi
}
-name="${1}" # the name of the alarm, as given in netdata health.d entries
-chart="${2}" # the name of the chart (type.id)
-family="${3}" # the family of the chart
-status="${4}" # the current status : UNITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL
-old_status="${5}" # the previous status: UNITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL
-value="${6}" # the current value
-old_value="${7}" # the previous value
-src="${8}" # the line number and file the alarm has been configured
-duration="${9}" # the duration in seconds the previous state took
-non_clear_duration="${10}" # the total duration in seconds this is non-clear
-units="${11}" # the units of the value
-info="${12}" # a short description of the alarm
+recipient="${1}" # the recepient of the email
+hostname="${2}" # the hostname this event refers to
+unique_id="${3}" # the unique id of this event
+alarm_id="${4}" # the unique id of the alarm that generated this event
+event_id="${5}" # the incremental id of the event, for this alarm
+when="${6}" # the timestamp this event occured
+name="${7}" # the name of the alarm, as given in netdata health.d entries
+chart="${8}" # the name of the chart (type.id)
+family="${9}" # the family of the chart
+status="${10}" # the current status : UNITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL
+old_status="${11}" # the previous status: UNITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL
+value="${12}" # the current value
+old_value="${13}" # the previous value
+src="${14}" # the line number and file the alarm has been configured
+duration="${15}" # the duration in seconds the previous state took
+non_clear_duration="${16}" # the total duration in seconds this is non-clear
+units="${17}" # the units of the value
+info="${18}" # a short description of the alarm
+
+to="${recipients[${recipient}]}"
+[ -z "${to}" ] && to="${default_recipient_for_all_roles}"
+[ -z "${to}" ] && to="root"
[ ! -z "${info}" ] && info=" <small><br/>${info}</small>"
# get the system hostname
-hostname="${NETDATA_HOSTNAME}"
+[ -z "${hostname}" ] && hostname="${NETDATA_HOSTNAME}"
[ -z "${hostname}" ] && hostname="${NETDATA_REGISTRY_HOSTNAME}"
-[ -z "${hostname}" ] && hostname="$(hostname)"
+[ -z "${hostname}" ] && hostname="$(hostname 2>/dev/null)"
goto_url="${NETDATA_REGISTRY_URL}/goto-host-from-alarm.html?machine_guid=${NETDATA_REGISTRY_UNIQUE_ID}&chart=${chart}&family=${family}"
-# get the current date
-date="$(date)"
+date="$(date --date=@${when} 2>/dev/null)"
+[ -z "${date}" ] && date="$(date 2>/dev/null)"
+# convert a duration in seconds, to a human readable duration
+# using DAYS, MINUTES, SECONDS
duration4human() {
local s="${1}" d=0 h=0 m=0 ds="day" hs="hour" ms="minute" ss="second"
d=$(( s / 86400 ))
}
severity="${status}"
-raised_for="<br/>(was ${old_status,,} for $(duration4human ${duration}))"
+raised_for="<br/><small>(was ${old_status,,} for $(duration4human ${duration}))</small>"
status_message="status unknown"
color="grey"
alarm="${name} = ${value} ${units}"
severity="Recovered from ${old_status}"
if [ $non_clear_duration -gt $duration ]
then
- raised_for="<br/>(had issues for $(duration4human ${non_clear_duration}))"
+ raised_for="<br/><small>(had issues for $(duration4human ${non_clear_duration}))</small>"
fi
elif [ "${old_status}" = "WARNING" -a "${status}" = "CRITICAL" ]
severity="Escalated to ${status}"
if [ $non_clear_duration -gt $duration ]
then
- raised_for="<br/>(has issues for $(duration4human ${non_clear_duration}))"
+ raised_for="<br/><small>(has issues for $(duration4human ${non_clear_duration}))</small>"
fi
elif [ "${old_status}" = "CRITICAL" -a "${status}" = "WARNING" ]
severity="Demoted to ${status}"
if [ $non_clear_duration -gt $duration ]
then
- raised_for="<br/>(has issues for $(duration4human ${non_clear_duration}))"
+ raised_for="<br/><small>(has issues for $(duration4human ${non_clear_duration}))</small>"
fi
else
# send the email
cat <<EOF | sendmail_from_pipe
-To: root
+To: ${to}
Subject: ${hostname} ${status_message} - ${chart}.${name}
Content-Type: text/html
#define RRDVAR_MAX_LENGTH 1024
static const char *health_default_exec = PLUGINS_DIR "/alarm-email.sh";
+static const char *health_default_recipient = "root";
int health_enabled = 1;
// ----------------------------------------------------------------------------
const char *units, const char *info,
int group_method, int after, int before, int update_every, uint32_t options,
calculated_number green, calculated_number red,
- const char *exec, const char *source,
+ const char *exec, const char *recipient, const char *source,
const char *calc, const char *warn, const char *crit) {
char fullname[RRDVAR_MAX_LENGTH + 1];
return NULL;
RRDCALC *rc = callocz(1, sizeof(RRDCALC));
-
+ rc->id = host->health_log.next_alarm_id++;
+ rc->next_event_id = 1;
rc->name = strdupz(name);
rc->hash = simple_hash(rc->name);
-
rc->chart = strdupz(chart);
rc->hash_chart = simple_hash(rc->chart);
rc->options = options;
if(exec) rc->exec = strdupz(exec);
+ if(recipient) rc->recipient = strdupz(recipient);
if(source) rc->source = strdupz(source);
if(units) rc->units = strdupz(units);
if(info) rc->info = strdupz(info);
error("Health alarm '%s.%s': failed to re-parse critical expression '%s'", chart, name, crit);
}
- debug(D_HEALTH, "Health runtime added alarm '%s.%s': exec '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s",
+ debug(D_HEALTH, "Health runtime added alarm '%s.%s': exec '%s', recipient '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s",
(rc->chart)?rc->chart:"NOCHART",
rc->name,
(rc->exec)?rc->exec:"DEFAULT",
+ (rc->recipient)?rc->recipient:"DEFAULT",
rc->green,
rc->red,
rc->group,
freez(rc->family);
freez(rc->dimensions);
freez(rc->exec);
+ freez(rc->recipient);
freez(rc->source);
freez(rc->units);
freez(rc->info);
if(rt->hash_context == st->hash_context && !strcmp(rt->context, st->context)) {
RRDCALC *rc = rrdcalc_create(st->rrdhost, rt->name, st->id,
rt->dimensions, rt->units, rt->info, rt->group, rt->after, rt->before, rt->update_every, rt->options,
- rt->green, rt->red, rt->exec, rt->source,
+ rt->green, rt->red,
+ rt->exec, rt->recipient, rt->source,
(rt->calculation)?rt->calculation->source:NULL,
(rt->warning)?rt->warning->source:NULL,
(rt->critical)?rt->critical->source:NULL);
freez(rt->name);
freez(rt->exec);
+ freez(rt->recipient);
freez(rt->context);
freez(rt->source);
freez(rt->units);
#define HEALTH_WARN_KEY "warn"
#define HEALTH_CRIT_KEY "crit"
#define HEALTH_EXEC_KEY "exec"
+#define HEALTH_RECIPIENT_KEY "to"
#define HEALTH_UNITS_KEY "units"
#define HEALTH_INFO_KEY "info"
return 0;
}
- debug(D_HEALTH, "Health configuration adding alarm '%s.%s': exec '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s",
+ debug(D_HEALTH, "Health configuration adding alarm '%s.%s': exec '%s', recipient '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s",
rc->chart?rc->chart:"NOCHART",
rc->name,
(rc->exec)?rc->exec:"DEFAULT",
+ (rc->recipient)?rc->recipient:"DEFAULT",
rc->green,
rc->red,
rc->group,
}
}
- debug(D_HEALTH, "Health configuration adding template '%s': context '%s', exec '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s'",
+ debug(D_HEALTH, "Health configuration adding template '%s': context '%s', exec '%s', recipient '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s'",
rt->name,
(rt->context)?rt->context:"NONE",
(rt->exec)?rt->exec:"DEFAULT",
+ (rt->recipient)?rt->recipient:"DEFAULT",
rt->green,
rt->red,
rt->group,
int health_readfile(const char *path, const char *filename) {
debug(D_HEALTH, "Health configuration reading file '%s/%s'", path, filename);
- static uint32_t hash_alarm = 0, hash_template = 0, hash_on = 0, hash_calc = 0, hash_green = 0, hash_red = 0, hash_warn = 0, hash_crit = 0, hash_exec = 0, hash_every = 0, hash_lookup = 0, hash_units = 0, hash_info = 0;
+ static uint32_t hash_alarm = 0, hash_template = 0, hash_on = 0, hash_calc = 0, hash_green = 0, hash_red = 0, hash_warn = 0, hash_crit = 0, hash_exec = 0, hash_every = 0, hash_lookup = 0, hash_units = 0, hash_info = 0, hash_recipient = 0;
char buffer[HEALTH_CONF_MAX_LINE + 1];
if(unlikely(!hash_alarm)) {
hash_every = simple_uhash(HEALTH_EVERY_KEY);
hash_units = simple_hash(HEALTH_UNITS_KEY);
hash_info = simple_hash(HEALTH_INFO_KEY);
+ hash_recipient = simple_hash(HEALTH_RECIPIENT_KEY);
}
snprintfz(buffer, HEALTH_CONF_MAX_LINE, "%s/%s", path, filename);
}
rc = callocz(1, sizeof(RRDCALC));
+ rc->id = localhost.health_log.next_alarm_id++;
+ rc->next_event_id = 1;
rc->name = strdupz(value);
rc->hash = simple_hash(rc->name);
rc->source = health_source_file(line, path, filename);
}
rc->exec = strdupz(value);
}
+ else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
+ if(rc->recipient) {
+ if(strcmp(rc->recipient, value))
+ info("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
+ line, path, filename, rc->name, key, rc->recipient, value, value);
+
+ freez(rc->recipient);
+ }
+ rc->recipient = strdupz(value);
+ }
else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
if(rc->units) {
if(strcmp(rc->units, value))
}
rt->exec = strdupz(value);
}
+ else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
+ if(rt->recipient) {
+ if(strcmp(rt->recipient, value))
+ info("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
+ line, path, filename, rt->name, key, rt->recipient, value, value);
+
+ freez(rt->recipient);
+ }
+ rt->recipient = strdupz(value);
+ }
else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
if(rt->units) {
if(strcmp(rt->units, value))
static inline void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae) {
buffer_sprintf(wb, "\n\t{\n"
- "\t\t\"id\":%u,\n"
+ "\t\t\"unique_id\":%u,\n"
+ "\t\t\"alarm_id\":%u,\n"
+ "\t\t\"alarm_event_id\":%u,\n"
"\t\t\"name\":\"%s\",\n"
"\t\t\"chart\":\"%s\",\n"
"\t\t\"family\":\"%s\",\n"
"\t\t\"exec_run\":%s,\n"
"\t\t\"exec_failed\":%s,\n"
"\t\t\"exec\":\"%s\",\n"
+ "\t\t\"recipient\":\"%s\",\n"
"\t\t\"exec_code\":%d,\n"
"\t\t\"source\":\"%s\",\n"
"\t\t\"units\":\"%s\",\n"
"\t\t\"non_clear_duration\":%lu,\n"
"\t\t\"status\":\"%s\",\n"
"\t\t\"old_status\":\"%s\",\n",
- ae->id,
+ ae->unique_id,
+ ae->alarm_id,
+ ae->alarm_event_id,
ae->name,
ae->chart,
ae->family,
(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_EXEC_RUN)?"true":"false",
(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_EXEC_FAILED)?"true":"false",
ae->exec?ae->exec:health_default_exec,
+ ae->recipient?ae->recipient:health_default_recipient,
ae->exec_code,
ae->source,
ae->units?ae->units:"",
"\t\t\t\"family\": \"%s\",\n"
"\t\t\t\"active\": %s,\n"
"\t\t\t\"exec\": \"%s\",\n"
+ "\t\t\t\"recipient\": \"%s\",\n"
"\t\t\t\"source\": \"%s\",\n"
"\t\t\t\"units\": \"%s\",\n"
"\t\t\t\"info\": \"%s\",\n"
, (rc->rrdset && rc->rrdset->family)?rc->rrdset->family:""
, (rc->rrdset)?"true":"false"
, rc->exec?rc->exec:health_default_exec
+ , rc->recipient?rc->recipient:health_default_recipient
, rc->source
, rc->units?rc->units:""
, rc->info?rc->info:""
return RRDCALC_STATUS_CLEAR;
}
-static inline void health_alarm_execute(ALARM_ENTRY *ae) {
+static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
if(ae->old_status == RRDCALC_STATUS_UNINITIALIZED && ae->new_status == RRDCALC_STATUS_CLEAR)
return;
const char *exec = ae->exec;
if(!exec) exec = health_default_exec;
- snprintfz(buffer, FILENAME_MAX, "exec %s '%s' '%s' '%s' '%s' '%s' '%0.0Lf' '%0.0Lf' '%s' '%u' '%u' '%s' '%s'",
+ const char *recipient = ae->recipient;
+ if(!recipient) recipient = health_default_recipient;
+
+ snprintfz(buffer, FILENAME_MAX, "exec %s '%s' '%s' '%u' '%u' '%u' '%zu' '%s' '%s' '%s' '%s' '%s' '%0.0Lf' '%0.0Lf' '%s' '%u' '%u' '%s' '%s'",
exec,
+ recipient,
+ host->hostname,
+ ae->unique_id,
+ ae->alarm_id,
+ ae->alarm_event_id,
+ ae->when,
ae->name,
ae->chart?ae->chart:"NOCAHRT",
ae->family?ae->family:"NOFAMILY",
ae->notifications |= HEALTH_ENTRY_NOTIFICATIONS_EXEC_FAILED;
}
-static inline void health_process_notifications(ALARM_ENTRY *ae) {
+static inline void health_process_notifications(RRDHOST *host, ALARM_ENTRY *ae) {
info("Health alarm '%s.%s' = %0.2Lf - changed status from %s to %s",
ae->chart?ae->chart:"NOCHART", ae->name,
ae->new_value,
rrdcalc_status2string(ae->new_status)
);
- health_alarm_execute(ae);
+ health_alarm_execute(host, ae);
}
-static inline void health_alarm_log(RRDHOST *host, time_t when,
+static inline void health_alarm_log(RRDHOST *host,
+ uint32_t alarm_id, uint32_t alarm_event_id,
+ time_t when,
const char *name, const char *chart, const char *family,
- const char *exec, time_t duration,
+ const char *exec, const char *recipient, time_t duration,
calculated_number old_value, calculated_number new_value,
int old_status, int new_status,
const char *source,
ae->family = strdupz(family);
if(exec) ae->exec = strdupz(exec);
+ if(recipient) ae->recipient = strdupz(recipient);
if(source) ae->source = strdupz(source);
if(units) ae->units = strdupz(units);
if(info) ae->info = strdupz(info);
- ae->id = host->health_log.nextid++;
+ ae->unique_id = host->health_log.next_log_id++;
+ ae->alarm_id = alarm_id;
+ ae->alarm_event_id = alarm_event_id;
ae->when = when;
ae->old_value = old_value;
ae->new_value = new_value;
pthread_rwlock_rdlock(&host->health_log.alarm_log_rwlock);
for(ae = host->health_log.alarms; ae ;ae = ae->next) {
- if(last_processed >= ae->id) break;
+ if(last_processed >= ae->unique_id) break;
if(!(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_PROCESSED) &&
!(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_UPDATED)) {
ae->notifications |= HEALTH_ENTRY_NOTIFICATIONS_PROCESSED;
- health_process_notifications(ae);
+ health_process_notifications(host, ae);
}
}
if(host->health_log.alarms)
- last_processed = host->health_log.alarms->id;
+ last_processed = host->health_log.alarms->unique_id;
pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock);
freez(ae->chart);
freez(ae->family);
freez(ae->exec);
+ freez(ae->recipient);
freez(ae->source);
freez(ae->units);
freez(ae->info);
}
if(status != rc->status) {
- health_alarm_log(&localhost, time(NULL), rc->name, rc->rrdset->id, rc->rrdset->family, rc->exec, now - rc->last_status_change, rc->old_value, rc->value, rc->status, status, rc->source, rc->units, rc->info);
+ health_alarm_log(&localhost, rc->id, rc->next_event_id++, time(NULL), rc->name, rc->rrdset->id, rc->rrdset->family, rc->exec, rc->recipient, now - rc->last_status_change, rc->old_value, rc->value, rc->status, status, rc->source, rc->units, rc->info);
rc->last_status_change = now;
rc->status = status;
}
#define RRDCALC_FLAG_CRIT_ERROR 0x00000020
typedef struct rrdcalc {
+ uint32_t id;
+ uint32_t next_event_id;
+
char *name;
uint32_t hash;
char *exec;
+ char *recipient;
char *chart; // the chart id this should be linked to
uint32_t hash_chart;
uint32_t hash_name;
char *exec;
+ char *recipient;
char *context;
uint32_t hash_context;
#define HEALTH_ENTRY_NOTIFICATIONS_EXEC_FAILED 0x00000008
typedef struct alarm_entry {
- uint32_t id;
+ uint32_t unique_id;
+ uint32_t alarm_id;
+ uint32_t alarm_event_id;
time_t when;
time_t duration;
char *family;
char *exec;
+ char *recipient;
int exec_code;
char *source;
} ALARM_ENTRY;
typedef struct alarm_log {
- uint32_t nextid;
+ uint32_t next_log_id;
+ uint32_t next_alarm_id;
unsigned int count;
unsigned int max;
ALARM_ENTRY *alarms;
int main(int argc, char **argv)
{
+ char *hostname = "localhost";
int i, check_config = 0;
int config_loaded = 0;
int dont_fork = 0;
info("Successfully set pthread stacksize to %zu bytes", wanted_stacksize);
}
+ // ------------------------------------------------------------------------
+ // initialize rrd host
+
+ rrdhost_init(hostname);
+
// ------------------------------------------------------------------------
// initialize the registry
registry.persons_expiration = config_get_number("registry", "registry expire idle persons days", 365) * 86400;
registry.registry_domain = config_get("registry", "registry domain", "");
registry.registry_to_announce = config_get("registry", "registry to announce", "https://registry.my-netdata.io");
- registry.hostname = config_get("registry", "registry hostname", config_get("global", "hostname", hostname));
+ registry.hostname = config_get("registry", "registry hostname", config_get("global", "hostname", localhost.hostname));
registry.verify_cookies_redirects = config_get_boolean("registry", "verify browser cookies support", 1);
setenv("NETDATA_REGISTRY_HOSTNAME", registry.hostname, 1);
AVL_LOCK_INITIALIZER
},
.health_log = {
- .nextid = 1,
+ .next_log_id = 1,
+ .next_alarm_id = 1,
.count = 0,
.max = 1000,
.alarms = NULL,
}
};
+void rrdhost_init(char *hostname) {
+ localhost.hostname = hostname;
+ localhost.health_log.next_log_id =
+ localhost.health_log.next_alarm_id = time(NULL);
+}
+
void rrdhost_rwlock(RRDHOST *host) {
pthread_rwlock_wrlock(&host->rrdset_root_rwlock);
}
};
typedef struct rrdhost RRDHOST;
extern RRDHOST localhost;
+extern void rrdhost_init(char *hostname);
#ifdef NETDATA_INTERNAL_CHECKS
#define rrdhost_check_wrlock(host) rrdhost_check_wrlock_int(host, __FILE__, __FUNCTION__, __LINE__)
#include "common.h"
-#define HOSTNAME_MAX 1024
-char *hostname = "unknown";
-
void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb)
{
pthread_rwlock_rdlock(&st->rwlock);
",\n\t\"update_every\": %d"
",\n\t\"history\": %d"
",\n\t\"charts\": {"
- , hostname
+ , localhost.hostname
, rrd_update_every
, rrd_default_history_entries
);
"\t\"history\": %d,\n"
"\t\"memory\": %lu\n"
"}\n"
- , hostname
+ , localhost.hostname
, rrd_update_every
, rrd_default_history_entries
, memory
a1 += '<li role="separator" class="divider"></li>';
el += '<li><a href="https://github.com/firehol/netdata/wiki/mynetdata-menu-item" style="color: #999;" target="_blank">What is this?</a></li>';
- a1 += '<li><a href="#" style="color: #999;" onclick="switchRegistryModalHandler(); return false;"><i class="fa fa-cog" aria-hidden="true" style="color: #999;"></i></a></li>'
+ a1 += '<li><a href="#" style="color: #999;" onclick="switchRegistryModalHandler(); return false;"><i class="fa fa-sliders" aria-hidden="true" style="color: #999;"></i></a></li>'
document.getElementById('mynetdata_servers').innerHTML = el;
document.getElementById('mynetdata_servers2').innerHTML = el;
<nav class="collapse navbar-collapse navbar-right" role="navigation">
<ul class="nav navbar-nav">
<li class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#alarmsModal" title="alarms"><i class="fa fa-bell"></i><span id="alarms_count_badge" class="badge"></span></a></li>
- <li class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal" title="dashboard settings"><i class="fa fa-cog"></i></a></li>
+ <li class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal" title="dashboard settings"><i class="fa fa-sliders"></i></a></li>
<li class="hidden-sm"><a href="https://github.com/firehol/netdata/wiki" class="btn" target="_blank" title="netdata community"><i class="fa fa-github"></i></a></li>
<li class="hidden-sm" id="updateButton"><a href="#" class="btn" data-toggle="modal" data-target="#updateModal" title="check for update"><i class="fa fa-cloud-download"></i><span id="update_badge" class="badge"></span></a></li>
<li class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#helpModal" title="dashboard help"><i class="fa fa-question-circle"></i></a></li>
<li class="dropdown hidden-md hidden-lg hidden-xs">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" id="current_view">Menu <strong class="caret"></strong></a>
<ul class="dropdown-menu scrollable-menu inpagemenu" role="menu">
- <li><a href="#" class="btn" data-toggle="modal" data-target="#alarmsModal"><i class="fa fa-cog"></i> alarms</a></li>
- <li><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal"><i class="fa fa-cog"></i> settings</a></li>
+ <li><a href="#" class="btn" data-toggle="modal" data-target="#alarmsModal"><i class="fa fa-bell"></i> alarms</a></li>
+ <li><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal"><i class="fa fa-sliders"></i> settings</a></li>
<li><a href="https://github.com/firehol/netdata/wiki" class="btn" target="_blank"><i class="fa fa-github"></i> community</a></li>
<li><a href="#" class="btn" data-toggle="modal" data-target="#helpModal"><i class="fa fa-question-circle"></i> help</a></li>
</ul>
var menuData = {
'system': {
title: 'System Overview',
+ icon: '<i class="fa fa-bookmark" aria-hidden="true"></i>',
info: 'Overview of the key system metrics.'
},
'ap': {
title: 'Access Points',
+ icon: '<i class="fa fa-wifi" aria-hidden="true"></i>',
info: undefined
},
'tc': {
title: 'Quality of Service',
+ icon: '<i class="fa fa-globe" aria-hidden="true"></i>',
info: 'Netdata collects and visualizes tc class utilization using its <a href="https://github.com/firehol/netdata/blob/master/plugins.d/tc-qos-helper.sh" target="_blank">tc-helper plugin</a>. If you also use <a href="http://firehol.org/#fireqos" target="_blank">FireQOS</a> for setting up QoS, netdata automatically collects interface and class names. If your QoS configuration includes overheads calculation, the values shown here will include these overheads (the total bandwidth for the same interface as reported in the Network Interfaces section, will be lower than the total bandwidth reported here). Also, data collection may have a slight time difference compared to the interface (QoS data collection is implemented with a BASH script, so a shift in data collection of a few milliseconds should be justified).'
},
'net': {
title: 'Network Interfaces',
+ icon: '<i class="fa fa-share-alt" aria-hidden="true"></i>',
info: 'Per network interface statistics collected from <code>/proc/net/dev</code>.'
},
'ipv4': {
title: 'IPv4 Networking',
+ icon: '<i class="fa fa-cloud" aria-hidden="true"></i>',
info: undefined
},
'ipv6': {
title: 'IPv6 Networking',
+ icon: '<i class="fa fa-cloud" aria-hidden="true"></i>',
info: undefined
},
'ipvs': {
title: 'IP Virtual Server',
+ icon: '<i class="fa fa-eye" aria-hidden="true"></i>',
info: undefined
},
'netfilter': {
title: 'Firewall (netfilter)',
+ icon: '<i class="fa fa-shield" aria-hidden="true"></i>',
info: undefined
},
'cpu': {
title: 'CPUs',
+ icon: '<i class="fa fa-bolt" aria-hidden="true"></i>',
info: undefined
},
'mem': {
title: 'Memory',
+ icon: '<i class="fa fa-bolt" aria-hidden="true"></i>',
info: undefined
},
'disk': {
title: 'Disks',
+ icon: '<i class="fa fa-folder" aria-hidden="true"></i>',
info: 'Charts with performance information for all the system disks. Special care has been given to present disk performance metrics in a way compatible with <code>iostat -x</code>. netdata by default prevents rendering performance charts for individual partitions and unmounted virtual disks. Disabled charts can still be enabled by altering the relative settings in the netdata configuration file.'
},
'sensors': {
title: 'Sensors',
+ icon: '<i class="fa fa-leaf" aria-hidden="true"></i>',
info: undefined
},
'nfsd': {
title: 'File Server (nfsd)',
+ icon: '<i class="fa fa-folder-open" aria-hidden="true"></i>',
info: undefined
},
'apps': {
title: 'Applications',
+ icon: '<i class="fa fa-heartbeat" aria-hidden="true"></i>',
info: 'Per application statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics for applications of interest, defined in <code>/etc/netdata/apps_groups.conf</code> (the default is <a href="https://github.com/firehol/netdata/blob/master/conf.d/apps_groups.conf" target="_blank">here</a>). The plugin internally builds a process tree (much like <code>ps fax</code> does), and groups processes together (evaluating both child and parent processes) so that the result is always a chart with a predefined set of dimensions (of course, only application groups found running are reported). The reported values are compatible with <code>top</code>, although the netdata plugin counts also the resources of exited children (unlike <code>top</code> which shows only the resources of the currently running processes). So for processes like shell scripts, the reported values include the resources used by the commands these scripts run within each timeframe.',
height: 1.5
},
'users': {
title: 'Users',
+ icon: '<i class="fa fa-user" aria-hidden="true"></i>',
info: 'Per user statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics per user. The reported values are compatible with <code>top</code>, although the netdata plugin counts also the resources of exited children (unlike <code>top</code> which shows only the resources of the currently running processes). So for processes like shell scripts, the reported values include the resources used by the commands these scripts run within each timeframe.',
height: 1.5
},
'groups': {
title: 'User Groups',
+ icon: '<i class="fa fa-users" aria-hidden="true"></i>',
info: 'Per user group statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics per user group. The reported values are compatible with <code>top</code>, although the netdata plugin counts also the resources of exited children (unlike <code>top</code> which shows only the resources of the currently running processes). So for processes like shell scripts, the reported values include the resources used by the commands these scripts run within each timeframe.',
height: 1.5
},
'netdata': {
title: 'Netdata Monitoring',
+ icon: '<i class="fa fa-bar-chart" aria-hidden="true"></i>',
info: undefined
},
},
'cgroup': {
- title: 'Container',
+ title: '',
+ icon: '<i class="fa fa-th" aria-hidden="true"></i>',
info: undefined
},
'cgqemu': {
- title: 'VM',
+ title: '',
+ icon: '<i class="fa fa-th-large" aria-hidden="true"></i>',
info: undefined
},
'memcached': {
title: 'memcached',
+ icon: '<i class="fa fa-database" aria-hidden="true"></i>',
info: undefined
},
'mysql': {
title: 'MySQL',
+ icon: '<i class="fa fa-database" aria-hidden="true"></i>',
info: undefined
},
'redis': {
title: 'Redis',
+ icon: '<i class="fa fa-database" aria-hidden="true"></i>',
info: undefined
},
'ipfs': {
title: 'IPFS',
+ icon: '<i class="fa fa-folder-open" aria-hidden="true"></i>',
info: undefined
},
'phpfpm': {
title: 'PHP-FPM',
+ icon: '<i class="fa fa-eye" aria-hidden="true"></i>',
+ info: undefined,
+ },
+
+ 'postfix': {
+ title: 'postfix',
+ icon: '<i class="fa fa-envelope" aria-hidden="true"></i>',
info: undefined,
},
'nginx': {
title: 'nginx',
+ icon: '<i class="fa fa-eye" aria-hidden="true"></i>',
info: undefined,
},
'apache': {
title: 'Apache',
+ icon: '<i class="fa fa-eye" aria-hidden="true"></i>',
info: undefined,
},
'named': {
title: 'named',
+ icon: '<i class="fa fa-tag" aria-hidden="true"></i>',
+ info: undefined
+ },
+
+ 'squid': {
+ title: 'squid',
+ icon: '<i class="fa fa-exchange" aria-hidden="true"></i>',
+ info: undefined
+ },
+
+ 'nut': {
+ title: 'UPS',
+ icon: '<i class="fa fa-battery-half" aria-hidden="true"></i>',
+ info: undefined
+ },
+
+ 'smawebbox': {
+ title: 'Solar Power',
+ icon: '<i class="fa fa-sun-o" aria-hidden="true"></i>',
+ info: undefined
+ },
+
+ 'snmp': {
+ title: 'SNMP',
+ icon: '<i class="fa fa-random" aria-hidden="true"></i>',
info: undefined
}
};
return def;
}
+function anyAttributeTitle(obj, attr, key, def) {
+ if(typeof obj[key] !== 'undefined') {
+ if(typeof obj[key][attr] !== 'undefined')
+ return obj[key][attr];
+ }
+
+ var r = def.replace(/_/g, ' ');
+
+ return r;
+}
+
function menuTitle(chart) {
if(typeof chart.menu_pattern !== 'undefined') {
- return anyAttribute(menuData, 'title', chart.menu_pattern, chart.menu_pattern).toString()
- + ': ' + chart.type.slice(-(chart.type.length - chart.menu_pattern.length - 1)).toString();
+ return anyAttributeTitle(menuData, 'title', chart.menu_pattern, chart.menu_pattern).toString()
+ + ' ' + chart.type.slice(-(chart.type.length - chart.menu_pattern.length - 1)).toString();
}
- return anyAttribute(menuData, 'title', chart.menu, chart.menu);
+ return anyAttributeTitle(menuData, 'title', chart.menu, chart.menu);
+}
+
+function menuIcon(chart) {
+ if(typeof chart.menu_pattern !== 'undefined')
+ return anyAttribute(menuData, 'icon', chart.menu_pattern, '<i class="fa fa-puzzle-piece" aria-hidden="true"></i>').toString();
+
+ return anyAttribute(menuData, 'icon', chart.menu, '<i class="fa fa-puzzle-piece" aria-hidden="true"></i>');
}
function menuInfo(menu) {
function submenuTitle(menu, submenu) {
var key = menu + '.' + submenu;
- return anyAttribute(submenuData, 'title', key, submenu);
+ var title = anyAttributeTitle(submenuData, 'title', key, submenu);
+ if(title.length > 30) {
+ var a = title.substring(0, 13);
+ var b = title.substring(title.length - 13, title.length);
+ return a + '...' + b;
+ }
+ return title;
}
function submenuInfo(menu, submenu) {
case 'mysql':
case 'named':
case 'nginx':
+ case 'nut':
case 'phpfpm':
case 'postfix':
case 'redis':
case 'ipfs':
+ case 'smawebbox':
case 'squid':
case 'snmp':
case 'tomcat':
// generate an entry at the main menu
var menuid = name2id(menu);
- sidebar += '<li class=""><a href="#' + menuid + '" onClick="return scrollToId(\'' + menuid + '\');">' + menus[menu].title + '</a><ul class="nav">';
+ sidebar += '<li class=""><a href="#' + menuid + '" onClick="return scrollToId(\'' + menuid + '\');">' + menus[menu].icon + ' ' + menus[menu].title + '</a><ul class="nav">';
html += '<div role="section"><div role="sectionhead"><h1 id="' + menuid + '" role="heading">' + menus[menu].title + '</h1></div><div id="menu_' + menuid + '" role="document">';
if(menus[menu].info !== null)
priority: charts[c].priority,
submenus: {},
title: menuTitle(charts[c]),
+ icon: menuIcon(charts[c]),
info: menuInfo(charts[c].menu),
height: menuHeight(charts[c].menu, options.chartsHeight)
};
renderPage(menus, data);
}
+// ----------------------------------------------------------------------------
+
function alarmsUpdateModal() {
var active = '<h3>Raised Alarms</h3><table class="table">';
var all = '<h3>All Running Alarms</h3><div class="panel-group" id="alarms_all_accordion" role="tablist" aria-multiselectable="true">';
function alarm_to_html(alarm, full) {
var chart = options.data.charts[alarm.chart];
- var html = '<tr><td class="text-center" style="vertical-align:middle" width="40%"><b>' + alarm.chart + '</b><br/> <br/><embed src="' + NETDATA.alarms.server + '/api/v1/badge.svg?chart=' + alarm.chart + '&alarm=' + alarm.name + '&refresh=auto" type="image/svg+xml" height="20" /><br/> <br/><span style="font-size: 18px">' + alarm.info + '</span></td>'
+ var html = '<tr><td class="text-center" style="vertical-align:middle" width="40%"><b>' + alarm.chart + '</b><br/> <br/><embed src="' + NETDATA.alarms.server + '/api/v1/badge.svg?chart=' + alarm.chart + '&alarm=' + alarm.name + '&refresh=auto" type="image/svg+xml" height="20" /><br/> <br/><span style="font-size: 18px">' + alarm.info + '</span><br/> <br/>role: <b>' + alarm.recipient + '</b></td>'
+ '<td><table class="table">'
+ ((typeof alarm.warn !== 'undefined')?('<tr><td width="10%" style="text-align:right">warning when</td><td><span style="font-family: monospace; color:#fe7d37; font-weight: bold;">' + alarm.warn + '</span></td></tr>'):'')
+ ((typeof alarm.crit !== 'undefined')?('<tr><td width="10%" style="text-align:right">critical when</td><td><span style="font-family: monospace; color: #e05d44; font-weight: bold;">' + alarm.crit + '</span></td></tr>'):'');
if(alarm.status === 'WARNING' || alarm.status === 'CRITICAL') {
if(!active_family_added) {
active_family_added = true;
- active += '<tr><th class="text-center"><h4>' + family + '</h4></th><th></th></tr>';
+ active += '<tr><th class="text-center" colspan="2"><h4>' + family + '</h4></th></tr>';
}
count_active++;
active += alarm_to_html(alarm, false);
/* activate bootstrap scrollspy (needed for sidebar) */
$(document.body).scrollspy({
target: '#sidebar',
- offset: $(window).height() / 3 // controls the diff of the <hX> element to the top, to select it
+ offset: $(window).height() / 5 // controls the diff of the <hX> element to the top, to select it
});
// change the URL based on the current position of the screen