• 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

94.1
/frontend/src/js/components/devices/dialogs/PhysicalDeviceOnboarding.tsx
1
// Copyright 2019 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
import { Link } from 'react-router-dom';
2✔
17

2✔
18
import { InfoOutlined as InfoIcon } from '@mui/icons-material';
2✔
19
import { Autocomplete, TextField, Typography } from '@mui/material';
2✔
20
import { createFilterOptions } from '@mui/material/useAutocomplete';
2✔
21

2✔
22
import CopyCode from '@northern.tech/common-ui/CopyCode';
2✔
23
import DocsLink from '@northern.tech/common-ui/DocsLink';
2✔
24
import { MenderTooltipClickable } from '@northern.tech/common-ui/helptips/MenderTooltip';
2✔
25
import { EXTERNAL_PROVIDER, onboardingSteps } from '@northern.tech/store/constants';
2✔
26
import {
2✔
27
  getCurrentSession,
2✔
28
  getFeatures,
2✔
29
  getFullVersionInformation,
2✔
30
  getHostAddress,
2✔
31
  getIsEnterprise,
2✔
32
  getIsPreview,
2✔
33
  getOnboardingState,
2✔
34
  getOrganization,
2✔
35
  getTenantCapabilities
2✔
36
} from '@northern.tech/store/selectors';
2✔
37
import { advanceOnboarding, setOnboardingApproach, setOnboardingDeviceType } from '@northern.tech/store/thunks';
2✔
38
import { versionCompare } from '@northern.tech/utils/helpers';
2✔
39

2✔
40
import { getDebConfigurationCode } from '../../../utils/helpers';
2✔
41
import { HELPTOOLTIPS } from '../../helptips/HelpTooltips';
2✔
42
import { MenderHelpTooltip } from '../../helptips/MenderTooltip';
2✔
43

2✔
44
const filter = createFilterOptions();
5✔
45

2✔
46
const types = [
5✔
47
  { title: 'raspberrypi3', value: 'raspberrypi3' },
2✔
48
  { title: 'raspberrypi4', value: 'raspberrypi4' }
2✔
49
];
2✔
50

2✔
51
export const ConvertedImageNote = () => (
5✔
52
  <Typography variant="body1">
4✔
53
    We prepared an image, ready for Mender, for you to start with. You can find it in the{' '}
2✔
54
    <DocsLink path="get-started/preparation/prepare-a-raspberry-pi-device" title="Prepare a Raspberry Pi device" /> documentation, which also contains
2✔
55
    instructions for initial device setup. Once you&apos;re done flashing you can go ahead and proceed to the next step.
2✔
56
  </Typography>
2✔
57
);
2✔
58

2✔
59
const IntegrationsLink = () => (
5✔
60
  <Link to="/settings/integrations" target="_blank">
2✔
61
    Integration settings
2✔
62
  </Link>
2✔
63
);
2✔
64

2✔
65
export const ExternalProviderTip = ({ hasExternalIntegration, integrationProvider }) => (
5✔
66
  <MenderTooltipClickable
10✔
67
    className="clickable flexbox muted"
2✔
68
    placement="bottom"
2✔
69
    style={{ alignItems: 'end', marginBottom: 3 }}
2✔
70
    title={
2✔
71
      <div style={{ maxWidth: 350 }}>
2✔
72
        {hasExternalIntegration ? (
2✔
73
          <p>
2✔
74
            Devices added here will be automatically integrated with the {EXTERNAL_PROVIDER[integrationProvider].title} you set in the <IntegrationsLink />.
2✔
75
          </p>
2✔
76
        ) : (
2✔
77
          <p>
2✔
78
            To connect your devices with {EXTERNAL_PROVIDER[integrationProvider].title}, go to <IntegrationsLink /> and set up the integration.
2✔
79
          </p>
2✔
80
        )}
2✔
81
      </div>
2✔
82
    }
2✔
83
  >
2✔
84
    <InfoIcon />
2✔
85
  </MenderTooltipClickable>
2✔
86
);
2✔
87

