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

mendersoftware / gui / 1315496247

03 Jun 2024 07:49AM UTC coverage: 83.437% (-16.5%) from 99.964%
1315496247

Pull #4434

gitlab-ci

mzedel
chore: aligned snapshots with updated mui version

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4434: chore: Bump the mui group with 3 updates

4476 of 6391 branches covered (70.04%)

8488 of 10173 relevant lines covered (83.44%)

140.36 hits per line

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

71.62
/src/js/components/settings/organization/organization.js
1
// Copyright 2017 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, useState } from 'react';
15
import CopyToClipboard from 'react-copy-to-clipboard';
16
import { useDispatch, useSelector } from 'react-redux';
17

18
// material ui
19
import { FileCopy as CopyPasteIcon } from '@mui/icons-material';
20
import { Button, Checkbox, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, List, MenuItem, Select } from '@mui/material';
21
import { makeStyles } from 'tss-react/mui';
22

23
import copy from 'copy-to-clipboard';
24
import moment from 'moment';
25

26
import { setSnackbar } from '../../../actions/appActions';
27
import {
28
  changeSsoConfig,
29
  deleteSsoConfig,
30
  downloadLicenseReport,
31
  getSsoConfigs,
32
  getUserOrganization,
33
  storeSsoConfig
34
} from '../../../actions/organizationActions';
35
import { TIMEOUTS } from '../../../constants/appConstants';
36
import { SSO_TYPES } from '../../../constants/organizationConstants.js';
37
import { createFileDownload, getSsoByType, toggle } from '../../../helpers';
38
import { getCurrentSession, getFeatures, getIsEnterprise, getIsPreview, getOrganization, getSsoConfig, getUserRoles } from '../../../selectors';
39
import ExpandableAttribute from '../../common/expandable-attribute';
40
import { HELPTOOLTIPS, MenderHelpTooltip } from '../../helptips/helptooltips';
41
import Billing from './billing';
42
import OrganizationSettingsItem, { maxWidth } from './organizationsettingsitem';
43
import { SSOConfig } from './ssoconfig';
44

45
const useStyles = makeStyles()(theme => ({
10✔
46
  copyNotification: { height: 30, padding: 15 },
47
  deviceLimitBar: { backgroundColor: theme.palette.grey[500], margin: '15px 0' },
48
  tenantInfo: { marginTop: 11, paddingBottom: 3, 'span': { marginLeft: theme.spacing(0.5), color: theme.palette.text.disabled } },
49
  tenantToken: { width: `calc(${maxWidth}px - ${theme.spacing(4)})` },
50
  tokenTitle: { paddingRight: 10 },
51
  tokenExplanation: { margin: '1em 0' },
52
  ssoSelect: { minWidth: 265 }
53
}));
54

55
export const OrgHeader = () => {
5✔
56
  const { classes } = useStyles();
11✔
57
  return (
11✔
58
    <div className="flexbox center-aligned">
59
      <div className={classes.tokenTitle}>Organization token</div>
60
      <MenderHelpTooltip id={HELPTOOLTIPS.tenantToken.id} disableHoverListener={false} placement="top" />
61
    </div>
62
  );
63
};
64

65
export const CopyTextToClipboard = ({ onCopy, token }) => {
5✔
66
  const [copied, setCopied] = useState(false);
21✔
67
  const { classes } = useStyles();
21✔
68

69
  const onCopied = () => {
21✔
70
    setCopied(true);
×
71
    onCopy();
×
72
    setTimeout(() => setCopied(false), TIMEOUTS.fiveSeconds);
×
73
  };
74

75
  return (
21✔
76
    <div>
77
      <CopyToClipboard text={token} onCopy={onCopied}>
78
        <Button startIcon={<CopyPasteIcon />}>Copy to clipboard</Button>
79
      </CopyToClipboard>
80
      <div className={classes.copyNotification}>{copied && <span className="green fadeIn">Copied to clipboard.</span>}</div>
21!
81
    </div>
82
  );
83
};
84

