// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-FileCopyrightText: 2020-2022 grommunio GmbH import React, { PureComponent } from 'react'; import { Button, Checkbox, FormControl, FormControlLabel, Grid, IconButton, MenuItem, Select, TextField, Typography, Tooltip } from '@mui/material'; import { withStyles } from '@mui/styles'; import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; import blue from '../../colors/blue'; import { red, yellow } from '@mui/material/colors'; import Delete from '@mui/icons-material/Delete'; import { DOMAIN_ADMIN_WRITE, SYSTEM_ADMIN_READ, SYSTEM_ADMIN_WRITE } from '../../constants'; import { CapabilityContext } from '../../CapabilityContext'; import { connect } from 'react-redux'; import MagnitudeAutocomplete from '../MagnitudeAutocomplete'; const styles = theme => ({ form: { width: '100%', marginTop: theme.spacing(4), }, input: { margin: theme.spacing(1, 0, 1, 0), }, flexInput: { margin: theme.spacing(1, 1, 1, 1), flex: 1, }, quota: { border: `1px solid ${blue['500']}`, margin: theme.spacing(2, 0, 2, 0), padding: theme.spacing(0.5), borderRadius: '8px', }, graphContainer: { padding: theme.spacing(0, 1, 0, 1), display: 'flex', flex: 1, alignItems: 'center', marginRight: 16, }, select: { minWidth: 60, }, barBackground: { width: '100%', height: 24, backgroundColor: '#949494', position: 'relative', }, quotaHeadline: { marginTop: -16, marginBottom: 0, padding: theme.spacing(0, 0.5), backgroundColor: theme.palette.background.paper, position: 'absolute', }, labelContainer: { display: 'flex', alignItems: 'center', }, adornment: { display: 'contents', }, }); class Account extends PureComponent { state = { autocompleteInput: '', } types = [ { name: 'User', ID: 0 }, { name: 'Mail list', ID: 1 }, { name: 'Room', ID: 7 }, { name: 'Equipment', ID: 8 }, ] statuses = [ { name: 'Normal', ID: 0 }, { name: 'Shared', ID: 4 }, ] formatMSE(rawMSE) { let exp = Math.log(rawMSE) / Math.log(1024) | 0; let res = (rawMSE / Math.pow(1024, exp)).toFixed(1); return res + '' + (exp === 0 ? 'bytes' : 'KMGTPEZY'[exp - 1] + 'B'); } calculateGraph() { const { classes, rawData, t } = this.props; const { messagesizeextended: rawMSE, storagequotalimit: rawSTQ, prohibitreceivequota: rawRQ, prohibitsendquota: rawSQ, } = rawData.properties || {}; const readableMSE = this.formatMSE(rawMSE); const usedSpace = ((rawMSE / rawSTQ / 1024) * 100 || 0).toFixed(0) + '%'; const rqPosition = (rawRQ / rawSTQ * 100).toFixed(0) + '%'; const sqPosition = (rawSQ / rawSTQ * 100).toFixed(0) + '%'; return <div className={classes.barBackground}> <div style={{ position: 'absolute', zIndex: 6, top: 0, width: '100%', }} > <Typography align="center"> {rawMSE !== undefined ? `${readableMSE} (${usedSpace})` : t('Store size indeterminate')} </Typography> </div> <div style={{ width: usedSpace, height: 24, background: 'linear-gradient(150deg, #56CCF2, #2F80ED)', display: 'flex', justifyContent: 'flex-end', }} ></div> <div style={{ position: 'absolute', zIndex: 5, width: sqPosition, height: 24, borderRight: `4px solid ${yellow['500']}`, top: 0, }} ></div> <div style={{ position: 'absolute', zIndex: 5, width: rqPosition, height: 24, borderRight: `4px solid ${red['500']}`, top: 0, }} ></div> </div>; } render() { const { classes, t, user, domain, sizeUnits, handleStatusInput, handlePropertyChange, handleIntPropertyChange, handleCheckbox, handleUnitChange, langs, handlePasswordChange, handleQuotaDelete, handleChatUser, handleServer, servers } = this.props; const writable = this.context.includes(DOMAIN_ADMIN_WRITE); const { username, status, properties, smtp, pop3_imap, changePassword, lang, //eslint-disable-line ldapID, chat, chatAdmin, privChat, privVideo, privFiles, privArchive, homeserver } = user; const { creationtime, displaytypeex, storagequotalimit, prohibitreceivequota, prohibitsendquota } = properties; return ( <FormControl className={classes.form}> <Grid container className={classes.input}> <TextField label={t("Username")} value={username || ''} autoFocus style={{ flex: 1, marginRight: 8 }} InputProps={{ endAdornment: <div>@{domain.domainname}</div>, }} disabled /> {writable && status !== 4 && ldapID === null && <Button variant="contained" color="primary" onClick={handlePasswordChange} size="small" > {t('Change password')} </Button>} </Grid> {ldapID && <TextField label={t("LDAP ID")} className={classes.input} value={ldapID || ''} disabled style={{ flex: 1, marginRight: 8 }} />} <TextField select className={classes.input} label={t("Mode")} fullWidth value={status || 0} onChange={handleStatusInput} > {this.statuses.map((status, key) => ( <MenuItem key={key} value={status.ID}> {status.name} </MenuItem> ))} </TextField> <TextField select className={classes.input} label={t("Type")} fullWidth disabled={displaytypeex === 1} value={displaytypeex || 0} onChange={handlePropertyChange('displaytypeex')} > {this.types.map((type, key) => ( <MenuItem key={key} value={type.ID}> {type.name} </MenuItem> ))} </TextField> {this.context.includes(SYSTEM_ADMIN_READ) && <MagnitudeAutocomplete value={homeserver || ''} filterAttribute={'hostname'} onChange={handleServer} className={classes.input} options={servers} label={t('Homeserver')} disabled={!this.context.includes(SYSTEM_ADMIN_WRITE)} />} <TextField select className={classes.input} label={t("Language")} fullWidth value={lang || 'en_US'} disabled > {langs.map((l) => ( <MenuItem key={l.code} value={l.code}> {l.code + ": " + l.name} </MenuItem> ))} </TextField> <div className={classes.quota}> <Typography color="textPrimary" className={classes.quotaHeadline}>{t('Used space')}</Typography> <Grid container style={{ marginTop: 8 }}> <TextField className={classes.flexInput} label={ <div className={classes.labelContainer}> {t("Send quota limit")} <div style={{ width: 6, height: 6, backgroundColor: yellow['500'], marginLeft: 4 }}></div> </div> } value={prohibitsendquota !== undefined ? prohibitsendquota : ''} onChange={handleIntPropertyChange('prohibitsendquota')} InputProps={{ endAdornment: <FormControl className={classes.adornment}> <Select onChange={handleUnitChange('prohibitsendquota')} value={sizeUnits.prohibitsendquota} className={classes.select} variant="standard" > <MenuItem value={1}>MB</MenuItem> <MenuItem value={2}>GB</MenuItem> <MenuItem value={3}>TB</MenuItem> </Select> <Tooltip title={('Delete quota')} placement="top"> <IconButton size="small" onClick={handleQuotaDelete('prohibitsendquota')}> <Delete color="error" fontSize="small" /> </IconButton> </Tooltip> </FormControl>, }} /> <TextField className={classes.flexInput} label={ <div className={classes.labelContainer}> {t("Receive quota limit")} <div style={{ width: 6, height: 6, backgroundColor: red['500'], marginLeft: 4 }}></div> </div> } value={prohibitreceivequota !== undefined ? prohibitreceivequota : ''} onChange={handleIntPropertyChange('prohibitreceivequota')} InputProps={{ endAdornment: <FormControl className={classes.adornment}> <Select onChange={handleUnitChange('prohibitreceivequota')} value={sizeUnits.prohibitreceivequota} className={classes.select} variant="standard" > <MenuItem value={1}>MB</MenuItem> <MenuItem value={2}>GB</MenuItem> <MenuItem value={3}>TB</MenuItem> </Select> <Tooltip title={('Delete quota')} placement="top"> <IconButton size="small" onClick={handleQuotaDelete('prohibitreceivequota')}> <Delete color="error" fontSize="small" /> </IconButton> </Tooltip> </FormControl>, }} /> <TextField className={classes.flexInput} label={ <div className={classes.labelContainer}> {t("Storage quota limit")} <div style={{ width: 6, height: 6, backgroundColor: '#ddd', marginLeft: 4 }}></div> </div> } value={storagequotalimit !== undefined ? storagequotalimit : ''} onChange={handleIntPropertyChange('storagequotalimit')} InputProps={{ endAdornment: <FormControl className={classes.adornment}> <Select onChange={handleUnitChange('storagequotalimit')} value={sizeUnits.storagequotalimit} className={classes.select} variant="standard" > <MenuItem value={1}>MB</MenuItem> <MenuItem value={2}>GB</MenuItem> <MenuItem value={3}>TB</MenuItem> </Select> <Tooltip title={('Delete quota')} placement="top"> <IconButton size="small" onClick={handleQuotaDelete('storagequotalimit')}> <Delete color="error" fontSize="small" /> </IconButton> </Tooltip> </FormControl>, }} /> <div className={classes.graphContainer}> {this.calculateGraph()} </div> </Grid> </div> <TextField className={classes.input} label={t("Creation time")} fullWidth value={creationtime || ''} onChange={handlePropertyChange('creationtime')} disabled /> {status !== 4 && <Tooltip placement="top-start" title={!domain.chat ? "This domain doesn't have a grommunio-chat team" : ''} > <Grid container className={classes.input}> <FormControlLabel control={ <Checkbox checked={chat || false} onChange={handleChatUser} color="primary" /> } label={t('Create grommunio-chat User')} disabled={!domain.chat} /> <FormControlLabel control={ <Checkbox checked={chatAdmin || false} onChange={handleCheckbox('chatAdmin')} color="primary" /> } disabled={!chat || !domain.chat} label={t('grommunio-chat admin permissions')} /> </Grid> </Tooltip>} {status !== 4 && <Grid container className={classes.input}> <FormControlLabel control={ <Checkbox checked={smtp || false } onChange={handleCheckbox('smtp')} color="primary" /> } label={t('Allow SMTP sending (used by POP3/IMAP clients)')} /> <FormControlLabel control={ <Checkbox checked={changePassword || false } onChange={handleCheckbox('changePassword')} color="primary" /> } label={t('Allow password changes')} /> <FormControlLabel control={ <Checkbox checked={pop3_imap || false /*eslint-disable-line*/} onChange={handleCheckbox('pop3_imap')} color="primary" /> } label={t('Allow POP3/IMAP logins')} /> </Grid>} {status !== 4 && <Grid container className={classes.input}> <FormControlLabel control={ <Checkbox checked={privChat || false } onChange={handleCheckbox('privChat')} color="primary" /> } disabled={!chat} label={t('Allow Chat')} /> <FormControlLabel control={ <Checkbox checked={privVideo || false } onChange={handleCheckbox('privVideo')} color="primary" /> } label={t('Allow Video')} /> <FormControlLabel control={ <Checkbox checked={privFiles || false } onChange={handleCheckbox('privFiles')} color="primary" /> } label={t('Allow Files')} /> <FormControlLabel control={ <Checkbox checked={privArchive || false } onChange={handleCheckbox('privArchive')} color="primary" /> } label={t('Allow Archive')} /> </Grid>} </FormControl> ); } } Account.contextType = CapabilityContext; Account.propTypes = { classes: PropTypes.object.isRequired, t: PropTypes.func.isRequired, domain: PropTypes.object, user: PropTypes.object.isRequired, sizeUnits: PropTypes.object.isRequired, handleInput: PropTypes.func.isRequired, handleStatusInput: PropTypes.func.isRequired, handlePropertyChange: PropTypes.func.isRequired, handleIntPropertyChange: PropTypes.func.isRequired, handleCheckbox: PropTypes.func.isRequired, handleUnitChange: PropTypes.func.isRequired, handlePasswordChange: PropTypes.func.isRequired, handleQuotaDelete: PropTypes.func.isRequired, handleChatUser: PropTypes.func.isRequired, handleServer: PropTypes.func.isRequired, rawData: PropTypes.object, langs: PropTypes.array, servers: PropTypes.array.isRequired, }; const mapStateToProps = state => { return { servers: state.servers.Servers, }; }; export default connect(mapStateToProps)( withTranslation()(withStyles(styles)(Account)));