4 files added
10 files modified
| | |
| | | state = STATE.DIRTY; |
| | | } |
| | | |
| | | /** |
| | | * Calls {@link #clear()} and removes the cache files. Therefore a new creation of this cache is forced. |
| | | */ |
| | | public void clearAndReset() { |
| | | synchronized (this) { |
| | | if (cacheFile.exists()) { |
| | | log.info("Clearing cache and deleting cache file (recreation is forced): " + cacheFile.getAbsolutePath()); |
| | | cacheFile.delete(); |
| | | } |
| | | clear(); |
| | | state = STATE.SAVED; |
| | | } |
| | | } |
| | | |
| | | protected void setDirty() { |
| | | state = STATE.DIRTY; |
| | | } |
| | |
| | | |
| | | protected abstract T load(BorgRepoConfig repoConfig, String identifier); |
| | | |
| | | public abstract boolean matches(T element, String identifier); |
| | | protected abstract boolean matches(T element, String identifier); |
| | | |
| | | public abstract String getIdentifier(T element); |
| | | protected abstract String getIdentifier(T element); |
| | | |
| | | public abstract void updateFrom(T dest, T source); |
| | | protected abstract void updateFrom(T dest, T source); |
| | | |
| | | /** |
| | | * Removes all entries (doesn't effect the cache files!). |
| | |
| | | } |
| | | |
| | | @Override |
| | | public boolean matches(ArchiveInfo element, String identifier) { |
| | | protected boolean matches(ArchiveInfo element, String identifier) { |
| | | return element.matches(identifier); |
| | | } |
| | | |
| | | @Override |
| | | public String getIdentifier(ArchiveInfo element) { |
| | | protected String getIdentifier(ArchiveInfo element) { |
| | | return element.getRepository().getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateFrom(ArchiveInfo dest, ArchiveInfo source) { |
| | | protected void updateFrom(ArchiveInfo dest, ArchiveInfo source) { |
| | | dest.updateFrom(source); |
| | | } |
| | | |
| | |
| | | import de.micromata.borgbutler.config.ConfigurationHandler; |
| | | import de.micromata.borgbutler.json.borg.Archive; |
| | | import de.micromata.borgbutler.json.borg.FilesystemItem; |
| | | import de.micromata.borgbutler.json.borg.RepoInfo; |
| | | import de.micromata.borgbutler.json.borg.Repository; |
| | | import lombok.Getter; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.slf4j.Logger; |
| | |
| | | } |
| | | } |
| | | |
| | | public Repository getRepository(String idOrName) { |
| | | BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(idOrName); |
| | | RepoInfo repoInfo = repoInfoCache.get(repoConfig, idOrName); |
| | | if (repoInfo == null) { |
| | | log.warn("Repo with name or id '" + idOrName + "' not found."); |
| | | return null; |
| | | } |
| | | return repoInfo.getRepository(); |
| | | } |
| | | |
| | | public List<Repository> getAllRepositories() { |
| | | List<Repository> repositories = new ArrayList<>(); |
| | | for (BorgRepoConfig repoConfig : ConfigurationHandler.getConfiguration().getRepoConfigs()) { |
| | | RepoInfo repoInfo = repoInfoCache.get(repoConfig, repoConfig.getName()); |
| | | if (repoInfo == null) { |
| | | log.warn("Repo with name '" + repoConfig.getName() + "' not found."); |
| | | continue; |
| | | } |
| | | repositories.add(repoInfo.getRepository()); |
| | | } |
| | | return repositories; |
| | | } |
| | | |
| | | public List<FilesystemItem> getArchiveContent(BorgRepoConfig repoConfig, Archive archive) { |
| | | if (archive == null || StringUtils.isBlank(archive.getArchive())) { |
| | | return null; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public boolean matches(RepoInfo element, String identifier) { |
| | | protected boolean matches(RepoInfo element, String identifier) { |
| | | return element.matches(identifier); |
| | | } |
| | | |
| | | @Override |
| | | public String getIdentifier(RepoInfo element) { |
| | | protected String getIdentifier(RepoInfo element) { |
| | | return element.getRepository().getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateFrom(RepoInfo dest, RepoInfo source) { |
| | | protected void updateFrom(RepoInfo dest, RepoInfo source) { |
| | | dest.updateFrom(source); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public boolean matches(RepoList element, String identifier) { |
| | | protected boolean matches(RepoList element, String identifier) { |
| | | return element.matches(identifier); |
| | | } |
| | | |
| | | @Override |
| | | public String getIdentifier(RepoList element) { |
| | | protected String getIdentifier(RepoList element) { |
| | | return element.getRepository().getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateFrom(RepoList dest, RepoList source) { |
| | | protected void updateFrom(RepoList dest, RepoList source) { |
| | | dest.updateFrom(source); |
| | | } |
| | | |
| | |
| | | return instance.configuration; |
| | | } |
| | | |
| | | public void read() { |
| | | private void read() { |
| | | log.info("Reading config file '" + configFile.getAbsolutePath() + "'"); |
| | | try { |
| | | String json = FileUtils.readFileToString(configFile, Definitions.STD_CHARSET); |
| | |
| | | configFile = new File(userHome, CONFIG_FILENAME); |
| | | backupConfigFile = new File(userHome, CONFIG_BACKUP_FILENAME); |
| | | workingDir = new File(userHome, APP_WORKING_DIR); |
| | | read(); |
| | | } |
| | | } |
| | |
| | | @Test |
| | | void repoCacheTest() { |
| | | ConfigurationHandler configHandler = ConfigurationHandler.getInstance(); |
| | | configHandler.read(); |
| | | Configuration config = ConfigurationHandler.getConfiguration(); |
| | | if (config.getRepoConfigs().size() == 0) { |
| | | log.info("No repos configured. Please configure repos first in: " + configHandler.getConfigFile().getAbsolutePath()); |
| | |
| | | File origConfigFile = new File(System.getProperty("user.home"), ".borgbutler-orig.json"); |
| | | FileUtils.copyFile(ConfigurationHandler.getInstance().getConfigFile(), origConfigFile); |
| | | Configuration configuration = ConfigurationHandler.getConfiguration(); |
| | | ConfigurationHandler.getInstance().read(); |
| | | ConfigurationHandler.getInstance().write(); |
| | | FileUtils.copyFile(origConfigFile, ConfigurationHandler.getInstance().getConfigFile()); |
| | | } |
| New file |
| | |
| | | package de.micromata.borgbutler.server.rest; |
| | | |
| | | import de.micromata.borgbutler.cache.ButlerCache; |
| | | import de.micromata.borgbutler.json.JsonUtils; |
| | | import de.micromata.borgbutler.json.borg.Repository; |
| | | import org.apache.commons.collections4.CollectionUtils; |
| | | |
| | | import javax.ws.rs.GET; |
| | | import javax.ws.rs.Path; |
| | | import javax.ws.rs.Produces; |
| | | import javax.ws.rs.QueryParam; |
| | | import javax.ws.rs.core.MediaType; |
| | | import java.util.List; |
| | | |
| | | @Path("/repos") |
| | | public class ReposRest { |
| | | @GET |
| | | @Path("refresh") |
| | | @Produces(MediaType.TEXT_PLAIN) |
| | | /** |
| | | * Reloads all templates on the server. |
| | | * @return "OK" |
| | | */ |
| | | public String refresh() { |
| | | ButlerCache.getInstance().getRepoInfoCache().clearAndReset(); |
| | | return "OK"; |
| | | } |
| | | |
| | | @GET |
| | | @Path("repo") |
| | | @Produces(MediaType.APPLICATION_JSON) |
| | | /** |
| | | * |
| | | * @param id |
| | | * @param prettyPrinter If true then the json output will be in pretty format. |
| | | * @see JsonUtils#toJson(Object, boolean) |
| | | */ |
| | | public String getTemplate(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) { |
| | | Repository repository = ButlerCache.getInstance().getRepository(id); |
| | | return JsonUtils.toJson(repository, prettyPrinter); |
| | | } |
| | | |
| | | @GET |
| | | @Path("list") |
| | | @Produces(MediaType.APPLICATION_JSON) |
| | | /** |
| | | * |
| | | * @param prettyPrinter If true then the json output will be in pretty format. |
| | | * @see JsonUtils#toJson(Object, boolean) |
| | | */ |
| | | public String getList(@QueryParam("prettyPrinter") boolean prettyPrinter) { |
| | | List<Repository> repositories = ButlerCache.getInstance().getAllRepositories(); |
| | | if (CollectionUtils.isEmpty(repositories)) { |
| | | return ""; |
| | | } |
| | | return JsonUtils.toJson(repositories, prettyPrinter); |
| | | } |
| | | } |
| | |
| | | Rest Services |
| | | </PageHeader> |
| | | <h3> |
| | | Repositories |
| | | </h3> |
| | | <ul> |
| | | <li><RestUrlLink service='repos/list'/></li> |
| | | </ul> |
| | | <h3> |
| | | Config |
| | | </h3> |
| | | <ul> |
| New file |
| | |
| | | import React from 'react'; |
| | | import {Link} from 'react-router-dom'; |
| | | import {Card, CardBody, CardFooter, CardHeader} from 'reactstrap'; |
| | | import {formatDateTime} from "../../../utilities/global"; |
| | | |
| | | class RepoCard extends React.Component { |
| | | |
| | | buildItem = (label, content) => { |
| | | return <li className="list-group-item">{label}{content.map((line, index) => { |
| | | return <div className="card-list-entry" key={index}>{line[0]} <span |
| | | className={`card-list-entry-value ${line[2]}`}>{line[1]}</span> |
| | | </div>; |
| | | })}</li>; |
| | | } |
| | | |
| | | render = () => { |
| | | const repo = this.props.repo; |
| | | let repoId = repo.id ? repo.id : repo.name; |
| | | let content = [[Name, repo.name, 'name']]; |
| | | let repoText = this.buildItem(null, content); |
| | | |
| | | return <React.Fragment> |
| | | <Card tag={Link} to={`/repos/${repo.primaryKey}`} outline color="success" className={'repo'} |
| | | style={{backgroundColor: '#fff'}}> |
| | | <CardHeader>{repoId}</CardHeader> |
| | | <CardBody> |
| | | <ul className="list-group list-group-flush"> |
| | | {repoText} |
| | | </ul> |
| | | </CardBody> |
| | | <CardFooter><span className={'lastModified'}>{formatDateTime(repo.lastModified)}</span></CardFooter> |
| | | </Card> |
| | | </React.Fragment> |
| | | }; |
| | | |
| | | constructor(props) { |
| | | super(props); |
| | | |
| | | this.buildItem = this.buildItem.bind(this); |
| | | } |
| | | } |
| | | |
| | | export default repoCard; |
| New file |
| | |
| | | a.card-link { |
| | | color: #111; |
| | | } |
| | | |
| | | a.card-link:hover { |
| | | text-decoration: none; |
| | | } |
| | | |
| | | a.card.template { |
| | | margin: 5pt; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | a.card.template div.card-footer { |
| | | color: #666; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | a.card.template:hover div.card-body, |
| | | a.card.template:hover div.card-body li.list-group-item { |
| | | background-color: #f5f5f5; |
| | | } |
| | | |
| | | a.card.template div.card-list-entry { |
| | | color: grey; |
| | | font-size: 80%; |
| | | } |
| | | |
| | | a.card.template div.card-list-entry .card-list-entry-value { |
| | | font-style: italic; |
| | | } |
| | | |
| | | a.card.template div.card-list-entry .filename { |
| | | font-family: monospace; |
| | | } |
| | | |
| | | a.card.template div.card-footer .lastModified { |
| | | float:right; |
| | | } |
| | | |
| | | a.card.template:hover div.card-header, |
| | | a.card.template:hover div.card-footer { |
| | | background-color: #eeee; |
| | | } |
| New file |
| | |
| | | import React from 'react' |
| | | import './RepoListView.css'; |
| | | import {CardDeck} from 'reactstrap'; |
| | | import {PageHeader} from '../../general/BootstrapComponents'; |
| | | import {getRestServiceUrl} from '../../../utilities/global'; |
| | | import ErrorAlert from '../../general/ErrorAlert'; |
| | | import TemplateCard from './TemplateCard'; |
| | | import {IconRefresh} from "../../general/IconComponents"; |
| | | import I18n from "../../general/translation/I18n"; |
| | | |
| | | class RepoListView extends React.Component { |
| | | |
| | | |
| | | path = getRestServiceUrl('repos'); |
| | | state = { |
| | | isFetching: false |
| | | }; |
| | | |
| | | componentDidMount = () => { |
| | | this.fetchTemplates(); |
| | | }; |
| | | |
| | | fetchTemplates = () => { |
| | | this.setState({ |
| | | isFetching: true, |
| | | failed: false, |
| | | definitions: undefined, |
| | | templates: undefined |
| | | }); |
| | | fetch(`${this.path}/list`, { |
| | | method: 'GET', |
| | | headers: { |
| | | 'Accept': 'application/json' |
| | | } |
| | | }) |
| | | .then(response => response.json()) |
| | | .then(json => { |
| | | const definitions = json.templateDefinitions.reduce((accumulator, current) => ({ |
| | | ...accumulator, |
| | | [current.refId]: current |
| | | }), {}); |
| | | |
| | | const templates = json.templates.map(template => { |
| | | if (typeof template.templateDefinition === 'object') { |
| | | //console.log('refId: ' + template.templateDefinition.refId + ', templateDefinition: ' + JSON.stringify(template.templateDefinition)) |
| | | definitions[template.templateDefinition.refId] = template.templateDefinition; |
| | | template.templateDefinition = template.templateDefinition.refId; |
| | | } |
| | | |
| | | return { |
| | | id: template.id, |
| | | primaryKey: template.fileDescriptor.primaryKey, |
| | | filename: template.fileDescriptor.filename, |
| | | lastModified: template.fileDescriptor.lastModified, |
| | | templateDefinitionId: template.templateDefinitionId, |
| | | templateDefinition: template.templateDefinition |
| | | }; |
| | | }); |
| | | |
| | | this.setState({ |
| | | isFetching: false, |
| | | definitions, templates |
| | | }) |
| | | }) |
| | | .catch(() => this.setState({isFetching: false, failed: true})); |
| | | }; |
| | | |
| | | render = () => { |
| | | let content = undefined; |
| | | |
| | | if (this.state.isFetching) { |
| | | |
| | | content = <i>Loading...</i>; |
| | | |
| | | } else if (this.state.failed) { |
| | | |
| | | content = <ErrorAlert |
| | | title={'Cannot load Templates'} |
| | | description={'Something went wrong during contacting the rest api.'} |
| | | action={{ |
| | | handleClick: this.fetchTemplates, |
| | | title: 'Try again' |
| | | }} |
| | | />; |
| | | |
| | | } else if (this.state.templates) { |
| | | |
| | | content = <React.Fragment> |
| | | <div |
| | | className={'btn btn-outline-primary refresh-button-right'} |
| | | onClick={this.fetchTemplates} |
| | | > |
| | | <IconRefresh/> |
| | | </div> |
| | | <CardDeck> |
| | | {this.state.templates.map(template => { |
| | | const definition = this.state.definitions[template.templateDefinition]; |
| | | |
| | | return <TemplateCard |
| | | key={template.primaryKey} |
| | | template={template} |
| | | definition={definition} |
| | | />; |
| | | })} |
| | | </CardDeck> |
| | | </React.Fragment>; |
| | | |
| | | } |
| | | |
| | | return <React.Fragment> |
| | | <PageHeader> |
| | | <I18n name={'templates'}/> |
| | | </PageHeader> |
| | | {content} |
| | | </React.Fragment>; |
| | | }; |
| | | |
| | | constructor(props) { |
| | | super(props); |
| | | |
| | | this.fetchTemplates = this.fetchTemplates.bind(this); |
| | | } |
| | | } |
| | | |
| | | export default RepoListView; |