85
export const Organization = () => {
5✔
86
  const [hasSingleSignOn, setHasSingleSignOn] = useState(false);
10✔
87
  const [isConfiguringSSO, setIsConfiguringSSO] = useState(false);
10✔
88
  const [isResettingSSO, setIsResettingSSO] = useState(false);
10✔
89
  const [showTokenWarning, setShowTokenWarning] = useState(false);
10✔
90
  const [newSso, setNewSso] = useState(undefined);
10✔
91
  const [selectedSsoItem, setSelectedSsoItem] = useState(undefined);
10✔
92
  const isEnterprise = useSelector(getIsEnterprise);
10✔
93
  const { isAdmin } = useSelector(getUserRoles);
10✔
94
  const canPreview = useSelector(getIsPreview);
10✔
95
  const { isHosted } = useSelector(getFeatures);
10✔
96
  const org = useSelector(getOrganization);
10✔
97
  const ssoConfig = useSelector(getSsoConfig);
10✔
98
  const dispatch = useDispatch();
10✔
99
  const { token } = useSelector(getCurrentSession);
10✔
100
  const { classes } = useStyles();
10✔
101

102
  useEffect(() => {
10✔
103
    dispatch(getUserOrganization());
2✔
104
  }, [dispatch]);
105

106
  useEffect(() => {
10✔
107
    dispatch(getSsoConfigs());
2✔
108
  }, [dispatch]);
109

110
  useEffect(() => {
10✔
111
    setHasSingleSignOn(!!ssoConfig);
5✔
112
    setIsConfiguringSSO(!!ssoConfig);
5✔
113
    if (ssoConfig) {
5✔
114
      setSelectedSsoItem(getSsoByType(ssoConfig.type));
2✔
115
    }
116
  }, [ssoConfig]);
117

118
  const dispatchedSetSnackbar = useCallback((...args) => dispatch(setSnackbar(...args)), [dispatch]);
10✔
119

120
  const onSaveSSOSettings = useCallback(
10✔
121
    (id, config) => {
122
      const { contentType } = getSsoByType(selectedSsoItem.id);
2✔
123
      if (isResettingSSO) {
2!
124
        return dispatch(deleteSsoConfig(ssoConfig)).then(() => setIsResettingSSO(false));
2✔
125
      }
126
      if (id) {
×
127
        return dispatch(changeSsoConfig({ id, config, contentType }));
×
128
      }
129
      return dispatch(storeSsoConfig({ config, contentType }));
×
130
    },
131
    [isResettingSSO, dispatch, ssoConfig, selectedSsoItem]
132
  );
133

134
  const onCancelSSOSettings = () => {
10✔
135
    setIsResettingSSO(false);
×
136
    setIsConfiguringSSO(hasSingleSignOn);
×
137
  };
138

139
  const onTokenExpansion = useCallback(() => setShowTokenWarning(true), []);
10✔
140

141
  const onDownloadReportClick = () =>
10✔
142
    dispatch(downloadLicenseReport()).then(report => createFileDownload(report, `Mender-license-report-${moment().format(moment.HTML5_FMT.DATE)}`, token));
×
143

144
  const onTenantInfoClick = () => {
10✔
145
    copy(`Organization: ${org.name}, Tenant ID: ${org.id}`);
×
146
    setSnackbar('Copied to clipboard');
×
147
  };
148

149
  const onSSOClick = () => {
10✔
150
    if (hasSingleSignOn) {
2!
151
      setIsConfiguringSSO(false);
2✔
152
      return setIsResettingSSO(true);
2✔
153
    }
154
    setIsConfiguringSSO(toggle);
×
155
  };
156

157
  const onSsoSelect = useCallback(
10✔
158
    ({ target: { value: type = '' } }) => {
×
159
      if (ssoConfig) {
×
160
        setNewSso(type);
×
161
      } else {
162
        setSelectedSsoItem(getSsoByType(type));
×
163
      }
164
    },
165
    [ssoConfig]
166
  );
167

168
  const changeSSO = () => {
10✔
169
    dispatch(deleteSsoConfig(ssoConfig)).then(() => {
×
170
      setSelectedSsoItem(getSsoByType(newSso));
×
171
      setIsConfiguringSSO(true);
×
172
      setNewSso(undefined);
×
173
    });
174
  };
175

176
  return (
10✔
177
    <div className="margin-top-small">
178
      <h2 className="margin-top-small">Organization and billing</h2>
179
      <List>
180
        <OrganizationSettingsItem
181
          title="Organization name"
182
          content={{
183
            action: { action: onTenantInfoClick, internal: true },
184
            description: (
185
              <div className={`clickable ${classes.tenantInfo}`} onClick={onTenantInfoClick}>
186
                {org.name}
187
                <span>({org.id})</span>
188
              </div>
189
            )
190
          }}
191
        />
192
        <OrganizationSettingsItem
193
          title={<OrgHeader />}
194
          content={{}}
195
          secondary={
196
            <>
197
              <ExpandableAttribute
198
                className={classes.tenantToken}
199
                component="div"
200
                disableGutters
201
                dividerDisabled
202
                key="org_token"
203
                onExpansion={onTokenExpansion}
204
                secondary={org.tenant_token}
205
                textClasses={{ secondary: 'inventory-text tenant-token-text' }}
206
              />
207
              {showTokenWarning && (
10!
208
                <p className="warning">
209
                  <b>Important</b>
210
                  <br />
211
                  Do not share your organization token with others. Treat this token like a password, as it can be used to request authorization for new
212
                  devices.
213
                </p>
214
              )}
215
            </>
216
          }
217
          sideBarContent={<CopyTextToClipboard onCopy={onTokenExpansion} token={org.tenant_token} />}
218
        />
219
      </List>
220

221
      {isEnterprise && isAdmin && (
30✔
222
        <div>
223
          <FormControlLabel
224
            className="margin-bottom-small"
225
            control={<Checkbox checked={!isResettingSSO && (hasSingleSignOn || isConfiguringSSO)} onChange={onSSOClick} />}
20✔
226
            label="Enable Single Sign-On"
227
          />
228
        </div>
229
      )}
230

231
      {isConfiguringSSO && (
12✔
232
        <div>
233
          <Select className={classes.ssoSelect} displayEmpty onChange={onSsoSelect} value={selectedSsoItem?.id || ''}>
2!
234
            <MenuItem value="">Select type</MenuItem>
235
            {SSO_TYPES.map(item => (
236
              <MenuItem key={item.id} value={item.id}>
4✔
237
                <div className="capitalized-start">{item.title}</div>
238
              </MenuItem>
239
            ))}
240
          </Select>
241
        </div>
242
      )}
243

244
      <div className="flexbox center-aligned">
245
        {isResettingSSO && !isConfiguringSSO && (
18✔
246
          <>
247
            <Button onClick={onCancelSSOSettings}>Cancel</Button>
248
            <Button onClick={onSaveSSOSettings} disabled={!hasSingleSignOn} variant="contained">
249
              Save
250
            </Button>
251
          </>
252
        )}
253
      </div>
254
      {selectedSsoItem && (
17✔
255
        <div className="margin-top">
256
          <Collapse className="margin-left-large" in={isConfiguringSSO}>
257
            <SSOConfig
258
              ssoItem={selectedSsoItem}
259
              config={ssoConfig}
260
              onSave={onSaveSSOSettings}
261
              onCancel={onCancelSSOSettings}
262
              setSnackbar={dispatchedSetSnackbar}
263
              token={token}
264
            />
265
          </Collapse>
266
        </div>
267
      )}
268

269
      {isHosted && <Billing />}
20✔
270
      {(canPreview || !isHosted) && isEnterprise && isAdmin && (
20!
271
        <Button className="margin-top" onClick={onDownloadReportClick} variant="contained">
272
          Download license report
273
        </Button>
274
      )}
275
      <ChangeSsoDialog dismiss={() => setNewSso(undefined)} open={!!newSso} submit={changeSSO} />
×
276
    </div>
277
  );
278
};
279

280
const ChangeSsoDialog = ({ dismiss, open, submit }) => (
5✔
281
  <Dialog open={open}>
10✔
282
    <DialogTitle>Change Single Sign-On type</DialogTitle>
283
    <DialogContent style={{ overflow: 'hidden' }}>Are you sure you want to change SSO type? This will lose your current settings.</DialogContent>
284
    <DialogActions>
285
      <Button style={{ marginRight: 10 }} onClick={dismiss}>
286
        Cancel
287
      </Button>
288
      <Button variant="contained" color="primary" onClick={() => submit()}>
×
289
        Change
290
      </Button>
291
    </DialogActions>
292
  </Dialog>
293
);
294

295
export default Organization;
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