• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-server / 10423

11 Nov 2025 04:53PM UTC coverage: 74.435% (-0.1%) from 74.562%
10423

push

gitlab-ci

web-flow
Merge pull request #1071 from mendersoftware/dependabot/npm_and_yarn/frontend/main/development-dependencies-92732187be

3868 of 5393 branches covered (71.72%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

176 existing lines in 95 files now uncovered.

64605 of 86597 relevant lines covered (74.6%)

7.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.75
/frontend/src/js/components/devices/device-details/Monitoring.tsx
1
// Copyright 2021 Northern.tech AS
2✔
2
//
2✔
3
//    Licensed under the Apache License, Version 2.0 (the "License");
2✔
4
//    you may not use this file except in compliance with the License.
2✔
5
//    You may obtain a copy of the License at
2✔
6
//
2✔
7
//        http://www.apache.org/licenses/LICENSE-2.0
2✔
8
//
2✔
9
//    Unless required by applicable law or agreed to in writing, software
2✔
10
//    distributed under the License is distributed on an "AS IS" BASIS,
2✔
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2✔
12
//    See the License for the specific language governing permissions and
2✔
13
//    limitations under the License.
2✔
14
import { useEffect, useState } from 'react';
2✔
15
import { useDispatch, useSelector } from 'react-redux';
2✔
16

2✔
17
import DocsLink from '@northern.tech/common-ui/DocsLink';
2✔
18
import EnterpriseNotification from '@northern.tech/common-ui/EnterpriseNotification';
2✔
19
import Pagination from '@northern.tech/common-ui/Pagination';
2✔
20
import Time from '@northern.tech/common-ui/Time';
2✔
21
import storeActions from '@northern.tech/store/actions';
2✔
22
import { BENEFITS, DEVICE_LIST_DEFAULTS } from '@northern.tech/store/constants';
2✔
23
import { getOfflineThresholdSettings, getTenantCapabilities } from '@northern.tech/store/selectors';
2✔
24
import { getDeviceAlerts } from '@northern.tech/store/thunks';
2✔
25

2✔
26
import MonitorDetailsDialog from '../dialogs/MonitorDetailsDialog';
2✔
27
import { DeviceConnectionNote } from './Connection';
2✔
28
import DeviceDataCollapse from './DeviceDataCollapse';
2✔
29
import { DeviceOfflineHeaderNotification, NoAlertsHeaderNotification, monitoringSeverities, severityMap } from './Notifications';
2✔
30

2✔
31
const { setAlertListState } = storeActions;
11✔
32

2✔
33
const { page: defaultPage, perPage: defaultPerPage } = DEVICE_LIST_DEFAULTS;
11✔
34

2✔
35
export const DeviceMonitorsMissingNote = () => (
11✔
36
  <DeviceConnectionNote>
3✔
37
    No alert monitor is currently configured for this device.
2✔
38
    <br />
2✔
39
    Please <DocsLink path="add-ons/monitor" title="see the documentation" /> for a description on how to configure different kinds of monitors.
2✔
40
  </DeviceConnectionNote>
2✔
41
);
2✔
42

2✔
43
const MonitoringAlert = ({ alert, className = '', onDetailsClick, style }) => {
11✔
44
  const { description, lines_before = [], lines_after = [], line_matching = '' } = alert.subject.details;
4✔
45
  const lines = [...lines_before, line_matching, ...lines_after].filter(i => i);
4✔
46
  return (
4✔
47
    <div className={`monitoring-alert column-data ${className}`} style={style}>
2✔
48
      {(severityMap[alert.level] ?? severityMap[monitoringSeverities.UNKNOWN]).icon}
2!
49
      <div className="key muted">
2✔
50
        <b>{alert.name}</b>
2✔
51
      </div>
2✔
52
      <div>{alert.level}</div>
2✔
53
      <Time value={alert.timestamp} />
2✔
UNCOV
54
      {(lines.length || description) && <a onClick={() => onDetailsClick(alert)}>view {lines.length ? 'log' : 'details'}</a>}
2!
55
    </div>
2✔
56
  );
2✔
57
};
2✔
58

2✔
59
const paginationCutoff = defaultPerPage;
11✔
60
export const DeviceMonitoring = ({ device, onDetailsClick }) => {
11✔
61
  const { hasMonitor } = useSelector(state => getTenantCapabilities(state));
4✔
62
  const { alerts = [], latest: latestAlerts = [] } = useSelector(state => state.monitor.alerts.byDeviceId[device.id]) ?? {};
4!
63
  const alertListState = useSelector(state => state.monitor.alerts.alertList) ?? {};
4!
64
  const offlineThresholdSettings = useSelector(getOfflineThresholdSettings);
3✔
65
  const dispatch = useDispatch();
3✔
66
  const { page: pageNo = defaultPage, perPage: pageLength = defaultPerPage, total: alertCount } = alertListState;
3✔
67

2✔
68
  useEffect(() => {
3✔
69
    if (hasMonitor) {
3!
70
      dispatch(getDeviceAlerts({ id: device.id, config: alertListState }));
2✔
71
    }
2✔
72
    // eslint-disable-next-line react-hooks/exhaustive-deps
2✔
73
  }, [device.id, dispatch, hasMonitor, pageNo, pageLength]);
2✔
74

2✔
75
  const onChangePage = page => dispatch(setAlertListState({ page }));
3✔
76

2✔
77
  const onChangeRowsPerPage = perPage => dispatch(setAlertListState({ page: 1, perPage }));
3✔
78

2✔
79
  const { monitors = [], isOffline, updated_ts = '' } = device;
3✔
80
  const hasMonitorsDefined = !!(monitors.length || alerts.length || latestAlerts.length);
3!
81

2✔
82
  return (
3✔
83
    <DeviceDataCollapse
2✔
84
      header={
2✔
85
        hasMonitorsDefined || isOffline ? (
2!
86
          <>
2✔
87
            {hasMonitorsDefined && !latestAlerts.length && <NoAlertsHeaderNotification />}
2!
88
            {latestAlerts.map(alert => (
2✔
89
              <MonitoringAlert className="margin-bottom-x-small" alert={alert} key={alert.id} onDetailsClick={onDetailsClick} />
3✔
90
            ))}
2✔
91
            {isOffline && <DeviceOfflineHeaderNotification offlineThresholdSettings={offlineThresholdSettings} />}
2!
92
          </>
2✔
93
        ) : (
2✔
94
          hasMonitor && <DeviceMonitorsMissingNote />
2!
95
        )
2✔
96
      }
2✔
97
      isAddOn
2✔
98
      title={
2✔
99
        <div className="flexbox center-aligned">
2✔
100
          <h4 className="margin-bottom-small margin-right">Monitoring</h4>
2✔
101
          {!!monitors.length && <Time className="muted" value={updated_ts} />}
2!
102
          <EnterpriseNotification className="margin-left-small" id={BENEFITS.deviceMonitor.id} />
2✔
103
        </div>
2✔
104
      }
2✔
105
    >
2✔
106
      {alerts.length ? (
2!
107
        <>
2✔
108
          <div>
2✔
109
            <h4 className="muted">Alert history</h4>
2✔
110
            {alerts.map(alert => (
2✔
111
              <MonitoringAlert alert={alert} key={alert.id} onDetailsClick={onDetailsClick} />
3✔
112
            ))}
2✔
113
          </div>
2✔
114
          <div className="flexbox margin-top">
2✔
115
            {alertCount > paginationCutoff && (
2✔
116
              <Pagination
2✔
117
                className="margin-top-none"
2✔
118
                count={alertCount}
2✔
119
                rowsPerPage={pageLength}
2✔
120
                onChangeRowsPerPage={onChangeRowsPerPage}
2✔
121
                page={pageNo}
2✔
122
                onChangePage={onChangePage}
2✔
123
              />
2✔
124
            )}
2✔
125
          </div>
2✔
126
        </>
2✔
127
      ) : (
2✔
128
        hasMonitorsDefined && (
2!
129
          <p className="muted margin-left-large" style={{ fontSize: 'larger' }}>
2✔
130
            There are currently no issues reported
2✔
131
          </p>
2✔
132
        )
2✔
133
      )}
2✔
134
    </DeviceDataCollapse>
2✔
135
  );
2✔
136
};
2✔
137

2✔
138
export const MonitoringTab = ({ device }) => {
11✔
139
  const [monitorDetails, setMonitorDetails] = useState();
3✔
140

2✔
141
  return (
3✔
142
    <>
2✔
143
      <DeviceMonitoring device={device} onDetailsClick={setMonitorDetails} />
2✔
UNCOV
144
      <MonitorDetailsDialog alert={monitorDetails} onClose={() => setMonitorDetails()} />
2✔
145
    </>
2✔
146
  );
2✔
147
};
2✔
148

2✔
149
export default MonitoringTab;
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc