diff --git a/.gitignore b/.gitignore
index 490d3c786f55eae73d4a4fb660cb4c866515299e..407747941db2dff5b836e61cd0abf1ff45aa3ed8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,4 +109,4 @@ venv.bak/
## mypy
.mypy_cache/
-reforis_static/reforis_schnapps/js/app.min.js
+reforis_static/reforis_snapshots/js/app.min.js
diff --git a/MANIFEST.in b/MANIFEST.in
index e59109ee7f25ea74e707c9baaa507a2a62114d86..eb4e2287ac2c554c4aec6eff850960e48ca23eb4 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1 @@
-recursive-include reforis_static/schnapps *
+recursive-include reforis_static/snapshots *
diff --git a/Makefile b/Makefile
index d56be0d2ed5412133e7348102715be6255afe94f..a81836715dbe1f132cae96cf652ac8628004c71b 100644
--- a/Makefile
+++ b/Makefile
@@ -5,11 +5,11 @@
.PHONY: all venv prepare-dev install install-js install-local-reforis watch-js build-js lint lint-js lint-js-fix lint-web test test-js test-web test-js-update-snapshots create-messages init-langs update-messages compile-messages clean
-DEV_PYTHON=python3.7
-ROUTER_PYTHON=python3.6
VENV_NAME?=venv
VENV_BIN=$(shell pwd)/$(VENV_NAME)/bin
+PYTHON=python3
+
JS_DIR=./js
LANGS = cs da de el en fi fo fr hr hu it ja ko lt nb nb_NO nl pl ro ru sk sv
@@ -40,29 +40,29 @@ all:
venv: $(VENV_NAME)/bin/activate
$(VENV_NAME)/bin/activate: setup.py
- test -d $(VENV_NAME) || $(DEV_PYTHON) -m virtualenv -p $(DEV_PYTHON) $(VENV_NAME)
+ test -d $(VENV_NAME) || $(PYTHON) -m virtualenv -p $(PYTHON) $(VENV_NAME)
# Some problem in latest version of setuptools during extracting translations.
- $(VENV_BIN)/$(DEV_PYTHON) -m pip install -U pip setuptools==39.1.0
- $(VENV_BIN)/$(DEV_PYTHON) -m pip install -e .[devel]
+ $(VENV_BIN)/$(PYTHON) -m pip install -U pip setuptools==39.1.0
+ $(VENV_BIN)/$(PYTHON) -m pip install -e .[devel]
touch $(VENV_NAME)/bin/activate
prepare-env:
which npm || curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
which npm || sudo apt install -y nodejs
- which $(DEV_PYTHON) || sudo apt install -y $(DEV_PYTHON) $(DEV_PYTHON)-pip
- which virtualenv || sudo $(DEV_PYTHON) -m pip install virtualenv
+ which $(PYTHON) || sudo apt install -y $(PYTHON) $(PYTHON)-pip
+ which virtualenv || sudo $(PYTHON) -m pip install virtualenv
prepare-dev:
cd $(JS_DIR); npm install
make venv
install:
- $(ROUTER_PYTHON) -m pip install -e .
- ln -sf /tmp/reforis-schnapps/reforis_static/reforis_schnapps /tmp/reforis/reforis_static/
+ $(PYTHON) -m pip install -e .
+ ln -sf /tmp/reforis-snapshots/reforis_static/reforis_snapshots /tmp/reforis/reforis_static/
/etc/init.d/lighttpd restart
install-js: js/package.json
cd $(JS_DIR); npm install --save-dev
install-local-reforis:
- $(VENV_BIN)/$(DEV_PYTHON) -m pip install -e ../reforis
+ $(VENV_BIN)/$(PYTHON) -m pip install -e ../reforis
watch-js:
cd $(JS_DIR); npm run-script watch
@@ -75,33 +75,33 @@ lint-js:
lint-js-fix:
cd $(JS_DIR); npm run lint:fix
lint-web: venv
- $(VENV_BIN)/$(DEV_PYTHON) -m pylint --rcfile=pylintrc reforis_schnapps
- $(VENV_BIN)/$(DEV_PYTHON) -m pycodestyle --config=pycodestyle reforis_schnapps
+ $(VENV_BIN)/$(PYTHON) -m pylint --rcfile=pylintrc reforis_snapshots
+ $(VENV_BIN)/$(PYTHON) -m pycodestyle --config=pycodestyle reforis_snapshots
test: test-js test-web
test-js:
cd $(JS_DIR); npm test
test-web: venv
- $(VENV_BIN)/$(DEV_PYTHON) -m pytest -vv tests
+ $(VENV_BIN)/$(PYTHON) -m pytest -vv tests
test-js-update-snapshots:
cd $(JS_DIR); npm test -- -u
create-messages:
- $(VENV_BIN)/pybabel extract -F babel.cfg -o ./reforis_schnapps/translations/messages.pot .
+ $(VENV_BIN)/pybabel extract -F babel.cfg -o ./reforis_snapshots/translations/messages.pot .
init-langs: create-messages
for lang in $(LANGS); do \
$(VENV_BIN)/pybabel init \
- -i reforis_schnapps/translations/messages.pot \
- -d reforis_schnapps/translations/ -l $$lang \
+ -i reforis_snapshots/translations/messages.pot \
+ -d reforis_snapshots/translations/ -l $$lang \
; done
update-messages:
- $(VENV_BIN)/pybabel update -i ./reforis_schnapps/translations/messages.pot -d ./reforis/translations
+ $(VENV_BIN)/pybabel update -i ./reforis_snapshots/translations/messages.pot -d ./reforis_snapshots/translations
compile-messages:
- $(VENV_BIN)/pybabel compile -f -d ./reforis_schnapps/translations
+ $(VENV_BIN)/pybabel compile -f -d ./reforis_snapshots/translations
clean:
find . -name '*.pyc' -exec rm -f {} +
rm -rf $(VENV_NAME) *.eggs *.egg-info dist build .cache
rm -rf dist build *.egg-info
- rm -rf $(JS_DIR)/node_modules/ reforis_static/reforis_schnapps/js/app.min.js
- $(ROUTER_PYTHON) -m pip uninstall -y reforis_schnapps
+ rm -rf $(JS_DIR)/node_modules/ reforis_static/reforis_snapshots/js/app.min.js
+ $(PYTHON) -m pip uninstall -y reforis_snapshots
diff --git a/README.md b/README.md
index 29489ceee07eab7f9d9f3480bccca8344a30f4a9..5c921b5a18143f88f1178cc53139b83be16079ef 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# reForis Schnapps plugin
+# reForis Snapshots plugin
To learn more about the plugin system see documentation of [reForis](https://gitlab.labs.nic.cz/turris/reforis).
diff --git a/babel.cfg b/babel.cfg
index ccb108427c655e848d22e95bfa7daee9070a8c9b..aadf334f55e570ed07fcb3511236de49936bf363 100644
--- a/babel.cfg
+++ b/babel.cfg
@@ -1,2 +1,2 @@
-[python: reforis_schnapps/**.py]
+[python: reforis_snapshots/**.py]
[javascript: js/src/**.js]
diff --git a/js/package-lock.json b/js/package-lock.json
index b2853ff6ee38b1c05dc7a3726da7df817e1bbba9..42eda3fbea4f8a3d9dc0d3f1b9beda59a1c58ffc 100644
--- a/js/package-lock.json
+++ b/js/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "reforis_schnapps",
+ "name": "reforis_snapshots",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
diff --git a/js/package.json b/js/package.json
index b286209682d9db782c848aec9e2122cb4c2a442a..71866bbb4a33bee7a575d5c380c1843737684993 100644
--- a/js/package.json
+++ b/js/package.json
@@ -1,5 +1,5 @@
{
- "name": "reforis_schnapps",
+ "name": "reforis_snapshots",
"author": "CZ.NIC, z.s.p.o.",
"license": "GPL-3.0",
"version": "0.1.0",
diff --git a/js/src/API.js b/js/src/API.js
index a66778fee7b0d30d14c41913e627f35d7a1f5aea..e68331db26242c01cab7565b504bbfd4961b3a90 100644
--- a/js/src/API.js
+++ b/js/src/API.js
@@ -7,11 +7,11 @@
import { REFORIS_URL_PREFIX } from "foris";
-const API_URL_PREFIX = `${REFORIS_URL_PREFIX}/schnapps/api`;
+const API_URL_PREFIX = `${REFORIS_URL_PREFIX}/snapshots/api`;
const API_URLs = new Proxy(
{
- example: "/example",
+ snapshots: "/snapshots",
},
{
get: (target, name) => `${API_URL_PREFIX}${target[name]}`,
diff --git a/js/src/app.js b/js/src/app.js
index 6e4c8406b3477e94fc0bf976c9052182e759c933..bc2abc3a028fd693d01d5a96888d7466d06d8a09 100644
--- a/js/src/app.js
+++ b/js/src/app.js
@@ -5,14 +5,14 @@
* See /LICENSE for more information.
*/
-import Schnapps from "./schnapps/Schnapps";
+import Snapshots from "./snapshots/Snapshots";
-const SchnappsPlugin = {
- name: _("Schnapps"),
+const SnapshotsPlugin = {
+ name: _("Snapshots"),
submenuId: "administration",
weight: 100,
- path: "/schnapps",
- component: Schnapps,
+ path: "/snapshots",
+ component: Snapshots,
};
-ForisPlugins.push(SchnappsPlugin);
+ForisPlugins.push(SnapshotsPlugin);
diff --git a/js/src/schnapps/Schnapps.js b/js/src/schnapps/Schnapps.js
deleted file mode 100644
index 537e5e16bd56e3c46346aa2e75c7c2f3b822f97f..0000000000000000000000000000000000000000
--- a/js/src/schnapps/Schnapps.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
- *
- * This is free software, licensed under the GNU General Public License v3.
- * See /LICENSE for more information.
- */
-
-import React, { useEffect } from "react";
-
-import { useAPIGet } from "foris";
-
-import API_URLs from "API";
-
-export default function Schnapps() {
- const [, getExample] = useAPIGet(API_URLs.example);
- useEffect(() => {
- getExample();
- }, [getExample]);
-
- return (
- <>
-
{_("Schnapps")}
- {_("Add your components here")}
- >
- );
-}
diff --git a/js/src/schnapps/__tests__/Schnapps.test.js b/js/src/schnapps/__tests__/Schnapps.test.js
deleted file mode 100644
index b1169e0c771ef42dfc114df4741e041ef2d0d239..0000000000000000000000000000000000000000
--- a/js/src/schnapps/__tests__/Schnapps.test.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
- *
- * This is free software, licensed under the GNU General Public License v3.
- * See /LICENSE for more information.
- */
-
-import React from "react";
-import mockAxios from "jest-mock-axios";
-import { render } from "foris/testUtils/customTestRender";
-
-import Schnapps from "../Schnapps";
-
-describe("", () => {
- it("should render component", () => {
- const { getByText } = render();
- expect(getByText("Schnapps")).toBeDefined();
- expect(mockAxios.get).toBeCalledWith("/reforis/schnapps/api/example", expect.anything());
- });
-});
diff --git a/js/src/snapshots/CreateSnapshotForm.js b/js/src/snapshots/CreateSnapshotForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..589feeebe65c81a28a985d7667739508c9e58b87
--- /dev/null
+++ b/js/src/snapshots/CreateSnapshotForm.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React, { useEffect } from "react";
+import PropTypes from "prop-types";
+
+import {
+ SubmitButton, SUBMIT_BUTTON_STATES, TextInput, useForm, formFieldsSize,
+} from "foris";
+
+CreateSnapshotForm.propTypes = {
+ createSnapshot: PropTypes.func.isRequired,
+};
+
+export default function CreateSnapshotForm({ createSnapshot }) {
+ const [formState, formChangeHandler, reloadForm] = useForm(validator);
+ const formData = formState.data;
+ const formErrors = formState.errors || {};
+ useEffect(() => {
+ reloadForm({ description: "" });
+ }, [reloadForm]);
+
+ function handleSubmit(event) {
+ event.preventDefault();
+ createSnapshot({ data: { description: formState.data.description } });
+ }
+
+ if (!formData) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+function validator(formData) {
+ if (!formData.description) {
+ return { description: _("Description is required.") };
+ }
+ return undefined;
+}
diff --git a/js/src/snapshots/Snapshots.js b/js/src/snapshots/Snapshots.js
new file mode 100644
index 0000000000000000000000000000000000000000..0277c00a7c28f1de775ea1bef9274aead5f14aed
--- /dev/null
+++ b/js/src/snapshots/Snapshots.js
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React, { useState } from "react";
+import PropTypes from "prop-types";
+
+import { API_STATE, Spinner, ErrorMessage } from "foris";
+
+import {
+ useGetSnapshots, useUpdateSnapshotsOnAdd, useCreateSnapshot, useDeleteSnapshot,
+ useUpdateSnapshotsOnDelete, useRollbackSnapshot, useUpdateSnapshotsOnRollback,
+} from "./hooks";
+import CreateSnapshotForm from "./CreateSnapshotForm";
+import SnapshotsTable from "./SnapshotsTable";
+
+Snapshots.propTypes = {
+ ws: PropTypes.object.isRequired,
+};
+
+export default function Snapshots({ ws }) {
+ const [snapshots, setSnapshots] = useState([]);
+
+ const [getState, getSnapshots] = useGetSnapshots(setSnapshots);
+ useUpdateSnapshotsOnAdd(ws, getSnapshots);
+
+ const [createState, createSnapshot] = useCreateSnapshot();
+
+ const [rollbackState, rollbackSnapshot] = useRollbackSnapshot();
+ useUpdateSnapshotsOnRollback(ws, getSnapshots);
+
+ const [deleteState, deleteSnapshot] = useDeleteSnapshot();
+ useUpdateSnapshotsOnDelete(ws, setSnapshots);
+
+ let componentContent;
+ if (getState === API_STATE.INIT
+ || [getState, createState, rollbackState, deleteState].includes(API_STATE.SENDING)) {
+ componentContent = ;
+ } else if (getState === API_STATE.ERROR) {
+ componentContent = ;
+ } else {
+ componentContent = (
+ <>
+
+ {_("Available Snapshots")}
+
+ >
+ );
+ }
+
+ return (
+ <>
+ {_("Snapshots")}
+ advanced options.") }} />
+ {componentContent}
+ >
+ );
+}
diff --git a/js/src/snapshots/SnapshotsTable.css b/js/src/snapshots/SnapshotsTable.css
new file mode 100644
index 0000000000000000000000000000000000000000..1bf25590daf854b27e3675cdceadcaed01ca8798
--- /dev/null
+++ b/js/src/snapshots/SnapshotsTable.css
@@ -0,0 +1,14 @@
+.snapshots-table td {
+ vertical-align: middle;
+}
+
+@media (max-width: 1400px) {
+ .snapshots-table-delete-icon {
+ display: none;
+ }
+}
+
+.snaphots-table-created-at {
+ text-align: center;
+ min-width: 6rem;
+}
diff --git a/js/src/snapshots/SnapshotsTable.js b/js/src/snapshots/SnapshotsTable.js
new file mode 100644
index 0000000000000000000000000000000000000000..690960aa0fea99e7cf37dc943a65347e78269d75
--- /dev/null
+++ b/js/src/snapshots/SnapshotsTable.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React from "react";
+import PropTypes from "prop-types";
+
+import { Button } from "foris";
+
+import "./SnapshotsTable.css";
+
+const snapshotShape = PropTypes.shape({
+ number: PropTypes.number.isRequired,
+ type: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired,
+ created: PropTypes.string.isRequired,
+ size: PropTypes.string.isRequired,
+});
+
+SnapshotsTable.propTypes = {
+ snapshots: PropTypes.arrayOf(snapshotShape).isRequired,
+ rollbackSnapshot: PropTypes.func.isRequired,
+ deleteSnapshot: PropTypes.func.isRequired,
+};
+
+export default function SnapshotsTable({ snapshots, rollbackSnapshot, deleteSnapshot }) {
+ return (
+
+
+
+
+ {_("Number")} |
+ {_("Type")} |
+ {_("Description")} |
+ {_("Created at")} |
+ {_("Size")} |
+ |
+ |
+
+
+
+ {snapshots.map(
+ (snapshot) => (
+ rollbackSnapshot(snapshot.number)}
+ deleteSnapshot={() => deleteSnapshot({ suffix: snapshot.number })}
+ />
+ ),
+ )}
+
+
+
+ );
+}
+
+SnapshotRow.propTypes = {
+ snapshot: snapshotShape.isRequired,
+ rollbackSnapshot: PropTypes.func.isRequired,
+ deleteSnapshot: PropTypes.func.isRequired,
+};
+
+function SnapshotRow({ snapshot, rollbackSnapshot, deleteSnapshot }) {
+ return (
+
+ {snapshot.number} |
+ {snapshot.type} |
+ {snapshot.description} |
+ {snapshot.created} |
+ {snapshot.size} |
+
+
+ |
+
+
+ |
+
+ );
+}
diff --git a/js/src/snapshots/__tests__/Snapshots.test.js b/js/src/snapshots/__tests__/Snapshots.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..98eda682ee234305a3efaf050b30fe37efdbce26
--- /dev/null
+++ b/js/src/snapshots/__tests__/Snapshots.test.js
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import React from "react";
+import mockAxios from "jest-mock-axios";
+import {
+ render, wait, getAllByText, queryByText, getByText, getByLabelText, queryByRole, getByRole,
+ act, fireEvent,
+} from "foris/testUtils/customTestRender";
+import { mockJSONError } from "foris/testUtils/network";
+import { mockSetAlert } from "foris/testUtils/alertContextMock";
+import { WebSockets } from "foris";
+
+import Snapshots from "../Snapshots";
+
+const SNAPSHOTS = [
+ {
+ number: 1, type: "single", description: "Whatever", created: "2020-01-30T10:27:34Z", size: "808 kB",
+ },
+ {
+ number: 2, type: "rollback", description: "Something", created: "2020-01-31T10:27:34Z", size: "909 kB",
+ },
+];
+
+function creteSnapshot(number, description) {
+ return {
+ number, type: "single", description, created: "2020-01-30T10:27:34Z", size: "808 kB",
+ };
+}
+
+describe("", () => {
+ let container;
+ let webSockets;
+
+ beforeEach(() => {
+ webSockets = new WebSockets();
+ ({ container } = render());
+ });
+
+ it("should render spinner", () => {
+ expect(container).toMatchSnapshot();
+ });
+
+ it("should render table", async () => {
+ expect(mockAxios.get).toBeCalledWith(
+ "/reforis/snapshots/api/snapshots", expect.anything(),
+ );
+ mockAxios.mockResponse({ data: SNAPSHOTS });
+ await wait(() => getByText(container, SNAPSHOTS[0].description));
+ expect(container).toMatchSnapshot();
+ });
+
+ it("should handle GET error", async () => {
+ mockJSONError();
+ await wait(() => expect(
+ getByText(container, "An error occurred while fetching data."),
+ ).toBeTruthy());
+ });
+
+ it("should display spinner while snapshot is being added", async () => {
+ // Prepare table
+ mockAxios.mockResponse({ data: SNAPSHOTS });
+ // Initially there's no spinner
+ await wait(() => expect(
+ queryByRole(container, "status"),
+ ).toBeNull());
+
+ // Create new snapshot
+ act(() => webSockets.dispatch(
+ { module: "schnapps", action: "create", data: {} },
+ ));
+ // Spinner should appear
+ await wait(() => getByRole(container, "status"));
+ });
+
+ it("should display spinner when snapshot is being removed", async () => {
+ // Prepare table
+ mockAxios.mockResponse({ data: SNAPSHOTS });
+ // Initially there's no spinner
+ await wait(() => expect(
+ queryByRole(container, "status"),
+ ).toBeNull());
+
+ // Delete device
+ fireEvent.click(getAllByText(container, "Delete")[0]);
+ // Spinner should appear
+ await wait(() => getByRole(container, "status"));
+ });
+
+ it("should display spinner while rolling back to snapshot", async () => {
+ // Prepare table
+ mockAxios.mockResponse({ data: SNAPSHOTS });
+ // Initially there's no spinner
+ await wait(() => expect(
+ queryByRole(container, "status"),
+ ).toBeNull());
+
+ // Delete device
+ fireEvent.click(getAllByText(container, "Rollback")[0]);
+ // Spinner should appear
+ await wait(() => getByRole(container, "status"));
+ });
+
+ describe("with table prepared", () => {
+ beforeEach(async () => {
+ // Prepare table
+ mockAxios.mockResponse({ data: SNAPSHOTS });
+ await wait(() => getByText(container, SNAPSHOTS[0].description));
+ });
+
+ it("should refresh table after new snapshot is added", async () => {
+ // Create new snapshot
+ const newDescription = "Nothing";
+ act(() => webSockets.dispatch(
+ { module: "schnapps", action: "create", data: {} },
+ ));
+ expect(mockAxios.get).toHaveBeenNthCalledWith(2, "/reforis/snapshots/api/snapshots", expect.anything());
+ mockAxios.mockResponse({ data: [...SNAPSHOTS, creteSnapshot(3, newDescription)] });
+ // New snapshot should appear
+ await wait(() => getByText(container, newDescription));
+ });
+
+ it("should refresh table after snapshot is removed", async () => {
+ // Delete snapshot
+ const deletedNumber = SNAPSHOTS[0].number;
+ fireEvent.click(getAllByText(container, "Delete")[0]);
+ expect(mockAxios.delete).toBeCalledWith(
+ `/reforis/snapshots/api/snapshots/${deletedNumber}`, expect.anything(),
+ );
+ mockAxios.mockResponse({ data: {} });
+
+ act(() => webSockets.dispatch(
+ { module: "schnapps", action: "delete", data: { number: deletedNumber } },
+ ));
+ // Device should disappear
+ await wait(() => expect(queryByText(container, SNAPSHOTS[0].description)).toBeNull());
+ });
+
+ it("should handle error on removal", async () => {
+ // Delete device
+ fireEvent.click(getAllByText(container, "Delete")[0]);
+ // Handle error
+ const errorMessage = "API didn't handle this well";
+ mockJSONError(errorMessage);
+ await wait(() => {
+ expect(mockSetAlert).toHaveBeenCalledWith(errorMessage);
+ });
+ });
+
+ it("should refresh table after rolling back to snapshot", async () => {
+ // Rollback to snapshot
+ const rollbackToNumber = SNAPSHOTS[0].number;
+ fireEvent.click(getAllByText(container, "Rollback")[0]);
+ expect(mockAxios.put).toBeCalledWith(
+ `/reforis/snapshots/api/snapshots/${rollbackToNumber}/rollback`, undefined, expect.anything(),
+ );
+ mockAxios.mockResponse({ data: {} });
+
+ act(() => webSockets.dispatch(
+ { module: "schnapps", action: "rollback", data: {} },
+ ));
+
+ const newDescription = `Rollback to ${rollbackToNumber}`;
+ expect(mockAxios.get).toHaveBeenNthCalledWith(2, "/reforis/snapshots/api/snapshots", expect.anything());
+ mockAxios.mockResponse({ data: [...SNAPSHOTS, creteSnapshot(3, newDescription)] });
+ // New snapshot should appear
+ await wait(() => getByText(container, newDescription));
+ });
+
+ it("should handle error on rollback", async () => {
+ // Delete device
+ fireEvent.click(getAllByText(container, "Rollback")[0]);
+ // Handle error
+ const errorMessage = "API didn't handle this well";
+ mockJSONError(errorMessage);
+ await wait(() => {
+ expect(mockSetAlert).toHaveBeenCalledWith(errorMessage);
+ });
+ });
+
+ describe("create snapshot form", () => {
+ let descriptionInput;
+ let submitButton;
+
+ beforeEach(async () => {
+ descriptionInput = getByLabelText(container, "Description");
+ submitButton = getByText(container, "Save");
+ });
+
+ it("should send request to create new snapshot", async () => {
+ // Prepare form
+ const description = "Discovery";
+ expect(submitButton.disabled).toBe(true);
+ fireEvent.change(descriptionInput, { target: { value: description } });
+ expect(submitButton.disabled).toBe(false);
+
+ // Create new snapshot
+ fireEvent.click(submitButton);
+ expect(mockAxios.post).toBeCalledWith(
+ "/reforis/snapshots/api/snapshots",
+ { description },
+ expect.anything(),
+ );
+ });
+
+ it("should handle API error on creating snapshot", async () => {
+ // Request new snapshot
+ fireEvent.change(descriptionInput, { target: { value: "qwe" } });
+ fireEvent.click(submitButton);
+
+ // Handle error
+ const errorMessage = "API didn't handle this well";
+ mockJSONError(errorMessage);
+ await wait(() => {
+ expect(mockSetAlert).toHaveBeenCalledWith(errorMessage);
+ });
+ });
+
+ it("should validate new snapshot description", async () => {
+ fireEvent.change(descriptionInput, { target: { value: "" } });
+ expect(getByText(container, "Description is required.")).toBeDefined();
+ expect(submitButton.disabled).toBe(true);
+ });
+ });
+ });
+});
diff --git a/js/src/snapshots/__tests__/__snapshots__/Snapshots.test.js.snap b/js/src/snapshots/__tests__/__snapshots__/Snapshots.test.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..7a3f545fb4dabb9d7264f7d65d2846de28813f47
--- /dev/null
+++ b/js/src/snapshots/__tests__/__snapshots__/Snapshots.test.js.snap
@@ -0,0 +1,258 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render spinner 1`] = `
+
+
+ Snapshots
+
+
+ This is an addition to Schnapps command-line utility which can provide more
+
+ advanced options
+
+ .
+
+
+
+`;
+
+exports[` should render table 1`] = `
+
+
+ Snapshots
+
+
+ This is an addition to Schnapps command-line utility which can provide more
+
+ advanced options
+
+ .
+
+
+
+ Available Snapshots
+
+
+
+
+
+
+ Number
+ |
+
+ Type
+ |
+
+ Description
+ |
+
+ Created at
+ |
+
+ Size
+ |
+ |
+ |
+
+
+
+
+
+ 1
+ |
+
+ single
+ |
+
+ Whatever
+ |
+
+ 2020-01-30T10:27:34Z
+ |
+
+ 808 kB
+ |
+
+
+ |
+
+
+ |
+
+
+
+ 2
+ |
+
+ rollback
+ |
+
+ Something
+ |
+
+ 2020-01-31T10:27:34Z
+ |
+
+ 909 kB
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+`;
diff --git a/js/src/snapshots/hooks.js b/js/src/snapshots/hooks.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2d15e470ae9aaafc0d6d2785652be8b072ca482
--- /dev/null
+++ b/js/src/snapshots/hooks.js
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
+ *
+ * This is free software, licensed under the GNU General Public License v3.
+ * See /LICENSE for more information.
+ */
+
+import { useEffect, useCallback } from "react";
+
+import {
+ useAlert, useWSForisModule, useAPIGet, useAPIPost, useAPIDelete, useAPIPut, API_STATE,
+} from "foris";
+
+import API_URLs from "API";
+
+export function useGetSnapshots(setSnapshots) {
+ const [getSnapshotsResponse, getSnapshots] = useAPIGet(API_URLs.snapshots);
+
+ // Initial data fetch
+ useEffect(() => {
+ getSnapshots();
+ }, [getSnapshots]);
+
+ // Update snapshots data
+ useEffect(() => {
+ if (getSnapshotsResponse.state === API_STATE.SUCCESS) {
+ setSnapshots(getSnapshotsResponse.data);
+ }
+ }, [getSnapshotsResponse, setSnapshots]);
+
+ return [getSnapshotsResponse.state, getSnapshots];
+}
+
+export function useUpdateSnapshotsOnAdd(ws, getSnapshots) {
+ const [addNotification] = useWSForisModule(ws, "schnapps", "create");
+ useEffect(() => {
+ if (!addNotification) {
+ return;
+ }
+ getSnapshots();
+ }, [addNotification, getSnapshots]);
+}
+
+export function useCreateSnapshot() {
+ const [setAlert] = useAlert();
+
+ // Handle API request
+ const [postSnapshotResponse, postSnapshot] = useAPIPost(`${API_URLs.snapshots}`);
+ useEffect(() => {
+ if (postSnapshotResponse.state === API_STATE.ERROR) {
+ setAlert(postSnapshotResponse.data);
+ }
+ }, [postSnapshotResponse, setAlert]);
+
+ return [postSnapshotResponse.state, postSnapshot];
+}
+
+export function useDeleteSnapshot() {
+ const [setAlert] = useAlert();
+
+ // Handle API request
+ const [deleteSnapshotResponse, deleteSnapshot] = useAPIDelete(`${API_URLs.snapshots}`);
+ useEffect(() => {
+ if (deleteSnapshotResponse.state === API_STATE.ERROR) {
+ setAlert(deleteSnapshotResponse.data);
+ }
+ }, [deleteSnapshotResponse, setAlert]);
+
+ return [deleteSnapshotResponse.state, deleteSnapshot];
+}
+
+export function useUpdateSnapshotsOnDelete(ws, setSnapshots) {
+ const [deleteNotification] = useWSForisModule(ws, "schnapps", "delete");
+
+ // Update devices data
+ const removeSnapshotFromTable = useCallback((number) => {
+ setSnapshots((previousDevices) => {
+ const snapshots = [...previousDevices];
+ const deleteIndex = snapshots.findIndex(
+ (snapshot) => snapshot.number === number,
+ );
+ if (deleteIndex !== -1) {
+ snapshots.splice(deleteIndex, 1);
+ }
+ return snapshots;
+ });
+ }, [setSnapshots]);
+
+ useEffect(() => {
+ if (!deleteNotification) {
+ return;
+ }
+ removeSnapshotFromTable(deleteNotification.number);
+ }, [removeSnapshotFromTable, deleteNotification]);
+}
+
+export function useRollbackSnapshot() {
+ const [setAlert] = useAlert();
+
+ // Handle API request
+ const [putSnapshotResponse, putSnapshot] = useAPIPut(`${API_URLs.snapshots}`);
+ useEffect(() => {
+ if (putSnapshotResponse.state === API_STATE.ERROR) {
+ setAlert(putSnapshotResponse.data);
+ }
+ }, [putSnapshotResponse, setAlert]);
+
+ function rollbackSnapshot(snapshotNumber) {
+ return putSnapshot({ suffix: `${snapshotNumber}/rollback` });
+ }
+
+ return [putSnapshotResponse.state, rollbackSnapshot];
+}
+
+export function useUpdateSnapshotsOnRollback(ws, getSnapshots) {
+ const [addNotification] = useWSForisModule(ws, "schnapps", "rollback");
+ useEffect(() => {
+ if (!addNotification) {
+ return;
+ }
+ getSnapshots();
+ }, [addNotification, getSnapshots]);
+}
diff --git a/js/webpack.config.js b/js/webpack.config.js
index 81d1d86fa56eb1c337893a1f90c8468d4f55745d..62ca4e3c7f15a5d178532357dd3310d55d507086 100644
--- a/js/webpack.config.js
+++ b/js/webpack.config.js
@@ -14,7 +14,7 @@ module.exports = () => ({
// Build js app to ../reforis_static{python_module_name}/app.min.js
// See https://gitlab.labs.nic.cz/turris/reforis/reforis-distutils/blob/master/reforis_distutils/__init__.py#L11
filename: "app.min.js",
- path: path.join(__dirname, "../reforis_static/reforis_schnapps/js/"),
+ path: path.join(__dirname, "../reforis_static/reforis_snapshots/js/"),
},
resolve: {
modules: [
diff --git a/reforis_schnapps/__init__.py b/reforis_schnapps/__init__.py
deleted file mode 100644
index 6d4480a72e0bb4c01ef652792640a8fa7f808ce1..0000000000000000000000000000000000000000
--- a/reforis_schnapps/__init__.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
-#
-# This is free software, licensed under the GNU General Public License v3.
-# See /LICENSE for more information.
-
-from pathlib import Path
-from http import HTTPStatus
-
-from flask import Blueprint, current_app, jsonify, request
-from flask_babel import gettext as _
-
-from reforis.foris_controller_api.utils import log_error, validate_json, APIError
-
-# pylint: disable=invalid-name
-blueprint = Blueprint(
- 'Schnapps',
- __name__,
- url_prefix='/schnapps/api',
-)
-
-BASE_DIR = Path(__file__).parent
-
-# pylint: disable=invalid-name
-schnapps = {
- 'blueprint': blueprint,
- # Define {python_module_name}/js/app.min.js
- # See https://gitlab.labs.nic.cz/turris/reforis/reforis-distutils/blob/master/reforis_distutils/__init__.py#L11
- 'js_app_path': 'reforis_schnapps/js/app.min.js',
- 'translations_path': BASE_DIR / 'translations',
-}
-
-
-@blueprint.route('/example', methods=['GET'])
-def get_example():
- return jsonify(current_app.backend.perform('example_module', 'example_action'))
-
-
-@blueprint.route('/example', methods=['POST'])
-def post_example():
- validate_json(request.json, {'modules': list})
-
- response = current_app.backend.perform('example_module', 'example_action', request.json)
- if response.get('result') is not True:
- raise APIError(_('Cannot create entity'), HTTPStatus.INTERNAL_SERVER_ERROR)
-
- return jsonify(response), HTTPStatus.CREATED
diff --git a/reforis_snapshots/__init__.py b/reforis_snapshots/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8003c52084d7925670ba4b29fb5114df91a9c381
--- /dev/null
+++ b/reforis_snapshots/__init__.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
+#
+# This is free software, licensed under the GNU General Public License v3.
+# See /LICENSE for more information.
+
+from pathlib import Path
+from http import HTTPStatus
+
+from flask import Blueprint, current_app, jsonify, request
+from flask_babel import gettext as _
+
+from reforis.foris_controller_api.utils import log_error, validate_json, APIError
+
+# pylint: disable=invalid-name
+blueprint = Blueprint(
+ 'Snapshots',
+ __name__,
+ url_prefix='/snapshots/api',
+)
+
+BASE_DIR = Path(__file__).parent
+
+# pylint: disable=invalid-name
+snapshots = {
+ 'blueprint': blueprint,
+ # Define {python_module_name}/js/app.min.js
+ # See https://gitlab.labs.nic.cz/turris/reforis/reforis-distutils/blob/master/reforis_distutils/__init__.py#L11
+ 'js_app_path': 'reforis_snapshots/js/app.min.js',
+ 'translations_path': BASE_DIR / 'translations',
+}
+
+
+@blueprint.route('/snapshots', methods=['GET'])
+def get_snapshots():
+ return jsonify(current_app.backend.perform('schnapps', 'list')['snapshots'])
+
+
+@blueprint.route('/snapshots', methods=['POST'])
+def create_snapshot():
+ validate_json(request.json, {'description': str})
+
+ response = current_app.backend.perform('schnapps', 'create', request.json)
+ if response.get('result') is not True:
+ raise APIError(_('Cannot create snapshot.'), HTTPStatus.INTERNAL_SERVER_ERROR)
+
+ return jsonify(response), HTTPStatus.ACCEPTED
+
+
+@blueprint.route('/snapshots/', methods=['DELETE'])
+def delete_snapshot(snapshot_number):
+ response = current_app.backend.perform('schnapps', 'delete', {'number': snapshot_number})
+ if response.get('result') is not True:
+ raise APIError(_('Cannot delete snapshot.'), HTTPStatus.INTERNAL_SERVER_ERROR)
+
+ return '', HTTPStatus.NO_CONTENT
+
+
+@blueprint.route('/snapshots//rollback', methods=['PUT'])
+def rollback_to_snapshot(snapshot_number):
+ response = current_app.backend.perform('schnapps', 'rollback', {'number': snapshot_number})
+ if response.get('result') is not True:
+ raise APIError(_('Cannot rollback to snapshot.'), HTTPStatus.INTERNAL_SERVER_ERROR)
+
+ return '', HTTPStatus.NO_CONTENT
diff --git a/reforis_schnapps/translations/.gitkeep b/reforis_snapshots/translations/.gitkeep
similarity index 100%
rename from reforis_schnapps/translations/.gitkeep
rename to reforis_snapshots/translations/.gitkeep
diff --git a/reforis_snapshots/translations/cs/LC_MESSAGES/messages.po b/reforis_snapshots/translations/cs/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..2249a44a3922b6aa776519bd8a7fb6a877ec8bc6
--- /dev/null
+++ b/reforis_snapshots/translations/cs/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Czech translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: cs\n"
+"Language-Team: cs \n"
+"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/da/LC_MESSAGES/messages.po b/reforis_snapshots/translations/da/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..db7d47c3113393d84cca3ff2ea99cc91647e8105
--- /dev/null
+++ b/reforis_snapshots/translations/da/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Danish translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: da\n"
+"Language-Team: da \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/de/LC_MESSAGES/messages.po b/reforis_snapshots/translations/de/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..016222a098fd7ce2691fa63c62abac0a3989d692
--- /dev/null
+++ b/reforis_snapshots/translations/de/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# German translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: de\n"
+"Language-Team: de \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/el/LC_MESSAGES/messages.po b/reforis_snapshots/translations/el/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..96726bc227a53d1bfdb17c96869cdf1ab782a00c
--- /dev/null
+++ b/reforis_snapshots/translations/el/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Greek translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: el\n"
+"Language-Team: el \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/en/LC_MESSAGES/messages.po b/reforis_snapshots/translations/en/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..a7228a0f11cd56a297a4a521bdc68f57531b0c4d
--- /dev/null
+++ b/reforis_snapshots/translations/en/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# English translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: en\n"
+"Language-Team: en \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/fi/LC_MESSAGES/messages.po b/reforis_snapshots/translations/fi/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..e31a6438274f9e31ad92ad7b24ae3fe7e0dfad33
--- /dev/null
+++ b/reforis_snapshots/translations/fi/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Finnish translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: fi\n"
+"Language-Team: fi \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/fo/LC_MESSAGES/messages.po b/reforis_snapshots/translations/fo/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..832383f10d9382512448f384cb4b481876d9cfe9
--- /dev/null
+++ b/reforis_snapshots/translations/fo/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Faroese translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: fo\n"
+"Language-Team: fo \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/fr/LC_MESSAGES/messages.po b/reforis_snapshots/translations/fr/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..a62d2ac516e5ed1cca45d9814b17627264bf7573
--- /dev/null
+++ b/reforis_snapshots/translations/fr/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# French translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: fr\n"
+"Language-Team: fr \n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/hr/LC_MESSAGES/messages.po b/reforis_snapshots/translations/hr/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..331b63c9f891e2c326d93835959e341006ed217e
--- /dev/null
+++ b/reforis_snapshots/translations/hr/LC_MESSAGES/messages.po
@@ -0,0 +1,85 @@
+# Croatian translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: hr\n"
+"Language-Team: hr \n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/hu/LC_MESSAGES/messages.po b/reforis_snapshots/translations/hu/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..19a0e040da4774d41a25953b23a56bccce532cd4
--- /dev/null
+++ b/reforis_snapshots/translations/hu/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Hungarian translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: hu\n"
+"Language-Team: hu \n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/it/LC_MESSAGES/messages.po b/reforis_snapshots/translations/it/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..502ecc62264d8e14a4dd37e77aea585324474bcd
--- /dev/null
+++ b/reforis_snapshots/translations/it/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Italian translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: it\n"
+"Language-Team: it \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/ja/LC_MESSAGES/messages.po b/reforis_snapshots/translations/ja/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..3254ba56ab08ad43b0330a7a10cc93897e4b2eab
--- /dev/null
+++ b/reforis_snapshots/translations/ja/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Japanese translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: ja\n"
+"Language-Team: ja \n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/ko/LC_MESSAGES/messages.po b/reforis_snapshots/translations/ko/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..304ff57eecd6e81471855bc2567f932a42eb4dc0
--- /dev/null
+++ b/reforis_snapshots/translations/ko/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Korean translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: ko\n"
+"Language-Team: ko \n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/lt/LC_MESSAGES/messages.po b/reforis_snapshots/translations/lt/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..a1bcba29f6374640da45c659869ae6c7738ecd90
--- /dev/null
+++ b/reforis_snapshots/translations/lt/LC_MESSAGES/messages.po
@@ -0,0 +1,85 @@
+# Lithuanian translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: lt\n"
+"Language-Team: lt \n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"(n%100<10 || n%100>=20) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/messages.pot b/reforis_snapshots/translations/messages.pot
new file mode 100644
index 0000000000000000000000000000000000000000..6d04008d6b22a393c82990e4bf4a19998b43b234
--- /dev/null
+++ b/reforis_snapshots/translations/messages.pot
@@ -0,0 +1,83 @@
+# Translations template for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/nb/LC_MESSAGES/messages.po b/reforis_snapshots/translations/nb/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..1f082a9f0b93e755375b0f974c460d215afb2640
--- /dev/null
+++ b/reforis_snapshots/translations/nb/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Norwegian Bokmål translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: nb\n"
+"Language-Team: nb \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/nb_NO/LC_MESSAGES/messages.po b/reforis_snapshots/translations/nb_NO/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..3f81eea3a71f78f2af4be8aa26036a7f18e76d19
--- /dev/null
+++ b/reforis_snapshots/translations/nb_NO/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Norwegian Bokmål (Norway) translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: nb_NO\n"
+"Language-Team: nb_NO \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/nl/LC_MESSAGES/messages.po b/reforis_snapshots/translations/nl/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..a52e1b90af9e01eb1f1adbad5ffeb19a47258b69
--- /dev/null
+++ b/reforis_snapshots/translations/nl/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Dutch translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: nl\n"
+"Language-Team: nl \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/pl/LC_MESSAGES/messages.po b/reforis_snapshots/translations/pl/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..3f892002cafabfdc89c21301fd93cbf6db918e84
--- /dev/null
+++ b/reforis_snapshots/translations/pl/LC_MESSAGES/messages.po
@@ -0,0 +1,85 @@
+# Polish translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: pl\n"
+"Language-Team: pl \n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && "
+"(n%100<10 || n%100>=20) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/ro/LC_MESSAGES/messages.po b/reforis_snapshots/translations/ro/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..b341645687260074023c0e5e8c0bca2336cc6845
--- /dev/null
+++ b/reforis_snapshots/translations/ro/LC_MESSAGES/messages.po
@@ -0,0 +1,85 @@
+# Romanian translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: ro\n"
+"Language-Team: ro \n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100"
+" < 20)) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/ru/LC_MESSAGES/messages.po b/reforis_snapshots/translations/ru/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..86899ecb05c469f273fefd74ac3f06ef6f30d433
--- /dev/null
+++ b/reforis_snapshots/translations/ru/LC_MESSAGES/messages.po
@@ -0,0 +1,85 @@
+# Russian translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: ru\n"
+"Language-Team: ru \n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/sk/LC_MESSAGES/messages.po b/reforis_snapshots/translations/sk/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..5fb64ecd469a6670d232a9996389799e854d595b
--- /dev/null
+++ b/reforis_snapshots/translations/sk/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Slovak translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: sk\n"
+"Language-Team: sk \n"
+"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_snapshots/translations/sv/LC_MESSAGES/messages.po b/reforis_snapshots/translations/sv/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..8471f4048b73f4d4fc353e7a7959d753f1f703d1
--- /dev/null
+++ b/reforis_snapshots/translations/sv/LC_MESSAGES/messages.po
@@ -0,0 +1,84 @@
+# Swedish translations for PROJECT.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2020.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-01-31 14:58+0100\n"
+"PO-Revision-Date: 2020-01-31 14:58+0100\n"
+"Last-Translator: FULL NAME \n"
+"Language: sv\n"
+"Language-Team: sv \n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.8.0\n"
+
+#: js/src/app.js:11 js/src/snapshots/Snapshots.js:60
+msgid "Snapshots"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:39
+msgid "Create new snapshot"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:41
+#: js/src/snapshots/SnapshotsTable.js:36
+msgid "Description"
+msgstr ""
+
+#: js/src/snapshots/CreateSnapshotForm.js:60
+msgid "Description is required."
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:48
+msgid "Available Snapshots"
+msgstr ""
+
+#: js/src/snapshots/Snapshots.js:61
+msgid ""
+"This is an addition to Schnapps command-line utility which can provide "
+"more advanced options."
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:34
+msgid "Number"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:35
+msgid "Type"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:37
+msgid "Created at"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:38
+msgid "Size"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:39 js/src/snapshots/SnapshotsTable.js:76
+msgid "Rollback"
+msgstr ""
+
+#: js/src/snapshots/SnapshotsTable.js:40 js/src/snapshots/SnapshotsTable.js:82
+msgid "Delete"
+msgstr ""
+
+#: reforis_snapshots/__init__.py:44
+msgid "Cannot create snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:53
+msgid "Cannot delete snapshot."
+msgstr ""
+
+#: reforis_snapshots/__init__.py:62
+msgid "Cannot rollback to snapshot."
+msgstr ""
+
diff --git a/reforis_static/reforis_schnapps/.gitkeep b/reforis_static/reforis_snapshots/.gitkeep
similarity index 100%
rename from reforis_static/reforis_schnapps/.gitkeep
rename to reforis_static/reforis_snapshots/.gitkeep
diff --git a/setup.py b/setup.py
index 30812379648b04680e5792fc01da4b74d40749be..90169ba13265ad846b46fcca4bab6088084c45cb 100644
--- a/setup.py
+++ b/setup.py
@@ -11,12 +11,12 @@ import pathlib
import setuptools
from setuptools.command.build_py import build_py
-NAME = 'reforis_schnapps'
+NAME = 'reforis_snapshots'
BASE_DIR = pathlib.Path(__file__).absolute().parent
-class SchnappsBuild(build_py):
+class SnapshotsBuild(build_py):
def run(self):
# build package
build_py.run(self)
@@ -63,7 +63,7 @@ setuptools.setup(
'git+https://gitlab.labs.nic.cz/turris/reforis/reforis-distutils.git#egg=reforis-distutils',
],
entry_points={
- 'foris.plugins': f'{NAME} = {NAME}:schnapps'
+ 'foris.plugins': f'{NAME} = {NAME}:snapshots'
},
classifiers=[
'Framework :: Flask',
@@ -76,7 +76,7 @@ setuptools.setup(
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
],
cmdclass={
- 'build_py': SchnappsBuild,
+ 'build_py': SnapshotsBuild,
},
zip_safe=False,
)
diff --git a/tests/test_api.py b/tests/test_api.py
index b1a15f95c752c4eb5a8f9e8e93315de1456c6c93..2884e7487f7395e28513e9be3f872b120b05fee2 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -7,22 +7,74 @@ from http import HTTPStatus
from reforis.test_utils import mock_backend_response
-@mock_backend_response({'example_module': {'example_action': {'key': 'value'}}})
-def test_get_example(client):
- response = client.get('/schnapps/api/example')
+SNAPSHOTS_URL = '/snapshots/api/snapshots'
+SNAPSHOT_URL = f"{SNAPSHOTS_URL}/1234"
+ROLLBACK_SNAPSHOT_URL = f"{SNAPSHOTS_URL}/1234/rollback"
+
+
+@mock_backend_response({'schnapps': {'list': {'snapshots': ['foo', 'bar']}}})
+def test_get_snapshots(client):
+ response = client.get(SNAPSHOTS_URL)
assert response.status_code == HTTPStatus.OK
- assert response.json['key'] == 'value'
+ assert response.json == ['foo', 'bar']
+
+@mock_backend_response({'schnapps': {'create': {'result': True}}})
+def test_post_snapshot(client):
+ response = client.post(
+ SNAPSHOTS_URL, json={'description': 'Lorem ipsum dolor sit amet.'},
+ )
+ assert response.status_code == HTTPStatus.ACCEPTED
+ assert response.json == {'result': True}
-@mock_backend_response({'example_module': {'example_action': {'result': True}}})
-def test_post_example_invalid_json(client):
- response = client.post('/schnapps/api/example', json=False)
+
+def test_post_snapshot_missing_description(client):
+ response = client.post(
+ SNAPSHOTS_URL, json={'foo': 'Lorem ipsum dolor sit amet.'},
+ )
assert response.status_code == HTTPStatus.BAD_REQUEST
- assert response.json == 'Invalid JSON'
+ assert response.json == {'description': 'Missing data for required field.'}
+
+
+@mock_backend_response({'schnapps': {'create': {'result': True}}})
+def test_post_snapshot_invalid_json(client):
+ response = client.post(
+ SNAPSHOTS_URL, json={'foo': 'Lorem ipsum dolor sit amet.'},
+ )
+ assert response.status_code == HTTPStatus.BAD_REQUEST
+ assert response.json == {'description': 'Missing data for required field.'}
+
+
+@mock_backend_response({'schnapps': {'create': {'result': False}}})
+def test_post_snapshot_backend_error(client):
+ response = client.post(
+ SNAPSHOTS_URL, json={'description': 'Lorem ipsum dolor sit amet.'},
+ )
+ assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
+ assert response.json == 'Cannot create snapshot.'
+
+
+@mock_backend_response({'schnapps': {'delete': {'result': True}}})
+def test_delete_device(client):
+ response = client.delete(SNAPSHOT_URL)
+ assert response.status_code == HTTPStatus.NO_CONTENT
+
+
+@mock_backend_response({'schnapps': {'delete': {'result': False}}})
+def test_delete_device_backend_error(client):
+ response = client.delete(SNAPSHOT_URL)
+ assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
+ assert response.json == 'Cannot delete snapshot.'
+
+
+@mock_backend_response({'schnapps': {'rollback': {'result': True}}})
+def test_rollback_snapshot(client):
+ response = client.put(ROLLBACK_SNAPSHOT_URL)
+ assert response.status_code == HTTPStatus.NO_CONTENT
-@mock_backend_response({'example_module': {'example_action': {'key': 'value'}}})
-def test_post_example_backend_error(client):
- response = client.post('/schnapps/api/example', json={'modules': []})
+@mock_backend_response({'schnapps': {'rollback': {'result': False}}})
+def test_rollback_snapshot_backend_error(client):
+ response = client.put(ROLLBACK_SNAPSHOT_URL)
assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
- assert response.json == 'Cannot create entity'
+ assert response.json == 'Cannot rollback to snapshot.'