• 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

81.0
/frontend/src/js/components/devices/dialogs/DeviceConnectionDialog.tsx
1
// Copyright 2019 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 { useNavigate } from 'react-router-dom';
17

18
import { Button, DialogActions, DialogContent, List, ListItem, Typography } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import DocsLink from '@northern.tech/common-ui/DocsLink';
22
import Loader from '@northern.tech/common-ui/Loader';
23
import { BaseDialog } from '@northern.tech/common-ui/dialogs/BaseDialog';
24
import { DEVICE_STATES, TIMEOUTS, onboardingSteps } from '@northern.tech/store/constants';
25
import { getDeviceCountsByStatus, getFeatures, getOnboardingState, getTenantCapabilities } from '@northern.tech/store/selectors';
26
import { advanceOnboarding, saveUserSettings, setDeviceListState } from '@northern.tech/store/thunks';
27

28
import docker from '../../../../assets/img/docker.png';
29
import raspberryPi4 from '../../../../assets/img/raspberrypi4.png';
30
import raspberryPi from '../../../../assets/img/raspberrypi.png';
31
import { HELPTOOLTIPS } from '../../helptips/HelpTooltips';
32
import { MenderHelpTooltip } from '../../helptips/MenderTooltip';
33
import PhysicalDeviceOnboarding from './PhysicalDeviceOnboarding';
34
import VirtualDeviceOnboarding from './VirtualDeviceOnboarding';
35

36
const useStyles = makeStyles()(theme => ({
3✔
37
  rpiQuickstart: {
38
    backgroundColor: theme.palette.background.lightgrey ? theme.palette.background.lightgrey : theme.palette.grey[100],
3!
39
    '.os-list img': {
40
      height: 80,
41
      margin: theme.spacing(2)
42
    }
43
  },
44
  virtualLogo: { height: 40, marginLeft: theme.spacing(2) },
45
  deviceSection: {
46
    border: `1px solid ${theme.palette.divider}`,
47
    borderRadius: theme.spacing(0.5),
48
    gap: theme.spacing(1)
49
  }
50
}));
51

52
const docsLinks = [
2✔
53
  { key: 'debian', target: 'operating-system-updates-debian-family', title: 'Debian family' },
54
  { key: 'yocto', target: 'operating-system-updates-yocto-project', title: 'Yocto OSes' }
55
];
56

57
const MenderHubReference = () => (
2✔
58
  <Typography variant="body1">
3✔
59
    Or visit {/* eslint-disable-next-line react/jsx-no-target-blank */}
60
    <a href="https://hub.mender.io/c/board-integrations" target="_blank" rel="noopener">
61
      Mender Hub
62
    </a>{' '}
63
    and search integrations for your device and OS.
64
  </Typography>
65
);
66

67
const OnPremDeviceConnectionExplainer = ({ isEnterprise }) => (
2✔
68
  <>
×
69
    <Typography variant="body1">
70
      You can connect almost any device and Linux OS with Mender, but to make things simple during evaluation we recommend you to get started with a Debian
71
      based setup. This also works with a Raspberry Pi as a test device.
72
      <br />
73
      Follow the <DocsLink path="client-installation/install-with-debian-package" title="installation instructions" /> for Debian packages and select the{' '}
74
      {isEnterprise ? 'Enterprise' : 'Demo'} server tab to configure the client.
×
75
      <br />
76
      For operating system updates, see the documentation to integrate the following with Mender:
77
    </Typography>
78
    <List>
79
      {docsLinks.map(item => (
80
        <ListItem key={item.key} disablePadding className="padding-top-none padding-bottom-none">
×
81
          <DocsLink path={item.target} title={item.title} />
82
        </ListItem>
83
      ))}
84
    </List>
85
    <MenderHubReference />
86
  </>
87
);
88

89
const DeviceConnectionExplainer = ({ setOnDevice, setVirtualDevice }) => {
2✔
90
  const { classes } = useStyles();
3✔
91
  return (
3✔
92
    <>
93
      <Typography variant="body1">
94
        You can connect almost any device and Linux OS with Mender, but to make things simple during evaluation we recommend you use a Raspberry Pi as a test
95
        device.
96
      </Typography>
97
      <div className={`margin-top-small padding-small rpi-quickstart ${classes.rpiQuickstart}`}>
98
        <Typography variant="subtitle1">Raspberry Pi quick start</Typography>
99
        <Typography variant="body1">We&apos;ll walk you through the steps to connect a Raspberry Pi and deploy your first update with Mender.</Typography>
100
        <div className="flexbox column centered">
101
          <div className="flexbox centered os-list">
102
            {[raspberryPi, raspberryPi4].map((tile, index) => (
103
              <img key={`tile-${index}`} src={tile} />
6✔
104
            ))}
105
          </div>
106
          <Button variant="contained" color="secondary" onClick={() => setOnDevice(true)}>
1✔
107
            Get started
108
          </Button>
109
        </div>
110
      </div>
111
      <div className="two-columns margin-top-small">
112
        <div className={`padding-small padding-bottom-none flexbox column ${classes.deviceSection}`}>
113
          <div className="flexbox center-aligned">
114
            <Typography variant="subtitle1" gutterBottom>
115
              Use a virtual device
116
            </Typography>
117
            <img src={docker} className={classes.virtualLogo} />
118
          </div>
119
          <Typography variant="body1">
120
            Don&apos;t have a Raspberry Pi?
121
            <br />
122
            You can use our Docker-run virtual device to go through the same tutorial.
123
          </Typography>
124
          <div>
125
            <Typography variant="body1" color="text.secondary">
126
              If you want to evaluate our commercial components such as mender-monitor, please use a physical device instead as the virtual client does not
127
              support these components at this time.
128
            </Typography>
129
            <Button variant="text" size="small" onClick={() => setVirtualDevice(true)}>
1✔
130
              Try a virtual device
131
            </Button>
132
          </div>
133
        </div>
134
        <div className={`padding-small ${classes.deviceSection}`}>
135
          <Typography variant="subtitle1" gutterBottom>
136
            Other devices
137
          </Typography>
138
          <Typography variant="body1">See the documentation to integrate the following with Mender:</Typography>
139
          <List>
140
            {docsLinks.map(item => (
141
              <ListItem key={item.key} disablePadding className="padding-top-none padding-bottom-none">
6✔
142
                <DocsLink path={item.target} title={item.title} />
143
              </ListItem>
144
            ))}
145
          </List>
146
          <MenderHubReference />
147
        </div>
148
      </div>
149
    </>
150
  );
151
};
152

