• 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

95.36
/frontend/src/js/components/devices/BaseDevices.tsx
1
// Copyright 2020 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 { Link } from 'react-router-dom';
2✔
15

2✔
16
import { defaultTextRender } from '@northern.tech/common-ui/DeviceIdentity';
2✔
17
import Time, { ApproximateRelativeDate } from '@northern.tech/common-ui/Time';
2✔
18
import { DEVICE_STATES, currentArtifact, rootfsImageVersion } from '@northern.tech/store/constants';
2✔
19
import pluralize from 'pluralize';
2✔
20

2✔
21
import preauthImage from '../../../assets/img/preauthorize.png';
2✔
22
import DeviceStatus from './DeviceStatus';
2✔
23

2✔
24
const AttributeRenderer = ({ content, textContent }) => (
15✔
25
  <div title={textContent}>
58✔
26
    <div className="text-overflow">{content}</div>
2✔
27
  </div>
2✔
28
);
2✔
29

2✔
30
export const DefaultAttributeRenderer = ({ column, device, idAttribute }) => (
15✔
31
  <AttributeRenderer content={column.textRender({ device, column, idAttribute })} textContent={column.textRender({ device, column, idAttribute })} />
4✔
32
);
2✔
33

2✔
34
export const getDeviceSoftwareText = (attributes = {}) => attributes[rootfsImageVersion] || '-';
56✔
35
export const DeviceSoftware = ({ device }) => (
15✔
36
  <AttributeRenderer content={getDeviceSoftwareText(device.attributes)} textContent={getDeviceSoftwareText(device.attributes)} />
29✔
37
);
2✔
38

2✔
39
export const getDeviceArtifactText = (attributes = {}) => attributes.artifact_name || '-';
15!
40
export const DeviceArtifact = ({ device }) => (
15✔
41
  <AttributeRenderer content={getDeviceArtifactText(device.attributes)} textContent={getDeviceArtifactText(device.attributes)} />
2✔
42
);
2✔
43

2✔
44
export const getDeviceTypeText = (attributes = {}) => (attributes.device_type?.length ? attributes.device_type.join(',') : '-');
56!
45
export const DeviceTypes = ({ device }) => (
15✔
46
  <AttributeRenderer content={getDeviceTypeText(device.attributes)} textContent={getDeviceTypeText(device.attributes)} />
29✔
47
);
2✔
48

2✔
49
export const RelativeDeviceTime = ({ device }) => (
15✔
50
  <div>
29✔
51
    <ApproximateRelativeDate updateTime={device.check_in_time_rounded} />
2✔
52
  </div>
2✔
53
);
2✔
54

2✔
55
export const DeviceCreationTime = ({ device }) =>
15✔
56
  device.created_ts ? (
3!
57
    <div>
2✔
58
      <Time value={device.created_ts} />
2✔
59
    </div>
2✔
60
  ) : (
2✔
61
    '-'
2✔
62
  );
2✔
63

2✔
64
export const DeviceStatusRenderer = ({ device }) => (
15✔
65
  <div>
29✔
66
    <DeviceStatus device={device} />
2✔
67
  </div>
2✔
68
);
2✔
69

2✔
70
export const AcceptedEmptyState = ({ allCount }) => (
15✔
71
  <div className="dashboard-placeholder">
6✔
72
    <p>No devices found</p>
2✔
73
    {!allCount && (
2✔
74
      <>
2✔
75
        <p>No devices have been authorized to connect to the Mender server yet.</p>
2✔
76
        <p>
2✔
77
          Visit the <Link to="/help/get-started">Help section</Link> to learn how to connect devices to the Mender server.
2✔
78
        </p>
2✔
79
      </>
2✔
80
    )}
2✔
81
  </div>
2✔
82
);
2✔
83

2✔
84
export const PreauthorizedEmptyState = ({ canManageDevices, limitMaxed, onClick }) => (
15✔
85
  <div className="dashboard-placeholder">
3✔
86
    <p>There are no preauthorized devices.</p>
2✔
87
    {canManageDevices && (
2!
88
      <p>
2✔
89
        {limitMaxed ? 'Preauthorize devices' : <a onClick={onClick}>Preauthorize devices</a>} so that when they come online, they will connect to the server
2!
90
        immediately
2✔
91
      </p>
2✔
92
    )}
2✔
93
    <img src={preauthImage} alt="preauthorize" />
2✔
94
  </div>
2✔
95
);
2✔
96

2✔
97
export const PendingEmptyState = ({ filters }) => (
15✔
98
  <div className="dashboard-placeholder">
3✔
99
    <p>
2✔
100
      {filters.length
2!
101
        ? `There are no pending devices matching the selected ${pluralize('filters', filters.length)}`
2✔
102
        : 'There are no devices pending authorization'}
2✔
103
    </p>
2✔
104
    <p>
2✔
105
      Visit the <Link to="/help/get-started">Help section</Link> to learn how to connect devices to the Mender server.
2✔
106
    </p>
2✔
107
  </div>
2✔
108
);
2✔
109

