diff --git a/urbackupserver/www2/src/api/urbackupserver.ts b/urbackupserver/www2/src/api/urbackupserver.ts index bf865b8eb..8cc4fcbd2 100644 --- a/urbackupserver/www2/src/api/urbackupserver.ts +++ b/urbackupserver/www2/src/api/urbackupserver.ts @@ -169,7 +169,7 @@ function calcPwHash( return MD5(rnd + pwmd5).toString(); } -type OsType = "windows" | "linux" | "mac"; +export type OsType = "windows" | "linux" | "mac"; export class SessionNotFoundError extends Error {} diff --git a/urbackupserver/www2/src/features/status/DownloadClient.tsx b/urbackupserver/www2/src/features/status/DownloadClient.tsx new file mode 100644 index 000000000..d61b88f2d --- /dev/null +++ b/urbackupserver/www2/src/features/status/DownloadClient.tsx @@ -0,0 +1,105 @@ +import { Button, Combobox, ComboboxProps, makeStyles, OptionOnSelectData, Popover, PopoverSurface, PopoverTrigger, tokens, useComboboxFilter } from "@fluentui/react-components"; +import { useId, useState } from "react"; +import { urbackupServer } from "../../App"; +import { OsType, StatusClientItem } from "../../api/urbackupserver"; + + +const useStyles = makeStyles({ + surface: { + marginInline: tokens.spacingHorizontalS, + }, + combobox: { + display: "grid", + justifyItems: "start", + gap: tokens.spacingHorizontalXS, + }, + listbox: { + translate: '-20px -40px' + } +}); + + + +export function DownloadClient({ + clients, + os, + children +}: { + clients: StatusClientItem[], + os: OsType, + children: React.ReactNode +}) { + const styles = useStyles(); + const id = useId(); + + const [open, setOpen] = useState(false); + + const options = clients.map(client => ({ + children: client.name, value: client.name + })) + + const comboId = useId(); + + const [query, setQuery] = useState(""); + const comboBoxChildren = useComboboxFilter(query, options, { + noOptionsMessage: `No results matched "${query}"` + }); + + const onOptionSelect: ComboboxProps["onOptionSelect"] = (_, data) => { + const text = data.optionValue + const selectedClient = clients.find(client => client.name === text) + + if (!selectedClient) { + return + } + + downloadClientFromId(selectedClient?.id, os) + + setOpen(false) + setQuery(""); + }; + + + return ( + { + const state = data.open ?? false + setOpen(state) + + if (state === false) { + setQuery("") + } + + }}> + + + + + +
+ + setQuery(ev.target.value)} + value={query} + listbox={{ + className: styles.listbox + }} + > + {comboBoxChildren} + +
+
+
+ ) +} + +function downloadClientFromId(id: number, os: OsType) { + location.href = urbackupServer.downloadClientURL( + id, + undefined, + os + ); +} \ No newline at end of file diff --git a/urbackupserver/www2/src/features/status/index.ts b/urbackupserver/www2/src/features/status/index.ts index 4543f4bbb..e5a4e8fd7 100644 --- a/urbackupserver/www2/src/features/status/index.ts +++ b/urbackupserver/www2/src/features/status/index.ts @@ -1 +1,2 @@ export * from "./StatusMenuAction"; +export * from './DownloadClient' \ No newline at end of file diff --git a/urbackupserver/www2/src/pages/Status.tsx b/urbackupserver/www2/src/pages/Status.tsx index e50e21640..9ba9b068f 100644 --- a/urbackupserver/www2/src/pages/Status.tsx +++ b/urbackupserver/www2/src/pages/Status.tsx @@ -30,7 +30,7 @@ import { ChevronLeft20Filled, ChevronRight20Filled, } from "@fluentui/react-icons"; -import { StatusMenuAction } from "../features/status"; +import { DownloadClient, StatusMenuAction } from "../features/status"; import { useStatusClientActions } from "../features/status/useStatusClientActions"; // Register icons used in Pagination @fluentui/react-experiments. See https://github.com/microsoft/fluentui/wiki/Using-icons#registering-custom-icons. @@ -145,6 +145,7 @@ const useStyles = makeStyles({ gridActions: { display: "flex", gap: tokens.spacingHorizontalS, + flexWrap: 'wrap' }, }); @@ -285,17 +286,14 @@ const Status = () => { idList={selectedRowsArray} trigger={With Selected} /> - + +
+ + Download client for Windows + + + Download client for Linux +