153
export const DeviceConnectionDialog = ({ onCancel }) => {
2✔
154
  const [onDevice, setOnDevice] = useState(false);
11✔
155
  const [progress, setProgress] = useState(1);
11✔
156
  const [virtualDevice, setVirtualDevice] = useState(false);
11✔
157
  const { pending: pendingCount } = useSelector(getDeviceCountsByStatus);
11✔
158
  const [pendingDevicesCount] = useState(pendingCount);
11✔
159
  const [hasMoreDevices, setHasMoreDevices] = useState(false);
11✔
160
  const { isEnterprise } = useSelector(getTenantCapabilities);
11✔
161
  const { isHosted } = useSelector(getFeatures);
11✔
162
  const { complete: onboardingComplete, deviceType: onboardingDeviceType } = useSelector(getOnboardingState);
11✔
163
  const dispatch = useDispatch();
11✔
164
  const navigate = useNavigate();
11✔
165

166
  useEffect(() => {
11✔
167
    setHasMoreDevices(pendingCount > pendingDevicesCount);
2✔
168
  }, [pendingDevicesCount, pendingCount]);
169

170
  useEffect(() => {
11✔
171
    if ((virtualDevice || progress >= 2) && hasMoreDevices && !window.location.hash.includes('pending')) {
3!
172
      dispatch(advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START));
×
173
      dispatch(setDeviceListState({ state: DEVICE_STATES.pending }));
×
174
      navigate('/devices/pending');
×
175
    }
176
    if (virtualDevice || progress >= 2) {
3✔
177
      dispatch(saveUserSettings({ onboarding: { deviceConnection: new Date().toISOString() } }));
1✔
178
    }
179
  }, [dispatch, hasMoreDevices, navigate, progress, virtualDevice]);
180

181
  const onBackClick = () => {
11✔
182
    let updatedProgress = progress - 1;
1✔
183
    if (!updatedProgress) {
1!
184
      updatedProgress = 1;
1✔
185
      setOnDevice(false);
1✔
186
      setVirtualDevice(false);
1✔
187
    }
188
    setProgress(updatedProgress);
1✔
189
  };
190

191
  const onAdvance = () => {
11✔
192
    dispatch(advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START));
×
193
    setProgress(progress + 1);
×
194
  };
195

196
  let content = <DeviceConnectionExplainer setOnDevice={setOnDevice} setVirtualDevice={setVirtualDevice} />;
11✔
197
  if (onDevice) {
11✔
198
    content = <PhysicalDeviceOnboarding progress={progress} />;
2✔
199
  } else if (virtualDevice) {
9✔
200
    content = <VirtualDeviceOnboarding />;
6✔
201
  } else if (!isHosted) {
3!
202
    content = <OnPremDeviceConnectionExplainer isEnterprise={isEnterprise} />;
×
203
  }
204

205
  if (hasMoreDevices && !onboardingComplete) {
11!
206
    setTimeout(onCancel, TIMEOUTS.twoSeconds);
×
207
  }
208

209
  const isPhysicalAndNotFinal = progress < 2 && (!virtualDevice || progress < 1);
11✔
210
  return (
11✔
211
    <BaseDialog open title="Connecting a device" maxWidth="sm" onClose={onCancel}>
212
      <DialogContent>{content}</DialogContent>
213
      <DialogActions>
214
        {onDevice || virtualDevice ? (
31✔
215
          <>
216
            {isPhysicalAndNotFinal && <Button onClick={onCancel}>Cancel</Button>}
10✔
217
            <Button onClick={onBackClick} variant="outlined">
218
              Back
219
            </Button>
220
            {isPhysicalAndNotFinal ? (
8✔
221
              <Button variant="contained" disabled={!(virtualDevice || (onDevice && onboardingDeviceType))} onClick={onAdvance}>
6✔
222
                Next
223
              </Button>
224
            ) : (
225
              <Button
226
                variant="contained"
227
                disabled={!onboardingComplete}
228
                onClick={onCancel}
229
                endIcon={!onboardingComplete && <Loader show small table style={{ top: -24 }} />}
12✔
230
              >
231
                {onboardingComplete ? 'Close' : 'Waiting for device'}
6!
232
              </Button>
233
            )}
234
          </>
235
        ) : (
236
          <>
237
            <MenderHelpTooltip id={HELPTOOLTIPS.deviceSupportTip.id} style={{ marginLeft: 20 }} />
238
            <div style={{ flexGrow: 1 }} />
239
            <Button onClick={onCancel}>Cancel</Button>
240
          </>
241
        )}
242
      </DialogActions>
243
    </BaseDialog>
244
  );
245
};
246

247
export default DeviceConnectionDialog;
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