1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2025-06-16 13:46:16 +02:00

Compare commits

..

20 Commits

Author SHA1 Message Date
22a1c1a929 Merge branch 'feature/ws-included' into 'master'
Draft: Using socket.io for websocket handling and make reforis configurable

See merge request turris/reforis/foris-js!201
2023-04-28 10:53:06 +02:00
f6dece80b2 Using socket.io for websocket handling and make reforis configurable
Socket.io wrapper is used to handle websockets now,
this means that websocket logic had to be redone.

Also it is necessary to set `REFORIS_PREFIX` env variable
during the build process. To set the path of backend url.
It was previously fixed to `/reforis`.
2023-04-27 10:05:22 +02:00
efb3fa80ce Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!210
2022-12-29 11:52:54 +01:00
9c3dcaf6b5 Merge branch 'bump-version-560' into 'dev'
Bump v5.6.0

See merge request turris/reforis/foris-js!209
2022-12-29 11:45:58 +01:00
fb41c9629e Bump v5.6.0
* Add & update Weblate translations
* Add CustomizationContext and custom hook
* Update caniuse-lite
* Remove testUtils from .gitignore
* Make ieee80211w_disabled as optional in WiFiForm
* Move contexts in a context folder
* NPM audit fix
2022-12-29 11:34:19 +01:00
620eee3f53 NPM audit fix 2022-12-29 11:33:47 +01:00
f6ec82609c Merge branch 'update-translations' into 'dev'
Add & update Weblate translations

See merge request turris/reforis/foris-js!208
2022-12-28 16:27:48 +01:00
0b47c38f21 Merge branch 'fix-custom-context-mock' into 'dev'
Fix custom context mock

See merge request turris/reforis/foris-js!207
2022-12-28 16:09:20 +01:00
6183669c9b Fix CustomizationContextMock for tests 2022-12-28 15:19:24 +01:00
f3694bb62c Make ieee80211w_disabled as optional in WiFiForm 2022-12-28 15:18:42 +01:00
0f2344a005 Merge branch 'add-customization-context' into 'dev'
Add CustomizationContext and custom hook

