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

mendersoftware / mender-server / 10489

17 Nov 2025 09:07AM UTC coverage: 78.221% (+3.8%) from 74.444%
10489

push

gitlab-ci

web-flow
Merge pull request #1084 from joelguittet/jguittet/main/docker-compose-healthy

Introduce docker compose healthcheck

3865 of 5388 branches covered (71.73%)

Branch coverage included in aggregate %.

6845 of 8304 relevant lines covered (82.43%)

68.08 hits per line

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

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

18
import { Button } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import { TwoColumnData } from '@northern.tech/common-ui/ConfigurationObject';
22
import DocsLink from '@northern.tech/common-ui/DocsLink';
23
import EnterpriseNotification from '@northern.tech/common-ui/EnterpriseNotification';
24
import storeActions from '@northern.tech/store/actions';
25
import { BENEFITS, DEVICE_LIST_DEFAULTS, SORTING_OPTIONS } from '@northern.tech/store/constants';
26
import { getCurrentSession, getDevicesById, getIdAttribute, getIsPreview, getOrganization } from '@northern.tech/store/selectors';
27
import { getSystemDevices } from '@northern.tech/store/thunks';
28
import { getDemoDeviceAddress, toggle } from '@northern.tech/utils/helpers';
29

30
import { getHeaders } from '../AuthorizedDevices';
31
import { routes } from '../BaseDevices';
32
import Devicelist from '../DeviceList';
33
import ConnectToGatewayDialog from '../dialogs/ConnectToGatewayDialog';
34
import DeviceDataCollapse from './DeviceDataCollapse';
35

36
const { setSnackbar } = storeActions;
8✔
37

38
const useStyles = makeStyles()(theme => ({ container: { maxWidth: 600, marginTop: theme.spacing(), marginBottom: theme.spacing() } }));
8✔
39

40
export const DeviceSystem = ({ columnSelection, device, onConnectToGatewayClick, openSettingsDialog }) => {
8✔
41
  const [columnHeaders, setColumnHeaders] = useState([]);
×
42
  const [headerKeys, setHeaderKeys] = useState([]);
×
43
  const [page, setPage] = useState(DEVICE_LIST_DEFAULTS.page);
×
44
  const [perPage, setPerPage] = useState(DEVICE_LIST_DEFAULTS.perPage);
×
45
  const { classes } = useStyles();
×
46
  const navigate = useNavigate();
×
47
  const dispatch = useDispatch();
×
48
  const devicesById = useSelector(getDevicesById);
×
49
  const idAttribute = useSelector(getIdAttribute);
×
50

51
  const { systemDeviceIds = [], systemDeviceTotal = 0 } = device;
×
52
  const deviceIp = getDemoDeviceAddress([device]);
×
53

54
  const [sortOptions, setSortOptions] = useState([]);
×
55

56
  const onSortChange = attribute => {
×
57
    let changedOrder = SORTING_OPTIONS.asc;
×
58
    if (sortOptions.length && sortOptions[0].attribute == attribute.name) {
×
59
      changedOrder = sortOptions[0].order === SORTING_OPTIONS.desc ? SORTING_OPTIONS.asc : SORTING_OPTIONS.desc;
×
60
    }
61
    setSortOptions([{ attribute: attribute.name, scope: attribute.scope, order: changedOrder }]);
×
62
  };
63

64
  useEffect(() => {
×
65
    const columnHeaders = getHeaders(columnSelection, routes.allDevices.defaultHeaders, idAttribute, openSettingsDialog);
×
66
    setColumnHeaders(columnHeaders);
×
67
    setHeaderKeys(columnHeaders.map(({ attribute: { name, scope } }) => `${name}-${scope}`).join('-'));
×
68
    // eslint-disable-next-line react-hooks/exhaustive-deps
69
  }, [columnSelection, idAttribute.attribute, openSettingsDialog]);
70

71
  useEffect(() => {
×
72
    if (device.attributes) {
×
73
      dispatch(getSystemDevices({ id: device.id, page, perPage, sortOptions }));
×
74
    }
75
    // eslint-disable-next-line react-hooks/exhaustive-deps
76
  }, [dispatch, device.id, device.attributes?.mender_is_gateway, page, perPage, sortOptions]);
77

78
  const onDeviceClick = (device = {}) => navigate(`/devices/${device.status}?id=${device.id}&open=true&tab=identity`);
×
79

80
  return (
×
81
    <>
82
      <DeviceDataCollapse
83
        title={
84
          <div className="flexbox center-aligned">
85
            <h4>Mender Gateway</h4>
86
            <EnterpriseNotification className="margin-left-small" id={BENEFITS.gateway.id} />
87
          </div>
88
        }
89
      >
90
        <TwoColumnData config={{ 'Server IP': deviceIp }} compact setSnackbar={message => dispatch(setSnackbar(message))} />
×
91
      </DeviceDataCollapse>
92
      <DeviceDataCollapse className={classes.container} title="System for this gateway">
93
        {systemDeviceTotal ? (
×
94
          <Devicelist
95
            customColumnSizes={[]}
96
            columnHeaders={columnHeaders}
97
            devices={systemDeviceIds.map(id => devicesById[id])}
×
98
            deviceListState={{ page, perPage }}
99
            headerKeys={headerKeys}
100
            idAttribute={idAttribute}
101
            onChangeRowsPerPage={setPerPage}
102
            onExpandClick={onDeviceClick}
103
            onResizeColumns={false}
104
            onPageChange={setPage}
105
            onSelect={false}
106
            onSort={onSortChange}
107
            pageLoading={false}
108
            pageTotal={systemDeviceTotal}
109
          />
110
        ) : (
111
          <div className="dashboard-placeholder">
112
            <p>No devices have been connected to this gateway device yet.</p>
113
            <div>
114
              Visit the <DocsLink path="get-started/mender-gateway" title="full Mender Gateway documentation" /> to learn how to make the most of the gateway
115
              functionality.
116
            </div>
117
          </div>
118
        )}
119
      </DeviceDataCollapse>
120
      <div className="flexbox">
121
        <Button color="secondary" component={Link} to={`/deployments?deviceId=${device.id}&open=true`}>
122
          Create deployment for this system
123
        </Button>
124
        <Button onClick={onConnectToGatewayClick}>Connect devices</Button>
125
      </div>
126
    </>
127
  );
128
};
129

130
const DeviceSystemTab = ({ device, ...remainder }) => {
8✔
131
  const [open, setOpen] = useState(false);
×
132
  const isPreRelease = useSelector(getIsPreview);
×
133
  const { tenant_token: tenantToken } = useSelector(getOrganization);
×
134
  const { token } = useSelector(getCurrentSession);
×
135

136
  const gatewayIp = getDemoDeviceAddress([device]);
×
137
  const toggleDialog = () => setOpen(toggle);
×
138
  return (
×
139
    <>
140
      <DeviceSystem onConnectToGatewayClick={toggleDialog} {...{ device, ...remainder }} />
141
      {open && <ConnectToGatewayDialog gatewayIp={gatewayIp} isPreRelease={isPreRelease} onCancel={toggleDialog} tenantToken={tenantToken} token={token} />}
×
142
    </>
143
  );
144
};
145

146
export default DeviceSystemTab;
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

© 2026 Coveralls, Inc