mirror of https://github.com/micromata/borgbackup-butler.git

...
Kai Reinhard
10.01.2018 5c6ef91b43f0b0cc225b1a5b8abdc08b670a9317
...
4 files added
10 files modified
344 ■■■■■ changed files
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/AbstractCache.java 14 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/AbstractElementsCache.java 6 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ArchiveListCache.java 6 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java 25 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/RepoInfoCache.java 6 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/RepoListCache.java 6 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java 3 ●●●● patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java 1 ●●●● patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/config/ConfigHandlerTest.java 1 ●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java 58 ●●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/develop/RestServices.jsx 6 ●●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/repos/RepoCard.jsx 43 ●●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/repos/RepoListView.css 44 ●●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/repos/RepoListView.jsx 125 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/AbstractCache.java
@@ -46,6 +46,20 @@
        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;
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/AbstractElementsCache.java
@@ -32,11 +32,11 @@
    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!).
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ArchiveListCache.java
@@ -20,17 +20,17 @@
    }
    @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);
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -5,6 +5,8 @@
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;
@@ -40,6 +42,29 @@
        }
    }
    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;
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/RepoInfoCache.java
@@ -20,17 +20,17 @@
    }
    @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);
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/RepoListCache.java
@@ -20,17 +20,17 @@
    }
    @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);
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java
@@ -29,7 +29,7 @@
        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);
@@ -68,5 +68,6 @@
        configFile = new File(userHome, CONFIG_FILENAME);
        backupConfigFile = new File(userHome, CONFIG_BACKUP_FILENAME);
        workingDir = new File(userHome, APP_WORKING_DIR);
        read();
    }
}
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java
@@ -23,7 +23,6 @@
    @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());
borgbutler-core/src/test/java/de/micromata/borgbutler/config/ConfigHandlerTest.java
@@ -12,7 +12,6 @@
        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());
    }
borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
New file
@@ -0,0 +1,58 @@
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);
    }
}
borgbutler-webapp/src/components/views/develop/RestServices.jsx
@@ -93,6 +93,12 @@
                    Rest Services
                </PageHeader>
                <h3>
                    Repositories
                </h3>
                <ul>
                    <li><RestUrlLink service='repos/list'/></li>
                </ul>
                <h3>
                    Config
                </h3>
                <ul>
borgbutler-webapp/src/components/views/repos/RepoCard.jsx
New file
@@ -0,0 +1,43 @@
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;
borgbutler-webapp/src/components/views/repos/RepoListView.css
New file
@@ -0,0 +1,44 @@
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;
}
borgbutler-webapp/src/components/views/repos/RepoListView.jsx
New file
@@ -0,0 +1,125 @@
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;