diff --git a/web/.babelrc.json b/web/.babelrc.json index dfe57f214c..146fc6a5e5 100644 --- a/web/.babelrc.json +++ b/web/.babelrc.json @@ -1,14 +1,17 @@ { - "presets": [ - ["@babel/env", { - "targets": { - "chrome": "57", - "firefox": "52", - "safari": "10.3", - "edge": "16", - "opera": "44" - } - }], - "@babel/preset-react" - ] + "presets": [ + [ + "@babel/env", + { + "targets": { + "chrome": "57", + "firefox": "52", + "safari": "10.3", + "edge": "16", + "opera": "44" + } + } + ], + "@babel/preset-react" + ] } diff --git a/web/.eslintrc.json b/web/.eslintrc.json index 1e82edd3c9..4b23faa524 100644 --- a/web/.eslintrc.json +++ b/web/.eslintrc.json @@ -1,83 +1,94 @@ { - "root": true, - "env": { - "browser": true, - "es6": true, - "jest": true + "root": true, + "env": { + "browser": true, + "es6": true, + "jest": true + }, + "extends": [ + "eslint:recommended", + "standard", + "standard-jsx", + "standard-react", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 7, + "ecmaFeatures": { + "jsx": true }, - "extends": [ - "eslint:recommended", - "standard", - "standard-jsx", - "standard-react", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 7, - "ecmaFeatures": { - "jsx": true - }, - "sourceType": "module" - }, - "plugins": ["agama-i18n", "flowtype", "i18next", "react", "react-hooks", "@typescript-eslint"], - "rules": { - "agama-i18n/string-literals": "error", - "i18next/no-literal-string": "error", - "indent": ["error", 2, - { - "ObjectExpression": "first", - "CallExpression": {"arguments": "first"}, - "MemberExpression": 1, - "ignoredNodes": [ "JSXAttribute" ], - "SwitchCase": 1 - }], - "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }], - "no-var": "error", - "no-multi-str": "off", - "no-use-before-define": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-use-before-define": "warn", - "@typescript-eslint/ban-ts-comment": "off", - "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], - "prefer-promise-reject-errors": ["error", { "allowEmptyReject": true }], - "react/jsx-indent": ["error", 2], - "semi": ["error", "always", { "omitLastInOneLineBlock": true }], - - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "error", - - "camelcase": "off", - "comma-dangle": "off", - "curly": "off", - "jsx-quotes": "off", - "key-spacing": "off", - "no-console": "off", - "quotes": "off", - "react/jsx-curly-spacing": "off", - "react/jsx-indent-props": "off", - "react/prop-types": "off", - "space-before-function-paren": "off", - "n/no-callback-literal": "off" - }, - "overrides": [ + "sourceType": "module" + }, + "plugins": [ + "agama-i18n", + "flowtype", + "i18next", + "react", + "react-hooks", + "@typescript-eslint" + ], + "rules": { + "agama-i18n/string-literals": "error", + "i18next/no-literal-string": "error", + "no-var": "error", + "no-multi-str": "off", + "no-use-before-define": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-use-before-define": "warn", + "@typescript-eslint/ban-ts-comment": "off", + "lines-between-class-members": [ + "error", + "always", { - // do not check translations in the testing or development files - "files": ["*.test.*", "test-utils.js"], - "rules": { - "i18next/no-literal-string": "off" - } - }, + "exceptAfterSingleLine": true + } + ], + "prefer-promise-reject-errors": [ + "error", { - // do not check translation arguments in the test, it checks some internals by passing variables - "files": ["i18n.test.js"], - "rules": { - "agama-i18n/string-literals": "off" - } + "allowEmptyReject": true } ], - "globals": { - "require": false, - "module": false + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "error", + "camelcase": "off", + "comma-dangle": "off", + "curly": "off", + "jsx-quotes": "off", + "key-spacing": "off", + "no-console": "off", + "quotes": "off", + "react/jsx-curly-spacing": "off", + "react/jsx-indent-props": "off", + "react/prop-types": "off", + "space-before-function-paren": "off", + "n/no-callback-literal": "off" + }, + "overrides": [ + { + // do not check translations in the testing or development files + "files": [ + "*.test.*", + "test-utils.js" + ], + "rules": { + "i18next/no-literal-string": "off" + } + }, + { + // do not check translation arguments in the test, it checks some internals by passing variables + "files": [ + "i18n.test.js" + ], + "rules": { + "agama-i18n/string-literals": "off" + } } + ], + "globals": { + "require": false, + "module": false + } } diff --git a/web/.prettierignore b/web/.prettierignore index 1af773a716..85883df3d9 100644 --- a/web/.prettierignore +++ b/web/.prettierignore @@ -1 +1,5 @@ -src/lib/cockpit.js +dist +po +public +share +src/lib diff --git a/web/.prettierrc b/web/.prettierrc index 585de22956..de753c537d 100644 --- a/web/.prettierrc +++ b/web/.prettierrc @@ -1,7 +1,3 @@ { - "arrowParens": "avoid", - "printWidth": 100, - "semi": true, - "singleQuote": false, - "trailingComma": "none" + "printWidth": 100 } diff --git a/web/.stylelintrc.json b/web/.stylelintrc.json index 2415dd3d08..a7365de1cd 100644 --- a/web/.stylelintrc.json +++ b/web/.stylelintrc.json @@ -1,7 +1,11 @@ { + "plugins": [ + "stylelint-prettier" + ], "extends": [ "stylelint-config-standard", - "stylelint-config-standard-scss" + "stylelint-config-standard-scss", + "stylelint-prettier/recommended" ], "rules": { "at-rule-empty-line-before": null, diff --git a/web/README.md b/web/README.md index 79ee998455..41a5b62405 100644 --- a/web/README.md +++ b/web/README.md @@ -30,7 +30,7 @@ machine (a virtual machine as well). In that case run AGAMA_SERVER=https://: npm run server -- --open ``` -Where `AGAMA_SERVER` is the IP address, the hostname or the full URL of the +Where `AGAMA_SERVER` is the IP address, the hostname or the full URL of the running Agama server instance. This is especially useful if you use the Live ISO which does not contain any development tools, you can develop the web frontend easily from your workstation. diff --git a/web/__mocks__/svg.js b/web/__mocks__/svg.js index f6f5bae744..c1b1273712 100644 --- a/web/__mocks__/svg.js +++ b/web/__mocks__/svg.js @@ -1,6 +1,6 @@ -import React from 'react'; +import React from "react"; -export default ({...props}) => ( +export default ({ ...props }) => ( // Simple SVG square based on a wikimedia example https://commons.wikimedia.org/wiki/SVG_examples diff --git a/web/babel.config.js b/web/babel.config.js index da01f98218..c2bda356f1 100644 --- a/web/babel.config.js +++ b/web/babel.config.js @@ -1,16 +1,13 @@ const { NODE_ENV } = process.env; -const presets = [ - '@babel/preset-react', - ['@babel/preset-env', { targets: { node: 'current' } }] -]; +const presets = ["@babel/preset-react", ["@babel/preset-env", { targets: { node: "current" } }]]; const plugins = []; -if (!['production', 'test'].includes(NODE_ENV)) { - plugins.push('react-refresh/babel'); +if (!["production", "test"].includes(NODE_ENV)) { + plugins.push("react-refresh/babel"); } module.exports = { presets, - plugins + plugins, }; diff --git a/web/cspell.json b/web/cspell.json index d1926048ce..ae6c026f21 100644 --- a/web/cspell.json +++ b/web/cspell.json @@ -84,11 +84,5 @@ ] } ], - "dictionaries": [ - "custom", - "css", - "en-common-misspelling", - "fullstack", - "html" - ] + "dictionaries": ["custom", "css", "en-common-misspelling", "fullstack", "html"] } diff --git a/web/jest.config.js b/web/jest.config.js index f416de4c8f..670ff97b57 100644 --- a/web/jest.config.js +++ b/web/jest.config.js @@ -3,8 +3,8 @@ * https://jestjs.io/docs/configuration */ -const { pathsToModuleNameMapper } = require('ts-jest'); -const { compilerOptions } = require('./tsconfig'); +const { pathsToModuleNameMapper } = require("ts-jest"); +const { compilerOptions } = require("./tsconfig"); module.exports = { // All imported modules in your tests should be mocked automatically @@ -23,18 +23,13 @@ module.exports = { collectCoverage: true, // An array of glob patterns indicating a set of files for which coverage information should be collected - collectCoverageFrom: [ - "src/**/*.{js,jsx}", - "!src/lib/*.js" - ], + collectCoverageFrom: ["src/**/*.{js,jsx}", "!src/lib/*.js"], // The directory where Jest should output its coverage files coverageDirectory: "coverage", // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: [ - "/node_modules/" - ], + coveragePathIgnorePatterns: ["/node_modules/"], // Indicates which provider should be used to instrument code for coverage // coverageProvider: "babel", @@ -66,8 +61,7 @@ module.exports = { // globalTeardown: undefined, // A set of global variables that need to be available in all test environments - globals: { - }, + globals: {}, // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. // maxWorkers: "50%", @@ -141,9 +135,7 @@ module.exports = { // A list of paths to modules that run some code to configure or set up the testing framework before each test // setupFilesAfterEnv: [], - setupFilesAfterEnv: [ - "/src/setupTests.js" - ], + setupFilesAfterEnv: ["/src/setupTests.js"], // The number of seconds after which a test is considered as slow and reported as such in the results. // slowTestThreshold: 5, @@ -190,7 +182,7 @@ module.exports = { // transform: undefined, transform: { "\\.m?jsx?$": "babel-jest", - "\\.(css|svg)$": "jest-transform-stub" + "\\.(css|svg)$": "jest-transform-stub", }, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation diff --git a/web/package-lock.json b/web/package-lock.json index aebca1daf9..4499acd7f6 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -48,6 +48,7 @@ "css-loader": "^7.1.1", "css-minimizer-webpack-plugin": "^6.0.0", "eslint": "^8.53.0", + "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", "eslint-config-standard-jsx": "^11.0.0", "eslint-config-standard-react": "^13.0.0", @@ -71,6 +72,7 @@ "jsdoc": "^4.0.2", "mini-css-extract-plugin": "^2.7.6", "po2json": "^1.0.0-alpha", + "prettier": "^3.3.2", "qunit": "^2.20.0", "react-refresh": "^0.14.0", "react-refresh-typescript": "^2.0.9", @@ -80,6 +82,7 @@ "stylelint": "^16.5.0", "stylelint-config-standard": "^36.0.0", "stylelint-config-standard-scss": "^13.1.0", + "stylelint-prettier": "^5.0.0", "stylelint-webpack-plugin": "^5.0.0", "terser-webpack-plugin": "^5.3.9", "ts-jest": "^29.2.2", @@ -8714,6 +8717,19 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-config-standard": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", @@ -9847,6 +9863,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/fast-equals": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", @@ -15920,6 +15943,35 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -17721,6 +17773,23 @@ } } }, + "node_modules/stylelint-prettier": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-5.0.0.tgz", + "integrity": "sha512-RHfSlRJIsaVg5Br94gZVdWlz/rBTyQzZflNE6dXvSxt/GthWMY3gEHsWZEBaVGg7GM+XrtVSp4RznFlB7i0oyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "prettier": ">=3.0.0", + "stylelint": ">=16.0.0" + } + }, "node_modules/stylelint-scss": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.3.0.tgz", diff --git a/web/package.json b/web/package.json index 4d88a8871e..861ea8933a 100644 --- a/web/package.json +++ b/web/package.json @@ -53,6 +53,7 @@ "css-loader": "^7.1.1", "css-minimizer-webpack-plugin": "^6.0.0", "eslint": "^8.53.0", + "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", "eslint-config-standard-jsx": "^11.0.0", "eslint-config-standard-react": "^13.0.0", @@ -76,6 +77,7 @@ "jsdoc": "^4.0.2", "mini-css-extract-plugin": "^2.7.6", "po2json": "^1.0.0-alpha", + "prettier": "^3.3.2", "qunit": "^2.20.0", "react-refresh": "^0.14.0", "react-refresh-typescript": "^2.0.9", @@ -85,6 +87,7 @@ "stylelint": "^16.5.0", "stylelint-config-standard": "^36.0.0", "stylelint-config-standard-scss": "^13.1.0", + "stylelint-prettier": "^5.0.0", "stylelint-webpack-plugin": "^5.0.0", "terser-webpack-plugin": "^5.3.9", "ts-jest": "^29.2.2", diff --git a/web/src/App.jsx b/web/src/App.jsx index 81a7a8d03b..4524a290ac 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -59,7 +59,7 @@ function App() { return ; } - if ((selectedProduct === undefined) && (location.pathname !== "/products")) { + if (selectedProduct === undefined && location.pathname !== "/products") { return ; } diff --git a/web/src/App.test.jsx b/web/src/App.test.jsx index 2fd0ac3986..74f65dbf48 100644 --- a/web/src/App.test.jsx +++ b/web/src/App.test.jsx @@ -41,27 +41,27 @@ jest.mock("~/queries/software", () => ({ useProduct: () => { return { products: mockProducts, - selectedProduct: mockSelectedProduct + selectedProduct: mockSelectedProduct, }; }, - useProductChanges: () => jest.fn() + useProductChanges: () => jest.fn(), })); jest.mock("~/queries/l10n", () => ({ ...jest.requireActual("~/queries/l10n"), - useL10nConfigChanges: () => jest.fn() + useL10nConfigChanges: () => jest.fn(), })); const mockClientStatus = { connected: true, error: false, phase: STARTUP, - status: BUSY + status: BUSY, }; jest.mock("~/context/installer", () => ({ ...jest.requireActual("~/context/installer"), - useInstallerClientStatus: () => mockClientStatus + useInstallerClientStatus: () => mockClientStatus, })); // Mock some components, @@ -80,14 +80,14 @@ describe("App", () => { l10n: { getUIKeymap: jest.fn().mockResolvedValue("en"), getUILocale: jest.fn().mockResolvedValue("en_us"), - setUILocale: jest.fn().mockResolvedValue("en_us") - } + setUILocale: jest.fn().mockResolvedValue("en_us"), + }, }; }); mockProducts = [ { id: "openSUSE", name: "openSUSE Tumbleweed" }, - { id: "Leap Micro", name: "openSUSE Micro" } + { id: "Leap Micro", name: "openSUSE Micro" }, ]; }); diff --git a/web/src/MainLayout.jsx b/web/src/MainLayout.jsx index 6ed3d31994..d85734d3d0 100644 --- a/web/src/MainLayout.jsx +++ b/web/src/MainLayout.jsx @@ -23,10 +23,22 @@ import React, { Suspense } from "react"; import { Outlet, NavLink, useNavigate } from "react-router-dom"; import { Button, - Masthead, MastheadContent, MastheadToggle, MastheadMain, MastheadBrand, - Nav, NavItem, NavList, - Page, PageSidebar, PageSidebarBody, PageToggleButton, - Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem + Masthead, + MastheadContent, + MastheadToggle, + MastheadMain, + MastheadBrand, + Nav, + NavItem, + NavList, + Page, + PageSidebar, + PageSidebarBody, + PageToggleButton, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, } from "@patternfly/react-core"; import { Icon, Loading } from "~/components/layout"; import { About, InstallerOptions, LogsButton } from "~/components/core"; @@ -90,7 +102,7 @@ const ChangeProductButton = () => { const Sidebar = () => { // TODO: Improve this and/or extract the NavItem to a wrapper component. - const links = rootRoutes.map(r => { + const links = rootRoutes.map((r) => { if (!r.handle || r.handle.hidden) return null; // eslint-disable-next-line agama-i18n/string-literals @@ -99,12 +111,14 @@ const Sidebar = () => { return ( - [className, isActive ? "pf-m-current" : ""].join(" ")}> - {name} - - } + component={({ className }) => ( + [className, isActive ? "pf-m-current" : ""].join(" ")} + > + {name} + + )} /> ); }); @@ -129,11 +143,7 @@ const Sidebar = () => { */ export default function Root() { return ( - } - sidebar={} - > + } sidebar={}> }> diff --git a/web/src/SimpleLayout.jsx b/web/src/SimpleLayout.jsx index 99ea0b3320..bb0d65d9bf 100644 --- a/web/src/SimpleLayout.jsx +++ b/web/src/SimpleLayout.jsx @@ -22,9 +22,13 @@ import React, { Suspense } from "react"; import { Outlet } from "react-router-dom"; import { - Masthead, MastheadContent, + Masthead, + MastheadContent, Page, - Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, } from "@patternfly/react-core"; import { InstallerOptions } from "./components/core"; import { _ } from "~/i18n"; @@ -34,7 +38,11 @@ import { Loading } from "./components/layout"; * Simple layout for displaying content that comes before product configuration * TODO: improve documentation */ -export default function SimpleLayout({ showOutlet = true, showInstallerOptions = false, children }) { +export default function SimpleLayout({ + showOutlet = true, + showInstallerOptions = false, + children, +}) { return ( @@ -42,17 +50,13 @@ export default function SimpleLayout({ showOutlet = true, showInstallerOptions = - - {showInstallerOptions && } - + {showInstallerOptions && } - }> - {showOutlet ? : children} - + }>{showOutlet ? : children} ); } diff --git a/web/src/agama.js b/web/src/agama.js index f39ecbf1fd..cd1d89287f 100644 --- a/web/src/agama.js +++ b/web/src/agama.js @@ -41,10 +41,8 @@ agama.locale = function locale(po) { const header = po[""]; if (header) { - if (header["plural-forms"]) - plural_fn = header["plural-forms"]; - if (header.language) - agama.language = header.language; + if (header["plural-forms"]) plural_fn = header["plural-forms"]; + if (header.language) agama.language = header.language; } } else if (po === null) { translations = {}; @@ -87,7 +85,7 @@ agama.ngettext = function ngettext(str1, strN, n) { // the plural function either returns direct index (integer) in the plural // translations or a boolean indicating simple plural form which // needs to be converted to index 0 (singular) or 1 (plural) - let index = (plural_index === true ? 1 : plural_index || 0); + let index = plural_index === true ? 1 : plural_index || 0; // skip the `null` item in the list generated by cockpit-po-plugin // TODO: get rid of that later @@ -98,7 +96,7 @@ agama.ngettext = function ngettext(str1, strN, n) { } // fallback, return the original text - return (n === 1) ? str1 : strN; + return n === 1 ? str1 : strN; }; // register a global object so it can be accessed from a separate po.js script diff --git a/web/src/assets/fonts.scss b/web/src/assets/fonts.scss index 4d8c1d1641..98b1961d65 100644 --- a/web/src/assets/fonts.scss +++ b/web/src/assets/fonts.scss @@ -13,38 +13,38 @@ /* LatoLatin-Regular */ @font-face { - font-family: Lato; - src: url("./fonts/LatoLatin-Regular.woff2") format("woff2"); - font-style: normal; - font-weight: normal; - text-rendering: optimizelegibility; + font-family: Lato; + src: url("./fonts/LatoLatin-Regular.woff2") format("woff2"); + font-style: normal; + font-weight: normal; + text-rendering: optimizelegibility; } /* LatoLatin-Italic */ @font-face { - font-family: Lato; - src: url("./fonts/LatoLatin-Italic.woff2") format("woff2"); - font-style: italic; - font-weight: normal; - text-rendering: optimizelegibility; + font-family: Lato; + src: url("./fonts/LatoLatin-Italic.woff2") format("woff2"); + font-style: italic; + font-weight: normal; + text-rendering: optimizelegibility; } /* LatoLatin-Bold */ @font-face { - font-family: Lato; - src: url("./fonts/LatoLatin-Bold.woff2") format("woff2"); - font-style: normal; - font-weight: bold; - text-rendering: optimizelegibility; + font-family: Lato; + src: url("./fonts/LatoLatin-Bold.woff2") format("woff2"); + font-style: normal; + font-weight: bold; + text-rendering: optimizelegibility; } /* LatoLatin-BoldItalic */ @font-face { - font-family: Lato; - src: url("./fonts/LatoLatin-BoldItalic.woff2") format("woff2"); - font-style: italic; - font-weight: bold; - text-rendering: optimizelegibility; + font-family: Lato; + src: url("./fonts/LatoLatin-BoldItalic.woff2") format("woff2"); + font-style: italic; + font-weight: bold; + text-rendering: optimizelegibility; } /** @@ -61,9 +61,11 @@ font-family: Poppins; font-style: normal; font-weight: 300; - src: local(""), - url("./fonts/poppins-v19-latin-ext_latin_devanagari-300.woff2") format("woff2"), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url("./fonts/poppins-v19-latin-ext_latin_devanagari-300.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + local(""), + url("./fonts/poppins-v19-latin-ext_latin_devanagari-300.woff2") format("woff2"), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url("./fonts/poppins-v19-latin-ext_latin_devanagari-300.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* poppins-300italic - latin-ext_latin_devanagari */ @@ -71,9 +73,11 @@ font-family: Poppins; font-style: italic; font-weight: 300; - src: local(""), - url("./fonts/poppins-v19-latin-ext_latin_devanagari-300italic.woff2") format("woff2"), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url("./fonts/poppins-v19-latin-ext_latin_devanagari-300italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + local(""), + url("./fonts/poppins-v19-latin-ext_latin_devanagari-300italic.woff2") format("woff2"), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url("./fonts/poppins-v19-latin-ext_latin_devanagari-300italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* poppins-regular - latin-ext_latin_devanagari */ @@ -81,9 +85,11 @@ font-family: Poppins; font-style: normal; font-weight: 400; - src: local(""), - url("./fonts/poppins-v19-latin-ext_latin_devanagari-regular.woff2") format("woff2"), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url("./fonts/poppins-v19-latin-ext_latin_devanagari-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + local(""), + url("./fonts/poppins-v19-latin-ext_latin_devanagari-regular.woff2") format("woff2"), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url("./fonts/poppins-v19-latin-ext_latin_devanagari-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* poppins-500 - latin-ext_latin_devanagari */ @@ -91,9 +97,11 @@ font-family: Poppins; font-style: normal; font-weight: 500; - src: local(""), - url("./fonts/poppins-v19-latin-ext_latin_devanagari-500.woff2") format("woff2"), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url("./fonts/poppins-v19-latin-ext_latin_devanagari-500.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + local(""), + url("./fonts/poppins-v19-latin-ext_latin_devanagari-500.woff2") format("woff2"), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url("./fonts/poppins-v19-latin-ext_latin_devanagari-500.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* poppins-500italic - latin-ext_latin_devanagari */ @@ -101,9 +109,11 @@ font-family: Poppins; font-style: italic; font-weight: 500; - src: local(""), - url("./fonts/poppins-v19-latin-ext_latin_devanagari-500italic.woff2") format("woff2"), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url("./fonts/poppins-v19-latin-ext_latin_devanagari-500italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + local(""), + url("./fonts/poppins-v19-latin-ext_latin_devanagari-500italic.woff2") format("woff2"), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url("./fonts/poppins-v19-latin-ext_latin_devanagari-500italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /** @@ -118,7 +128,11 @@ font-family: "Roboto Mono"; font-style: normal; font-weight: 400; - src: local(""), - url("./fonts/roboto-mono-v13-vietnamese_latin-ext_latin_greek_cyrillic-ext_cyrillic-regular.woff2") format("woff2"), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url("./fonts/roboto-mono-v13-vietnamese_latin-ext_latin_greek_cyrillic-ext_cyrillic-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + local(""), + url("./fonts/roboto-mono-v13-vietnamese_latin-ext_latin_greek_cyrillic-ext_cyrillic-regular.woff2") + format("woff2"), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url("./fonts/roboto-mono-v13-vietnamese_latin-ext_latin_greek_cyrillic-ext_cyrillic-regular.woff") + format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } diff --git a/web/src/assets/styles/app.scss b/web/src/assets/styles/app.scss index dab621c696..c8dfab146e 100644 --- a/web/src/assets/styles/app.scss +++ b/web/src/assets/styles/app.scss @@ -1,7 +1,11 @@ // Better alignment for expandable section with a sibling list ul.pf-v5-c-list + div.pf-v5-c-expandable-section { > button { - margin-inline-start: calc(var(--pf-v5-global--spacer--lg) - var(--pf-v5-global--spacer--sm) - var(--pf-v5-global--icon--FontSize--sm)); + margin-inline-start: calc( + var(--pf-v5-global--spacer--lg) - var(--pf-v5-global--spacer--sm) - var( + --pf-v5-global--icon--FontSize--sm + ) + ); } > div { diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index f580648b72..be65e47243 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -19,7 +19,7 @@ [data-type="agama/page-menu"] { > button { - --pf-v5-c-button--PaddingRight: 0 + --pf-v5-c-button--PaddingRight: 0; } a { @@ -66,7 +66,7 @@ ul[data-type="agama/list"] { background: var(--color-gray-light); margin-block-end: 0; - &:nth-child(n+2) { + &:nth-child(n + 2) { border-top: 0; } @@ -244,7 +244,8 @@ table[data-type="agama/tree-table"] { padding-inline-start: calc(var(--spacer-large) * 1.4); } - &.pf-m-tree-view-grid-md.pf-v5-c-table tr:where(.pf-v5-c-table__tr).pf-m-tree-view-details-expanded { + &.pf-m-tree-view-grid-md.pf-v5-c-table + tr:where(.pf-v5-c-table__tr).pf-m-tree-view-details-expanded { padding-block-end: var(--spacer-smaller); } @@ -258,11 +259,15 @@ table[data-type="agama/tree-table"] { display: inherit; } - &.pf-m-tree-view-grid-md.pf-v5-c-table tbody:where(.pf-v5-c-table__tbody) tr:where(.pf-v5-c-table__tr)::before { + &.pf-m-tree-view-grid-md.pf-v5-c-table + tbody:where(.pf-v5-c-table__tbody) + tr:where(.pf-v5-c-table__tr)::before { inset-inline-start: 0; } - &.pf-v5-c-table.pf-m-compact tr:where(.pf-v5-c-table__tr):not(.pf-v5-c-table__expandable-row) > *:last-child { + &.pf-v5-c-table.pf-m-compact + tr:where(.pf-v5-c-table__tr):not(.pf-v5-c-table__expandable-row) + > *:last-child { padding-inline-end: 8px; } @@ -277,7 +282,7 @@ table.devices-table { tr.dimmed-row { background-color: #fff; opacity: 0.8; - background: repeating-linear-gradient( -45deg, #fcfcff, #fcfcff 3px, #fff 3px, #fff 10px ); + background: repeating-linear-gradient(-45deg, #fcfcff, #fcfcff 3px, #fff 3px, #fff 10px); td { color: var(--color-gray-dimmed); @@ -304,7 +309,7 @@ table.proposal-result { tbody tr[aria-level="2"] th .pf-v5-c-table__tree-view-main { padding-inline-start: calc( - var(--pf-v5-c-table--m-compact--cell--first-last-child--PaddingLeft) + var(--spacer-large) + var(--pf-v5-c-table--m-compact--cell--first-last-child--PaddingLeft) + var(--spacer-large) ); } /** End of temporary hack */ @@ -324,7 +329,7 @@ table.proposal-result { &.selected::after { --arrow-size: var(--spacer-small, 10px); - content:''; + content: ""; position: absolute; bottom: -1px; left: 50%; @@ -357,8 +362,8 @@ table.proposal-result { [role="dialog"] { section:not([class^="pf-c"]) { > svg:first-child { - block-size: 24px; - inline-size: 24px; + block-size: 24px; + inline-size: 24px; } h2 { diff --git a/web/src/assets/styles/composition.scss b/web/src/assets/styles/composition.scss index eebf2a6c1d..e6f7956fd3 100644 --- a/web/src/assets/styles/composition.scss +++ b/web/src/assets/styles/composition.scss @@ -6,14 +6,17 @@ display: flex; flex-direction: column; flex: 1 1 0; - gap: 0 + gap: 0; } form > div:nth-child(2) { overflow-y: auto; min-block-size: 120px; margin-block-end: var(--spacer-medium); - table { background: transparent; } + + table { + background: transparent; + } } form > div:last-child { diff --git a/web/src/assets/styles/global.scss b/web/src/assets/styles/global.scss index 6bf961bd7d..0a0faf460e 100644 --- a/web/src/assets/styles/global.scss +++ b/web/src/assets/styles/global.scss @@ -1,24 +1,29 @@ // Global CSS starts -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { // margin: 0; font-family: var(--ff-headlines); font-weight: var(--fw-bold); } h1 { - font-size: var(--pf-v5-global--FontSize--2xl) + font-size: var(--pf-v5-global--FontSize--2xl); } h2 { - font-size: var(--pf-v5-global--FontSize--xl) + font-size: var(--pf-v5-global--FontSize--xl); } h3 { - font-size: var(--pf-v5-global--FontSize--lg) + font-size: var(--pf-v5-global--FontSize--lg); } h4 { - font-size: var(--pf-v5-global--FontSize--md) + font-size: var(--pf-v5-global--FontSize--md); } a { diff --git a/web/src/assets/styles/index.scss b/web/src/assets/styles/index.scss index e97db6b414..d8a72887da 100644 --- a/web/src/assets/styles/index.scss +++ b/web/src/assets/styles/index.scss @@ -9,4 +9,3 @@ @use "~/assets/styles/utilities.scss"; @use "~/assets/styles/composition.scss"; @use "~/assets/styles/blocks.scss"; - diff --git a/web/src/assets/styles/patternfly-overrides.scss b/web/src/assets/styles/patternfly-overrides.scss index 4cf2b70cd6..4fbdc7b943 100644 --- a/web/src/assets/styles/patternfly-overrides.scss +++ b/web/src/assets/styles/patternfly-overrides.scss @@ -131,7 +131,9 @@ .pf-v5-c-switch { // We prefer having same label color for checked and not checked switches - --pf-v5-c-switch__input--not-checked__label--Color: var(--pf-v5-c-switch__input--checked__label--Color); + --pf-v5-c-switch__input--not-checked__label--Color: var( + --pf-v5-c-switch__input--checked__label--Color + ); } // Make the switch focus looks like the rest @@ -143,9 +145,7 @@ // Avoid form select toggle icon overlap input Text .pf-v5-c-form-control__toggle-icon { padding-inline-end: 0; - margin-inline-start: calc( - var(--pf-v5-c-form-control__toggle-icon--PaddingRight) * 2 - ); + margin-inline-start: calc(var(--pf-v5-c-form-control__toggle-icon--PaddingRight) * 2); } // Adjust icons for a menu item @@ -230,7 +230,8 @@ .pf-v5-c-table tr[aria-level="1"] { border-block-end: 0; - border-block-start: var(--pf-v5-c-table--border-width--base) solid var(--pf-v5-c-table--BorderColor); + border-block-start: var(--pf-v5-c-table--border-width--base) solid + var(--pf-v5-c-table--BorderColor); } .pf-v5-c-table tr[aria-level="2"] { @@ -251,7 +252,6 @@ } } - // New-ui overrides // ================ @@ -260,7 +260,7 @@ fill: var(--pf-v5-c-nav__link--Color); } -.pf-v5-c-page__sidebar-body{ +.pf-v5-c-page__sidebar-body { fill: white; } @@ -275,8 +275,12 @@ // that knowst that link "isActive") .pf-v5-c-tabs__link.pf-m-current { - --pf-v5-c-tabs__link--after--BorderColor: var(--pf-v5-c-tabs__item--m-current__link--after--BorderColor); - --pf-v5-c-tabs__link--after--BorderWidth: var(--pf-v5-c-tabs__item--m-current__link--after--BorderWidth); + --pf-v5-c-tabs__link--after--BorderColor: var( + --pf-v5-c-tabs__item--m-current__link--after--BorderColor + ); + --pf-v5-c-tabs__link--after--BorderWidth: var( + --pf-v5-c-tabs__item--m-current__link--after--BorderWidth + ); } // Color for icons in Masthead diff --git a/web/src/assets/styles/utilities.scss b/web/src/assets/styles/utilities.scss index 980c327205..37aad08505 100644 --- a/web/src/assets/styles/utilities.scss +++ b/web/src/assets/styles/utilities.scss @@ -125,7 +125,11 @@ background-color: #fff; background-repeat: no-repeat; background-attachment: local, local, scroll, scroll; - background-size: 100% 48px, 100% 48px, 100% 16px, 100% 16px; + background-size: + 100% 48px, + 100% 48px, + 100% 16px, + 100% 16px; } // FIXME: drop as soon as Tip component gets rethought / refactored diff --git a/web/src/client/dbus.js b/web/src/client/dbus.js index da36d1c604..d3b74cc18d 100644 --- a/web/src/client/dbus.js +++ b/web/src/client/dbus.js @@ -116,9 +116,7 @@ class DBusClient { * @return {Promise} DBusProxies object */ async proxies(iface, path_namespace, options) { - const all = await this.client.proxies( - iface, path_namespace, { watch: true, ...options } - ); + const all = await this.client.proxies(iface, path_namespace, { watch: true, ...options }); await all.wait(); return all; } @@ -148,15 +146,16 @@ class DBusClient { let property; try { - const result = await this.client.call( - path, "org.freedesktop.DBus.Properties", "Get", [iface, name] - ); + const result = await this.client.call(path, "org.freedesktop.DBus.Properties", "Get", [ + iface, + name, + ]); property = result[0]; } catch (error) { console.warn(`Could not get the ${name} property in ${iface}`, error); } - return (property === undefined) ? null : property.v; + return property === undefined ? null : property.v; } /** @@ -172,14 +171,14 @@ class DBusClient { { path, interface: "org.freedesktop.DBus.Properties", - member: "PropertiesChanged" + member: "PropertiesChanged", }, (_path, _iface, _signal, args) => { const [source_iface, changes, invalid] = args; if (iface === source_iface) { handler(changes, invalid); } - } + }, ); return remove; } diff --git a/web/src/client/dbus.test.js b/web/src/client/dbus.test.js index ec0e270765..38f9c67ff0 100644 --- a/web/src/client/dbus.test.js +++ b/web/src/client/dbus.test.js @@ -25,13 +25,13 @@ import DBusClient from "./dbus"; import cockpit from "../lib/cockpit"; const proxyObject = { - wait: jest.fn().mockResolvedValue(null) + wait: jest.fn().mockResolvedValue(null), }; const cockpitDBusClient = { proxy: jest.fn().mockReturnValue(proxyObject), proxies: jest.fn().mockReturnValue(proxyObject), - call: jest.fn().mockReturnValue(true) + call: jest.fn().mockReturnValue(true), }; describe("DBusClient", () => { @@ -43,11 +43,13 @@ describe("DBusClient", () => { it("returns a proxy for the given iface and path", async () => { const client = new DBusClient("org.opensuse.Agama.Manager1"); const proxy = await client.proxy( - "org.opensuse.Agama.Manager1", "/org/opensuse/Agama/Manager1" + "org.opensuse.Agama.Manager1", + "/org/opensuse/Agama/Manager1", ); expect(cockpitDBusClient.proxy).toHaveBeenCalledWith( - "org.opensuse.Agama.Manager1", "/org/opensuse/Agama/Manager1", - { watch: true } + "org.opensuse.Agama.Manager1", + "/org/opensuse/Agama/Manager1", + { watch: true }, ); expect(proxy).toBe(proxyObject); }); @@ -71,13 +73,13 @@ describe("DBusClient", () => { "org.opensuse.Agama.Software1", "/org/opensuse/Agama/Software1", "SelectProduct", - ["alp"] + ["alp"], ); expect(cockpitDBusClient.call).toHaveBeenCalledWith( "org.opensuse.Agama.Software1", "/org/opensuse/Agama/Software1", "SelectProduct", - ["alp"] + ["alp"], ); expect(result).toEqual(true); }); diff --git a/web/src/client/http.js b/web/src/client/http.js index 682f17c220..3398fbb5bf 100644 --- a/web/src/client/http.js +++ b/web/src/client/http.js @@ -61,7 +61,7 @@ class WSClient { error: [], close: [], open: [], - events: [] + events: [], }; this.reconnectAttempts = 0; @@ -70,17 +70,18 @@ class WSClient { wsState() { const state = this.client.readyState; - if ((state !== SocketStates.CONNECTED) && (this.reconnectAttempts >= MAX_ATTEMPTS)) return SocketStates.UNRECOVERABLE; + if (state !== SocketStates.CONNECTED && this.reconnectAttempts >= MAX_ATTEMPTS) + return SocketStates.UNRECOVERABLE; return state; } isRecoverable() { - return (this.wsState() !== SocketStates.UNRECOVERABLE); + return this.wsState() !== SocketStates.UNRECOVERABLE; } isConnected() { - return (this.wsState() === SocketStates.CONNECTED); + return this.wsState() === SocketStates.CONNECTED; } buildClient() { @@ -254,7 +255,7 @@ class HTTPClient { const wsUrl = new URL(this.url.toString()); wsUrl.pathname = wsUrl.pathname.concat("api/ws"); - wsUrl.protocol = (this.url.protocol === "http:") ? "ws" : "wss"; + wsUrl.protocol = this.url.protocol === "http:" ? "ws" : "wss"; this._ws = new WSClient(wsUrl); return this._ws; } diff --git a/web/src/client/index.js b/web/src/client/index.js index 7816270a2e..b7131840a0 100644 --- a/web/src/client/index.js +++ b/web/src/client/index.js @@ -70,7 +70,7 @@ import { HTTPClient, WSClient } from "./http"; const createIssuesList = (product = [], software = [], storage = [], users = []) => { const list = { product, storage, software, users }; - list.isEmpty = !Object.values(list).some(v => v.length > 0); + list.isEmpty = !Object.values(list).some((v) => v.length > 0); return list; }; @@ -80,7 +80,7 @@ const createIssuesList = (product = [], software = [], storage = [], users = []) * @param {URL} url - URL of the HTTP API. * @return {InstallerClient} */ -const createClient = url => { +const createClient = (url) => { const client = new HTTPClient(url); const l10n = new L10nClient(client); // TODO: unify with the manager client @@ -115,16 +115,16 @@ const createClient = url => { * @param {IssuesHandler} handler - Callback function. * @return {() => void} - Function to deregister the callback. */ - const onIssuesChange = handler => { + const onIssuesChange = (handler) => { const unsubscribeCallbacks = []; - unsubscribeCallbacks.push(product.onIssuesChange(i => handler({ product: i }))); - unsubscribeCallbacks.push(storage.onIssuesChange(i => handler({ storage: i }))); - unsubscribeCallbacks.push(software.onIssuesChange(i => handler({ software: i }))); - unsubscribeCallbacks.push(users.onIssuesChange(i => handler({ users: i }))); + unsubscribeCallbacks.push(product.onIssuesChange((i) => handler({ product: i }))); + unsubscribeCallbacks.push(storage.onIssuesChange((i) => handler({ storage: i }))); + unsubscribeCallbacks.push(software.onIssuesChange((i) => handler({ software: i }))); + unsubscribeCallbacks.push(users.onIssuesChange((i) => handler({ users: i }))); return () => { - unsubscribeCallbacks.forEach(cb => cb()); + unsubscribeCallbacks.forEach((cb) => cb()); }; }; @@ -145,9 +145,9 @@ const createClient = url => { onIssuesChange, isConnected, isRecoverable, - onConnect: handler => client.ws().onOpen(handler), - onDisconnect: handler => client.ws().onClose(handler), - ws: () => client.ws() + onConnect: (handler) => client.ws().onOpen(handler), + onDisconnect: (handler) => client.ws().onClose(handler), + ws: () => client.ws(), }; }; diff --git a/web/src/client/manager.js b/web/src/client/manager.js index 4517d61e16..365a831b01 100644 --- a/web/src/client/manager.js +++ b/web/src/client/manager.js @@ -152,6 +152,6 @@ class ManagerClient extends WithProgress( WithStatus(ManagerBaseClient, "/manager/status", MANAGER_SERVICE), "/manager/progress", MANAGER_SERVICE, -) { } +) {} export { ManagerClient }; diff --git a/web/src/client/mixins.js b/web/src/client/mixins.js index 3c39361d07..5da50e515b 100644 --- a/web/src/client/mixins.js +++ b/web/src/client/mixins.js @@ -61,11 +61,7 @@ * @return {void} */ -const ISSUES_SOURCES = [ - "unknown", - "system", - "config", -]; +const ISSUES_SOURCES = ["unknown", "system", "config"]; const buildIssue = ({ description, details, source, severity }) => { return { @@ -219,8 +215,7 @@ const WithProgress = (superclass, progress_path, service_name) => finished: false, }; } else { - const { steps, currentStep, maxSteps, currentTitle, finished } = - await response.json(); + const { steps, currentStep, maxSteps, currentTitle, finished } = await response.json(); return { steps, total: maxSteps, @@ -266,7 +261,7 @@ const WithProgress = (superclass, progress_path, service_name) => /** * @param {string} message - Error message */ -const createError = message => { +const createError = (message) => { return { message }; }; diff --git a/web/src/client/monitor.js b/web/src/client/monitor.js index 51f2c0b54c..a298f64f17 100644 --- a/web/src/client/monitor.js +++ b/web/src/client/monitor.js @@ -24,7 +24,8 @@ import DBusClient from "./dbus"; const NAME_OWNER_CHANGED = { - interface: "org.freedesktop.DBus", member: "NameOwnerChanged" + interface: "org.freedesktop.DBus", + member: "NameOwnerChanged", }; /** diff --git a/web/src/client/network.test.js b/web/src/client/network.test.js index a42410ef70..ec2fef58e5 100644 --- a/web/src/client/network.test.js +++ b/web/src/client/network.test.js @@ -33,7 +33,7 @@ const mockWiredConnection = { method6: "manual", addresses: ["192.168.122.100/24"], nameservers: ["192.168.122.1"], - gateway4: "192.168.122.1" + gateway4: "192.168.122.1", }; const mockWirelessConnection = { @@ -44,9 +44,9 @@ const mockWirelessConnection = { passworkd: "agama.test", security: "wpa-psk", ssid: "Agama", - mode: "infrastructure" + mode: "infrastructure", }, - status: "down" + status: "down", }; const mockConnection = { @@ -57,14 +57,14 @@ const mockConnection = { method6: "manual", addresses: [{ address: "192.168.122.100", prefix: 24 }], nameservers: ["192.168.122.1"], - gateway4: "192.168.122.1" + gateway4: "192.168.122.1", }; const mockSettings = { hostname: "localhost.localdomain", connectivity: true, wireless_enabled: true, - networking_enabled: true + networking_enabled: true, }; const mockJsonFn = jest.fn(); @@ -93,7 +93,7 @@ describe("NetworkClient", () => { const client = new NetworkClient(http); mockJsonFn.mockResolvedValue([mockWiredConnection, mockWirelessConnection]); const connections = await client.connections(); - const eth0 = connections.find(c => c.id === "eth0"); + const eth0 = connections.find((c) => c.id === "eth0"); expect(eth0).toEqual(mockConnection); }); }); diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index dd8f02180e..e7a4df7545 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -35,7 +35,7 @@ const DeviceType = Object.freeze({ ETHERNET: 1, WIRELESS: 2, DUMMY: 3, - BOND: 4 + BOND: 4, }); /** @@ -54,7 +54,7 @@ const NetworkEventTypes = Object.freeze({ CONNECTION_ADDED: "connectionAdded", CONNECTION_UPDATED: "connectionUpdated", CONNECTION_REMOVED: "connectionRemoved", - SETTINGS_UPDATED: "settingsUpdated" + SETTINGS_UPDATED: "settingsUpdated", }); /** @@ -119,18 +119,20 @@ class NetworkClient { * @return {Device} */ fromApiDevice(device) { - const nameservers = (device?.ipConfig?.nameservers || []); + const nameservers = device?.ipConfig?.nameservers || []; const { ipConfig = {}, ...dev } = device; const routes4 = (ipConfig.routes4 || []).map((route) => { const [ip, netmask] = route.destination.split("/"); - const destination = (netmask !== undefined) ? { address: ip, prefix: ipPrefixFor(netmask) } : { address: ip }; + const destination = + netmask !== undefined ? { address: ip, prefix: ipPrefixFor(netmask) } : { address: ip }; return { ...route, destination }; }); const routes6 = (ipConfig.routes6 || []).map((route) => { const [ip, netmask] = route.destination.split("/"); - const destination = (netmask !== undefined) ? { address: ip, prefix: ipPrefixFor(netmask) } : { address: ip }; + const destination = + netmask !== undefined ? { address: ip, prefix: ipPrefixFor(netmask) } : { address: ip }; return { ...route, destination }; }); @@ -164,7 +166,7 @@ class NetworkClient { } fromApiConnection(connection) { - const nameservers = (connection.nameservers || []); + const nameservers = connection.nameservers || []; const addresses = (connection.addresses || []).map((address) => { const [ip, netmask] = address.split("/"); if (netmask !== undefined) { @@ -214,7 +216,7 @@ class NetworkClient { ssid: ap.ssid, hwAddress: ap.hw_address, strength: ap.strength, - security: securityFromFlags(ap.flags, ap.wpaFlags, ap.rsnFlags) + security: securityFromFlags(ap.flags, ap.wpaFlags, ap.rsnFlags), }); }); } @@ -242,31 +244,34 @@ class NetworkClient { return accessPoints .sort((a, b) => b.strength - a.strength) - .reduce((networks, ap) => { - // Do not include networks without SSID - if (!ap.ssid || ap.ssid === "") return networks; - // Do not include "duplicates" - if (knownSsids.includes(ap.ssid)) return networks; - - const network = { - ...ap, - settings: connections.find(c => c.wireless?.ssid === ap.ssid), - device: devices.find(c => c.connection === ap.ssid) - }; - - // Group networks - if (network.device) { - networks.connected.push(network); - } else if (network.settings) { - networks.configured.push(network); - } else { - networks.others.push(network); - } - - knownSsids.push(network.ssid); - - return networks; - }, { connected: [], configured: [], others: [] }); + .reduce( + (networks, ap) => { + // Do not include networks without SSID + if (!ap.ssid || ap.ssid === "") return networks; + // Do not include "duplicates" + if (knownSsids.includes(ap.ssid)) return networks; + + const network = { + ...ap, + settings: connections.find((c) => c.wireless?.ssid === ap.ssid), + device: devices.find((c) => c.connection === ap.ssid), + }; + + // Group networks + if (network.device) { + networks.connected.push(network); + } else if (network.settings) { + networks.configured.push(network); + } else { + networks.others.push(network); + } + + knownSsids.push(network.ssid); + + return networks; + }, + { connected: [], configured: [], others: [] }, + ); } /** @@ -312,7 +317,10 @@ class NetworkClient { * @return {Promise} the added connection */ async addConnection(connection) { - const response = await this.client.post("/network/connections", this.toApiConnection(connection)); + const response = await this.client.post( + "/network/connections", + this.toApiConnection(connection), + ); if (!response.ok) { console.error("Failed to post list of connections", response); return null; @@ -371,14 +379,14 @@ class NetworkClient { */ async addresses() { const conns = await this.connections(); - return conns.flatMap(c => c.addresses); + return conns.flatMap((c) => c.addresses); } /* - * Returns network general settings - * + * Returns network general settings + * * @return {Promise} - */ + */ async settings() { const response = await this.client.get("/network/state"); if (!response.ok) { @@ -410,9 +418,4 @@ class NetworkClient { } } -export { - ConnectionState, - ConnectionTypes, - NetworkClient, - NetworkEventTypes -}; +export { ConnectionState, ConnectionTypes, NetworkClient, NetworkEventTypes }; diff --git a/web/src/client/network/model.js b/web/src/client/network/model.js index d8b4d910e9..14aa4b31bd 100644 --- a/web/src/client/network/model.js +++ b/web/src/client/network/model.js @@ -33,7 +33,7 @@ const ConnectionState = Object.freeze({ ACTIVATING: 1, ACTIVATED: 2, DEACTIVATING: 3, - DEACTIVATED: 4 + DEACTIVATED: 4, }); const DeviceState = Object.freeze({ @@ -46,7 +46,7 @@ const DeviceState = Object.freeze({ NEEDAUTH: "needAuth", ACTIVATED: "activated", DEACTIVATING: "deactivating", - FAILED: "failed" + FAILED: "failed", }); /** @@ -72,14 +72,14 @@ const ConnectionTypes = Object.freeze({ BOND: "bond", BRIDGE: "bridge", VLAN: "vlan", - UNKNOWN: "unknown" + UNKNOWN: "unknown", }); const SecurityProtocols = Object.freeze({ WEP: "WEP", WPA: "WPA1", RSN: "WPA2", - _8021X: "802.1X" + _8021X: "802.1X", }); // security protocols @@ -198,7 +198,17 @@ const ApSecurityFlags = Object.freeze({ * @param {object} [options.wireless] Wireless Settings * @return {Connection} */ -const createConnection = ({ id, iface, method4, method6, gateway4, gateway6, addresses, nameservers, wireless }) => { +const createConnection = ({ + id, + iface, + method4, + method6, + gateway4, + gateway6, + addresses, + nameservers, + wireless, +}) => { const connection = { id, iface, @@ -207,7 +217,7 @@ const createConnection = ({ id, iface, method4, method6, gateway4, gateway6, add gateway4: gateway4 || "", gateway6: gateway6 || "", addresses: addresses || [], - nameservers: nameservers || [] + nameservers: nameservers || [], }; if (wireless) connection.wireless = wireless; @@ -215,7 +225,18 @@ const createConnection = ({ id, iface, method4, method6, gateway4, gateway6, add return connection; }; -const createDevice = ({ name, macAddress, method4, method6, gateway4, gateway6, addresses, nameservers, routes4, routes6 }) => { +const createDevice = ({ + name, + macAddress, + method4, + method6, + gateway4, + gateway6, + addresses, + nameservers, + routes4, + routes6, +}) => { return { name, macAddress, @@ -226,7 +247,7 @@ const createDevice = ({ name, macAddress, method4, method6, gateway4, gateway6, addresses: addresses || [], nameservers: nameservers || [], routes4: routes4 || [], - routes6: routes6 || [] + routes6: routes6 || [], }; }; @@ -240,14 +261,12 @@ const createDevice = ({ name, macAddress, method4, method6, gateway4, gateway6, * @param {string[]} [options.security] - Supported security protocols * @return {AccessPoint} */ -const createAccessPoint = ({ ssid, hwAddress, strength, security }) => ( - { - ssid, - hwAddress, - strength, - security: security || [] - } -); +const createAccessPoint = ({ ssid, hwAddress, strength, security }) => ({ + ssid, + hwAddress, + strength, + security: security || [], +}); /** * @param {number} flags - AP flags @@ -258,7 +277,7 @@ const createAccessPoint = ({ ssid, hwAddress, strength, security }) => ( const securityFromFlags = (flags, wpa_flags, rsn_flags) => { const security = []; - if ((flags & ApFlags.PRIVACY) && (wpa_flags === 0) && (rsn_flags === 0)) { + if (flags & ApFlags.PRIVACY && wpa_flags === 0 && rsn_flags === 0) { security.push(SecurityProtocols.WEP); } @@ -268,9 +287,7 @@ const securityFromFlags = (flags, wpa_flags, rsn_flags) => { if (rsn_flags > 0) { security.push(SecurityProtocols.RSN); } - if ( - (wpa_flags & ApSecurityFlags.KEY_MGMT_8021_X) || (rsn_flags & ApSecurityFlags.KEY_MGMT_8021_X) - ) { + if (wpa_flags & ApSecurityFlags.KEY_MGMT_8021_X || rsn_flags & ApSecurityFlags.KEY_MGMT_8021_X) { security.push(SecurityProtocols._8021X); } diff --git a/web/src/client/network/model.test.js b/web/src/client/network/model.test.js index e953552772..c25d998242 100644 --- a/web/src/client/network/model.test.js +++ b/web/src/client/network/model.test.js @@ -36,7 +36,7 @@ describe("createConnection", () => { addresses: [], nameservers: [], gateway4: "", - gateway6: "" + gateway6: "", }); expect(connection.wireless).toBeUndefined(); }); @@ -69,7 +69,7 @@ describe("createAccessPoint", () => { ssid: "WIFI1", hwAddress: "11:22:33:44:55:66", strength: 90, - security: [] + security: [], }); }); }); diff --git a/web/src/client/network/utils.js b/web/src/client/network/utils.js index 0e8ac55e82..b1dc153f1f 100644 --- a/web/src/client/network/utils.js +++ b/web/src/client/network/utils.js @@ -87,22 +87,23 @@ const intToIPString = (address) => { }; /** Convert a IP address from text to network byte-order -* -* FIXME: Currently it is assumed 'le' ordering which should be read from NetworkManager State -* -* @param {string} text - string representing an IPv4 address -* @return {number} IP address as network byte-order -*/ + * + * FIXME: Currently it is assumed 'le' ordering which should be read from NetworkManager State + * + * @param {string} text - string representing an IPv4 address + * @return {number} IP address as network byte-order + */ const stringToIPInt = (text) => { - if (text === "") - return 0; + if (text === "") return 0; const parts = text.split("."); const bytes = parts.map((s) => parseInt(s.trim())); let num = 0; const shift = (b) => 0x100 * num + b; - for (const n of bytes.reverse()) { num = shift(n) } + for (const n of bytes.reverse()) { + num = shift(n); + } return num; }; @@ -121,11 +122,4 @@ const formatIp = (addr) => { } }; -export { - isValidIp, - isValidIpPrefix, - intToIPString, - stringToIPInt, - formatIp, - ipPrefixFor -}; +export { isValidIp, isValidIpPrefix, intToIPString, stringToIPInt, formatIp, ipPrefixFor }; diff --git a/web/src/client/network/utils.test.js b/web/src/client/network/utils.test.js index de1a27fc9b..be79d3b627 100644 --- a/web/src/client/network/utils.test.js +++ b/web/src/client/network/utils.test.js @@ -21,7 +21,14 @@ // @ts-check -import { isValidIp, isValidIpPrefix, intToIPString, stringToIPInt, formatIp, ipPrefixFor } from "./utils"; +import { + isValidIp, + isValidIpPrefix, + intToIPString, + stringToIPInt, + formatIp, + ipPrefixFor, +} from "./utils"; describe("#isValidIp", () => { it("returns true when the IP is valid", () => { diff --git a/web/src/client/phase.js b/web/src/client/phase.js index 21edeb8278..5024a61448 100644 --- a/web/src/client/phase.js +++ b/web/src/client/phase.js @@ -26,5 +26,5 @@ export const INSTALL = 2; export default { STARTUP, CONFIG, - INSTALL + INSTALL, }; diff --git a/web/src/client/software.js b/web/src/client/software.js index 8c4ba092d0..0ecc03b073 100644 --- a/web/src/client/software.js +++ b/web/src/client/software.js @@ -336,7 +336,10 @@ class ProductBaseClient { } } -class ProductClient - extends WithIssues(ProductBaseClient, "/software/issues/product", PRODUCT_PATH) {} +class ProductClient extends WithIssues( + ProductBaseClient, + "/software/issues/product", + PRODUCT_PATH, +) {} export { ProductClient, SelectedBy, SoftwareClient }; diff --git a/web/src/client/software.test.js b/web/src/client/software.test.js index 007271894a..301f596b15 100644 --- a/web/src/client/software.test.js +++ b/web/src/client/software.test.js @@ -103,7 +103,7 @@ describe("ProductClient", () => { mockJsonFn.mockResolvedValue({ key: "", email: "", - requirement: "Optional" + requirement: "Optional", }); const http = new HTTPClient(new URL("http://localhost")); const client = new ProductClient(http); @@ -121,7 +121,7 @@ describe("ProductClient", () => { mockJsonFn.mockResolvedValue({ key: "111222", email: "test@test.com", - requirement: "Mandatory" + requirement: "Mandatory", }); const http = new HTTPClient(new URL("http://localhost")); const client = new ProductClient(http); diff --git a/web/src/client/status.js b/web/src/client/status.js index c2a31dec24..0eb3b42306 100644 --- a/web/src/client/status.js +++ b/web/src/client/status.js @@ -24,5 +24,5 @@ export const BUSY = 1; export default { BUSY, - IDLE + IDLE, }; diff --git a/web/src/client/storage.js b/web/src/client/storage.js index d7d59fbfc4..ab58fd31d7 100644 --- a/web/src/client/storage.js +++ b/web/src/client/storage.js @@ -174,7 +174,7 @@ class DBusClient { const ProposalTargets = Object.freeze({ DISK: "disk", NEW_LVM_VG: "newLvmVg", - REUSED_LVM_VG: "reusedLvmVg" + REUSED_LVM_VG: "reusedLvmVg", }); /** @@ -187,7 +187,7 @@ const VolumeTargets = Object.freeze({ NEW_PARTITION: "new_partition", NEW_VG: "new_vg", DEVICE: "device", - FILESYSTEM: "filesystem" + FILESYSTEM: "filesystem", }); /** @@ -198,7 +198,7 @@ const VolumeTargets = Object.freeze({ */ const EncryptionMethods = Object.freeze({ LUKS2: "luks2", - TPM: "tpm_fde" + TPM: "tpm_fde", }); /** @@ -240,20 +240,25 @@ class DevicesManager { name: "", description: "", isDrive: false, - type: "" + type: "", }; }; /** @type {(names: string[]) => StorageDevice[]} */ const buildCollectionFromNames = (names) => { - return names.map(name => ({ ...buildDefaultDevice(), name })); + return names.map((name) => ({ ...buildDefaultDevice(), name })); }; /** @type {(sids: String[], jsonDevices: object[]) => StorageDevice[]} */ const buildCollection = (sids, jsonDevices) => { if (sids === null || sids === undefined) return []; - return sids.map(sid => buildDevice(jsonDevices.find(dev => dev.deviceInfo?.sid === sid), jsonDevices)); + return sids.map((sid) => + buildDevice( + jsonDevices.find((dev) => dev.deviceInfo?.sid === sid), + jsonDevices, + ), + ); }; /** @type {(device: StorageDevice, info: object) => void} */ @@ -314,19 +319,19 @@ class DevicesManager { type: tableInfo.type, partitions, unpartitionedSize: device.size - partitions.reduce((s, p) => s + p.size, 0), - unusedSlots: tableInfo.unusedSlots.map(s => Object.assign({}, s)) + unusedSlots: tableInfo.unusedSlots.map((s) => Object.assign({}, s)), }; }; /** @type {(device: StorageDevice, filesystemInfo: object) => void} */ const addFilesystemInfo = (device, filesystemInfo) => { - const buildMountPath = path => path.length > 0 ? path : undefined; - const buildLabel = label => label.length > 0 ? label : undefined; + const buildMountPath = (path) => (path.length > 0 ? path : undefined); + const buildLabel = (label) => (label.length > 0 ? label : undefined); device.filesystem = { sid: filesystemInfo.sid, type: filesystemInfo.type, mountPath: buildMountPath(filesystemInfo.mountPath), - label: buildLabel(filesystemInfo.label) + label: buildLabel(filesystemInfo.label), }; }; @@ -334,7 +339,7 @@ class DevicesManager { const addComponentInfo = (device, info) => { device.component = { type: info.type, - deviceNames: info.deviceNames + deviceNames: info.deviceNames, }; }; @@ -370,7 +375,7 @@ class DevicesManager { return []; } const jsonDevices = await response.json(); - return jsonDevices.map(d => buildDevice(d, jsonDevices)); + return jsonDevices.map((d) => buildDevice(d, jsonDevices)); } } @@ -394,7 +399,7 @@ class ProposalManager { */ async getAvailableDevices() { const findDevice = (devices, sid) => { - const device = devices.find(d => d.sid === sid); + const device = devices.find((d) => d.sid === sid); if (device === undefined) console.warn("Device not found: ", sid); @@ -409,7 +414,7 @@ class ProposalManager { return []; } const usable_devices = await response.json(); - return usable_devices.map(sid => findDevice(systemDevices, sid)).filter(d => d); + return usable_devices.map((sid) => findDevice(systemDevices, sid)).filter((d) => d); } /** @@ -430,18 +435,18 @@ class ProposalManager { const isAvailable = (device) => { const isChildren = (device, parentDevice) => { const partitions = parentDevice.partitionTable?.partitions || []; - return !!partitions.find(d => d.name === device.name); + return !!partitions.find((d) => d.name === device.name); }; - return !!availableDevices.find(d => d.name === device.name || isChildren(device, d)); + return !!availableDevices.find((d) => d.name === device.name || isChildren(device, d)); }; /** @type {(device: StorageDevice[]) => boolean} */ const allAvailable = (devices) => devices.every(isAvailable); const system = await this.system.getDevices(); - const mds = system.filter(d => d.type === "md" && allAvailable(d.devices)); - const vgs = system.filter(d => d.type === "lvmVg" && allAvailable(d.physicalVolumes)); + const mds = system.filter((d) => d.type === "md" && allAvailable(d.devices)); + const vgs = system.filter((d) => d.type === "lvmVg" && allAvailable(d.physicalVolumes)); return [...availableDevices, ...mds, ...vgs]; } @@ -458,7 +463,7 @@ class ProposalManager { return []; } - return response.json().then(params => params.mountPoints); + return response.json().then((params) => params.mountPoints); } /** @@ -473,7 +478,7 @@ class ProposalManager { return []; } - return response.json().then(params => params.encryptionMethods); + return response.json().then((params) => params.encryptionMethods); } /** @@ -493,7 +498,7 @@ class ProposalManager { const systemDevices = await this.system.getDevices(); const productMountPoints = await this.getProductMountPoints(); - return response.json().then(volume => { + return response.json().then((volume) => { return this.buildVolume(volume, systemDevices, productMountPoints); }); } @@ -502,7 +507,7 @@ class ProposalManager { * Gets the values of the current proposal * * @return {Promise} - */ + */ async getResult() { const settingsResponse = await this.client.get("/storage/proposal/settings"); if (!settingsResponse.ok) { @@ -524,9 +529,12 @@ class ProposalManager { */ const buildTarget = (value) => { switch (value) { - case "disk": return "DISK"; - case "newLvmVg": return "NEW_LVM_VG"; - case "reusedLvmVg": return "REUSED_LVM_VG"; + case "disk": + return "DISK"; + case "newLvmVg": + return "NEW_LVM_VG"; + case "reusedLvmVg": + return "REUSED_LVM_VG"; default: console.info(`Unknown proposal target "${value}", using "disk".`); return "DISK"; @@ -536,7 +544,7 @@ class ProposalManager { /** @todo Read installation devices from D-Bus. */ const buildInstallationDevices = (settings, devices) => { const findDevice = (name) => { - const device = devices.find(d => d.name === name); + const device = devices.find((d) => d.name === name); if (device === undefined) console.error("Device object not found: ", name); @@ -546,19 +554,19 @@ class ProposalManager { // Only consider the device assigned to a volume as installation device if it is needed // to find space in that device. For example, devices directly formatted or mounted are not // considered as installation devices. - const volumes = settings.volumes.filter(vol => ( - [VolumeTargets.NEW_PARTITION, VolumeTargets.NEW_VG].includes(vol.target)) + const volumes = settings.volumes.filter((vol) => + [VolumeTargets.NEW_PARTITION, VolumeTargets.NEW_VG].includes(vol.target), ); const values = [ settings.targetDevice, settings.targetPVDevices, - volumes.map(v => v.targetDevice) + volumes.map((v) => v.targetDevice), ].flat(); if (settings.configureBoot) values.push(settings.bootDevice); - const names = uniq(compact(values)).filter(d => d.length > 0); + const names = uniq(compact(values)).filter((d) => d.length > 0); // #findDevice returns undefined if no device is found with the given name. return compact(names.sort().map(findDevice)); @@ -574,14 +582,16 @@ class ProposalManager { settings: { ...settings, target: buildTarget(settings.target), - volumes: settings.volumes.map(v => this.buildVolume(v, systemDevices, productMountPoints)), + volumes: settings.volumes.map((v) => + this.buildVolume(v, systemDevices, productMountPoints), + ), // NOTE: strictly speaking, installation devices does not belong to the settings. It // should be a separate method instead of an attribute in the settings object. // Nevertheless, it was added here for simplicity and to avoid passing more props in some // react components. Please, do not use settings as a jumble. - installationDevices: buildInstallationDevices(settings, systemDevices) + installationDevices: buildInstallationDevices(settings, systemDevices), }, - actions + actions, }; } @@ -602,7 +612,7 @@ class ProposalManager { mountPath: volume.mountPath, snapshots: volume.snapshots, target: VolumeTargets[volume.target], - targetDevice: volume.targetDevice?.name + targetDevice: volume.targetDevice?.name, }; }; @@ -618,7 +628,7 @@ class ProposalManager { target: ProposalTargets[settings.target], targetDevice: settings.targetDevice, targetPVDevices: settings.targetPVDevices, - volumes: settings.volumes?.map(buildHttpVolume) + volumes: settings.volumes?.map(buildHttpVolume), }; }; @@ -659,11 +669,16 @@ class ProposalManager { */ const buildTarget = (value) => { switch (value) { - case "default": return "DEFAULT"; - case "new_partition": return "NEW_PARTITION"; - case "new_vg": return "NEW_VG"; - case "device": return "DEVICE"; - case "filesystem": return "FILESYSTEM"; + case "default": + return "DEFAULT"; + case "new_partition": + return "NEW_PARTITION"; + case "new_vg": + return "NEW_VG"; + case "device": + return "DEVICE"; + case "filesystem": + return "FILESYSTEM"; default: console.info(`Unknown volume target "${value}", using "default".`); return "DEFAULT"; @@ -673,7 +688,7 @@ class ProposalManager { const volume = { ...rawVolume, target: buildTarget(rawVolume.target), - targetDevice: devices.find(d => d.name === rawVolume.targetDevice) + targetDevice: devices.find((d) => d.name === rawVolume.targetDevice), }; // Indicate whether a volume is defined by the product. @@ -731,7 +746,7 @@ class DASDManager { return { path: job.path, running: job.Running, - exitCode: job.ExitCode + exitCode: job.ExitCode, }; } @@ -762,7 +777,7 @@ class DASDManager { */ async format(devices) { const proxy = await this.managerProxy(); - const devicesPath = devices.map(d => this.devicePath(d)); + const devicesPath = devices.map((d) => this.devicePath(d)); proxy.Format(devicesPath); } @@ -774,7 +789,7 @@ class DASDManager { */ async setDIAG(devices, value) { const proxy = await this.managerProxy(); - const devicesPath = devices.map(d => this.devicePath(d)); + const devicesPath = devices.map((d) => this.devicePath(d)); proxy.SetDiag(devicesPath, value); } @@ -785,7 +800,7 @@ class DASDManager { */ async enableDevices(devices) { const proxy = await this.managerProxy(); - const devicesPath = devices.map(d => this.devicePath(d)); + const devicesPath = devices.map((d) => this.devicePath(d)); proxy.Enable(devicesPath); } @@ -796,7 +811,7 @@ class DASDManager { */ async disableDevices(devices) { const proxy = await this.managerProxy(); - const devicesPath = devices.map(d => this.devicePath(d)); + const devicesPath = devices.map((d) => this.devicePath(d)); proxy.Disable(devicesPath); } @@ -817,7 +832,8 @@ class DASDManager { async getJobs() { const proxy = await this.jobsProxy(); - return Object.values(proxy).filter(p => p.Running) + return Object.values(proxy) + .filter((p) => p.Running) .map(this.buildJob); } @@ -920,7 +936,7 @@ class DASDManager { name: device.DeviceName, partitionInfo: enabled ? device.PartitionInfo : "", status: device.Status, - type: device.Type + type: device.Type, }; } @@ -1146,7 +1162,10 @@ class ZFCPManager { */ async controllersProxy() { if (!this.proxies.controllers) - this.proxies.controllers = await this.client().proxies(ZFCP_CONTROLLER_IFACE, ZFCP_CONTROLLERS_NAMESPACE); + this.proxies.controllers = await this.client().proxies( + ZFCP_CONTROLLER_IFACE, + ZFCP_CONTROLLERS_NAMESPACE, + ); return this.proxies.controllers; } @@ -1258,7 +1277,7 @@ class ZFCPManager { id: dbusBasename(proxy.path), active: proxy.Active, lunScan: proxy.LUNScan, - channel: proxy.Channel + channel: proxy.Channel, }; } @@ -1289,7 +1308,7 @@ class ZFCPManager { name: proxy.Name, channel: proxy.Channel, wwpn: proxy.WWPN, - lun: proxy.LUN + lun: proxy.LUN, }; } @@ -1608,8 +1627,12 @@ class StorageBaseClient { */ class StorageClient extends WithIssues( WithProgress( - WithStatus(StorageBaseClient, "/storage/status", SERVICE_NAME), "/storage/progress", SERVICE_NAME - ), "/storage/issues", SERVICE_NAME -) { } + WithStatus(StorageBaseClient, "/storage/status", SERVICE_NAME), + "/storage/progress", + SERVICE_NAME, + ), + "/storage/issues", + SERVICE_NAME, +) {} export { StorageClient, EncryptionMethods }; diff --git a/web/src/client/storage.test.js b/web/src/client/storage.test.js index 704b485633..f481599a25 100644 --- a/web/src/client/storage.test.js +++ b/web/src/client/storage.test.js @@ -60,7 +60,7 @@ const sda = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }; @@ -77,10 +77,10 @@ const sda1 = { start: 123, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], udevPaths: [], - isEFI: true + isEFI: true, }; /** @type {StorageDevice} */ @@ -95,10 +95,10 @@ const sda2 = { start: 1789, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], udevPaths: [], - isEFI: false + isEFI: false, }; /** @type {StorageDevice} */ @@ -121,9 +121,9 @@ const sdb = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }; /** @type {StorageDevice} */ @@ -146,9 +146,9 @@ const sdc = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; /** @type {StorageDevice} */ @@ -171,9 +171,9 @@ const sdd = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; /** @type {StorageDevice} */ @@ -196,9 +196,9 @@ const sde = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; /** @type {StorageDevice} */ @@ -216,10 +216,10 @@ const md0 = { encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, devices: [], - systems : ["openSUSE Leap 15.2"], + systems: ["openSUSE Leap 15.2"], udevIds: [], udevPaths: [], - filesystem: { sid: 100, type: "ext4", mountPath: "/test", label: "system" } + filesystem: { sid: 100, type: "ext4", mountPath: "/test", label: "system" }, }; /** @type {StorageDevice} */ @@ -243,9 +243,9 @@ const raid = { encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, devices: [], - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; /** @type {StorageDevice} */ @@ -268,9 +268,9 @@ const multipath = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; /** @type {StorageDevice} */ @@ -293,9 +293,9 @@ const dasd = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; /** @type {StorageDevice} */ @@ -318,9 +318,9 @@ const sdf = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; /** @type {StorageDevice} */ @@ -335,10 +335,10 @@ const sdf1 = { start: 1024, encrypted: true, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], udevPaths: [], - isEFI: false + isEFI: false, }; /** @type {StorageDevice} */ @@ -348,7 +348,7 @@ const lvmVg = { type: "lvmVg", name: "/dev/vg0", description: "LVM", - size: 512 + size: 512, }; /** @type {StorageDevice} */ @@ -363,9 +363,9 @@ const lvmLv1 = { start: 0, encrypted: false, shrinking: { supported: 128 }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; // Define relationship between devices @@ -374,49 +374,49 @@ sda.partitionTable = { type: "gpt", partitions: [sda1, sda2], unpartitionedSize: 256, - unusedSlots: [{ start: 1234, size: 256 }] + unusedSlots: [{ start: 1234, size: 256 }], }; sda1.component = { type: "md_device", - deviceNames: ["/dev/md0"] + deviceNames: ["/dev/md0"], }; sda2.component = { type: "md_device", - deviceNames: ["/dev/md0"] + deviceNames: ["/dev/md0"], }; sdb.component = { type: "raid_device", - deviceNames: ["/dev/mapper/isw_ddgdcbibhd_244"] + deviceNames: ["/dev/mapper/isw_ddgdcbibhd_244"], }; sdc.component = { type: "raid_device", - deviceNames: ["/dev/mapper/isw_ddgdcbibhd_244"] + deviceNames: ["/dev/mapper/isw_ddgdcbibhd_244"], }; sdd.component = { type: "multipath_wire", - deviceNames: ["/dev/mapper/36005076305ffc73a00000000000013b4"] + deviceNames: ["/dev/mapper/36005076305ffc73a00000000000013b4"], }; sde.component = { type: "multipath_wire", - deviceNames: ["/dev/mapper/36005076305ffc73a00000000000013b4"] + deviceNames: ["/dev/mapper/36005076305ffc73a00000000000013b4"], }; sdf.partitionTable = { type: "gpt", partitions: [sdf1], unpartitionedSize: 1536, - unusedSlots: [] + unusedSlots: [], }; sdf1.component = { type: "physical_volume", - deviceNames: ["/dev/vg0"] + deviceNames: ["/dev/vg0"], }; md0.devices = [sda1, sda2]; @@ -427,15 +427,15 @@ raid.devices = [ name: "/dev/sdb", description: "", isDrive: false, - type: "" + type: "", }, { sid: 0, name: "/dev/sdc", description: "", isDrive: false, - type: "" - } + type: "", + }, ]; multipath.wires = [ @@ -444,22 +444,36 @@ multipath.wires = [ name: "/dev/sdd", description: "", isDrive: false, - type: "" + type: "", }, { sid: 0, name: "/dev/sde", description: "", isDrive: false, - type: "" - } + type: "", + }, ]; lvmVg.logicalVolumes = [lvmLv1]; lvmVg.physicalVolumes = [sdf1]; const systemDevices = { - sda, sda1, sda2, sdb, sdc, sdd, sde, md0, raid, multipath, dasd, sdf, sdf1, lvmVg, lvmLv1 + sda, + sda1, + sda2, + sdb, + sdc, + sdd, + sde, + md0, + raid, + multipath, + dasd, + sdf, + sdf1, + lvmVg, + lvmLv1, }; // Staging devices @@ -486,9 +500,9 @@ const sdbStaging = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }; const stagingDevices = { sdb: sdbStaging }; @@ -507,7 +521,7 @@ const contexts = { spacePolicy: "custom", spaceActions: [ { device: "/dev/sda", action: "force_delete" }, - { device: "/dev/sdb", action: "resize" } + { device: "/dev/sdb", action: "resize" }, ], volumes: [ { @@ -527,8 +541,8 @@ const contexts = { snapshotsConfigurable: true, snapshotsAffectSizes: true, adjustByRam: false, - sizeRelevantVolumes: ["/home"] - } + sizeRelevantVolumes: ["/home"], + }, }, { mountPath: "/home", @@ -547,19 +561,19 @@ const contexts = { snapshotsConfigurable: false, snapshotsAffectSizes: false, adjustByRam: false, - sizeRelevantVolumes: [] - } - } - ] + sizeRelevantVolumes: [], + }, + }, + ], }, - actions: [{ device: 2, text: "Mount /dev/sdb1 as root", subvol: false, delete: false }] + actions: [{ device: 2, text: "Mount /dev/sdb1 as root", subvol: false, delete: false }], }; }, withAvailableDevices: () => [59, 62], withIssues: () => [ { description: "Issue 1", details: "", source: 1, severity: 1 }, { description: "Issue 2", details: "", source: 1, severity: 0 }, - { description: "Issue 3", details: "", source: 2, severity: 1 } + { description: "Issue 3", details: "", source: 2, severity: 1 }, ], withoutISCSINodes: () => { cockpitProxies.iscsiNodes = {}; @@ -600,7 +614,7 @@ const contexts = { Formatted: false, Id: "0.0.019e", PartitionInfo: "", - Type: "ECKD" + Type: "ECKD", }, "/org/opensuse/Agama/Storage1/dasds/9": { path: "/org/opensuse/Agama/Storage1/dasds/9", @@ -611,8 +625,8 @@ const contexts = { Formatted: false, Id: "0.0.ffff", PartitionInfo: "/dev/dasd_sample_9", - Type: "FBA" - } + Type: "FBA", + }, }; }, withoutZFCPControllers: () => { @@ -624,14 +638,14 @@ const contexts = { path: "/org/opensuse/Agama/Storage1/zfcp_controllers/1", Active: false, LUNScan: false, - Channel: "0.0.fa00" + Channel: "0.0.fa00", }, "/org/opensuse/Agama/Storage1/zfcp_controllers/2": { path: "/org/opensuse/Agama/Storage1/zfcp_controllers/2", Active: false, LUNScan: false, - Channel: "0.0.fc00" - } + Channel: "0.0.fc00", + }, }; }, withoutZFCPDisks: () => { @@ -644,15 +658,15 @@ const contexts = { Name: "/dev/sda", Channel: "0.0.fa00", WWPN: "0x500507630703d3b3", - LUN: "0x0000000000000000" + LUN: "0x0000000000000000", }, "/org/opensuse/Agama/Storage1/zfcp_disks/2": { path: "/org/opensuse/Agama/Storage1/zfcp_disks/2", Name: "/dev/sdb", Channel: "0.0.fa00", WWPN: "0x500507630703d3b3", - LUN: "0x0000000000000001" - } + LUN: "0x0000000000000001", + }, }; }, withSystemDevices: () => [ @@ -660,7 +674,7 @@ const contexts = { deviceInfo: { sid: 59, name: "/dev/sda", - description: "" + description: "", }, blockDevice: { active: true, @@ -670,7 +684,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], - udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"] + udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }, drive: { type: "disk", @@ -682,20 +696,20 @@ const contexts = { transport: "usb", info: { dellBOSS: false, - sdCard: true - } + sdCard: true, + }, }, partitionTable: { type: "gpt", partitions: [60, 61], - unusedSlots: [{ start: 1234, size: 256 }] - } + unusedSlots: [{ start: 1234, size: 256 }], + }, }, { deviceInfo: { sid: 60, name: "/dev/sda1", - description: "" + description: "", }, partition: { efi: true }, blockDevice: { @@ -706,19 +720,19 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, component: { type: "md_device", deviceNames: ["/dev/md0"], - devices: [66] - } + devices: [66], + }, }, { deviceInfo: { sid: 61, name: "/dev/sda2", - description: "" + description: "", }, partition: { efi: false }, blockDevice: { @@ -729,19 +743,19 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, component: { type: "md_device", deviceNames: ["/dev/md0"], - devices: [66] - } + devices: [66], + }, }, { deviceInfo: { sid: 62, name: "/dev/sdb", - description: "" + description: "", }, blockDevice: { active: true, @@ -751,7 +765,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }, drive: { type: "disk", @@ -763,20 +777,20 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } + sdCard: false, + }, }, component: { type: "raid_device", deviceNames: ["/dev/mapper/isw_ddgdcbibhd_244"], - devices: [67] - } + devices: [67], + }, }, { deviceInfo: { sid: 63, name: "/dev/sdc", - description: "" + description: "", }, blockDevice: { active: true, @@ -786,7 +800,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, drive: { type: "disk", @@ -798,20 +812,20 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } + sdCard: false, + }, }, component: { type: "raid_device", deviceNames: ["/dev/mapper/isw_ddgdcbibhd_244"], - devices: [67] - } + devices: [67], + }, }, { deviceInfo: { sid: 64, name: "/dev/sdd", - description: "" + description: "", }, blockDevice: { active: true, @@ -821,7 +835,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, drive: { type: "disk", @@ -833,20 +847,20 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } + sdCard: false, + }, }, component: { type: "multipath_wire", deviceNames: ["/dev/mapper/36005076305ffc73a00000000000013b4"], - devices: [68] - } + devices: [68], + }, }, { deviceInfo: { sid: 65, name: "/dev/sde", - description: "" + description: "", }, blockDevice: { active: true, @@ -856,7 +870,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, drive: { type: "disk", @@ -868,20 +882,20 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } + sdCard: false, + }, }, component: { type: "multipath_wire", deviceNames: ["/dev/mapper/36005076305ffc73a00000000000013b4"], - devices: [68] - } + devices: [68], + }, }, { deviceInfo: { sid: 66, name: "/dev/md0", - description: "EXT4 RAID" + description: "EXT4 RAID", }, blockDevice: { active: true, @@ -891,25 +905,25 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: ["openSUSE Leap 15.2"], udevIds: [], - udevPaths: [] + udevPaths: [], }, md: { level: "raid0", uuid: "12345:abcde", - devices: [60, 61] + devices: [60, 61], }, filesystem: { sid: 100, type: "ext4", mountPath: "/test", - label: "system" - } + label: "system", + }, }, { deviceInfo: { sid: 67, name: "/dev/mapper/isw_ddgdcbibhd_244", - description: "" + description: "", }, blockDevice: { active: true, @@ -919,7 +933,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, drive: { type: "raid", @@ -931,18 +945,18 @@ const contexts = { transport: "", info: { dellBOSS: true, - sdCard: false - } + sdCard: false, + }, }, raid: { - devices: ["/dev/sdb", "/dev/sdc"] - } + devices: ["/dev/sdb", "/dev/sdc"], + }, }, { deviceInfo: { sid: 68, name: "/dev/mapper/36005076305ffc73a00000000000013b4", - description: "" + description: "", }, blockDevice: { active: true, @@ -952,7 +966,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, drive: { type: "multipath", @@ -964,18 +978,18 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } + sdCard: false, + }, }, multipath: { - wires: ["/dev/sdd", "/dev/sde"] - } + wires: ["/dev/sdd", "/dev/sde"], + }, }, { deviceInfo: { sid: 69, name: "/dev/dasda", - description: "" + description: "", }, blockDevice: { active: true, @@ -985,7 +999,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, drive: { type: "dasd", @@ -997,15 +1011,15 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } - } + sdCard: false, + }, + }, }, { deviceInfo: { sid: 70, name: "/dev/sdf", - description: "" + description: "", }, blockDevice: { active: true, @@ -1015,7 +1029,7 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, drive: { type: "disk", @@ -1027,20 +1041,20 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } + sdCard: false, + }, }, partitionTable: { type: "gpt", partitions: [71], - unusedSlots: [] - } + unusedSlots: [], + }, }, { deviceInfo: { sid: 71, name: "/dev/sdf1", - description: "PV of vg0" + description: "PV of vg0", }, partition: { efi: false }, blockDevice: { @@ -1051,32 +1065,32 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, component: { type: "physical_volume", deviceNames: ["/dev/vg0"], - devices: [72] - } + devices: [72], + }, }, { deviceInfo: { sid: 72, name: "/dev/vg0", - description: "LVM" + description: "LVM", }, lvmVg: { type: "physical_volume", size: 512, physicalVolumes: [71], - logicalVolumes: [73] - } + logicalVolumes: [73], + }, }, { deviceInfo: { sid: 73, name: "/dev/vg0/lv1", - description: "" + description: "", }, blockDevice: { active: true, @@ -1086,11 +1100,11 @@ const contexts = { shrinking: { supported: 128 }, systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }, lvmLv: { - volumeGroup: [72] - } + volumeGroup: [72], + }, }, ], withStagingDevices: () => [ @@ -1098,7 +1112,7 @@ const contexts = { deviceInfo: { sid: 62, name: "/dev/sdb", - description: "" + description: "", }, drive: { type: "disk", @@ -1110,8 +1124,8 @@ const contexts = { transport: "", info: { dellBOSS: false, - sdCard: false - } + sdCard: false, + }, }, blockDevice: { active: true, @@ -1121,28 +1135,37 @@ const contexts = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] - } - } - ] + udevPaths: ["pci-0000:00-19"], + }, + }, + ], }; const mockProxy = (iface, path) => { switch (iface) { - case "org.opensuse.Agama.Storage1.ISCSI.Initiator": return cockpitProxies.iscsiInitiator; - case "org.opensuse.Agama.Storage1.ISCSI.Node": return cockpitProxies.iscsiNode[path]; - case "org.opensuse.Agama.Storage1.DASD.Manager": return cockpitProxies.dasdManager; - case "org.opensuse.Agama.Storage1.ZFCP.Manager": return cockpitProxies.zfcpManager; - case "org.opensuse.Agama.Storage1.ZFCP.Controller": return cockpitProxies.zfcpController[path]; + case "org.opensuse.Agama.Storage1.ISCSI.Initiator": + return cockpitProxies.iscsiInitiator; + case "org.opensuse.Agama.Storage1.ISCSI.Node": + return cockpitProxies.iscsiNode[path]; + case "org.opensuse.Agama.Storage1.DASD.Manager": + return cockpitProxies.dasdManager; + case "org.opensuse.Agama.Storage1.ZFCP.Manager": + return cockpitProxies.zfcpManager; + case "org.opensuse.Agama.Storage1.ZFCP.Controller": + return cockpitProxies.zfcpController[path]; } }; const mockProxies = (iface) => { switch (iface) { - case "org.opensuse.Agama.Storage1.ISCSI.Node": return cockpitProxies.iscsiNodes; - case "org.opensuse.Agama.Storage1.DASD.Device": return cockpitProxies.dasdDevices; - case "org.opensuse.Agama.Storage1.ZFCP.Controller": return cockpitProxies.zfcpControllers; - case "org.opensuse.Agama.Storage1.ZFCP.Disk": return cockpitProxies.zfcpDisks; + case "org.opensuse.Agama.Storage1.ISCSI.Node": + return cockpitProxies.iscsiNodes; + case "org.opensuse.Agama.Storage1.DASD.Device": + return cockpitProxies.dasdDevices; + case "org.opensuse.Agama.Storage1.ZFCP.Controller": + return cockpitProxies.zfcpControllers; + case "org.opensuse.Agama.Storage1.ZFCP.Disk": + return cockpitProxies.zfcpDisks; } }; @@ -1189,7 +1212,7 @@ let http; jest.mock("./http", () => { return { - HTTPClient: jest.fn().mockImplementation(() => mockHTTPClient) + HTTPClient: jest.fn().mockImplementation(() => mockHTTPClient), }; }); @@ -1202,7 +1225,7 @@ beforeEach(() => { proxy: mockProxy, proxies: mockProxies, onObjectChanged: mockOnObjectChanged, - call: mockCall + call: mockCall, }; }); @@ -1276,13 +1299,10 @@ describe("#isDeprecated", () => { describe("when the HTTP call fails", () => { beforeEach(() => { - mockGetFn.mockImplementation(path => { - if (path === "/storage/devices/dirty") - return { ok: false, json: undefined }; - else - return { ok: true, json: mockJsonFn }; - } - ); + mockGetFn.mockImplementation((path) => { + if (path === "/storage/devices/dirty") return { ok: false, json: undefined }; + else return { ok: true, json: mockJsonFn }; + }); client = new StorageClient(http); }); @@ -1305,10 +1325,7 @@ describe.skip("#onDeprecate", () => { describe("if the system was not deprecated", () => { beforeEach(() => { - emitSignal( - "/org/opensuse/Agama/Storage1", - "org.opensuse.Agama.Storage1", - {}); + emitSignal("/org/opensuse/Agama/Storage1", "org.opensuse.Agama.Storage1", {}); }); it("does not run the handler", async () => { @@ -1318,10 +1335,9 @@ describe.skip("#onDeprecate", () => { describe("if the system was deprecated", () => { beforeEach(() => { - emitSignal( - "/org/opensuse/Agama/Storage1", - "org.opensuse.Agama.Storage1", - { DeprecatedSystem: true }); + emitSignal("/org/opensuse/Agama/Storage1", "org.opensuse.Agama.Storage1", { + DeprecatedSystem: true, + }); }); it("runs the handler", async () => { @@ -1353,11 +1369,13 @@ describe("#getIssues", () => { it("returns the list of issues", async () => { const issues = await client.getIssues(); - expect(issues).toEqual(expect.arrayContaining([ - { description: "Issue 1", details: "", source: "system", severity: "error" }, - { description: "Issue 2", details: "", source: "system", severity: "warn" }, - { description: "Issue 3", details: "", source: "config", severity: "error" } - ])); + expect(issues).toEqual( + expect.arrayContaining([ + { description: "Issue 1", details: "", source: "system", severity: "error" }, + { description: "Issue 2", details: "", source: "system", severity: "warn" }, + { description: "Issue 3", details: "", source: "config", severity: "error" }, + ]), + ); }); }); }); @@ -1370,7 +1388,9 @@ describe("#getErrors", () => { it("returns the issues with error severity", async () => { const errors = await client.getErrors(); - expect(errors.map(e => e.description)).toEqual(expect.arrayContaining(["Issue 1", "Issue 3"])); + expect(errors.map((e) => e.description)).toEqual( + expect.arrayContaining(["Issue 1", "Issue 3"]), + ); }); }); @@ -1382,10 +1402,14 @@ describe.skip("#onIssuesChange", () => { const handler = jest.fn(); client.onIssuesChange(handler); - emitSignal( - "/org/opensuse/Agama/Storage1", - "org.opensuse.Agama1.Issues", - { All: { v: [["Issue 1", "", 1, 0], ["Issue 2", "", 2, 1]] } }); + emitSignal("/org/opensuse/Agama/Storage1", "org.opensuse.Agama1.Issues", { + All: { + v: [ + ["Issue 1", "", 1, 0], + ["Issue 2", "", 2, 1], + ], + }, + }); expect(handler).toHaveBeenCalledWith([ { description: "Issue 1", details: "", source: "system", severity: "warn" }, @@ -1424,13 +1448,10 @@ describe("#system", () => { describe("when the HTTP call fails", () => { beforeEach(() => { - mockGetFn.mockImplementation(path => { - if (path === "/storage/devices/system") - return { ok: false, json: undefined }; - else - return { ok: true, json: mockJsonFn }; - } - ); + mockGetFn.mockImplementation((path) => { + if (path === "/storage/devices/system") return { ok: false, json: undefined }; + else return { ok: true, json: mockJsonFn }; + }); client = new StorageClient(http); }); @@ -1473,13 +1494,10 @@ describe("#staging", () => { describe("when the HTTP call fails", () => { beforeEach(() => { - mockGetFn.mockImplementation(path => { - if (path === "/storage/devices/result") - return { ok: false, json: undefined }; - else - return { ok: true, json: mockJsonFn }; - } - ); + mockGetFn.mockImplementation((path) => { + if (path === "/storage/devices/result") return { ok: false, json: undefined }; + else return { ok: true, json: mockJsonFn }; + }); client = new StorageClient(http); }); @@ -1499,7 +1517,7 @@ describe("#proposal", () => { beforeEach(() => { response = { ok: true, json: jest.fn().mockResolvedValue(contexts.withAvailableDevices()) }; - mockGetFn.mockImplementation(path => { + mockGetFn.mockImplementation((path) => { switch (path) { case "/storage/devices/system": return { ok: true, json: jest.fn().mockResolvedValue(contexts.withSystemDevices()) }; @@ -1543,13 +1561,10 @@ describe("#proposal", () => { describe("when the HTTP call fails", () => { beforeEach(() => { - mockGetFn.mockImplementation(path => { - if (path === "/storage/product/params") - return { ok: false, json: undefined }; - else - return { ok: true, json: mockJsonFn }; - } - ); + mockGetFn.mockImplementation((path) => { + if (path === "/storage/product/params") return { ok: false, json: undefined }; + else return { ok: true, json: mockJsonFn }; + }); client = new StorageClient(http); }); @@ -1574,13 +1589,10 @@ describe("#proposal", () => { describe("when the HTTP call fails", () => { beforeEach(() => { - mockGetFn.mockImplementation(path => { - if (path === "/storage/product/params") - return { ok: false, json: undefined }; - else - return { ok: true, json: mockJsonFn }; - } - ); + mockGetFn.mockImplementation((path) => { + if (path === "/storage/product/params") return { ok: false, json: undefined }; + else return { ok: true, json: mockJsonFn }; + }); client = new StorageClient(http); }); @@ -1619,9 +1631,9 @@ describe("#proposal", () => { snapshotsConfigurable: false, snapshotsAffectSizes: false, adjustByRam: false, - sizeRelevantVolumes: [] - } - }) + sizeRelevantVolumes: [], + }, + }), }; default: return { @@ -1643,19 +1655,22 @@ describe("#proposal", () => { snapshotsConfigurable: false, snapshotsAffectSizes: false, adjustByRam: false, - sizeRelevantVolumes: [] - } - }) + sizeRelevantVolumes: [], + }, + }), }; } }; - mockGetFn.mockImplementation(path => { + mockGetFn.mockImplementation((path) => { switch (path) { case "/storage/devices/system": return { ok: true, json: jest.fn().mockResolvedValue(contexts.withSystemDevices()) }; case "/storage/product/params": - return { ok: true, json: jest.fn().mockResolvedValue({ mountPoints: ["/", "swap", "/home"] }) }; + return { + ok: true, + json: jest.fn().mockResolvedValue({ mountPoints: ["/", "swap", "/home"] }), + }; // GET for /storage/product/volume_for?path=XX default: return response(path); @@ -1686,8 +1701,8 @@ describe("#proposal", () => { snapshotsAffectSizes: false, adjustByRam: false, sizeRelevantVolumes: [], - productDefined: true - } + productDefined: true, + }, }); const generic = await client.proposal.defaultVolume(""); @@ -1710,8 +1725,8 @@ describe("#proposal", () => { snapshotsAffectSizes: false, adjustByRam: false, sizeRelevantVolumes: [], - productDefined: false - } + productDefined: false, + }, }); }); @@ -1750,7 +1765,7 @@ describe("#proposal", () => { const proposal = contexts.withProposal(); mockJsonFn.mockResolvedValue(proposal.settings); - mockGetFn.mockImplementation(path => { + mockGetFn.mockImplementation((path) => { switch (path) { case "/storage/devices/system": return { ok: true, json: jest.fn().mockResolvedValue(contexts.withSystemDevices()) }; @@ -1759,7 +1774,10 @@ describe("#proposal", () => { case "/storage/proposal/actions": return { ok: true, json: jest.fn().mockResolvedValue(proposal.actions) }; case "/storage/product/params": - return { ok: true, json: jest.fn().mockResolvedValue({ mountPoints: ["/", "swap"] }) }; + return { + ok: true, + json: jest.fn().mockResolvedValue({ mountPoints: ["/", "swap"] }), + }; } }); }); @@ -1777,7 +1795,7 @@ describe("#proposal", () => { spacePolicy: "custom", spaceActions: [ { device: "/dev/sda", action: "force_delete" }, - { device: "/dev/sdb", action: "resize" } + { device: "/dev/sdb", action: "resize" }, ], volumes: [ { @@ -1797,8 +1815,8 @@ describe("#proposal", () => { snapshotsConfigurable: true, snapshotsAffectSizes: true, sizeRelevantVolumes: ["/home"], - productDefined: true - } + productDefined: true, + }, }, { mountPath: "/home", @@ -1817,26 +1835,28 @@ describe("#proposal", () => { snapshotsConfigurable: false, snapshotsAffectSizes: false, sizeRelevantVolumes: [], - productDefined: false - } - } - ] + productDefined: false, + }, + }, + ], }); - expect(settings.installationDevices.map(d => d.name).sort()).toStrictEqual( - ["/dev/sda", "/dev/sdb"].sort() + expect(settings.installationDevices.map((d) => d.name).sort()).toStrictEqual( + ["/dev/sda", "/dev/sdb"].sort(), ); expect(actions).toStrictEqual([ - { device: 2, text: "Mount /dev/sdb1 as root", subvol: false, delete: false } + { device: 2, text: "Mount /dev/sdb1 as root", subvol: false, delete: false }, ]); }); describe("if boot is not configured", () => { beforeEach(() => { - mockJsonFn.mockResolvedValue( - { ...contexts.withProposal().settings, configureBoot: false, bootDevice: "/dev/sdc" } - ); + mockJsonFn.mockResolvedValue({ + ...contexts.withProposal().settings, + configureBoot: false, + bootDevice: "/dev/sdc", + }); }); it("does not include the boot device as installation device", async () => { @@ -1851,7 +1871,7 @@ describe("#proposal", () => { let response = { ok: true, json: jest.fn().mockResolvedValue(true) }; beforeEach(() => { - mockPutFn.mockImplementation(path => { + mockPutFn.mockImplementation((path) => { if (path === "/storage/proposal/settings") return response; return { ok: true }; @@ -1881,13 +1901,13 @@ describe("#proposal", () => { minSize: 1024, maxSize: 2048, autoSize: false, - snapshots: true + snapshots: true, }, { mountPath: "/test2", - minSize: 1024 - } - ] + minSize: 1024, + }, + ], }); expect(mockPutFn).toHaveBeenCalledWith("/storage/proposal/settings", { @@ -1905,13 +1925,13 @@ describe("#proposal", () => { minSize: 1024, maxSize: 2048, autoSize: false, - snapshots: true + snapshots: true, }, { mountPath: "/test2", - minSize: 1024 - } - ] + minSize: 1024, + }, + ], }); }); @@ -1921,7 +1941,9 @@ describe("#proposal", () => { spaceActions: [{ device: "/dev/sda", action: "resize" }], }); - expect(mockPutFn).toHaveBeenCalledWith("/storage/proposal/settings", { spacePolicy: "delete" }); + expect(mockPutFn).toHaveBeenCalledWith("/storage/proposal/settings", { + spacePolicy: "delete", + }); }); it("returns false if the call fails", async () => { @@ -1958,7 +1980,7 @@ describe.skip("#dasd", () => { hexId: 414, name: "sample_dasd_device", partitionInfo: "", - type: "ECKD" + type: "ECKD", }; const probeFn = jest.fn(); @@ -1972,7 +1994,7 @@ describe.skip("#dasd", () => { Probe: probeFn, SetDiag: setDiagFn, Enable: enableFn, - Disable: disableFn + Disable: disableFn, }; contexts.withDASDDevices(); }); @@ -2008,7 +2030,7 @@ describe.skip("#dasd", () => { hexId: 414, name: "dasd_sample_8", partitionInfo: "", - type: "ECKD" + type: "ECKD", }); expect(result).toContainEqual({ id: "9", @@ -2020,7 +2042,7 @@ describe.skip("#dasd", () => { hexId: 65535, name: "dasd_sample_9", partitionInfo: "/dev/dasd_sample_9", - type: "FBA" + type: "FBA", }); }); }); @@ -2029,16 +2051,10 @@ describe.skip("#dasd", () => { describe("#setDIAG", () => { it("requests for setting DIAG for given devices", async () => { await client.dasd.setDIAG([sampleDasdDevice], true); - expect(setDiagFn).toHaveBeenCalledWith( - ["/org/opensuse/Agama/Storage1/dasds/8"], - true - ); + expect(setDiagFn).toHaveBeenCalledWith(["/org/opensuse/Agama/Storage1/dasds/8"], true); await client.dasd.setDIAG([sampleDasdDevice], false); - expect(setDiagFn).toHaveBeenCalledWith( - ["/org/opensuse/Agama/Storage1/dasds/8"], - false - ); + expect(setDiagFn).toHaveBeenCalledWith(["/org/opensuse/Agama/Storage1/dasds/8"], false); }); }); @@ -2063,25 +2079,23 @@ describe.skip("#zfcp", () => { let disksCallbacks; const mockEventListener = (proxy, callbacks) => { - proxy.addEventListener = jest.fn().mockImplementation( - (signal, handler) => { - if (!callbacks[signal]) callbacks[signal] = []; - callbacks[signal].push(handler); - } - ); + proxy.addEventListener = jest.fn().mockImplementation((signal, handler) => { + if (!callbacks[signal]) callbacks[signal] = []; + callbacks[signal].push(handler); + }); proxy.removeEventListener = jest.fn(); }; const emitSignals = (callbacks, signal, proxy) => { - callbacks[signal].forEach(handler => handler(null, proxy)); + callbacks[signal].forEach((handler) => handler(null, proxy)); }; beforeEach(() => { client = new StorageClient(); cockpitProxies.zfcpManager = { Probe: probeFn, - AllowLUNScan: true + AllowLUNScan: true, }; controllersCallbacks = {}; @@ -2171,13 +2185,13 @@ describe.skip("#zfcp", () => { id: "1", active: false, lunScan: false, - channel: "0.0.fa00" + channel: "0.0.fa00", }); expect(result).toContainEqual({ id: "2", active: false, lunScan: false, - channel: "0.0.fc00" + channel: "0.0.fc00", }); }); }); @@ -2208,14 +2222,14 @@ describe.skip("#zfcp", () => { name: "/dev/sda", channel: "0.0.fa00", wwpn: "0x500507630703d3b3", - lun: "0x0000000000000000" + lun: "0x0000000000000000", }); expect(result).toContainEqual({ id: "2", name: "/dev/sdb", channel: "0.0.fa00", wwpn: "0x500507630703d3b3", - lun: "0x0000000000000001" + lun: "0x0000000000000001", }); }); }); @@ -2225,12 +2239,12 @@ describe.skip("#zfcp", () => { const wwpns = ["0x500507630703d3b3", "0x500507630708d3b3"]; const controllerProxy = { - GetWWPNs: jest.fn().mockReturnValue(wwpns) + GetWWPNs: jest.fn().mockReturnValue(wwpns), }; beforeEach(() => { cockpitProxies.zfcpController = { - "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy + "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy, }; }); @@ -2253,16 +2267,16 @@ describe.skip("#zfcp", () => { describe("#getLUNs", () => { const luns = { - "0x500507630703d3b3": ["0x0000000000000000", "0x0000000000000001", "0x0000000000000002"] + "0x500507630703d3b3": ["0x0000000000000000", "0x0000000000000001", "0x0000000000000002"], }; const controllerProxy = { - GetLUNs: jest.fn().mockImplementation(wwpn => luns[wwpn]) + GetLUNs: jest.fn().mockImplementation((wwpn) => luns[wwpn]), }; beforeEach(() => { cockpitProxies.zfcpController = { - "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy + "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy, }; }); @@ -2287,12 +2301,12 @@ describe.skip("#zfcp", () => { const activateFn = jest.fn().mockReturnValue(0); const controllerProxy = { - Activate: activateFn + Activate: activateFn, }; beforeEach(() => { cockpitProxies.zfcpController = { - "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy + "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy, }; }); @@ -2318,17 +2332,21 @@ describe.skip("#zfcp", () => { const activateDiskFn = jest.fn().mockReturnValue(0); const controllerProxy = { - ActivateDisk: activateDiskFn + ActivateDisk: activateDiskFn, }; beforeEach(() => { cockpitProxies.zfcpController = { - "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy + "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy, }; }); it("tries to activate the given zFCP disk", async () => { - const result = await client.zfcp.activateDisk({ id: "1" }, "0x500507630703d3b3", "0x0000000000000000"); + const result = await client.zfcp.activateDisk( + { id: "1" }, + "0x500507630703d3b3", + "0x0000000000000000", + ); expect(activateDiskFn).toHaveBeenCalledWith("0x500507630703d3b3", "0x0000000000000000"); expect(result).toEqual(0); }); @@ -2339,7 +2357,11 @@ describe.skip("#zfcp", () => { }); it("returns undefined", async () => { - const result = await client.zfcp.activateDisk({ id: "1" }, "0x500507630703d3b3", "0x0000000000000000"); + const result = await client.zfcp.activateDisk( + { id: "1" }, + "0x500507630703d3b3", + "0x0000000000000000", + ); expect(result).toBeUndefined(); }); }); @@ -2349,17 +2371,21 @@ describe.skip("#zfcp", () => { const deactivateDiskFn = jest.fn().mockReturnValue(0); const controllerProxy = { - ActivateDisk: deactivateDiskFn + ActivateDisk: deactivateDiskFn, }; beforeEach(() => { cockpitProxies.zfcpController = { - "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy + "/org/opensuse/Agama/Storage1/zfcp_controllers/1": controllerProxy, }; }); it("tries to deactivate the given zFCP disk", async () => { - const result = await client.zfcp.activateDisk({ id: "1" }, "0x500507630703d3b3", "0x0000000000000000"); + const result = await client.zfcp.activateDisk( + { id: "1" }, + "0x500507630703d3b3", + "0x0000000000000000", + ); expect(deactivateDiskFn).toHaveBeenCalledWith("0x500507630703d3b3", "0x0000000000000000"); expect(result).toEqual(0); }); @@ -2370,7 +2396,11 @@ describe.skip("#zfcp", () => { }); it("returns undefined", async () => { - const result = await client.zfcp.deactivateDisk({ id: "1" }, "0x500507630703d3b3", "0x0000000000000000"); + const result = await client.zfcp.deactivateDisk( + { id: "1" }, + "0x500507630703d3b3", + "0x0000000000000000", + ); expect(result).toBeUndefined(); }); }); @@ -2385,11 +2415,14 @@ describe.skip("#zfcp", () => { path: "/org/opensuse/Agama/Storage1/zfcp_controllers/1", Active: true, LUNScan: true, - Channel: "0.0.fa00" + Channel: "0.0.fa00", }); expect(handler).toHaveBeenCalledWith({ - id: "1", active: true, lunScan: true, channel: "0.0.fa00" + id: "1", + active: true, + lunScan: true, + channel: "0.0.fa00", }); }); }); @@ -2404,7 +2437,7 @@ describe.skip("#zfcp", () => { Name: "/dev/sda", Channel: "0.0.fa00", WWPN: "0x500507630703d3b3", - LUN: "0x0000000000000000" + LUN: "0x0000000000000000", }); expect(handler).toHaveBeenCalledWith({ @@ -2412,7 +2445,7 @@ describe.skip("#zfcp", () => { name: "/dev/sda", channel: "0.0.fa00", wwpn: "0x500507630703d3b3", - lun: "0x0000000000000000" + lun: "0x0000000000000000", }); }); }); @@ -2427,7 +2460,7 @@ describe.skip("#zfcp", () => { Name: "/dev/sda", Channel: "0.0.fa00", WWPN: "0x500507630703d3b3", - LUN: "0x0000000000000000" + LUN: "0x0000000000000000", }); expect(handler).toHaveBeenCalledWith({ @@ -2435,7 +2468,7 @@ describe.skip("#zfcp", () => { name: "/dev/sda", channel: "0.0.fa00", wwpn: "0x500507630703d3b3", - lun: "0x0000000000000000" + lun: "0x0000000000000000", }); }); }); @@ -2450,7 +2483,7 @@ describe.skip("#zfcp", () => { Name: "/dev/sda", Channel: "0.0.fa00", WWPN: "0x500507630703d3b3", - LUN: "0x0000000000000000" + LUN: "0x0000000000000000", }); expect(handler).toHaveBeenCalledWith({ @@ -2458,7 +2491,7 @@ describe.skip("#zfcp", () => { name: "/dev/sda", channel: "0.0.fa00", wwpn: "0x500507630703d3b3", - lun: "0x0000000000000000" + lun: "0x0000000000000000", }); }); }); @@ -2573,19 +2606,18 @@ describe("#iscsi", () => { reversePassword: "nonsecret", }; await client.iscsi.discover("192.168.100.101", 3260, options); - expect(mockPostFn).toHaveBeenCalledWith( - "/storage/iscsi/discover", - { address: "192.168.100.101", port: 3260, options }, - ); + expect(mockPostFn).toHaveBeenCalledWith("/storage/iscsi/discover", { + address: "192.168.100.101", + port: 3260, + options, + }); }); }); describe("#delete", () => { it("deletes the given iSCSI node", async () => { await client.iscsi.delete({ id: "1" }); - expect(mockDeleteFn).toHaveBeenCalledWith( - "/storage/iscsi/nodes/1", - ); + expect(mockDeleteFn).toHaveBeenCalledWith("/storage/iscsi/nodes/1"); }); }); @@ -2602,16 +2634,11 @@ describe("#iscsi", () => { const result = await client.iscsi.login({ id: "1" }, auth); expect(result).toEqual(0); - expect(mockPostFn).toHaveBeenCalledWith( - "/storage/iscsi/nodes/1/login", - auth, - ); + expect(mockPostFn).toHaveBeenCalledWith("/storage/iscsi/nodes/1/login", auth); }); it("returns 1 when the startup is invalid", async () => { - mockPostFn.mockImplementation(() => ( - { ok: false, json: mockJsonFn } - )); + mockPostFn.mockImplementation(() => ({ ok: false, json: mockJsonFn })); mockJsonFn.mockResolvedValue("InvalidStartup"); const result = await client.iscsi.login({ id: "1" }, { ...auth, startup: "invalid" }); @@ -2619,9 +2646,7 @@ describe("#iscsi", () => { }); it("returns 2 in case of an error different from an invalid startup value", async () => { - mockPostFn.mockImplementation(() => ( - { ok: false, json: mockJsonFn } - )); + mockPostFn.mockImplementation(() => ({ ok: false, json: mockJsonFn })); mockJsonFn.mockResolvedValue("Failed"); const result = await client.iscsi.login({ id: "1" }, { ...auth, startup: "invalid" }); diff --git a/web/src/client/users.js b/web/src/client/users.js index 761d4f8714..1e1bc2c768 100644 --- a/web/src/client/users.js +++ b/web/src/client/users.js @@ -26,10 +26,10 @@ import { WithIssues } from "./mixins"; const SERVICE_NAME = "org.opensuse.Agama.Manager1"; /** -* @typedef {object} UserResult -* @property {boolean} result - whether the action succeeded or not -* @property {string[]} issues - issues found when applying the action -*/ + * @typedef {object} UserResult + * @property {boolean} result - whether the action succeeded or not + * @property {string[]} issues - issues found when applying the action + */ /** * @typedef {object} User @@ -41,11 +41,11 @@ const SERVICE_NAME = "org.opensuse.Agama.Manager1"; */ /** -* @typedef {object} UserSettings -* @property {User} [firstUser] - first user -* @property {boolean} [rootPasswordSet] - whether the root password is set -* @property {string} [rootSSHKey] - root SSH public key -*/ + * @typedef {object} UserSettings + * @property {User} [firstUser] - first user + * @property {boolean} [rootPasswordSet] - whether the root password is set + * @property {string} [rootSSHKey] - root SSH public key + */ /** * Users client diff --git a/web/src/components/core/About.jsx b/web/src/components/core/About.jsx index 736dbef7a0..15ac6ced4f 100644 --- a/web/src/components/core/About.jsx +++ b/web/src/components/core/About.jsx @@ -66,32 +66,31 @@ export default function About({ {buttonText} - + { // TRANSLATORS: content of the "About" popup (1/2) - _("Agama is an experimental installer for (open)SUSE systems. It \ + _( + "Agama is an experimental installer for (open)SUSE systems. It \ is still under development so, please, do not use it in \ production environments. If you want to give it a try, we \ recommend using a virtual machine to prevent any possible \ -data loss.") +data loss.", + ) } - { - sprintf( - // TRANSLATORS: content of the "About" popup (2/2) - // %s is replaced by the project URL - _("For more information, please visit the project's repository at %s."), - "https://github.com/openSUSE/agama" - ) - } + {sprintf( + // TRANSLATORS: content of the "About" popup (2/2) + // %s is replaced by the project URL + _("For more information, please visit the project's repository at %s."), + "https://github.com/openSUSE/agama", + )} - {_("Close")} + + {_("Close")} + diff --git a/web/src/components/core/About.test.jsx b/web/src/components/core/About.test.jsx index 6c379cd1b6..d0c9d1e498 100644 --- a/web/src/components/core/About.test.jsx +++ b/web/src/components/core/About.test.jsx @@ -29,19 +29,19 @@ import About from "./About"; describe("About", () => { it("renders a help icon inside the button by default", () => { const { container } = plainRender(); - const icon = container.querySelector('svg'); + const icon = container.querySelector("svg"); expect(icon).toHaveAttribute("data-icon-name", "help"); }); it("does not render a help icon inside the button if showIcon=false", () => { const { container } = plainRender(); - const icon = container.querySelector('svg'); + const icon = container.querySelector("svg"); expect(icon).toBeNull(); }); it("allows setting its icon size", () => { const { container } = plainRender(); - const icon = container.querySelector('svg'); + const icon = container.querySelector("svg"); expect(icon.classList.contains("icon-xxs")).toBe(true); }); diff --git a/web/src/components/core/ButtonLink.jsx b/web/src/components/core/ButtonLink.jsx index 5afe4448fd..87492b79a9 100644 --- a/web/src/components/core/ButtonLink.jsx +++ b/web/src/components/core/ButtonLink.jsx @@ -23,7 +23,7 @@ import React from "react"; import { Link } from "react-router-dom"; -import buttonStyles from '@patternfly/react-styles/css/components/Button/button'; +import buttonStyles from "@patternfly/react-styles/css/components/Button/button"; // TODO: Evaluate which is better, this approach or just using a // PF/Button with onClick callback and "component" prop sets as "a" @@ -32,12 +32,10 @@ export default function ButtonLink({ to, isPrimary = false, children, ...props } return ( {children} diff --git a/web/src/components/core/CardField.jsx b/web/src/components/core/CardField.jsx index e38356a010..5695cb4c16 100644 --- a/web/src/components/core/CardField.jsx +++ b/web/src/components/core/CardField.jsx @@ -23,10 +23,15 @@ import React from "react"; import { - Card, CardHeader, CardTitle, CardBody, CardFooter, - Flex, FlexItem, + Card, + CardHeader, + CardTitle, + CardBody, + CardFooter, + Flex, + FlexItem, } from "@patternfly/react-core"; -import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; +import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; // FIXME: improve name and documentation // TODO: allows having a drawer, see storage/ProposalResultActions @@ -45,8 +50,7 @@ const CardField = ({ children = [], cardProps = {}, cardHeaderProps = {}, - cardDescriptionProps = {} - + cardDescriptionProps = {}, }) => { // TODO: replace aria-label with the proper aria-labelledby return ( @@ -63,7 +67,11 @@ const CardField = ({ - {description &&
{description}
} + {description && ( + +
{description}
+
+ )} {children} {actions && {actions}} diff --git a/web/src/components/core/Description.jsx b/web/src/components/core/Description.jsx index 0d5e271f1a..829f149fe5 100644 --- a/web/src/components/core/Description.jsx +++ b/web/src/components/core/Description.jsx @@ -33,11 +33,13 @@ import { Popover, Button } from "@patternfly/react-core"; * @param {React.ReactNode} props.children - The wrapped content. * @param {import("@patternfly/react-core").PopoverProps} [props.otherProps] */ -export default function Description ({ description, children, ...otherProps }) { +export default function Description({ description, children, ...otherProps }) { if (description) { return ( - + ); } diff --git a/web/src/components/core/Drawer.jsx b/web/src/components/core/Drawer.jsx index 7a6c85f6a4..504f09afa6 100644 --- a/web/src/components/core/Drawer.jsx +++ b/web/src/components/core/Drawer.jsx @@ -23,8 +23,14 @@ import React, { forwardRef, useImperativeHandle, useState } from "react"; import { - Drawer as PFDrawer, DrawerPanelBody, - DrawerPanelContent, DrawerContent, DrawerContentBody, DrawerHead, DrawerActions, DrawerCloseButton, + Drawer as PFDrawer, + DrawerPanelBody, + DrawerPanelContent, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerActions, + DrawerCloseButton, } from "@patternfly/react-core"; /** @@ -37,41 +43,36 @@ import { * * @todo write documentation */ -const Drawer = forwardRef( - ({ panelHeader, panelContent, isExpanded = false, children }, ref) => { - const [isOpen, setIsOpen] = useState(isExpanded); - const open = () => setIsOpen(true); - const close = () => setIsOpen(false); - const publicAPI = () => ({ open, close }); +const Drawer = forwardRef(({ panelHeader, panelContent, isExpanded = false, children }, ref) => { + const [isOpen, setIsOpen] = useState(isExpanded); + const open = () => setIsOpen(true); + const close = () => setIsOpen(false); + const publicAPI = () => ({ open, close }); - useImperativeHandle(ref, publicAPI, []); + useImperativeHandle(ref, publicAPI, []); - const onEscape = event => event.key === 'Escape' && close(); + const onEscape = (event) => event.key === "Escape" && close(); - return ( - - - - {panelHeader} - - - - - - {panelContent} - - - } - colorVariant="no-background" - > - - {children} - - - - ); - }); + return ( + + + + {panelHeader} + + + + + {panelContent} + + } + colorVariant="no-background" + > + {children} + + + ); +}); export default Drawer; diff --git a/web/src/components/core/EmailInput.jsx b/web/src/components/core/EmailInput.jsx index dc84711157..7c80797ec5 100644 --- a/web/src/components/core/EmailInput.jsx +++ b/web/src/components/core/EmailInput.jsx @@ -32,23 +32,24 @@ import { noop } from "~/utils"; * @returns {boolean} */ const validateEmail = (email) => { - const regexp = /^[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/; + const regexp = + /^[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/; const validateFormat = (email) => { - const parts = email.split('@'); + const parts = email.split("@"); return parts.length === 2 && regexp.test(email); }; const validateSizes = (email) => { - const [account, address] = email.split('@'); + const [account, address] = email.split("@"); if (account.length > 64) return false; if (address.length > 255) return false; - const domainParts = address.split('.'); + const domainParts = address.split("."); - if (domainParts.find(p => p.length > 63)) return false; + if (domainParts.find((p) => p.length > 63)) return false; return true; }; @@ -76,11 +77,7 @@ export default function EmailInput({ onValidate = noop, ...props }) { return ( - + ); } diff --git a/web/src/components/core/EmailInput.test.jsx b/web/src/components/core/EmailInput.test.jsx index 0c64ce08b7..786b3c4bd5 100644 --- a/web/src/components/core/EmailInput.test.jsx +++ b/web/src/components/core/EmailInput.test.jsx @@ -28,15 +28,10 @@ import { plainRender } from "~/test-utils"; describe("EmailInput component", () => { it("renders an email input", () => { plainRender( - + , ); - const inputField = screen.getByRole('textbox', { name: "User email" }); + const inputField = screen.getByRole("textbox", { name: "User email" }); expect(inputField).toHaveAttribute("type", "email"); }); @@ -63,7 +58,7 @@ describe("EmailInput component", () => { it("triggers onChange callback", async () => { const { user } = plainRender(); - const emailInput = screen.getByRole('textbox', { name: "Test email" }); + const emailInput = screen.getByRole("textbox", { name: "Test email" }); expect(screen.queryByText("Email value updated!")).toBeNull(); @@ -73,7 +68,7 @@ describe("EmailInput component", () => { it("triggers onValidate callback", async () => { const { user } = plainRender(); - const emailInput = screen.getByRole('textbox', { name: "Test email" }); + const emailInput = screen.getByRole("textbox", { name: "Test email" }); expect(screen.queryByText("Email is not valid!")).toBeNull(); @@ -83,7 +78,7 @@ describe("EmailInput component", () => { it("marks the input as invalid if the value is not a valid email", async () => { const { user } = plainRender(); - const emailInput = screen.getByRole('textbox', { name: "Test email" }); + const emailInput = screen.getByRole("textbox", { name: "Test email" }); await user.type(emailInput, "foo"); diff --git a/web/src/components/core/EmptyState.jsx b/web/src/components/core/EmptyState.jsx index 32c256aa46..487a9279c4 100644 --- a/web/src/components/core/EmptyState.jsx +++ b/web/src/components/core/EmptyState.jsx @@ -57,7 +57,7 @@ export default function EmptyStateWrapper({ children, ...rest }) { - if (noPadding) rest.className = [rest.className, 'no-padding'].join(" ").trim(); + if (noPadding) rest.className = [rest.className, "no-padding"].join(" ").trim(); return ( diff --git a/web/src/components/core/ExpandableSelector.jsx b/web/src/components/core/ExpandableSelector.jsx index aaad695abd..8d3b5e09e3 100644 --- a/web/src/components/core/ExpandableSelector.jsx +++ b/web/src/components/core/ExpandableSelector.jsx @@ -22,7 +22,16 @@ // @ts-check import React, { useState } from "react"; -import { Table, Thead, Tr, Th, Tbody, Td, ExpandableRowContent, RowSelectVariant } from "@patternfly/react-table"; +import { + Table, + Thead, + Tr, + Th, + Tbody, + Td, + ExpandableRowContent, + RowSelectVariant, +} from "@patternfly/react-table"; /** * @typedef {import("@patternfly/react-table").TableProps} TableProps @@ -61,7 +70,11 @@ const TableHeader = ({ columns }) => ( - { columns?.map((c, i) => {c.name}) } + {columns?.map((c, i) => ( + + {c.name} + + ))} ); @@ -88,7 +101,7 @@ const sanitizeSelection = (selection, allowMultiple) => { if (!allowMultiple && selection.length > 1) { console.error( "`itemsSelected` prop can only have more than one item when selector `isMultiple`. " + - "Using only the first element" + "Using only the first element", ); return [selection[0]]; @@ -136,8 +149,9 @@ export default function ExpandableSelector({ const selection = sanitizeSelection(itemsSelected, isMultiple); const isItemSelected = (item) => { const selected = selection.find((selectionItem) => { - return Object.hasOwn(selectionItem, itemIdKey) && - selectionItem[itemIdKey] === item[itemIdKey]; + return ( + Object.hasOwn(selectionItem, itemIdKey) && selectionItem[itemIdKey] === item[itemIdKey] + ); }); return selected !== undefined || selection.includes(item); @@ -145,7 +159,7 @@ export default function ExpandableSelector({ const isItemExpanded = (key) => expandedItemsKeys.includes(key); const toggleExpanded = (key) => { if (isItemExpanded(key)) { - setExpandedItemsKeys(expandedItemsKeys.filter(k => k !== key)); + setExpandedItemsKeys(expandedItemsKeys.filter((k) => k !== key)); } else { setExpandedItemsKeys([...expandedItemsKeys, key]); } @@ -158,7 +172,7 @@ export default function ExpandableSelector({ } if (isItemSelected(item)) { - onSelectionChange(selection.filter(i => i !== item)); + onSelectionChange(selection.filter((i) => i !== item)); } else { onSelectionChange([...selection, item]); } @@ -178,14 +192,14 @@ export default function ExpandableSelector({ rowIndex, onSelect: () => updateSelection(item), isSelected: isItemSelected(item), - variant: isMultiple ? RowSelectVariant.checkbox : RowSelectVariant.radio + variant: isMultiple ? RowSelectVariant.checkbox : RowSelectVariant.radio, }; return ( - { columns?.map((column, index) => ( + {columns?.map((column, index) => ( {column.value(item)} @@ -215,13 +229,13 @@ export default function ExpandableSelector({ rowIndex, onSelect: () => updateSelection(item), isSelected: isItemSelected(item), - variant: isMultiple ? RowSelectVariant.checkbox : RowSelectVariant.radio + variant: isMultiple ? RowSelectVariant.checkbox : RowSelectVariant.radio, }; const renderChildren = () => { if (!validChildren) return; - return children.map(item => renderItemChild(item, isItemExpanded(itemKey), sharedData)); + return children.map((item) => renderItemChild(item, isItemExpanded(itemKey), sharedData)); }; // TODO: Add label to Tbody? @@ -230,13 +244,13 @@ export default function ExpandableSelector({ - { columns?.map((column, index) => ( + {columns?.map((column, index) => ( {column.value(item)} ))} - { renderChildren() } + {renderChildren()} ); }; @@ -244,7 +258,7 @@ export default function ExpandableSelector({ // @see SharedData const sharedData = { rowIndex: 0 }; - const TableBody = () => items?.map(item => renderItem(item, sharedData)); + const TableBody = () => items?.map((item) => renderItem(item, sharedData)); return ( diff --git a/web/src/components/core/ExpandableSelector.test.jsx b/web/src/components/core/ExpandableSelector.test.jsx index eee55dd6be..3726b59a4a 100644 --- a/web/src/components/core/ExpandableSelector.test.jsx +++ b/web/src/components/core/ExpandableSelector.test.jsx @@ -40,7 +40,7 @@ const sda = { name: "/dev/sda", size: 1024, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }; @@ -53,9 +53,9 @@ const sda1 = { name: "/dev/sda1", size: 512, shrinking: { supported: 128 }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; const sda2 = { @@ -66,15 +66,15 @@ const sda2 = { name: "/dev/sda2", size: 512, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; sda.partitionTable = { type: "gpt", partitions: [sda1, sda2], - unpartitionedSize: 512 + unpartitionedSize: 512, }; const sdb = { @@ -93,24 +93,22 @@ const sdb = { name: "/dev/sdb", size: 2048, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }; const lv1 = { sid: "163", name: "/dev/system/vg/lv1", - content: "Personal Data" + content: "Personal Data", }; const vg = { sid: "162", type: "vg", name: "/dev/system/vg", - lvs: [ - lv1 - ] + lvs: [lv1], }; const columns = [ @@ -122,7 +120,7 @@ const columns = [ if (item.type === "vg") return `${item.lvs.length} logical volume(s)`; return item.content; - } + }, }, { name: "Size", value: (item) => item.size }, ]; @@ -135,11 +133,9 @@ const commonProps = { items: [sda, sdb, vg], itemIdKey: "sid", initialExpandedKeys: [sda.sid, vg.sid], - itemChildren: (item) => ( - item.isDrive ? item.partitionTable?.partitions : item.lvs - ), + itemChildren: (item) => (item.isDrive ? item.partitionTable?.partitions : item.lvs), onSelectionChange: onChangeFn, - "aria-label": "Device selector" + "aria-label": "Device selector", }; describe("ExpandableSelector", () => { @@ -203,7 +199,7 @@ describe("ExpandableSelector", () => { it("renders as expanded items which value for `itemIdKey` is included in `initialExpandedKeys` prop", () => { plainRender( - + , ); const table = screen.getByRole("grid"); within(table).getByRole("row", { name: /dev\/sda1 512/ }); @@ -212,7 +208,7 @@ describe("ExpandableSelector", () => { it("keeps track of expanded items", async () => { const { user } = plainRender( - + , ); const table = screen.getByRole("grid"); const sdaRow = within(table).getByRole("row", { name: /sda 1024/ }); @@ -233,9 +229,7 @@ describe("ExpandableSelector", () => { }); it("uses 'id' as key when `itemIdKey` prop is not given", () => { - plainRender( - - ); + plainRender(); const table = screen.getByRole("grid"); // Since itemIdKey does not match the id used for the item, they are @@ -246,7 +240,7 @@ describe("ExpandableSelector", () => { it("uses given `itemIdKey` as key", () => { plainRender( - + , ); const table = screen.getByRole("grid"); @@ -269,7 +263,7 @@ describe("ExpandableSelector", () => { plainRender(); expect(console.error).toHaveBeenCalledWith( expect.stringContaining("prop must be an array"), - "Whatever" + "Whatever", ); }); @@ -331,7 +325,7 @@ describe("ExpandableSelector", () => { it("outputs to console.error", () => { plainRender(); expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("Using only the first element") + expect.stringContaining("Using only the first element"), ); }); @@ -423,12 +417,14 @@ describe("ExpandableSelector", () => { const lv1Row = within(table).getByRole("row", { name: /Personal Data/ }); const selection = screen.getAllByRole("checkbox", { checked: true }); expect(selection.length).toEqual(2); - [sda1Row, lv1Row].forEach(row => within(row).getByRole("checkbox", { checked: true })); + [sda1Row, lv1Row].forEach((row) => within(row).getByRole("checkbox", { checked: true })); }); describe("and user selects an already selected item", () => { it("triggers the `onSelectionChange` callback with a collection not including the item", async () => { - const { user } = plainRender(); + const { user } = plainRender( + , + ); const sda1row = screen.getByRole("row", { name: /dev\/sda1/ }); const sda1radio = within(sda1row).getByRole("checkbox"); await user.click(sda1radio); diff --git a/web/src/components/core/Fieldset.jsx b/web/src/components/core/Fieldset.jsx index 5169347b91..f7a69e8298 100644 --- a/web/src/components/core/Fieldset.jsx +++ b/web/src/components/core/Fieldset.jsx @@ -42,11 +42,7 @@ import { Stack } from "@patternfly/react-core"; * @param {JSX.Element} [props.children] - the section content * @param {object} [props.otherProps] fieldset element attributes, see {@link https://html.spec.whatwg.org/#the-fieldset-element} */ -export default function Fieldset({ - legend, - children, - ...otherProps -}) { +export default function Fieldset({ legend, children, ...otherProps }) { return (
diff --git a/web/src/components/core/FormLabel.jsx b/web/src/components/core/FormLabel.jsx index a6cafa793d..8e5a2be394 100644 --- a/web/src/components/core/FormLabel.jsx +++ b/web/src/components/core/FormLabel.jsx @@ -20,11 +20,11 @@ */ import React from "react"; -import styles from '@patternfly/react-styles/css/components/Form/form'; +import styles from "@patternfly/react-styles/css/components/Form/form"; /** * A missing PatternFly FormLabel, see: -* https://github.com/patternfly/patternfly-react/blob/d68f302609a6abf8da34d1c33b153f604d6b329d/packages/react-core/src/components/Form/FormGroup.tsx#L108-L123 + * https://github.com/patternfly/patternfly-react/blob/d68f302609a6abf8da34d1c33b153f604d6b329d/packages/react-core/src/components/Form/FormGroup.tsx#L108-L123 * * @param {object} props - component props * @param {boolean} [props.isRequired=false] - whether the associated field is mandatory @@ -34,10 +34,13 @@ import styles from '@patternfly/react-styles/css/components/Form/form'; */ export default function FormLabel({ isRequired = false, fieldId, children }) { return ( - @@ -75,7 +83,9 @@ function InstallationFinished() { const iguana = await client.manager.useIguana(); // FIXME: This logic should likely not be placed here, it's too coupled to storage internals. // Something to fix when this whole page is refactored in a (hopefully near) future. - const { settings: { encryptionPassword, encryptionMethod } } = await client.storage.proposal.getResult(); + const { + settings: { encryptionPassword, encryptionMethod }, + } = await client.storage.proposal.getResult(); setUsingIguana(iguana); setUsingTpm(encryptionPassword?.length > 0 && encryptionMethod === EncryptionMethods.TPM); } @@ -99,12 +109,17 @@ function InstallationFinished() { icon={} /> - + {_("The installation on your machine is complete.")} {usingIguana ? _("At this point you can power off the machine.") - : _("At this point you can reboot the machine to log in to the new system.")} + : _( + "At this point you can reboot the machine to log in to the new system.", + )} {usingTpm && } diff --git a/web/src/components/core/InstallerOptions.jsx b/web/src/components/core/InstallerOptions.jsx index 440ad04545..34783bcf49 100644 --- a/web/src/components/core/InstallerOptions.jsx +++ b/web/src/components/core/InstallerOptions.jsx @@ -23,7 +23,14 @@ import React, { useState } from "react"; import { useLocation } from "react-router-dom"; -import { Button, Flex, Form, FormGroup, FormSelect, FormSelectOption } from "@patternfly/react-core"; +import { + Button, + Flex, + Form, + FormGroup, + FormSelect, + FormSelectOption, +} from "@patternfly/react-core"; import { Icon } from "~/components/layout"; import { Popup } from "~/components/core"; import { _ } from "~/i18n"; @@ -47,7 +54,7 @@ export default function InstallerOptions() { language: initialLanguage, keymap: initialKeymap, changeLanguage, - changeKeymap + changeKeymap, } = useInstallerL10n(); const [language, setLanguage] = useState(initialLanguage); const [keymap, setKeymap] = useState(initialKeymap); @@ -85,16 +92,10 @@ export default function InstallerOptions() { aria-label={_("Show installer options")} /> - +
- + ( - ) - )} + + ))} - - {localConnection() - ? ( - setKeymap(value)} - > - {keymaps.map((keymap, index) => ( - ) - )} - - ) - : _("Cannot be changed in remote installation")} + + {localConnection() ? ( + setKeymap(value)} + > + {keymaps.map((keymap, index) => ( + + ))} + + ) : ( + _("Cannot be changed in remote installation") + )}
- {_("Accept")} + + {_("Accept")} +
diff --git a/web/src/components/core/IssuesHint.jsx b/web/src/components/core/IssuesHint.jsx index 81d557a017..525c737232 100644 --- a/web/src/components/core/IssuesHint.jsx +++ b/web/src/components/core/IssuesHint.jsx @@ -34,7 +34,9 @@ export default function IssuesHint({ issues }) { {_("Before starting the installation, you need to address the following problems:")}

- {issues.map((i, idx) => {i.description})} + {issues.map((i, idx) => ( + {i.description} + ))} diff --git a/web/src/components/core/IssuesHint.test.jsx b/web/src/components/core/IssuesHint.test.jsx index 62c48ddfe2..4965a95ee8 100644 --- a/web/src/components/core/IssuesHint.test.jsx +++ b/web/src/components/core/IssuesHint.test.jsx @@ -28,7 +28,7 @@ it("renders a list of issues", () => { const issue = { description: "You need to create a user", source: "config", - severity: "error" + severity: "error", }; plainRender(); expect(screen.getByText(issue.description)).toBeInTheDocument(); diff --git a/web/src/components/core/ListSearch.jsx b/web/src/components/core/ListSearch.jsx index 33451d9a26..5f187f0d37 100644 --- a/web/src/components/core/ListSearch.jsx +++ b/web/src/components/core/ListSearch.jsx @@ -28,10 +28,7 @@ const search = (elements, term) => { const value = term.toLowerCase(); const match = (element) => { - return Object.values(element) - .join('') - .toLowerCase() - .includes(value); + return Object.values(element).join("").toLowerCase().includes(value); }; return elements.filter(match); @@ -50,7 +47,7 @@ const search = (elements, term) => { export default function ListSearch({ placeholder = _("Search"), elements = [], - onChange: onChangeProp = noop + onChange: onChangeProp = noop, }) { const [value, setValue] = useState(""); const [resultSize, setResultSize] = useState(elements.length); @@ -60,7 +57,7 @@ export default function ListSearch({ onChangeProp(result); }; - const searchHandler = useDebounce(term => { + const searchHandler = useDebounce((term) => { updateResult(search(elements, term)); }, 500); diff --git a/web/src/components/core/ListSearch.test.jsx b/web/src/components/core/ListSearch.test.jsx index b89c318e76..54583c0440 100644 --- a/web/src/components/core/ListSearch.test.jsx +++ b/web/src/components/core/ListSearch.test.jsx @@ -28,7 +28,7 @@ const fruits = [ { name: "Apple", color: "red", size: "medium" }, { name: "Banana", color: "yellow", size: "medium" }, { name: "Grape", color: "green", size: "small" }, - { name: "Pear", color: "green", size: "medium" } + { name: "Pear", color: "green", size: "medium" }, ]; const FruitList = ({ fruits }) => { @@ -38,7 +38,11 @@ const FruitList = ({ fruits }) => { <>
    - {filteredFruits.map((f, i) =>
  • {f.name}
  • )} + {filteredFruits.map((f, i) => ( +
  • + {f.name} +
  • + ))}
); @@ -51,8 +55,8 @@ it("searches for elements matching the given term (case-insensitive)", async () // Search for "medium" size fruit await user.type(searchInput, "medium"); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: /grape/ })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: /grape/ })).not.toBeInTheDocument(), ); screen.getByRole("option", { name: "Apple" }); screen.getByRole("option", { name: "Banana" }); @@ -61,11 +65,11 @@ it("searches for elements matching the given term (case-insensitive)", async () // Search for "green" fruit await user.clear(searchInput); await user.type(searchInput, "Green"); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Apple" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Apple" })).not.toBeInTheDocument(), ); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument(), ); screen.getByRole("option", { name: "Grape" }); screen.getByRole("option", { name: "Pear" }); @@ -73,11 +77,11 @@ it("searches for elements matching the given term (case-insensitive)", async () // Search for known fruit await user.clear(searchInput); await user.type(searchInput, "ap"); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument(), ); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Pear" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Pear" })).not.toBeInTheDocument(), ); screen.getByRole("option", { name: "Apple" }); screen.getByRole("option", { name: "Grape" }); @@ -85,16 +89,16 @@ it("searches for elements matching the given term (case-insensitive)", async () // Search for unknown fruit await user.clear(searchInput); await user.type(searchInput, "tomato"); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Apple" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Apple" })).not.toBeInTheDocument(), ); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Banana" })).not.toBeInTheDocument(), ); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Grape" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Grape" })).not.toBeInTheDocument(), ); - await waitFor(() => ( - expect(screen.queryByRole("option", { name: "Pear" })).not.toBeInTheDocument()) + await waitFor(() => + expect(screen.queryByRole("option", { name: "Pear" })).not.toBeInTheDocument(), ); }); diff --git a/web/src/components/core/LoginPage.jsx b/web/src/components/core/LoginPage.jsx index 3781ced107..76acd31cb8 100644 --- a/web/src/components/core/LoginPage.jsx +++ b/web/src/components/core/LoginPage.jsx @@ -27,10 +27,13 @@ import { ActionGroup, Button, Card, - Flex, FlexItem, - Form, FormGroup, - Grid, GridItem, - Stack + Flex, + FlexItem, + Form, + FormGroup, + Grid, + GridItem, + Stack, } from "@patternfly/react-core"; import { About, EmptyState, FormValidationError, Page, PasswordInput } from "~/components/core"; import { Center } from "~/components/layout"; @@ -73,8 +76,10 @@ export default function LoginPage() { // TRANSLATORS: description why root password is needed. The text in the // square brackets [] is displayed in bold, use only please, do not translate // it and keep the brackets. - const [rootExplanationStart, rootUser, rootExplanationEnd] = _("The installer requires [root] \ -user privileges.").split(/[[\]]/); + const [rootExplanationStart, rootUser, rootExplanationEnd] = _( + "The installer requires [root] \ +user privileges.", + ).split(/[[\]]/); return ( @@ -82,18 +87,11 @@ user privileges.").split(/[[\]]/); - +

{rootExplanationStart} {rootUser} {rootExplanationEnd}

-

- {_("Please, provide its password to log in to the system.")} -

+

{_("Please, provide its password to log in to the system.")}

diff --git a/web/src/components/core/LoginPage.test.jsx b/web/src/components/core/LoginPage.test.jsx index a47fccbd48..d95ed34d51 100644 --- a/web/src/components/core/LoginPage.test.jsx +++ b/web/src/components/core/LoginPage.test.jsx @@ -35,9 +35,9 @@ jest.mock("~/context/auth", () => ({ return { isAuthenticated: mockIsAuthenticated, login: mockLoginFn, - error: mockLoginError + error: mockLoginError, }; - } + }, })); describe.skip("LoginPage", () => { diff --git a/web/src/components/core/LogsButton.jsx b/web/src/components/core/LogsButton.jsx index a6bfb8f16b..c3315cab54 100644 --- a/web/src/components/core/LogsButton.jsx +++ b/web/src/components/core/LogsButton.jsx @@ -50,7 +50,7 @@ const LogsButton = ({ ...props }) => { * @param {string} url - the file location to download from */ const autoDownload = (url) => { - const a = document.createElement('a'); + const a = document.createElement("a"); a.href = url; a.download = FILENAME; @@ -60,12 +60,12 @@ const LogsButton = ({ ...props }) => { const clickHandler = () => { setTimeout(() => { URL.revokeObjectURL(url); - a.removeEventListener('click', clickHandler); + a.removeEventListener("click", clickHandler); }, 150); }; // Add the click event listener on the anchor element - a.addEventListener('click', clickHandler, false); + a.addEventListener("click", clickHandler, false); // Programmatically trigger a click on the anchor element // Needed for make the download to happen automatically without attaching the anchor element to @@ -76,9 +76,7 @@ const LogsButton = ({ ...props }) => { const collectAndDownload = () => { setError(null); setIsCollecting(true); - cancellablePromise( - client.manager.fetchLogs().then((response) => response.blob()), - ) + cancellablePromise(client.manager.fetchLogs().then((response) => response.blob())) .then(URL.createObjectURL) .then(autoDownload) .catch((error) => { @@ -104,23 +102,29 @@ const LogsButton = ({ ...props }) => { - {isCollecting && + {isCollecting && ( } + title={_( + "The browser will run the logs download as soon as they are ready. Please, be patient.", + )} + /> + )} - {error && + {error && ( } + /> + )} - {_("Close")} + + {_("Close")} + diff --git a/web/src/components/core/LogsButton.test.jsx b/web/src/components/core/LogsButton.test.jsx index 6eef301fcd..eab60e1c44 100644 --- a/web/src/components/core/LogsButton.test.jsx +++ b/web/src/components/core/LogsButton.test.jsx @@ -42,7 +42,7 @@ beforeEach(() => { return { manager: { fetchLogs: fetchLogsFn, - } + }, }; }); }); @@ -89,7 +89,7 @@ describe("LogsButton", () => { describe("and logs are collected successfully", () => { beforeEach(() => { fetchLogsFn.mockResolvedValue({ - blob: jest.fn().mockResolvedValue(new Blob(["testing"])) + blob: jest.fn().mockResolvedValue(new Blob(["testing"])), }); }); @@ -104,12 +104,12 @@ describe("LogsButton", () => { // "Download logs". document._createElement = document.createElement; - const anchorMock = document.createElement('a'); + const anchorMock = document.createElement("a"); anchorMock.setAttribute = jest.fn(); anchorMock.click = jest.fn(); jest.spyOn(document, "createElement").mockImplementation((tag) => { - return (tag === 'a') ? anchorMock : document._createElement(tag); + return tag === "a" ? anchorMock : document._createElement(tag); }); // Now, let's simulate the "Download logs" user click @@ -117,9 +117,12 @@ describe("LogsButton", () => { await user.click(button); // And test what we're looking for - expect(document.createElement).toHaveBeenCalledWith('a'); + expect(document.createElement).toHaveBeenCalledWith("a"); expect(anchorMock).toHaveAttribute("href", "fake-blob-url"); - expect(anchorMock).toHaveAttribute("download", expect.stringMatching(/agama-installation-logs/)); + expect(anchorMock).toHaveAttribute( + "download", + expect.stringMatching(/agama-installation-logs/), + ); expect(anchorMock.click).toHaveBeenCalled(); // Be polite and restore document.createElement function, diff --git a/web/src/components/core/NumericTextInput.jsx b/web/src/components/core/NumericTextInput.jsx index a8a095ba6e..1aa66ebc5e 100644 --- a/web/src/components/core/NumericTextInput.jsx +++ b/web/src/components/core/NumericTextInput.jsx @@ -56,7 +56,5 @@ export default function NumericTextInput({ value = "", onChange = noop, ...textI } }; - return ( - - ); + return ; } diff --git a/web/src/components/core/Page.jsx b/web/src/components/core/Page.jsx index a6c70af5a0..cdfae2514b 100644 --- a/web/src/components/core/Page.jsx +++ b/web/src/components/core/Page.jsx @@ -25,14 +25,17 @@ import React from "react"; import { NavLink, Outlet, useNavigate, useMatches, useLocation } from "react-router-dom"; import { Button, - Card, CardBody, CardHeader, + Card, + CardBody, + CardHeader, Flex, - PageGroup, PageSection, - Stack + PageGroup, + PageSection, + Stack, } from "@patternfly/react-core"; import { _ } from "~/i18n"; -import tabsStyles from '@patternfly/react-styles/css/components/Tabs/tabs'; -import flexStyles from '@patternfly/react-styles/css/utilities/Flex/flex'; +import tabsStyles from "@patternfly/react-styles/css/components/Tabs/tabs"; +import flexStyles from "@patternfly/react-styles/css/utilities/Flex/flex"; /** * @typedef {import("@patternfly/react-core").ButtonProps} ButtonProps @@ -95,17 +98,21 @@ const CancelAction = ({ text = _("Cancel"), navigateTo }) => { // FIXME: would replace Actions const NextActions = ({ children }) => ( - + - - {children} - + {children} ); const MainContent = ({ children, ...props }) => ( - {children} + + {children} + ); const Navigation = ({ routes }) => { @@ -121,13 +128,21 @@ const Navigation = ({ routes }) => { @@ -136,14 +151,8 @@ const Navigation = ({ routes }) => { const Header = ({ hasGutter = true, children, ...props }) => { return ( - - - {children} - + + {children} ); }; @@ -179,7 +188,7 @@ const CardSection = ({ title, children, ...props }) => { const Page = () => { const location = useLocation(); const matches = useMatches(); - const currentRoute = matches.find(r => r.pathname === location.pathname); + const currentRoute = matches.find((r) => r.pathname === location.pathname); const titleFromRoute = currentRoute?.handle?.name; return ( diff --git a/web/src/components/core/Page.test.jsx b/web/src/components/core/Page.test.jsx index 096c3d63ca..4733dbf555 100644 --- a/web/src/components/core/Page.test.jsx +++ b/web/src/components/core/Page.test.jsx @@ -48,7 +48,7 @@ describe.skip("Page", () => { // if defined outside, the mock is cleared automatically createClient.mockImplementation(() => { return { - l10n: l10nClientMock + l10n: l10nClientMock, }; }); }); @@ -95,7 +95,7 @@ describe.skip("Page", () => {
Page content
, - { withL10n: true } + { withL10n: true }, ); screen.getByText("Page content"); @@ -112,11 +112,11 @@ describe.skip("Page", () => { , - { withL10n: true } + { withL10n: true }, ); // Sidebar is rendering it's own header, let's ignore it - const [header,] = screen.getAllByRole("banner"); + const [header] = screen.getAllByRole("banner"); const menuButton = within(header).getByRole("button", { name: "Testing menu" }); await user.click(menuButton); screen.getByRole("menuitem", { name: "Switch to advanced mode" }); @@ -130,11 +130,11 @@ describe.skip("Page", () => { Discard , - { withL10n: true } + { withL10n: true }, ); // Sidebar is rendering it's own footer, let's ignore it - const [footer,] = screen.getAllByRole("contentinfo"); + const [footer] = screen.getAllByRole("contentinfo"); within(footer).getByRole("button", { name: "Save" }); within(footer).getByRole("button", { name: "Discard" }); }); @@ -167,7 +167,7 @@ describe.skip("Page.Actions", () => { plainRender( - + , ); screen.getByRole("button", { name: "Plain action" }); @@ -185,7 +185,7 @@ describe.skip("Page.Menu", () => { <>The menu entry - + , ); screen.getByRole("button", { name: "Show page menu" }); @@ -223,7 +223,9 @@ describe.skip("Page.Action", () => { it("triggers form submission if it's a submit action and has an associated form", async () => { // NOTE: using preventDefault here to avoid a jsdom error // Error: Not implemented: HTMLFormElement.prototype.requestSubmit - const onSubmit = jest.fn((e) => { e.preventDefault() }); + const onSubmit = jest.fn((e) => { + e.preventDefault(); + }); const { user } = plainRender( <> @@ -231,7 +233,7 @@ describe.skip("Page.Action", () => { Send - + , ); const button = screen.getByRole("button", { name: "Send" }); await user.click(button); @@ -242,20 +244,17 @@ describe.skip("Page.Action", () => { const onClick = jest.fn(); // NOTE: using preventDefault here to avoid a jsdom error // Error: Not implemented: HTMLFormElement.prototype.requestSubmit - const onSubmit = jest.fn((e) => { e.preventDefault() }); + const onSubmit = jest.fn((e) => { + e.preventDefault(); + }); const { user } = plainRender( <> - + Send - + , ); const button = screen.getByRole("button", { name: "Send" }); await user.click(button); diff --git a/web/src/components/core/PasswordAndConfirmationInput.jsx b/web/src/components/core/PasswordAndConfirmationInput.jsx index ff8be137df..f23680ba0c 100644 --- a/web/src/components/core/PasswordAndConfirmationInput.jsx +++ b/web/src/components/core/PasswordAndConfirmationInput.jsx @@ -29,7 +29,14 @@ import { _ } from "~/i18n"; // TODO: improve the component to allow working only in uncontrlled mode if // needed. // TODO: improve the showErrors thingy -const PasswordAndConfirmationInput = ({ inputRef, showErrors = true, value, onChange, onValidation, isDisabled = false }) => { +const PasswordAndConfirmationInput = ({ + inputRef, + showErrors = true, + value, + onChange, + onValidation, + isDisabled = false, +}) => { const passwordInput = inputRef?.current; const [password, setPassword] = useState(value || ""); const [confirmation, setConfirmation] = useState(value || ""); @@ -80,10 +87,7 @@ const PasswordAndConfirmationInput = ({ inputRef, showErrors = true, value, onCh onBlur={() => validate(password, confirmation)} />
- + { it("displays a warning", async () => { const password = ""; - const { user } = plainRender( - - ); + const { user } = plainRender(); const passwordInput = screen.getByLabelText("Password"); user.type(passwordInput, "123456"); @@ -38,9 +36,7 @@ describe("when the passwords do not match", () => { }); it("uses the given password value for confirmation too", async () => { - plainRender( - - ); + plainRender(); const passwordInput = screen.getByLabelText("Password"); const confirmationInput = screen.getByLabelText("Password confirmation"); @@ -51,9 +47,7 @@ it("uses the given password value for confirmation too", async () => { describe("when isDisabled", () => { it("disables both, password and confirmation", async () => { - plainRender( - - ); + plainRender(); const passwordInput = screen.getByLabelText("Password"); const confirmationInput = screen.getByLabelText("Password confirmation"); diff --git a/web/src/components/core/PasswordInput.jsx b/web/src/components/core/PasswordInput.jsx index aefb3ca898..1bb192fbed 100644 --- a/web/src/components/core/PasswordInput.jsx +++ b/web/src/components/core/PasswordInput.jsx @@ -47,17 +47,14 @@ export default function PasswordInput({ id, inputRef, ...props }) { if (!id) { const field = props.label || props["aria-label"] || props.name; - console.error(`The PasswordInput component must have an 'id' but it was not given for '${field}'`); + console.error( + `The PasswordInput component must have an 'id' but it was not given for '${field}'`, + ); } return ( - + -); +const Action = ({ children, ...buttonProps }) => ; /** * A Popup primary action @@ -77,7 +73,9 @@ const Action = ({ children, ...buttonProps }) => ( * @param {ButtonWithoutVariantProps} props */ const PrimaryAction = ({ children, ...actionProps }) => ( - {children} + + {children} + ); /** @@ -92,7 +90,9 @@ const PrimaryAction = ({ children, ...actionProps }) => ( * @param {ButtonWithoutVariantProps} props */ const Confirm = ({ children = _("Confirm"), ...actionProps }) => ( - {children} + + {children} + ); /** @@ -113,7 +113,9 @@ const Confirm = ({ children = _("Confirm"), ...actionProps }) => ( * @param {ButtonWithoutVariantProps} props */ const SecondaryAction = ({ children, ...actionProps }) => ( - {children} + + {children} + ); /** @@ -128,7 +130,9 @@ const SecondaryAction = ({ children, ...actionProps }) => ( * @param {ButtonWithoutVariantProps} props */ const Cancel = ({ children = _("Cancel"), ...actionProps }) => ( - {children} + + {children} + ); /** @@ -149,7 +153,9 @@ const Cancel = ({ children = _("Cancel"), ...actionProps }) => ( * @param {ButtonWithoutVariantProps} props */ const AncillaryAction = ({ children, ...actionsProps }) => ( - {children} + + {children} + ); /** diff --git a/web/src/components/core/ProgressReport.jsx b/web/src/components/core/ProgressReport.jsx index d41a8be3c3..63b0230231 100644 --- a/web/src/components/core/ProgressReport.jsx +++ b/web/src/components/core/ProgressReport.jsx @@ -30,7 +30,7 @@ import { ProgressStepper, Spinner, Stack, - Truncate + Truncate, } from "@patternfly/react-core"; import { _ } from "~/i18n"; @@ -58,7 +58,11 @@ const Progress = ({ steps, step, firstStep, detail }) => {
{_("In progress")}
- +
); @@ -138,7 +142,11 @@ function ProgressReport({ title, firstStep }) { - +

{progressTitle} diff --git a/web/src/components/core/ProgressText.jsx b/web/src/components/core/ProgressText.jsx index e5117120cc..411e48ea3c 100644 --- a/web/src/components/core/ProgressText.jsx +++ b/web/src/components/core/ProgressText.jsx @@ -35,12 +35,10 @@ import { Split, Text } from "@patternfly/react-core"; * @param {number} [props.total] Number of steps */ export default function ProgressText({ message, current, total }) { - const text = (current === 0) ? message : `${message} (${current}/${total})`; + const text = current === 0 ? message : `${message} (${current}/${total})`; return ( - - {text} - + {text} ); } diff --git a/web/src/components/core/RowActions.jsx b/web/src/components/core/RowActions.jsx index d72b847887..864fcb141d 100644 --- a/web/src/components/core/RowActions.jsx +++ b/web/src/components/core/RowActions.jsx @@ -20,10 +20,10 @@ */ import React from "react"; -import { MenuToggle } from '@patternfly/react-core'; -import { ActionsColumn } from '@patternfly/react-table'; +import { MenuToggle } from "@patternfly/react-core"; +import { ActionsColumn } from "@patternfly/react-table"; -import { Icon } from '~/components/layout'; +import { Icon } from "~/components/layout"; import { _ } from "~/i18n"; /** diff --git a/web/src/components/core/Section.jsx b/web/src/components/core/Section.jsx index 0435ad602d..1d8c6c891d 100644 --- a/web/src/components/core/Section.jsx +++ b/web/src/components/core/Section.jsx @@ -24,7 +24,7 @@ import React from "react"; import { Link } from "react-router-dom"; import { PageSection, Stack } from "@patternfly/react-core"; -import { Icon } from '~/components/layout'; +import { Icon } from "~/components/layout"; /** * @typedef {import("~/components/layout/Icon").IconName} IconName */ @@ -75,7 +75,7 @@ export default function Section({ id, errors, children, - "aria-label": ariaLabel + "aria-label": ariaLabel, }) { const headerId = `${name || crypto.randomUUID()}-section-header`; @@ -93,7 +93,10 @@ export default function Section({ return (
-

{headerIcon}{headerText}

+

+ {headerIcon} + {headerText} +

{renderDescription &&

{description}

}
); @@ -102,9 +105,7 @@ export default function Section({ return (
- - {children} - + {children} ); } diff --git a/web/src/components/core/Section.test.jsx b/web/src/components/core/Section.test.jsx index d293cccd03..5d7c5cbb11 100644 --- a/web/src/components/core/Section.test.jsx +++ b/web/src/components/core/Section.test.jsx @@ -66,14 +66,16 @@ describe.skip("Section", () => { it("does not render an icon if not valid icon name is given", () => { // @ts-expect-error: Creating the icon name dynamically is unlikely, but let's be safe. - const { container } = plainRender(
); + const { container } = plainRender( +
, + ); const icon = container.querySelector("svg"); expect(icon).toBeNull(); }); it("renders given description as part of the header", () => { plainRender( -
+
, ); const header = screen.getByRole("banner"); within(header).getByText(/Short explanation/); @@ -149,18 +151,18 @@ describe.skip("Section", () => { it("renders given errors", () => { plainRender( -
+
, ); screen.getByText("Something went wrong"); }); it("renders given content", () => { - plainRender( -
- A settings summary -
- ); + plainRender(
A settings summary
); screen.getByText("A settings summary"); }); diff --git a/web/src/components/core/SectionSkeleton.jsx b/web/src/components/core/SectionSkeleton.jsx index e34da05402..57e2e01dd5 100644 --- a/web/src/components/core/SectionSkeleton.jsx +++ b/web/src/components/core/SectionSkeleton.jsx @@ -24,24 +24,16 @@ import { Skeleton } from "@patternfly/react-core"; import { _ } from "~/i18n"; const WaitingSkeleton = ({ width }) => { - return ( - - ); + return ; }; const SectionSkeleton = ({ numRows = 2 }) => { return ( <> - { - Array.from({ length: numRows }, (_, i) => { - const width = i % 2 === 0 ? "50%" : "25%"; - return ; - }) - } + {Array.from({ length: numRows }, (_, i) => { + const width = i % 2 === 0 ? "50%" : "25%"; + return ; + })} ); }; diff --git a/web/src/components/core/ServerError.jsx b/web/src/components/core/ServerError.jsx index 3452081129..0846b5b136 100644 --- a/web/src/components/core/ServerError.jsx +++ b/web/src/components/core/ServerError.jsx @@ -20,7 +20,12 @@ */ import React from "react"; -import { EmptyState, EmptyStateIcon, EmptyStateBody, EmptyStateHeader } from "@patternfly/react-core"; +import { + EmptyState, + EmptyStateIcon, + EmptyStateBody, + EmptyStateHeader, +} from "@patternfly/react-core"; import { Center, Icon } from "~/components/layout"; import { Page } from "~/components/core"; import SimpleLayout from "~/SimpleLayout"; @@ -43,16 +48,12 @@ function ServerError() { headingLevel="h2" icon={} /> - - {_("Please, check whether it is running.")} - + {_("Please, check whether it is running.")} - - {_("Reload")} - + {_("Reload")} ); diff --git a/web/src/components/core/Tip.jsx b/web/src/components/core/Tip.jsx index 8a72e22f3b..ae31ab7023 100644 --- a/web/src/components/core/Tip.jsx +++ b/web/src/components/core/Tip.jsx @@ -37,7 +37,7 @@ import { Icon } from "~/components/layout"; * @param {React.ReactElement} props.description - Details displayed after clicking the label. * @param {React.ReactNode} props.children - The content of the label. */ -export default function Tip ({ description, children }) { +export default function Tip({ description, children }) { if (description) { return ( diff --git a/web/src/components/core/TreeTable.jsx b/web/src/components/core/TreeTable.jsx index 6072fd18d3..57c7ab4d1b 100644 --- a/web/src/components/core/TreeTable.jsx +++ b/web/src/components/core/TreeTable.jsx @@ -22,7 +22,7 @@ // @ts-check import React, { useEffect, useState } from "react"; -import { Table, Thead, Tr, Th, Tbody, Td, TreeRowWrapper } from '@patternfly/react-table'; +import { Table, Thead, Tr, Th, Tbody, Td, TreeRowWrapper } from "@patternfly/react-table"; /** * @typedef {import("@patternfly/react-table").TableProps} TableProps @@ -73,7 +73,7 @@ export default function TreeTable({ const toggle = (item) => { if (isExpanded(item)) { - setExpanded(expanded.filter(d => d !== item)); + setExpanded(expanded.filter((d) => d !== item)); } else { setExpanded([...expanded, item]); } @@ -83,13 +83,15 @@ export default function TreeTable({ return columns.map((c, cIdx) => { const props = { dataLabel: c.name, - className: c.classNames + className: c.classNames, }; if (cIdx === 0) props.treeRow = treeRow; return ( -

+ ); }); }; @@ -97,53 +99,48 @@ export default function TreeTable({ const renderRows = (items, level, hidden = false) => { if (items?.length <= 0) return; - return ( - items.map((item, itemIdx) => { - const children = itemChildren(item); - const expanded = isExpanded(item); - - const treeRow = { - onCollapse: () => toggle(item), - props: { - isExpanded: expanded, - isDetailsExpanded: true, - isHidden: hidden, - "aria-level": level, - "aria-posinset": itemIdx + 1, - "aria-setsize": children?.length || 0 - } - }; - - const rowProps = { - row: { props: treeRow.props }, - className: rowClassNames(item) - }; - - return ( - - {renderColumns(item, treeRow)} - { renderRows(children, level + 1, !expanded)} - - ); - }) - ); + return items.map((item, itemIdx) => { + const children = itemChildren(item); + const expanded = isExpanded(item); + + const treeRow = { + onCollapse: () => toggle(item), + props: { + isExpanded: expanded, + isDetailsExpanded: true, + isHidden: hidden, + "aria-level": level, + "aria-posinset": itemIdx + 1, + "aria-setsize": children?.length || 0, + }, + }; + + const rowProps = { + row: { props: treeRow.props }, + className: rowClassNames(item), + }; + + return ( + + {renderColumns(item, treeRow)} + {renderRows(children, level + 1, !expanded)} + + ); + }); }; return ( -
{c.value(item)} + {c.value(item)} +
+
- { columns.map((c, i) => ) } + {columns.map((c, i) => ( + + ))} - - { renderRows(items, 1) } - + {renderRows(items, 1)}
{c.name} + {c.name} +
); } diff --git a/web/src/components/l10n/KeyboardSelection.jsx b/web/src/components/l10n/KeyboardSelection.jsx index fa58525b08..f9a45de367 100644 --- a/web/src/components/l10n/KeyboardSelection.jsx +++ b/web/src/components/l10n/KeyboardSelection.jsx @@ -25,7 +25,7 @@ import { useNavigate } from "react-router-dom"; import { ListSearch, Page } from "~/components/core"; import { _ } from "~/i18n"; import { useConfigMutation, useL10n } from "~/queries/l10n"; -import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; +import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; // TODO: Add documentation and typechecking // TODO: Evaluate if worth it extracting the selector @@ -35,7 +35,7 @@ export default function KeyboardSelection() { const { keymaps, selectedKeymap: currentKeymap } = useL10n(); const [selected, setSelected] = useState(currentKeymap.id); const [filteredKeymaps, setFilteredKeymaps] = useState( - keymaps.sort((k1, k2) => k1.name > k2.name ? 1 : -1) + keymaps.sort((k1, k2) => (k1.name > k2.name ? 1 : -1)), ); const searchHelp = _("Filter by description or keymap code"); @@ -57,7 +57,8 @@ export default function KeyboardSelection() { <> {name} - {id} + {" "} + {id} } value={id} @@ -67,9 +68,7 @@ export default function KeyboardSelection() { }); if (keymapsList.length === 0) { - keymapsList = ( - {_("None of the keymaps match the filter.")} - ); + keymapsList = {_("None of the keymaps match the filter.")}; } return ( @@ -81,9 +80,7 @@ export default function KeyboardSelection() { - - {keymapsList} - + {keymapsList} diff --git a/web/src/components/l10n/KeyboardSelection.test.jsx b/web/src/components/l10n/KeyboardSelection.test.jsx index b08daf2659..627b2cb196 100644 --- a/web/src/components/l10n/KeyboardSelection.test.jsx +++ b/web/src/components/l10n/KeyboardSelection.test.jsx @@ -27,17 +27,17 @@ import { mockNavigateFn, plainRender } from "~/test-utils"; const keymaps = [ { id: "us", name: "English" }, - { id: "es", name: "Spanish" } + { id: "es", name: "Spanish" }, ]; const mockConfigMutation = { - mutate: jest.fn() + mutate: jest.fn(), }; jest.mock("~/queries/l10n", () => ({ ...jest.requireActual("~/queries/l10n"), useConfigMutation: () => mockConfigMutation, - useL10n: () => ({ keymaps, selectedKeymap: keymaps[0] }) + useL10n: () => ({ keymaps, selectedKeymap: keymaps[0] }), })); jest.mock("react-router-dom", () => ({ diff --git a/web/src/components/l10n/L10nPage.jsx b/web/src/components/l10n/L10nPage.jsx index 2eaa70babd..33e76b3fa5 100644 --- a/web/src/components/l10n/L10nPage.jsx +++ b/web/src/components/l10n/L10nPage.jsx @@ -20,22 +20,21 @@ */ import React from "react"; -import { Gallery, GalleryItem, } from "@patternfly/react-core"; +import { Gallery, GalleryItem } from "@patternfly/react-core"; import { useLoaderData } from "react-router-dom"; import { ButtonLink, CardField, Page } from "~/components/core"; -import { LOCALE_SELECTION_PATH, KEYMAP_SELECTION_PATH, TIMEZONE_SELECTION_PATH } from "~/routes/l10n"; +import { + LOCALE_SELECTION_PATH, + KEYMAP_SELECTION_PATH, + TIMEZONE_SELECTION_PATH, +} from "~/routes/l10n"; import { _ } from "~/i18n"; import { useL10n } from "~/queries/l10n"; const Section = ({ label, value, children }) => { return ( - - - {children} - + + {children} ); }; @@ -46,11 +45,7 @@ const Section = ({ label, value, children }) => { * @component */ export default function L10nPage() { - const { - selectedLocale: locale, - selectedTimezone: timezone, - selectedKeymap: keymap - } = useL10n(); + const { selectedLocale: locale, selectedTimezone: timezone, selectedKeymap: keymap } = useL10n(); return ( <> @@ -72,10 +67,7 @@ export default function L10nPage() { -
+
{keymap ? _("Change") : _("Select")} @@ -85,7 +77,7 @@ export default function L10nPage() {
{timezone ? _("Change") : _("Select")} diff --git a/web/src/components/l10n/L10nPage.test.jsx b/web/src/components/l10n/L10nPage.test.jsx index 6ad324dc6e..808f5ce5aa 100644 --- a/web/src/components/l10n/L10nPage.test.jsx +++ b/web/src/components/l10n/L10nPage.test.jsx @@ -27,27 +27,27 @@ let mockLoadedData; const locales = [ { id: "en_US.UTF-8", name: "English", territory: "United States" }, - { id: "es_ES.UTF-8", name: "Spanish", territory: "Spain" } + { id: "es_ES.UTF-8", name: "Spanish", territory: "Spain" }, ]; const keymaps = [ { id: "us", name: "English" }, - { id: "es", name: "Spanish" } + { id: "es", name: "Spanish" }, ]; const timezones = [ { id: "Europe/Berlin", parts: ["Europe", "Berlin"] }, - { id: "Europe/Madrid", parts: ["Europe", "Madrid"] } + { id: "Europe/Madrid", parts: ["Europe", "Madrid"] }, ]; -jest.mock('react-router-dom', () => ({ +jest.mock("react-router-dom", () => ({ ...jest.requireActual("react-router-dom"), // TODO: mock the link because it needs a working router. - Link: ({ children }) => + Link: ({ children }) => , })); jest.mock("~/queries/l10n", () => ({ - useL10n: () => mockLoadedData + useL10n: () => mockLoadedData, })); beforeEach(() => { diff --git a/web/src/components/l10n/LocaleSelection.jsx b/web/src/components/l10n/LocaleSelection.jsx index 543c18336a..cf6f39f00c 100644 --- a/web/src/components/l10n/LocaleSelection.jsx +++ b/web/src/components/l10n/LocaleSelection.jsx @@ -25,7 +25,7 @@ import { useNavigate } from "react-router-dom"; import { ListSearch, Page } from "~/components/core"; import { _ } from "~/i18n"; import { useConfigMutation, useL10n } from "~/queries/l10n"; -import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; +import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; // TODO: Add documentation and typechecking // TODO: Evaluate if worth it extracting the selector @@ -53,8 +53,12 @@ export default function LocaleSelection() { onChange={() => setSelected(id)} label={ - {name} - {territory} + + {name} + + + {territory} + {id} } @@ -65,9 +69,7 @@ export default function LocaleSelection() { }); if (localesList.length === 0) { - localesList = ( - {_("None of the locales match the filter.")} - ); + localesList = {_("None of the locales match the filter.")}; } return ( @@ -80,9 +82,7 @@ export default function LocaleSelection() {
- - {localesList} - + {localesList}
diff --git a/web/src/components/l10n/LocaleSelection.test.jsx b/web/src/components/l10n/LocaleSelection.test.jsx index a104ddcd30..857e084270 100644 --- a/web/src/components/l10n/LocaleSelection.test.jsx +++ b/web/src/components/l10n/LocaleSelection.test.jsx @@ -27,22 +27,22 @@ import { mockNavigateFn, plainRender } from "~/test-utils"; const locales = [ { id: "en_US.UTF-8", name: "English", territory: "United States" }, - { id: "es_ES.UTF-8", name: "Spanish", territory: "Spain" } + { id: "es_ES.UTF-8", name: "Spanish", territory: "Spain" }, ]; const mockConfigMutation = { - mutate: jest.fn() + mutate: jest.fn(), }; jest.mock("~/queries/l10n", () => ({ ...jest.requireActual("~/queries/l10n"), useL10n: () => ({ locales, selectedLocale: locales[0] }), - useConfigMutation: () => mockConfigMutation + useConfigMutation: () => mockConfigMutation, })); jest.mock("react-router-dom", () => ({ ...jest.requireActual("react-router-dom"), - useNavigate: () => mockNavigateFn + useNavigate: () => mockNavigateFn, })); it("allows changing the keyboard", async () => { diff --git a/web/src/components/l10n/TimezoneSelection.jsx b/web/src/components/l10n/TimezoneSelection.jsx index a67c65fc5a..385d689a70 100644 --- a/web/src/components/l10n/TimezoneSelection.jsx +++ b/web/src/components/l10n/TimezoneSelection.jsx @@ -26,7 +26,7 @@ import { ListSearch, Page } from "~/components/core"; import { _ } from "~/i18n"; import { timezoneTime } from "~/utils"; import { useConfigMutation, useL10n } from "~/queries/l10n"; -import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; +import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; let date; @@ -44,7 +44,7 @@ const timezoneWithDetails = (timezone) => { const sortedTimezones = (timezones) => { return timezones.sort((timezone1, timezone2) => { - const timezoneText = t => t.parts.join('').toLowerCase(); + const timezoneText = (t) => t.parts.join("").toLowerCase(); return timezoneText(timezone1) > timezoneText(timezone2) ? 1 : -1; }); }; @@ -79,8 +79,9 @@ export default function TimezoneSelection() { label={ <> - {parts.join('-')} - {country} + {parts.join("-")} + {" "} + {country} } description={ @@ -97,24 +98,24 @@ export default function TimezoneSelection() { }); if (timezonesList.length === 0) { - timezonesList = ( - {_("None of the time zones match the filter.")} - ); + timezonesList = {_("None of the time zones match the filter.")}; } return ( <>

{_(" Timezone selection")}

- +
- - {timezonesList} - + {timezonesList}
diff --git a/web/src/components/l10n/TimezoneSelection.test.jsx b/web/src/components/l10n/TimezoneSelection.test.jsx index 61e882fc8f..4a1b3ae061 100644 --- a/web/src/components/l10n/TimezoneSelection.test.jsx +++ b/web/src/components/l10n/TimezoneSelection.test.jsx @@ -27,22 +27,22 @@ import { mockNavigateFn, plainRender } from "~/test-utils"; const timezones = [ { id: "Europe/Berlin", parts: ["Europe", "Berlin"], country: "Germany", utcOffset: 1 }, - { id: "Europe/Madrid", parts: ["Europe", "Madrid"], country: "Spain", utfOffset: 1 } + { id: "Europe/Madrid", parts: ["Europe", "Madrid"], country: "Spain", utfOffset: 1 }, ]; const mockConfigMutation = { - mutate: jest.fn() + mutate: jest.fn(), }; jest.mock("~/queries/l10n", () => ({ ...jest.requireActual("~/queries/l10n"), useConfigMutation: () => mockConfigMutation, - useL10n: () => ({ timezones, selectedTimezone: timezones[0] }) + useL10n: () => ({ timezones, selectedTimezone: timezones[0] }), })); jest.mock("react-router-dom", () => ({ ...jest.requireActual("react-router-dom"), - useNavigate: () => mockNavigateFn + useNavigate: () => mockNavigateFn, })); it("allows changing the keyboard", async () => { diff --git a/web/src/components/layout/Center.jsx b/web/src/components/layout/Center.jsx index 5e692e9c95..52bd24f759 100644 --- a/web/src/components/layout/Center.jsx +++ b/web/src/components/layout/Center.jsx @@ -50,9 +50,7 @@ import React from "react"; */ const Center = ({ children, ...htmlProps }) => (
-
- {children} -
+
{children}
); diff --git a/web/src/components/layout/Icon.jsx b/web/src/components/layout/Icon.jsx index 086d6b60fe..47ad45b5ec 100644 --- a/web/src/components/layout/Icon.jsx +++ b/web/src/components/layout/Icon.jsx @@ -19,7 +19,7 @@ * find current contact information at www.suse.com. */ -import React from 'react'; +import React from "react"; // NOTE: "@icons" is an alias to use a shorter path to real @material-symbols // icons location. Check the tsconfig.json file to see its value. @@ -150,12 +150,10 @@ const icons = { wifi_off: WifiOff, // brand icons linux_logo: SiLinux, - windows_logo: SiWindows + windows_logo: SiWindows, }; -const PREDEFINED_SIZES = [ - "xxxs", "xxs", "xs", "s", "m", "l", "xl", "xxl", "xxxl" -]; +const PREDEFINED_SIZES = ["xxxs", "xxs", "xs", "s", "m", "l", "xl", "xxl", "xxxl"]; /** * Agama Icon component diff --git a/web/src/components/layout/Icon.test.jsx b/web/src/components/layout/Icon.test.jsx index bf2212b33c..1836e63ba0 100644 --- a/web/src/components/layout/Icon.test.jsx +++ b/web/src/components/layout/Icon.test.jsx @@ -40,20 +40,20 @@ describe("Icon", () => { describe("mounted with a known name", () => { it("renders an aria-hidden SVG element", async () => { const { container } = plainRender(); - const icon = container.querySelector('svg'); + const icon = container.querySelector("svg"); expect(icon).toHaveAttribute("aria-hidden", "true"); }); it("includes the icon name as a data attribute of the SVG", async () => { const { container } = plainRender(); - const icon = container.querySelector('svg'); + const icon = container.querySelector("svg"); expect(icon).toHaveAttribute("data-icon-name", "wifi"); }); describe("and a predefined size", () => { it("adds a CSS class for given size", () => { const { container } = plainRender(); - const icon = container.querySelector('svg'); + const icon = container.querySelector("svg"); // Check that width and height are set to default (see .svgrrc for // production, __mocks__/svg.js for testing) expect(icon).toHaveAttribute("width", "28"); @@ -66,7 +66,7 @@ describe("Icon", () => { describe("and an arbitrary size", () => { it("change the width and height attributes to given value", () => { const { container } = plainRender(); - const icon = container.querySelector('svg'); + const icon = container.querySelector("svg"); expect(icon).toHaveAttribute("width", "1dhv"); expect(icon).toHaveAttribute("height", "1dhv"); }); @@ -77,9 +77,7 @@ describe("Icon", () => { it("outputs to console.error", () => { // @ts-expect-error: It's unlikely to happen, but let's test it anyway plainRender(); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("'apsens' not found") - ); + expect(console.error).toHaveBeenCalledWith(expect.stringContaining("'apsens' not found")); }); it("renders nothing", async () => { @@ -93,9 +91,7 @@ describe("Icon", () => { it("outputs to console.error", () => { // @ts-expect-error: It's unlikely to happen, but let's test it anyway plainRender(); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("not found") - ); + expect(console.error).toHaveBeenCalledWith(expect.stringContaining("not found")); }); it("renders nothing", () => { diff --git a/web/src/components/network/AddressesDataList.jsx b/web/src/components/network/AddressesDataList.jsx index 4c4ff9c3ae..15038a8d4f 100644 --- a/web/src/components/network/AddressesDataList.jsx +++ b/web/src/components/network/AddressesDataList.jsx @@ -28,9 +28,14 @@ import React from "react"; import { Button, - DataList, DataListItem, DataListItemRow, DataListItemCells, DataListCell, DataListAction, + DataList, + DataListItem, + DataListItemRow, + DataListItemCells, + DataListCell, + DataListAction, Flex, - Stack + Stack, } from "@patternfly/react-core"; import { FormLabel } from "~/components/core"; @@ -42,9 +47,9 @@ let index = 0; export default function AddressesDataList({ addresses: originalAddresses, updateAddresses, - allowEmpty = true + allowEmpty = true, }) { - const addresses = originalAddresses.map(addr => { + const addresses = originalAddresses.map((addr) => { const newAddr = addr; if (!newAddr.id) newAddr.id = index++; return newAddr; @@ -56,13 +61,13 @@ export default function AddressesDataList({ }; const updateAddress = (id, field, value) => { - const address = addresses.find(addr => addr.id === id); + const address = addresses.find((addr) => addr.id === id); address[field] = value; updateAddresses(addresses); }; - const deleteAddress = id => { - const addressIdx = addresses.findIndex(addr => addr.id === id); + const deleteAddress = (id) => { + const addressIdx = addresses.findIndex((addr) => addr.id === id); addresses.splice(addressIdx, 1); updateAddresses(addresses); }; @@ -73,7 +78,12 @@ export default function AddressesDataList({ return ( - @@ -99,7 +109,7 @@ export default function AddressesDataList({ placeholder={_("Prefix length or netmask")} aria-label={_("Prefix length or netmask")} /> - + , ]; return ( @@ -121,7 +131,7 @@ export default function AddressesDataList({ {_("Addresses")} - {addresses.map(address => renderAddress(address))} + {addresses.map((address) => renderAddress(address))} @@ -98,7 +109,7 @@ export default function DnsDataList({ servers: originalServers, updateDnsServers {_("DNS")} - {servers.map(server => renderDns(server))} + {servers.map((server) => renderDns(server))} {/* TRANSLATORS: button label */} - + ); diff --git a/web/src/components/network/WifiConnectionForm.test.jsx b/web/src/components/network/WifiConnectionForm.test.jsx index fd77bb5e92..b332ec1069 100644 --- a/web/src/components/network/WifiConnectionForm.test.jsx +++ b/web/src/components/network/WifiConnectionForm.test.jsx @@ -31,7 +31,7 @@ jest.mock("~/client"); Element.prototype.scrollIntoView = jest.fn(); const hiddenNetworkMock = { - hidden: true + hidden: true, }; const networkMock = { @@ -48,7 +48,7 @@ beforeEach(() => { return { network: { addAndConnectTo: addAndConnectToFn, - } + }, }; }); }); @@ -100,7 +100,7 @@ describe("WifiConnectionForm", () => { expect(addAndConnectToFn).toHaveBeenCalledWith( "Wi-Fi Network", - expect.objectContaining({ security: "wpa-psk", password: "wifi-password" }) + expect.objectContaining({ security: "wpa-psk", password: "wifi-password" }), ); }); diff --git a/web/src/components/network/WifiHiddenNetworkForm.jsx b/web/src/components/network/WifiHiddenNetworkForm.jsx index 3519816f4f..9d58ea7848 100644 --- a/web/src/components/network/WifiHiddenNetworkForm.jsx +++ b/web/src/components/network/WifiHiddenNetworkForm.jsx @@ -38,12 +38,13 @@ import { _ } from "~/i18n"; function WifiHiddenNetworkForm({ network, visible, beforeHiding, onSubmitCallback }) { return ( <> - {visible && + {visible && ( } + /> + )} ); } diff --git a/web/src/components/network/WifiHiddenNetworkForm.test.jsx b/web/src/components/network/WifiHiddenNetworkForm.test.jsx index 37bc6b7620..0bd628301c 100644 --- a/web/src/components/network/WifiHiddenNetworkForm.test.jsx +++ b/web/src/components/network/WifiHiddenNetworkForm.test.jsx @@ -26,7 +26,9 @@ import { plainRender } from "~/test-utils"; import { WifiHiddenNetworkForm } from "~/components/network"; -jest.mock("~/components/network/WifiConnectionForm", () => () =>
WifiConnectionForm mock
); +jest.mock("~/components/network/WifiConnectionForm", () => () => ( +
WifiConnectionForm mock
+)); describe("WifiHiddenNetworkForm", () => { describe("when it is visible", () => { diff --git a/web/src/components/network/WifiNetworksListPage.jsx b/web/src/components/network/WifiNetworksListPage.jsx index a12f1d6a4c..b0b21f0f31 100644 --- a/web/src/components/network/WifiNetworksListPage.jsx +++ b/web/src/components/network/WifiNetworksListPage.jsx @@ -22,14 +22,26 @@ import React from "react"; import { Button, - Card, CardBody, - DataList, DataListCell, DataListItem, DataListItemCells, DataListItemRow, - Drawer, DrawerActions, DrawerCloseButton, DrawerContent, DrawerContentBody, DrawerHead, DrawerPanelBody, DrawerPanelContent, + Card, + CardBody, + DataList, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelBody, + DrawerPanelContent, Flex, Label, Spinner, Split, - Stack + Stack, } from "@patternfly/react-core"; import { Icon } from "~/components/layout"; import { WifiConnectionForm } from "~/components/network"; @@ -70,11 +82,7 @@ const connectionAddresses = (network) => { }; const ConnectionData = ({ network }) => { - return ( - - {connectionAddresses(network)} - - ); + return {connectionAddresses(network)}; }; const WifiDrawerPanelBody = ({ network, onCancel, onForget }) => { @@ -96,9 +104,7 @@ const WifiDrawerPanelBody = ({ network, onCancel, onForget }) => { await client.network.connectTo(network.settings)}> {_("Connect")} - - {_("Edit")} - + {_("Edit")} @@ -123,7 +129,11 @@ const WifiDrawerPanelBody = ({ network, onCancel, onForget }) => { {_("Edit")} - @@ -137,11 +147,7 @@ const WifiDrawerPanelBody = ({ network, onCancel, onForget }) => { const NetworkFormName = ({ network }) => { if (!network) return; - return ( -

- {network === HIDDEN_NETWORK ? _("Connect to a hidden network") : network.ssid} -

- ); + return

{network === HIDDEN_NETWORK ? _("Connect to a hidden network") : network.ssid}

; }; const NetworkListName = ({ network }) => { @@ -150,8 +156,16 @@ const NetworkListName = ({ network }) => { return ( {network.ssid} - {network.settings && } - {state === _("Connected") && } + {network.settings && ( + + )} + {state === _("Connected") && ( + + )} ); }; @@ -170,14 +184,14 @@ function WifiNetworksListPage({ selected, onSelectionChange, networks = [], - forceUpdateNetworksCallback = () => { } + forceUpdateNetworksCallback = () => {}, }) { const selectHiddenNetwork = () => { onSelectionChange(HIDDEN_NETWORK); }; const selectNetwork = (ssid) => { - onSelectionChange(networks.find(n => n.ssid === ssid)); + onSelectionChange(networks.find((n) => n.ssid === ssid)); }; const unselectNetwork = () => { @@ -185,7 +199,7 @@ function WifiNetworksListPage({ }; const renderElements = () => { - return networks.map(n => { + return networks.map((n) => { return ( @@ -194,12 +208,19 @@ function WifiNetworksListPage({ - -
{n.security.join(", ")}
-
{n.strength}
+ +
+ {n.security.join(", ")} +
+
+ {n.strength} +
-
+ , ]} />
diff --git a/web/src/components/network/WifiSelectorPage.jsx b/web/src/components/network/WifiSelectorPage.jsx index 2e81023694..1f0a82efa0 100644 --- a/web/src/components/network/WifiSelectorPage.jsx +++ b/web/src/components/network/WifiSelectorPage.jsx @@ -37,7 +37,12 @@ const baseHiddenNetwork = { ssid: undefined, hidden: true }; function WifiSelectorPage() { const { network: client } = useInstallerClient(); - const { connections: initialConnections, devices: initialDevices, accessPoints, networks: initialNetworks } = useLoaderData(); + const { + connections: initialConnections, + devices: initialDevices, + accessPoints, + networks: initialNetworks, + } = useLoaderData(); const [data, saveData] = useLocalStorage("agama-network", { selectedWifi: null }); // Reevaluate how to keep the state in the future const [selected, setSelected] = useState(data.selectedWifi); @@ -77,14 +82,14 @@ function WifiSelectorPage() { // Let's keep the selected network up to date after networks information is // updated (e.g., if the network status change); if (networks) { - setSelected(prev => { - return networksFromValues(networks).find(n => n.ssid === prev?.ssid); + setSelected((prev) => { + return networksFromValues(networks).find((n) => n.ssid === prev?.ssid); }); } }, [networks]); useEffect(() => { - setActiveNetwork(networksFromValues(networks).find(d => d.device)); + setActiveNetwork(networksFromValues(networks).find((d) => d.device)); }, [networks]); useEffect(() => { @@ -107,7 +112,7 @@ function WifiSelectorPage() { const current_device = devices.find((d) => d.name === name); if (data.state === DeviceState.FAILED) { - if (current_device && (data.stateReason === 7)) { + if (current_device && data.stateReason === 7) { console.log(`FAILED Device ${name} updated' with data`, data); setNeedAuth(current_device.connection); } diff --git a/web/src/components/network/routes.js b/web/src/components/network/routes.js index 7c4e21a819..2627c6381f 100644 --- a/web/src/components/network/routes.js +++ b/web/src/components/network/routes.js @@ -40,7 +40,7 @@ const loaders = { }, connection: async ({ params }) => { const connections = await client.network.connections(); - return connections.find(c => c.id === params.id); + return connections.find((c) => c.id === params.id); }, wifis: async () => { const connections = await client.network.connections(); @@ -57,21 +57,21 @@ const routes = { element: , handle: { name: N_("Network"), - icon: "settings_ethernet" + icon: "settings_ethernet", }, children: [ { index: true, element: , loader: loaders.all }, { path: "connections/:id/edit", element: , - loader: loaders.connection + loader: loaders.connection, }, { path: "wifis", element: , loader: loaders.wifis, - } - ] + }, + ], }; export default routes; diff --git a/web/src/components/overview/L10nSection.test.jsx b/web/src/components/overview/L10nSection.test.jsx index a3f29f6945..9b6b944db7 100644 --- a/web/src/components/overview/L10nSection.test.jsx +++ b/web/src/components/overview/L10nSection.test.jsx @@ -26,7 +26,7 @@ import { L10nSection } from "~/components/overview"; const locales = [ { id: "en_US.UTF-8", name: "English", territory: "United States" }, - { id: "de_DE.UTF-8", name: "German", territory: "Germany" } + { id: "de_DE.UTF-8", name: "German", territory: "Germany" }, ]; jest.mock("~/queries/l10n", () => ({ diff --git a/web/src/components/overview/OverviewPage.jsx b/web/src/components/overview/OverviewPage.jsx index 78185b647d..ebc4532ec4 100644 --- a/web/src/components/overview/OverviewPage.jsx +++ b/web/src/components/overview/OverviewPage.jsx @@ -22,8 +22,10 @@ import React, { useEffect, useState } from "react"; import { CardBody, - Grid, GridItem, - Hint, HintBody, + Grid, + GridItem, + Hint, + HintBody, NotificationDrawer, NotificationDrawerBody, NotificationDrawerList, @@ -44,7 +46,7 @@ import { _ } from "~/i18n"; const SCOPE_HEADERS = { users: _("Users"), storage: _("Storage"), - software: _("Software") + software: _("Software"), }; const ReadyForInstallation = () => ( @@ -65,7 +67,11 @@ const IssuesList = ({ issues }) => { const link = ( - + {issue.description} @@ -78,9 +84,7 @@ const IssuesList = ({ issues }) => { return ( - - {list} - + {list} ); @@ -94,13 +98,11 @@ export default function OverviewPage() { client.issues().then(setIssues); }, [client]); - const resultSectionProps = - issues.isEmpty - ? {} - : { - + const resultSectionProps = issues.isEmpty + ? {} + : { label: _("Installation"), - description: _("Before installing, please check the following problems.") + description: _("Before installing, please check the following problems."), }; return ( @@ -111,7 +113,7 @@ export default function OverviewPage() { {_( - "Take your time to check your configuration before starting the installation process." + "Take your time to check your configuration before starting the installation process.", )} @@ -120,7 +122,7 @@ export default function OverviewPage() { diff --git a/web/src/components/overview/OverviewPage.test.jsx b/web/src/components/overview/OverviewPage.test.jsx index cb0a26f2b5..a5024b9284 100644 --- a/web/src/components/overview/OverviewPage.test.jsx +++ b/web/src/components/overview/OverviewPage.test.jsx @@ -32,7 +32,7 @@ jest.mock("~/client"); jest.mock("~/queries/software", () => ({ ...jest.requireActual("~/queries/software"), useProduct: () => ({ selectedProduct: mockSelectedProduct }), - useProductChanges: () => jest.fn() + useProductChanges: () => jest.fn(), })); jest.mock("~/components/overview/L10nSection", () => () =>
Localization Section
); @@ -44,9 +44,9 @@ beforeEach(() => { createClient.mockImplementation(() => { return { manager: { - startInstallation: startInstallationFn + startInstallation: startInstallationFn, }, - issues: jest.fn().mockResolvedValue({ isEmpty: true }) + issues: jest.fn().mockResolvedValue({ isEmpty: true }), }; }); }); diff --git a/web/src/components/overview/SoftwareSection.jsx b/web/src/components/overview/SoftwareSection.jsx index 69b7d9664c..3070acd1d2 100644 --- a/web/src/components/overview/SoftwareSection.jsx +++ b/web/src/components/overview/SoftwareSection.jsx @@ -46,7 +46,7 @@ export default function SoftwareSection() { if (proposal.patterns === undefined) return; const ids = Object.keys(proposal.patterns); - const selected = patterns.filter(p => ids.includes(p.name)).sort((a, b) => a.order - b.order); + const selected = patterns.filter((p) => ids.includes(p.name)).sort((a, b) => a.order - b.order); setSelectedPatterns(selected); }, [client, proposal, patterns]); @@ -73,7 +73,7 @@ export default function SoftwareSection() { {msg2} - {selectedPatterns.map(p => ( + {selectedPatterns.map((p) => ( {p.summary} ))} diff --git a/web/src/components/overview/SoftwareSection.test.jsx b/web/src/components/overview/SoftwareSection.test.jsx index 7c32f146ea..5c9d779f1f 100644 --- a/web/src/components/overview/SoftwareSection.test.jsx +++ b/web/src/components/overview/SoftwareSection.test.jsx @@ -33,7 +33,7 @@ const gnomePattern = { category: "Graphical Environments", icon: "./pattern-gnome", summary: "GNOME Desktop Environment (Wayland)", - order: 1120 + order: 1120, }; const kdePattern = { @@ -41,7 +41,7 @@ const kdePattern = { category: "Graphical Environments", icon: "./pattern-kde", summary: "KDE Applications and Plasma Desktop", - order: 1110 + order: 1110, }; beforeEach(() => { @@ -50,8 +50,8 @@ beforeEach(() => { software: { onSelectedPatternsChanged: noop, getProposal: jest.fn().mockResolvedValue({ size: "500 MiB", patterns: { kde: 1 } }), - getPatterns: jest.fn().mockResolvedValue([gnomePattern, kdePattern]) - } + getPatterns: jest.fn().mockResolvedValue([gnomePattern, kdePattern]), + }, }; }); }); diff --git a/web/src/components/overview/StorageSection.jsx b/web/src/components/overview/StorageSection.jsx index b26dc82ee9..2a579eba3b 100644 --- a/web/src/components/overview/StorageSection.jsx +++ b/web/src/components/overview/StorageSection.jsx @@ -45,27 +45,27 @@ import { IDLE } from "~/client/status"; * @param {String} policy Find space policy * @returns {String} Translated description */ -const msgLvmMultipleDisks = policy => { +const msgLvmMultipleDisks = (policy) => { switch (policy) { case "resize": // TRANSLATORS: installing on an LVM with multiple physical partitions/disks return _( - "Install in a new Logical Volume Manager (LVM) volume group shrinking existing partitions at the underlying devices as needed" + "Install in a new Logical Volume Manager (LVM) volume group shrinking existing partitions at the underlying devices as needed", ); case "keep": // TRANSLATORS: installing on an LVM with multiple physical partitions/disks return _( - "Install in a new Logical Volume Manager (LVM) volume group without modifying the partitions at the underlying devices" + "Install in a new Logical Volume Manager (LVM) volume group without modifying the partitions at the underlying devices", ); case "delete": // TRANSLATORS: installing on an LVM with multiple physical partitions/disks return _( - "Install in a new Logical Volume Manager (LVM) volume group deleting all the content of the underlying devices" + "Install in a new Logical Volume Manager (LVM) volume group deleting all the content of the underlying devices", ); case "custom": // TRANSLATORS: installing on an LVM with multiple physical partitions/disks return _( - "Install in a new Logical Volume Manager (LVM) volume group using a custom strategy to find the needed space at the underlying devices" + "Install in a new Logical Volume Manager (LVM) volume group using a custom strategy to find the needed space at the underlying devices", ); } }; @@ -77,31 +77,31 @@ const msgLvmMultipleDisks = policy => { * @returns {String} Translated description with %s placeholder for the device * name */ -const msgLvmSingleDisk = policy => { +const msgLvmSingleDisk = (policy) => { switch (policy) { case "resize": // TRANSLATORS: installing on an LVM with a single physical partition/disk, // %s will be replaced by a device name and its size (eg. "/dev/sda, 20 GiB") return _( - "Install in a new Logical Volume Manager (LVM) volume group on %s shrinking existing partitions as needed" + "Install in a new Logical Volume Manager (LVM) volume group on %s shrinking existing partitions as needed", ); case "keep": // TRANSLATORS: installing on an LVM with a single physical partition/disk, // %s will be replaced by a device name and its size (eg. "/dev/sda, 20 GiB") return _( - "Install in a new Logical Volume Manager (LVM) volume group on %s without modifying existing partitions" + "Install in a new Logical Volume Manager (LVM) volume group on %s without modifying existing partitions", ); case "delete": // TRANSLATORS: installing on an LVM with a single physical partition/disk, // %s will be replaced by a device name and its size (eg. "/dev/sda, 20 GiB") return _( - "Install in a new Logical Volume Manager (LVM) volume group on %s deleting all its content" + "Install in a new Logical Volume Manager (LVM) volume group on %s deleting all its content", ); case "custom": // TRANSLATORS: installing on an LVM with a single physical partition/disk, // %s will be replaced by a device name and its size (eg. "/dev/sda, 20 GiB") return _( - "Install in a new Logical Volume Manager (LVM) volume group on %s using a custom strategy to find the needed space" + "Install in a new Logical Volume Manager (LVM) volume group on %s using a custom strategy to find the needed space", ); } }; @@ -137,7 +137,7 @@ export default function StorageSection() { useEffect(loadProposal, [loadProposal]); useEffect(() => { - return client.storage.onStatusChange(status => { + return client.storage.onStatusChange((status) => { if (status === IDLE) { loadProposal(); } @@ -146,8 +146,8 @@ export default function StorageSection() { if (result === undefined) return; - const label = deviceName => { - const device = availableDevices.find(d => d.name === deviceName); + const label = (deviceName) => { + const device = availableDevices.find((d) => d.name === deviceName); return device ? deviceLabel(device) : deviceName; }; @@ -155,7 +155,11 @@ export default function StorageSection() { const pvDevices = result.settings.targetPVDevices; if (pvDevices.length > 1) { - return {msgLvmMultipleDisks(result.settings.spacePolicy)}; + return ( + + {msgLvmMultipleDisks(result.settings.spacePolicy)} + + ); } else { const [msg1, msg2] = msgLvmSingleDisk(result.settings.spacePolicy).split("%s"); @@ -174,7 +178,7 @@ export default function StorageSection() { const targetDevice = result.settings.targetDevice; if (!targetDevice) return {_("No device selected yet")}; - const fullMsg = policy => { + const fullMsg = (policy) => { switch (policy) { case "resize": // TRANSLATORS: %s will be replaced by the device name and its size, diff --git a/web/src/components/overview/StorageSection.test.jsx b/web/src/components/overview/StorageSection.test.jsx index 26de852153..5a3ba9a431 100644 --- a/web/src/components/overview/StorageSection.test.jsx +++ b/web/src/components/overview/StorageSection.test.jsx @@ -29,25 +29,25 @@ jest.mock("~/client"); const availableDevices = [ { name: "/dev/sda", size: 536870912000 }, - { name: "/dev/sdb", size: 697932185600 } + { name: "/dev/sdb", size: 697932185600 }, ]; const proposalResult = { settings: { target: "disk", targetDevice: "/dev/sda", - spacePolicy: "delete" + spacePolicy: "delete", }, - actions: [] + actions: [], }; const storageMock = { probe: jest.fn().mockResolvedValue(0), proposal: { getAvailableDevices: jest.fn().mockResolvedValue(availableDevices), - getResult: jest.fn().mockResolvedValue(proposalResult) + getResult: jest.fn().mockResolvedValue(proposalResult), }, - onStatusChange: jest.fn() + onStatusChange: jest.fn(), }; let storage; diff --git a/web/src/components/overview/routes.js b/web/src/components/overview/routes.js index ec0a565658..f9f98041a5 100644 --- a/web/src/components/overview/routes.js +++ b/web/src/components/overview/routes.js @@ -28,8 +28,8 @@ const routes = { element: , handle: { name: N_("Overview"), - icon: "list_alt" - } + icon: "list_alt", + }, }; export default routes; diff --git a/web/src/components/product/ProductRegistrationForm.test.jsx b/web/src/components/product/ProductRegistrationForm.test.jsx index 645285f26b..2c4a9ea44e 100644 --- a/web/src/components/product/ProductRegistrationForm.test.jsx +++ b/web/src/components/product/ProductRegistrationForm.test.jsx @@ -25,12 +25,12 @@ import { screen } from "@testing-library/react"; import { plainRender } from "~/test-utils"; import { ProductRegistrationForm } from "~/components/product"; -it.skip("renders a field for entering the registration code", async() => { +it.skip("renders a field for entering the registration code", async () => { plainRender(); await screen.findByLabelText(/Registration code/); }); -it.skip("renders a field for entering an email", async() => { +it.skip("renders a field for entering an email", async () => { plainRender(); await screen.findByLabelText("Email"); }); @@ -42,7 +42,9 @@ const ProductRegistrationFormTest = () => { return ( <> - + {isSubmitted &&

Form is submitted!

} {isValid === false &&

Form is not valid!

} diff --git a/web/src/components/product/ProductRegistrationPage.jsx b/web/src/components/product/ProductRegistrationPage.jsx index 9b74b7beb2..df3672003b 100644 --- a/web/src/components/product/ProductRegistrationPage.jsx +++ b/web/src/components/product/ProductRegistrationPage.jsx @@ -64,29 +64,26 @@ export default function ProductRegistrationPage() { <>

{sprintf(_("Register %s"), selectedProduct.name)}

- { - error && + {error && (

{error}

- } + )}
setCode(v)} /> - setEmail(v)} - /> + setEmail(v)} />
- {_("Accept")} + + {_("Accept")} + ); diff --git a/web/src/components/product/ProductSelectionPage.jsx b/web/src/components/product/ProductSelectionPage.jsx index 1ba59c83cd..bd5f2a8403 100644 --- a/web/src/components/product/ProductSelectionPage.jsx +++ b/web/src/components/product/ProductSelectionPage.jsx @@ -20,14 +20,8 @@ */ import React, { useState } from "react"; -import { - Card, CardBody, - Flex, - Form, - Grid, GridItem, - Radio -} from "@patternfly/react-core"; -import styles from '@patternfly/react-styles/css/utilities/Text/text'; +import { Card, CardBody, Flex, Form, Grid, GridItem, Radio } from "@patternfly/react-core"; +import styles from "@patternfly/react-styles/css/utilities/Text/text"; import { _ } from "~/i18n"; import { Page } from "~/components/core"; @@ -35,9 +29,7 @@ import { Loading, Center } from "~/components/layout"; import { useConfigMutation, useProduct } from "~/queries/software"; const Label = ({ children }) => ( - - {children} - + {children} ); function ProductSelectionPage() { @@ -63,7 +55,7 @@ function ProductSelectionPage() { ); }; - const isSelectionDisabled = !nextProduct || (nextProduct === selectedProduct); + const isSelectionDisabled = !nextProduct || nextProduct === selectedProduct; return (
diff --git a/web/src/components/product/ProductSelectionPage.test.jsx b/web/src/components/product/ProductSelectionPage.test.jsx index 4a9db4db8a..f742352726 100644 --- a/web/src/components/product/ProductSelectionPage.test.jsx +++ b/web/src/components/product/ProductSelectionPage.test.jsx @@ -29,13 +29,13 @@ const products = [ { id: "Tumbleweed", name: "openSUSE Tumbleweed", - description: "Tumbleweed description..." + description: "Tumbleweed description...", }, { id: "MicroOS", name: "openSUSE MicroOS", - description: "MicroOS description" - } + description: "MicroOS description", + }, ]; jest.mock("~/client"); @@ -44,21 +44,21 @@ jest.mock("~/queries/software", () => ({ useProduct: () => { return { products, - selectedProduct: products[0] + selectedProduct: products[0], }; }, - useProductChanges: () => jest.fn() + useProductChanges: () => jest.fn(), })); const managerMock = { - startProbing: jest.fn() + startProbing: jest.fn(), }; const productMock = { getAll: () => Promise.resolve(products), getSelected: jest.fn(() => Promise.resolve(products[0])), select: jest.fn().mockResolvedValue(), - onChange: jest.fn() + onChange: jest.fn(), }; beforeEach(() => { diff --git a/web/src/components/questions/GenericQuestion.jsx b/web/src/components/questions/GenericQuestion.jsx index f0f3802cff..8028b4be97 100644 --- a/web/src/components/questions/GenericQuestion.jsx +++ b/web/src/components/questions/GenericQuestion.jsx @@ -33,9 +33,7 @@ export default function GenericQuestion({ question, answerCallback }) { return ( - - { question.text } - + {question.text} ( - installerRender() -); +const renderQuestion = () => + installerRender(); describe("GenericQuestion", () => { it("renders the question text", async () => { @@ -44,7 +43,7 @@ describe("GenericQuestion", () => { await screen.findByText(question.text); }); - it("sets chosen option and calls the callback after user clicking an action", async() => { + it("sets chosen option and calls the callback after user clicking an action", async () => { const { user } = renderQuestion(); let button = await screen.findByRole("button", { name: /Sometimes/ }); diff --git a/web/src/components/questions/LuksActivationQuestion.jsx b/web/src/components/questions/LuksActivationQuestion.jsx index e42b0eb969..8e31b915fc 100644 --- a/web/src/components/questions/LuksActivationQuestion.jsx +++ b/web/src/components/questions/LuksActivationQuestion.jsx @@ -60,12 +60,10 @@ export default function LuksActivationQuestion({ question, answerCallback }) { aria-label={_("Question")} titleIconVariant={() => } > - { renderAlert(parseInt(question.data.attempt)) } - - { question.text } - + {renderAlert(parseInt(question.data.attempt))} + {question.text}
- { /* TRANSLATORS: field label */ } + {/* TRANSLATORS: field label */} ( - installerRender() -); +const renderQuestion = () => + installerRender(); describe("LuksActivationQuestion", () => { beforeEach(() => { @@ -97,7 +96,7 @@ describe("LuksActivationQuestion", () => { }); describe("by clicking on 'Skip'", () => { - it("calls the callback after setting both, answer and password", async() => { + it("calls the callback after setting both, answer and password", async () => { const { user } = renderQuestion(); const passwordInput = await screen.findByLabelText("Encryption Password"); @@ -105,13 +104,15 @@ describe("LuksActivationQuestion", () => { const skipButton = await screen.findByRole("button", { name: /Skip/ }); await user.click(skipButton); - expect(question).toEqual(expect.objectContaining({ password: "notSecret", answer: "skip" })); + expect(question).toEqual( + expect.objectContaining({ password: "notSecret", answer: "skip" }), + ); expect(answerFn).toHaveBeenCalledWith(question); }); }); describe("by clicking on 'Decrypt'", () => { - it("calls the callback after setting both, answer and password", async() => { + it("calls the callback after setting both, answer and password", async () => { const { user } = renderQuestion(); const passwordInput = await screen.findByLabelText("Encryption Password"); @@ -119,19 +120,23 @@ describe("LuksActivationQuestion", () => { const decryptButton = await screen.findByRole("button", { name: /Decrypt/ }); await user.click(decryptButton); - expect(question).toEqual(expect.objectContaining({ password: "notSecret", answer: "decrypt" })); + expect(question).toEqual( + expect.objectContaining({ password: "notSecret", answer: "decrypt" }), + ); expect(answerFn).toHaveBeenCalledWith(question); }); }); describe("submitting the form by pressing 'enter'", () => { - it("calls the callback after setting both, answer and password", async() => { + it("calls the callback after setting both, answer and password", async () => { const { user } = renderQuestion(); const passwordInput = await screen.findByLabelText("Encryption Password"); await user.type(passwordInput, "notSecret{enter}"); - expect(question).toEqual(expect.objectContaining({ password: "notSecret", answer: "decrypt" })); + expect(question).toEqual( + expect.objectContaining({ password: "notSecret", answer: "decrypt" }), + ); expect(answerFn).toHaveBeenCalledWith(question); }); }); diff --git a/web/src/components/questions/QuestionActions.jsx b/web/src/components/questions/QuestionActions.jsx index 021f1ca1cc..f0ececc376 100644 --- a/web/src/components/questions/QuestionActions.jsx +++ b/web/src/components/questions/QuestionActions.jsx @@ -31,7 +31,7 @@ import { Popup } from "~/components/core"; * @param {String} text - string to be capitalized * @return {String} capitalized text */ -const label = text => `${text[0].toUpperCase()}${text.slice(1)}`; +const label = (text) => `${text[0].toUpperCase()}${text.slice(1)}`; /** * A component for building a Question actions, using the defaultAction @@ -48,7 +48,7 @@ const label = text => `${text[0].toUpperCase()}${text.slice(1)}`; * @param {Object} [props.conditions={}] - an object holding conditions, like when an action is disabled */ export default function QuestionActions({ actions, defaultAction, actionCallback, conditions }) { - let [[primaryAction], secondaryActions] = partition(actions, a => a === defaultAction); + let [[primaryAction], secondaryActions] = partition(actions, (a) => a === defaultAction); // Ensure there is always a primary action if (!primaryAction) [primaryAction, ...secondaryActions] = secondaryActions; @@ -62,17 +62,15 @@ export default function QuestionActions({ actions, defaultAction, actionCallback > {label(primaryAction)} - { - secondaryActions.map(action => - actionCallback(action)} - isDisabled={conditions?.disable?.[action]} - > - {label(action)} - - ) - } + {secondaryActions.map((action) => ( + actionCallback(action)} + isDisabled={conditions?.disable?.[action]} + > + {label(action)} + + ))} ); } diff --git a/web/src/components/questions/QuestionActions.test.jsx b/web/src/components/questions/QuestionActions.test.jsx index c6b7475866..f390d1d2d2 100644 --- a/web/src/components/questions/QuestionActions.test.jsx +++ b/web/src/components/questions/QuestionActions.test.jsx @@ -30,21 +30,20 @@ let question = { id: 1, text: "Should we use a component for rendering actions?", options: ["no", "maybe", "sure"], - defaultOption + defaultOption, }; const actionCallback = jest.fn(); -const renderQuestionActions = () => ( +const renderQuestionActions = () => installerRender( - ) -); + conditions={{ disable: { no: true } }} + />, + ); describe("QuestionActions", () => { describe("when question has a default option", () => { @@ -95,10 +94,10 @@ describe("QuestionActions", () => { renderQuestionActions(); let button = await screen.findByRole("button", { name: "No" }); - expect(button).toHaveAttribute('disabled'); + expect(button).toHaveAttribute("disabled"); button = await screen.findByRole("button", { name: "Maybe" }); - expect(button).not.toHaveAttribute('disabled'); + expect(button).not.toHaveAttribute("disabled"); }); it("calls the actionCallback when user clicks on action", async () => { diff --git a/web/src/components/questions/Questions.jsx b/web/src/components/questions/Questions.jsx index 2b5e48e505..14be33362d 100644 --- a/web/src/components/questions/Questions.jsx +++ b/web/src/components/questions/Questions.jsx @@ -40,10 +40,13 @@ export default function Questions() { [], ); - const answerQuestion = useCallback((question) => { - client.questions.answer(question); - removeQuestion(question.id); - }, [client.questions, removeQuestion]); + const answerQuestion = useCallback( + (question) => { + client.questions.answer(question); + removeQuestion(question.id); + }, + [client.questions, removeQuestion], + ); useEffect(() => { client.questions.listenQuestions(); diff --git a/web/src/components/questions/Questions.test.jsx b/web/src/components/questions/Questions.test.jsx index 6c4d6e625b..e951a853f9 100644 --- a/web/src/components/questions/Questions.test.jsx +++ b/web/src/components/questions/Questions.test.jsx @@ -29,10 +29,9 @@ import { Questions } from "~/components/questions"; jest.mock("~/client"); jest.mock("~/components/questions/GenericQuestion", () => () =>
A Generic question mock
); -jest.mock( - "~/components/questions/LuksActivationQuestion", - () => () =>
A LUKS activation question mock
, -); +jest.mock("~/components/questions/LuksActivationQuestion", () => () => ( +
A LUKS activation question mock
+)); const handlers = {}; const genericQuestion = { id: 1, type: "generic" }; diff --git a/web/src/components/software/SoftwarePage.jsx b/web/src/components/software/SoftwarePage.jsx index a758559b4f..49c55ee89e 100644 --- a/web/src/components/software/SoftwarePage.jsx +++ b/web/src/components/software/SoftwarePage.jsx @@ -39,7 +39,7 @@ import { DescriptionListTerm, Grid, GridItem, - Stack + Stack, } from "@patternfly/react-core"; /** @@ -61,11 +61,11 @@ import { */ function buildPatterns(patterns, selection) { return patterns - .map(pattern => { + .map((pattern) => { const selectedBy = selection[pattern.name] !== undefined ? selection[pattern.name] : 2; return { ...pattern, - selectedBy + selectedBy, }; }) .sort((a, b) => a.order - b.order); @@ -79,7 +79,7 @@ function buildPatterns(patterns, selection) { * @return {JSX.Element} */ const SelectedPatternsList = ({ patterns }) => { - const selected = patterns.filter(p => p.selectedBy !== SelectedBy.NONE); + const selected = patterns.filter((p) => p.selectedBy !== SelectedBy.NONE); if (selected.length === 0) { return <>{_("No additional software was selected.")}; @@ -89,7 +89,7 @@ const SelectedPatternsList = ({ patterns }) => {

{_("The following software patterns are selected for installation:")}

- {selected.map(pattern => ( + {selected.map((pattern) => ( {pattern.summary} {pattern.description} @@ -125,8 +125,8 @@ function SoftwarePage() { useEffect(() => { if (!patterns) return; - return client.software.onSelectedPatternsChanged(selection => { - client.software.getProposal().then(proposal => setProposal(proposal)); + return client.software.onSelectedPatternsChanged((selection) => { + client.software.getProposal().then((proposal) => setProposal(proposal)); setPatterns(buildPatterns(patterns, selection)); }); }, [client.software, patterns]); @@ -162,12 +162,12 @@ function SoftwarePage() { - {_("Change selection")} - - } + label={_("Selected patterns")} + actions={ + + {_("Change selection")} + + } > diff --git a/web/src/components/software/SoftwarePatternsSelection.jsx b/web/src/components/software/SoftwarePatternsSelection.jsx index c431d888b5..5f0acdf647 100644 --- a/web/src/components/software/SoftwarePatternsSelection.jsx +++ b/web/src/components/software/SoftwarePatternsSelection.jsx @@ -31,7 +31,7 @@ import { DataListItemCells, DataListItemRow, SearchInput, - Stack + Stack, } from "@patternfly/react-core"; import { Section, Page } from "~/components/core"; @@ -109,13 +109,15 @@ function sortGroups(groups) { * @return {Pattern[]} List of patterns including its selection status */ function buildPatterns(patterns, selection) { - return patterns.map((pattern) => { - const selectedBy = (selection[pattern.name] !== undefined) ? selection[pattern.name] : 2; - return { - ...pattern, - selectedBy, - }; - }).sort((a, b) => a.order - b.order); + return patterns + .map((pattern) => { + const selectedBy = selection[pattern.name] !== undefined ? selection[pattern.name] : 2; + return { + ...pattern, + selectedBy, + }; + }) + .sort((a, b) => a.order - b.order); } /** @@ -151,9 +153,10 @@ function SoftwarePatternsSelection() { if (searchValue !== "") { // case insensitive search const searchData = searchValue.toUpperCase(); - const filtered = patterns.filter((p) => - p.name.toUpperCase().indexOf(searchData) !== -1 || - p.description.toUpperCase().indexOf(searchData) !== -1 + const filtered = patterns.filter( + (p) => + p.name.toUpperCase().indexOf(searchData) !== -1 || + p.description.toUpperCase().indexOf(searchData) !== -1, ); setVisiblePatterns(filtered); } else { @@ -166,17 +169,21 @@ function SoftwarePatternsSelection() { }); }, [patterns, searchValue, client.software]); - const onToggle = useCallback((name) => { - const selected = patterns.filter((p) => p.selectedBy === SelectedBy.USER) - .reduce((all, p) => { - all[p.name] = true; - return all; - }, {}); - const pattern = patterns.find((p) => p.name === name); - selected[name] = pattern.selectedBy === SelectedBy.NONE; + const onToggle = useCallback( + (name) => { + const selected = patterns + .filter((p) => p.selectedBy === SelectedBy.USER) + .reduce((all, p) => { + all[p.name] = true; + return all; + }, {}); + const pattern = patterns.find((p) => p.name === name); + selected[name] = pattern.selectedBy === SelectedBy.NONE; - client.software.selectPatterns(selected); - }, [patterns, client.software]); + client.software.selectPatterns(selected); + }, + [patterns, client.software], + ); // FIXME: use loading indicator when busy, we cannot know if it will be // quickly or not in advance. @@ -190,45 +197,48 @@ function SoftwarePatternsSelection() { // be selected/deselected immediately. // TODO: extract to a DataListSelector component or so. let selector = sortGroups(groups).map((groupName) => { - const selectedIds = groups[groupName].filter((p) => p.selectedBy !== SelectedBy.NONE).map((p) => - p.name - ); + const selectedIds = groups[groupName] + .filter((p) => p.selectedBy !== SelectedBy.NONE) + .map((p) => p.name); return ( -
+
- { - groups[groupName].map(option => ( - - - onToggle(option.name)} aria-labelledby="check-action-item1" name="check-action-check1" isChecked={selectedIds.includes(option.name)} /> - - -
- {option.summary} {option.selectedBy === SelectedBy.AUTO && } -
-
{option.description}
-
- , - ]} - /> -
-
- )) - } + {groups[groupName].map((option) => ( + + + onToggle(option.name)} + aria-labelledby="check-action-item1" + name="check-action-check1" + isChecked={selectedIds.includes(option.name)} + /> + + +
+ {option.summary}{" "} + {option.selectedBy === SelectedBy.AUTO && ( + + )} +
+
{option.description}
+
+ , + ]} + /> +
+
+ ))}
); }); if (selector.length === 0) { - selector = ( - {_("None of the patterns match the filter.")} - ); + selector = {_("None of the patterns match the filter.")}; } return ( @@ -250,9 +260,7 @@ function SoftwarePatternsSelection() { - - {selector} - + {selector} diff --git a/web/src/components/software/SoftwarePatternsSelection.test.jsx b/web/src/components/software/SoftwarePatternsSelection.test.jsx index 4d4633d640..1faf1225da 100644 --- a/web/src/components/software/SoftwarePatternsSelection.test.jsx +++ b/web/src/components/software/SoftwarePatternsSelection.test.jsx @@ -42,9 +42,7 @@ describe.skip("SoftwarePatternsSelection", () => { }); it("displays the patterns in a group in correct order", async () => { - plainRender( - , - ); + plainRender(); // the "Base Technologies" pattern group const baseGroup = await screen.findByRole("region", { name: "Base Technologies" }); @@ -58,9 +56,7 @@ describe.skip("SoftwarePatternsSelection", () => { }); it("displays only the matching patterns when using the search filter", async () => { - const { user } = plainRender( - , - ); + const { user } = plainRender(); // enter "multimedia" into the search filter const searchFilter = await screen.findByRole("textbox", { name: "Search" }); @@ -72,17 +68,16 @@ describe.skip("SoftwarePatternsSelection", () => { const desktopGroup = screen.getByRole("region", { name: "Desktop Functions" }); expect(within(desktopGroup).queryByRole("row", { name: /Multimedia/ })).toBeInTheDocument(); - expect(within(desktopGroup).queryByRole("row", { name: /Office Software/ })).not - .toBeInTheDocument(); + expect( + within(desktopGroup).queryByRole("row", { name: /Office Software/ }), + ).not.toBeInTheDocument(); }); it("displays the checkbox depending whether the patter is selected", async () => { const pattern = patterns.find((p) => p.name === "yast2_basis"); pattern.selectedBy = SelectedBy.USER; - plainRender( - , - ); + plainRender(); // the "Base Technologies" pattern group const baseGroup = await screen.findByRole("region", { name: "Base Technologies" }); diff --git a/web/src/components/software/UsedSize.jsx b/web/src/components/software/UsedSize.jsx index ce7467726a..499ec1369c 100644 --- a/web/src/components/software/UsedSize.jsx +++ b/web/src/components/software/UsedSize.jsx @@ -34,9 +34,7 @@ export default function UsedSize({ size }) { return ( -

- {_("This space includes the base system and the selected software patterns.")} -

+

{_("This space includes the base system and the selected software patterns.")}

); } diff --git a/web/src/components/software/icons/README.md b/web/src/components/software/icons/README.md index c945854d48..99db442b53 100644 --- a/web/src/components/software/icons/README.md +++ b/web/src/components/software/icons/README.md @@ -1,4 +1,3 @@ # Status Icons -The package status icons were copied from the [libyui-qt-pkg]( -https://github.com/libyui/libyui/tree/master/libyui-qt-pkg/src/icons) package. +The package status icons were copied from the [libyui-qt-pkg](https://github.com/libyui/libyui/tree/master/libyui-qt-pkg/src/icons) package. diff --git a/web/src/components/software/routes.js b/web/src/components/software/routes.js index 0f821f1646..e6214c434f 100644 --- a/web/src/components/software/routes.js +++ b/web/src/components/software/routes.js @@ -30,18 +30,18 @@ const routes = { element: , handle: { name: N_("Software"), - icon: "apps" + icon: "apps", }, children: [ { index: true, - element: + element: , }, { path: "patterns/select", element: , - } - ] + }, + ], }; export default routes; diff --git a/web/src/components/storage/BootConfigField.jsx b/web/src/components/storage/BootConfigField.jsx index 669feb30db..eb509be202 100644 --- a/web/src/components/storage/BootConfigField.jsx +++ b/web/src/components/storage/BootConfigField.jsx @@ -42,11 +42,7 @@ import { Icon } from "~/components/layout"; const Link = ({ isBold = false }) => { const text = _("Change boot options"); - return ( - - {isBold ? {text} : text} - - ); + return {isBold ? {text} : text}; }; /** @@ -67,12 +63,7 @@ const Link = ({ isBold = false }) => { * * @param {BootConfigFieldProps} props */ -export default function BootConfigField({ - configureBoot, - bootDevice, - isLoading, - onChange -}) { +export default function BootConfigField({ configureBoot, bootDevice, isLoading, onChange }) { const onAccept = ({ configureBoot, bootDevice }) => { onChange({ configureBoot, bootDevice }); }; @@ -84,12 +75,20 @@ export default function BootConfigField({ let value; if (!configureBoot) { - value = <> {_("Installation will not configure partitions for booting.")}; + value = ( + <> + {" "} + {_("Installation will not configure partitions for booting.")} + + ); } else if (!bootDevice) { value = _("Installation will configure partitions for booting at the installation disk."); } else { // TRANSLATORS: %s is the disk used to configure the boot-related partitions (eg. "/dev/sda, 80 GiB) - value = sprintf(_("Installation will configure partitions for booting at %s."), deviceLabel(bootDevice)); + value = sprintf( + _("Installation will configure partitions for booting at %s."), + deviceLabel(bootDevice), + ); } return ( diff --git a/web/src/components/storage/BootConfigField.test.jsx b/web/src/components/storage/BootConfigField.test.jsx index e478b95651..f1e66f0a5f 100644 --- a/web/src/components/storage/BootConfigField.test.jsx +++ b/web/src/components/storage/BootConfigField.test.jsx @@ -49,7 +49,7 @@ const sda = { name: "/dev/sda", size: 1024, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }; @@ -64,7 +64,7 @@ beforeEach(() => { defaultBootDevice: undefined, availableDevices: [sda], isLoading: false, - onChange: jest.fn() + onChange: jest.fn(), }; }); diff --git a/web/src/components/storage/BootSelection.jsx b/web/src/components/storage/BootSelection.jsx index 0dbb3383ad..0241f51a32 100644 --- a/web/src/components/storage/BootSelection.jsx +++ b/web/src/components/storage/BootSelection.jsx @@ -23,12 +23,7 @@ import React, { useCallback, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { - Card, CardBody, - Form, FormGroup, - Radio, - Stack -} from "@patternfly/react-core"; +import { Card, CardBody, Form, FormGroup, Radio, Stack } from "@patternfly/react-core"; import { _ } from "~/i18n"; import { DevicesFormSelect } from "~/components/storage"; import { Page } from "~/components/core"; @@ -37,7 +32,7 @@ import { deviceLabel } from "~/components/storage/utils"; import { sprintf } from "sprintf-js"; import { useCancellablePromise } from "~/utils"; import { useInstallerClient } from "~/context/installer"; -import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; +import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; // FIXME: improve classNames // FIXME: improve and rename to BootSelectionDialog @@ -86,7 +81,7 @@ export default function BootSelectionDialog() { selectedOption = BOOT_MANUAL_ID; } - const findDevice = (name) => availableDevices.find(d => d.name === name); + const findDevice = (name) => availableDevices.find((d) => d.name === name); setState({ load: true, @@ -94,7 +89,7 @@ export default function BootSelectionDialog() { configureBoot, defaultBootDevice: findDevice(defaultBootDevice), availableDevices, - selectedOption + selectedOption, }); }; @@ -125,7 +120,7 @@ export default function BootSelectionDialog() { const description = _( "To ensure the new system is able to boot, the installer may need to create or configure some \ -partitions in the appropriate disk." +partitions in the appropriate disk.", ); const automaticText = () => { @@ -136,7 +131,7 @@ partitions in the appropriate disk." return sprintf( // TRANSLATORS: %s is replaced by a device name and size (e.g., "/dev/sda, 500GiB") _("Partitions to boot will be allocated at the installation disk (%s)."), - deviceLabel(state.defaultBootDevice) + deviceLabel(state.defaultBootDevice), ); }; @@ -166,7 +161,12 @@ partitions in the appropriate disk." defaultChecked={state.selectedOption === BOOT_AUTO_ID} onChange={updateSelectedOption} label={ - + {_("Automatic")} } @@ -179,7 +179,12 @@ partitions in the appropriate disk." defaultChecked={state.selectedOption === BOOT_MANUAL_ID} onChange={updateSelectedOption} label={ - + {_("Select a disk")} } @@ -206,13 +211,20 @@ partitions in the appropriate disk." defaultChecked={state.selectedOption === BOOT_DISABLED_ID} onChange={updateSelectedOption} label={ - + {_("Do not configure")} } body={
- {_("No partitions will be automatically configured for booting. Use with caution.")} + {_( + "No partitions will be automatically configured for booting. Use with caution.", + )}
} /> diff --git a/web/src/components/storage/BootSelection.test.jsx b/web/src/components/storage/BootSelection.test.jsx index df352f933b..9b65e98c75 100644 --- a/web/src/components/storage/BootSelection.test.jsx +++ b/web/src/components/storage/BootSelection.test.jsx @@ -73,7 +73,7 @@ const sdb = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }; /** @type {StorageDevice} */ @@ -96,7 +96,7 @@ const sdc = { shrinking: { unsupported: ["Resizing is not supported"] }, systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }; let props; @@ -110,7 +110,7 @@ describe.skip("BootSelection", () => { bootDevice: undefined, defaultBootDevice: undefined, onCancel: jest.fn(), - onAccept: jest.fn() + onAccept: jest.fn(), }; }); @@ -205,7 +205,7 @@ describe.skip("BootSelection", () => { expect(props.onAccept).toHaveBeenCalledWith({ configureBoot: true, - bootDevice: undefined + bootDevice: undefined, }); }); }); @@ -229,7 +229,7 @@ describe.skip("BootSelection", () => { expect(props.onAccept).toHaveBeenCalledWith({ configureBoot: true, - bootDevice: sdb + bootDevice: sdb, }); }); }); @@ -250,7 +250,7 @@ describe.skip("BootSelection", () => { expect(props.onAccept).toHaveBeenCalledWith({ configureBoot: false, - bootDevice: undefined + bootDevice: undefined, }); }); }); diff --git a/web/src/components/storage/DASDFormatProgress.jsx b/web/src/components/storage/DASDFormatProgress.jsx index 74c9047523..5caac19079 100644 --- a/web/src/components/storage/DASDFormatProgress.jsx +++ b/web/src/components/storage/DASDFormatProgress.jsx @@ -20,7 +20,7 @@ */ import React, { useEffect, useState } from "react"; -import { Progress, Skeleton, Stack } from '@patternfly/react-core'; +import { Progress, Skeleton, Stack } from "@patternfly/react-core"; import { Popup } from "~/components/core"; import { _ } from "~/i18n"; import { useInstallerClient } from "~/context/installer"; @@ -30,14 +30,14 @@ export default function DASDFormatProgress({ job, devices, isOpen = true }) { const [progress, setProgress] = useState(undefined); useEffect(() => { - client.dasd.onFormatProgress(job.path, p => setProgress(p)); + client.dasd.onFormatProgress(job.path, (p) => setProgress(p)); }, [client.dasd, job.path]); const ProgressContent = ({ progress }) => { return ( {Object.entries(progress).map(([path, [total, step, done]]) => { - const device = devices.find(d => d.id === path.split("/").slice(-1)[0]); + const device = devices.find((d) => d.id === path.split("/").slice(-1)[0]); return ( + {progress ? : } ); diff --git a/web/src/components/storage/DASDPage.jsx b/web/src/components/storage/DASDPage.jsx index ec6e4a329e..2a11d067ed 100644 --- a/web/src/components/storage/DASDPage.jsx +++ b/web/src/components/storage/DASDPage.jsx @@ -36,19 +36,19 @@ const reducer = (state, action) => { case "ADD_DEVICE": { const { device } = payload; - if (state.devices.find(d => d.id === device.id)) return state; + if (state.devices.find((d) => d.id === device.id)) return state; return { ...state, devices: [...state.devices, device] }; } case "UPDATE_DEVICE": { const { device } = payload; - const index = state.devices.findIndex(d => d.id === device.id); + const index = state.devices.findIndex((d) => d.id === device.id); const devices = [...state.devices]; - index !== -1 ? devices[index] = device : devices.push(device); + index !== -1 ? (devices[index] = device) : devices.push(device); - const selectedDevicesIds = state.selectedDevices.map(d => d.id); - const selectedDevices = devices.filter(d => selectedDevicesIds.includes(d.id)); + const selectedDevicesIds = state.selectedDevices.map((d) => d.id); + const selectedDevices = devices.filter((d) => selectedDevicesIds.includes(d.id)); return { ...state, devices, selectedDevices }; } @@ -56,7 +56,7 @@ const reducer = (state, action) => { case "REMOVE_DEVICE": { const { device } = payload; - return { ...state, devices: state.devices.filter(d => d.id !== device.id) }; + return { ...state, devices: state.devices.filter((d) => d.id !== device.id) }; } case "SET_MIN_CHANNEL": { @@ -76,7 +76,7 @@ const reducer = (state, action) => { case "UNSELECT_DEVICE": { const { device } = payload; - return { ...state, selectedDevices: state.selectedDevices.filter(d => d.id !== device.id) }; + return { ...state, selectedDevices: state.selectedDevices.filter((d) => d.id !== device.id) }; } case "SELECT_ALL_DEVICES": { @@ -158,17 +158,21 @@ export default function DASDPage() { const action = (type, device) => dispatch({ type, payload: { device } }); subscriptions.push( - await client.dasd.deviceEventListener("added", d => action("ADD_DEVICE", d)), - await client.dasd.deviceEventListener("removed", d => action("REMOVE_DEVICE", d)), - await client.dasd.deviceEventListener("changed", d => action("UPDATE_DEVICE", d)) + await client.dasd.deviceEventListener("added", (d) => action("ADD_DEVICE", d)), + await client.dasd.deviceEventListener("removed", (d) => action("REMOVE_DEVICE", d)), + await client.dasd.deviceEventListener("changed", (d) => action("UPDATE_DEVICE", d)), ); - await client.dasd.onJobAdded((data) => dispatch({ type: "START_FORMAT_JOB", payload: { data } })); - await client.dasd.onJobChanged((data) => dispatch({ type: "UPDATE_FORMAT_JOB", payload: { data } })); + await client.dasd.onJobAdded((data) => + dispatch({ type: "START_FORMAT_JOB", payload: { data } }), + ); + await client.dasd.onJobChanged((data) => + dispatch({ type: "UPDATE_FORMAT_JOB", payload: { data } }), + ); }; const unsubscribe = () => { - subscriptions.forEach(fn => fn()); + subscriptions.forEach((fn) => fn()); }; subscribe(); @@ -178,7 +182,9 @@ export default function DASDPage() { return ( <> - {state.formatJob.running && } + {state.formatJob.running && ( + + )} ); } diff --git a/web/src/components/storage/DASDTable.jsx b/web/src/components/storage/DASDTable.jsx index 9f4032e04c..9937b1c4c9 100644 --- a/web/src/components/storage/DASDTable.jsx +++ b/web/src/components/storage/DASDTable.jsx @@ -23,17 +23,24 @@ import React, { useState } from "react"; import { Button, Divider, - Dropdown, DropdownItem, DropdownList, + Dropdown, + DropdownItem, + DropdownList, MenuToggle, - TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, - Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem -} from '@patternfly/react-core'; -import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, +} from "@patternfly/react-core"; +import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; import { Icon } from "~/components/layout"; import { SectionSkeleton } from "~/components/core"; import { _ } from "~/i18n"; import { hex } from "~/utils"; -import { sort } from 'fast-sort'; +import { sort } from "fast-sort"; import { useInstallerClient } from "~/context/installer"; // FIXME: please, note that this file still requiring refinements until reach a @@ -42,13 +49,12 @@ const columnData = (device, column) => { let data = device[column.id]; switch (column.id) { - case 'formatted': - case 'diag': - if (!device.enabled) - data = ""; + case "formatted": + case "diag": + if (!device.enabled) data = ""; break; - case 'partitionInfo': - data = data.split(",").map(d =>
{d}
); + case "partitionInfo": + data = data.split(",").map((d) =>
{d}
); break; } @@ -69,7 +75,7 @@ const columns = [ // usually keep untranslated { id: "diag", label: _("DIAG") }, { id: "formatted", label: _("Formatted") }, - { id: "partitionInfo", label: _("Partition Info") } + { id: "partitionInfo", label: _("Partition Info") }, ]; const Actions = ({ devices, isDisabled }) => { @@ -84,7 +90,7 @@ const Actions = ({ devices, isDisabled }) => { const setDiagOn = () => client.dasd.setDIAG(devices, true); const setDiagOff = () => client.dasd.setDIAG(devices, false); const format = () => { - const offline = devices.filter(d => !d.enabled); + const offline = devices.filter((d) => !d.enabled); if (offline.length > 0) { return false; @@ -94,7 +100,9 @@ const Actions = ({ devices, isDisabled }) => { }; const Action = ({ children, ...props }) => ( - {children} + + {children} + ); return ( @@ -109,38 +117,48 @@ const Actions = ({ devices, isDisabled }) => { )} > - { /** TRANSLATORS: drop down menu action, activate the device */} - {_("Activate")} - { /** TRANSLATORS: drop down menu action, deactivate the device */} - {_("Deactivate")} + {/** TRANSLATORS: drop down menu action, activate the device */} + + {_("Activate")} + + {/** TRANSLATORS: drop down menu action, deactivate the device */} + + {_("Deactivate")} + - { /** TRANSLATORS: drop down menu action, enable DIAG access method */} - {_("Set DIAG On")} - { /** TRANSLATORS: drop down menu action, disable DIAG access method */} - {_("Set DIAG Off")} + {/** TRANSLATORS: drop down menu action, enable DIAG access method */} + + {_("Set DIAG On")} + + {/** TRANSLATORS: drop down menu action, disable DIAG access method */} + + {_("Set DIAG Off")} + - { /** TRANSLATORS: drop down menu action, format the disk */} - {_("Format")} + {/** TRANSLATORS: drop down menu action, format the disk */} + + {_("Format")} + ); }; const filterDevices = (devices, from, to) => { - const allChannels = devices.map(d => d.hexId); + const allChannels = devices.map((d) => d.hexId); const min = hex(from) || Math.min(...allChannels); const max = hex(to) || Math.max(...allChannels); - return devices.filter(d => d.hexId >= min && d.hexId <= max); + return devices.filter((d) => d.hexId >= min && d.hexId <= max); }; export default function DASDTable({ state, dispatch }) { const [sortingColumn, setSortingColumn] = useState(columns[0]); - const [sortDirection, setSortDirection] = useState('asc'); + const [sortDirection, setSortDirection] = useState("asc"); - const sortColumnIndex = () => columns.findIndex(c => c.id === sortingColumn.id); + const sortColumnIndex = () => columns.findIndex((c) => c.id === sortingColumn.id); const filteredDevices = filterDevices(state.devices, state.minChannel, state.maxChannel); - const selectedDevicesIds = state.selectedDevices.map(d => d.id); + const selectedDevicesIds = state.selectedDevices.map((d) => d.id); // Selecting const selectAll = (isSelecting = true) => { @@ -156,7 +174,7 @@ export default function DASDTable({ state, dispatch }) { // Sorting // See https://github.com/snovakovic/fast-sort const sortBy = sortingColumn.sortBy || sortingColumn.id; - const sortedDevices = sort(filteredDevices)[sortDirection](d => d[sortBy]); + const sortedDevices = sort(filteredDevices)[sortDirection]((d) => d[sortBy]); // FIXME: this can be improved and even extracted to be used with other tables. const getSortParams = (columnIndex) => { @@ -166,7 +184,7 @@ export default function DASDTable({ state, dispatch }) { setSortingColumn(columns[index]); setSortDirection(direction); }, - columnIndex + columnIndex, }; }; @@ -194,15 +212,35 @@ export default function DASDTable({ state, dispatch }) { - )} + + ))} {sortedDevices.map((device, rowIndex) => ( - )} + + ))} ))} @@ -224,7 +262,7 @@ export default function DASDTable({ state, dispatch }) { placeholder={_("Filter by min channel")} onChange={onMinChannelFilterChange} /> - {state.minChannel !== "" && + {state.minChannel !== "" && ( - } + + )} @@ -245,7 +284,7 @@ export default function DASDTable({ state, dispatch }) { placeholder={_("Filter by max channel")} onChange={onMaxChannelFilterChange} /> - {state.maxChannel !== "" && + {state.maxChannel !== "" && ( - } + + )} - + diff --git a/web/src/components/storage/DeviceSelection.jsx b/web/src/components/storage/DeviceSelection.jsx index 12e5b28e24..653ef377b7 100644 --- a/web/src/components/storage/DeviceSelection.jsx +++ b/web/src/components/storage/DeviceSelection.jsx @@ -29,12 +29,13 @@ import { Card, CardBody, Flex, - Form, FormGroup, + Form, + FormGroup, PageSection, Radio, - Stack + Stack, } from "@patternfly/react-core"; -import a11y from '@patternfly/react-styles/css/utilities/Accessibility/accessibility'; +import a11y from "@patternfly/react-styles/css/utilities/Accessibility/accessibility"; import { _ } from "~/i18n"; import { deviceChildren } from "~/components/storage/utils"; @@ -88,8 +89,8 @@ export default function DeviceSelection() { load: true, availableDevices, target: settings.target, - targetDevice: availableDevices.find(d => d.name === settings.targetDevice), - targetPVDevices: availableDevices.filter(d => settings.targetPVDevices?.includes(d.name)), + targetDevice: availableDevices.find((d) => d.name === settings.targetDevice), + targetPVDevices: availableDevices.filter((d) => settings.targetPVDevices?.includes(d.name)), }); }; @@ -114,7 +115,7 @@ export default function DeviceSelection() { const newSettings = { target: state.target, targetDevice: isTargetDisk ? state.targetDevice?.name : "", - targetPVDevices: isTargetNewLvmVg ? state.targetPVDevices.map(d => d.name) : [] + targetPVDevices: isTargetNewLvmVg ? state.targetPVDevices.map((d) => d.name) : [], }; await client.proposal.calculate({ ...settings, ...newSettings }); @@ -133,15 +134,19 @@ export default function DeviceSelection() { // TRANSLATORS: description for using plain partitions for installing the // system, the text in the square brackets [] is displayed in bold, use only // one pair in the translation - const [msgStart1, msgBold1, msgEnd1] = _("The file systems will be allocated \ -by default as [new partitions in the selected device].").split(/[[\]]/); + const [msgStart1, msgBold1, msgEnd1] = _( + "The file systems will be allocated \ +by default as [new partitions in the selected device].", + ).split(/[[\]]/); // TRANSLATORS: description for using logical volumes for installing the // system, the text in the square brackets [] is displayed in bold, use only // one pair in the translation - const [msgStart2, msgBold2, msgEnd2] = _("The file systems will be allocated \ + const [msgStart2, msgBold2, msgEnd2] = _( + "The file systems will be allocated \ by default as [logical volumes of a new LVM Volume Group]. The corresponding \ physical volumes will be created on demand as new partitions at the selected \ -devices.").split(/[[\]]/); +devices.", + ).split(/[[\]]/); return ( <> @@ -224,7 +229,10 @@ devices.").split(/[[\]]/); - + {_("Prepare more devices by configuring advanced")} diff --git a/web/src/components/storage/DeviceSelectorTable.jsx b/web/src/components/storage/DeviceSelectorTable.jsx index 8554e26eb2..bafba38bf8 100644 --- a/web/src/components/storage/DeviceSelectorTable.jsx +++ b/web/src/components/storage/DeviceSelectorTable.jsx @@ -23,7 +23,11 @@ import React from "react"; import { - DeviceName, DeviceDetails, DeviceSize, FilesystemLabel, toStorageDevice + DeviceName, + DeviceDetails, + DeviceSize, + FilesystemLabel, + toStorageDevice, } from "~/components/storage/device-utils"; import { ExpandableSelector } from "~/components/core"; import { Icon } from "~/components/layout"; @@ -73,8 +77,8 @@ const DeviceInfo = ({ item }) => { } else { const technology = device.transport || device.bus; type = technology - // TRANSLATORS: %s is substituted by the type of disk like "iSCSI" or "SATA" - ? sprintf(_("%s disk"), technology) + ? // TRANSLATORS: %s is substituted by the type of disk like "iSCSI" or "SATA" + sprintf(_("%s disk"), technology) : _("Disk"); } } @@ -137,8 +141,7 @@ const DeviceInfo = ({ item }) => { const DeviceExtendedDetails = ({ item }) => { const device = toStorageDevice(item); - if (!device || ["partition", "lvmLv"].includes(device.type)) - return ; + if (!device || ["partition", "lvmLv"].includes(device.type)) return ; // TODO: there is a lot of room for improvement here, but first we would need // device.description (comes from YaST) to be way more granular @@ -158,7 +161,11 @@ const DeviceExtendedDetails = ({ item }) => { return _("No content found"); } - return
{device.description}
; + return ( +
+ {device.description} +
+ ); }; const Systems = () => { @@ -167,7 +174,11 @@ const DeviceExtendedDetails = ({ item }) => { const System = ({ system }) => { const logo = /windows/i.test(system) ? "windows_logo" : "linux_logo"; - return
{system}
; + return ( +
+ {system} +
+ ); }; return device.systems.map((s, i) => ); @@ -185,7 +196,7 @@ const DeviceExtendedDetails = ({ item }) => { const columns = [ { name: _("Device"), value: (item) => }, { name: _("Details"), value: (item) => }, - { name: _("Size"), value: (item) => , classNames: "sizes-column" } + { name: _("Size"), value: (item) => , classNames: "sizes-column" }, ]; /** diff --git a/web/src/components/storage/DevicesFormSelect.jsx b/web/src/components/storage/DevicesFormSelect.jsx index baf73b0098..0ffab68e2c 100644 --- a/web/src/components/storage/DevicesFormSelect.jsx +++ b/web/src/components/storage/DevicesFormSelect.jsx @@ -22,8 +22,8 @@ // @ts-check import React from "react"; -import { FormSelect, FormSelectOption } from '@patternfly/react-core'; -import { deviceSize } from '~/components/storage/utils'; +import { FormSelect, FormSelectOption } from "@patternfly/react-core"; +import { deviceSize } from "~/components/storage/utils"; /** * @typedef {import ("@patternfly/react-core").FormSelectProps} PFFormSelectProps @@ -52,9 +52,9 @@ export default function DevicesFormSelect({ devices, selectedDevice, onChange, . onChange(devices.find(d => d.sid === Number(value)))} + onChange={(_, value) => onChange(devices.find((d) => d.sid === Number(value)))} > - { devices.map(device => ( + {devices.map((device) => ( this.#isUsed(d)) - .map(d => d.sid); + const sids = targetSystem + .concat(targetStaging) + .filter((d) => this.#isUsed(d)) + .map((d) => d.sid); - return compact(uniq(sids).map(sid => this.stagingDevice(sid))); + return compact(uniq(sids).map((sid) => this.stagingDevice(sid))); } /** @@ -164,7 +165,7 @@ export default class DevicesManager { * @returns {StorageDevice[]} */ deletedDevices() { - return this.#deleteActionsDevice().filter(d => !d.isDrive); + return this.#deleteActionsDevice().filter((d) => !d.isDrive); } /** @@ -176,7 +177,7 @@ export default class DevicesManager { * @returns {StorageDevice[]} */ resizedDevices() { - return this.#resizeActionsDevice().filter(d => !d.isDrive); + return this.#resizeActionsDevice().filter((d) => !d.isDrive); } /** @@ -187,8 +188,8 @@ export default class DevicesManager { */ deletedSystems() { const systems = this.#deleteActionsDevice() - .filter(d => !d.partitionTable) - .map(d => d.systems) + .filter((d) => !d.partitionTable) + .map((d) => d.systems) .flat(); return compact(systems); } @@ -201,8 +202,8 @@ export default class DevicesManager { */ resizedSystems() { const systems = this.#resizeActionsDevice() - .filter(d => !d.partitionTable) - .map(d => d.systems) + .filter((d) => !d.partitionTable) + .map((d) => d.systems) .flat(); return compact(systems); } @@ -213,7 +214,7 @@ export default class DevicesManager { * @returns {StorageDevice|undefined} */ #device(sid, source) { - return source.find(d => d.sid === sid); + return source.find((d) => d.sid === sid); } /** @@ -230,24 +231,24 @@ export default class DevicesManager { * @returns {boolean} */ #isUsed(device) { - const sids = uniq(compact(this.actions.map(a => a.device))); + const sids = uniq(compact(this.actions.map((a) => a.device))); const partitions = device.partitionTable?.partitions || []; const lvmLvs = device.logicalVolumes || []; - return sids.includes(device.sid) || - partitions.find(p => this.#isUsed(p)) !== undefined || - lvmLvs.find(l => this.#isUsed(l)) !== undefined; + return ( + sids.includes(device.sid) || + partitions.find((p) => this.#isUsed(p)) !== undefined || + lvmLvs.find((l) => this.#isUsed(l)) !== undefined + ); } /** * @returns {StorageDevice[]} */ #deleteActionsDevice() { - const sids = this.actions - .filter(a => a.delete) - .map(a => a.device); - const devices = sids.map(sid => this.systemDevice(sid)); + const sids = this.actions.filter((a) => a.delete).map((a) => a.device); + const devices = sids.map((sid) => this.systemDevice(sid)); return compact(devices); } @@ -255,10 +256,8 @@ export default class DevicesManager { * @returns {StorageDevice[]} */ #resizeActionsDevice() { - const sids = this.actions - .filter(a => a.resize) - .map(a => a.device); - const devices = sids.map(sid => this.systemDevice(sid)); + const sids = this.actions.filter((a) => a.resize).map((a) => a.device); + const devices = sids.map((sid) => this.systemDevice(sid)); return compact(devices); } } diff --git a/web/src/components/storage/DevicesManager.test.js b/web/src/components/storage/DevicesManager.test.js index cab7d9dad2..4edc74fc89 100644 --- a/web/src/components/storage/DevicesManager.test.js +++ b/web/src/components/storage/DevicesManager.test.js @@ -305,7 +305,7 @@ describe("usedDevices", () => { { sid: 63, isDrive: true, partitionTable: { partitions: [] } }, { sid: 64, isDrive: false, type: "lvmVg", logicalVolumes: [] }, { sid: 65, isDrive: false, type: "lvmVg", logicalVolumes: [] }, - { sid: 66, isDrive: false, type: "lvmVg", logicalVolumes: [{ sid: 68 }] } + { sid: 66, isDrive: false, type: "lvmVg", logicalVolumes: [{ sid: 68 }] }, ]; staging = [ { sid: 60, isDrive: false }, @@ -317,7 +317,7 @@ describe("usedDevices", () => { // Logical volume added { sid: 65, isDrive: false, type: "lvmVg", logicalVolumes: [{ sid: 70 }, { sid: 71 }] }, // Logical volume removed - { sid: 66, isDrive: false, type: "lvmVg", logicalVolumes: [] } + { sid: 66, isDrive: false, type: "lvmVg", logicalVolumes: [] }, ]; }); @@ -348,20 +348,24 @@ describe("usedDevices", () => { // This logical volume was added (belongs to device 65). { device: 70 }, // This logical volume was added (belongs to device 65). - { device: 71 } + { device: 71 }, ]; }); it("does not include removed disk devices or LVM volume groups", () => { const manager = new DevicesManager(system, staging, actions); - const sids = manager.usedDevices().map(d => d.sid) + const sids = manager + .usedDevices() + .map((d) => d.sid) .sort(); expect(sids).not.toContain(61); }); it("includes all disk devices and LVM volume groups affected by the actions", () => { const manager = new DevicesManager(system, staging, actions); - const sids = manager.usedDevices().map(d => d.sid) + const sids = manager + .usedDevices() + .map((d) => d.sid) .sort(); expect(sids).toEqual([62, 63, 65, 66]); }); @@ -370,26 +374,22 @@ describe("usedDevices", () => { describe("resizedDevices", () => { beforeEach(() => { - system = [ - { sid: 60 }, - { sid: 62 }, - { sid: 63 }, - { sid: 64 }, - { sid: 65, isDrive: true } - ]; + system = [{ sid: 60 }, { sid: 62 }, { sid: 63 }, { sid: 64 }, { sid: 65, isDrive: true }]; actions = [ { device: 60, delete: true }, // This device does not exist in system. { device: 61, delete: true }, { device: 62, delete: false, resize: true }, { device: 63, delete: false, resize: true }, - { device: 65, delete: true } + { device: 65, delete: true }, ]; }); it("includes all resized devices", () => { const manager = new DevicesManager(system, staging, actions); - const sids = manager.resizedDevices().map(d => d.sid) + const sids = manager + .resizedDevices() + .map((d) => d.sid) .sort(); expect(sids).toEqual([62, 63]); }); @@ -404,12 +404,12 @@ describe("resizedSystems", () => { sid: 63, systems: ["openSUSE Leap", "openSUSE Tumbleweed"], partitionTable: { - partitions: [{ sid: 65 }, { sid: 66 }] - } + partitions: [{ sid: 65 }, { sid: 66 }], + }, }, { sid: 64 }, { sid: 65, systems: ["openSUSE Leap"] }, - { sid: 66, systems: ["openSUSE Tumbleweed"] } + { sid: 66, systems: ["openSUSE Tumbleweed"] }, ]; actions = [ { device: 60, delete: false, resize: true }, @@ -418,7 +418,7 @@ describe("resizedSystems", () => { { device: 62, delete: false }, { device: 63, delete: false, resize: true }, { device: 65, delete: true, resize: true }, - { device: 66, delete: false, resize: true } + { device: 66, delete: false, resize: true }, ]; }); @@ -434,26 +434,22 @@ describe("resizedSystems", () => { describe("deletedDevices", () => { beforeEach(() => { - system = [ - { sid: 60 }, - { sid: 62 }, - { sid: 63 }, - { sid: 64 }, - { sid: 65, isDrive: true } - ]; + system = [{ sid: 60 }, { sid: 62 }, { sid: 63 }, { sid: 64 }, { sid: 65, isDrive: true }]; actions = [ { device: 60, delete: true }, // This device does not exist in system. { device: 61, delete: true }, { device: 62, delete: false }, { device: 63, delete: true }, - { device: 65, delete: true } + { device: 65, delete: true }, ]; }); it("includes all deleted devices", () => { const manager = new DevicesManager(system, staging, actions); - const sids = manager.deletedDevices().map(d => d.sid) + const sids = manager + .deletedDevices() + .map((d) => d.sid) .sort(); expect(sids).toEqual([60, 63]); }); @@ -468,12 +464,12 @@ describe("deletedSystems", () => { sid: 63, systems: ["openSUSE Leap", "openSUSE Tumbleweed"], partitionTable: { - partitions: [{ sid: 65 }, { sid: 66 }] - } + partitions: [{ sid: 65 }, { sid: 66 }], + }, }, { sid: 64 }, { sid: 65, systems: ["openSUSE Leap"] }, - { sid: 66, systems: ["openSUSE Tumbleweed"] } + { sid: 66, systems: ["openSUSE Tumbleweed"] }, ]; actions = [ { device: 60, delete: true }, @@ -482,7 +478,7 @@ describe("deletedSystems", () => { { device: 62, delete: false }, { device: 63, delete: true }, { device: 65, delete: true }, - { device: 66, delete: true } + { device: 66, delete: true }, ]; }); diff --git a/web/src/components/storage/DevicesTechMenu.jsx b/web/src/components/storage/DevicesTechMenu.jsx index ffdb42aea7..dc0a47a659 100644 --- a/web/src/components/storage/DevicesTechMenu.jsx +++ b/web/src/components/storage/DevicesTechMenu.jsx @@ -23,10 +23,7 @@ import React, { useEffect, useState } from "react"; import { useHref } from "react-router-dom"; -import { - MenuToggle, - Select, SelectList, SelectOption -} from "@patternfly/react-core"; +import { MenuToggle, Select, SelectList, SelectOption } from "@patternfly/react-core"; import { _ } from "~/i18n"; import { useInstallerClient } from "~/context/installer"; @@ -38,11 +35,7 @@ const DASDLink = () => { const href = useHref("/storage/dasd"); return ( - + DASD ); @@ -56,11 +49,7 @@ const ZFCPLink = () => { const href = useHref("/storage/zfcp"); return ( - + {_("zFCP")} ); @@ -74,11 +63,7 @@ const ISCSILink = () => { const href = useHref("/storage/iscsi"); return ( - + {_("iSCSI")} ); @@ -104,7 +89,7 @@ export default function DevicesTechMenu({ label }) { client.zfcp.isSupported().then(setShowZFCPLink); }, [client.dasd, client.zfcp]); - const toggle = toggleRef => ( + const toggle = (toggleRef) => ( setIsOpen(!isOpen)} isExpanded={isOpen}> {label} diff --git a/web/src/components/storage/DevicesTechMenu.test.jsx b/web/src/components/storage/DevicesTechMenu.test.jsx index ec7e7fb34c..6cd7917a1b 100644 --- a/web/src/components/storage/DevicesTechMenu.test.jsx +++ b/web/src/components/storage/DevicesTechMenu.test.jsx @@ -30,13 +30,13 @@ jest.mock("~/client"); const isDASDSupportedFn = jest.fn(); const dasd = { - isSupported: isDASDSupportedFn + isSupported: isDASDSupportedFn, }; const isZFCPSupportedFn = jest.fn(); const zfcp = { - isSupported: isZFCPSupportedFn + isSupported: isZFCPSupportedFn, }; beforeEach(() => { @@ -45,7 +45,7 @@ beforeEach(() => { createClient.mockImplementation(() => { return { - storage: { dasd, zfcp } + storage: { dasd, zfcp }, }; }); }); diff --git a/web/src/components/storage/EncryptionField.jsx b/web/src/components/storage/EncryptionField.jsx index 93a5fab08b..017ea2dc95 100644 --- a/web/src/components/storage/EncryptionField.jsx +++ b/web/src/components/storage/EncryptionField.jsx @@ -36,12 +36,14 @@ import { noop } from "~/utils"; // Field texts at root level to avoid redefinitions every time the component // is rendered. const LABEL = _("Encryption"); -const DESCRIPTION = _("Protection for the information stored at \ -the device, including data, programs, and system files."); +const DESCRIPTION = _( + "Protection for the information stored at \ +the device, including data, programs, and system files.", +); const VALUES = { disabled: _("disabled"), [EncryptionMethods.LUKS2]: _("enabled"), - [EncryptionMethods.TPM]: _("using TPM unlocking") + [EncryptionMethods.TPM]: _("using TPM unlocking"), }; const Value = ({ isLoading, isEnabled, method }) => { @@ -87,7 +89,7 @@ export default function EncryptionField({ // FIXME: should be available methods actually a prop? methods = [], isLoading = false, - onChange = noop + onChange = noop, }) { const validPassword = useCallback(() => password?.length > 0, [password]); const [isEnabled, setIsEnabled] = useState(validPassword()); @@ -117,7 +119,7 @@ export default function EncryptionField({ cardDescriptionProps={{ isFilled: true }} actions={} > - {isDialogOpen && + {isDialogOpen && ( } + /> + )} ); } diff --git a/web/src/components/storage/EncryptionSettingsDialog.jsx b/web/src/components/storage/EncryptionSettingsDialog.jsx index da5740124b..0801c45298 100644 --- a/web/src/components/storage/EncryptionSettingsDialog.jsx +++ b/web/src/components/storage/EncryptionSettingsDialog.jsx @@ -34,15 +34,19 @@ import { EncryptionMethods } from "~/client/storage"; */ const DIALOG_TITLE = _("Encryption"); -const DIALOG_DESCRIPTION = _("Full Disk Encryption (FDE) allows to protect the information stored \ -at the device, including data, programs, and system files."); +const DIALOG_DESCRIPTION = _( + "Full Disk Encryption (FDE) allows to protect the information stored \ +at the device, including data, programs, and system files.", +); // TRANSLATORS: "Trusted Platform Module" is the name of the technology and TPM its abbreviation const TPM_LABEL = _("Use the Trusted Platform Module (TPM) to decrypt automatically on each boot"); // TRANSLATORS: The word 'directly' is key here. For example, booting to the installer media and then choosing // 'Boot from Hard Disk' from there will not work. Keep it sort (this is a hint in a form) but keep it clear. -const TPM_EXPLANATION = _("The password will not be needed to boot and access the data if the \ +const TPM_EXPLANATION = _( + "The password will not be needed to boot and access the data if the \ TPM can verify the integrity of the system. TPM sealing requires the new system to be booted \ -directly on its first run."); +directly on its first run.", +); /** * Renders a dialog that allows the user change encryption settings @@ -66,7 +70,7 @@ export default function EncryptionSettingsDialog({ isOpen = false, isLoading = false, onCancel, - onAccept + onAccept, }) { const [isEnabled, setIsEnabled] = useState(passwordProp?.length > 0); const [password, setPassword] = useState(passwordProp); @@ -77,11 +81,15 @@ export default function EncryptionSettingsDialog({ const formId = "encryptionSettingsForm"; // reset the settings only after loading is finished - if (isLoading && !wasLoading) { setWasLoading(true) } + if (isLoading && !wasLoading) { + setWasLoading(true); + } if (!isLoading && wasLoading) { setWasLoading(false); // refresh the state when the real values are loaded - if (method !== methodProp) { setMethod(methodProp) } + if (method !== methodProp) { + setMethod(methodProp); + } if (password !== passwordProp) { setPassword(passwordProp); setIsEnabled(passwordProp?.length > 0); @@ -93,7 +101,8 @@ export default function EncryptionSettingsDialog({ }, [isEnabled, password, passwordsMatch]); const changePassword = (_, v) => setPassword(v); - const changeMethod = (_, useTPM) => setMethod(useTPM ? EncryptionMethods.TPM : EncryptionMethods.LUKS2); + const changeMethod = (_, useTPM) => + setMethod(useTPM ? EncryptionMethods.TPM : EncryptionMethods.LUKS2); const submitSettings = (e) => { e.preventDefault(); @@ -108,7 +117,12 @@ export default function EncryptionSettingsDialog({ const tpmAvailable = methods.includes(EncryptionMethods.TPM); return ( - + - {tpmAvailable && + {tpmAvailable && ( } + /> + )} diff --git a/web/src/components/storage/EncryptionSettingsDialog.test.jsx b/web/src/components/storage/EncryptionSettingsDialog.test.jsx index 512dbe042c..bfaf0814a9 100644 --- a/web/src/components/storage/EncryptionSettingsDialog.test.jsx +++ b/web/src/components/storage/EncryptionSettingsDialog.test.jsx @@ -40,7 +40,7 @@ describe.skip("EncryptionSettingsDialog", () => { methods: Object.values(EncryptionMethods), isOpen: true, onCancel: onCancelFn, - onAccept: onAcceptFn + onAccept: onAcceptFn, }; }); @@ -73,9 +73,7 @@ describe.skip("EncryptionSettingsDialog", () => { await user.type(confirmationInput, "2345"); await user.click(acceptButton); - expect(props.onAccept).toHaveBeenCalledWith( - expect.objectContaining({ password: "2345" }) - ); + expect(props.onAccept).toHaveBeenCalledWith(expect.objectContaining({ password: "2345" })); }); }); @@ -94,9 +92,10 @@ describe.skip("EncryptionSettingsDialog", () => { await user.click(tpmCheckbox); await user.click(acceptButton); - expect(props.onAccept).toHaveBeenCalledWith( - { password: "9876", method: EncryptionMethods.TPM } - ); + expect(props.onAccept).toHaveBeenCalledWith({ + password: "9876", + method: EncryptionMethods.TPM, + }); }); it("allows unsetting the encryption", async () => { @@ -125,7 +124,7 @@ describe.skip("EncryptionSettingsDialog", () => { expect(tpmCheckbox).not.toBeChecked(); await user.click(acceptButton); expect(props.onAccept).toHaveBeenCalledWith( - expect.not.objectContaining({ method: EncryptionMethods.TPM }) + expect.not.objectContaining({ method: EncryptionMethods.TPM }), ); }); }); diff --git a/web/src/components/storage/InstallationDeviceField.jsx b/web/src/components/storage/InstallationDeviceField.jsx index 002decc3cd..3a817fb9a5 100644 --- a/web/src/components/storage/InstallationDeviceField.jsx +++ b/web/src/components/storage/InstallationDeviceField.jsx @@ -24,7 +24,7 @@ import React from "react"; import { Skeleton } from "@patternfly/react-core"; import { ButtonLink, CardField } from "~/components/core"; -import { deviceLabel } from '~/components/storage/utils'; +import { deviceLabel } from "~/components/storage/utils"; import { _ } from "~/i18n"; import { sprintf } from "sprintf-js"; @@ -56,7 +56,10 @@ const targetValue = (target, targetDevice, targetPVDevices) => { if (targetPVDevices.length === 1) { // TRANSLATORS: %s is the disk used for the LVM physical volumes (eg. "/dev/sda, 80 GiB) - return sprintf(_("File systems created at a new LVM volume group on %s"), deviceLabel(targetPVDevices[0])); + return sprintf( + _("File systems created at a new LVM volume group on %s"), + deviceLabel(targetPVDevices[0]), + ); } } @@ -90,19 +93,21 @@ export default function InstallationDeviceField({ isLoading, }) { let value; - if (isLoading || !target) - value = ; - else - value = targetValue(target, targetDevice, targetPVDevices); + if (isLoading || !target) value = ; + else value = targetValue(target, targetDevice, targetPVDevices); return ( - : {_("Change")} + isLoading ? ( + + ) : ( + + {_("Change")} + + ) } > {value} diff --git a/web/src/components/storage/InstallationDeviceField.test.jsx b/web/src/components/storage/InstallationDeviceField.test.jsx index a402188b3b..b2ab5cd248 100644 --- a/web/src/components/storage/InstallationDeviceField.test.jsx +++ b/web/src/components/storage/InstallationDeviceField.test.jsx @@ -31,7 +31,7 @@ jest.mock("@patternfly/react-core", () => { return { ...original, - Skeleton: () =>
PF-Skeleton
+ Skeleton: () =>
PF-Skeleton
, }; }); @@ -58,7 +58,7 @@ const sda = { name: "/dev/sda", size: 1024, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }; @@ -81,9 +81,9 @@ const sdb = { name: "/dev/sdb", size: 2048, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }; /** @type {InstallationDeviceFieldProps} */ @@ -96,7 +96,7 @@ beforeEach(() => { targetPVDevices: [], devices: [sda, sdb], isLoading: false, - onChange: jest.fn() + onChange: jest.fn(), }; }); @@ -199,7 +199,7 @@ it.skip("allows changing the selected device", async () => { expect(props.onChange).toHaveBeenCalledWith({ target: "DISK", targetDevice: sdb, - targetPVDevices: [] + targetPVDevices: [], }); }); diff --git a/web/src/components/storage/PartitionsField.jsx b/web/src/components/storage/PartitionsField.jsx index 96394f77f7..88faf9d78c 100644 --- a/web/src/components/storage/PartitionsField.jsx +++ b/web/src/components/storage/PartitionsField.jsx @@ -24,28 +24,36 @@ import React, { useState } from "react"; import { Button, - CardBody, CardExpandableContent, + CardBody, + CardExpandableContent, Divider, - Dropdown, DropdownList, DropdownItem, + Dropdown, + DropdownList, + DropdownItem, Flex, - List, ListItem, + List, + ListItem, MenuToggle, Skeleton, Split, - Stack -} from '@patternfly/react-core'; -import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; -import { CardField, RowActions, Tip } from '~/components/core'; + Stack, +} from "@patternfly/react-core"; +import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; +import { CardField, RowActions, Tip } from "~/components/core"; import { noop } from "~/utils"; import { _ } from "~/i18n"; import { sprintf } from "sprintf-js"; import { - deviceSize, hasSnapshots, isTransactionalRoot, isTransactionalSystem, reuseDevice -} from '~/components/storage/utils'; + deviceSize, + hasSnapshots, + isTransactionalRoot, + isTransactionalSystem, + reuseDevice, +} from "~/components/storage/utils"; import BootConfigField from "~/components/storage/BootConfigField"; import SnapshotsField from "~/components/storage/SnapshotsField"; -import VolumeDialog from '~/components/storage/VolumeDialog'; -import VolumeLocationDialog from '~/components/storage/VolumeLocationDialog'; +import VolumeDialog from "~/components/storage/VolumeDialog"; +import VolumeLocationDialog from "~/components/storage/VolumeLocationDialog"; /** * @typedef {import ("~/client/storage").ProposalTarget} ProposalTarget @@ -62,11 +70,14 @@ import VolumeLocationDialog from '~/components/storage/VolumeLocationDialog'; */ const SizeText = ({ volume }) => { let targetSize; - if (reuseDevice(volume)) - targetSize = volume.targetDevice.size; + if (reuseDevice(volume)) targetSize = volume.targetDevice.size; const minSize = deviceSize(targetSize || volume.minSize); - const maxSize = targetSize ? deviceSize(targetSize) : volume.maxSize ? deviceSize(volume.maxSize) : undefined; + const maxSize = targetSize + ? deviceSize(targetSize) + : volume.maxSize + ? deviceSize(volume.maxSize) + : undefined; if (minSize && maxSize && minSize !== maxSize) return `${minSize} - ${maxSize}`; // TRANSLATORS: minimum device size, %s is replaced by size string, e.g. "17.5 GiB" @@ -86,24 +97,24 @@ const BasicVolumeText = ({ volume, target }) => { const snapshots = hasSnapshots(volume); const transactional = isTransactionalRoot(volume); const size = SizeText({ volume }); - const lvm = (target === "NEW_LVM_VG"); + const lvm = target === "NEW_LVM_VG"; // When target is "filesystem" or "device" this is irrelevant since the type of device // is not mentioned const lv = volume.target === "NEW_VG" || (volume.target === "DEFAULT" && lvm); if (transactional) - return (lv) - // TRANSLATORS: "/" is in an LVM logical volume. %s replaced by size string, e.g. "17.5 GiB" - ? sprintf(_("Transactional Btrfs root volume (%s)"), size) - // TRANSLATORS: %s replaced by size string, e.g. "17.5 GiB" - : sprintf(_("Transactional Btrfs root partition (%s)"), size); + return lv + ? // TRANSLATORS: "/" is in an LVM logical volume. %s replaced by size string, e.g. "17.5 GiB" + sprintf(_("Transactional Btrfs root volume (%s)"), size) + : // TRANSLATORS: %s replaced by size string, e.g. "17.5 GiB" + sprintf(_("Transactional Btrfs root partition (%s)"), size); if (snapshots) - return (lv) - // TRANSLATORS: "/" is in an LVM logical volume. %s replaced by size string, e.g. "17.5 GiB" - ? sprintf(_("Btrfs root volume with snapshots (%s)"), size) - // TRANSLATORS: %s replaced by size string, e.g. "17.5 GiB" - : sprintf(_("Btrfs root partition with snapshots (%s)"), size); + return lv + ? // TRANSLATORS: "/" is in an LVM logical volume. %s replaced by size string, e.g. "17.5 GiB" + sprintf(_("Btrfs root volume with snapshots (%s)"), size) + : // TRANSLATORS: %s replaced by size string, e.g. "17.5 GiB" + sprintf(_("Btrfs root partition with snapshots (%s)"), size); const volTarget = volume.target; const mount = volume.mountPath; @@ -120,11 +131,11 @@ const BasicVolumeText = ({ volume, target }) => { // %1$s is replaced by the device name, and %2$s by the size return sprintf(_("Swap at %1$s (%2$s)"), device, size); - return (lv) - // TRANSLATORS: Swap is in an LVM logical volume. %s replaced by size string, e.g. "8 GiB" - ? sprintf(_("Swap volume (%s)"), size) - // TRANSLATORS: %s replaced by size string, e.g. "8 GiB" - : sprintf(_("Swap partition (%s)"), size); + return lv + ? // TRANSLATORS: Swap is in an LVM logical volume. %s replaced by size string, e.g. "8 GiB" + sprintf(_("Swap volume (%s)"), size) + : // TRANSLATORS: %s replaced by size string, e.g. "8 GiB" + sprintf(_("Swap partition (%s)"), size); } const type = volume.fsType; @@ -135,14 +146,14 @@ const BasicVolumeText = ({ volume, target }) => { // %1$s is replaced by the filesystem type, %2$s by the device name, and %3$s by the size return sprintf(_("%1$s root at %2$s (%3$s)"), type, device, size); - return (lv) - // TRANSLATORS: "/" is in an LVM logical volume. - // Results in something like "Btrfs root volume (at least 20 GiB)" since - // $1$s is replaced by filesystem type and %2$s by size description - ? sprintf(_("%1$s root volume (%2$s)"), type, size) - // TRANSLATORS: Results in something like "Btrfs root partition (at least 20 GiB)" since - // $1$s is replaced by filesystem type and %2$s by size description - : sprintf(_("%1$s root partition (%2$s)"), type, size); + return lv + ? // TRANSLATORS: "/" is in an LVM logical volume. + // Results in something like "Btrfs root volume (at least 20 GiB)" since + // $1$s is replaced by filesystem type and %2$s by size description + sprintf(_("%1$s root volume (%2$s)"), type, size) + : // TRANSLATORS: Results in something like "Btrfs root partition (at least 20 GiB)" since + // $1$s is replaced by filesystem type and %2$s by size description + sprintf(_("%1$s root partition (%2$s)"), type, size); } if (volTarget === "DEVICE") @@ -150,14 +161,14 @@ const BasicVolumeText = ({ volume, target }) => { // %1$s is replaced by filesystem type, %2$s by mount point, %3$s by device name and %4$s by size return sprintf(_("%1$s %2$s at %3$s (%4$s)"), type, mount, device, size); - return (lv) - // TRANSLATORS: The filesystem is in an LVM logical volume. - // Results in something like "Ext4 /home volume (at least 10 GiB)" since - // %1$s is replaced by the filesystem type, %2$s by the mount point and %3$s by the size description - ? sprintf(_("%1$s %2$s volume (%3$s)"), type, mount, size) - // TRANSLATORS: This results in something like "Ext4 /home partition (at least 10 GiB)" since - // %1$s is replaced by the filesystem type, %2$s by the mount point and %3$s by the size description - : sprintf(_("%1$s %2$s partition (%3$s)"), type, mount, size); + return lv + ? // TRANSLATORS: The filesystem is in an LVM logical volume. + // Results in something like "Ext4 /home volume (at least 10 GiB)" since + // %1$s is replaced by the filesystem type, %2$s by the mount point and %3$s by the size description + sprintf(_("%1$s %2$s volume (%3$s)"), type, mount, size) + : // TRANSLATORS: This results in something like "Ext4 /home partition (at least 10 GiB)" since + // %1$s is replaced by the filesystem type, %2$s by the mount point and %3$s by the size description + sprintf(_("%1$s %2$s partition (%3$s)"), type, mount, size); }; /** @@ -168,11 +179,9 @@ const BasicVolumeText = ({ volume, target }) => { * @param {StorageDevice} props.device */ const BootLabelText = ({ configure, device }) => { - if (!configure) - return _("Do not configure partitions for booting"); + if (!configure) return _("Do not configure partitions for booting"); - if (!device) - return _("Boot partitions at installation disk"); + if (!device) return _("Boot partitions at installation disk"); // TRANSLATORS: %s is the disk used to configure the boot-related partitions (eg. "/dev/sda, 80 GiB) return sprintf(_("Boot partitions at %s"), device.name); @@ -199,17 +208,22 @@ const AutoCalculatedHint = ({ volume }) => { {/* TRANSLATORS: header for a list of items referring to size limits for file systems */} {_("These limits are affected by:")} - {snapshotsAffectSizes && + {snapshotsAffectSizes && ( // TRANSLATORS: list item, this affects the computed partition size limits - {_("The configuration of snapshots")}} - {sizeRelevantVolumes.length > 0 && + {_("The configuration of snapshots")} + )} + {sizeRelevantVolumes.length > 0 && ( // TRANSLATORS: list item, this affects the computed partition size limits // %s is replaced by a list of the volumes (like "/home, /boot") - {sprintf(_("Presence of other volumes (%s)"), sizeRelevantVolumes.join(", "))}} - {adjustByRam && + + {sprintf(_("Presence of other volumes (%s)"), sizeRelevantVolumes.join(", "))} + + )} + {adjustByRam && ( // TRANSLATORS: list item, describes a factor that affects the computed size of a // file system; eg. adjusting the size of the swap - {_("The amount of RAM in the system")}} + {_("The amount of RAM in the system")} + )} ); @@ -224,7 +238,14 @@ const AutoCalculatedHint = ({ volume }) => { */ const VolumeLabel = ({ volume, target }) => { return ( - + {BasicVolumeText({ volume, target })} ); @@ -239,7 +260,14 @@ const VolumeLabel = ({ volume, target }) => { */ const BootLabel = ({ bootDevice, configureBoot }) => { return ( - + {BootLabelText({ configure: configureBoot, device: bootDevice })} ); @@ -249,10 +277,10 @@ const BootLabel = ({ bootDevice, configureBoot }) => { // components to a new file. /** - * @component - * @param {object} props - * @param {Volume} props.volume - */ + * @component + * @param {object} props + * @param {Volume} props.volume + */ const VolumeSizeLimits = ({ volume }) => { const isAuto = volume.autoSize; @@ -260,16 +288,18 @@ const VolumeSizeLimits = ({ volume }) => { {SizeText({ volume })} {/* TRANSLATORS: device flag, the partition size is automatically computed */} - {isAuto && !reuseDevice(volume) && {_("auto")}} + {isAuto && !reuseDevice(volume) && ( + {_("auto")} + )} ); }; /** - * @component - * @param {object} props - * @param {Volume} props.volume - */ + * @component + * @param {object} props + * @param {Volume} props.volume + */ const VolumeDetails = ({ volume }) => { const snapshots = hasSnapshots(volume); const transactional = isTransactionalRoot(volume); @@ -277,20 +307,18 @@ const VolumeDetails = ({ volume }) => { if (volume.target === "FILESYSTEM") // TRANSLATORS: %s will be replaced by a file-system type like "Btrfs" or "Ext4" return sprintf(_("Reused %s"), volume.targetDevice?.filesystem?.type || ""); - if (transactional) - return _("Transactional Btrfs"); - if (snapshots) - return _("Btrfs with snapshots"); + if (transactional) return _("Transactional Btrfs"); + if (snapshots) return _("Btrfs with snapshots"); return volume.fsType; }; /** - * @component - * @param {object} props - * @param {Volume} props.volume - * @param {ProposalTarget} props.target - */ + * @component + * @param {object} props + * @param {Volume} props.volume + * @param {ProposalTarget} props.target + */ const VolumeLocation = ({ volume, target }) => { if (volume.target === "NEW_PARTITION") // TRANSLATORS: %s will be replaced by a disk name (eg. "/dev/sda") @@ -300,27 +328,26 @@ const VolumeLocation = ({ volume, target }) => { return sprintf(_("Separate LVM at %s"), volume.targetDevice?.name || ""); if (volume.target === "DEVICE" || volume.target === "FILESYSTEM") return volume.targetDevice?.name || ""; - if (target === "NEW_LVM_VG") - return _("Logical volume at system LVM"); + if (target === "NEW_LVM_VG") return _("Logical volume at system LVM"); return _("Partition at installation disk"); }; /** - * @component - * @param {object} props - * @param {Volume} props.volume - * @param {() => void} props.onEdit - * @param {() => void} props.onResetLocation - * @param {() => void} props.onLocation - * @param {() => void} props.onDelete - */ + * @component + * @param {object} props + * @param {Volume} props.volume + * @param {() => void} props.onEdit + * @param {() => void} props.onResetLocation + * @param {() => void} props.onLocation + * @param {() => void} props.onDelete + */ const VolumeActions = ({ volume, onEdit, onResetLocation, onLocation, onDelete }) => { const actions = [ { title: _("Edit"), onClick: onEdit }, volume.target !== "DEFAULT" && { title: _("Reset location"), onClick: onResetLocation }, { title: _("Change location"), onClick: onLocation }, - !volume.outline.required && { title: _("Delete"), onClick: onDelete, isDanger: true } + !volume.outline.required && { title: _("Delete"), onClick: onDelete, isDanger: true }, ]; return ; @@ -352,7 +379,7 @@ const VolumeRow = ({ targetDevices, isLoading, onEdit = noop, - onDelete = noop + onDelete = noop, }) => { /** @type {[string, (dialog: string) => void]} */ const [dialog, setDialog] = useState(); @@ -378,7 +405,9 @@ const VolumeRow = ({ if (isLoading) { return (
- + ); } @@ -387,9 +416,15 @@ const VolumeRow = ({ <> - - - + + + - {isEditDialogOpen && + {isEditDialogOpen && ( } - {isLocationDialogOpen && + /> + )} + {isLocationDialogOpen && ( } + /> + )} ); }; @@ -443,7 +480,7 @@ const VolumesTable = ({ target, targetDevices, isLoading, - onVolumesChange + onVolumesChange, }) => { const columns = { mountPath: _("Mount point"), @@ -451,12 +488,12 @@ const VolumesTable = ({ size: _("Size"), // TRANSLATORS: where (and how) the file-system is going to be created location: _("Location"), - actions: _("Actions") + actions: _("Actions"), }; /** @type {(volume: Volume) => void} */ const editVolume = (volume) => { - const index = volumes.findIndex(v => v.mountPath === volume.mountPath); + const index = volumes.findIndex((v) => v.mountPath === volume.mountPath); const newVolumes = [...volumes]; newVolumes[index] = volume; onVolumesChange(newVolumes); @@ -464,7 +501,7 @@ const VolumesTable = ({ /** @type {(volume: Volume) => void} */ const deleteVolume = (volume) => { - const newVolumes = volumes.filter(v => v.mountPath !== volume.mountPath); + const newVolumes = volumes.filter((v) => v.mountPath !== volume.mountPath); onVolumesChange(newVolumes); }; @@ -502,9 +539,7 @@ const VolumesTable = ({ - - {renderVolumes()} - + {renderVolumes()}
selectAll(isSelecting), isSelected: filteredDevices.length === state.selectedDevices.length }} /> - {columns.map((column, index) => {column.label} selectAll(isSelecting), + isSelected: filteredDevices.length === state.selectedDevices.length, + }} + /> + {columns.map((column, index) => ( + + {column.label} +
selectDevice(device, isSelecting), isSelected: selectedDevicesIds.includes(device.id), isDisabled: false }} /> - {columns.map(column => {columnData(device, column)} selectDevice(device, isSelecting), + isSelected: selectedDevicesIds.includes(device.id), + isDisabled: false, + }} + /> + {columns.map((column) => ( + + {columnData(device, column)} +
+ +
{volume.mountPath} + + + + + +
); }; @@ -532,7 +567,9 @@ const Basic = ({ volumes, configureBoot, bootDevice, target, isLoading }) => { return ( - {volumes.map((v, i) => )} + {volumes.map((v, i) => ( + + ))} ); @@ -563,7 +600,9 @@ const AddVolumeButton = ({ options, onClick }) => { // Shows a button if the only option is to add an arbitrary volume. if (options.length === 1 && options[0] === "") { return ( - + ); } @@ -574,7 +613,7 @@ const AddVolumeButton = ({ options, onClick }) => { isOpen={isOpen} onSelect={onSelect} onOpenChange={setIsOpen} - toggle={toggleRef => ( + toggle={(toggleRef) => ( { return ( - {_("Other")} + + {_("Other")} + ); } else { return ( - {option} + + {option} + ); } })} @@ -637,7 +680,7 @@ const Advanced = ({ defaultBootDevice, onVolumesChange, onBootChange, - isLoading + isLoading, }) => { const [isVolumeDialogOpen, setIsVolumeDialogOpen] = useState(false); /** @type {[Volume|undefined, (volume: Volume) => void]} */ @@ -651,7 +694,7 @@ const Advanced = ({ const onAcceptVolumeDialog = (volume) => { closeVolumeDialog(); - const index = volumes.findIndex(v => v.mountPath === volume.mountPath); + const index = volumes.findIndex((v) => v.mountPath === volume.mountPath); if (index !== -1) { const newVolumes = [...volumes]; @@ -666,7 +709,7 @@ const Advanced = ({ /** @type {(mountPath: string) => void} */ const addVolume = (mountPath) => { - const template = templates.find(t => t.mountPath === mountPath); + const template = templates.find((t) => t.mountPath === mountPath); setTemplate(template); openVolumeDialog(); }; @@ -676,13 +719,13 @@ const Advanced = ({ * @type {() => string[]} */ const mountPathOptions = () => { - const mountPaths = volumes.map(v => v.mountPath); + const mountPaths = volumes.map((v) => v.mountPath); const isTransactional = isTransactionalSystem(templates); return templates - .map(t => t.mountPath) - .filter(p => !mountPaths.includes(p)) - .filter(p => !isTransactional || p.length); + .map((t) => t.mountPath) + .filter((p) => !mountPaths.includes(p)) + .filter((p) => !isTransactional || p.length); }; /** @@ -691,14 +734,14 @@ const Advanced = ({ */ const showAddVolume = () => { const hasOptionalVolumes = () => { - return templates.find(t => t.mountPath.length && !t.outline.required) !== undefined; + return templates.find((t) => t.mountPath.length && !t.outline.required) !== undefined; }; return !isTransactionalSystem(templates) || hasOptionalVolumes(); }; /** @type {Volume} */ - const rootVolume = volumes.find(v => v.mountPath === "/"); + const rootVolume = volumes.find((v) => v.mountPath === "/"); /** @type {(config: SnapshotsConfig) => void} */ const changeBtrfsSnapshots = ({ active }) => { @@ -716,7 +759,9 @@ const Advanced = ({ return ( - {showSnapshotsField && } + {showSnapshotsField && ( + + )} {showAddVolume() && } - + - {isVolumeDialogOpen && + {isVolumeDialogOpen && ( } + /> + )} setIsExpanded(!isExpanded); @@ -799,19 +847,21 @@ export default function PartitionsField({ return ( - {!isExpanded && + {!isExpanded && ( - } + + )} { return { ...original, - Skeleton: () =>
PFSkeleton
+ Skeleton: () =>
PFSkeleton
, }; }); @@ -59,8 +59,8 @@ const rootVolume = { snapshotsAffectSizes: true, sizeRelevantVolumes: [], adjustByRam: false, - productDefined: true - } + productDefined: true, + }, }; /** @type {Volume} */ @@ -81,8 +81,8 @@ const swapVolume = { snapshotsAffectSizes: false, sizeRelevantVolumes: [], adjustByRam: false, - productDefined: true - } + productDefined: true, + }, }; /** @type {Volume} */ @@ -102,8 +102,8 @@ const homeVolume = { snapshotsAffectSizes: false, sizeRelevantVolumes: [], adjustByRam: false, - productDefined: true - } + productDefined: true, + }, }; /** @type {Volume} */ @@ -124,8 +124,8 @@ const arbitraryVolume = { snapshotsAffectSizes: false, adjustByRam: false, sizeRelevantVolumes: [], - productDefined: false - } + productDefined: false, + }, }; /** @type {StorageDevice} */ @@ -138,7 +138,7 @@ const sda = { vendor: "Micron", model: "Micron 1100 SATA", transport: "usb", - size: 1024 + size: 1024, }; /** @type {StorageDevice} */ @@ -151,8 +151,8 @@ const sda1 = { size: 256, filesystem: { sid: 169, - type: "Swap" - } + type: "Swap", + }, }; /** @type {StorageDevice} */ @@ -165,8 +165,8 @@ const sda2 = { size: 512, filesystem: { sid: 179, - type: "Ext4" - } + type: "Ext4", + }, }; /** @type {PartitionsFieldProps} */ @@ -191,7 +191,7 @@ beforeEach(() => { bootDevice: undefined, defaultBootDevice: undefined, onVolumesChange: jest.fn(), - onBootChange: jest.fn() + onBootChange: jest.fn(), }; }); @@ -304,7 +304,9 @@ describe.skip("if there are volumes", () => { expect(within(body).queryAllByRole("row").length).toEqual(3); within(body).getByRole("row", { name: "/ Btrfs 1 KiB - 2 KiB Partition at installation disk" }); - within(body).getByRole("row", { name: "/home XFS at least 1 KiB Partition at installation disk" }); + within(body).getByRole("row", { + name: "/home XFS at least 1 KiB Partition at installation disk", + }); within(body).getByRole("row", { name: "swap Swap 1 KiB Partition at installation disk" }); }); @@ -312,7 +314,9 @@ describe.skip("if there are volumes", () => { const { user } = await expandField(); const [, body] = await screen.findAllByRole("rowgroup"); - const row = within(body).getByRole("row", { name: "/home XFS at least 1 KiB Partition at installation disk" }); + const row = within(body).getByRole("row", { + name: "/home XFS at least 1 KiB Partition at installation disk", + }); const actions = within(row).getByRole("button", { name: "Actions" }); await user.click(actions); const deleteAction = within(row).queryByRole("menuitem", { name: "Delete" }); @@ -325,7 +329,9 @@ describe.skip("if there are volumes", () => { const { user } = await expandField(); const [, body] = await screen.findAllByRole("rowgroup"); - const row = within(body).getByRole("row", { name: "/home XFS at least 1 KiB Partition at installation disk" }); + const row = within(body).getByRole("row", { + name: "/home XFS at least 1 KiB Partition at installation disk", + }); const actions = within(row).getByRole("button", { name: "Actions" }); await user.click(actions); const editAction = within(row).queryByRole("menuitem", { name: "Edit" }); @@ -339,7 +345,9 @@ describe.skip("if there are volumes", () => { const { user } = await expandField(); const [, body] = await screen.findAllByRole("rowgroup"); - const row = within(body).getByRole("row", { name: "/home XFS at least 1 KiB Partition at installation disk" }); + const row = within(body).getByRole("row", { + name: "/home XFS at least 1 KiB Partition at installation disk", + }); const actions = within(row).getByRole("button", { name: "Actions" }); await user.click(actions); const locationAction = within(row).queryByRole("menuitem", { name: "Change location" }); @@ -354,7 +362,9 @@ describe.skip("if there are volumes", () => { const { user } = await expandField(); const [, body] = await screen.findAllByRole("rowgroup"); - const row = within(body).getByRole("row", { name: "/home XFS at least 1 KiB Partition at installation disk" }); + const row = within(body).getByRole("row", { + name: "/home XFS at least 1 KiB Partition at installation disk", + }); const actions = within(row).getByRole("button", { name: "Actions" }); await user.click(actions); expect(within(row).queryByRole("menuitem", { name: "Reset location" })).toBeNull(); @@ -369,15 +379,21 @@ describe.skip("if there are volumes", () => { const { user } = await expandField(); const [, body] = await screen.findAllByRole("rowgroup"); - const row = within(body).getByRole("row", { name: "/home XFS at least 1 KiB Partition at /dev/sda" }); + const row = within(body).getByRole("row", { + name: "/home XFS at least 1 KiB Partition at /dev/sda", + }); const actions = within(row).getByRole("button", { name: "Actions" }); await user.click(actions); const resetLocationAction = within(row).queryByRole("menuitem", { name: "Reset location" }); await user.click(resetLocationAction); expect(props.onVolumesChange).toHaveBeenCalledWith( expect.arrayContaining([ - expect.objectContaining({ mountPath: "/home", target: "DEFAULT", targetDevice: undefined }) - ]) + expect.objectContaining({ + mountPath: "/home", + target: "DEFAULT", + targetDevice: undefined, + }), + ]), ); // NOTE: sadly we cannot perform the below check because the component is @@ -397,7 +413,9 @@ describe.skip("if there are volumes", () => { const [, volumes] = await screen.findAllByRole("rowgroup"); - within(volumes).getByRole("row", { name: "/ Transactional Btrfs 1 KiB - 2 KiB Partition at installation disk" }); + within(volumes).getByRole("row", { + name: "/ Transactional Btrfs 1 KiB - 2 KiB Partition at installation disk", + }); }); }); @@ -411,7 +429,9 @@ describe.skip("if there are volumes", () => { const [, volumes] = await screen.findAllByRole("rowgroup"); - within(volumes).getByRole("row", { name: "/ Btrfs with snapshots 1 KiB - 2 KiB Partition at installation disk" }); + within(volumes).getByRole("row", { + name: "/ Btrfs with snapshots 1 KiB - 2 KiB Partition at installation disk", + }); }); }); @@ -420,7 +440,7 @@ describe.skip("if there are volumes", () => { props.volumes = [ rootVolume, { ...swapVolume, target: "NEW_PARTITION", targetDevice: sda }, - { ...homeVolume, target: "NEW_VG", targetDevice: sda } + { ...homeVolume, target: "NEW_VG", targetDevice: sda }, ]; }); @@ -430,7 +450,9 @@ describe.skip("if there are volumes", () => { const [, volumes] = await screen.findAllByRole("rowgroup"); within(volumes).getByRole("row", { name: "swap Swap 1 KiB Partition at /dev/sda" }); - within(volumes).getByRole("row", { name: "/home XFS at least 1 KiB Separate LVM at /dev/sda" }); + within(volumes).getByRole("row", { + name: "/home XFS at least 1 KiB Separate LVM at /dev/sda", + }); }); }); @@ -439,7 +461,7 @@ describe.skip("if there are volumes", () => { props.volumes = [ rootVolume, { ...swapVolume, target: "FILESYSTEM", targetDevice: sda1 }, - { ...homeVolume, target: "DEVICE", targetDevice: sda2 } + { ...homeVolume, target: "DEVICE", targetDevice: sda2 }, ]; }); diff --git a/web/src/components/storage/ProposalActionsDialog.jsx b/web/src/components/storage/ProposalActionsDialog.jsx index 89e19d7a8e..e3874dc578 100644 --- a/web/src/components/storage/ProposalActionsDialog.jsx +++ b/web/src/components/storage/ProposalActionsDialog.jsx @@ -20,7 +20,7 @@ */ import React, { useState } from "react"; -import { List, ListItem, ExpandableSection, } from "@patternfly/react-core"; +import { List, ListItem, ExpandableSection } from "@patternfly/react-core"; import { _, n_ } from "~/i18n"; import { sprintf } from "sprintf-js"; import { partition } from "~/utils"; @@ -58,17 +58,23 @@ export default function ProposalActionsDialog({ actions = [] }) { if (actions.length === 0) return null; - const [generalActions, subvolActions] = partition(actions, a => !a.subvol); + const [generalActions, subvolActions] = partition(actions, (a) => !a.subvol); const toggleText = isExpanded - // TRANSLATORS: show/hide toggle action, this is a clickable link - ? sprintf(n_("Hide %d subvolume action", "Hide %d subvolume actions", subvolActions.length), subvolActions.length) - // TRANSLATORS: show/hide toggle action, this is a clickable link - : sprintf(n_("Show %d subvolume action", "Show %d subvolume actions", subvolActions.length), subvolActions.length); + ? // TRANSLATORS: show/hide toggle action, this is a clickable link + sprintf( + n_("Hide %d subvolume action", "Hide %d subvolume actions", subvolActions.length), + subvolActions.length, + ) + : // TRANSLATORS: show/hide toggle action, this is a clickable link + sprintf( + n_("Show %d subvolume action", "Show %d subvolume actions", subvolActions.length), + subvolActions.length, + ); return ( <> - {subvolActions.length > 0 && + {subvolActions.length > 0 && ( - } + + )} ); } diff --git a/web/src/components/storage/ProposalActionsDialog.test.jsx b/web/src/components/storage/ProposalActionsDialog.test.jsx index 8d97f6b47b..b12544d70a 100644 --- a/web/src/components/storage/ProposalActionsDialog.test.jsx +++ b/web/src/components/storage/ProposalActionsDialog.test.jsx @@ -25,27 +25,59 @@ import { plainRender } from "~/test-utils"; import { ProposalActionsDialog } from "~/components/storage"; const actions = [ - { text: 'Create GPT on /dev/vdc', subvol: false, delete: false }, - { text: 'Create partition /dev/vdc1 (8.00 MiB) as BIOS Boot Partition', subvol: false, delete: false }, - { text: 'Create encrypted partition /dev/vdc2 (29.99 GiB) as LVM physical volume', subvol: false, delete: false }, - { text: 'Create volume group system0 (29.98 GiB) with /dev/mapper/cr_vdc2 (29.99 GiB)', subvol: false, delete: false }, - { text: 'Create LVM logical volume /dev/system0/root (20.00 GiB) on volume group system0 for / with btrfs', subvol: false, delete: false }, + { text: "Create GPT on /dev/vdc", subvol: false, delete: false }, + { + text: "Create partition /dev/vdc1 (8.00 MiB) as BIOS Boot Partition", + subvol: false, + delete: false, + }, + { + text: "Create encrypted partition /dev/vdc2 (29.99 GiB) as LVM physical volume", + subvol: false, + delete: false, + }, + { + text: "Create volume group system0 (29.98 GiB) with /dev/mapper/cr_vdc2 (29.99 GiB)", + subvol: false, + delete: false, + }, + { + text: "Create LVM logical volume /dev/system0/root (20.00 GiB) on volume group system0 for / with btrfs", + subvol: false, + delete: false, + }, ]; const subvolumeActions = [ - { text: 'Create subvolume @ on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/var on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/usr/local on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/srv on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/root on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/opt on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/home on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/boot/writable on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/boot/grub2/x86_64-efi on /dev/system0/root (20.00 GiB)', subvol: true, delete: false }, - { text: 'Create subvolume @/boot/grub2/i386-pc on /dev/system0/root (20.00 GiB)', subvol: true, delete: false } + { text: "Create subvolume @ on /dev/system0/root (20.00 GiB)", subvol: true, delete: false }, + { text: "Create subvolume @/var on /dev/system0/root (20.00 GiB)", subvol: true, delete: false }, + { + text: "Create subvolume @/usr/local on /dev/system0/root (20.00 GiB)", + subvol: true, + delete: false, + }, + { text: "Create subvolume @/srv on /dev/system0/root (20.00 GiB)", subvol: true, delete: false }, + { text: "Create subvolume @/root on /dev/system0/root (20.00 GiB)", subvol: true, delete: false }, + { text: "Create subvolume @/opt on /dev/system0/root (20.00 GiB)", subvol: true, delete: false }, + { text: "Create subvolume @/home on /dev/system0/root (20.00 GiB)", subvol: true, delete: false }, + { + text: "Create subvolume @/boot/writable on /dev/system0/root (20.00 GiB)", + subvol: true, + delete: false, + }, + { + text: "Create subvolume @/boot/grub2/x86_64-efi on /dev/system0/root (20.00 GiB)", + subvol: true, + delete: false, + }, + { + text: "Create subvolume @/boot/grub2/i386-pc on /dev/system0/root (20.00 GiB)", + subvol: true, + delete: false, + }, ]; -const destructiveAction = { text: 'Delete ext4 on /dev/vdc', subvol: false, delete: true }; +const destructiveAction = { text: "Delete ext4 on /dev/vdc", subvol: false, delete: true }; const onCloseFn = jest.fn(); @@ -56,7 +88,7 @@ it.skip("renders nothing by default", () => { it.skip("renders nothing when isOpen=false", () => { const { container } = plainRender( - + , ); expect(container).toBeEmptyDOMElement(); }); @@ -77,11 +109,13 @@ describe.skip("when isOpen", () => { const dialog = screen.getByRole("dialog", { name: "Planned Actions" }); const actionsList = within(dialog).getByRole("list"); const actionsListItems = within(actionsList).getAllByRole("listitem"); - expect(actionsListItems.map(i => i.textContent)).toEqual(actions.map(a => a.text)); + expect(actionsListItems.map((i) => i.textContent)).toEqual(actions.map((a) => a.text)); }); it("triggers the onClose callback when user clicks the Close button", async () => { - const { user } = plainRender(); + const { user } = plainRender( + , + ); const closeButton = screen.getByRole("button", { name: "Close" }); await user.click(closeButton); @@ -92,12 +126,18 @@ describe.skip("when isOpen", () => { describe("when there is a destructive action", () => { it("emphasizes the action", () => { plainRender( - + , ); // https://stackoverflow.com/a/63080940 const actionItems = screen.getAllByRole("listitem"); - const destructiveActionItem = actionItems.find(item => item.textContent === destructiveAction.text); + const destructiveActionItem = actionItems.find( + (item) => item.textContent === destructiveAction.text, + ); expect(destructiveActionItem).toHaveClass("proposal-action--delete"); }); @@ -106,7 +146,11 @@ describe.skip("when isOpen", () => { describe("when there are subvolume actions", () => { it("does not render the subvolume actions", () => { plainRender( - + , ); // For now, we know that there are two lists and the subvolume list is the second one. @@ -120,7 +164,11 @@ describe.skip("when isOpen", () => { it("renders the subvolume actions after clicking on 'show subvolumes'", async () => { const { user } = plainRender( - + , ); const link = screen.getByText(/Show.*subvolume actions/); @@ -137,7 +185,7 @@ describe.skip("when isOpen", () => { const [, subvolList] = screen.getAllByRole("list"); const subvolItems = within(subvolList).getAllByRole("listitem"); - expect(subvolItems.map(i => i.textContent)).toEqual(subvolumeActions.map(a => a.text)); + expect(subvolItems.map((i) => i.textContent)).toEqual(subvolumeActions.map((a) => a.text)); }); }); }); diff --git a/web/src/components/storage/ProposalActionsSummary.jsx b/web/src/components/storage/ProposalActionsSummary.jsx index 157a599627..ce673db4c4 100644 --- a/web/src/components/storage/ProposalActionsSummary.jsx +++ b/web/src/components/storage/ProposalActionsSummary.jsx @@ -27,7 +27,7 @@ import { CardField, ButtonLink } from "~/components/core"; import DevicesManager from "~/components/storage/DevicesManager"; import { _, n_ } from "~/i18n"; import { sprintf } from "sprintf-js"; -import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; +import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; /** * @typedef {import ("~/client/storage").Action} Action @@ -49,7 +49,7 @@ const DeletionsInfo = ({ policy, manager, spaceActions }) => { let label; let systemsLabel; const systems = manager.deletedSystems(); - const deleteActions = manager.actions.filter(a => a.delete && !a.subvol).length; + const deleteActions = manager.actions.filter((a) => a.delete && !a.subvol).length; const isDeletePolicy = policy?.id === "delete"; const hasDeleteActions = deleteActions !== 0; @@ -61,13 +61,14 @@ const DeletionsInfo = ({ policy, manager, spaceActions }) => { // TRANSLATORS: %d will be replaced by the amount of destructive actions label = ( - { - sprintf(n_( + {sprintf( + n_( "There is %d destructive action planned", "There are %d destructive actions planned", - deleteActions - ), deleteActions) - } + deleteActions, + ), + deleteActions, + )} ); } @@ -76,7 +77,11 @@ const DeletionsInfo = ({ policy, manager, spaceActions }) => { // FIXME: Use the Intl.ListFormat instead of the `join(", ")` used below. // Most probably, a `listFormat` or similar wrapper should live in src/i18n.js or so. // Read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat - systemsLabel = <>{_("affecting")} {systems.join(", ")}; + systemsLabel = ( + <> + {_("affecting")} {systems.join(", ")} + + ); } return ( @@ -99,7 +104,7 @@ const ResizesInfo = ({ policy, manager, validProposal, spaceActions }) => { let label; let systemsLabel; const systems = manager.resizedSystems(); - const resizeActions = manager.actions.filter(a => a.resize).length; + const resizeActions = manager.actions.filter((a) => a.resize).length; const isResizePolicy = policy?.id === "resize"; const hasResizeActions = resizeActions !== 0; @@ -112,18 +117,21 @@ const ResizesInfo = ({ policy, manager, validProposal, spaceActions }) => { } else if (validProposal && (isResizePolicy || spaceActions.length > 0) && !hasResizeActions) { label = _("Shrinking some partitions is allowed but not needed"); } else if (hasResizeActions) { - label = sprintf(n_( - "%d partition will be shrunk", - "%d partitions will be shrunk", - resizeActions - ), resizeActions); + label = sprintf( + n_("%d partition will be shrunk", "%d partitions will be shrunk", resizeActions), + resizeActions, + ); } if (systems.length) { // FIXME: Use the Intl.ListFormat instead of the `join(", ")` used below. // Most probably, a `listFormat` or similar wrapper should live in src/i18n.js or so. // Read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat - systemsLabel = <>{_("affecting")} {systems.join(", ")}; + systemsLabel = ( + <> + {_("affecting")} {systems.join(", ")} + + ); } return ( @@ -156,27 +164,23 @@ const ActionsInfo = ({ actions, validProposal, onClick }) => { label = ( ); } - return ( - - {label} - - ); + return {label}; }; const ActionsSkeleton = () => ( - + @@ -215,8 +219,11 @@ export default function ProposalActionsSummary({ // eslint-disable-next-line agama-i18n/string-literals value = _(policy.summaryLabels[0]); } else { - // eslint-disable-next-line agama-i18n/string-literals - value = sprintf(n_(policy.summaryLabels[0], policy.summaryLabels[1], devices.length), devices.length); + value = sprintf( + // eslint-disable-next-line agama-i18n/string-literals + n_(policy.summaryLabels[0], policy.summaryLabels[1], devices.length), + devices.length, + ); } const devicesManager = new DevicesManager(system, staging, actions); @@ -225,35 +232,37 @@ export default function ProposalActionsSummary({ : {_("Change")} + isLoading ? ( + + ) : ( + {_("Change")} + ) } cardProps={{ isFullHeight: false }} > - { - isLoading - ? - : ( - - a.action === "force_delete")} - /> - a.action === "resize")} - /> - - - ) - } + {isLoading ? ( + + ) : ( + + a.action === "force_delete")} + /> + a.action === "resize")} + /> + + + )} ); diff --git a/web/src/components/storage/ProposalActionsSummary.test.jsx b/web/src/components/storage/ProposalActionsSummary.test.jsx index eda355bc2f..3cb7aa52bb 100644 --- a/web/src/components/storage/ProposalActionsSummary.test.jsx +++ b/web/src/components/storage/ProposalActionsSummary.test.jsx @@ -50,9 +50,9 @@ const sda = { udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }; -const keepPolicy = SPACE_POLICIES.find(p => p.id === "keep"); -const deletePolicy = SPACE_POLICIES.find(p => p.id === "delete"); -const resizePolicy = SPACE_POLICIES.find(p => p.id === "resize"); +const keepPolicy = SPACE_POLICIES.find((p) => p.id === "keep"); +const deletePolicy = SPACE_POLICIES.find((p) => p.id === "delete"); +const resizePolicy = SPACE_POLICIES.find((p) => p.id === "resize"); const defaultProps = { isLoading: false, @@ -60,7 +60,7 @@ const defaultProps = { onActionsClick: jest.fn(), system: devices.system, staging: devices.staging, - actions + actions, }; describe("ProposalActionsSummary", () => { @@ -75,7 +75,7 @@ describe("ProposalActionsSummary", () => { const props = { ...defaultProps, policy: deletePolicy, - actions: [{ device: 79, subvol: false, delete: true, text: "" }] + actions: [{ device: 79, subvol: false, delete: true, text: "" }], }; installerRender(); @@ -90,7 +90,7 @@ describe("ProposalActionsSummary", () => { const props = { ...defaultProps, policy: resizePolicy, - actions: [{ device: 79, subvol: false, delete: false, resize: true, text: "" }] + actions: [{ device: 79, subvol: false, delete: false, resize: true, text: "" }], }; installerRender(); diff --git a/web/src/components/storage/ProposalPage.jsx b/web/src/components/storage/ProposalPage.jsx index b24897de5d..43956c5ca3 100644 --- a/web/src/components/storage/ProposalPage.jsx +++ b/web/src/components/storage/ProposalPage.jsx @@ -49,7 +49,7 @@ const initialState = { system: [], staging: [], actions: [], - errors: [] + errors: [], }; const reducer = (state, action) => { @@ -183,8 +183,8 @@ export default function ProposalPage() { }, [client, cancellablePromise]); const loadDevices = useCallback(async () => { - const system = await cancellablePromise(client.system.getDevices()) || []; - const staging = await cancellablePromise(client.staging.getDevices()) || []; + const system = (await cancellablePromise(client.system.getDevices())) || []; + const staging = (await cancellablePromise(client.staging.getDevices())) || []; return { system, staging }; }, [client, cancellablePromise]); @@ -193,9 +193,12 @@ export default function ProposalPage() { return issues.map(toValidationError); }, [client, cancellablePromise]); - const calculateProposal = useCallback(async (settings) => { - return await cancellablePromise(client.proposal.calculate(settings)); - }, [client, cancellablePromise]); + const calculateProposal = useCallback( + async (settings) => { + return await cancellablePromise(client.proposal.calculate(settings)); + }, + [client, cancellablePromise], + ); const load = useCallback(async () => { dispatch({ type: "START_LOADING" }); @@ -229,24 +232,38 @@ export default function ProposalPage() { dispatch({ type: "UPDATE_ERRORS", payload: { errors } }); if (result !== undefined) dispatch({ type: "STOP_LOADING" }); - }, [calculateProposal, cancellablePromise, client, loadAvailableDevices, loadVolumeDevices, loadDevices, loadEncryptionMethods, loadErrors, loadProposalResult, loadVolumeTemplates]); - - const calculate = useCallback(async (settings) => { - dispatch({ type: "START_LOADING" }); + }, [ + calculateProposal, + cancellablePromise, + client, + loadAvailableDevices, + loadVolumeDevices, + loadDevices, + loadEncryptionMethods, + loadErrors, + loadProposalResult, + loadVolumeTemplates, + ]); + + const calculate = useCallback( + async (settings) => { + dispatch({ type: "START_LOADING" }); + + await calculateProposal(settings); - await calculateProposal(settings); - - const result = await loadProposalResult(); - dispatch({ type: "UPDATE_RESULT", payload: { result } }); + const result = await loadProposalResult(); + dispatch({ type: "UPDATE_RESULT", payload: { result } }); - const devices = await loadDevices(); - dispatch({ type: "UPDATE_DEVICES", payload: devices }); + const devices = await loadDevices(); + dispatch({ type: "UPDATE_DEVICES", payload: devices }); - const errors = await loadErrors(); - dispatch({ type: "UPDATE_ERRORS", payload: { errors } }); + const errors = await loadErrors(); + dispatch({ type: "UPDATE_ERRORS", payload: { errors } }); - dispatch({ type: "STOP_LOADING" }); - }, [calculateProposal, loadDevices, loadErrors, loadProposalResult]); + dispatch({ type: "STOP_LOADING" }); + }, + [calculateProposal, loadDevices, loadErrors, loadProposalResult], + ); useEffect(() => { load().catch(console.error); @@ -275,7 +292,7 @@ export default function ProposalPage() { calculate(newSettings).catch(console.error); }; - const spacePolicy = SPACE_POLICIES.find(p => p.id === state.settings.spacePolicy); + const spacePolicy = SPACE_POLICIES.find((p) => p.id === state.settings.spacePolicy); /** * @todo Enable type checking and ensure the components are called with the correct props. @@ -292,9 +309,7 @@ export default function ProposalPage() { - + { return { ...original, - Skeleton: () =>
PFSkeleton
- + Skeleton: () =>
PFSkeleton
, }; }); jest.mock("./DevicesTechMenu", () => () =>
Devices Tech Menu
); @@ -51,12 +50,12 @@ jest.mock("./DevicesTechMenu", () => () =>
Devices Tech Menu
); jest.mock("~/queries/software", () => ({ ...jest.requireActual("~/queries/software"), useProduct: () => ({ - selectedProduct: { name: "Test" } + selectedProduct: { name: "Test" }, }), - useProductChanges: () => jest.fn() + useProductChanges: () => jest.fn(), })); -const createClientMock = /** @type {jest.Mock} */(createClient); +const createClientMock = /** @type {jest.Mock} */ (createClient); /** @type {StorageDevice} */ const vda = { @@ -73,7 +72,7 @@ const vda = { sdCard: true, active: true, name: "/dev/vda", - size: 1e+12, + size: 1e12, systems: ["Windows 11", "openSUSE Leap 15.2"], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], @@ -90,7 +89,7 @@ const vdb = { driver: ["ahci", "mmcblk"], bus: "IDE", name: "/dev/vdb", - size: 1e+6 + size: 1e6, }; /** @@ -98,28 +97,26 @@ const vdb = { * @returns {Volume} */ const volume = (mountPath) => { - return ( - { - mountPath, - target: "DEFAULT", - fsType: "Btrfs", - minSize: 1024, - maxSize: 1024, - autoSize: false, - snapshots: false, - transactional: false, - outline: { - required: false, - fsTypes: ["Btrfs"], - supportAutoSize: false, - snapshotsConfigurable: false, - snapshotsAffectSizes: false, - sizeRelevantVolumes: [], - adjustByRam: false, - productDefined: false - } - } - ); + return { + mountPath, + target: "DEFAULT", + fsType: "Btrfs", + minSize: 1024, + maxSize: 1024, + autoSize: false, + snapshots: false, + transactional: false, + outline: { + required: false, + fsTypes: ["Btrfs"], + supportAutoSize: false, + snapshotsConfigurable: false, + snapshotsAffectSizes: false, + sizeRelevantVolumes: [], + adjustByRam: false, + productDefined: false, + }, + }; }; /** @type {StorageClient} */ @@ -141,9 +138,9 @@ beforeEach(() => { spacePolicy: "", spaceActions: [], volumes: [], - installationDevices: [] + installationDevices: [], }, - actions: [] + actions: [], }; storage = { @@ -155,21 +152,21 @@ beforeEach(() => { getEncryptionMethods: jest.fn().mockResolvedValue([]), getProductMountPoints: jest.fn().mockResolvedValue([]), getResult: jest.fn().mockResolvedValue(proposalResult), - defaultVolume: jest.fn(mountPath => Promise.resolve(volume(mountPath))), + defaultVolume: jest.fn((mountPath) => Promise.resolve(volume(mountPath))), calculate: jest.fn().mockResolvedValue(0), }, // @ts-expect-error Some methods have to be private to avoid type complaint. system: { - getDevices: jest.fn().mockResolvedValue([vda, vdb]) + getDevices: jest.fn().mockResolvedValue([vda, vdb]), }, // @ts-expect-error Some methods have to be private to avoid type complaint. staging: { - getDevices: jest.fn().mockResolvedValue([vda]) + getDevices: jest.fn().mockResolvedValue([vda]), }, getErrors: jest.fn().mockResolvedValue([]), isDeprecated: jest.fn().mockResolvedValue(false), onDeprecate: jest.fn(), - onStatusChange: jest.fn() + onStatusChange: jest.fn(), }; createClientMock.mockImplementation(() => ({ storage })); diff --git a/web/src/components/storage/ProposalResultSection.jsx b/web/src/components/storage/ProposalResultSection.jsx index 829398ae40..e8f7f3f062 100644 --- a/web/src/components/storage/ProposalResultSection.jsx +++ b/web/src/components/storage/ProposalResultSection.jsx @@ -39,7 +39,10 @@ import { _ } from "~/i18n"; */ const ResultSkeleton = () => ( - + @@ -72,13 +75,19 @@ export default function ProposalResultSection({ > {isLoading && } - {errors.length === 0 - ? - : ( - - {errors.map((e, i) =>
{e.message}
)} -
- )} + {errors.length === 0 ? ( + + ) : ( + + {errors.map((e, i) => ( +
{e.message}
+ ))} +
+ )}
); diff --git a/web/src/components/storage/ProposalResultSection.test.jsx b/web/src/components/storage/ProposalResultSection.test.jsx index ccbc15a971..0e3db28354 100644 --- a/web/src/components/storage/ProposalResultSection.test.jsx +++ b/web/src/components/storage/ProposalResultSection.test.jsx @@ -63,7 +63,7 @@ describe.skip("ProposalResultSection", () => { it("does not render a warning when there are not delete actions", () => { const props = { ...defaultProps, - actions: defaultProps.actions.filter(a => !a.delete) + actions: defaultProps.actions.filter((a) => !a.delete), }; plainRender(); @@ -81,7 +81,7 @@ describe.skip("ProposalResultSection", () => { // affected systems are rendered in the warning summary const props = { ...defaultProps, - actions: [{ device: 79, subvol: false, delete: true, text: "" }] + actions: [{ device: 79, subvol: false, delete: true, text: "" }], }; plainRender(); diff --git a/web/src/components/storage/ProposalResultTable.jsx b/web/src/components/storage/ProposalResultTable.jsx index c84ec4d86c..ebaec78719 100644 --- a/web/src/components/storage/ProposalResultTable.jsx +++ b/web/src/components/storage/ProposalResultTable.jsx @@ -24,7 +24,10 @@ import React from "react"; import { Label, Flex } from "@patternfly/react-core"; import { - DeviceName, DeviceDetails, DeviceSize, toStorageDevice + DeviceName, + DeviceDetails, + DeviceSize, + toStorageDevice, } from "~/components/storage/device-utils"; // eslint-disable-next-line @typescript-eslint/no-unused-vars import DevicesManager from "~/components/storage/DevicesManager"; @@ -71,7 +74,11 @@ const DeviceCustomDetails = ({ item, devicesManager }) => { return ( - {isNew() && } + {isNew() && ( + + )} ); }; @@ -90,14 +97,15 @@ const DeviceCustomSize = ({ item, devicesManager }) => { return ( - {isResized && + {isResized && ( } + + )} ); }; @@ -111,7 +119,9 @@ const columns = (devicesManager) => { const renderMountPoint = (item) => ; /** @type {(item: TableItem) => React.ReactNode} */ - const renderDetails = (item) => ; + const renderDetails = (item) => ( + + ); /** @type {(item: TableItem) => React.ReactNode} */ const renderSize = (item) => ; @@ -120,7 +130,7 @@ const columns = (devicesManager) => { { name: _("Device"), value: renderDevice }, { name: _("Mount Point"), value: renderMountPoint }, { name: _("Details"), value: renderDetails }, - { name: _("Size"), value: renderSize, classNames: "sizes-column" } + { name: _("Size"), value: renderSize, classNames: "sizes-column" }, ]; }; diff --git a/web/src/components/storage/ProposalSettingsSection.jsx b/web/src/components/storage/ProposalSettingsSection.jsx index 3c0b1d21f1..d35f6a5f16 100644 --- a/web/src/components/storage/ProposalSettingsSection.jsx +++ b/web/src/components/storage/ProposalSettingsSection.jsx @@ -74,18 +74,15 @@ export default function ProposalSettingsSection({ volumeTemplates, isLoading = false, changing = undefined, - onChange + onChange, }) { /** @param {import("~/components/storage/InstallationDeviceField").TargetConfig} targetConfig */ const changeTarget = ({ target, targetDevice, targetPVDevices }) => { - onChange( - CHANGING.TARGET, - { - target, - targetDevice: targetDevice?.name, - targetPVDevices: targetPVDevices.map(d => d.name) - } - ); + onChange(CHANGING.TARGET, { + target, + targetDevice: targetDevice?.name, + targetPVDevices: targetPVDevices.map((d) => d.name), + }); }; /** @param {import("~/components/storage/EncryptionField").EncryptionConfig} encryptionConfig */ @@ -100,20 +97,17 @@ export default function ProposalSettingsSection({ /** @param {import("~/components/storage/PartitionsField").BootConfig} bootConfig */ const changeBoot = ({ configureBoot, bootDevice }) => { - onChange( - CHANGING.BOOT, - { - configureBoot, - bootDevice: bootDevice?.name - } - ); + onChange(CHANGING.BOOT, { + configureBoot, + bootDevice: bootDevice?.name, + }); }; /** * @param {string} name * @returns {StorageDevice|undefined} */ - const findDevice = (name) => availableDevices.find(a => a.name === name); + const findDevice = (name) => availableDevices.find((a) => a.name === name); /** @type {StorageDevice|undefined} */ const targetDevice = findDevice(settings.targetDevice); @@ -156,7 +150,9 @@ export default function ProposalSettingsSection({ configureBoot={settings.configureBoot} bootDevice={bootDevice} defaultBootDevice={defaultBootDevice} - isLoading={showSkeleton(isLoading, "PartitionsField", changing) || settings.volumes === undefined} + isLoading={ + showSkeleton(isLoading, "PartitionsField", changing) || settings.volumes === undefined + } onVolumesChange={changeVolumes} onBootChange={changeBoot} /> diff --git a/web/src/components/storage/ProposalSettingsSection.test.jsx b/web/src/components/storage/ProposalSettingsSection.test.jsx index caa0a17729..44d19ffc13 100644 --- a/web/src/components/storage/ProposalSettingsSection.test.jsx +++ b/web/src/components/storage/ProposalSettingsSection.test.jsx @@ -36,7 +36,7 @@ jest.mock("@patternfly/react-core", () => { return { ...original, - Skeleton: () =>
PFSkeleton
+ Skeleton: () =>
PFSkeleton
, }; }); @@ -58,7 +58,7 @@ const sda = { name: "/dev/sda", size: 1024, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }; @@ -81,9 +81,9 @@ const sdb = { name: "/dev/sdb", size: 2048, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: ["pci-0000:00-19"] + udevPaths: ["pci-0000:00-19"], }; /** @type {ProposalSettingsSectionProps} */ @@ -103,13 +103,13 @@ beforeEach(() => { spacePolicy: "delete", spaceActions: [], volumes: [], - installationDevices: [sda, sdb] + installationDevices: [sda, sdb], }, availableDevices: [], volumeDevices: [], encryptionMethods: [], volumeTemplates: [], - onChange: jest.fn() + onChange: jest.fn(), }; }); diff --git a/web/src/components/storage/ProposalTransactionalInfo.jsx b/web/src/components/storage/ProposalTransactionalInfo.jsx index 8e65762dc0..1221ad6374 100644 --- a/web/src/components/storage/ProposalTransactionalInfo.jsx +++ b/web/src/components/storage/ProposalTransactionalInfo.jsx @@ -45,9 +45,15 @@ export default function ProposalTransactionalInfo({ settings }) { const title = _("Transactional root file system"); /* TRANSLATORS: %s is replaced by a product name (e.g., openSUSE Tumbleweed) */ const description = sprintf( - _("%s is an immutable system with atomic updates. It uses a read-only Btrfs file system updated via snapshots."), - selectedProduct.name + _( + "%s is an immutable system with atomic updates. It uses a read-only Btrfs file system updated via snapshots.", + ), + selectedProduct.name, ); - return {description}; + return ( + + {description} + + ); } diff --git a/web/src/components/storage/ProposalTransactionalInfo.test.jsx b/web/src/components/storage/ProposalTransactionalInfo.test.jsx index 561793d4ba..4d6c9018dd 100644 --- a/web/src/components/storage/ProposalTransactionalInfo.test.jsx +++ b/web/src/components/storage/ProposalTransactionalInfo.test.jsx @@ -27,9 +27,9 @@ import { ProposalTransactionalInfo } from "~/components/storage"; jest.mock("~/queries/software", () => ({ ...jest.requireActual("~/queries/software"), useProduct: () => ({ - selectedProduct : { name: "Test" } + selectedProduct: { name: "Test" }, }), - useProductChanges: () => jest.fn() + useProductChanges: () => jest.fn(), })); let props; diff --git a/web/src/components/storage/SnapshotsField.jsx b/web/src/components/storage/SnapshotsField.jsx index ead1c41e83..5aecc518e3 100644 --- a/web/src/components/storage/SnapshotsField.jsx +++ b/web/src/components/storage/SnapshotsField.jsx @@ -26,7 +26,7 @@ import { Split, Switch } from "@patternfly/react-core"; import { _ } from "~/i18n"; import { noop } from "~/utils"; import { hasFS } from "~/components/storage/utils"; -import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; +import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; /** * @typedef {import ("~/client/storage").ProposalSettings} ProposalSettings @@ -34,8 +34,10 @@ import textStyles from '@patternfly/react-styles/css/utilities/Text/text'; */ const LABEL = _("Use Btrfs snapshots for the root file system"); -const DESCRIPTION = _("Allows to boot to a previous version of the \ -system after configuration changes or software upgrades."); +const DESCRIPTION = _( + "Allows to boot to a previous version of the \ +system after configuration changes or software upgrades.", +); /** * Allows to define snapshots enablement @@ -50,10 +52,7 @@ system after configuration changes or software upgrades."); * * @param {SnapshotsFieldProps} props */ -export default function SnapshotsField({ - rootVolume, - onChange = noop -}) { +export default function SnapshotsField({ rootVolume, onChange = noop }) { const isChecked = hasFS(rootVolume, "Btrfs") && rootVolume.snapshots; const switchState = () => { @@ -62,12 +61,7 @@ export default function SnapshotsField({ return ( - +
{LABEL}
{DESCRIPTION}
diff --git a/web/src/components/storage/SnapshotsField.test.jsx b/web/src/components/storage/SnapshotsField.test.jsx index 44126ce6c6..2f29b470c4 100644 --- a/web/src/components/storage/SnapshotsField.test.jsx +++ b/web/src/components/storage/SnapshotsField.test.jsx @@ -48,8 +48,8 @@ const rootVolume = { snapshotsAffectSizes: true, adjustByRam: false, sizeRelevantVolumes: ["/home"], - productDefined: true - } + productDefined: true, + }, }; const onChangeFn = jest.fn(); diff --git a/web/src/components/storage/SpaceActionsTable.jsx b/web/src/components/storage/SpaceActionsTable.jsx index c756fc2a67..7007cc8090 100644 --- a/web/src/components/storage/SpaceActionsTable.jsx +++ b/web/src/components/storage/SpaceActionsTable.jsx @@ -24,17 +24,23 @@ import React from "react"; import { Button, - Flex, FlexItem, - List, ListItem, + Flex, + FlexItem, + List, + ListItem, Popover, - ToggleGroup, ToggleGroupItem + ToggleGroup, + ToggleGroupItem, } from "@patternfly/react-core"; import { sprintf } from "sprintf-js"; import { _ } from "~/i18n"; -import { deviceChildren, deviceSize } from '~/components/storage/utils'; +import { deviceChildren, deviceSize } from "~/components/storage/utils"; import { - DeviceName, DeviceDetails, DeviceSize, toStorageDevice + DeviceName, + DeviceDetails, + DeviceSize, + toStorageDevice, } from "~/components/storage/device-utils"; import { TreeTable } from "~/components/core"; import { Icon } from "~/components/layout"; @@ -58,8 +64,9 @@ const DeviceInfoContent = ({ device }) => { if (minSize) { const recoverable = device.size - minSize; - return ( - sprintf(_("Up to %s can be recovered by shrinking the device."), deviceSize(recoverable)) + return sprintf( + _("Up to %s can be recovered by shrinking the device."), + deviceSize(recoverable), ); } @@ -69,7 +76,9 @@ const DeviceInfoContent = ({ device }) => { <> {_("The device cannot be shrunk:")} - {reasons.map((reason, idx) => {reason})} + {reasons.map((reason, idx) => ( + {reason} + ))} ); @@ -84,10 +93,7 @@ const DeviceInfoContent = ({ device }) => { */ const DeviceInfo = ({ device }) => { return ( - } - > + }> + ); }; @@ -426,18 +447,24 @@ const ControllersSection = ({ client, manager, load = noop, isLoading = false }) const Content = () => { const LUNScanInfo = () => { const msg = allowLUNScan - // TRANSLATORS: the text in the square brackets [] will be displayed in bold - ? _("Automatic LUN scan is [enabled]. Activating a controller which is \ -running in NPIV mode will automatically configures all its LUNs.") - // TRANSLATORS: the text in the square brackets [] will be displayed in bold - : _("Automatic LUN scan is [disabled]. LUNs have to be manually \ -configured after activating a controller."); + ? // TRANSLATORS: the text in the square brackets [] will be displayed in bold + _( + "Automatic LUN scan is [enabled]. Activating a controller which is \ +running in NPIV mode will automatically configures all its LUNs.", + ) + : // TRANSLATORS: the text in the square brackets [] will be displayed in bold + _( + "Automatic LUN scan is [disabled]. LUNs have to be manually \ +configured after activating a controller.", + ); const [msgStart, msgBold, msgEnd] = msg.split(/[[\]]/); return (

- {msgStart}{msgBold}{msgEnd} + {msgStart} + {msgBold} + {msgEnd}

); }; @@ -474,7 +501,9 @@ const DiskPopup = ({ client, manager, onClose = noop }) => { const onSubmit = async (formData) => { setIsAcceptDisabled(true); const controller = manager.getController(formData.channel); - const result = await cancellablePromise(client.activateDisk(controller, formData.wwpn, formData.lun)); + const result = await cancellablePromise( + client.activateDisk(controller, formData.wwpn, formData.lun), + ); setIsAcceptDisabled(false); if (result === 0) onClose(); @@ -495,11 +524,7 @@ const DiskPopup = ({ client, manager, onClose = noop }) => { onLoading={onLoading} /> - + {_("Accept")} @@ -525,9 +550,7 @@ const DisksSection = ({ client, manager, isLoading = false }) => { const EmptyState = () => { const NoActiveControllers = () => { - return ( -
{_("Please, try to activate a zFCP controller.")}
- ); + return
{_("Please, try to activate a zFCP controller.")}
; }; const NoActiveDisks = () => { @@ -535,7 +558,9 @@ const DisksSection = ({ client, manager, isLoading = false }) => { <>
{_("Please, try to activate a zFCP disk.")}
{/* TRANSLATORS: button label */} - + ); }; @@ -557,7 +582,9 @@ const DisksSection = ({ client, manager, isLoading = false }) => { {/* TRANSLATORS: button label */} - + @@ -572,12 +599,7 @@ const DisksSection = ({ client, manager, isLoading = false }) => {
{isLoading && } {!isLoading && manager.disks.length === 0 ? : } - {isActivateOpen && - } + {isActivateOpen && }
); }; @@ -630,7 +652,7 @@ const reducer = (state, action) => { const initialState = { manager: new Manager(), - isLoading: true + isLoading: true, }; /** @@ -642,17 +664,20 @@ export default function ZFCPPage() { const { cancellablePromise } = useCancellablePromise(); const [state, dispatch] = useReducer(reducer, initialState); - const getLUNs = useCallback(async (controller) => { - const luns = []; - const wwpns = await cancellablePromise(client.zfcp.getWWPNs(controller)); - for (const wwpn of wwpns) { - const all = await cancellablePromise(client.zfcp.getLUNs(controller, wwpn)); - for (const lun of all) { - luns.push({ channel: controller.channel, wwpn, lun }); + const getLUNs = useCallback( + async (controller) => { + const luns = []; + const wwpns = await cancellablePromise(client.zfcp.getWWPNs(controller)); + for (const wwpn of wwpns) { + const all = await cancellablePromise(client.zfcp.getLUNs(controller, wwpn)); + for (const lun of all) { + luns.push({ channel: controller.channel, wwpn, lun }); + } } - } - return luns; - }, [client.zfcp, cancellablePromise]); + return luns; + }, + [client.zfcp, cancellablePromise], + ); const load = useCallback(async () => { dispatch({ type: "START_LOADING" }); @@ -688,13 +713,13 @@ export default function ZFCPPage() { action("ADD_LUNS", { luns }); } }), - await client.zfcp.onDiskAdded(d => action("ADD_DISK", { disk: d })), - await client.zfcp.onDiskRemoved(d => action("REMOVE_DISK", { disk: d })) + await client.zfcp.onDiskAdded((d) => action("ADD_DISK", { disk: d })), + await client.zfcp.onDiskRemoved((d) => action("REMOVE_DISK", { disk: d })), ); }; const unsubscribe = () => { - subscriptions.forEach(fn => fn()); + subscriptions.forEach((fn) => fn()); }; subscribe(); @@ -709,11 +734,7 @@ export default function ZFCPPage() { load={load} isLoading={state.isLoading} /> - + ); } diff --git a/web/src/components/storage/ZFCPPage.test.jsx b/web/src/components/storage/ZFCPPage.test.jsx index 3d57819152..ba2fac17f3 100644 --- a/web/src/components/storage/ZFCPPage.test.jsx +++ b/web/src/components/storage/ZFCPPage.test.jsx @@ -31,19 +31,30 @@ jest.mock("@patternfly/react-core", () => { return { ...original, - Skeleton: () =>
PFSkeleton
- + Skeleton: () =>
PFSkeleton
, }; }); const controllers = [ { id: "1", channel: "0.0.fa00", active: false, lunScan: false }, - { id: "2", channel: "0.0.fb00", active: true, lunScan: false } + { id: "2", channel: "0.0.fb00", active: true, lunScan: false }, ]; const disks = [ - { id: "1", name: "/dev/sda", channel: "0.0.fb00", wwpn: "0x500507630703d3b3", lun: "0x0000000000000001" }, - { id: "2", name: "/dev/sdb", channel: "0.0.fb00", wwpn: "0x500507630703d3b3", lun: "0x0000000000000002" } + { + id: "1", + name: "/dev/sda", + channel: "0.0.fb00", + wwpn: "0x500507630703d3b3", + lun: "0x0000000000000001", + }, + { + id: "2", + name: "/dev/sdb", + channel: "0.0.fb00", + wwpn: "0x500507630703d3b3", + lun: "0x0000000000000002", + }, ]; const defaultClient = { @@ -59,7 +70,7 @@ const defaultClient = { activateController: jest.fn().mockResolvedValue(0), getAllowLUNScan: jest.fn().mockResolvedValue(false), activateDisk: jest.fn().mockResolvedValue(0), - deactivateDisk: jest.fn().mockResolvedValue(0) + deactivateDisk: jest.fn().mockResolvedValue(0), }; let client; @@ -83,10 +94,18 @@ it.skip("loads the zFCP devices", async () => { screen.getAllByText(/PFSkeleton/); expect(screen.queryAllByRole("grid").length).toBe(0); await waitFor(() => expect(client.probe).toHaveBeenCalled()); - await waitFor(() => expect(client.getLUNs).toHaveBeenCalledWith(controllers[1], "0x500507630703d3b3")); - await waitFor(() => expect(client.getLUNs).toHaveBeenCalledWith(controllers[1], "0x500507630704d3b3")); - await waitFor(() => expect(client.getLUNs).not.toHaveBeenCalledWith(controllers[0], "0x500507630703d3b3")); - await waitFor(() => expect(client.getLUNs).not.toHaveBeenCalledWith(controllers[0], "0x500507630704d3b3")); + await waitFor(() => + expect(client.getLUNs).toHaveBeenCalledWith(controllers[1], "0x500507630703d3b3"), + ); + await waitFor(() => + expect(client.getLUNs).toHaveBeenCalledWith(controllers[1], "0x500507630704d3b3"), + ); + await waitFor(() => + expect(client.getLUNs).not.toHaveBeenCalledWith(controllers[0], "0x500507630703d3b3"), + ); + await waitFor(() => + expect(client.getLUNs).not.toHaveBeenCalledWith(controllers[0], "0x500507630704d3b3"), + ); expect(screen.getAllByRole("grid").length).toBe(2); }); @@ -177,16 +196,20 @@ describe.skip("if there are not controllers", () => { describe.skip("if there are disks", () => { beforeEach(() => { client.getWWPNs = jest.fn().mockResolvedValue(["0x500507630703d3b3"]); - client.getLUNs = jest.fn().mockResolvedValue( - ["0x0000000000000001", "0x0000000000000002", "0x0000000000000003"] - ); + client.getLUNs = jest + .fn() + .mockResolvedValue(["0x0000000000000001", "0x0000000000000002", "0x0000000000000003"]); }); it("renders the information for each disk", async () => { installerRender(); - await screen.findByRole("row", { name: "/dev/sda 0.0.fb00 0x500507630703d3b3 0x0000000000000001" }); - await screen.findByRole("row", { name: "/dev/sdb 0.0.fb00 0x500507630703d3b3 0x0000000000000002" }); + await screen.findByRole("row", { + name: "/dev/sda 0.0.fb00 0x500507630703d3b3 0x0000000000000001", + }); + await screen.findByRole("row", { + name: "/dev/sdb 0.0.fb00 0x500507630703d3b3 0x0000000000000002", + }); }); it("renders a button for activating a disk", async () => { @@ -212,9 +235,9 @@ describe.skip("if there are disks", () => { describe("if the controller is not using auto LUN scan", () => { beforeEach(() => { - client.getControllers = jest.fn().mockResolvedValue([ - { id: "1", channel: "0.0.fb00", active: true, lunScan: false } - ]); + client.getControllers = jest + .fn() + .mockResolvedValue([{ id: "1", channel: "0.0.fb00", active: true, lunScan: false }]); }); it("allows deactivating a disk", async () => { @@ -229,15 +252,17 @@ describe.skip("if there are disks", () => { const [controller] = await client.getControllers(); const [disk] = await client.getDisks(); - await waitFor(() => expect(client.deactivateDisk).toHaveBeenCalledWith(controller, disk.wwpn, disk.lun)); + await waitFor(() => + expect(client.deactivateDisk).toHaveBeenCalledWith(controller, disk.wwpn, disk.lun), + ); }); }); describe("if the controller is using auto LUN scan", () => { beforeEach(() => { - client.getControllers = jest.fn().mockResolvedValue([ - { id: "1", channel: "0.0.fb00", active: true, lunScan: true } - ]); + client.getControllers = jest + .fn() + .mockResolvedValue([{ id: "1", channel: "0.0.fb00", active: true, lunScan: true }]); }); it("does not allow deactivating a disk", async () => { @@ -278,7 +303,9 @@ describe.skip("if there are not disks", () => { await screen.findByText("No zFCP disks found."); await screen.findByText(/try to activate a zFCP controller/); - await waitFor(() => expect(screen.queryByRole("button", { name: "Activate zFCP disk" })).toBeNull()); + await waitFor(() => + expect(screen.queryByRole("button", { name: "Activate zFCP disk" })).toBeNull(), + ); }); }); }); @@ -286,9 +313,9 @@ describe.skip("if there are not disks", () => { describe.skip("if the button for adding a disk is used", () => { beforeEach(() => { client.getWWPNs = jest.fn().mockResolvedValue(["0x500507630703d3b3"]); - client.getLUNs = jest.fn().mockResolvedValue( - ["0x0000000000000001", "0x0000000000000002", "0x0000000000000003"] - ); + client.getLUNs = jest + .fn() + .mockResolvedValue(["0x0000000000000001", "0x0000000000000002", "0x0000000000000003"]); }); it("opens a popup with the form for a new disk", async () => { @@ -331,7 +358,9 @@ describe.skip("if the button for adding a disk is used", () => { await user.click(accept); expect(client.activateDisk).toHaveBeenCalledWith( - controllers[1], "0x500507630703d3b3", "0x0000000000000003" + controllers[1], + "0x500507630703d3b3", + "0x0000000000000003", ); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); diff --git a/web/src/components/storage/device-utils.jsx b/web/src/components/storage/device-utils.jsx index 9262681db7..5f98ba43c5 100644 --- a/web/src/components/storage/device-utils.jsx +++ b/web/src/components/storage/device-utils.jsx @@ -59,7 +59,11 @@ const FilesystemLabel = ({ item }) => { const label = device.filesystem?.label; if (!label) return null; - return ; + return ( + + ); }; /** @@ -88,8 +92,7 @@ const DeviceDetails = ({ item }) => { if (!device) return _("Unused space"); const renderContent = (device) => { - if (!device.partitionTable && device.systems?.length > 0) - return device.systems.join(", "); + if (!device.partitionTable && device.systems?.length > 0) return device.systems.join(", "); return device.description; }; @@ -100,7 +103,9 @@ const DeviceDetails = ({ item }) => { }; return ( -
{renderContent(device)} {renderPTableType(device)}
+
+ {renderContent(device)} {renderPTableType(device)} +
); }; diff --git a/web/src/components/storage/device-utils.test.jsx b/web/src/components/storage/device-utils.test.jsx index 218208233f..6f4b380660 100644 --- a/web/src/components/storage/device-utils.test.jsx +++ b/web/src/components/storage/device-utils.test.jsx @@ -25,7 +25,11 @@ import React from "react"; import { screen } from "@testing-library/react"; import { plainRender } from "~/test-utils"; import { - DeviceDetails, DeviceName, DeviceSize, FilesystemLabel, toStorageDevice + DeviceDetails, + DeviceName, + DeviceSize, + FilesystemLabel, + toStorageDevice, } from "~/components/storage/device-utils"; /** @@ -52,9 +56,9 @@ const vda = { name: "/dev/vda", description: "", size: 1024, - systems : ["Windows 11", "openSUSE Leap 15.2"], + systems: ["Windows 11", "openSUSE Leap 15.2"], udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], - udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"] + udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], }; /** @type {StorageDevice} */ @@ -69,11 +73,11 @@ const vda1 = { start: 123, encrypted: false, shrinking: { supported: 128 }, - systems : [], + systems: [], udevIds: [], udevPaths: [], isEFI: false, - filesystem: { sid: 100, type: "ext4", mountPath: "/test", label: "system" } + filesystem: { sid: 100, type: "ext4", mountPath: "/test", label: "system" }, }; /** @type {StorageDevice} */ @@ -88,9 +92,9 @@ const lvmLv1 = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; describe("FilesystemLabel", () => { @@ -156,7 +160,7 @@ describe("DeviceDetails", () => { type: "gpt", partitions: [], unpartitionedSize: 0, - unusedSlots: [] + unusedSlots: [], }; }); diff --git a/web/src/components/storage/iscsi/AuthFields.jsx b/web/src/components/storage/iscsi/AuthFields.jsx index 9c4b07d51a..811a82f28e 100644 --- a/web/src/components/storage/iscsi/AuthFields.jsx +++ b/web/src/components/storage/iscsi/AuthFields.jsx @@ -32,28 +32,28 @@ export default function AuthFields({ data, onChange, onValidate }) { const onReverseUsernameChange = (_, v) => onChange("reverseUsername", v); const onReversePasswordChange = (_, v) => onChange("reversePassword", v); - const isValidText = v => v.length > 0; + const isValidText = (v) => v.length > 0; const isValidUsername = () => isValidText(data.username); const isValidPassword = () => isValidText(data.password); const isValidReverseUsername = () => isValidText(data.reverseUsername); const isValidReversePassword = () => isValidText(data.reversePassword); const isValidAuth = () => isValidUsername() && isValidPassword(); - const showUsernameError = () => isValidPassword() ? !isValidUsername() : false; - const showPasswordError = () => isValidUsername() ? !isValidPassword() : false; + const showUsernameError = () => (isValidPassword() ? !isValidUsername() : false); + const showPasswordError = () => (isValidUsername() ? !isValidPassword() : false); const showReverseUsernameError = () => { - return (isValidAuth() && isValidReversePassword()) ? !isValidReverseUsername() : false; + return isValidAuth() && isValidReversePassword() ? !isValidReverseUsername() : false; }; const showReversePasswordError = () => { - return (isValidAuth() && isValidReverseUsername()) ? !isValidReversePassword() : false; + return isValidAuth() && isValidReverseUsername() ? !isValidReversePassword() : false; }; useEffect(() => { onValidate( !showUsernameError() && - !showPasswordError() && - !showReverseUsernameError() && - !showReversePasswordError() + !showPasswordError() && + !showReverseUsernameError() && + !showReversePasswordError(), ); }); @@ -75,10 +75,7 @@ export default function AuthFields({ data, onChange, onValidate }) { return ( <>
- + - +
- + - + - + - +
diff --git a/web/src/components/storage/iscsi/DiscoverForm.jsx b/web/src/components/storage/iscsi/DiscoverForm.jsx index e66574e60a..788d9dc075 100644 --- a/web/src/components/storage/iscsi/DiscoverForm.jsx +++ b/web/src/components/storage/iscsi/DiscoverForm.jsx @@ -20,11 +20,7 @@ */ import React, { useEffect, useRef, useState } from "react"; -import { - Alert, - Form, FormGroup, - TextInput, -} from "@patternfly/react-core"; +import { Alert, Form, FormGroup, TextInput } from "@patternfly/react-core"; import { FormValidationError, Popup } from "~/components/core"; import { AuthFields } from "~/components/storage/iscsi"; @@ -38,7 +34,7 @@ const defaultData = { username: "", password: "", reverseUsername: "", - reversePassword: "" + reversePassword: "", }; export default function DiscoverForm({ onSubmit: onSubmitProp, onCancel }) { @@ -84,11 +80,7 @@ export default function DiscoverForm({ onSubmit: onSubmitProp, onCancel }) { const isValidAddress = () => isValidIp(data.address); const isValidPort = () => Number.isInteger(parseInt(data.port)); const isValidForm = () => { - return ( - isValidAddress() && - isValidPort() && - isValidAuth - ); + return isValidAddress() && isValidPort() && isValidAuth; }; const showAddressError = () => data.address.length > 0 && !isValidAddress(data.address); @@ -101,23 +93,14 @@ export default function DiscoverForm({ onSubmit: onSubmitProp, onCancel }) { // TRANSLATORS: popup title
- {isFailed && - ( -
- -

{_("Make sure you provide the correct values")}

-
-
- )} - + {isFailed && ( +
+ +

{_("Make sure you provide the correct values")}

+
+
+ )} + - + - + - + - setIsValidAuth(v)} - /> + setIsValidAuth(v)} /> - +
diff --git a/web/src/components/storage/iscsi/EditNodeForm.jsx b/web/src/components/storage/iscsi/EditNodeForm.jsx index acdfe44bf7..8ea1b32081 100644 --- a/web/src/components/storage/iscsi/EditNodeForm.jsx +++ b/web/src/components/storage/iscsi/EditNodeForm.jsx @@ -20,9 +20,7 @@ */ import React, { useState } from "react"; -import { - Form, FormGroup, FormSelect, FormSelectOption -} from "@patternfly/react-core"; +import { Form, FormGroup, FormSelect, FormSelectOption } from "@patternfly/react-core"; import { sprintf } from "sprintf-js"; import { _ } from "~/i18n"; @@ -61,10 +59,7 @@ export default function EditNodeForm({ node, onSubmit: onSubmitProp, onCancel })
- + diff --git a/web/src/components/storage/iscsi/InitiatorForm.jsx b/web/src/components/storage/iscsi/InitiatorForm.jsx index 67ae7df209..4eb9dcd26c 100644 --- a/web/src/components/storage/iscsi/InitiatorForm.jsx +++ b/web/src/components/storage/iscsi/InitiatorForm.jsx @@ -56,11 +56,7 @@ export default function InitiatorForm({ initiator, onSubmit: onSubmitProp, onCan
- + diff --git a/web/src/components/storage/iscsi/InitiatorPresenter.jsx b/web/src/components/storage/iscsi/InitiatorPresenter.jsx index 9c06f77ea9..2a0c3362b4 100644 --- a/web/src/components/storage/iscsi/InitiatorPresenter.jsx +++ b/web/src/components/storage/iscsi/InitiatorPresenter.jsx @@ -92,14 +92,9 @@ export default function InitiatorPresenter({ initiator, client }) { - {isFormOpen && - ( - - )} + {isFormOpen && ( + + )} ); } diff --git a/web/src/components/storage/iscsi/InitiatorSection.jsx b/web/src/components/storage/iscsi/InitiatorSection.jsx index ebe9c3eeb4..908d99c9db 100644 --- a/web/src/components/storage/iscsi/InitiatorSection.jsx +++ b/web/src/components/storage/iscsi/InitiatorSection.jsx @@ -47,10 +47,7 @@ export default function InitiatorSection() { return ( // TRANSLATORS: iSCSI initiator section name
- +
); } diff --git a/web/src/components/storage/iscsi/LoginForm.jsx b/web/src/components/storage/iscsi/LoginForm.jsx index 8775944087..c94a89fa70 100644 --- a/web/src/components/storage/iscsi/LoginForm.jsx +++ b/web/src/components/storage/iscsi/LoginForm.jsx @@ -20,10 +20,7 @@ */ import React, { useState } from "react"; -import { - Alert, - Form, FormGroup, FormSelect, FormSelectOption -} from "@patternfly/react-core"; +import { Alert, Form, FormGroup, FormSelect, FormSelectOption } from "@patternfly/react-core"; import { sprintf } from "sprintf-js"; import { _ } from "~/i18n"; @@ -36,7 +33,7 @@ export default function LoginForm({ node, onSubmit: onSubmitProp, onCancel }) { password: "", reverseUsername: "", reversePassword: "", - startup: "onboot" + startup: "onboot", }); const [isLoading, setIsLoading] = useState(false); const [isFailed, setIsFailed] = useState(false); @@ -68,10 +65,11 @@ export default function LoginForm({ node, onSubmit: onSubmitProp, onCancel }) { // TRANSLATORS: %s is replaced by the iSCSI target name
- { isFailed && + {isFailed && (

{_("Make sure you provide the correct values")}

-
} + + )} {/* TRANSLATORS: iSCSI start up mode (on boot/manual/automatic) */} - setIsValidAuth(v)} - /> + setIsValidAuth(v)} /> - +
diff --git a/web/src/components/storage/iscsi/NodeStartupOptions.js b/web/src/components/storage/iscsi/NodeStartupOptions.js index 1b9486f15a..acf61668df 100644 --- a/web/src/components/storage/iscsi/NodeStartupOptions.js +++ b/web/src/components/storage/iscsi/NodeStartupOptions.js @@ -24,7 +24,7 @@ import { _ } from "~/i18n"; const NodeStartupOptions = Object.freeze({ MANUAL: { label: _("Manual"), value: "manual" }, ONBOOT: { label: _("On boot"), value: "onboot" }, - AUTOMATIC: { label: _("Automatic"), value: "automatic" } + AUTOMATIC: { label: _("Automatic"), value: "automatic" }, }); export default NodeStartupOptions; diff --git a/web/src/components/storage/iscsi/NodesPresenter.jsx b/web/src/components/storage/iscsi/NodesPresenter.jsx index b1e07a7282..bc6fef9e9a 100644 --- a/web/src/components/storage/iscsi/NodesPresenter.jsx +++ b/web/src/components/storage/iscsi/NodesPresenter.jsx @@ -20,14 +20,14 @@ */ import React, { useState } from "react"; -import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; +import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; import { sprintf } from "sprintf-js"; import { _ } from "~/i18n"; -import { RowActions } from '~/components/core'; +import { RowActions } from "~/components/core"; import { EditNodeForm, LoginForm, NodeStartupOptions } from "~/components/storage/iscsi"; -export default function NodesPresenter ({ nodes, client }) { +export default function NodesPresenter({ nodes, client }) { const [currentNode, setCurrentNode] = useState(); const [isEditFormOpen, setIsEditFormOpen] = useState(false); const [isLoginFormOpen, setIsLoginFormOpen] = useState(false); @@ -62,7 +62,7 @@ export default function NodesPresenter ({ nodes, client }) { // TRANSLATORS: iSCSI connection status if (!node.connected) return _("Disconnected"); - const startup = Object.values(NodeStartupOptions).find(o => o.value === node.startup); + const startup = Object.values(NodeStartupOptions).find((o) => o.value === node.startup); // TRANSLATORS: iSCSI connection status, %s is replaced by node label return sprintf(_("Connected (%s)"), startup.label); }; @@ -71,27 +71,25 @@ export default function NodesPresenter ({ nodes, client }) { const actions = { edit: { title: _("Edit"), - onClick: () => openEditForm(node) + onClick: () => openEditForm(node), }, delete: { title: _("Delete"), onClick: () => client.iscsi.delete(node), - isDanger: true + isDanger: true, }, login: { title: _("Login"), - onClick: () => openLoginForm(node) + onClick: () => openLoginForm(node), }, logout: { title: _("Logout"), - onClick: () => client.iscsi.logout(node) - } + onClick: () => client.iscsi.logout(node), + }, }; - if (node.connected) - return [actions.edit, actions.logout]; - else - return [actions.login, actions.delete]; + if (node.connected) return [actions.edit, actions.logout]; + else return [actions.login, actions.delete]; }; const NodeRow = ({ node }) => { @@ -110,7 +108,7 @@ export default function NodesPresenter ({ nodes, client }) { }; const Content = () => { - return nodes.map(n => ); + return nodes.map((n) => ); }; return ( @@ -130,18 +128,12 @@ export default function NodesPresenter ({ nodes, client }) { - { isLoginFormOpen && - } - { isEditFormOpen && - } + {isLoginFormOpen && ( + + )} + {isEditFormOpen && ( + + )} ); } diff --git a/web/src/components/storage/iscsi/TargetsSection.jsx b/web/src/components/storage/iscsi/TargetsSection.jsx index 52170d05e7..b34890e1c5 100644 --- a/web/src/components/storage/iscsi/TargetsSection.jsx +++ b/web/src/components/storage/iscsi/TargetsSection.jsx @@ -20,11 +20,7 @@ */ import React, { useEffect, useReducer } from "react"; -import { - Button, - Toolbar, ToolbarItem, ToolbarContent, - Stack -} from "@patternfly/react-core"; +import { Button, Toolbar, ToolbarItem, ToolbarContent, Stack } from "@patternfly/react-core"; import { Section, SectionSkeleton } from "~/components/core"; import { NodesPresenter, DiscoverForm } from "~/components/storage/iscsi"; import { useInstallerClient } from "~/context/installer"; @@ -38,7 +34,7 @@ const reducer = (state, action) => { } case "ADD_NODE": { - if (state.nodes.find(n => n.id === action.payload.node.id)) return state; + if (state.nodes.find((n) => n.id === action.payload.node.id)) return state; const nodes = [...state.nodes]; nodes.push(action.payload.node); @@ -46,7 +42,7 @@ const reducer = (state, action) => { } case "UPDATE_NODE": { - const index = state.nodes.findIndex(n => n.id === action.payload.node.id); + const index = state.nodes.findIndex((n) => n.id === action.payload.node.id); if (index === -1) return state; const nodes = [...state.nodes]; @@ -55,7 +51,7 @@ const reducer = (state, action) => { } case "REMOVE_NODE": { - const nodes = state.nodes.filter(n => n.id !== action.payload.node.id); + const nodes = state.nodes.filter((n) => n.id !== action.payload.node.id); return { ...state, nodes }; } @@ -84,7 +80,7 @@ const reducer = (state, action) => { const initialState = { nodes: [], isDiscoverFormOpen: false, - isLoading: true + isLoading: true, }; export default function TargetsSection() { @@ -106,9 +102,9 @@ export default function TargetsSection() { useEffect(() => { const action = (type, node) => dispatch({ type, payload: { node } }); - client.iscsi.onNodeAdded(n => action("ADD_NODE", n)); - client.iscsi.onNodeChanged(n => action("UPDATE_NODE", n)); - client.iscsi.onNodeRemoved(n => action("REMOVE_NODE", n)); + client.iscsi.onNodeAdded((n) => action("ADD_NODE", n)); + client.iscsi.onNodeChanged((n) => action("UPDATE_NODE", n)); + client.iscsi.onNodeRemoved((n) => action("REMOVE_NODE", n)); }, [client.iscsi]); const openDiscoverForm = () => { @@ -140,9 +136,13 @@ export default function TargetsSection() { return (
{_("No iSCSI targets found.")}
-
{_("Please, perform an iSCSI discovery in order to find available iSCSI targets.")}
+
+ {_("Please, perform an iSCSI discovery in order to find available iSCSI targets.")} +
{/* TRANSLATORS: button label, starts iSCSI discovery */} - +
); } @@ -157,10 +157,7 @@ export default function TargetsSection() { - + ); }; @@ -169,11 +166,9 @@ export default function TargetsSection() { // TRANSLATORS: iSCSI targets section title
- {state.isDiscoverFormOpen && - } + {state.isDiscoverFormOpen && ( + + )}
); } diff --git a/web/src/components/storage/routes.js b/web/src/components/storage/routes.js index 7efffd35cc..a8be087e3e 100644 --- a/web/src/components/storage/routes.js +++ b/web/src/components/storage/routes.js @@ -34,7 +34,7 @@ import { N_ } from "~/i18n"; const navigation = [ // FIXME: use index: true { path: "/storage", element: , handle: { name: N_("Proposal") } }, - { path: "iscsi", element: , handle: { name: N_("iSCSI") } } + { path: "iscsi", element: , handle: { name: N_("iSCSI") } }, ]; // if (something) { @@ -48,7 +48,7 @@ const navigation = [ const selectors = [ { path: "target-device", element: }, { path: "booting-partition", element: }, - { path: "space-policy", element: } + { path: "space-policy", element: }, ]; const routes = { @@ -56,12 +56,9 @@ const routes = { element: , handle: { name: N_("Storage"), - icon: "hard_drive" + icon: "hard_drive", }, - children: [ - ...navigation, - ...selectors, - ] + children: [...navigation, ...selectors], }; export default routes; diff --git a/web/src/components/storage/test-data/full-result-example.js b/web/src/components/storage/test-data/full-result-example.js index 79aaa3ed32..f3587215e4 100644 --- a/web/src/components/storage/test-data/full-result-example.js +++ b/web/src/components/storage/test-data/full-result-example.js @@ -1,1446 +1,1281 @@ export const settings = { - "bootDevice": "/dev/vdc", - "lvm": false, - "spacePolicy": "custom", - "spaceActions": [ + bootDevice: "/dev/vdc", + lvm: false, + spacePolicy: "custom", + spaceActions: [ { - "device": "/dev/vdc3", - "action": "force_delete" + device: "/dev/vdc3", + action: "force_delete", }, { - "device": "/dev/vdc4", - "action": "resize" + device: "/dev/vdc4", + action: "resize", }, { - "device": "/dev/vdc1", - "action": "force_delete" - } + device: "/dev/vdc1", + action: "force_delete", + }, ], - "systemVGDevices": [], - "encryptionPassword": "", - "encryptionMethod": "luks2", - "volumes": [ + systemVGDevices: [], + encryptionPassword: "", + encryptionMethod: "luks2", + volumes: [ { - "mountPath": "/", - "fsType": "Btrfs", - "minSize": 18790481920, - "autoSize": true, - "snapshots": true, - "transactional": false, - "outline": { - "required": true, - "fsTypes": [ - "Btrfs", - "Ext2", - "Ext3", - "Ext4", - "XFS" - ], - "supportAutoSize": true, - "snapshotsConfigurable": true, - "snapshotsAffectSizes": true, - "sizeRelevantVolumes": [ - "/home" - ] - } + mountPath: "/", + fsType: "Btrfs", + minSize: 18790481920, + autoSize: true, + snapshots: true, + transactional: false, + outline: { + required: true, + fsTypes: ["Btrfs", "Ext2", "Ext3", "Ext4", "XFS"], + supportAutoSize: true, + snapshotsConfigurable: true, + snapshotsAffectSizes: true, + sizeRelevantVolumes: ["/home"], + }, }, { - "mountPath": "swap", - "fsType": "Swap", - "minSize": 1610612736, - "maxSize": 1610612736, - "autoSize": false, - "snapshots": false, - "transactional": false, - "outline": { - "required": false, - "fsTypes": [ - "Swap" - ], - "supportAutoSize": false, - "snapshotsConfigurable": false, - "snapshotsAffectSizes": false, - "sizeRelevantVolumes": [] - } - } + mountPath: "swap", + fsType: "Swap", + minSize: 1610612736, + maxSize: 1610612736, + autoSize: false, + snapshots: false, + transactional: false, + outline: { + required: false, + fsTypes: ["Swap"], + supportAutoSize: false, + snapshotsConfigurable: false, + snapshotsAffectSizes: false, + sizeRelevantVolumes: [], + }, + }, ], - "installationDevices": [ + installationDevices: [ { - "sid": 70, - "name": "/dev/vdc", - "description": "Disk", - "isDrive": true, - "type": "disk", - "vendor": "", - "model": "Disk", - "driver": [ - "virtio-pci", - "virtio_blk" - ], - "bus": "None", - "busId": "", - "transport": "unknown", - "sdCard": false, - "dellBOSS": false, - "active": true, - "encrypted": false, - "start": 0, - "size": 32212254720, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0" - ], - "partitionTable": { - "type": "gpt", - "partitions": [ + sid: 70, + name: "/dev/vdc", + description: "Disk", + isDrive: true, + type: "disk", + vendor: "", + model: "Disk", + driver: ["virtio-pci", "virtio_blk"], + bus: "None", + busId: "", + transport: "unknown", + sdCard: false, + dellBOSS: false, + active: true, + encrypted: false, + start: 0, + size: 32212254720, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0"], + partitionTable: { + type: "gpt", + partitions: [ { - "sid": 78, - "name": "/dev/vdc1", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part1" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } + sid: 78, + name: "/dev/vdc1", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part1"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, }, { - "sid": 79, - "name": "/dev/vdc2", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 10487808, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [ - "openSUSE Leap 15.2", - "Fedora 10.30" - ], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } + sid: 79, + name: "/dev/vdc2", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 10487808, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: ["openSUSE Leap 15.2", "Fedora 10.30"], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part2"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, }, { - "sid": 80, - "name": "/dev/vdc3", - "description": "XFS Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 20973568, - "size": 1073741824, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part3" - ], - "isEFI": false, - "filesystem": { - "sid": 92, - "type": "xfs" - } + sid: 80, + name: "/dev/vdc3", + description: "XFS Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 20973568, + size: 1073741824, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part3"], + isEFI: false, + filesystem: { + sid: 92, + type: "xfs", + }, }, { - "sid": 81, - "name": "/dev/vdc4", - "description": "Linux", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 23070720, - "size": 2147483648, - "shrinking": { "supported": 2147483136 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part4" - ], - "isEFI": false - } + sid: 81, + name: "/dev/vdc4", + description: "Linux", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 23070720, + size: 2147483648, + shrinking: { supported: 2147483136 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part4"], + isEFI: false, + }, ], - "unpartitionedSize": 18253611008, - "unusedSlots": [ + unpartitionedSize: 18253611008, + unusedSlots: [ { - "start": 27265024, - "size": 18252545536 - } - ] - } - } - ] + start: 27265024, + size: 18252545536, + }, + ], + }, + }, + ], }; export const devices = { - "system": [ + system: [ { - "sid": 71, - "name": "/dev/vda", - "description": "Disk", - "isDrive": true, - "type": "disk", - "vendor": "", - "model": "Disk", - "driver": [ - "virtio-pci", - "virtio_blk" - ], - "bus": "None", - "busId": "", - "transport": "unknown", - "sdCard": false, - "dellBOSS": false, - "active": true, - "encrypted": false, - "start": 0, - "size": 53687091200, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0" - ], - "partitionTable": { - "type": "gpt", - "partitions": [ + sid: 71, + name: "/dev/vda", + description: "Disk", + isDrive: true, + type: "disk", + vendor: "", + model: "Disk", + driver: ["virtio-pci", "virtio_blk"], + bus: "None", + busId: "", + transport: "unknown", + sdCard: false, + dellBOSS: false, + active: true, + encrypted: false, + start: 0, + size: 53687091200, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0"], + partitionTable: { + type: "gpt", + partitions: [ { - "sid": 83, - "name": "/dev/vda1", - "description": "BIOS Boot Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 8388608, - "shrinking": { "supported": 8388096 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part1" - ], - "isEFI": false + sid: 83, + name: "/dev/vda1", + description: "BIOS Boot Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 8388608, + shrinking: { supported: 8388096 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part1"], + isEFI: false, }, { - "sid": 84, - "name": "/dev/vda2", - "description": "PV of system", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 53677637120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "physical_volume", - "deviceNames": [ - "/dev/system" - ] - } - } + sid: 84, + name: "/dev/vda2", + description: "PV of system", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 53677637120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part2"], + isEFI: false, + component: { + type: "physical_volume", + deviceNames: ["/dev/system"], + }, + }, ], - "unpartitionedSize": 1065472, - "unusedSlots": [] - } + unpartitionedSize: 1065472, + unusedSlots: [], + }, }, { - "sid": 69, - "name": "/dev/vdb", - "description": "Ext4 Disk", - "isDrive": true, - "type": "disk", - "vendor": "", - "model": "Disk", - "driver": [ - "virtio-pci", - "virtio_blk" - ], - "bus": "None", - "busId": "", - "transport": "unknown", - "sdCard": false, - "dellBOSS": false, - "active": true, - "encrypted": false, - "start": 0, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:08:00.0" - ], - "filesystem": { - "sid": 87, - "type": "ext4" - } + sid: 69, + name: "/dev/vdb", + description: "Ext4 Disk", + isDrive: true, + type: "disk", + vendor: "", + model: "Disk", + driver: ["virtio-pci", "virtio_blk"], + bus: "None", + busId: "", + transport: "unknown", + sdCard: false, + dellBOSS: false, + active: true, + encrypted: false, + start: 0, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:08:00.0"], + filesystem: { + sid: 87, + type: "ext4", + }, }, { - "sid": 70, - "name": "/dev/vdc", - "description": "Disk", - "isDrive": true, - "type": "disk", - "vendor": "", - "model": "Disk", - "driver": [ - "virtio-pci", - "virtio_blk" - ], - "bus": "None", - "busId": "", - "transport": "unknown", - "sdCard": false, - "dellBOSS": false, - "active": true, - "encrypted": false, - "start": 0, - "size": 32212254720, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0" - ], - "partitionTable": { - "type": "gpt", - "partitions": [ + sid: 70, + name: "/dev/vdc", + description: "Disk", + isDrive: true, + type: "disk", + vendor: "", + model: "Disk", + driver: ["virtio-pci", "virtio_blk"], + bus: "None", + busId: "", + transport: "unknown", + sdCard: false, + dellBOSS: false, + active: true, + encrypted: false, + start: 0, + size: 32212254720, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0"], + partitionTable: { + type: "gpt", + partitions: [ { - "sid": 78, - "name": "/dev/vdc1", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part1" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } + sid: 78, + name: "/dev/vdc1", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part1"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, }, { - "sid": 79, - "name": "/dev/vdc2", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 10487808, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [ - "openSUSE Leap 15.2", - "Fedora 10.30" - ], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } + sid: 79, + name: "/dev/vdc2", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 10487808, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: ["openSUSE Leap 15.2", "Fedora 10.30"], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part2"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, }, { - "sid": 80, - "name": "/dev/vdc3", - "description": "XFS Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 20973568, - "size": 1073741824, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part3" - ], - "isEFI": false, - "filesystem": { - "sid": 92, - "type": "xfs" - } + sid: 80, + name: "/dev/vdc3", + description: "XFS Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 20973568, + size: 1073741824, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part3"], + isEFI: false, + filesystem: { + sid: 92, + type: "xfs", + }, }, { - "sid": 81, - "name": "/dev/vdc4", - "description": "Linux", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 23070720, - "size": 2147483648, - "shrinking": { "supported": 2147483136 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part4" - ], - "isEFI": false - } + sid: 81, + name: "/dev/vdc4", + description: "Linux", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 23070720, + size: 2147483648, + shrinking: { supported: 2147483136 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part4"], + isEFI: false, + }, ], - "unpartitionedSize": 18253611008, - "unusedSlots": [ + unpartitionedSize: 18253611008, + unusedSlots: [ { - "start": 27265024, - "size": 18252545536 - } - ] - } + start: 27265024, + size: 18252545536, + }, + ], + }, }, { - "sid": 72, - "name": "/dev/md0", - "description": "Disk", - "isDrive": false, - "type": "md", - "level": "raid0", - "uuid": "644aeee1:5f5b946a:4da99758:3f85b3ea", - "devices": [ + sid: 72, + name: "/dev/md0", + description: "Disk", + isDrive: false, + type: "md", + level: "raid0", + uuid: "644aeee1:5f5b946a:4da99758:3f85b3ea", + devices: [ { - "sid": 78, - "name": "/dev/vdc1", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part1" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } + sid: 78, + name: "/dev/vdc1", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part1"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, }, { - "sid": 79, - "name": "/dev/vdc2", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 10487808, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [ - "openSUSE Leap 15.2", - "Fedora 10.30" - ], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } - } - ], - "active": true, - "encrypted": false, - "start": 0, - "size": 10737287168, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [ - "md-uuid-644aeee1:5f5b946a:4da99758:3f85b3ea" + sid: 79, + name: "/dev/vdc2", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 10487808, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: ["openSUSE Leap 15.2", "Fedora 10.30"], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part2"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, + }, ], - "udevPaths": [], - "partitionTable": { - "type": "gpt", - "partitions": [ + active: true, + encrypted: false, + start: 0, + size: 10737287168, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: ["md-uuid-644aeee1:5f5b946a:4da99758:3f85b3ea"], + udevPaths: [], + partitionTable: { + type: "gpt", + partitions: [ { - "sid": 86, - "name": "/dev/md0p1", - "description": "Ext4 Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 2147483648, - "shrinking": { "supported": 2040147968 }, - "systems": [], - "udevIds": [ - "md-uuid-644aeee1:5f5b946a:4da99758:3f85b3ea-part1" - ], - "udevPaths": [], - "isEFI": false, - "filesystem": { - "sid": 93, - "type": "ext4" - } - } + sid: 86, + name: "/dev/md0p1", + description: "Ext4 Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 2147483648, + shrinking: { supported: 2040147968 }, + systems: [], + udevIds: ["md-uuid-644aeee1:5f5b946a:4da99758:3f85b3ea-part1"], + udevPaths: [], + isEFI: false, + filesystem: { + sid: 93, + type: "ext4", + }, + }, ], - "unpartitionedSize": 8589803520, - "unusedSlots": [ + unpartitionedSize: 8589803520, + unusedSlots: [ { - "start": 4196352, - "size": 8588738048 - } - ] - } + start: 4196352, + size: 8588738048, + }, + ], + }, }, { - "sid": 73, - "name": "/dev/system", - "description": "LVM", - "isDrive": false, - "type": "lvmVg", - "size": 53674508288, - "physicalVolumes": [ + sid: 73, + name: "/dev/system", + description: "LVM", + isDrive: false, + type: "lvmVg", + size: 53674508288, + physicalVolumes: [ { - "sid": 84, - "name": "/dev/vda2", - "description": "PV of system", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 53677637120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "physical_volume", - "deviceNames": [ - "/dev/system" - ] - } - } + sid: 84, + name: "/dev/vda2", + description: "PV of system", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 53677637120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part2"], + isEFI: false, + component: { + type: "physical_volume", + deviceNames: ["/dev/system"], + }, + }, ], - "logicalVolumes": [ + logicalVolumes: [ { - "sid": 75, - "name": "/dev/system/root", - "description": "Ext4 LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 51527024640, - "shrinking": { "supported": 30647779328 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 88, - "type": "ext4", - "mountPath": "/" - } + sid: 75, + name: "/dev/system/root", + description: "Ext4 LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 51527024640, + shrinking: { supported: 30647779328 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 88, + type: "ext4", + mountPath: "/", + }, }, { - "sid": 76, - "name": "/dev/system/swap", - "description": "Swap LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 2147483648, - "shrinking": { "supported": 2143289344 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 90, - "type": "swap", - "mountPath": "swap" - } - } - ] + sid: 76, + name: "/dev/system/swap", + description: "Swap LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 2147483648, + shrinking: { supported: 2143289344 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 90, + type: "swap", + mountPath: "swap", + }, + }, + ], }, { - "sid": 75, - "name": "/dev/system/root", - "description": "Ext4 LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 51527024640, - "shrinking": { "supported": 30647779328 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 88, - "type": "ext4", - "mountPath": "/" - } + sid: 75, + name: "/dev/system/root", + description: "Ext4 LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 51527024640, + shrinking: { supported: 30647779328 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 88, + type: "ext4", + mountPath: "/", + }, }, { - "sid": 76, - "name": "/dev/system/swap", - "description": "Swap LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 2147483648, - "shrinking": { "supported": 2143289344 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 90, - "type": "swap", - "mountPath": "swap" - } + sid: 76, + name: "/dev/system/swap", + description: "Swap LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 2147483648, + shrinking: { supported: 2143289344 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 90, + type: "swap", + mountPath: "swap", + }, }, { - "sid": 83, - "name": "/dev/vda1", - "description": "BIOS Boot Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 8388608, - "shrinking": { "supported": 8388096 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part1" - ], - "isEFI": false + sid: 83, + name: "/dev/vda1", + description: "BIOS Boot Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 8388608, + shrinking: { supported: 8388096 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part1"], + isEFI: false, }, { - "sid": 84, - "name": "/dev/vda2", - "description": "PV of system", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 53677637120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "physical_volume", - "deviceNames": [ - "/dev/system" - ] - } + sid: 84, + name: "/dev/vda2", + description: "PV of system", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 53677637120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part2"], + isEFI: false, + component: { + type: "physical_volume", + deviceNames: ["/dev/system"], + }, }, { - "sid": 78, - "name": "/dev/vdc1", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part1" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } + sid: 78, + name: "/dev/vdc1", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part1"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, }, { - "sid": 79, - "name": "/dev/vdc2", - "description": "Part of md0", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 10487808, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [ - "openSUSE Leap 15.2", - "Fedora 10.30" - ], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "md_device", - "deviceNames": [ - "/dev/md0" - ] - } + sid: 79, + name: "/dev/vdc2", + description: "Part of md0", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 10487808, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: ["openSUSE Leap 15.2", "Fedora 10.30"], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part2"], + isEFI: false, + component: { + type: "md_device", + deviceNames: ["/dev/md0"], + }, }, { - "sid": 80, - "name": "/dev/vdc3", - "description": "XFS Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 20973568, - "size": 1073741824, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part3" - ], - "isEFI": false, - "filesystem": { - "sid": 92, - "type": "xfs" - } + sid: 80, + name: "/dev/vdc3", + description: "XFS Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 20973568, + size: 1073741824, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part3"], + isEFI: false, + filesystem: { + sid: 92, + type: "xfs", + }, }, { - "sid": 81, - "name": "/dev/vdc4", - "description": "Linux", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 23070720, - "size": 2147483648, - "shrinking": { "supported": 2147483136 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part4" - ], - "isEFI": false + sid: 81, + name: "/dev/vdc4", + description: "Linux", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 23070720, + size: 2147483648, + shrinking: { supported: 2147483136 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part4"], + isEFI: false, }, { - "sid": 86, - "name": "/dev/md0p1", - "description": "Ext4 Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 2147483648, - "shrinking": { "supported": 2040147968 }, - "systems": [], - "udevIds": [ - "md-uuid-644aeee1:5f5b946a:4da99758:3f85b3ea-part1" - ], - "udevPaths": [], - "isEFI": false, - "filesystem": { - "sid": 93, - "type": "ext4" - } - } + sid: 86, + name: "/dev/md0p1", + description: "Ext4 Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 2147483648, + shrinking: { supported: 2040147968 }, + systems: [], + udevIds: ["md-uuid-644aeee1:5f5b946a:4da99758:3f85b3ea-part1"], + udevPaths: [], + isEFI: false, + filesystem: { + sid: 93, + type: "ext4", + }, + }, ], - "staging": [ + staging: [ { - "sid": 71, - "name": "/dev/vda", - "description": "Disk", - "isDrive": true, - "type": "disk", - "vendor": "", - "model": "Disk", - "driver": [ - "virtio-pci", - "virtio_blk" - ], - "bus": "None", - "busId": "", - "transport": "unknown", - "sdCard": false, - "dellBOSS": false, - "active": true, - "encrypted": false, - "start": 0, - "size": 53687091200, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0" - ], - "partitionTable": { - "type": "gpt", - "partitions": [ + sid: 71, + name: "/dev/vda", + description: "Disk", + isDrive: true, + type: "disk", + vendor: "", + model: "Disk", + driver: ["virtio-pci", "virtio_blk"], + bus: "None", + busId: "", + transport: "unknown", + sdCard: false, + dellBOSS: false, + active: true, + encrypted: false, + start: 0, + size: 53687091200, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0"], + partitionTable: { + type: "gpt", + partitions: [ { - "sid": 83, - "name": "/dev/vda1", - "description": "BIOS Boot Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 8388608, - "shrinking": { "supported": 8388096 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part1" - ], - "isEFI": false + sid: 83, + name: "/dev/vda1", + description: "BIOS Boot Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 8388608, + shrinking: { supported: 8388096 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part1"], + isEFI: false, }, { - "sid": 84, - "name": "/dev/vda2", - "description": "PV of system", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 53677637120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "physical_volume", - "deviceNames": [ - "/dev/system" - ] - } - } + sid: 84, + name: "/dev/vda2", + description: "PV of system", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 53677637120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part2"], + isEFI: false, + component: { + type: "physical_volume", + deviceNames: ["/dev/system"], + }, + }, ], - "unpartitionedSize": 1065472, - "unusedSlots": [] - } + unpartitionedSize: 1065472, + unusedSlots: [], + }, }, { - "sid": 69, - "name": "/dev/vdb", - "description": "Ext4 Disk", - "isDrive": true, - "type": "disk", - "vendor": "", - "model": "Disk", - "driver": [ - "virtio-pci", - "virtio_blk" - ], - "bus": "None", - "busId": "", - "transport": "unknown", - "sdCard": false, - "dellBOSS": false, - "active": true, - "encrypted": false, - "start": 0, - "size": 5368709120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:08:00.0" - ], - "filesystem": { - "sid": 87, - "type": "ext4" - } + sid: 69, + name: "/dev/vdb", + description: "Ext4 Disk", + isDrive: true, + type: "disk", + vendor: "", + model: "Disk", + driver: ["virtio-pci", "virtio_blk"], + bus: "None", + busId: "", + transport: "unknown", + sdCard: false, + dellBOSS: false, + active: true, + encrypted: false, + start: 0, + size: 5368709120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:08:00.0"], + filesystem: { + sid: 87, + type: "ext4", + }, }, { - "sid": 70, - "name": "/dev/vdc", - "description": "Disk", - "isDrive": true, - "type": "disk", - "vendor": "", - "model": "Disk", - "driver": [ - "virtio-pci", - "virtio_blk" - ], - "bus": "None", - "busId": "", - "transport": "unknown", - "sdCard": false, - "dellBOSS": false, - "active": true, - "encrypted": false, - "start": 0, - "size": 32212254720, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0" - ], - "partitionTable": { - "type": "gpt", - "partitions": [ + sid: 70, + name: "/dev/vdc", + description: "Disk", + isDrive: true, + type: "disk", + vendor: "", + model: "Disk", + driver: ["virtio-pci", "virtio_blk"], + bus: "None", + busId: "", + transport: "unknown", + sdCard: false, + dellBOSS: false, + active: true, + encrypted: false, + start: 0, + size: 32212254720, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0"], + partitionTable: { + type: "gpt", + partitions: [ { - "sid": 79, - "name": "/dev/vdc2", - "description": "Linux RAID", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 10487808, - "size": 5368709120, - "shrinking": { "supported": 5368708608 }, - "systems": [ - "openSUSE Leap 15.2", - "Fedora 10.30" - ], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part2" - ], - "isEFI": false + sid: 79, + name: "/dev/vdc2", + description: "Linux RAID", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 10487808, + size: 5368709120, + shrinking: { supported: 5368708608 }, + systems: ["openSUSE Leap 15.2", "Fedora 10.30"], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part2"], + isEFI: false, }, { - "sid": 81, - "name": "/dev/vdc4", - "description": "Linux", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 23070720, - "size": 1608515584, - "shrinking": { "supported": 1608515072 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part4" - ], - "isEFI": false + sid: 81, + name: "/dev/vdc4", + description: "Linux", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 23070720, + size: 1608515584, + shrinking: { supported: 1608515072 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part4"], + isEFI: false, }, { - "sid": 459, - "name": "/dev/vdc1", - "description": "BIOS Boot Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 8388608, - "shrinking": { "supported": 8388096 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part1" - ], - "isEFI": false + sid: 459, + name: "/dev/vdc1", + description: "BIOS Boot Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 8388608, + shrinking: { supported: 8388096 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part1"], + isEFI: false, }, { - "sid": 460, - "name": "/dev/vdc3", - "description": "Swap Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 1610612736, - "shrinking": { "supported": 1610571776 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part3" - ], - "isEFI": false, - "filesystem": { - "sid": 461, - "type": "swap", - "mountPath": "swap" - } + sid: 460, + name: "/dev/vdc3", + description: "Swap Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 1610612736, + shrinking: { supported: 1610571776 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part3"], + isEFI: false, + filesystem: { + sid: 461, + type: "swap", + mountPath: "swap", + }, }, { - "sid": 463, - "name": "/dev/vdc5", - "description": "Btrfs Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 26212352, - "size": 18791513600, - "shrinking": { "supported": 18523078144 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part5" - ], - "isEFI": false, - "filesystem": { - "sid": 464, - "type": "btrfs", - "mountPath": "/" - } - } + sid: 463, + name: "/dev/vdc5", + description: "Btrfs Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 26212352, + size: 18791513600, + shrinking: { supported: 18523078144 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part5"], + isEFI: false, + filesystem: { + sid: 464, + type: "btrfs", + mountPath: "/", + }, + }, ], - "unpartitionedSize": 4824515072, - "unusedSlots": [ + unpartitionedSize: 4824515072, + unusedSlots: [ { - "start": 3164160, - "size": 3749707776 + start: 3164160, + size: 3749707776, }, { - "start": 20973568, - "size": 1073741824 - } - ] - } + start: 20973568, + size: 1073741824, + }, + ], + }, }, { - "sid": 73, - "name": "/dev/system", - "description": "LVM", - "isDrive": false, - "type": "lvmVg", - "size": 53674508288, - "physicalVolumes": [ + sid: 73, + name: "/dev/system", + description: "LVM", + isDrive: false, + type: "lvmVg", + size: 53674508288, + physicalVolumes: [ { - "sid": 84, - "name": "/dev/vda2", - "description": "PV of system", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 53677637120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "physical_volume", - "deviceNames": [ - "/dev/system" - ] - } - } + sid: 84, + name: "/dev/vda2", + description: "PV of system", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 53677637120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part2"], + isEFI: false, + component: { + type: "physical_volume", + deviceNames: ["/dev/system"], + }, + }, ], - "logicalVolumes": [ + logicalVolumes: [ { - "sid": 75, - "name": "/dev/system/root", - "description": "Ext4 LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 51527024640, - "shrinking": { "supported": 30647779328 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 88, - "type": "ext4", - "mountPath": "/" - } + sid: 75, + name: "/dev/system/root", + description: "Ext4 LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 51527024640, + shrinking: { supported: 30647779328 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 88, + type: "ext4", + mountPath: "/", + }, }, { - "sid": 76, - "name": "/dev/system/swap", - "description": "Swap LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 2147483648, - "shrinking": { "supported": 2143289344 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 90, - "type": "swap", - "mountPath": "swap" - } - } - ] + sid: 76, + name: "/dev/system/swap", + description: "Swap LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 2147483648, + shrinking: { supported: 2143289344 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 90, + type: "swap", + mountPath: "swap", + }, + }, + ], }, { - "sid": 75, - "name": "/dev/system/root", - "description": "Ext4 LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 51527024640, - "shrinking": { "supported": 30647779328 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 88, - "type": "ext4", - "mountPath": "/" - } + sid: 75, + name: "/dev/system/root", + description: "Ext4 LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 51527024640, + shrinking: { supported: 30647779328 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 88, + type: "ext4", + mountPath: "/", + }, }, { - "sid": 76, - "name": "/dev/system/swap", - "description": "Swap LV", - "isDrive": false, - "type": "lvmLv", - "active": true, - "encrypted": false, - "start": 0, - "size": 2147483648, - "shrinking": { "supported": 2143289344 }, - "systems": [], - "udevIds": [], - "udevPaths": [], - "filesystem": { - "sid": 90, - "type": "swap", - "mountPath": "swap" - } + sid: 76, + name: "/dev/system/swap", + description: "Swap LV", + isDrive: false, + type: "lvmLv", + active: true, + encrypted: false, + start: 0, + size: 2147483648, + shrinking: { supported: 2143289344 }, + systems: [], + udevIds: [], + udevPaths: [], + filesystem: { + sid: 90, + type: "swap", + mountPath: "swap", + }, }, { - "sid": 83, - "name": "/dev/vda1", - "description": "BIOS Boot Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 8388608, - "shrinking": { "supported": 8388096 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part1" - ], - "isEFI": false + sid: 83, + name: "/dev/vda1", + description: "BIOS Boot Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 8388608, + shrinking: { supported: 8388096 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part1"], + isEFI: false, }, { - "sid": 84, - "name": "/dev/vda2", - "description": "PV of system", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 53677637120, - "shrinking": { "unsupported": ["Resizing is not supported"] }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:04:00.0-part2" - ], - "isEFI": false, - "component": { - "type": "physical_volume", - "deviceNames": [ - "/dev/system" - ] - } + sid: 84, + name: "/dev/vda2", + description: "PV of system", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 53677637120, + shrinking: { unsupported: ["Resizing is not supported"] }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:04:00.0-part2"], + isEFI: false, + component: { + type: "physical_volume", + deviceNames: ["/dev/system"], + }, }, { - "sid": 79, - "name": "/dev/vdc2", - "description": "Linux RAID", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 10487808, - "size": 5368709120, - "shrinking": { "supported": 5368708608 }, - "systems": [ - "openSUSE Leap 15.2", - "Fedora 10.30" - ], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part2" - ], - "isEFI": false + sid: 79, + name: "/dev/vdc2", + description: "Linux RAID", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 10487808, + size: 5368709120, + shrinking: { supported: 5368708608 }, + systems: ["openSUSE Leap 15.2", "Fedora 10.30"], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part2"], + isEFI: false, }, { - "sid": 81, - "name": "/dev/vdc4", - "description": "Linux", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 23070720, - "size": 1608515584, - "shrinking": { "supported": 1608515072 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part4" - ], - "isEFI": false + sid: 81, + name: "/dev/vdc4", + description: "Linux", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 23070720, + size: 1608515584, + shrinking: { supported: 1608515072 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part4"], + isEFI: false, }, { - "sid": 459, - "name": "/dev/vdc1", - "description": "BIOS Boot Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 2048, - "size": 8388608, - "shrinking": { "supported": 8388096 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part1" - ], - "isEFI": false + sid: 459, + name: "/dev/vdc1", + description: "BIOS Boot Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 2048, + size: 8388608, + shrinking: { supported: 8388096 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part1"], + isEFI: false, }, { - "sid": 460, - "name": "/dev/vdc3", - "description": "Swap Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 18432, - "size": 1610612736, - "shrinking": { "supported": 1610571776 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part3" - ], - "isEFI": false, - "filesystem": { - "sid": 461, - "type": "swap", - "mountPath": "swap" - } + sid: 460, + name: "/dev/vdc3", + description: "Swap Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 18432, + size: 1610612736, + shrinking: { supported: 1610571776 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part3"], + isEFI: false, + filesystem: { + sid: 461, + type: "swap", + mountPath: "swap", + }, }, { - "sid": 463, - "name": "/dev/vdc5", - "description": "Btrfs Partition", - "isDrive": false, - "type": "partition", - "active": true, - "encrypted": false, - "start": 26212352, - "size": 18791513600, - "shrinking": { "supported": 18523078144 }, - "systems": [], - "udevIds": [], - "udevPaths": [ - "pci-0000:09:00.0-part5" - ], - "isEFI": false, - "filesystem": { - "sid": 464, - "type": "btrfs", - "mountPath": "/" - } - } - ] + sid: 463, + name: "/dev/vdc5", + description: "Btrfs Partition", + isDrive: false, + type: "partition", + active: true, + encrypted: false, + start: 26212352, + size: 18791513600, + shrinking: { supported: 18523078144 }, + systems: [], + udevIds: [], + udevPaths: ["pci-0000:09:00.0-part5"], + isEFI: false, + filesystem: { + sid: 464, + type: "btrfs", + mountPath: "/", + }, + }, + ], }; export const actions = [ { - "device": 86, - "text": "Delete partition /dev/md0p1 (2.00 GiB)", - "subvol": false, - "delete": true + device: 86, + text: "Delete partition /dev/md0p1 (2.00 GiB)", + subvol: false, + delete: true, }, { - "device": 72, - "text": "Delete RAID0 /dev/md0 (10.00 GiB)", - "subvol": false, - "delete": true + device: 72, + text: "Delete RAID0 /dev/md0 (10.00 GiB)", + subvol: false, + delete: true, }, { - "device": 80, - "text": "Delete partition /dev/vdc3 (1.00 GiB)", - "subvol": false, - "delete": true + device: 80, + text: "Delete partition /dev/vdc3 (1.00 GiB)", + subvol: false, + delete: true, }, { - "device": 78, - "text": "Delete partition /dev/vdc1 (5.00 GiB)", - "subvol": false, - "delete": true + device: 78, + text: "Delete partition /dev/vdc1 (5.00 GiB)", + subvol: false, + delete: true, }, { - "device": 81, - "text": "Shrink partition /dev/vdc4 from 2.00 GiB to 1.50 GiB", - "subvol": false, - "delete": false + device: 81, + text: "Shrink partition /dev/vdc4 from 2.00 GiB to 1.50 GiB", + subvol: false, + delete: false, }, { - "device": 459, - "text": "Create partition /dev/vdc1 (8.00 MiB) as BIOS Boot Partition", - "subvol": false, - "delete": false + device: 459, + text: "Create partition /dev/vdc1 (8.00 MiB) as BIOS Boot Partition", + subvol: false, + delete: false, }, { - "device": 460, - "text": "Create partition /dev/vdc3 (1.50 GiB) for swap", - "subvol": false, - "delete": false + device: 460, + text: "Create partition /dev/vdc3 (1.50 GiB) for swap", + subvol: false, + delete: false, }, { - "device": 463, - "text": "Create partition /dev/vdc5 (17.50 GiB) for / with btrfs", - "subvol": false, - "delete": false + device: 463, + text: "Create partition /dev/vdc5 (17.50 GiB) for / with btrfs", + subvol: false, + delete: false, }, { - "device": 467, - "text": "Create subvolume @ on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 467, + text: "Create subvolume @ on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 482, - "text": "Create subvolume @/boot/grub2/x86_64-efi on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 482, + text: "Create subvolume @/boot/grub2/x86_64-efi on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 480, - "text": "Create subvolume @/boot/grub2/i386-pc on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 480, + text: "Create subvolume @/boot/grub2/i386-pc on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 478, - "text": "Create subvolume @/var on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 478, + text: "Create subvolume @/var on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 476, - "text": "Create subvolume @/usr/local on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 476, + text: "Create subvolume @/usr/local on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 474, - "text": "Create subvolume @/srv on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 474, + text: "Create subvolume @/srv on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 472, - "text": "Create subvolume @/root on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 472, + text: "Create subvolume @/root on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 470, - "text": "Create subvolume @/opt on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false + device: 470, + text: "Create subvolume @/opt on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, }, { - "device": 468, - "text": "Create subvolume @/home on /dev/vdc5 (17.50 GiB)", - "subvol": true, - "delete": false - } + device: 468, + text: "Create subvolume @/home on /dev/vdc5 (17.50 GiB)", + subvol: true, + delete: false, + }, ]; diff --git a/web/src/components/storage/utils.js b/web/src/components/storage/utils.js index 8cb5e408f0..47d9367ec7 100644 --- a/web/src/components/storage/utils.js +++ b/web/src/components/storage/utils.js @@ -57,7 +57,7 @@ import { N_ } from "~/i18n"; const SIZE_METHODS = Object.freeze({ AUTO: "auto", MANUAL: "fixed", - RANGE: "range" + RANGE: "range", }); const SIZE_UNITS = Object.freeze({ @@ -79,8 +79,8 @@ const SPACE_POLICIES = [ summaryLabels: [ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence // would read as "Find space deleting current content". Keep it short - N_("deleting current content") - ] + N_("deleting current content"), + ], }, { id: "resize", @@ -89,8 +89,8 @@ const SPACE_POLICIES = [ summaryLabels: [ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence // would read as "Find space shrinking partitions". Keep it short. - N_("shrinking partitions") - ] + N_("shrinking partitions"), + ], }, { id: "keep", @@ -99,8 +99,8 @@ const SPACE_POLICIES = [ summaryLabels: [ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence // would read as "Find space without modifying any partition". Keep it short. - N_("without modifying any partition") - ] + N_("without modifying any partition"), + ], }, { id: "custom", @@ -109,9 +109,9 @@ const SPACE_POLICIES = [ summaryLabels: [ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence // would read as "Find space with custom actions". Keep it short. - N_("with custom actions") - ] - } + N_("with custom actions"), + ], + }, ]; /** @@ -130,7 +130,8 @@ const splitSize = (size) => { // From D-Bus, maxSize comes as undefined when set as "unlimited", but for Agama UI // it means "leave it empty" const sanitizedSize = size === undefined ? "" : size; - const parsedSize = typeof sanitizedSize === "string" ? sanitizedSize : xbytes(sanitizedSize, { iec: true }); + const parsedSize = + typeof sanitizedSize === "string" ? sanitizedSize : xbytes(sanitizedSize, { iec: true }); const [qty, unit] = parsedSize.split(" "); // `Number` will remove trailing zeroes; // parseFloat ensures Number does not transform "" into 0. @@ -138,7 +139,7 @@ const splitSize = (size) => { return { unit, - size: isNaN(sanitizedQty) ? undefined : sanitizedQty + size: isNaN(sanitizedQty) ? undefined : sanitizedQty, }; }; @@ -226,12 +227,12 @@ const deviceLabel = (device) => { const deviceChildren = (device) => { const partitionTableChildren = (partitionTable) => { const { partitions, unusedSlots } = partitionTable; - const children = partitions.concat(unusedSlots).filter(i => !!i); - return children.sort((a, b) => a.start < b.start ? -1 : 1); + const children = partitions.concat(unusedSlots).filter((i) => !!i); + return children.sort((a, b) => (a.start < b.start ? -1 : 1)); }; const lvmVgChildren = (lvmVg) => { - return lvmVg.logicalVolumes.sort((a, b) => a.name < b.name ? -1 : 1); + return lvmVg.logicalVolumes.sort((a, b) => (a.name < b.name ? -1 : 1)); }; if (device.partitionTable) return partitionTableChildren(device.partitionTable); @@ -283,7 +284,7 @@ const isTransactionalRoot = (volume) => { * @returns {boolean} */ const isTransactionalSystem = (volumes = []) => { - return volumes.find(v => isTransactionalRoot(v)) !== undefined; + return volumes.find((v) => isTransactionalRoot(v)) !== undefined; }; /** @@ -311,13 +312,13 @@ const reuseDevice = (volume) => volume.target === "FILESYSTEM" || volume.target * @param {Volume} volume * @returns {string} */ -const volumeLabel = (volume) => volume.mountPath === "/" ? "root" : volume.mountPath; +const volumeLabel = (volume) => (volume.mountPath === "/" ? "root" : volume.mountPath); /** * GiB to Bytes. * * @type {(value: number) => number } */ -const gib = (value) => value * (1024 ** 3); +const gib = (value) => value * 1024 ** 3; export { DEFAULT_SIZE_UNIT, @@ -337,5 +338,5 @@ export { isTransactionalSystem, mountFilesystem, reuseDevice, - volumeLabel + volumeLabel, }; diff --git a/web/src/components/storage/utils.test.js b/web/src/components/storage/utils.test.js index 72359504c3..0f07410a00 100644 --- a/web/src/components/storage/utils.test.js +++ b/web/src/components/storage/utils.test.js @@ -31,7 +31,7 @@ import { hasFS, hasSnapshots, isTransactionalRoot, - isTransactionalSystem + isTransactionalSystem, } from "./utils"; /** @@ -64,8 +64,8 @@ const volume = (properties = {}) => { snapshotsAffectSizes: false, sizeRelevantVolumes: [], adjustByRam: false, - productDefined: false - } + productDefined: false, + }, }; return { ...testVolume, ...properties }; @@ -87,7 +87,7 @@ const sda = { name: "/dev/sda", description: "", size: 1024, - systems : [], + systems: [], udevIds: [], udevPaths: [], }; @@ -104,10 +104,10 @@ const sda1 = { start: 123, encrypted: false, shrinking: { supported: 128 }, - systems : [], + systems: [], udevIds: [], udevPaths: [], - isEFI: false + isEFI: false, }; /** @type {StorageDevice} */ @@ -122,10 +122,10 @@ const sda2 = { start: 1789, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], udevPaths: [], - isEFI: false + isEFI: false, }; sda.partitionTable = { @@ -134,8 +134,8 @@ sda.partitionTable = { unpartitionedSize: 0, unusedSlots: [ { start: 1, size: 1024 }, - { start: 2345, size: 512 } - ] + { start: 2345, size: 512 }, + ], }; /** @type {StorageDevice} */ @@ -145,7 +145,7 @@ const lvmVg = { type: "lvmVg", name: "/dev/vg0", description: "LVM", - size: 512 + size: 512, }; /** @type {StorageDevice} */ @@ -160,9 +160,9 @@ const lvmLv1 = { start: 0, encrypted: false, shrinking: { unsupported: ["Resizing is not supported"] }, - systems : [], + systems: [], udevIds: [], - udevPaths: [] + udevPaths: [], }; lvmVg.logicalVolumes = [lvmLv1]; @@ -209,8 +209,8 @@ describe("deviceChildren", () => { it("returns the partitions and unused slots", () => { const children = deviceChildren(device); expect(children.length).toEqual(4); - device.partitionTable.partitions.forEach(p => expect(children).toContainEqual(p)); - device.partitionTable.unusedSlots.forEach(s => expect(children).toContainEqual(s)); + device.partitionTable.partitions.forEach((p) => expect(children).toContainEqual(p)); + device.partitionTable.unusedSlots.forEach((s) => expect(children).toContainEqual(s)); }); }); @@ -222,7 +222,7 @@ describe("deviceChildren", () => { it("returns the logical volumes", () => { const children = deviceChildren(device); expect(children.length).toEqual(1); - device.logicalVolumes.forEach(l => expect(children).toContainEqual(l)); + device.logicalVolumes.forEach((l) => expect(children).toContainEqual(l)); }); }); @@ -333,7 +333,7 @@ describe("isTransactionalSystem", () => { const volumes = [ volume({ mountPath: "/" }), - volume({ mountPath: "/home", transactional: true }) + volume({ mountPath: "/home", transactional: true }), ]; expect(isTransactionalSystem(volumes)).toBe(false); }); @@ -341,7 +341,7 @@ describe("isTransactionalSystem", () => { it("returns true if volumes includes a transactional root", () => { const volumes = [ volume({ mountPath: "EXT4" }), - volume({ mountPath: "/", transactional: true }) + volume({ mountPath: "/", transactional: true }), ]; expect(isTransactionalSystem(volumes)).toBe(true); }); diff --git a/web/src/components/users/FirstUser.jsx b/web/src/components/users/FirstUser.jsx index 4d16709c20..d0677714c9 100644 --- a/web/src/components/users/FirstUser.jsx +++ b/web/src/components/users/FirstUser.jsx @@ -21,9 +21,9 @@ import React, { useState, useEffect } from "react"; import { Skeleton, Split, Stack } from "@patternfly/react-core"; -import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; +import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; import { useNavigate } from "react-router-dom"; -import { RowActions, ButtonLink } from '~/components/core'; +import { RowActions, ButtonLink } from "~/components/core"; import { _ } from "~/i18n"; import { useCancellablePromise } from "~/utils"; import { useInstallerClient } from "~/context/installer"; @@ -35,11 +35,15 @@ const UserNotDefined = ({ actionCb }) => {
{_("No user defined yet.")}
- {_("Please, be aware that a user must be defined before installing the system to be able to log into it.")} + {_( + "Please, be aware that a user must be defined before installing the system to be able to log into it.", + )}
- {_("Define a user now")} + + {_("Define a user now")} + @@ -83,14 +87,14 @@ export default function FirstUser() { const [isLoading, setIsLoading] = useState(true); useEffect(() => { - cancellablePromise(client.users.getUser()).then(userValues => { + cancellablePromise(client.users.getUser()).then((userValues) => { setUser(userValues); setIsLoading(false); }); }, [client.users, cancellablePromise]); useEffect(() => { - return client.users.onUsersChange(changes => { + return client.users.onUsersChange((changes) => { if (changes.firstUser !== undefined) { setUser(changes.firstUser); } @@ -114,13 +118,13 @@ export default function FirstUser() { const actions = [ { title: _("Edit"), - onClick: () => navigate('/users/first/edit') + onClick: () => navigate("/users/first/edit"), }, { title: _("Discard"), onClick: remove, - isDanger: true - } + isDanger: true, + }, ]; if (isLoading) { diff --git a/web/src/components/users/FirstUserForm.jsx b/web/src/components/users/FirstUserForm.jsx index 94c9c36220..9915c2d980 100644 --- a/web/src/components/users/FirstUserForm.jsx +++ b/web/src/components/users/FirstUserForm.jsx @@ -23,22 +23,33 @@ import React, { useState, useEffect, useRef } from "react"; import { Alert, Checkbox, - Form, FormGroup, + Form, + FormGroup, TextInput, - Menu, MenuContent, MenuList, MenuItem, - Grid, GridItem, + Menu, + MenuContent, + MenuList, + MenuItem, + Grid, + GridItem, Stack, - Switch + Switch, } from "@patternfly/react-core"; import { useNavigate } from "react-router-dom"; import { Loading } from "~/components/layout"; -import { PasswordAndConfirmationInput, Page } from '~/components/core'; +import { PasswordAndConfirmationInput, Page } from "~/components/core"; import { _ } from "~/i18n"; import { useCancellablePromise } from "~/utils"; import { useInstallerClient } from "~/context/installer"; -import { suggestUsernames } from '~/components/users/utils'; +import { suggestUsernames } from "~/components/users/utils"; -const UsernameSuggestions = ({ isOpen = false, entries, onSelect, setInsideDropDown, focusedIndex = -1 }) => { +const UsernameSuggestions = ({ + isOpen = false, + entries, + onSelect, + setInsideDropDown, + focusedIndex = -1, +}) => { if (!isOpen) return; return ( @@ -57,7 +68,7 @@ const UsernameSuggestions = ({ isOpen = false, entries, onSelect, setInsideDropD isFocused={focusedIndex === index} onClick={() => onSelect(suggestion)} > - { /* TRANSLATORS: dropdown username suggestions */} + {/* TRANSLATORS: dropdown username suggestions */} {_("Use suggested username")} {suggestion} ))} @@ -85,12 +96,12 @@ export default function FirstUserForm() { const passwordRef = useRef(); useEffect(() => { - cancellablePromise(client.users.getUser()).then(userValues => { + cancellablePromise(client.users.getUser()).then((userValues) => { const editing = userValues.userName !== ""; setState({ load: true, user: userValues, - isEditing: editing + isEditing: editing, }); setChangePassword(!editing); }); @@ -136,7 +147,7 @@ export default function FirstUserForm() { } // FIXME: improve validations - if (Object.values(user).some(v => v === "")) { + if (Object.values(user).some((v) => v === "")) { setErrors([_("All fields are required")]); return; } @@ -165,24 +176,27 @@ export default function FirstUserForm() { const handleKeyDown = (e) => { switch (e.key) { - case 'ArrowDown': + case "ArrowDown": e.preventDefault(); // Prevent page scrolling renderSuggestions(e); setFocusedIndex((prevIndex) => (prevIndex + 1) % suggestions.length); break; - case 'ArrowUp': + case "ArrowUp": e.preventDefault(); // Prevent page scrolling renderSuggestions(e); - setFocusedIndex((prevIndex) => (prevIndex - (prevIndex === -1 ? 0 : 1) + suggestions.length) % suggestions.length); + setFocusedIndex( + (prevIndex) => + (prevIndex - (prevIndex === -1 ? 0 : 1) + suggestions.length) % suggestions.length, + ); break; - case 'Enter': + case "Enter": if (focusedIndex >= 0) { e.preventDefault(); onSuggestionSelected(suggestions[focusedIndex]); } break; - case 'Escape': - case 'Tab': + case "Escape": + case "Tab": setShowSuggestions(false); break; default: @@ -199,10 +213,13 @@ export default function FirstUserForm() {
- {errors.length > 0 && + {errors.length > 0 && ( - {errors.map((e, i) =>

{e}

)} -
} + {errors.map((e, i) => ( +

{e}

+ ))} + + )} @@ -249,12 +266,13 @@ export default function FirstUserForm() { - {state.isEditing && + {state.isEditing && ( setChangePassword(!changePassword)} - />} + /> + )} {
{_("No root authentication method defined yet.")}
- {_("Please, define at least one authentication method for logging into the system as root.")} + {_( + "Please, define at least one authentication method for logging into the system as root.", + )}
{/* TRANSLATORS: push button label */} - + {/* TRANSLATORS: push button label */} - +
); @@ -76,7 +82,7 @@ export default function RootAuthMethods() { }, [client, cancellablePromise]); useEffect(() => { - return client.onUsersChange(changes => { + return client.onUsersChange((changes) => { if (changes.rootPasswordSet !== undefined) setIsPasswordDefined(changes.rootPasswordSet); if (changes.rootSSHKey !== undefined) setSSHKey(changes.rootSSHKey); }); @@ -92,27 +98,25 @@ export default function RootAuthMethods() { const passwordActions = [ { title: isPasswordDefined ? _("Change") : _("Set"), - onClick: openPasswordForm - + onClick: openPasswordForm, }, isPasswordDefined && { title: _("Discard"), onClick: () => client.removeRootPassword(), - isDanger: true - } + isDanger: true, + }, ].filter(Boolean); const sshKeyActions = [ { title: isSSHKeyDefined ? _("Change") : _("Set"), - onClick: openSSHKeyForm + onClick: openSSHKeyForm, }, sshKey && { title: _("Discard"), onClick: () => client.setRootSSHKey(""), - isDanger: true - } - + isDanger: true, + }, ].filter(Boolean); if (isLoading) { @@ -125,9 +129,7 @@ export default function RootAuthMethods() { } const PasswordLabel = () => { - return isPasswordDefined - ? _("Already set") - : _("Not set"); + return isPasswordDefined ? _("Already set") : _("Not set"); }; const SSHKeyLabel = () => { @@ -161,14 +163,18 @@ export default function RootAuthMethods() { {_("Password")} - + + + {_("SSH Key")} - + + + @@ -181,20 +187,26 @@ export default function RootAuthMethods() { return ( <> - {isPasswordFormOpen && + {isPasswordFormOpen && ( } + /> + )} - {isSSHKeyFormOpen && + {isSSHKeyFormOpen && ( } + /> + )} ); } diff --git a/web/src/components/users/RootAuthMethods.test.jsx b/web/src/components/users/RootAuthMethods.test.jsx index 0b9af79247..bc41d28d5c 100644 --- a/web/src/components/users/RootAuthMethods.test.jsx +++ b/web/src/components/users/RootAuthMethods.test.jsx @@ -34,7 +34,7 @@ jest.mock("@patternfly/react-core", () => { return { ...original, - Skeleton: () =>
PFSkeleton
+ Skeleton: () =>
PFSkeleton
, }; }); @@ -56,8 +56,8 @@ beforeEach(() => { getRootSSHKey: getRootSSHKeyFn, setRootSSHKey: setRootSSHKeyFn, onUsersChange: onUsersChangeFn, - removeRootPassword: removeRootPasswordFn - } + removeRootPassword: removeRootPasswordFn, + }, }; }); }); @@ -123,8 +123,7 @@ describe("when ready", () => { installerRender(); const table = await screen.findByRole("grid"); - const passwordRow = within(table).getByText("Password") - .closest("tr"); + const passwordRow = within(table).getByText("Password").closest("tr"); within(passwordRow).getByText("Already set"); }); @@ -132,8 +131,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const passwordRow = within(table).getByText("Password") - .closest("tr"); + const passwordRow = within(table).getByText("Password").closest("tr"); const actionsToggler = within(passwordRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const setAction = within(passwordRow).queryByRole("menuitem", { name: "Set" }); @@ -144,8 +142,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const passwordRow = within(table).getByText("Password") - .closest("tr"); + const passwordRow = within(table).getByText("Password").closest("tr"); const actionsToggler = within(passwordRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const changeAction = await within(passwordRow).queryByRole("menuitem", { name: "Change" }); @@ -158,8 +155,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const passwordRow = within(table).getByText("Password") - .closest("tr"); + const passwordRow = within(table).getByText("Password").closest("tr"); const actionsToggler = within(passwordRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const discardAction = await within(passwordRow).queryByRole("menuitem", { name: "Discard" }); @@ -177,8 +173,7 @@ describe("when ready", () => { installerRender(); const table = await screen.findByRole("grid"); - const passwordRow = within(table).getByText("Password") - .closest("tr"); + const passwordRow = within(table).getByText("Password").closest("tr"); within(passwordRow).getByText("Not set"); }); @@ -186,8 +181,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const passwordRow = within(table).getByText("Password") - .closest("tr"); + const passwordRow = within(table).getByText("Password").closest("tr"); const actionsToggler = within(passwordRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const setAction = within(passwordRow).getByRole("menuitem", { name: "Set" }); @@ -199,8 +193,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const passwordRow = within(table).getByText("Password") - .closest("tr"); + const passwordRow = within(table).getByText("Password").closest("tr"); const actionsToggler = within(passwordRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); @@ -219,8 +212,7 @@ describe("when ready", () => { installerRender(); const table = await screen.findByRole("grid"); - const sshKeyRow = within(table).getByText("SSH Key") - .closest("tr"); + const sshKeyRow = within(table).getByText("SSH Key").closest("tr"); within(sshKeyRow).getByText("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDM+"); within(sshKeyRow).getByText("test@example"); }); @@ -229,8 +221,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const sshKeyRow = within(table).getByText("SSH Key") - .closest("tr"); + const sshKeyRow = within(table).getByText("SSH Key").closest("tr"); const actionsToggler = within(sshKeyRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const setAction = within(sshKeyRow).queryByRole("menuitem", { name: "Set" }); @@ -241,8 +232,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const sshKeyRow = within(table).getByText("SSH Key") - .closest("tr"); + const sshKeyRow = within(table).getByText("SSH Key").closest("tr"); const actionsToggler = within(sshKeyRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const changeAction = await within(sshKeyRow).queryByRole("menuitem", { name: "Change" }); @@ -255,8 +245,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const sshKeyRow = within(table).getByText("SSH Key") - .closest("tr"); + const sshKeyRow = within(table).getByText("SSH Key").closest("tr"); const actionsToggler = within(sshKeyRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const discardAction = await within(sshKeyRow).queryByRole("menuitem", { name: "Discard" }); @@ -274,8 +263,7 @@ describe("when ready", () => { installerRender(); const table = await screen.findByRole("grid"); - const sshKeyRow = within(table).getByText("SSH Key") - .closest("tr"); + const sshKeyRow = within(table).getByText("SSH Key").closest("tr"); within(sshKeyRow).getByText("Not set"); }); @@ -283,8 +271,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const sshKeyRow = within(table).getByText("SSH Key") - .closest("tr"); + const sshKeyRow = within(table).getByText("SSH Key").closest("tr"); const actionsToggler = within(sshKeyRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); const setAction = within(sshKeyRow).getByRole("menuitem", { name: "Set" }); @@ -296,8 +283,7 @@ describe("when ready", () => { const { user } = installerRender(); const table = await screen.findByRole("grid"); - const sshKeyRow = within(table).getByText("SSH Key") - .closest("tr"); + const sshKeyRow = within(table).getByText("SSH Key").closest("tr"); const actionsToggler = within(sshKeyRow).getByRole("button", { name: "Actions" }); await user.click(actionsToggler); diff --git a/web/src/components/users/RootPasswordPopup.jsx b/web/src/components/users/RootPasswordPopup.jsx index 34b2e6cda1..aa3dc015d0 100644 --- a/web/src/components/users/RootPasswordPopup.jsx +++ b/web/src/components/users/RootPasswordPopup.jsx @@ -21,7 +21,7 @@ import React, { useState } from "react"; import { Form } from "@patternfly/react-core"; -import { PasswordAndConfirmationInput, Popup } from '~/components/core'; +import { PasswordAndConfirmationInput, Popup } from "~/components/core"; import { _ } from "~/i18n"; import { useInstallerClient } from "~/context/installer"; @@ -38,11 +38,7 @@ import { useInstallerClient } from "~/context/installer"; * @param {boolean} props.isOpen - whether the dialog should be visible * @param {function} props.onClose - the function to be called when the dialog is closed */ -export default function RootPasswordPopup({ - title = _("Root password"), - isOpen, - onClose -}) { +export default function RootPasswordPopup({ title = _("Root password"), isOpen, onClose }) { const { users: client } = useInstallerClient(); const [password, setPassword] = useState(""); const [isValidPassword, setIsValidPassword] = useState(true); @@ -74,7 +70,11 @@ export default function RootPasswordPopup({ - + diff --git a/web/src/components/users/RootPasswordPopup.test.jsx b/web/src/components/users/RootPasswordPopup.test.jsx index 90671eeae5..4340e75475 100644 --- a/web/src/components/users/RootPasswordPopup.test.jsx +++ b/web/src/components/users/RootPasswordPopup.test.jsx @@ -38,7 +38,7 @@ beforeEach(() => { return { users: { setRootPassword: setRootPasswordFn, - } + }, }; }); }); diff --git a/web/src/components/users/RootSSHKeyPopup.jsx b/web/src/components/users/RootSSHKeyPopup.jsx index 4b047fa8f3..cd444f5bf4 100644 --- a/web/src/components/users/RootSSHKeyPopup.jsx +++ b/web/src/components/users/RootSSHKeyPopup.jsx @@ -23,7 +23,7 @@ import React, { useState } from "react"; import { Form, FormGroup, FileUpload } from "@patternfly/react-core"; import { _ } from "~/i18n"; -import { Popup } from '~/components/core'; +import { Popup } from "~/components/core"; import { useInstallerClient } from "~/context/installer"; /** @@ -43,7 +43,7 @@ export default function RootSSHKeyPopup({ title = _("Set root SSH public key"), currentKey = "", isOpen, - onClose + onClose, }) { const client = useInstallerClient(); const [isLoading, setIsLoading] = useState(false); diff --git a/web/src/components/users/RootSSHKeyPopup.test.jsx b/web/src/components/users/RootSSHKeyPopup.test.jsx index 124a01c75f..64ed1fd177 100644 --- a/web/src/components/users/RootSSHKeyPopup.test.jsx +++ b/web/src/components/users/RootSSHKeyPopup.test.jsx @@ -37,7 +37,7 @@ beforeEach(() => { return { users: { setRootSSHKey: setRootSSHKeyFn, - } + }, }; }); }); diff --git a/web/src/components/users/routes.js b/web/src/components/users/routes.js index e5ebf086eb..4c108d1f4c 100644 --- a/web/src/components/users/routes.js +++ b/web/src/components/users/routes.js @@ -30,19 +30,19 @@ const routes = { element: , handle: { name: N_("Users"), - icon: "manage_accounts" + icon: "manage_accounts", }, children: [ { index: true, element: }, { path: "first", - element: + element: , }, { path: "first/edit", - element: - } - ] + element: , + }, + ], }; export default routes; diff --git a/web/src/components/users/utils.js b/web/src/components/users/utils.js index b13576d8b8..9f36687099 100644 --- a/web/src/components/users/utils.js +++ b/web/src/components/users/utils.js @@ -32,9 +32,9 @@ const suggestUsernames = (fullName) => { // Cleaning the name. const cleanedName = fullName - .normalize('NFD') + .normalize("NFD") .trim() - .replace(/[\u0300-\u036f]/g, '') // Replacing accented characters with English equivalents, eg. š with s. + .replace(/[\u0300-\u036f]/g, "") // Replacing accented characters with English equivalents, eg. š with s. .replace(/[^\p{L}\p{N} ]/gu, "") // Keep only letters, numbers and spaces. Covering the whole Unicode range, not just ASCII. .toLowerCase(); @@ -42,7 +42,7 @@ const suggestUsernames = (fullName) => { const parts = cleanedName.split(/\s+/); const suggestions = new Set(); - const firstLetters = parts.map(p => p[0]).join(''); + const firstLetters = parts.map((p) => p[0]).join(""); const lastPosition = parts.length - 1; const [firstPart, ...allExceptFirst] = parts; @@ -52,16 +52,16 @@ const suggestUsernames = (fullName) => { // Just the first part of the name suggestions.add(firstPart); // The first letter of the first part plus all other parts - suggestions.add(firstLetter + allExceptFirst.join('')); + suggestions.add(firstLetter + allExceptFirst.join("")); // The first part plus the first letters of all other parts - suggestions.add(firstPart + allExceptFirstLetter.join('')); + suggestions.add(firstPart + allExceptFirstLetter.join("")); // The first letters except the last one plus the last part suggestions.add(firstLetters.substring(0, lastPosition) + lastPart); // All parts without spaces - suggestions.add(parts.join('')); + suggestions.add(parts.join("")); // let's drop suggestions with less than 3 characters - suggestions.forEach(s => { + suggestions.forEach((s) => { if (s.length < 3) suggestions.delete(s); }); @@ -69,6 +69,4 @@ const suggestUsernames = (fullName) => { return [...suggestions]; }; -export { - suggestUsernames -}; +export { suggestUsernames }; diff --git a/web/src/components/users/utils.test.js b/web/src/components/users/utils.test.js index 0c303e2803..5754994ccc 100644 --- a/web/src/components/users/utils.test.js +++ b/web/src/components/users/utils.test.js @@ -23,45 +23,65 @@ import { suggestUsernames } from "./utils"; -describe('suggestUsernames', () => { - test('returns empty collection if fullName not defined', () => { +describe("suggestUsernames", () => { + test("returns empty collection if fullName not defined", () => { expect(suggestUsernames(undefined)).toEqual([]); expect(suggestUsernames(null)).toEqual([]); }); - test('handles basic single name', () => { - expect(suggestUsernames('John')).toEqual(expect.arrayContaining(['john'])); + test("handles basic single name", () => { + expect(suggestUsernames("John")).toEqual(expect.arrayContaining(["john"])); }); - test('handles basic two-part name', () => { - expect(suggestUsernames('John Doe')).toEqual(expect.arrayContaining(['john', 'jdoe', 'johnd', 'johndoe'])); + test("handles basic two-part name", () => { + expect(suggestUsernames("John Doe")).toEqual( + expect.arrayContaining(["john", "jdoe", "johnd", "johndoe"]), + ); }); - test('handles name with middle initial', () => { - expect(suggestUsernames('John Q. Doe')).toEqual(expect.arrayContaining(['john', 'jqdoe', 'johnqd', 'johnqdoe'])); + test("handles name with middle initial", () => { + expect(suggestUsernames("John Q. Doe")).toEqual( + expect.arrayContaining(["john", "jqdoe", "johnqd", "johnqdoe"]), + ); }); - test('normalizes accented characters', () => { - expect(suggestUsernames('José María')).toEqual(expect.arrayContaining(['jose', 'jmaria', 'josem', 'josemaria'])); + test("normalizes accented characters", () => { + expect(suggestUsernames("José María")).toEqual( + expect.arrayContaining(["jose", "jmaria", "josem", "josemaria"]), + ); }); - test('removes hyphens and apostrophes', () => { - expect(suggestUsernames("Jean-Luc O'Neill")).toEqual(expect.arrayContaining(['jeanluc', 'joneill', 'jeanluco', 'jeanluconeill'])); + test("removes hyphens and apostrophes", () => { + expect(suggestUsernames("Jean-Luc O'Neill")).toEqual( + expect.arrayContaining(["jeanluc", "joneill", "jeanluco", "jeanluconeill"]), + ); }); - test('removes non-alphanumeric characters', () => { - expect(suggestUsernames("Anna*#& Maria$%^")).toEqual(expect.arrayContaining(['anna', 'amaria', 'annam', 'annamaria'])); + test("removes non-alphanumeric characters", () => { + expect(suggestUsernames("Anna*#& Maria$%^")).toEqual( + expect.arrayContaining(["anna", "amaria", "annam", "annamaria"]), + ); }); - test('handles long name with multiple parts', () => { - expect(suggestUsernames("Maria del Carmen Fernandez Vega")).toEqual(expect.arrayContaining(['maria', 'mdelcarmenfernandezvega', 'mariadcfv', 'mdcfvega', 'mariadelcarmenfernandezvega'])); + test("handles long name with multiple parts", () => { + expect(suggestUsernames("Maria del Carmen Fernandez Vega")).toEqual( + expect.arrayContaining([ + "maria", + "mdelcarmenfernandezvega", + "mariadcfv", + "mdcfvega", + "mariadelcarmenfernandezvega", + ]), + ); }); - test('handles empty or invalid input', () => { + test("handles empty or invalid input", () => { expect(suggestUsernames("")).toEqual(expect.arrayContaining([])); }); - test('trims spaces and handles multiple spaces between names', () => { - expect(suggestUsernames(" John Doe ")).toEqual(expect.arrayContaining(['john', 'jdoe', 'johnd', 'johndoe'])); + test("trims spaces and handles multiple spaces between names", () => { + expect(suggestUsernames(" John Doe ")).toEqual( + expect.arrayContaining(["john", "jdoe", "johnd", "johndoe"]), + ); }); }); diff --git a/web/src/context/app.jsx b/web/src/context/app.jsx index 704e03339d..121e07e5e8 100644 --- a/web/src/context/app.jsx +++ b/web/src/context/app.jsx @@ -40,9 +40,7 @@ function AppProviders({ children }) { - - {children} - + {children} diff --git a/web/src/context/auth.jsx b/web/src/context/auth.jsx index 90cdde5933..caf7daefd5 100644 --- a/web/src/context/auth.jsx +++ b/web/src/context/auth.jsx @@ -40,7 +40,7 @@ function useAuth() { const AuthErrors = Object.freeze({ SERVER: "server", AUTH: "auth", - OTHER: "other" + OTHER: "other", }); /** @@ -59,10 +59,10 @@ function AuthProvider({ children }) { }); const result = response.status === 200; - if ((response.status >= 500) && (response.status < 600)) { + if (response.status >= 500 && response.status < 600) { setError(AuthErrors.SERVER); } - if ((response.status >= 400) && (response.status < 500)) { + if (response.status >= 400 && response.status < 500) { setError(AuthErrors.AUTH); } setIsLoggedIn(result); @@ -83,10 +83,10 @@ function AuthProvider({ children }) { }) .then((response) => { setIsLoggedIn(response.status === 200); - if ((response.status >= 500) && (response.status < 600)) { + if (response.status >= 500 && response.status < 600) { setError(AuthErrors.SERVER); } - if ((response.status >= 400) && (response.status < 500)) { + if (response.status >= 400 && response.status < 500) { setError(AuthErrors.AUTH); } }) diff --git a/web/src/context/installer.jsx b/web/src/context/installer.jsx index fd4f063a3b..0b4b4c8246 100644 --- a/web/src/context/installer.jsx +++ b/web/src/context/installer.jsx @@ -28,7 +28,10 @@ const InstallerClientContext = React.createContext(null); // TODO: we use a separate context to avoid changing all the codes to // `useInstallerClient`. We should merge them in the future. const InstallerClientStatusContext = React.createContext({ - connected: false, error: false, phase: undefined, status: undefined + connected: false, + error: false, + phase: undefined, + status: undefined, }); /** @@ -65,17 +68,15 @@ function useInstallerClientStatus() { } /** - * @param {object} props - * @param {import("~/client").InstallerClient|undefined} [props.client] client to connect to - * Agama service; if it is undefined, it instantiates a new one using the address - * registered in /run/agama/bus.address. - * @param {number} [props.interval=2000] - Interval in milliseconds between connection attempt - * (2000 by default). - * @param {React.ReactNode} [props.children] - content to display within the provider - */ -function InstallerClientProvider({ - children, client = null -}) { + * @param {object} props + * @param {import("~/client").InstallerClient|undefined} [props.client] client to connect to + * Agama service; if it is undefined, it instantiates a new one using the address + * registered in /run/agama/bus.address. + * @param {number} [props.interval=2000] - Interval in milliseconds between connection attempt + * (2000 by default). + * @param {React.ReactNode} [props.children] - content to display within the provider + */ +function InstallerClientProvider({ children, client = null }) { const [value, setValue] = useState(client); const [connected, setConnected] = useState(false); const [error, setError] = useState(false); @@ -91,7 +92,7 @@ function InstallerClientProvider({ // allow hot replacement for the clients code if (module.hot) { // if anything coming from `import ... from "~/client"` is updated then this hook is called - module.hot.accept("~/client", async function() { + module.hot.accept("~/client", async function () { console.log("[Agama HMR] A client module has been updated"); const updated_client = await createDefaultClient(); @@ -151,8 +152,4 @@ function InstallerClientProvider({ ); } -export { - InstallerClientProvider, - useInstallerClient, - useInstallerClientStatus -}; +export { InstallerClientProvider, useInstallerClient, useInstallerClientStatus }; diff --git a/web/src/context/installer.test.jsx b/web/src/context/installer.test.jsx index ef84f406c6..0e025f2a82 100644 --- a/web/src/context/installer.test.jsx +++ b/web/src/context/installer.test.jsx @@ -52,8 +52,8 @@ describe("installer context", () => { getPhase: jest.fn().mockResolvedValue(STARTUP), getStatus: jest.fn().mockResolvedValue(BUSY), onPhaseChange: jest.fn(), - onStatusChange: jest.fn() - } + onStatusChange: jest.fn(), + }, }; }); }); @@ -62,7 +62,7 @@ describe("installer context", () => { plainRender( - + , ); await screen.findByText("connected: false"); await screen.findByText("phase: 0"); diff --git a/web/src/context/installerL10n.jsx b/web/src/context/installerL10n.jsx index 6849e523f1..08df7fda73 100644 --- a/web/src/context/installerL10n.jsx +++ b/web/src/context/installerL10n.jsx @@ -62,7 +62,9 @@ function useInstallerL10n() { function agamaLanguage() { // language from cookie, empty string if not set (regexp taken from Cockpit) // https://github.com/cockpit-project/cockpit/blob/98a2e093c42ea8cd2431cf15c7ca0e44bb4ce3f1/pkg/shell/shell-modals.jsx#L91 - const languageString = decodeURIComponent(document.cookie.replace(/(?:(?:^|.*;\s*)agamaLang\s*=\s*([^;]*).*$)|^.*$/, "$1")); + const languageString = decodeURIComponent( + document.cookie.replace(/(?:(?:^|.*;\s*)agamaLang\s*=\s*([^;]*).*$)|^.*$/, "$1"), + ); if (languageString) { return languageString.toLowerCase(); } @@ -81,12 +83,16 @@ function storeAgamaLanguage(language) { if (current === language) return false; // Code taken from Cockpit. - const cookie = "agamaLang=" + encodeURIComponent(language) + "; path=/; expires=Sun, 16 Jul 3567 06:23:41 GMT"; + const cookie = + "agamaLang=" + encodeURIComponent(language) + "; path=/; expires=Sun, 16 Jul 3567 06:23:41 GMT"; document.cookie = cookie; // for backward compatibility, CockpitLang cookie is needed to load correct po.js content from Cockpit // TODO: remove after dropping Cockpit completely - const cockpit_cookie = "CockpitLang=" + encodeURIComponent(language) + "; path=/; expires=Sun, 16 Jul 3567 06:23:41 GMT"; + const cockpit_cookie = + "CockpitLang=" + + encodeURIComponent(language) + + "; path=/; expires=Sun, 16 Jul 3567 06:23:41 GMT"; document.cookie = cockpit_cookie; window.localStorage.setItem("cockpit.lang", language); @@ -101,11 +107,11 @@ function storeAgamaLanguage(language) { * @return {string|undefined} Undefined if not set. */ function languageFromQuery() { - const lang = (new URLSearchParams(window.location.search)).get("lang"); + const lang = new URLSearchParams(window.location.search).get("lang"); if (!lang) return undefined; const [language, country] = lang.toLowerCase().split(/[-_]/); - return (country) ? `${language}-${country}` : language; + return country ? `${language}-${country}` : language; } /** @@ -137,7 +143,7 @@ function languageFromLocale(locale) { */ function languageToLocale(language) { const [lang, country] = language.split("-"); - const locale = (country) ? `${lang}_${country.toUpperCase()}` : lang; + const locale = country ? `${lang}_${country.toUpperCase()}` : lang; return `${locale}.UTF-8`; } @@ -147,7 +153,7 @@ function languageToLocale(language) { * @return {Array} */ function navigatorLanguages() { - return navigator.languages.map(l => l.toLowerCase()); + return navigator.languages.map((l) => l.toLowerCase()); } /** @@ -162,7 +168,7 @@ function findSupportedLanguage(languages) { for (const candidate of languages) { const [language, country] = candidate.split("-"); - const match = supported.find(s => { + const match = supported.find((s) => { const [supportedLanguage, supportedCountry] = s.split("-"); if (language === supportedLanguage) { return country === undefined || country === supportedCountry; @@ -215,53 +221,62 @@ function InstallerL10nProvider({ children }) { const [backendPending, setBackendPending] = useState(false); const { cancellablePromise } = useCancellablePromise(); - const storeInstallerLanguage = useCallback(async (newLanguage) => { - if (!client) { - setBackendPending(true); - return false; - } + const storeInstallerLanguage = useCallback( + async (newLanguage) => { + if (!client) { + setBackendPending(true); + return false; + } - const locale = await cancellablePromise(client.l10n.getUILocale()); - const currentLanguage = languageFromLocale(locale); + const locale = await cancellablePromise(client.l10n.getUILocale()); + const currentLanguage = languageFromLocale(locale); - if (currentLanguage !== newLanguage) { - // FIXME: fallback to en-US if the language is not supported. - await cancellablePromise(client.l10n.setUILocale(languageToLocale(newLanguage))); - return true; - } + if (currentLanguage !== newLanguage) { + // FIXME: fallback to en-US if the language is not supported. + await cancellablePromise(client.l10n.setUILocale(languageToLocale(newLanguage))); + return true; + } - return false; - }, [client, cancellablePromise]); + return false; + }, + [client, cancellablePromise], + ); - const changeLanguage = useCallback(async (lang) => { - const wanted = lang || languageFromQuery(); + const changeLanguage = useCallback( + async (lang) => { + const wanted = lang || languageFromQuery(); - if (wanted === "xx" || wanted === "xx-xx") { - agama.language = wanted; - setLanguage(wanted); - return; - } + if (wanted === "xx" || wanted === "xx-xx") { + agama.language = wanted; + setLanguage(wanted); + return; + } - const current = agamaLanguage(); - const candidateLanguages = [wanted, current].concat(navigatorLanguages()).filter(l => l); - const newLanguage = findSupportedLanguage(candidateLanguages) || "en-us"; + const current = agamaLanguage(); + const candidateLanguages = [wanted, current].concat(navigatorLanguages()).filter((l) => l); + const newLanguage = findSupportedLanguage(candidateLanguages) || "en-us"; - let mustReload = storeAgamaLanguage(newLanguage); - mustReload = await storeInstallerLanguage(newLanguage) || mustReload; + let mustReload = storeAgamaLanguage(newLanguage); + mustReload = (await storeInstallerLanguage(newLanguage)) || mustReload; - if (mustReload) { - reload(newLanguage); - } else { - setLanguage(newLanguage); - } - }, [storeInstallerLanguage, setLanguage]); + if (mustReload) { + reload(newLanguage); + } else { + setLanguage(newLanguage); + } + }, + [storeInstallerLanguage, setLanguage], + ); - const changeKeymap = useCallback(async (id) => { - if (!client) return; + const changeKeymap = useCallback( + async (id) => { + if (!client) return; - setKeymap(id); - client.l10n.setUIKeymap(id); - }, [setKeymap, client]); + setKeymap(id); + client.l10n.setUIKeymap(id); + }, + [setKeymap, client], + ); useEffect(() => { if (!language) changeLanguage(); @@ -281,12 +296,7 @@ function InstallerL10nProvider({ children }) { const value = { language, changeLanguage, keymap, changeKeymap }; - return ( - {children} - ); + return {children}; } -export { - InstallerL10nProvider, - useInstallerL10n -}; +export { InstallerL10nProvider, useInstallerL10n }; diff --git a/web/src/context/installerL10n.test.jsx b/web/src/context/installerL10n.test.jsx index 1808b398a6..7b47d3f140 100644 --- a/web/src/context/installerL10n.test.jsx +++ b/web/src/context/installerL10n.test.jsx @@ -39,7 +39,7 @@ const client = { getPhase: jest.fn(), getStatus: jest.fn(), onPhaseChange: jest.fn(), - onStatusChange: jest.fn() + onStatusChange: jest.fn(), }, l10n: { getUILocale: getUILocaleFn, diff --git a/web/src/context/root.jsx b/web/src/context/root.jsx index dc2f4eb170..c8ce648267 100644 --- a/web/src/context/root.jsx +++ b/web/src/context/root.jsx @@ -31,11 +31,7 @@ import { AuthProvider } from "./auth"; * @param {React.ReactNode} [props.children] - content to display within the provider. */ function RootProviders({ children }) { - return ( - - {children} - - ); + return {children}; } export { RootProviders }; diff --git a/web/src/hooks/useNodeSiblings.js b/web/src/hooks/useNodeSiblings.js index f95d5b6074..cf98a42d5e 100644 --- a/web/src/hooks/useNodeSiblings.js +++ b/web/src/hooks/useNodeSiblings.js @@ -28,16 +28,16 @@ import { noop } from "~/utils"; const useNodeSiblings = (node) => { if (!node) return [noop, noop]; - const siblings = [...node.parentNode.children].filter(n => n !== node); + const siblings = [...node.parentNode.children].filter((n) => n !== node); const addAttribute = (attribute, value) => { - siblings.forEach(sibling => { + siblings.forEach((sibling) => { sibling.setAttribute(attribute, value); }); }; const removeAttribute = (attribute) => { - siblings.forEach(sibling => { + siblings.forEach((sibling) => { sibling.removeAttribute(attribute); }); }; diff --git a/web/src/hooks/useNodeSiblings.test.js b/web/src/hooks/useNodeSiblings.test.js index c668d67e37..b2a569f845 100644 --- a/web/src/hooks/useNodeSiblings.test.js +++ b/web/src/hooks/useNodeSiblings.test.js @@ -1,5 +1,5 @@ -import { renderHook } from '@testing-library/react'; -import useNodeSiblings from './useNodeSiblings'; +import { renderHook } from "@testing-library/react"; +import useNodeSiblings from "./useNodeSiblings"; // Mocked HTMLElement for testing const mockNode = { @@ -12,8 +12,8 @@ const mockNode = { }, }; -describe('useNodeSiblings', () => { - it('should return noop functions when node is not provided', () => { +describe("useNodeSiblings", () => { + it("should return noop functions when node is not provided", () => { const { result } = renderHook(() => useNodeSiblings(null)); const [addAttribute, removeAttribute] = result.current; @@ -23,27 +23,36 @@ describe('useNodeSiblings', () => { expect(removeAttribute).toEqual(expect.any(Function)); // Call the noop functions to ensure they don't throw any errors - expect(() => addAttribute('attribute', 'value')).not.toThrow(); - expect(() => removeAttribute('attribute')).not.toThrow(); + expect(() => addAttribute("attribute", "value")).not.toThrow(); + expect(() => removeAttribute("attribute")).not.toThrow(); }); - it('should add attribute to all siblings when addAttribute is called', () => { + it("should add attribute to all siblings when addAttribute is called", () => { const { result } = renderHook(() => useNodeSiblings(mockNode)); const [addAttribute] = result.current; - const attributeName = 'attribute'; - const attributeValue = 'value'; + const attributeName = "attribute"; + const attributeValue = "value"; addAttribute(attributeName, attributeValue); - expect(mockNode.parentNode.children[0].setAttribute).toHaveBeenCalledWith(attributeName, attributeValue); - expect(mockNode.parentNode.children[1].setAttribute).toHaveBeenCalledWith(attributeName, attributeValue); - expect(mockNode.parentNode.children[2].setAttribute).toHaveBeenCalledWith(attributeName, attributeValue); + expect(mockNode.parentNode.children[0].setAttribute).toHaveBeenCalledWith( + attributeName, + attributeValue, + ); + expect(mockNode.parentNode.children[1].setAttribute).toHaveBeenCalledWith( + attributeName, + attributeValue, + ); + expect(mockNode.parentNode.children[2].setAttribute).toHaveBeenCalledWith( + attributeName, + attributeValue, + ); }); - it('should remove attribute from all siblings when removeAttribute is called', () => { + it("should remove attribute from all siblings when removeAttribute is called", () => { const { result } = renderHook(() => useNodeSiblings(mockNode)); const [, removeAttribute] = result.current; - const attributeName = 'attribute'; + const attributeName = "attribute"; removeAttribute(attributeName); diff --git a/web/src/i18n.js b/web/src/i18n.js index ac78dd7f5c..3ae1d68095 100644 --- a/web/src/i18n.js +++ b/web/src/i18n.js @@ -72,7 +72,7 @@ const xTranslate = (str) => { * @param {string} str the input string to translate * @return {string} translated or original text */ -const _ = (str) => isTestingLanguage() ? xTranslate(str) : agama.gettext(str); +const _ = (str) => (isTestingLanguage() ? xTranslate(str) : agama.gettext(str)); /** * Similar to the _() function. This variant returns singular or plural form @@ -86,9 +86,7 @@ const _ = (str) => isTestingLanguage() ? xTranslate(str) : agama.gettext(str); * @return {string} translated or original text */ const n_ = (str1, strN, n) => { - return isTestingLanguage() - ? xTranslate((n === 1) ? str1 : strN) - : agama.ngettext(str1, strN, n); + return isTestingLanguage() ? xTranslate(n === 1 ? str1 : strN) : agama.ngettext(str1, strN, n); }; /** @@ -137,11 +135,6 @@ const N_ = (str) => str; * @return {string} the original text, either "string1" or "stringN" depending * on the value "num" */ -const Nn_ = (str1, strN, n) => (n === 1) ? str1 : strN; +const Nn_ = (str1, strN, n) => (n === 1 ? str1 : strN); -export { - _, - n_, - N_, - Nn_ -}; +export { _, n_, N_, Nn_ }; diff --git a/web/src/index.html b/web/src/index.html index 918d6d681b..138aa7d9d1 100644 --- a/web/src/index.html +++ b/web/src/index.html @@ -1,9 +1,9 @@ - + - + Agama diff --git a/web/src/index.js b/web/src/index.js index 0d487303f7..f9028aa93d 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -50,5 +50,5 @@ const root = createRoot(container); root.render( - + , ); diff --git a/web/src/languages.json b/web/src/languages.json index 8c64959024..d406f186b0 100644 --- a/web/src/languages.json +++ b/web/src/languages.json @@ -1,12 +1,12 @@ { - "ca-es": "Català", - "de-de": "Deutsch", - "en-us": "English", - "es-es": "Español", - "ja-jp": "日本語", - "nb-NO": "Norsk bokmål", - "pt-BR": "Português", - "ru-ru": "Русский", - "sv-se": "Svenska", - "zh-Hans": "中文" -} \ No newline at end of file + "ca-es": "Català", + "de-de": "Deutsch", + "en-us": "English", + "es-es": "Español", + "ja-jp": "日本語", + "nb-NO": "Norsk bokmål", + "pt-BR": "Português", + "ru-ru": "Русский", + "sv-se": "Svenska", + "zh-Hans": "中文" +} diff --git a/web/src/queries/l10n.ts b/web/src/queries/l10n.ts index 2dd5ee7fda..525178a93d 100644 --- a/web/src/queries/l10n.ts +++ b/web/src/queries/l10n.ts @@ -30,7 +30,7 @@ import { timezoneUTCOffset } from "~/utils"; const configQuery = () => { return { queryKey: ["l10n/config"], - queryFn: () => fetch("/api/l10n/config").then(res => res.json()) + queryFn: () => fetch("/api/l10n/config").then((res) => res.json()), }; }; @@ -46,7 +46,7 @@ const localesQuery = () => ({ return { id, name: language, territory }; }); }, - staleTime: Infinity + staleTime: Infinity, }); /** @@ -62,7 +62,7 @@ const timezonesQuery = () => ({ return { id: code, parts, country, utcOffset: offset }; }); }, - staleTime: Infinity + staleTime: Infinity, }); /** @@ -78,7 +78,7 @@ const keymapsQuery = () => ({ }); return keymaps.sort((a, b) => (a.name < b.name ? -1 : 1)); }, - staleTime: Infinity + staleTime: Infinity, }); /** @@ -88,14 +88,14 @@ const keymapsQuery = () => ({ */ const useConfigMutation = () => { const query = { - mutationFn: newConfig => + mutationFn: (newConfig) => fetch("/api/l10n/config", { method: "PATCH", body: JSON.stringify(newConfig), headers: { - "Content-Type": "application/json" - } - }) + "Content-Type": "application/json", + }, + }), }; return useMutation(query); }; @@ -113,7 +113,7 @@ const useL10nConfigChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent(event => { + return client.ws().onEvent((event) => { if (event.type === "L10nConfigChanged") { queryClient.invalidateQueries({ queryKey: ["l10n/config"] }); } @@ -125,12 +125,12 @@ const useL10nConfigChanges = () => { const useL10n = () => { const [{ data: config }, { data: locales }, { data: keymaps }, { data: timezones }] = useSuspenseQueries({ - queries: [configQuery(), localesQuery(), keymapsQuery(), timezonesQuery()] + queries: [configQuery(), localesQuery(), keymapsQuery(), timezonesQuery()], }); - const selectedLocale = locales.find(l => l.id === config.locales[0]); - const selectedKeymap = keymaps.find(k => k.id === config.keymap); - const selectedTimezone = timezones.find(t => t.id === config.timezone); + const selectedLocale = locales.find((l) => l.id === config.locales[0]); + const selectedKeymap = keymaps.find((k) => k.id === config.keymap); + const selectedTimezone = timezones.find((t) => t.id === config.timezone); return { locales, @@ -138,7 +138,7 @@ const useL10n = () => { timezones, selectedLocale, selectedKeymap, - selectedTimezone + selectedTimezone, }; }; @@ -149,5 +149,5 @@ export { timezonesQuery, useConfigMutation, useL10n, - useL10nConfigChanges + useL10nConfigChanges, }; diff --git a/web/src/queries/software.js b/web/src/queries/software.js index cb2ef253dc..a902d10ad7 100644 --- a/web/src/queries/software.js +++ b/web/src/queries/software.js @@ -24,19 +24,19 @@ import { QueryClient, useMutation, useQueryClient, - useSuspenseQueries + useSuspenseQueries, } from "@tanstack/react-query"; import { useInstallerClient } from "~/context/installer"; const configQuery = () => ({ queryKey: ["software/config"], - queryFn: () => fetch("/api/software/config").then(res => res.json()) + queryFn: () => fetch("/api/software/config").then((res) => res.json()), }); const productsQuery = () => ({ queryKey: ["software/products"], - queryFn: () => fetch("/api/software/products").then(res => res.json()), - staleTime: Infinity + queryFn: () => fetch("/api/software/products").then((res) => res.json()), + staleTime: Infinity, }); /** @@ -49,19 +49,19 @@ const useConfigMutation = () => { const client = useInstallerClient(); const query = { - mutationFn: newConfig => + mutationFn: (newConfig) => fetch("/api/software/config", { // FIXME: use "PATCH" instead method: "PUT", body: JSON.stringify(newConfig), headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["software/config"] }); client.manager.startProbing(); - } + }, }; return useMutation(query); }; @@ -79,7 +79,7 @@ const useProductChanges = () => { if (!client) return; const queryClient = new QueryClient(); - return client.ws().onEvent(event => { + return client.ws().onEvent((event) => { if (event.type === "ProductChanged") { queryClient.invalidateQueries({ queryKey: ["software/config"] }); } @@ -89,13 +89,13 @@ const useProductChanges = () => { const useProduct = () => { const [{ data: config }, { data: products }] = useSuspenseQueries({ - queries: [configQuery(), productsQuery()] + queries: [configQuery(), productsQuery()], }); - const selectedProduct = products.find(p => p.id === config.product); + const selectedProduct = products.find((p) => p.id === config.product); return { products, - selectedProduct + selectedProduct, }; }; diff --git a/web/src/router.js b/web/src/router.js index a4b6732b5f..333f28e83f 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -42,7 +42,7 @@ const rootRoutes = [ networkRoutes, storageRoutes, softwareRoutes, - usersRoutes + usersRoutes, ]; const protectedRoutes = [ @@ -55,17 +55,17 @@ const protectedRoutes = [ children: [ { index: true, - element: + element: , }, - ...rootRoutes - ] + ...rootRoutes, + ], }, { element: , - children: [productsRoutes] - } - ] - } + children: [productsRoutes], + }, + ], + }, ]; const routes = [ @@ -76,20 +76,17 @@ const routes = [ children: [ { index: true, - element: - } - ] + element: , + }, + ], }, { path: "/", element: , - children: [...protectedRoutes] - } + children: [...protectedRoutes], + }, ]; const router = createHashRouter(routes); -export { - router, - rootRoutes -}; +export { router, rootRoutes }; diff --git a/web/src/routes/l10n.js b/web/src/routes/l10n.js index 3e0df54833..17f9a2a919 100644 --- a/web/src/routes/l10n.js +++ b/web/src/routes/l10n.js @@ -23,12 +23,7 @@ import React from "react"; import { Page } from "~/components/core"; import { L10nPage, LocaleSelection, KeymapSelection, TimezoneSelection } from "~/components/l10n"; import { queryClient } from "~/context/app"; -import { - configQuery, - localesQuery, - keymapsQuery, - timezonesQuery, -} from "~/queries/l10n"; +import { configQuery, localesQuery, keymapsQuery, timezonesQuery } from "~/queries/l10n"; import { N_ } from "~/i18n"; const L10N_PATH = "/l10n"; @@ -41,12 +36,12 @@ const routes = { element: , handle: { name: N_("Localization"), - icon: "globe" + icon: "globe", }, children: [ { index: true, - element: + element: , }, { path: LOCALE_SELECTION_PATH, @@ -59,14 +54,9 @@ const routes = { { path: TIMEZONE_SELECTION_PATH, element: , - } - ] + }, + ], }; export default routes; -export { - L10N_PATH, - LOCALE_SELECTION_PATH, - KEYMAP_SELECTION_PATH, - TIMEZONE_SELECTION_PATH -}; +export { L10N_PATH, LOCALE_SELECTION_PATH, KEYMAP_SELECTION_PATH, TIMEZONE_SELECTION_PATH }; diff --git a/web/src/routes/products.js b/web/src/routes/products.js index 9ddb6eef94..0b30ce962c 100644 --- a/web/src/routes/products.js +++ b/web/src/routes/products.js @@ -32,13 +32,13 @@ const productsRoutes = { children: [ { index: true, - element: + element: , }, { path: "progress", - element: - } - ] + element: , + }, + ], }; export default productsRoutes; diff --git a/web/src/test-utils.js b/web/src/test-utils.js index 7aaa06f560..8452491f88 100644 --- a/web/src/test-utils.js +++ b/web/src/test-utils.js @@ -72,12 +72,12 @@ const mockUseRevalidator = jest.fn(); const mockRoutes = (...routes) => initialRoutes.mockReturnValueOnce(routes); // Centralize the react-router-dom mock here -jest.mock('react-router-dom', () => ({ +jest.mock("react-router-dom", () => ({ ...jest.requireActual("react-router-dom"), useNavigate: () => mockNavigateFn, Navigate: ({ to: route }) => <>Navigating to {route}, Outlet: () => <>Outlet Content, - useRevalidator: () => mockUseRevalidator + useRevalidator: () => mockUseRevalidator, })); const Providers = ({ children, withL10n }) => { @@ -102,24 +102,18 @@ const Providers = ({ children, withL10n }) => { getStatus: noop, onPhaseChange: noop, onStatusChange: noop, - ...client.manager + ...client.manager, }; if (withL10n) { return ( - - {children} - + {children} ); } - return ( - - {children} - - ); + return {children}; }; /** @@ -134,19 +128,15 @@ const installerRender = (ui, options = {}) => { const Wrapper = ({ children }) => ( - - {children} - + {children} ); - return ( - { - user: userEvent.setup(), - ...render(ui, { wrapper: Wrapper, ...options }) - } - ); + return { + user: userEvent.setup(), + ...render(ui, { wrapper: Wrapper, ...options }), + }; }; /** @@ -165,16 +155,12 @@ const plainRender = (ui, options = {}) => { const queryClient = new QueryClient({}); const Wrapper = ({ children }) => ( - - {children} - - ); - return ( - { - user: userEvent.setup(), - ...render(ui, { wrapper: Wrapper, ...options }) - } + {children} ); + return { + user: userEvent.setup(), + ...render(ui, { wrapper: Wrapper, ...options }), + }; }; /** @@ -234,5 +220,5 @@ export { mockNavigateFn, mockRoutes, mockUseRevalidator, - resetLocalStorage + resetLocalStorage, }; diff --git a/web/src/test-utils.test.js b/web/src/test-utils.test.js index a76ccfb583..c5670616d6 100644 --- a/web/src/test-utils.test.js +++ b/web/src/test-utils.test.js @@ -19,9 +19,7 @@ * find current contact information at www.suse.com. */ -import { - resetLocalStorage -} from "./test-utils"; +import { resetLocalStorage } from "./test-utils"; beforeAll(() => { jest.spyOn(Storage.prototype, "clear"); @@ -49,7 +47,7 @@ describe("resetLocalStorage", () => { it("sets an initial state if given value is an object", () => { resetLocalStorage({ storage: "something", - for: "later" + for: "later", }); expect(window.localStorage.setItem).toHaveBeenCalledWith("storage", "something"); expect(window.localStorage.setItem).toHaveBeenCalledWith("for", "later"); diff --git a/web/src/utils.js b/web/src/utils.js index 938e6af327..dd90e6cbd3 100644 --- a/web/src/utils.js +++ b/web/src/utils.js @@ -30,15 +30,14 @@ import { useEffect, useRef, useCallback, useState } from "react"; * @param {any} value - the value to be checked * @return {boolean} true when given value is an object; false otherwise */ -const isObject = (value) => ( - typeof value === 'object' && - value !== null && - !Array.isArray(value) && - !(value instanceof RegExp) && - !(value instanceof Date) && - !(value instanceof Set) && - !(value instanceof Map) -); +const isObject = (value) => + typeof value === "object" && + value !== null && + !Array.isArray(value) && + !(value instanceof RegExp) && + !(value instanceof Date) && + !(value instanceof Set) && + !(value instanceof Map); /** * Returns an empty function useful to be used as a default callback. @@ -64,7 +63,7 @@ const partition = (collection, filter) => { const pass = []; const fail = []; - collection.forEach(element => { + collection.forEach((element) => { filter(element) ? pass.push(element) : fail.push(element); }); @@ -79,7 +78,7 @@ const partition = (collection, filter) => { * @returns {Array} */ function compact(collection) { - return collection.filter(e => e !== null && e !== undefined); + return collection.filter((e) => e !== null && e !== undefined); } /** @@ -106,7 +105,7 @@ function uniq(collection) { * @returns {String} - CSS classes joined together after ignoring falsy values */ function classNames(...classes) { - return classes.filter((item) => !!item).join(' '); + return classes.filter((item) => !!item).join(" "); } /** @@ -128,15 +127,15 @@ function makeCancellable(promise) { const cancellablePromise = new Promise((resolve, reject) => { promise - .then((value) => (!isCanceled && resolve(value))) - .catch((error) => (!isCanceled && reject(error))); + .then((value) => !isCanceled && resolve(value)) + .catch((error) => !isCanceled && reject(error)); }); return { promise: cancellablePromise, cancel() { isCanceled = true; - } + }, }; } @@ -175,7 +174,7 @@ function useCancellablePromise() { promises.current = []; return () => { - promises.current.forEach(p => p.cancel()); + promises.current.forEach((p) => p.cancel()); promises.current = []; }; }, []); @@ -197,9 +196,7 @@ function useCancellablePromise() { * @param {*} fallbackState */ const useLocalStorage = (storageKey, fallbackState) => { - const [value, setValue] = useState( - JSON.parse(localStorage.getItem(storageKey)) ?? fallbackState - ); + const [value, setValue] = useState(JSON.parse(localStorage.getItem(storageKey)) ?? fallbackState); useEffect(() => { localStorage.setItem(storageKey, JSON.stringify(value)); @@ -338,10 +335,11 @@ const remoteConnection = (...args) => !localConnection(...args); */ const timezoneTime = (timezone, { date = new Date() }) => { try { - const formatter = new Intl.DateTimeFormat( - "en-US", - { timeZone: timezone, timeStyle: "short", hour12: false } - ); + const formatter = new Intl.DateTimeFormat("en-US", { + timeZone: timezone, + timeStyle: "short", + hour12: false, + }); return formatter.format(date); } catch (e) { @@ -360,11 +358,11 @@ const timezoneTime = (timezone, { date = new Date() }) => { const timezoneUTCOffset = (timezone) => { try { const date = new Date(); - const dateLocaleString = date.toLocaleString( - "en-US", - { timeZone: timezone, timeZoneName: "short" } - ); - const [timezoneName] = dateLocaleString.split(' ').slice(-1); + const dateLocaleString = date.toLocaleString("en-US", { + timeZone: timezone, + timeZoneName: "short", + }); + const [timezoneName] = dateLocaleString.split(" ").slice(-1); const dateString = date.toString(); const offset = Date.parse(`${dateString} UTC`) - Date.parse(`${dateString} ${timezoneName}`); @@ -394,5 +392,5 @@ export { localConnection, remoteConnection, timezoneTime, - timezoneUTCOffset + timezoneUTCOffset, }; diff --git a/web/src/utils.test.js b/web/src/utils.test.js index 5d340828f8..e9f5671de8 100644 --- a/web/src/utils.test.js +++ b/web/src/utils.test.js @@ -20,8 +20,15 @@ */ import { - classNames, partition, compact, uniq, noop, toValidationError, - localConnection, remoteConnection, isObject + classNames, + partition, + compact, + uniq, + noop, + toValidationError, + localConnection, + remoteConnection, + isObject, } from "./utils"; describe("noop", () => { @@ -34,7 +41,7 @@ describe("noop", () => { describe("partition", () => { it("returns two groups of elements that do and do not satisfy provided filter", () => { const numbers = [1, 2, 3, 4, 5, 6]; - const [odd, even] = partition(numbers, number => number % 2); + const [odd, even] = partition(numbers, (number) => number % 2); expect(odd).toEqual([1, 3, 5]); expect(even).toEqual([2, 4, 6]); @@ -44,28 +51,38 @@ describe("partition", () => { describe("compact", () => { it("removes null and undefined values", () => { expect(compact([])).toEqual([]); - expect(compact([undefined, null, "", 0, 1, NaN, false, true])) - .toEqual(["", 0, 1, NaN, false, true]); + expect(compact([undefined, null, "", 0, 1, NaN, false, true])).toEqual([ + "", + 0, + 1, + NaN, + false, + true, + ]); }); }); describe("uniq", () => { it("removes duplicated values", () => { expect(uniq([])).toEqual([]); - expect(uniq([undefined, null, null, 0, 1, NaN, false, true, false, "test"])) - .toEqual([undefined, null, 0, 1, NaN, false, true, "test"]); + expect(uniq([undefined, null, null, 0, 1, NaN, false, true, false, "test"])).toEqual([ + undefined, + null, + 0, + 1, + NaN, + false, + true, + "test", + ]); }); }); describe("classNames", () => { it("join given arguments, ignoring falsy values", () => { - expect(classNames( - "bg-yellow", - false && "h-24", - undefined, - null, - true && "w-24", - )).toEqual("bg-yellow w-24"); + expect(classNames("bg-yellow", false && "h-24", undefined, null, true && "w-24")).toEqual( + "bg-yellow w-24", + ); }); }); @@ -75,7 +92,7 @@ describe("toValidationError", () => { description: "Issue 1", details: "Details issue 1", source: "config", - severity: "warn" + severity: "warn", }; expect(toValidationError(issue)).toEqual({ message: "Issue 1" }); }); @@ -159,9 +176,7 @@ describe("isObject", () => { }); it("returns false when called with a map", () => { - const map = new Map([ - ["dummy", "map"] - ]); + const map = new Map([["dummy", "map"]]); expect(isObject(map)).toBe(false); }); }); diff --git a/web/svgo.config.js b/web/svgo.config.js index c77334001a..c6ea4118a4 100644 --- a/web/svgo.config.js +++ b/web/svgo.config.js @@ -1,8 +1,8 @@ module.exports = { plugins: [ { - name: 'removeViewBox', - active: false - } - ] + name: "removeViewBox", + active: false, + }, + ], }; diff --git a/web/tsconfig.json b/web/tsconfig.json index bde7ef8c06..fc69f759e1 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -10,18 +10,10 @@ "jsx": "react", "allowSyntheticDefaultImports": true, "paths": { - "~/*": [ - "src/*" - ], - "~/client": [ - "src/client/index.js" - ], - "@icons/*": [ - "node_modules/@material-symbols/svg-400/outlined/*" - ] + "~/*": ["src/*"], + "~/client": ["src/client/index.js"], + "@icons/*": ["node_modules/@material-symbols/svg-400/outlined/*"] } }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/web/typedoc.json b/web/typedoc.json index c93eacf7cd..7d0b6cc273 100644 --- a/web/typedoc.json +++ b/web/typedoc.json @@ -2,24 +2,13 @@ "$schema": "https://typedoc.org/schema.json", "name": "Agama Installer", "entryPointStrategy": "expand", - "exclude": [ - "./src/lib" - ], + "exclude": ["./src/lib"], "excludeNotDocumented": true, - "plugin": [ - "typedoc-plugin-external-module-map", - "typedoc-plugin-merge-modules", - ], + "plugin": ["typedoc-plugin-external-module-map", "typedoc-plugin-merge-modules"], "excludeReferences": true, "mergeModulesMergeMode": "module-category", - "external-modulemap": [ - ".*\/src\/(client)\/", - ".*\/src\/components\/([\\w\\-_]+)\/", - ], - "sort": [ - "alphabetical", - "visibility" - ], + "external-modulemap": [".*/src/(client)/", ".*/src/components/([\\w\\-_]+)/"], + "sort": ["alphabetical", "visibility"], "navigation": { "includeCategories": true, "includeGroups": true @@ -28,5 +17,5 @@ "navigationLinks": { "GitHub": "https://github.com/openSUSE/agama" }, - "skipErrorChecking": true, + "skipErrorChecking": true } diff --git a/web/webpack.config.js b/web/webpack.config.js index 87df2a4819..1ea7895157 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -24,7 +24,7 @@ const development = !production; const eslint = process.env.ESLINT !== "0"; /* Default to disable csslint for faster production builds */ -const stylelint = process.env.STYLELINT ? (process.env.STYLELINT !== "0") : development; +const stylelint = process.env.STYLELINT ? process.env.STYLELINT !== "0" : development; // Agama API server. By default it connects to a local development server. let agamaServer = process.env.AGAMA_SERVER || "localhost"; @@ -83,7 +83,7 @@ module.exports = { resolve: { modules: ["node_modules", path.resolve(__dirname, "src/lib")], plugins: [new TsconfigPathsPlugin({ extensions: [".ts", ".tsx", ".js", ".jsx", ".json"] })], - extensions: [".ts", ".tsx", ".js", ".jsx", ".json"] + extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], }, resolveLoader: { modules: ["node_modules", path.resolve(__dirname, "src/lib")], @@ -132,20 +132,20 @@ module.exports = { // Thus, it's needed not mangling function names ending in PageMenu to keep it working in production // until adopting a better solution, if any. terserOptions: { - keep_fnames: /PageMenu$/ + keep_fnames: /PageMenu$/, }, extractComments: { condition: true, filename: `[file].LICENSE.txt?query=[query]&filebase=[base]`, banner(licenseFile) { return `License information can be found in ${licenseFile}`; - } - } + }, + }, }), // remove also the spaces between the tags new HtmlMinimizerPlugin({ minimizerOptions: { conservativeCollapse: false } }), - new CssMinimizerPlugin() - ] + new CssMinimizerPlugin(), + ], }, module: { @@ -158,12 +158,12 @@ module.exports = { loader: require.resolve("ts-loader"), options: { getCustomTransformers: () => ({ - before: [development && ReactRefreshTypeScript()].filter(Boolean) + before: [development && ReactRefreshTypeScript()].filter(Boolean), }), - transpileOnly: development - } - } - ] + transpileOnly: development, + }, + }, + ], }, { test: /\.(js|jsx)$/,