borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -124,9 +124,15 @@ this.repoCacheAccess.clear(); } public void clearRepoCacheAccess(String repo) { if (this.repoCacheAccess.get(repo) != null) { log.info("Clearing repository cache '" + repo + "'..."); this.repoCacheAccess.remove(repo); } } public void clearRepoCacheAccess(Repository repository) { log.info("Clearing repository cache '" + repository.getName() + "'..."); this.repoCacheAccess.remove(repository.getName()); clearRepoCacheAccess(repository.getName()); } /** borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
@@ -1,6 +1,5 @@ package de.micromata.borgbutler.config; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; @@ -29,7 +28,6 @@ private String passwordCommand; @Getter @Setter @JsonIgnore private String id; public String[] getEnvironmentVariables() { @@ -51,4 +49,11 @@ if (StringUtils.isBlank(value)) return; list.add(variable + "=" + value); } public void copyFrom(BorgRepoConfig other) { this.displayName = other.displayName; this.repo = other.repo; this.passphrase = other.passphrase; this.passwordCommand = other.passwordCommand; } } borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
@@ -10,10 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import java.util.List; @@ -68,6 +65,23 @@ return JsonUtils.toJson(repoConfig, prettyPrinter); } @POST @Path("repoConfig") @Produces(MediaType.TEXT_PLAIN) public void setRepoConfig(String jsonConfig) { BorgRepoConfig newRepoConfig = JsonUtils.fromJson(BorgRepoConfig.class, jsonConfig); BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(newRepoConfig.getId()); if (repoConfig == null) { log.error("Can't find repo config '" + newRepoConfig.getId() + "'. Can't save new settings."); return; } ButlerCache.getInstance().clearRepoCacheAccess(repoConfig.getRepo()); ButlerCache.getInstance().clearRepoCacheAccess(newRepoConfig.getRepo()); repoConfig.copyFrom(newRepoConfig); ConfigurationHandler.getInstance().save(); } /** * * @param id id or name of repo. borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
@@ -1,5 +1,5 @@ import React from 'react' import {Nav, NavLink, TabContent, Table, TabPane} from 'reactstrap'; import {Badge, Nav, NavLink, TabContent, Table, TabPane} from 'reactstrap'; import {Link} from "react-router-dom"; import classNames from 'classnames'; import {PageHeader} from '../../general/BootstrapComponents'; @@ -13,8 +13,10 @@ state = { id: this.props.match.params.id, displayName: this.props.match.params.displayName, isFetching: false, activeTab: '1', redirectOnError: true }; componentDidMount = () => { @@ -43,7 +45,12 @@ repo: json }) }) .catch(() => this.setState({isFetching: false, failed: true})); .catch(() => { this.setState({isFetching: false, failed: true}) if (this.state.redirectOnError && this.state.activeTab !== '3') { this.setState({activeTab: '3', redirectOnError: false}); } }); }; toggleTab = tab => () => { @@ -52,22 +59,46 @@ }) }; afterSave() { if (!this.state.failed) { this.setState({ activeTab: '1' }) } } afterCancel() { if (!this.state.failed) { this.setState({ activeTab: '1' }) } } render = () => { let content = undefined; let errorBadge = ''; let content1 = undefined; let content2 = undefined; const repo = this.state.repo; let pageHeader = ''; const displayName = (this.state.displayName) ? this.state.displayName : `Error: id=${this.state.id}`; let pageHeader = <React.Fragment> {displayName} </React.Fragment>; if (this.state.isFetching) { content = <JobMonitorPanel repo={this.state.id}/>; content1 = <JobMonitorPanel repo={this.state.id}/>; content2 = content1; } else if (this.state.failed) { content = <ErrorAlert content1 = <ErrorAlert title={'Cannot load Repositories'} description={'Something went wrong during contacting the rest api.'} description={'Something went wrong, may-be wrong configuration?'} action={{ handleClick: this.fetchRepo, title: 'Try again' }} />; content2 = content1; errorBadge = <Badge color="danger" pill>!</Badge>; } else if (this.state.repo) { pageHeader = <React.Fragment> {repo.displayName} @@ -128,91 +159,90 @@ <td>{repo.cache.path}</td> </tr> } content = <React.Fragment> <Nav tabs> <NavLink className={classNames({active: this.state.activeTab === '1'})} onClick={this.toggleTab('1')} > Archives </NavLink> <NavLink className={classNames({active: this.state.activeTab === '2'})} onClick={this.toggleTab('2')} > Information </NavLink> <NavLink className={classNames({active: this.state.activeTab === '3'})} onClick={this.toggleTab('3')} > Configuration </NavLink> </Nav> <TabContent activeTab={this.state.activeTab}> <TabPane tabId={'1'}> <Table hover> <tbody> <tr> <th>Archive</th> <th>Time</th> <th></th> <th>Id</th> </tr> {repo.archives.map((archive) => { // Return the element. Also pass key let loaded = ''; if (archive.fileListAlreadyCached) { loaded = <IconCheck/>; } return ( <tr key={archive.id}> <td><Link to={`/archives/${repo.id}/${archive.id}`}>{archive.name}</Link></td> <td>{archive.time}</td> <td>{loaded}</td> <td>{archive.id}</td> </tr>); })} </tbody> </Table> </TabPane> <TabPane tabId={'2'}> <Table striped bordered hover> <tbody> <tr> <td>Id</td> <td>{repo.id}</td> </tr> <tr> <td>Name</td> <td>{repo.name}</td> </tr> <tr> <td>Location</td> <td>{repo.location}</td> </tr> {stats} <tr> <td>Security dir</td> <td>{repo.securityDir}</td> </tr> {encryption} {cachePath} </tbody> </Table> </TabPane> <TabPane tabId={'3'}> <RepoConfigPanel id={repo.id}/> </TabPane> </TabContent> </React.Fragment>; content1 = <Table hover> <tbody> <tr> <th>Archive</th> <th>Time</th> <th></th> <th>Id</th> </tr> {repo.archives.map((archive) => { // Return the element. Also pass key let loaded = ''; if (archive.fileListAlreadyCached) { loaded = <IconCheck/>; } return ( <tr key={archive.id}> <td><Link to={`/archives/${repo.id}/${archive.id}`}>{archive.name}</Link></td> <td>{archive.time}</td> <td>{loaded}</td> <td>{archive.id}</td> </tr>); })} </tbody> </Table>; content2 = <Table striped bordered hover> <tbody> <tr> <td>Id</td> <td>{repo.id}</td> </tr> <tr> <td>Name</td> <td>{repo.name}</td> </tr> <tr> <td>Location</td> <td>{repo.location}</td> </tr> {stats} <tr> <td>Security dir</td> <td>{repo.securityDir}</td> </tr> {encryption} {cachePath} </tbody> </Table>; } return <React.Fragment> <PageHeader> {pageHeader} </PageHeader> {content} <Nav tabs> <NavLink className={classNames({active: this.state.activeTab === '1'})} onClick={this.toggleTab('1')} > Archives {errorBadge} </NavLink> <NavLink className={classNames({active: this.state.activeTab === '2'})} onClick={this.toggleTab('2')} > Information {errorBadge} </NavLink> <NavLink className={classNames({active: this.state.activeTab === '3'})} onClick={this.toggleTab('3')} > Configuration </NavLink> </Nav> <TabContent activeTab={this.state.activeTab}> <TabPane tabId={'1'}> {content1} </TabPane> <TabPane tabId={'2'}> {content2} </TabPane> <TabPane tabId={'3'}> <RepoConfigPanel id={this.state.id} afterCancel={this.afterCancel} afterSave={this.afterSave} repoError={this.state.failed}/> </TabPane> </TabContent> </React.Fragment>; }; @@ -221,6 +251,8 @@ this.fetchRepo = this.fetchRepo.bind(this); this.toggleTab = this.toggleTab.bind(this); this.afterCancel = this.afterCancel.bind(this); this.afterSave = this.afterSave.bind(this); } } borgbutler-webapp/src/components/views/repos/RepoCard.jsx
@@ -18,7 +18,7 @@ let repoText = this.buildItem(null, content); return <React.Fragment> <Card tag={Link} to={`/repoArchives/${repo.id}`} outline color="success" className={'repo'} <Card tag={Link} to={`/repoArchives/${repo.id}/${repo.displayName}`} outline color="success" className={'repo'} style={{backgroundColor: '#fff'}}> <CardHeader>{repo.displayName}</CardHeader> <CardBody> borgbutler-webapp/src/components/views/repos/RepoConfigPanel.jsx
@@ -7,9 +7,7 @@ import PropTypes from "prop-types"; import ErrorAlert from "../../general/ErrorAlert"; class RepoConfigPanel extends React .Component { class RepoConfigPanel extends React.Component { constructor(props) { super(props); @@ -61,27 +59,42 @@ this.setState({repoConfig: {...this.state.repoConfig, [event.target.name]: event.target.value}}); } onSave(event) { this.setState({ loading: true }) this.setState({ loading: false }) this.setReload(); async onSave(event) { const response = fetch(getRestServiceUrl("repos/repoConfig"), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.state.repoConfig) }); if (response) await response; if (this.props.afterSave) { this.props.afterSave(); } } onCancel() { this.setReload(); async onCancel() { const response = this.fetch(); if (response) await response; if (this.props.afterCancel) { this.props.afterCancel(); } } render() { let content; let repoError = ''; if (this.props.repoError) { repoError =<ErrorAlert title={'Cannot access repository'} description={'Repo not available or mis-configured (please refer the log files for more details).'} /> } if (this.state.isFetching) { content = <React.Fragment>Loading...</React.Fragment>; } else if (this.state.failed) { content = <ErrorAlert title={'Cannot load config or repository'} title={'Cannot load config of repository'} description={'Something went wrong during contacting the rest api.'} action={{ handleClick: this.fetchRepo, @@ -109,7 +122,7 @@ onChange={this.handleTextChange} placeholder="Enter the password command to get the command from."/> <FormLabelInputField label={'Password'} fieldLength={6} type={'password'} name={'passwordCommand'} value={repoConfig.password} name={'passphrase'} value={repoConfig.passphrase} onChange={this.handleTextChange} hint={"It's recommended to use password command instead."} /> @@ -125,13 +138,15 @@ <LoadingOverlay active={this.state.loading}/> </React.Fragment>; } return <React.Fragment>{content}</React.Fragment>; return <React.Fragment>{content}{repoError}</React.Fragment>; } } RepoConfigPanel .propTypes = { id: PropTypes.string RepoConfigPanel.propTypes = { afterCancel: PropTypes.func.isRequired, afterSave: PropTypes.func.isRequired, id: PropTypes.string, repoError: PropTypes.bool }; export default RepoConfigPanel; borgbutler-webapp/src/containers/WebApp.jsx
@@ -81,7 +81,7 @@ /> )) } <Route path={'/repoArchives/:id'} component={RepoArchiveListView}/> <Route path={'/repoArchives/:id/:displayName'} component={RepoArchiveListView}/> <Route path={'/archives/:repoId/:archiveId'} component={ArchiveView}/> </Switch> </div>