2✔
88
export const DeviceTypeSelectionStep = ({
5✔
89
  hasConvertedImage,
2✔
90
  hasExternalIntegration,
2✔
91
  integrationProvider,
2✔
92
  onboardingState,
2✔
93
  onSelect,
2✔
94
  selection = '',
2✔
95
  version
2✔
96
}) => {
2✔
97
  const shouldShowOnboardingTip = !onboardingState.complete && onboardingState.showTips;
9✔
98
  const hasExternalIntegrationSupport = versionCompare(version, '3.2') > -1;
9✔
99
  return (
9✔
100
    <>
2✔
101
      <Typography variant="subtitle1" gutterBottom>
2✔
102
        Enter your device type
2✔
103
      </Typography>
2✔
104
      <Typography variant="body1" gutterBottom>
2✔
105
        Setting this attribute on the device ensures that the device will only receive updates for compatible software releases.
2✔
106
      </Typography>
2✔
107
      <div
2✔
108
        className="margin-top-small margin-bottom-small"
2✔
109
        style={{ display: 'grid', gridTemplateColumns: 'max-content max-content 150px', alignItems: 'center', gap: 16 }}
2✔
110
      >
2✔
111
        <Autocomplete
2✔
112
          id="device-type-selection"
2✔
113
          autoSelect
2✔
114
          autoHighlight
2✔
115
          filterSelectedOptions
2✔
116
          freeSolo
2✔
117
          getOptionLabel={option => {
2✔
118
            // Value selected with enter, right from the input
2✔
119
            if (typeof option === 'string') {
21!
120
              return option;
21✔
121
            }
2✔
UNCOV
122
            if (option.key === 'custom' && option.value === selection) {
2!
123
              return option.value;
2✔
124
            }
2✔
125
            return option.title;
2✔
126
          }}
2✔
127
          handleHomeEndKeys
2✔
128
          includeInputInList
2✔
129
          filterOptions={(options, params) => {
2✔
130
            const filtered = filter(options, params);
2✔
131
            if (filtered.length !== 1 && params.inputValue !== '') {
2!
132
              filtered.push({
2✔
133
                value: params.inputValue,
2✔
134
                key: 'custom',
2✔
135
                title: `Use "${params.inputValue}"`
2✔
136
              });
2✔
137
            }
2✔
138
            return filtered;
2✔
139
          }}
2✔
140
          options={types}
2✔
141
          onChange={onSelect}
2✔
142
          renderInput={params => (
2✔
143
            <TextField {...params} label="Device type" placeholder="Choose a device type" InputProps={{ ...params.InputProps }} style={{ marginTop: 0 }} />
10✔
144
          )}
2✔
145
          style={{ maxWidth: 300 }}
2✔
146
          value={selection}
2✔
147
        />
2✔
148
        {hasExternalIntegrationSupport && <ExternalProviderTip hasExternalIntegration={hasExternalIntegration} integrationProvider={integrationProvider} />}
2✔
149
        {shouldShowOnboardingTip ? <MenderHelpTooltip id={HELPTOOLTIPS.deviceTypeTip.id} placement="bottom" /> : <div />}
2✔
150
      </div>
2✔
151
      {hasConvertedImage && <ConvertedImageNote />}
2✔
152
    </>
2✔
153
  );
2✔
154
};
2✔
155

2✔
156
export const InstallationStep = ({ advanceOnboarding, selection, ...remainingProps }) => {
5✔
157
  const codeToCopy = getDebConfigurationCode({ ...remainingProps, deviceType: selection });
3✔
158
  return (
3✔
159
    <>
2✔
160
      <Typography variant="subtitle1" gutterBottom>
2✔
161
        Log into your device and install the Mender client
2✔
162
      </Typography>
2✔
163
      <Typography className="margin-bottom-small" variant="body1">
2✔
164
        Copy & paste and run this command <b>on your device</b>:
2✔
165
      </Typography>
2✔
UNCOV
166
      <CopyCode code={codeToCopy} onCopy={() => advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START)} withDescription={true} />
2✔
167
      <Typography variant="body1">
2✔
168
        This downloads the Mender client on the device, sets the configuration and starts the client. Once the client has started, your device will attempt to
2✔
169
        connect to the server. It will then appear in your Pending devices tab and you can continue.
2✔
170
      </Typography>
2✔
171
    </>
2✔
172
  );
