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

mendersoftware / gui / 1057188406

01 Nov 2023 04:24AM UTC coverage: 82.824% (-17.1%) from 99.964%
1057188406

Pull #4134

gitlab-ci

web-flow
chore: Bump uuid from 9.0.0 to 9.0.1

Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.0 to 9.0.1.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4134: chore: Bump uuid from 9.0.0 to 9.0.1

4349 of 6284 branches covered (0.0%)

8313 of 10037 relevant lines covered (82.82%)

200.97 hits per line

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

59.3
/src/js/components/devices/troubleshoot/terminal-wrapper.js
1
// Copyright 2021 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 React, { useCallback, useEffect, useRef, useState } from 'react';
15
import Dropzone from 'react-dropzone';
16
import { useSelector } from 'react-redux';
17
import { Link } from 'react-router-dom';
18

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

22
import moment from 'moment';
23
import momentDurationFormatSetup from 'moment-duration-format';
24

25
import { BEGINNING_OF_TIME, TIMEOUTS } from '../../../constants/appConstants';
26
import { getFeatures, getIsPreview, getTenantCapabilities, getUserCapabilities } from '../../../selectors';
27
import Tracking from '../../../tracking';
28
import { useSession } from '../../../utils/sockethook';
29
import { MaybeTime } from '../../common/time';
30
import { getCode } from '../dialogs/make-gateway-dialog';
31
import Terminal from '../troubleshoot/terminal';
32
import ListOptions from '../widgets/listoptions';
33

34
momentDurationFormatSetup(moment);
12✔
35

36
const useStyles = makeStyles()(theme => ({
12✔
37
  connectionActions: { marginTop: theme.spacing() },
38
  connectionButton: { background: theme.palette.background.terminal, display: 'grid', placeContent: 'center' },
39
  sessionInfo: { gap: theme.spacing(3), marginBottom: theme.spacing(), '&>div': { gap: theme.spacing(2) } },
40
  terminalContent: {
41
    display: 'grid',
42
    gridTemplateRows: `max-content 0 minmax(${theme.spacing(60)}, 1fr) max-content`,
43
    flexGrow: 1,
44
    overflow: 'hidden',
45
    '&.device-connected': {
46
      gridTemplateRows: `max-content minmax(${theme.spacing(80)}, 1fr) max-content`
47
    }
48
  }
49
}));
50

51
const SessionInfo = ({ socketInitialized, startTime }) => {
12✔
52
  const [elapsed, setElapsed] = useState(moment());
6✔
53
  const timer = useRef();
6✔
54
  const { classes } = useStyles();
6✔
55

56
  useEffect(() => {
6✔
57
    clearInterval(timer.current);
4✔
58
    if (!socketInitialized) {
4✔
59
      return;
3✔
60
    }
61
    timer.current = setInterval(() => setElapsed(moment()), TIMEOUTS.halfASecond);
1✔
62
    return () => {
1✔
63
      clearInterval(timer.current);
1✔
64
    };
65
  }, [socketInitialized]);
66

67
  return (
6✔
68
    <div className={`flexbox ${classes.sessionInfo}`}>
69
      {[
70
        { key: 'status', title: 'Session status', content: socketInitialized ? 'connected' : 'disconnected' },
6✔
71
        { key: 'start', title: 'Connection start', content: <MaybeTime value={startTime} /> },
72
        {
73
          key: 'duration',
74
          title: 'Duration',
75
          content: startTime ? `${moment.duration(elapsed.diff(moment(startTime))).format('hh:mm:ss', { trim: false })}` : '-'
6✔
76
        }
77
      ].map(({ key, title, content }) => (
78
        <div key={key} className="flexbox">
18✔
79
          <div>{title}</div>
80
          <b>{content}</b>
81
        </div>
82
      ))}
83
    </div>
84
  );
85
};
86

