// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-FileCopyrightText: 2020-2022 grommunio GmbH import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { withStyles } from '@mui/styles'; import { withTranslation } from 'react-i18next'; import { Paper, Table, TableHead, TableRow, TableCell, TableBody, Typography, Button, Grid, TextField, InputAdornment, Tabs, Tab, IconButton } from '@mui/material'; import Search from '@mui/icons-material/Search'; import { connect } from 'react-redux'; import { fetchDBConfData, deleteDBService } from '../actions/dbconf'; import UploadServiceFile from '../components/Dialogs/UploadServiceFile'; import GeneralDelete from '../components/Dialogs/GeneralDelete'; import { Delete } from '@mui/icons-material'; import CreateDbconfFile from '../components/Dialogs/CreateDbconfFile'; import { defaultFetchLimit, SYSTEM_ADMIN_WRITE } from '../constants'; import { CapabilityContext } from '../CapabilityContext'; import TableViewContainer from '../components/TableViewContainer'; const styles = theme => ({ paper: { padding: theme.spacing(2, 2, 2, 2), }, buttonGrid: { margin: theme.spacing(0, 2, 2, 2), }, actions: { display: 'flex', flex: 1, margin: theme.spacing(0, 4, 0, 0), justifyContent: 'flex-end', alignItems: 'flex-end', }, pre: { margin: theme.spacing(1, 0, 1, 0), }, title: { marginTop: 16, }, button: { marginLeft: 8, }, }); class DBConf extends Component { componentDidMount() { this.props.fetch() .catch(msg => { this.setState({ snackbar: msg || 'Unknown error' }); }); } state = { snackbar: '', match: '', adding: false, deleting: false, configuring: false, offset: defaultFetchLimit, tab: 0, } handleAdd = () => this.setState({ adding: true }); handleAddingSuccess = () => this.setState({ adding: false, configuring: false, snackbar: 'Success!' }); handleAddingClose = () => this.setState({ adding: false, configuring: false }); handleAddingError = error => this.setState({ snackbar: error }); handleDelete = service => event => { event.stopPropagation(); this.setState({ deleting: service }); } handleDeleteSuccess = () => { this.setState({ deleting: false, snackbar: 'Success!' }); } handleDeleteClose = () => this.setState({ deleting: false }); handleDeleteError = error => this.setState({ snackbar: error }); handleNavigation = path => event => { const { history } = this.props; event.preventDefault(); history.push(`/${path}`); } handleMatch = e => { const { value } = e.target; this.setState({ match: value }); } handleTab = (e, tab) => this.setState({ tab }); render() { const { classes, t, services, commands } = this.props; const { adding, configuring, snackbar, match, tab, deleting } = this.state; const writable = this.context.includes(SYSTEM_ADMIN_WRITE); return ( <TableViewContainer headline={t("Configuration DB")} href="https://docs.grommunio.com/admin/administration.html#db-configuration" subtitle={t('dbconf_sub')} snackbar={snackbar} onSnackbarClose={() => this.setState({ snackbar: '' })} > <Grid container alignItems="flex-end" className={classes.buttonGrid}> <Button variant="contained" color="primary" onClick={() => this.setState({ adding: true })} disabled={!writable} > {t("Create file")} </Button> <Button variant="contained" color="primary" onClick={() => this.setState({ configuring: true })} className={classes.button} disabled={!writable} > {t("Configure grommunio-dbconf")} </Button> <div className={classes.actions}> <TextField value={match} onChange={this.handleMatch} placeholder={t("search services")} variant="outlined" className={classes.textfield} InputProps={{ startAdornment: ( <InputAdornment position="start"> <Search color="secondary" /> </InputAdornment> ), }} color="primary" /> </div> </Grid> <Grid container alignItems="flex-end" className={classes.buttonGrid}> <Tabs textColor="primary" indicatorColor="primary" value={tab} onChange={this.handleTab} > <Tab value={0} label="Services" /> <Tab value={1} label="Commands" /> </Tabs> </Grid> {tab === 0 ? <Paper elevation={1}> <Table size="small"> <TableHead> <TableRow> <TableCell> {t('Name')} </TableCell> <TableCell padding="checkbox"></TableCell> </TableRow> </TableHead> <TableBody> {services.filter(s => s.includes(match)).map((service, idx) => <TableRow onClick={this.handleNavigation('dbconf/' + service)} key={idx} hover> <TableCell>{service}</TableCell> <TableCell align="right"> {writable && <IconButton onClick={this.handleDelete(service)} size="large"> <Delete color="error" /> </IconButton>} </TableCell> </TableRow> )} </TableBody> </Table> </Paper> : <Paper className={classes.paper}> <Typography variant="h6">Key</Typography> {commands.key.length > 0 ? commands.key.map((key, idx) => <pre className={classes.pre} key={idx}> <code key={idx}>{key}</code> </pre> ) : <Typography><i>none</i></Typography>} <Typography className={classes.title} variant="h6">File</Typography> {commands.file.length > 0 ? commands.file.map((key, idx) => <pre className={classes.pre} key={idx}> <code>{key}</code> </pre> ) : <Typography><i>none</i></Typography>} <Typography className={classes.title} variant="h6">Service</Typography> {commands.service.length > 0 ? commands.service.map((key, idx) => <pre className={classes.pre} key={idx}> <code>{key}</code> </pre> ) : <Typography><i>none</i></Typography>} </Paper>} <GeneralDelete open={!!deleting} delete={this.props.delete} onSuccess={this.handleDeleteSuccess} onError={this.handleDeleteError} onClose={this.handleDeleteClose} item={deleting} id={deleting} /> <UploadServiceFile open={adding} onClose={this.handleAddingClose} onError={this.handleAddingError} onSuccess={this.handleAddingSuccess} /> <CreateDbconfFile open={configuring} onClose={this.handleAddingClose} onError={this.handleAddingError} onSuccess={this.handleAddingSuccess} /> </TableViewContainer> ); } } DBConf.contextType = CapabilityContext; DBConf.propTypes = { classes: PropTypes.object.isRequired, t: PropTypes.func.isRequired, history: PropTypes.object.isRequired, services: PropTypes.array.isRequired, commands: PropTypes.object.isRequired, fetch: PropTypes.func.isRequired, delete: PropTypes.func.isRequired, }; const mapStateToProps = state => { const { dbconf } = state; return { services: dbconf.services, commands: dbconf.commands, }; }; const mapDispatchToProps = dispatch => { return { fetch: async () => { await dispatch(fetchDBConfData({})).catch(msg => Promise.reject(msg)); }, delete: async service => { await dispatch(deleteDBService(service)).catch(msg => Promise.reject(msg)); }, }; }; export default connect(mapStateToProps, mapDispatchToProps)( withTranslation()(withStyles(styles)(DBConf)));