• 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

98.31
/frontend/src/js/components/tenants/ExpandedTenant.tsx
1
// Copyright 2024 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 { useState } from 'react';
2✔
15
import { useDispatch, useSelector } from 'react-redux';
2✔
16
import { Link } from 'react-router-dom';
2✔
17

2✔
18
import { Button, Checkbox, Divider, Drawer, FormControl, FormControlLabel, FormHelperText, TextField, formControlLabelClasses } from '@mui/material';
2✔
19
import { makeStyles } from 'tss-react/mui';
2✔
20

2✔
21
import { TwoColumns } from '@northern.tech/common-ui/ConfigurationObject';
2✔
22
import { ConfirmModal } from '@northern.tech/common-ui/ConfirmModal';
2✔
23
import { DrawerTitle } from '@northern.tech/common-ui/DrawerTitle';
2✔
24
import { SupportLink } from '@northern.tech/common-ui/SupportLink';
2✔
25
import actions from '@northern.tech/store/actions';
2✔
26
import { generateTenantPathById } from '@northern.tech/store/locationutils';
2✔
27
import { getOrganization, getSsoConfig } from '@northern.tech/store/selectors';
2✔
28
import { AppDispatch } from '@northern.tech/store/store';
2✔
29
import { editTenantDeviceLimit, removeTenant } from '@northern.tech/store/thunks';
2✔
30
import copy from 'copy-to-clipboard';
2✔
31

2✔
32
import { DeviceCount } from '../header/DeviceCount';
2✔
33
import { Tenant } from './types';
2✔
34

2✔
35
interface ExpandedTenantProps {
2✔
36
  onCloseClick: () => void;
2✔
37
  tenant: Tenant;
2✔
38
}
2✔
39

2✔
40
const useStyles = makeStyles()(theme => ({
9✔
41
  devLimitInput: { minWidth: 150 },
2✔
42
  formWrapper: { display: 'flex', flexDirection: 'column', gap: theme.spacing(2), maxWidth: 750, [`.${formControlLabelClasses.root}`]: { marginTop: 0 } },
2✔
43
  ssoLink: {
2✔
44
    marginLeft: `calc(1em + ${theme.spacing(1.5)})`, // 1em as the width of the checkbox + the padding around the checkbox
2✔
45
    marginTop: theme.spacing(-1)
2✔
46
  }
2✔
47
}));
2✔
48
const { setSnackbar } = actions;
9✔
49