87
const TroubleshootContent = ({ device, onDownload, setSocketClosed, setUploadPath, setFile, setSnackbar, setSocketInitialized, socketInitialized }) => {
12✔
88
  const [terminalInput, setTerminalInput] = useState('');
6✔
89
  const [startTime, setStartTime] = useState();
6✔
90
  const [snackbarAlreadySet, setSnackbarAlreadySet] = useState(false);
6✔
91
  const snackTimer = useRef();
6✔
92
  const { classes } = useStyles();
6✔
93
  const termRef = useRef({ terminal: React.createRef(), terminalRef: React.createRef() });
6✔
94

95
  const { isHosted } = useSelector(getFeatures);
6✔
96
  const { hasAuditlogs, isEnterprise } = useSelector(getTenantCapabilities);
6✔
97
  const { canAuditlog } = useSelector(getUserCapabilities);
6✔
98
  const canPreview = useSelector(getIsPreview);
6✔
99
  const onMessageReceived = useCallback(message => {
6✔
100
    if (!termRef.current.terminal.current) {
×
101
      return;
×
102
    }
103
    termRef.current.terminal.current.write(new Uint8Array(message));
×
104
  }, []);
105

106
  const onNotify = useCallback(
6✔
107
    content => {
108
      if (snackbarAlreadySet) {
×
109
        return;
×
110
      }
111
      setSnackbarAlreadySet(true);
×
112
      setSnackbar(content, TIMEOUTS.threeSeconds);
×
113
      snackTimer.current = setTimeout(() => setSnackbarAlreadySet(false), TIMEOUTS.threeSeconds + TIMEOUTS.debounceShort);
×
114
    },
115
    [setSnackbar, snackbarAlreadySet]
116
  );
117

118
  const onHealthCheckFailed = useCallback(() => {
6✔
119
    if (!socketInitialized) {
×
120
      return;
×
121
    }
122
    onNotify('Health check failed: connection with the device lost.');
×
123
  }, [onNotify, socketInitialized]);
124

125
  const onSocketClose = useCallback(
6✔
126
    event => {
127
      if (!socketInitialized) {
×
128
        return;
×
129
      }
130
      if (event.wasClean) {
×
131
        onNotify(`Connection with the device closed.`);
×
132
      } else if (event.code == 1006) {
×
133
        // 1006: abnormal closure
134
        onNotify('Connection to the remote terminal is forbidden.');
×
135
      } else {
136
        onNotify('Connection with the device died.');
×
137
      }
138
      setSocketInitialized(false);
×
139
      setSocketClosed(true);
×
140
    },
141
    [onNotify, setSocketClosed, setSocketInitialized, socketInitialized]
142
  );
143

144
  const [connect, sendMessage, close, sessionState] = useSession({
6✔
145
    onClose: onSocketClose,
146
    onHealthCheckFailed,
147
    onMessageReceived,
148
    onNotify,
149
    onOpen: setSocketInitialized
150
  });
151

152
  useEffect(() => {
6✔
153
    if (socketInitialized === undefined) {
4✔
154
      return;
3✔
155
    }
156
    if (socketInitialized) {
1!
157
      setStartTime(new Date());
1✔
158
      setSnackbar('Connection with the device established.', TIMEOUTS.fiveSeconds);
1✔
159
    } else {
160
      close();
×
161
    }
162
  }, [close, setSnackbar, socketInitialized]);
163

164
  useEffect(() => {
6✔
165
    return () => {
4✔
166
      clearTimeout(snackTimer.current);
4✔
167
      close();
4✔
168
    };
169
  }, [close]);
170

171
  useEffect(() => {
6✔
172
    if (sessionState !== WebSocket.OPEN) {
4!
173
      return;
×
174
    }
175
    return close;
4✔
176
  }, [close, sessionState]);
177

178
  const onConnectionToggle = () => {
6✔
179
    if (sessionState === WebSocket.CLOSED) {
×
180
      setStartTime();
×
181
      setSocketInitialized(undefined);
×
182
      setSocketClosed(false);
×
183
      connect(device.id);
×
184
      Tracking.event({ category: 'devices', action: 'open_terminal' });
×
185
    } else {
186
      setSocketInitialized(false);
×
187
      close();
×
188
    }
189
  };
190

191
  const onMakeGatewayClick = () => {
6✔
192
    const code = getCode(canPreview);
×
193
    setTerminalInput(code);
×
194
  };
195

196
  const onDrop = acceptedFiles => {
6✔
197
    if (acceptedFiles.length === 1) {
×
198
      setFile(acceptedFiles[0]);
×
199
      setUploadPath(`/tmp/${acceptedFiles[0].name}`);
×
200
    }
201
  };
202

203
  const commandHandlers = isHosted && isEnterprise ? [{ key: 'thing', onClick: onMakeGatewayClick, title: 'Promote to Mender gateway' }] : [];
6!
204

205
  const visibilityToggle = !socketInitialized ? { maxHeight: 0, overflow: 'hidden' } : {};
6✔
206
  return (
6✔
207
    <div className={`${classes.terminalContent} ${socketInitialized ? 'device-connected' : ''}`}>
6✔
208
      <SessionInfo socketInitialized={socketInitialized} startTime={startTime} />
209
      <Dropzone activeClassName="active" rejectClassName="active" multiple={false} onDrop={onDrop} noClick>
210
        {({ getRootProps }) => (
211
          <div {...getRootProps()} style={{ position: 'relative', ...visibilityToggle }}>
6✔
212
            <Terminal
213
              onDownloadClick={onDownload}
214
              sendMessage={sendMessage}
215
              socketInitialized={socketInitialized}
216
              style={{ position: 'absolute', width: '100%', height: '100%', ...visibilityToggle }}
217
              textInput={terminalInput}
218
              xtermRef={termRef}
219
            />
220
          </div>
221
        )}
222
      </Dropzone>
223
      {!socketInitialized && (
10✔
224
        <div className={classes.connectionButton}>
225
          <Button variant="contained" color="secondary" onClick={onConnectionToggle}>
226
            Connect Terminal
227
          </Button>
228
        </div>
229
      )}
230
      <div className={`flexbox space-between ${classes.connectionActions}`}>
231
        <Button onClick={onConnectionToggle}>{socketInitialized ? 'Disconnect' : 'Connect'} Terminal</Button>
6✔
232
        {canAuditlog && hasAuditlogs && (
12!
233
          <Button component={Link} to={`/auditlog?objectType=device&objectId=${device.id}&startDate=${BEGINNING_OF_TIME}`}>
234
            View Session Logs for this device
235
          </Button>
236
        )}
237
        {socketInitialized && !!commandHandlers.length && <ListOptions options={commandHandlers} title="Quick commands" />}
8!
238
      </div>
239
    </div>
240
  );
241
};
242

243
export default TroubleshootContent;
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