2✔
173
};
2✔
174

2✔
175
const steps = {
5✔
176
  1: DeviceTypeSelectionStep,
2✔
177
  2: InstallationStep
2✔
178
};
2✔
179

2✔
180
const integrationProvider = EXTERNAL_PROVIDER['iot-hub'].provider;
5✔
181

2✔
182
export const PhysicalDeviceOnboarding = ({ progress }) => {
5✔
183
  const [selection, setSelection] = useState('');
8✔
184
  const hasExternalIntegration = useSelector(state => {
8✔
185
    const { credentials = {} } = state.organization.externalDeviceIntegrations.find(integration => integration.provider === integrationProvider) ?? {};
14✔
186
    const { [EXTERNAL_PROVIDER['iot-hub'].credentialsAttribute]: azureConnectionString = '' } = credentials;
14✔
187
    return !!azureConnectionString;
14✔
188
  });
2✔
189
  const ipAddress = useSelector(getHostAddress);
8✔
190
  const isEnterprise = useSelector(getIsEnterprise);
8✔
191
  const { isHosted } = useSelector(getFeatures);
8✔
192
  const isPreRelease = useSelector(getIsPreview);
8✔
193
  const onboardingState = useSelector(getOnboardingState);
8✔
194
  const { tenant_token: tenantToken } = useSelector(getOrganization);
8✔
195
  const { Integration: version } = useSelector(getFullVersionInformation);
8✔
196
  const { token } = useSelector(getCurrentSession);
8✔
197
  const { hasMonitor } = useSelector(getTenantCapabilities);
8✔
198
  const dispatch = useDispatch();
8✔
199

2✔
200
  useEffect(() => {
8✔
201
    dispatch(setOnboardingApproach('physical'));
4✔
202
  }, [dispatch]);
2✔
203

2✔
204
  const onSelect = (e, deviceType, reason) => {
8✔
205
    if (reason === 'selectOption') {
2!
206
      dispatch(setOnboardingDeviceType(deviceType.value));
2✔
207
      setSelection(deviceType.value);
2✔
208
    } else if (reason === 'clear') {
2!
209
      dispatch(setOnboardingDeviceType(''));
2✔
210
      setSelection('');
2✔
211
    }
2✔
212
  };
2✔
213

2✔
214
  const hasConvertedImage = !!selection && selection.length && (selection.startsWith('raspberrypi3') || selection.startsWith('raspberrypi4'));
8!
215

2✔
216
  const ComponentToShow = steps[progress];
8✔
217
  return (
8✔
218
    <ComponentToShow
2✔
UNCOV
219
      advanceOnboarding={step => dispatch(advanceOnboarding(step))}
2✔
220
      hasConvertedImage={hasConvertedImage}
2✔
221
      hasExternalIntegration={hasExternalIntegration}
2✔
222
      hasMonitor={hasMonitor}
2✔
223
      integrationProvider={integrationProvider}
2✔
224
      ipAddress={ipAddress}
2✔
225
      isEnterprise={isEnterprise}
2✔
226
      isHosted={isHosted}
2✔
227
      isPreRelease={isPreRelease}
2✔
228
      onboardingState={onboardingState}
2✔
229
      onSelect={onSelect}
2✔
230
      selection={selection}
2✔
231
      tenantToken={tenantToken}
2✔
232
      token={token}
2✔
233
      version={version}
2✔
234
    />
2✔
235
  );
2✔
236
};
2✔
237

2✔
238
export default PhysicalDeviceOnboarding;
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