From 8541832f7d4802b2550cdaf401f705c2e42fa7ca Mon Sep 17 00:00:00 2001 From: Kazi Date: Sat, 12 Jul 2025 13:22:23 +0600 Subject: [PATCH 01/10] Fix type errors --- urbackupserver/www2/src/api/urbackupserver.ts | 16 +++++++---- .../www2/src/components/ErrorPage.tsx | 28 ++++++++++++++----- .../features/settings/Fields/TextField.tsx | 2 ++ .../SettingsServer/SettingsServer.tsx | 2 +- .../features/status/BackupResultContext.tsx | 2 +- .../www2/src/features/status/LastBackups.tsx | 20 +++++++++---- 6 files changed, 49 insertions(+), 21 deletions(-) diff --git a/urbackupserver/www2/src/api/urbackupserver.ts b/urbackupserver/www2/src/api/urbackupserver.ts index ee2a3d286..a428c8c2d 100644 --- a/urbackupserver/www2/src/api/urbackupserver.ts +++ b/urbackupserver/www2/src/api/urbackupserver.ts @@ -571,11 +571,11 @@ export enum AddUserResult { function randomString() { - var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; - var string_length = 50; - var randomstring = ''; + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + const string_length = 50; + let randomstring = ''; - var array = new Uint32Array(string_length); + const array = new Uint32Array(string_length); if(window.crypto && window.crypto.getRandomValues(array)) { for (var i=0; i { + saveGeneralSettings = async ( + settings: Partial & { + settings: GeneralSettings['settings'] + }, + ) => { const params : Record = { "sa": "general_save" }; for (const [key, value] of Object.entries(settings.settings)) { if (typeof value == "object") diff --git a/urbackupserver/www2/src/components/ErrorPage.tsx b/urbackupserver/www2/src/components/ErrorPage.tsx index 3582978ea..5bb9f98d3 100644 --- a/urbackupserver/www2/src/components/ErrorPage.tsx +++ b/urbackupserver/www2/src/components/ErrorPage.tsx @@ -1,4 +1,4 @@ -import { useRouteError } from "react-router-dom"; +import { isRouteErrorResponse, useRouteError } from "react-router-dom"; import { BackupsAccessDeniedError } from "../api/urbackupserver"; @@ -9,9 +9,7 @@ export function ErrorPage({ returnToLink }: { returnToLink: React.ReactNode }) { return (

Backups Access Denied

-

- {error.statusText || error.message} -

+

Return to {returnToLink}

); @@ -20,10 +18,26 @@ export function ErrorPage({ returnToLink }: { returnToLink: React.ReactNode }) { return (

Page not found

-

- {error.statusText || error.message} -

+

Return to {returnToLink}

); } + +function ErrorPageContent({ error }: { error: unknown }) { + if (isRouteErrorResponse(error)) { + return ( +

+ {error.statusText} +

+ ); + } + + if (error instanceof Error) { + return ( +

+ {error.message} +

+ ); + } +} diff --git a/urbackupserver/www2/src/features/settings/Fields/TextField.tsx b/urbackupserver/www2/src/features/settings/Fields/TextField.tsx index de22a7b3d..df0ee39c7 100644 --- a/urbackupserver/www2/src/features/settings/Fields/TextField.tsx +++ b/urbackupserver/www2/src/features/settings/Fields/TextField.tsx @@ -51,6 +51,8 @@ export function TextField({ return ( (
diff --git a/urbackupserver/www2/src/features/settings/SettingsServer/SettingsServer.tsx b/urbackupserver/www2/src/features/settings/SettingsServer/SettingsServer.tsx index eca70a0ec..70ea7e938 100644 --- a/urbackupserver/www2/src/features/settings/SettingsServer/SettingsServer.tsx +++ b/urbackupserver/www2/src/features/settings/SettingsServer/SettingsServer.tsx @@ -119,7 +119,7 @@ function FormSection({ type={f.type} onChange={(data) => { const newSetting = { - [f.name]: f.transformer?.api(+data) ?? data, + [f.name]: f.transformer?.api(Number(data)) ?? data, }; updateSettings(newSetting); diff --git a/urbackupserver/www2/src/features/status/BackupResultContext.tsx b/urbackupserver/www2/src/features/status/BackupResultContext.tsx index 28c893e5b..44bdf533f 100644 --- a/urbackupserver/www2/src/features/status/BackupResultContext.tsx +++ b/urbackupserver/www2/src/features/status/BackupResultContext.tsx @@ -27,7 +27,7 @@ export const BackupResultProvider = ({ // Clear oldest backupResults every 3s, if any useEffect(() => { - let timeoutId: number; + let timeoutId: ReturnType; if (backupResults.length) { timeoutId = setTimeout(() => { setBackupResults((prev) => prev.slice(1)); diff --git a/urbackupserver/www2/src/features/status/LastBackups.tsx b/urbackupserver/www2/src/features/status/LastBackups.tsx index 14ae5c4e3..559533233 100644 --- a/urbackupserver/www2/src/features/status/LastBackups.tsx +++ b/urbackupserver/www2/src/features/status/LastBackups.tsx @@ -20,8 +20,7 @@ import { useBackupResult } from "./BackupResultContext"; import { formatDatetime } from "../../utils/format"; export function LastFileBackup(item: StatusClientItem) { - const formattedLastBackup = - item.lastbackup === 0 ? "Never" : formatDatetime(item.lastbackup); + const formattedLastBackup = formatLastBackup(item.lastbackup); const fileBackupProcesses = item.processes.filter( (p) => @@ -46,10 +45,7 @@ export function LastFileBackup(item: StatusClientItem) { } export function LastImageBackup(item: StatusClientItem) { - const formattedLastBackup = - item.lastbackup_image === 0 - ? "Never" - : formatDatetime(item.lastbackup_image); + const formattedLastBackup = formatLastBackup(item.lastbackup_image); const imageBackupProcesses = item.processes.filter( (p) => @@ -136,3 +132,15 @@ function ProcessResult({ ); }); } + +function formatLastBackup( + lastBackup: + | StatusClientItem["lastbackup"] + | StatusClientItem["lastbackup_image"], +) { + if (lastBackup === 0 || lastBackup === "-") { + return "Never"; + } + + return formatDatetime(lastBackup); +} From 0427998c85ce01f6fb91ca3b76b4fc7e3f7268ce Mon Sep 17 00:00:00 2001 From: Kazi Date: Tue, 29 Jul 2025 17:15:24 +0600 Subject: [PATCH 02/10] Fix Activities button spacing --- urbackupserver/www2/src/components/StackStyles.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/www2/src/components/StackStyles.tsx b/urbackupserver/www2/src/components/StackStyles.tsx index ec841426e..4ebe43583 100644 --- a/urbackupserver/www2/src/components/StackStyles.tsx +++ b/urbackupserver/www2/src/components/StackStyles.tsx @@ -52,7 +52,7 @@ export const useStackStyles = makeStyles({ marginInline: "auto", }, sidebar: { - width: "22ch", + width: "19ch", borderRight: "1px solid", padding: "10pt", position: "sticky", From 783debf76d642e67c17074fc0d92738c826871fd Mon Sep 17 00:00:00 2001 From: Kazi Date: Thu, 31 Jul 2025 16:27:28 +0600 Subject: [PATCH 03/10] Refactor to remove use of CSS-in-JS with CSS --- urbackupserver/www2/src/App.tsx | 60 ++++++----------- .../www2/src/components/HeaderBar.tsx | 18 ++---- .../www2/src/components/Layout.module.css | 29 +++++++++ urbackupserver/www2/src/components/Layout.tsx | 23 +++++++ .../src/components/SelectClientCombobox.tsx | 2 +- .../www2/src/components/StackStyles.tsx | 64 ------------------- urbackupserver/www2/src/css/global.css | 60 +++++++++++++++-- .../activities/OngoingActivitiesActions.tsx | 22 ++----- .../features/backups/BackupContentTable.tsx | 38 ++--------- .../src/features/backups/BackupsTable.tsx | 13 +--- .../features/backups/ClientBackupActions.tsx | 37 ++++------- .../features/backups/ClientBackupsTable.tsx | 17 +---- .../www2/src/features/logs/ClientLog.tsx | 32 +--------- .../src/features/logs/ClientLogs.module.css | 15 +++++ .../www2/src/features/logs/ClientLogs.tsx | 38 ++--------- .../www2/src/features/logs/LiveLog.tsx | 25 ++------ .../www2/src/features/logs/LogReports.tsx | 13 +--- .../www2/src/features/logs/LogTable.tsx | 27 ++------ .../www2/src/features/logs/LogsTable.tsx | 21 ++---- .../src/features/status/DownloadClient.tsx | 25 +------- .../www2/src/features/status/LastBackups.tsx | 26 ++------ urbackupserver/www2/src/pages/Status.tsx | 14 +--- 22 files changed, 213 insertions(+), 406 deletions(-) create mode 100644 urbackupserver/www2/src/components/Layout.module.css create mode 100644 urbackupserver/www2/src/components/Layout.tsx delete mode 100644 urbackupserver/www2/src/components/StackStyles.tsx create mode 100644 urbackupserver/www2/src/features/logs/ClientLogs.module.css diff --git a/urbackupserver/www2/src/App.tsx b/urbackupserver/www2/src/App.tsx index e215923f8..7fedc1f66 100644 --- a/urbackupserver/www2/src/App.tsx +++ b/urbackupserver/www2/src/App.tsx @@ -1,6 +1,5 @@ import * as React from "react"; import { Suspense, useEffect, useState } from "react"; -import HeaderBar from "./components/HeaderBar"; import NavSidebar from "./components/NavSidebar"; import { proxy, useSnapshot } from "valtio"; import { createHashRouter, RouterProvider } from "react-router-dom"; @@ -13,11 +12,9 @@ import { teamsDarkTheme, Spinner, Toaster, - mergeClasses, Link, } from "@fluentui/react-components"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import { useStackStyles } from "./components/StackStyles"; import UrBackupServer, { SessionNotFoundError } from "./api/urbackupserver"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { i18n } from "@lingui/core"; @@ -34,6 +31,7 @@ import { ClientLog } from "./features/logs/ClientLog"; import { SettingsPage } from "./pages/SettingsPage"; import { SettingsNavSidebar } from "./features/settings/SettingsNavSidebar"; import { SettingsServer } from "./features/settings/SettingsServer/SettingsServer"; +import { Layout } from "./components/Layout"; import "./css/global.css"; const initialDark = @@ -241,52 +239,30 @@ const App: React.FunctionComponent = () => { })(); }, []); - const styles = useStackStyles(); - return ( -
-
- -
-
-
- {snap.loggedIn && ( -
- {snap.activePage === Pages.Settings ? ( - // TODO: Move sidebars into RouterProvider via common layout - }> - - - ) : ( - - )} -
- )} -
+ + {snap.loggedIn && ( + + {snap.activePage === Pages.Settings ? ( + // TODO: Move sidebars into RouterProvider via common layout }> - + -
-
-
-
+ ) : ( + + )} + + )} + + }> + + + + {/* Following only bundled in development mode */} diff --git a/urbackupserver/www2/src/components/HeaderBar.tsx b/urbackupserver/www2/src/components/HeaderBar.tsx index 6910027e9..23310c651 100644 --- a/urbackupserver/www2/src/components/HeaderBar.tsx +++ b/urbackupserver/www2/src/components/HeaderBar.tsx @@ -1,20 +1,16 @@ import logoImage from "../assets/urbackup.png"; -import { useStackStyles } from "./StackStyles"; import { Avatar, Image } from "@fluentui/react-components"; export const HeaderBar = () => { - const styles = useStackStyles(); - return ( -
-
- -
-
UrBackup
-
-
- +
+
+
+ +
+
UrBackup
+
); }; diff --git a/urbackupserver/www2/src/components/Layout.module.css b/urbackupserver/www2/src/components/Layout.module.css new file mode 100644 index 000000000..254147779 --- /dev/null +++ b/urbackupserver/www2/src/components/Layout.module.css @@ -0,0 +1,29 @@ +.stackVertical { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.stackHorizontal { + display: flex; + flex: 1; +} + +.content { + flex: 1; + padding-inline: 10pt; + padding-block: 10pt 40pt; + max-width: 1200px; + margin-inline: auto; +} + +.sidebar { + width: 19ch; + border-right: 1px solid; + padding: 10pt; + position: sticky; + top: 0; + left: 0; + max-height: 100vh; + overflow-y: auto; +} diff --git a/urbackupserver/www2/src/components/Layout.tsx b/urbackupserver/www2/src/components/Layout.tsx new file mode 100644 index 000000000..14a686ca7 --- /dev/null +++ b/urbackupserver/www2/src/components/Layout.tsx @@ -0,0 +1,23 @@ +import HeaderBar from "./HeaderBar"; +import styles from "./Layout.module.css"; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( +
+ +
{children}
+
+ ); +} + +function Sidebar({ children }: { children: React.ReactNode }) { + return ; +} + +function Content({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +Layout.Sidebar = Sidebar; + +Layout.Content = Content; diff --git a/urbackupserver/www2/src/components/SelectClientCombobox.tsx b/urbackupserver/www2/src/components/SelectClientCombobox.tsx index 7053262c2..9709fc420 100644 --- a/urbackupserver/www2/src/components/SelectClientCombobox.tsx +++ b/urbackupserver/www2/src/components/SelectClientCombobox.tsx @@ -31,7 +31,7 @@ export function SelectClientCombobox({ const labelId = useId(); return ( -
+
{showLabel && (