See merge request turris/reforis/foris-js!183
2022-12-22 15:58:04 +01:00
f2ae6c4d0a Move contexts in a context folder 2022-12-22 15:35:22 +01:00
f382e743aa Update caniuse-lite 2022-12-22 15:35:22 +01:00
d71f4a7967 Remove testUtils from .gitignore
Because the testUtils folder consists of useful utils for testing,
we want to track changes in these files.
2022-12-22 15:35:21 +01:00
aeabc0bf06 Add info about CustomizationContext to the docs 2022-12-22 15:35:21 +01:00
46fe75d3cf Add tests for CustomizationContext 2022-12-22 15:35:20 +01:00
c469d8dfde Add CustomizationContext and custom hook
As we want to share customization context between reForis & Plugins
2022-12-22 15:35:20 +01:00
f327428765 Add about endpoint to forisUrls 2022-12-22 15:35:19 +01:00
5a359661da Translated using Weblate (Russian)
Currently translated at 100.0% (69 of 69 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2022-12-07 13:47:07 +01:00
3d30e2720e Translated using Weblate (Slovak)
Currently translated at 100.0% (69 of 69 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2022-12-05 17:48:13 +01:00
24 changed files with 13708 additions and 14061 deletions

1
.gitignore vendored
View File

@ -51,4 +51,3 @@ coverage.xml
dist/ dist/
foris-*.tgz foris-*.tgz
styleguide/ styleguide/
testUtils

27402
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "foris", "name": "foris",
"version": "5.5.0", "version": "5.6.0",
"description": "Foris JS library is a set of components and utils for reForis application and plugins.", "description": "Foris JS library is a set of components and utils for reForis application and plugins.",
"author": "CZ.NIC, z.s.p.o.", "author": "CZ.NIC, z.s.p.o.",
"repository": { "repository": {
@ -18,8 +18,9 @@
"immutability-helper": "3.0.1", "immutability-helper": "3.0.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"qrcode.react": "^1.0.1", "qrcode.react": "^1.0.1",
"react-datetime": "^3.1.1", "react-datetime": "^3.2.0",
"react-uid": "^2.2.0" "react-uid": "^2.2.0",
"socket.io-client": "^4.6.1"
}, },
"peerDependencies": { "peerDependencies": {
"bootstrap": "4.4.1", "bootstrap": "4.4.1",
@ -29,14 +30,14 @@
"react-router-dom": "^5.1.2" "react-router-dom": "^5.1.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.12.10", "@babel/cli": "^7.20.7",
"@babel/core": "^7.9.0", "@babel/core": "^7.20.12",
"@babel/plugin-transform-runtime": "^7.9.0", "@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.9.0", "@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.9.4", "@babel/preset-react": "^7.9.4",
"@fortawesome/fontawesome-free": "^5.13.0", "@fortawesome/fontawesome-free": "^5.13.0",
"@testing-library/react": "^8.0.9", "@testing-library/react": "^8.0.9",
"babel-loader": "^8.1.0", "babel-loader": "^8.3.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"bootstrap": "^4.5.0", "bootstrap": "^4.5.0",
"css-loader": "^5.2.4", "css-loader": "^5.2.4",
@ -45,18 +46,18 @@
"eslint-config-reforis": "^1.0.0", "eslint-config-reforis": "^1.0.0",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.1.4",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"jest": "^25.2.0", "jest": "^29.5.0",
"jest-mock-axios": "^3.2.0", "jest-mock-axios": "^3.2.0",
"moment-timezone": "^0.5.34", "moment-timezone": "^0.5.40",
"prettier": "2.0.5", "prettier": "2.0.5",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "16.9.0", "react": "16.9.0",
"react-dom": "16.9.0", "react-dom": "16.9.0",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.3.4",
"react-styleguidist": "^11.2.0", "react-styleguidist": "^13.1.1",
"snapshot-diff": "^0.7.0", "snapshot-diff": "^0.7.0",
"style-loader": "^1.2.1", "style-loader": "^1.2.1",
"webpack": "^5.68.0" "webpack": "^5.75.0"
}, },
"scripts": { "scripts": {
"lint": "eslint src", "lint": "eslint src",

View File

@ -14,7 +14,7 @@ import { ForisURLs } from "../utils/forisUrls";
import { Button } from "../bootstrap/Button"; import { Button } from "../bootstrap/Button";
import { Modal, ModalHeader, ModalBody, ModalFooter } from "../bootstrap/Modal"; import { Modal, ModalHeader, ModalBody, ModalFooter } from "../bootstrap/Modal";
import { useAlert } from "../alertContext/AlertContext"; import { useAlert } from "../context/alertContext/AlertContext";
export function RebootButton(props) { export function RebootButton(props) {
const [triggered, setTriggered] = useState(false); const [triggered, setTriggered] = useState(false);

View File

@ -9,7 +9,7 @@ import React, { useEffect, useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Button } from "../../bootstrap/Button"; import { Button } from "../../bootstrap/Button";
import { useAlert } from "../../alertContext/AlertContext"; import { useAlert } from "../../context/alertContext/AlertContext";
import { ALERT_TYPES } from "../../bootstrap/Alert"; import { ALERT_TYPES } from "../../bootstrap/Alert";
import { useAPIPost } from "../../api/hooks"; import { useAPIPost } from "../../api/hooks";
import { API_STATE } from "../../api/utils"; import { API_STATE } from "../../api/utils";
@ -25,7 +25,7 @@ export function ResetWiFiSettings({ ws, endpoint }) {
useEffect(() => { useEffect(() => {
const module = "wifi"; const module = "wifi";
ws.subscribe(module).bind(module, "reset", () => { ws.bind(module, "reset", () => {
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
setTimeout(() => location.reload(), 1000); setTimeout(() => location.reload(), 1000);
}); });

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
@ -64,7 +64,7 @@ DeviceForm.propTypes = {
guest_wifi: PropTypes.object.isRequired, guest_wifi: PropTypes.object.isRequired,
encryption: PropTypes.string.isRequired, encryption: PropTypes.string.isRequired,
available_bands: PropTypes.array.isRequired, available_bands: PropTypes.array.isRequired,
ieee80211w_disabled: PropTypes.bool.isRequired, ieee80211w_disabled: PropTypes.bool,
}), }),
formErrors: PropTypes.object.isRequired, formErrors: PropTypes.object.isRequired,
setFormValue: PropTypes.func.isRequired, setFormValue: PropTypes.func.isRequired,

View File

@ -8,8 +8,8 @@
import React, { useState, useContext, useCallback } from "react"; import React, { useState, useContext, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Alert, ALERT_TYPES } from "../bootstrap/Alert"; import { Alert, ALERT_TYPES } from "../../bootstrap/Alert";
import { Portal } from "../utils/Portal"; import { Portal } from "../../utils/Portal";
AlertContextProvider.propTypes = { AlertContextProvider.propTypes = {
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React, { useContext, useEffect } from "react";
import PropTypes from "prop-types";
import { useAPIGet } from "../../api/hooks";
import { ForisURLs } from "../../utils/forisUrls";
import { Spinner } from "../../bootstrap/Spinner";
CustomizationContextProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
};
function CustomizationContextProvider({ children }) {
const { CustomizationContext } = window;
const [getCustomizationResponse, getCustomization] = useAPIGet(
ForisURLs.about
);
useEffect(() => {
getCustomization();
}, [getCustomization]);
if (getCustomizationResponse.state !== "success") {
return <Spinner fullScreen />;
}
const deviceDetails = getCustomizationResponse.data || {};
const isCustomized = !!(
deviceDetails &&
deviceDetails.customization !== undefined &&
deviceDetails.customization === "shield"
);
return (
<CustomizationContext.Provider value={{ deviceDetails, isCustomized }}>
{children}
</CustomizationContext.Provider>
);
}
function useCustomizationContext() {
const { CustomizationContext } = window;
return useContext(CustomizationContext);
}
export { CustomizationContextProvider, useCustomizationContext };

View File

@ -0,0 +1,3 @@
It provides customization context to the children. `CustomizationContext` allows
using `useCustomizationContext` in components to check if the reForis UI is
customized or not for specific devices.

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://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 { render, wait, getByText } from "customTestRender";
import mockAxios from "jest-mock-axios";
import {
useCustomizationContext,
CustomizationContextProvider,
} from "../CustomizationContext";
const CUSTOM = "Description / component for customized reForis (Shield)";
const ORIGINAL = "Description / component for original reForis (other devices)";
const CustomizationTest = () => {
const { isCustomized } = useCustomizationContext();
return <p>{isCustomized ? CUSTOM : ORIGINAL}</p>;
};
describe("CustomizationContext", () => {
let componentContainer;
beforeEach(() => {
const { container } = render(
<CustomizationContextProvider>
<CustomizationTest />
</CustomizationContextProvider>
);
componentContainer = container;
});
it("should render component without customization", async () => {
mockAxios.mockResponse({ data: {} });
await wait(() => getByText(componentContainer, ORIGINAL));
expect(componentContainer).toMatchSnapshot();
});
it("should render customized component", async () => {
mockAxios.mockResponse({ data: { customization: "shield" } });
await wait(() => getByText(componentContainer, CUSTOM));
expect(componentContainer).toMatchSnapshot();
});
});

View File

@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustomizationContext should render component without customization 1`] = `
<div>
<p>
Description / component for original reForis (other devices)
</p>
</div>
`;
exports[`CustomizationContext should render customized component 1`] = `
<div>
<p>
Description / component for customized reForis (Shield)
</p>
</div>
`;

View File

@ -13,7 +13,7 @@ import { ALERT_TYPES } from "../../bootstrap/Alert";
import { API_STATE } from "../../api/utils"; import { API_STATE } from "../../api/utils";
import { formFieldsSize } from "../../bootstrap/constants"; import { formFieldsSize } from "../../bootstrap/constants";
import { Spinner } from "../../bootstrap/Spinner"; import { Spinner } from "../../bootstrap/Spinner";
import { useAlert } from "../../alertContext/AlertContext"; import { useAlert } from "../../context/alertContext/AlertContext";
import { useAPIPost } from "../../api/hooks"; import { useAPIPost } from "../../api/hooks";
import { useForisModule, useForm } from "../hooks"; import { useForisModule, useForm } from "../hooks";

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
@ -90,4 +90,13 @@ export {
} from "./utils/validations"; } from "./utils/validations";
// Alert context // Alert context
export { AlertContextProvider, useAlert } from "./alertContext/AlertContext"; export {
AlertContextProvider,
useAlert,
} from "./context/alertContext/AlertContext";
// Customization context
export {
CustomizationContextProvider,
useCustomizationContext,
} from "./context/customizationContext/CustomizationContext";

View File

@ -14,6 +14,7 @@ import { render } from "@testing-library/react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { AlertContextMock } from "./alertContextMock"; import { AlertContextMock } from "./alertContextMock";
import { CustomizationContextMock } from "./cutomizationContextMock";
Wrapper.propTypes = { Wrapper.propTypes = {
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
@ -24,11 +25,13 @@ Wrapper.propTypes = {
function Wrapper({ children }) { function Wrapper({ children }) {
return ( return (
<AlertContextMock> <CustomizationContextMock>
<StaticRouter> <AlertContextMock>
<UIDReset>{children}</UIDReset> <StaticRouter>
</StaticRouter> <UIDReset>{children}</UIDReset>
</AlertContextMock> </StaticRouter>
</AlertContextMock>
</CustomizationContextMock>
); );
} }

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
window.CustomizationContext = React.createContext();
const deviceDetails = {
kernel: "5.x.x",
model: "Turris Omnia",
os_branch: {
mode: "branch",
value: "hbs",
},
os_version: "6.x.x",
reforis_version: "1.x.x",
serial: 123456789,
};
const isCustomized = false;
function CustomizationContextMock({ children }) {
return (
<CustomizationContext.Provider value={{ deviceDetails, isCustomized }}>
{children}
</CustomizationContext.Provider>
);
}
export { CustomizationContextMock };

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (http://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
export const REFORIS_URL_PREFIX = "/reforis"; export const REFORIS_URL_PREFIX = process.env.REFORIS_PREFIX || "";
export const REFORIS_API_URL_PREFIX = `${REFORIS_URL_PREFIX}/api`; export const REFORIS_API_URL_PREFIX = `${REFORIS_URL_PREFIX}/api`;
export const ForisURLs = { export const ForisURLs = {
@ -36,5 +36,6 @@ export const ForisURLs = {
luci: "/cgi-bin/luci", luci: "/cgi-bin/luci",
// API // API
about: `${REFORIS_API_URL_PREFIX}/about`,
reboot: `${REFORIS_API_URL_PREFIX}/reboot`, reboot: `${REFORIS_API_URL_PREFIX}/reboot`,
}; };

View File

@ -7,49 +7,33 @@
/* eslint no-console: "off" */ /* eslint no-console: "off" */
const PROTOCOL = window.location.protocol === "http:" ? "ws" : "wss"; import { REFORIS_URL_PREFIX } from "../utils/forisUrls";
const URL = process.env.LIGHTTPD const { io } = require("socket.io-client");
? `${PROTOCOL}://${window.location.host}/${
process.env.WSPATH || "foris-ws"
}`
: `${PROTOCOL}://${window.location.hostname}:9081`;
const WAITING_FOR_CONNECTION_TIMEOUT = 500;
export class WebSockets { export class WebSockets {
constructor() { constructor() {
this.ws = new WebSocket(URL); this.socket = io("/notifications", {
this.ws.onerror = (e) => { path: `${REFORIS_URL_PREFIX}/reforis-ws`,
console.error("WS: Error:", e); });
}; this.connection = null;
this.ws.onmessage = (e) => { this.socket.on("disconnect", (reason) => {
console.debug(`WS: Received Message: ${e.data}`); this.connection = null;
const data = JSON.parse(e.data); console.debug(`SocketIO disconnected (${reason})`);
this.dispatch(data); });
}; this.socket.on("notification", (message) => {
this.ws.onopen = () => { console.debug("WS: Received Message:", message);
console.debug("WS: Connection open."); this.dispatch(message);
}; });
this.ws.onclose = () => { this.socket.on("connect", (connection) => {
console.debug("WS: Connection closed."); this.connection = connection;
}; console.debug(`SocketIO connected.`);
});
// callbacks[module][action] // callbacks[module][action]
this.callbacks = {}; this.callbacks = {};
} }
waitForConnection(callback) {
if (this.ws.readyState === 1) {
callback();
} else {
const that = this;
setTimeout(() => {
that.waitForConnection(callback);
}, WAITING_FOR_CONNECTION_TIMEOUT);
}
}
bind(module, action, callback) { bind(module, action, callback) {
this.callbacks[module] = this.callbacks[module] || {}; this.callbacks[module] = this.callbacks[module] || {};
this.callbacks[module][action] = this.callbacks[module][action] || []; this.callbacks[module][action] = this.callbacks[module][action] || [];
@ -57,13 +41,6 @@ export class WebSockets {
return this; return this;
} }
subscribe(module) {
this.waitForConnection(() => {
this.send("subscribe", module);
});
return this;
}
unbind(module, action, callback) { unbind(module, action, callback) {
const callbacks = this.callbacks[module][action]; const callbacks = this.callbacks[module][action];
@ -77,28 +54,12 @@ export class WebSockets {
} }
if (Object.keys(this.callbacks[module]).length === 0) { if (Object.keys(this.callbacks[module]).length === 0) {
this.unsubscribe(module); delete this.callbacks[module];
} }
return this; return this;
} }
unsubscribe(module) {
this.waitForConnection(() => {
this.send("unsubscribe", module);
delete this.callbacks[module];
});
return this;
}
send(action, params) {
const payload = JSON.stringify({ action, params });
this.waitForConnection(() => {
this.ws.send(payload);
});
return this;
}
dispatch(json) { dispatch(json) {
if (!json.module) return; if (!json.module) return;
@ -107,18 +68,15 @@ export class WebSockets {
chain = this.callbacks[json.module][json.action]; chain = this.callbacks[json.module][json.action];
} catch (error) { } catch (error) {
if (error instanceof TypeError) { if (error instanceof TypeError) {
console.warn( console.debug(
`Callback for this message wasn't found:${error.data}` `Callbacks for this module wasn't found: ${json.module}`
); );
} else throw error; } else throw error;
} }
if (typeof chain === "undefined") return; if (typeof chain === "undefined") return;
console.debug("Handling WS message", json);
chain.forEach((callback) => callback(json)); chain.forEach((callback) => callback(json));
} }
close() {
this.ws.close();
}
} }

View File

@ -32,7 +32,7 @@ export function useWSForisModule(
setData(message.data); setData(message.data);
} }
ws.subscribe(module).bind(module, action, callback); ws.bind(module, action, callback);
return () => { return () => {
ws.unbind(module, action, callback); ws.unbind(module, action, callback);

View File

@ -43,7 +43,7 @@ module.exports = {
}, },
{ {
name: "Alert Context", name: "Alert Context",
components: ["src/alertContext/AlertContext.js"], components: ["src/context/alertContext/AlertContext.js"],
exampleMode: "expand", exampleMode: "expand",
usageMode: "expand", usageMode: "expand",
}, },
@ -51,6 +51,14 @@ module.exports = {
sectionDepth: 1, sectionDepth: 1,
}, },
{
name: "Customization Context",
components: [
"src/context/customizationContext/CustomizationContext.js",
],
exampleMode: "expand",
usageMode: "expand",
},
{ {
name: "Bootstrap components", name: "Bootstrap components",
description: "Set of bootstrap components.", description: "Set of bootstrap components.",

View File

@ -8,16 +8,17 @@ msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-12-02 15:54+0100\n" "POT-Creation-Date: 2022-12-02 15:54+0100\n"
"PO-Revision-Date: 2022-05-28 09:19+0000\n" "PO-Revision-Date: 2022-12-07 12:47+0000\n"
"Last-Translator: Алексей Леньшин <alenshin@gmail.com>\n" "Last-Translator: Алексей Леньшин <alenshin@gmail.com>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/turris/foris-js/"
"ru/>\n"
"Language: ru\n" "Language: ru\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/turris/foris-"
"js/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" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\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"
"X-Generator: Weblate 4.15-dev\n"
"Generated-By: Babel 2.11.0\n" "Generated-By: Babel 2.11.0\n"
#: src/api/utils.js:61 #: src/api/utils.js:61
@ -118,13 +119,15 @@ msgstr "Шифрование"
#: src/common/WiFiSettings/WiFiForm.js:226 #: src/common/WiFiSettings/WiFiForm.js:226
msgid "Disable Management Frame Protection" msgid "Disable Management Frame Protection"
msgstr "" msgstr "Отключить защиту кадров управления"
#: src/common/WiFiSettings/WiFiForm.js:227 #: src/common/WiFiSettings/WiFiForm.js:227
msgid "" msgid ""
"In case you have trouble connecting to WiFi Access Point, try disabling " "In case you have trouble connecting to WiFi Access Point, try disabling "
"Management Frame Protection." "Management Frame Protection."
msgstr "" msgstr ""
"Если у вас возникли проблемы с подключением к точке доступа Wi-Fi, "
"попробуйте отключить защиту кадров управления."
#: src/common/WiFiSettings/WiFiForm.js:262 #: src/common/WiFiSettings/WiFiForm.js:262
msgid "auto" msgid "auto"
@ -381,4 +384,3 @@ msgstr "Не содержит списка электронных адресов
#~ "конфигурации Wi-Fi и восстановлению " #~ "конфигурации Wi-Fi и восстановлению "
#~ "значений по умолчанию.\n" #~ "значений по умолчанию.\n"
#~ " " #~ " "

View File

@ -8,15 +8,16 @@ msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-12-02 15:54+0100\n" "POT-Creation-Date: 2022-12-02 15:54+0100\n"
"PO-Revision-Date: 2022-05-30 06:14+0000\n" "PO-Revision-Date: 2022-12-05 16:48+0000\n"
"Last-Translator: Atec <dr.atec@gmail.com>\n" "Last-Translator: Atec <dr.atec@gmail.com>\n"
"Language-Team: Slovak <https://hosted.weblate.org/projects/turris/foris-js/"
"sk/>\n"
"Language: sk\n" "Language: sk\n"
"Language-Team: Slovak <https://hosted.weblate.org/projects/turris/foris-"
"js/sk/>\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 4.15-dev\n"
"Generated-By: Babel 2.11.0\n" "Generated-By: Babel 2.11.0\n"
#: src/api/utils.js:61 #: src/api/utils.js:61
@ -117,13 +118,15 @@ msgstr "Šifrovanie"
#: src/common/WiFiSettings/WiFiForm.js:226 #: src/common/WiFiSettings/WiFiForm.js:226
msgid "Disable Management Frame Protection" msgid "Disable Management Frame Protection"
msgstr "" msgstr "Zakázať Management Frame Protection"
#: src/common/WiFiSettings/WiFiForm.js:227 #: src/common/WiFiSettings/WiFiForm.js:227
msgid "" msgid ""
"In case you have trouble connecting to WiFi Access Point, try disabling " "In case you have trouble connecting to WiFi Access Point, try disabling "
"Management Frame Protection." "Management Frame Protection."
msgstr "" msgstr ""
"Ak máte problémy s pripojením k prístupovému bodu WiFi, skúste zakázať "
"Management Frame Protection."
#: src/common/WiFiSettings/WiFiForm.js:262 #: src/common/WiFiSettings/WiFiForm.js:262
msgid "auto" msgid "auto"
@ -377,4 +380,3 @@ msgstr "Neobsahuje zoznam e-mailov oddelených čiarkami."
#~ "že sa tým odstráni aktuálna konfigurácia" #~ "že sa tým odstráni aktuálna konfigurácia"
#~ " a obnovia sa východiskové hodnoty.\n" #~ " a obnovia sa východiskové hodnoty.\n"
#~ " " #~ " "