Line data Source code
1 : /*
2 : * Copyright (C) 2020 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 339 : import cockpit from "cockpit";
7 339 : import React, { useState } from "react";
8 : import { AboutModal } from "@patternfly/react-core/dist/esm/components/AboutModal/index.js";
9 : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
10 : import { Divider } from "@patternfly/react-core/dist/esm/components/Divider/index.js";
11 : import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
12 : import { Menu, MenuContent, MenuSearch, MenuSearchInput, MenuItem, MenuList } from "@patternfly/react-core/dist/esm/components/Menu/index.js";
13 : import {
14 : Modal, ModalBody, ModalFooter, ModalHeader
15 : } from '@patternfly/react-core/dist/esm/components/Modal/index.js';
16 : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
17 : import { Content, ContentVariants } from "@patternfly/react-core/dist/esm/components/Content/index.js";
18 : import { SearchIcon } from '@patternfly/react-icons';
19 :
20 : import { useInit } from "hooks";
21 : import { DialogResult } from "dialogs";
22 :
23 : import { ShellState } from "./state";
24 :
25 : import "menu-select-widget.scss";
26 :
27 339 : const _ = cockpit.gettext;
28 :
29 : interface Package {
30 : name: string;
31 : version: string;
32 : }
33 :
34 1 : export const AboutCockpitModal = ({
35 1 : dialogResult
36 1 : } : {
37 : dialogResult: DialogResult<void>
38 1 : }) => {
39 1 : const [packages, setPackages] = useState<Package[]>([]);
40 :
41 1 : useInit(() => {
42 1 : const packages: Package[] = [];
43 1 : const cmd = "(set +e; rpm -qa --qf '%{NAME} %{VERSION}\\n'; dpkg-query -f '${Package} ${Version}\n' --show; pacman -Q) 2> /dev/null | grep cockpit | sort";
44 1 : cockpit.spawn(["bash", "-c", cmd], { err: "message" })
45 1 : .then(pkgs =>
46 1 : pkgs.trim().split("\n")
47 1 : .forEach(p => {
48 1 : const parts = p.split(" ");
49 1 : packages.push({ name: parts[0], version: parts[1] });
50 1 : })
51 1 : )
52 0 : .catch(error => console.error("Could not read packages versions:", error))
53 1 : .finally(() => setPackages(packages));
54 1 : });
55 :
56 1 : return (
57 1 : <AboutModal
58 1 : isOpen
59 1 : onClose={() => dialogResult.resolve()}
60 1 : id="about-cockpit-modal"
61 1 : aria-label="About Cockpit and its versions"
62 1 : trademark={_("Licensed under GNU LGPL version 2.1")}
63 1 : productName={_("Web Console")}
64 1 : brandImageSrc="../shell/images/cockpit-icon-gray.svg"
65 1 : brandImageAlt={_("Web console logo")}
66 : >
67 1 : <Content component={ContentVariants.p}>
68 1 : {_("Cockpit is an interactive Linux server admin interface.")}
69 1 : </Content>
70 1 : <Content component={ContentVariants.p}>
71 1 : <Content component={ContentVariants.a} href="https://cockpit-project.org" target="_blank" rel="noopener noreferrer">
72 1 : {_("Project website")}
73 1 : </Content>
74 1 : </Content>
75 1 : <Content component="dl">
76 0 : {packages === null && <span>{_("Loading packages...")}</span>}
77 1 : {packages !== null && packages.map(p =>
78 1 : <React.Fragment key={p.name}>
79 1 : <Content key={p.name} component="dt">{p.name}</Content>
80 1 : <Content component="dd">{p.version}</Content>
81 1 : </React.Fragment>
82 1 : )}
83 1 : </Content>
84 1 : </AboutModal>
85 : );
86 1 : };
87 :
88 : export interface LangModalProps {
89 : dialogResult: DialogResult<void>;
90 : state: ShellState;
91 : }
92 :
93 0 : export const LangModal = ({
94 0 : dialogResult,
95 0 : state
96 0 : }: LangModalProps) => {
97 0 : const language = document.cookie.replace(/(?:(?:^|.*;\s*)CockpitLang\s*=\s*([^;]*).*$)|^.*$/, "$1") || "en-us";
98 :
99 0 : const [selected, setSelected] = useState(language);
100 0 : const [searchInput, setSearchInput] = useState("");
101 :
102 0 : function onSelect() {
103 0 : if (!selected)
104 0 : return;
105 :
106 0 : const cookie = "CockpitLang=" + encodeURIComponent(selected) + "; path=/; expires=Sun, 16 Jul 3567 06:23:41 GMT";
107 0 : document.cookie = cookie;
108 0 : window.localStorage.setItem("cockpit.lang", selected);
109 0 : window.location.reload();
110 0 : }
111 :
112 0 : return (
113 0 : <Modal isOpen position="top" variant="small"
114 0 : id="display-language-modal"
115 0 : className="display-language-modal"
116 0 : onClose={() => dialogResult.resolve()}
117 : >
118 0 : <ModalHeader title={_("Display language")} />
119 0 : <ModalBody>
120 0 : <Flex direction={{ default: 'column' }}>
121 0 : <p>{_("Choose the language to be used in the application")}</p>
122 0 : <Menu id="display-language-list"
123 0 : isPlain
124 0 : isScrollable
125 0 : className="ct-menu-select-widget"
126 0 : onSelect={(_, selected) => setSelected(selected as string)}
127 0 : activeItemId={selected}
128 0 : selected={selected}>
129 0 : <MenuSearch>
130 0 : <MenuSearchInput>
131 0 : <TextInput
132 0 : value={searchInput}
133 0 : aria-label={_("Filter menu items")}
134 0 : customIcon={<SearchIcon />}
135 0 : type="search"
136 0 : onChange={(_event, value) => setSearchInput(value)}
137 0 : />
138 0 : </MenuSearchInput>
139 0 : </MenuSearch>
140 0 : <Divider />
141 0 : <MenuContent>
142 0 : <MenuList>
143 : {
144 0 : (() => {
145 0 : const locales = state.config.manifest.locales || {};
146 0 : const filteredLocales = Object.keys(locales)
147 0 : .filter(key => !searchInput || locales[key].toLowerCase().includes(searchInput.toString().toLowerCase()));
148 :
149 0 : if (filteredLocales.length === 0) {
150 0 : return (
151 0 : <MenuItem isDisabled>
152 0 : {_("No languages match")}
153 0 : </MenuItem>
154 : );
155 0 : }
156 0 : return filteredLocales.map(key => {
157 0 : return (
158 0 : <MenuItem itemId={key} key={key} data-value={key}>
159 0 : {locales[key]}
160 0 : </MenuItem>
161 : );
162 0 : });
163 0 : })()
164 : }
165 0 : </MenuList>
166 0 : </MenuContent>
167 0 : </Menu>
168 0 : </Flex>
169 0 : </ModalBody>
170 0 : <ModalFooter>
171 0 : <Button variant='primary' onClick={onSelect}>{_("Select")}</Button>
172 0 : <Button variant='link' onClick={() => dialogResult.resolve()}>{_("Cancel")}</Button>
173 0 : </ModalFooter>
174 0 : </Modal>
175 : );
176 0 : };
177 :
178 : export interface OopsModalProps {
179 : dialogResult: DialogResult<void>;
180 : }
181 :
182 0 : export function OopsModal({ dialogResult }: OopsModalProps) {
183 0 : return (
184 0 : <Modal isOpen position="top" variant="medium"
185 0 : onClose={() => dialogResult.resolve()}
186 : >
187 0 : <ModalHeader title={_("Unexpected error")} />
188 0 : <ModalBody>
189 0 : {_("Cockpit had an unexpected internal error.")}
190 0 : <br />
191 0 : <br />
192 0 : <span>{("You can try restarting Cockpit by pressing refresh in your browser. The javascript console contains details about this error") + " ("}
193 0 : <b>{_("Ctrl-Shift-I")}</b>
194 0 : {" " + _("in most browsers") + ")."}
195 0 : </span>
196 0 : </ModalBody>
197 0 : <ModalFooter>
198 0 : <Button variant='secondary' onClick={() => dialogResult.resolve()}>{_("Close")}</Button>
199 0 : </ModalFooter>
200 0 : </Modal>
201 : );
202 0 : }
|