2✔
50
export const ExpandedTenant = (props: ExpandedTenantProps) => {
9✔
51
  const { onCloseClick, tenant } = props;
7✔
52
  const { name, id, device_limit = 0, device_count = 0, binary_delta } = tenant;
7✔
53

2✔
54
  const [shouldDelete, setShouldDelete] = useState<boolean>(false);
7✔
55
  const [newLimitForm, setNewLimitForm] = useState<boolean>(false);
7✔
56
  const [newLimit, setNewLimit] = useState<number>(device_limit);
7✔
57
  const [hasLimitError, setHasLimitError] = useState<boolean>(false);
7✔
58

2✔
59
  const { device_count: spDeviceUtilization, device_limit: spDeviceLimit } = useSelector(getOrganization);
7✔
60
  const ssoConfig = useSelector(getSsoConfig);
7✔
61

2✔
62
  const currentLimit = spDeviceLimit - spDeviceUtilization + device_limit;
7✔
63
  const { classes } = useStyles();
7✔
64
  const dispatch = useDispatch<AppDispatch>();
7✔
65

2✔
66
  const copyLinkToClipboard = () => {
7✔
67
    const location = window.origin + '/ui';
2✔
68
    copy(`${location}${generateTenantPathById(id)}`);
2✔
69
    dispatch(setSnackbar('Link copied to clipboard'));
2✔
70
  };
2✔
71

2✔
72
  const onChangeLimit = ({ target: { validity, value } }) => {
7✔
73
    if (validity.valid) {
4!
74
      setNewLimit(value);
4✔
75
      return setHasLimitError(false);
4✔
76
    }
2✔
77
    setHasLimitError(true);
2✔
78
  };
2✔
79

2✔
80
  const onNewLimitSubmit = async () => {
7✔
81
    await dispatch(editTenantDeviceLimit({ id, name, newLimit: Number(newLimit) }));
3✔
UNCOV
82
    setNewLimitForm(false);
2✔
83
  };
2✔
84

2✔
85
  const deleteTenant = () => dispatch(removeTenant({ id }));
7✔
86

2✔
87
  return (
7✔
88
    <Drawer onClose={onCloseClick} open={true} PaperProps={{ style: { minWidth: '67vw' } }} anchor="right">
2✔
89
      <DrawerTitle
2✔
90
        title={`Tenant Information for ${name}`}
2✔
91
        onLinkCopy={copyLinkToClipboard}
2✔
UNCOV
92
        preCloser={<Button onClick={() => setShouldDelete(true)}>Delete tenant</Button>}
2✔
93
        onClose={onCloseClick}
2✔
94
      />
2✔
95
      <Divider className="margin-bottom-large" />
2✔
96
      <div className={classes.formWrapper}>
2✔
UNCOV
97
        <TwoColumns className="align-self-start" setSnackbar={(str: string) => dispatch(setSnackbar(str))} items={{ name, ID: id }} />
2✔
98
        <FormControlLabel control={<Checkbox color="primary" size="small" disabled checked={binary_delta} />} label="Enable Delta Artifact generation" />
2✔
99
        {!!ssoConfig && (
2!
100
          <>
2✔
101
            <FormControlLabel
2✔
102
              control={<Checkbox color="primary" size="small" checked disabled />}
2✔
103
              label="Restrict to Service Provider’s Single Sign-On settings"
2✔
104
            />
2✔
105
            <Link className={classes.ssoLink} to="/settings/organization">
2✔
106
              View Single Sign-On settings
2✔
107
            </Link>
2✔
108
          </>
2✔
109
        )}
2✔
110
        <div className={`flexbox ${newLimitForm ? '' : 'center-aligned'} margin-top-small`}>
2✔
111
          <DeviceCount current={device_count} max={device_limit} variant="detailed" />
2✔
112
          <div className="margin-left">
2✔
113
            {newLimitForm ? (
2✔
114
              <FormControl className={classes.formWrapper}>
2✔
115
                <div className="flexbox center-aligned">
2✔
116
                  <TextField
2✔
117
                    className={classes.devLimitInput}
2✔
118
                    label="Set device limit"
2✔
119
                    type="number"
2✔
120
                    onChange={onChangeLimit}
2✔
121
                    slotProps={{ htmlInput: { min: device_count, max: currentLimit } }}
2✔
122
                    error={hasLimitError}
2✔
123
                    value={newLimit}
2✔
124
                  />
2✔
125
                  <Button
2✔
126
                    className="margin-left"
2✔
127
                    onClick={() => {
2✔
128
                      setNewLimit(device_limit);
2✔
129
                      setNewLimitForm(false);
2✔
130
                    }}
2✔
131
                  >
2✔
132
                    Cancel
2✔
133
                  </Button>
2✔
134
                  <Button className="margin-left-x-small" onClick={onNewLimitSubmit} color="primary" variant="contained">
2✔
135
                    Save
2✔
136
                  </Button>
2✔
137
                </div>
2✔
138
                <FormHelperText className={`${hasLimitError ? 'warning' : 'info'} margin-top-none`}>Maximum limit: {currentLimit}</FormHelperText>
2!
139
                <FormHelperText className="info margin-top-none">
2✔
140
                  {spDeviceUtilization} devices assigned of maximum {spDeviceLimit} across all tenants.
2✔
141
                  <br />
2✔
142
                  <SupportLink className="capitalized-start" variant="support" /> to increase your total limit
2✔
143
                </FormHelperText>
2✔
144
              </FormControl>
2✔
145
            ) : (
2✔
146
              <Button onClick={() => setNewLimitForm(true)}>Edit device limit</Button>
3✔
147
            )}
2✔
148
          </div>
2✔
149
        </div>
2✔
150
        <ConfirmModal
2✔
151
          header="Are you sure you want to delete this tenant?"
2✔
152
          description="All devices, users, artifacts and audit logs associated with the tenant will be removed."
2✔
153
          toType="delete"
2✔
154
          open={shouldDelete}
2✔
UNCOV
155
          close={() => setShouldDelete(false)}
2✔
156
          onConfirm={() => {
2✔
157
            deleteTenant();
2✔
158
            setShouldDelete(false);
2✔
159
            onCloseClick();
2✔
160
          }}
2✔
161
        />
2✔
162
      </div>
2✔
163
    </Drawer>
2✔
164
  );
2✔
165
};
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