2✔
110
export const RejectedEmptyState = ({ filters }) => (
15✔
111
  <div className="dashboard-placeholder">
3✔
112
    <p>{filters.length ? `There are no rejected devices matching the selected ${pluralize('filters', filters.length)}` : 'There are no rejected devices'}</p>
2!
113
  </div>
2✔
114
);
2✔
115

2✔
116
export const defaultHeaders = {
15✔
117
  currentSoftware: {
2✔
118
    title: 'Current software',
2✔
119
    attribute: { name: rootfsImageVersion, scope: 'inventory' },
2✔
120
    component: DeviceSoftware,
2✔
121
    sortable: true,
2✔
122
    textRender: getDeviceSoftwareText
2✔
123
  },
2✔
124
  currentArtifact: {
2✔
125
    title: 'Current artifact',
2✔
126
    attribute: { name: currentArtifact, scope: 'inventory' },
2✔
127
    component: DeviceArtifact,
2✔
128
    sortable: true,
2✔
129
    textRender: getDeviceArtifactText
2✔
130
  },
2✔
131
  deviceCreationTime: {
2✔
132
    title: 'First request',
2✔
133
    attribute: { name: 'created_ts', scope: 'system' },
2✔
134
    component: DeviceCreationTime,
2✔
135
    sortable: true
2✔
136
  },
2✔
137
  deviceId: {
2✔
138
    title: 'Device ID',
2✔
139
    attribute: { name: 'id', scope: 'identity' },
2✔
140
    sortable: true,
2✔
UNCOV
141
    textRender: ({ device }) => device.id
2✔
142
  },
2✔
143
  deviceStatus: {
2✔
144
    title: 'Status',
2✔
145
    attribute: { name: 'status', scope: 'identity' },
2✔
146
    component: DeviceStatusRenderer,
2✔
147
    sortable: true,
2✔
148
    textRender: defaultTextRender
2✔
149
  },
2✔
150
  deviceType: {
2✔
151
    title: 'Device type',
2✔
152
    attribute: { name: 'device_type', scope: 'inventory' },
2✔
153
    component: DeviceTypes,
2✔
154
    sortable: true,
2✔
155
    textRender: getDeviceTypeText
2✔
156
  },
2✔
157
  lastCheckIn: {
2✔
158
    title: 'Latest activity',
2✔
159
    attribute: { name: 'check_in_time', scope: 'system' },
2✔
160
    component: RelativeDeviceTime,
2✔
161
    sortable: true
2✔
162
  }
2✔
163
};
2✔
164

2✔
165
const baseDevicesRoute = '/devices';
15✔
166

2✔
167
const acceptedDevicesRoute = {
15✔
168
  key: DEVICE_STATES.accepted,
2✔
169
  groupRestricted: false,
2✔
170
  route: `${baseDevicesRoute}/${DEVICE_STATES.accepted}`,
2✔
171
  title: () => DEVICE_STATES.accepted,
41✔
172
  emptyState: AcceptedEmptyState,
2✔
173
  defaultHeaders: [defaultHeaders.deviceType, defaultHeaders.currentSoftware, defaultHeaders.lastCheckIn]
2✔
174
};
2✔
175

2✔
176
export const routes = {
15✔
177
  allDevices: {
2✔
178
    ...acceptedDevicesRoute,
2✔
179
    route: `${baseDevicesRoute}/any`,
2✔
180
    key: 'any',
2✔
181
    title: () => 'any'
41✔
182
  },
2✔
183
  devices: acceptedDevicesRoute,
2✔
184
  [DEVICE_STATES.accepted]: acceptedDevicesRoute,
2✔
185
  [DEVICE_STATES.pending]: {
2✔
186
    key: DEVICE_STATES.pending,
2✔
187
    groupRestricted: true,
2✔
188
    route: `${baseDevicesRoute}/${DEVICE_STATES.pending}`,
2✔
189
    title: count => `${DEVICE_STATES.pending}${count ? ` (${count})` : ''}`,
41!
190
    emptyState: PendingEmptyState,
2✔
191
    defaultHeaders: [defaultHeaders.deviceCreationTime, defaultHeaders.lastCheckIn]
2✔
192
  },
2✔
193
  [DEVICE_STATES.preauth]: {
2✔
194
    key: DEVICE_STATES.preauth,
2✔
195
    groupRestricted: true,
2✔
196
    route: `${baseDevicesRoute}/${DEVICE_STATES.preauth}`,
2✔
197
    title: () => DEVICE_STATES.preauth,
41✔
198
    emptyState: PreauthorizedEmptyState,
2✔
199
    defaultHeaders: [
2✔
200
      {
2✔
201
        ...defaultHeaders.deviceCreationTime,
2✔
202
        title: 'Date added'
2✔
203
      }
2✔
204
    ]
2✔
205
  },
2✔
206
  [DEVICE_STATES.rejected]: {
2✔
207
    key: DEVICE_STATES.rejected,
2✔
208
    groupRestricted: true,
2✔
209
    route: `${baseDevicesRoute}/${DEVICE_STATES.rejected}`,
2✔
210
    title: () => DEVICE_STATES.rejected,
41✔
211
    emptyState: RejectedEmptyState,
2✔
212
    defaultHeaders: [defaultHeaders.deviceCreationTime, defaultHeaders.lastCheckIn]
2✔
213
  }
2✔
214
};
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