From 7f409f6f0ce24cb2fff5dff4423083db3e7b3ca1 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sat, 26 Jul 2025 23:34:50 +1200
Subject: [PATCH 01/11] feat(ui): size, files and dirs column formatting
- Right align Size, Files and Dirs columns
- Use toLocaleString() for Files and Dirs values
---
src/components/DirectoryItems.jsx | 10 +++++++++-
src/css/App.css | 4 ++++
src/pages/SnapshotHistory.jsx | 13 ++++++++++++-
src/pages/Snapshots.jsx | 16 +++++++++-------
4 files changed, 34 insertions(+), 9 deletions(-)
diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index d93ac556..8eaf45a2 100644
--- a/src/components/DirectoryItems.jsx
+++ b/src/components/DirectoryItems.jsx
@@ -61,19 +61,27 @@ export function DirectoryItems({ historyState, items }) {
accessorFn: (x) => sizeInfo(x),
header: "Size",
width: 100,
- cell: (x) => sizeWithFailures(x.cell.getValue(), x.row.original.summ, bytesStringBase2),
+ cell: (x) =>
+ {sizeWithFailures(x.cell.getValue(), x.row.original.summ, bytesStringBase2)}
+
,
},
{
id: "files",
accessorFn: (x) => (x.summ ? x.summ.files : undefined),
header: "Files",
width: 100,
+ cell: (x) =>
+ {x.getValue().toLocaleString()}
+
},
{
id: "dirs",
accessorFn: (x) => (x.summ ? x.summ.dirs : undefined),
header: "Directories",
width: 100,
+ cell: (x) =>
+ {x.getValue().toLocaleString()}
+
},
];
diff --git a/src/css/App.css b/src/css/App.css
index f7640eaa..0e0f4eed 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -97,6 +97,10 @@ body {
background-color: var(--background-color);
}
+#kopia .table .align-right{
+ text-align: right;
+}
+
#kopia nav.navbar {
padding-left: 10px;
padding-right: 10px;
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 4dd8853b..88aca404 100644
--- a/src/pages/SnapshotHistory.jsx
+++ b/src/pages/SnapshotHistory.jsx
@@ -441,17 +441,28 @@ class SnapshotHistoryInternal extends Component {
header: "Size",
accessorFn: (x) => x.summary.size,
width: 100,
- cell: (x) => sizeWithFailures(x.cell.getValue(), x.row.original.summary, bytesStringBase2),
+ cell: (x) =>
+
+ {sizeWithFailures(x.cell.getValue(), x.row.original.summary, bytesStringBase2)}
+
,
},
{
header: "Files",
accessorFn: (x) => x.summary.files,
width: 100,
+ cell: (x) =>
+
+ {x.cell.getValue().toLocaleString()}
+
,
},
{
header: "Dirs",
accessorFn: (x) => x.summary.dirs,
width: 100,
+ cell: (x) =>
+
+ {x.cell.getValue().toLocaleString()}
+
,
},
];
diff --git a/src/pages/Snapshots.jsx b/src/pages/Snapshots.jsx
index cc993623..60d7033b 100644
--- a/src/pages/Snapshots.jsx
+++ b/src/pages/Snapshots.jsx
@@ -341,13 +341,15 @@ export class Snapshots extends Component {
width: 120,
accessorFn: (x) => (x.lastSnapshot ? x.lastSnapshot.stats.totalSize : 0),
cell: (x) =>
- sizeWithFailures(
- x.cell.getValue(),
- x.row.original.lastSnapshot && x.row.original.lastSnapshot.rootEntry
- ? x.row.original.lastSnapshot.rootEntry.summ
- : null,
- bytesStringBase2,
- ),
+
+ {sizeWithFailures(
+ x.cell.getValue(),
+ x.row.original.lastSnapshot && x.row.original.lastSnapshot.rootEntry
+ ? x.row.original.lastSnapshot.rootEntry.summ
+ : null,
+ bytesStringBase2,
+ )}
+
,
},
{
id: "lastSnapshotTime",
From e65772262ec8819cb1af243c96dcfcc42b8713b2 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 01:40:53 +1200
Subject: [PATCH 02/11] fix(ui): fixed byte representation not honored for
snapshot list (#4733)
---
src/pages/SnapshotHistory.jsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 88aca404..8886ccac 100644
--- a/src/pages/SnapshotHistory.jsx
+++ b/src/pages/SnapshotHistory.jsx
@@ -1,5 +1,5 @@
import axios from "axios";
-import React, { Component, useContext } from "react";
+import React, { Component } from "react";
import Badge from "react-bootstrap/Badge";
import Form from "react-bootstrap/Form";
import Row from "react-bootstrap/Row";
@@ -675,6 +675,7 @@ class SnapshotHistoryInternal extends Component {
);
}
}
+SnapshotHistoryInternal.contextType = UIPreferencesContext;
SnapshotHistoryInternal.propTypes = {
host: PropTypes.string,
@@ -687,7 +688,6 @@ SnapshotHistoryInternal.propTypes = {
export function SnapshotHistory(props) {
const navigate = useNavigate();
const location = useLocation();
- useContext(UIPreferencesContext);
-
+
return ;
}
From ea583ed78f13e6cc562cc14b000952064d3f89f5 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 01:30:47 +1200
Subject: [PATCH 03/11] feat(ui): added locale to preferences
---
src/contexts/UIPreferencesContext.tsx | 14 ++++++++++++++
src/pages/Preferences.jsx | 13 ++++++++++++-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/src/contexts/UIPreferencesContext.tsx b/src/contexts/UIPreferencesContext.tsx
index 985f80c9..67d32f1a 100644
--- a/src/contexts/UIPreferencesContext.tsx
+++ b/src/contexts/UIPreferencesContext.tsx
@@ -11,6 +11,7 @@ const DEFAULT_PREFERENCES = {
theme: getDefaultTheme(),
preferWebDav: false,
fontSize: "fs-6",
+ locale: "en-US",
} as SerializedUIPreferences;
const PREFERENCES_URL = "/api/v1/ui-preferences";
@@ -24,11 +25,13 @@ export interface UIPreferences {
get bytesStringBase2(): boolean;
get defaultSnapshotViewAll(): boolean;
get fontSize(): FontSize;
+ get locale(): string;
setTheme: (theme: Theme) => void;
setPageSize: (pageSize: number) => void;
setByteStringBase: (bytesStringBase2: string) => void;
setDefaultSnapshotViewAll: (defaultSnapshotViewAll: boolean) => void;
setFontSize: (size: string) => void;
+ setLocale: (locale: string) => void;
}
interface SerializedUIPreferences {
@@ -37,6 +40,7 @@ interface SerializedUIPreferences {
defaultSnapshotViewAll?: boolean;
theme: Theme;
fontSize: FontSize;
+ locale: string;
}
export interface UIPreferenceProviderProps {
@@ -108,6 +112,12 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
[],
);
+ const setLocale = (input: string) =>
+ setPreferences((oldPreferences) => {
+ return { ...oldPreferences, locale:input };
+ }
+ );
+
useEffect(() => {
axios
.get(PREFERENCES_URL)
@@ -124,6 +134,9 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
} else {
storedPreferences.pageSize = normalizePageSize(storedPreferences.pageSize);
}
+ if (!storedPreferences.locale || ( storedPreferences.locale as string ) === "") {
+ storedPreferences.locale = DEFAULT_PREFERENCES.locale;
+ }
setTheme(storedPreferences.theme);
setFontSize(storedPreferences.fontSize);
setPreferences(storedPreferences);
@@ -164,6 +177,7 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
setByteStringBase,
setDefaultSnapshotViewAll,
setFontSize,
+ setLocale,
} as UIPreferences;
return {props.children};
diff --git a/src/pages/Preferences.jsx b/src/pages/Preferences.jsx
index 2fe976ab..5e08081f 100644
--- a/src/pages/Preferences.jsx
+++ b/src/pages/Preferences.jsx
@@ -12,7 +12,7 @@ import { UIPreferencesContext } from "../contexts/UIPreferencesContext";
* Class that exports preferences
*/
export function Preferences() {
- const { theme, bytesStringBase2, fontSize, setByteStringBase, setTheme, setFontSize } =
+ const { theme, bytesStringBase2, fontSize, locale, setByteStringBase, setTheme, setFontSize, setLocale } =
useContext(UIPreferencesContext);
return (
@@ -62,6 +62,17 @@ export function Preferences() {
+
+ Locale
+ setLocale(e.target.value)}
+ />
+
From f96a77e7c4171f6262042ac8e88241715dfb691b Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 04:03:14 +1200
Subject: [PATCH 04/11] refactor(ui): added locale format util class
---
src/components/DirectoryItems.jsx | 11 ++++++-----
src/pages/SnapshotHistory.jsx | 12 ++++++------
src/utils/formatutils.js | 27 +++++++++++++++++++++++++++
3 files changed, 39 insertions(+), 11 deletions(-)
diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index 8eaf45a2..574386d7 100644
--- a/src/components/DirectoryItems.jsx
+++ b/src/components/DirectoryItems.jsx
@@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router-dom";
import KopiaTable from "./KopiaTable";
-import { objectLink, rfc3339TimestampForDisplay } from "../utils/formatutils";
+import { objectLink, LocaleFormatUtils } from "../utils/formatutils";
import { sizeWithFailures } from "../utils/uiutil";
import { UIPreferencesContext } from "../contexts/UIPreferencesContext";
import PropTypes from "prop-types";
@@ -41,7 +41,8 @@ function directoryLinkOrDownload(x, state) {
export function DirectoryItems({ historyState, items }) {
const context = React.useContext(UIPreferencesContext);
- const { bytesStringBase2 } = context;
+ const { bytesStringBase2, locale } = context;
+ const fmt = new LocaleFormatUtils(locale);
const columns = [
{
id: "name",
@@ -54,7 +55,7 @@ export function DirectoryItems({ historyState, items }) {
accessorFn: (x) => x.mtime,
header: "Last Modification",
width: 200,
- cell: (x) => rfc3339TimestampForDisplay(x.cell.getValue()),
+ cell: (x) => fmt.timestamp(x.cell.getValue()),
},
{
id: "size",
@@ -71,7 +72,7 @@ export function DirectoryItems({ historyState, items }) {
header: "Files",
width: 100,
cell: (x) =>
- {x.getValue().toLocaleString()}
+ {fmt.number(x.getValue())}
},
{
@@ -80,7 +81,7 @@ export function DirectoryItems({ historyState, items }) {
header: "Directories",
width: 100,
cell: (x) =>
- {x.getValue().toLocaleString()}
+ {fmt.number(x.getValue())}
},
];
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 8886ccac..5e51c2b0 100644
--- a/src/pages/SnapshotHistory.jsx
+++ b/src/pages/SnapshotHistory.jsx
@@ -9,7 +9,7 @@ import Spinner from "react-bootstrap/Spinner";
import { Link, useNavigate, useLocation } from "react-router-dom";
import KopiaTable from "../components/KopiaTable";
import { CLIEquivalent } from "../components/CLIEquivalent";
-import { compare, objectLink, parseQuery, rfc3339TimestampForDisplay } from "../utils/formatutils";
+import { compare, objectLink, parseQuery, LocaleFormatUtils } from "../utils/formatutils";
import { errorAlert, redirect, sizeWithFailures } from "../utils/uiutil";
import { sourceQueryStringParams } from "../utils/policyutil";
import { GoBackButton } from "../components/GoBackButton";
@@ -350,7 +350,8 @@ class SnapshotHistoryInternal extends Component {
render() {
let { snapshots, unfilteredCount, uniqueCount, isLoading, error } = this.state;
- const { bytesStringBase2 } = this.context;
+ const { bytesStringBase2, locale } = this.context;
+ const fmt = new LocaleFormatUtils(locale);
if (error) {
return {error.message}
;
}
@@ -385,10 +386,9 @@ class SnapshotHistoryInternal extends Component {
header: "Start time",
width: 200,
cell: (x) => {
- let timestamp = rfc3339TimestampForDisplay(x.row.original.startTime);
return (
- {timestamp}
+ {fmt.timestamp(x.row.original.startTime)}
);
},
@@ -452,7 +452,7 @@ class SnapshotHistoryInternal extends Component {
width: 100,
cell: (x) =>
- {x.cell.getValue().toLocaleString()}
+ {fmt.number(x.cell.getValue())}
,
},
{
@@ -461,7 +461,7 @@ class SnapshotHistoryInternal extends Component {
width: 100,
cell: (x) =>
- {x.cell.getValue().toLocaleString()}
+ {fmt.number(x.cell.getValue())}
,
},
];
diff --git a/src/utils/formatutils.js b/src/utils/formatutils.js
index cb65de00..f2472292 100644
--- a/src/utils/formatutils.js
+++ b/src/utils/formatutils.js
@@ -224,3 +224,30 @@ export function formatDuration(from, to, useMultipleUnits = false) {
return formatMilliseconds(ms, useMultipleUnits);
}
+
+export class LocaleFormatUtils{
+
+ constructor( locale ){
+ if ( !locale || locale === '' ){
+ this.locale = undefined;
+ }else{
+ this.locale = locale;
+ }
+ }
+
+ number(f) {
+ if (isNaN( parseFloat(f) )){
+ return "";
+ }
+ return f.toLocaleString(this.locale);
+ }
+
+ timestamp(ts){
+ if (!ts) {
+ return "";
+ }
+
+ let dt = new Date(ts);
+ return dt.toLocaleString( this.locale );
+ }
+}
\ No newline at end of file
From 471730a3002cc557fc7355ff74d7f5739aae7d29 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 19:15:06 +1200
Subject: [PATCH 05/11] feat(ui): added sorting to DirectoryItems Name column
(fixes: https://github.com/kopia/kopia/issues/4684)
---
src/components/DirectoryItems.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index 574386d7..8e294ec0 100644
--- a/src/components/DirectoryItems.jsx
+++ b/src/components/DirectoryItems.jsx
@@ -46,6 +46,7 @@ export function DirectoryItems({ historyState, items }) {
const columns = [
{
id: "name",
+ accessorFn: (x) => objectName(x.name, x.type),
header: "Name",
width: "",
cell: (x) => directoryLinkOrDownload(x.row.original, historyState),
From 2e1352cdae6d27ca7bafe38148eddaa9598746c9 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sat, 26 Jul 2025 23:34:50 +1200
Subject: [PATCH 06/11] feat(ui): size, files and dirs column formatting
- Right align Size, Files and Dirs columns
- Use toLocaleString() for Files and Dirs values
---
src/components/DirectoryItems.jsx | 10 +++++++++-
src/css/App.css | 4 ++++
src/pages/SnapshotHistory.jsx | 13 ++++++++++++-
src/pages/Snapshots.jsx | 16 +++++++++-------
4 files changed, 34 insertions(+), 9 deletions(-)
diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index d93ac556..8eaf45a2 100644
--- a/src/components/DirectoryItems.jsx
+++ b/src/components/DirectoryItems.jsx
@@ -61,19 +61,27 @@ export function DirectoryItems({ historyState, items }) {
accessorFn: (x) => sizeInfo(x),
header: "Size",
width: 100,
- cell: (x) => sizeWithFailures(x.cell.getValue(), x.row.original.summ, bytesStringBase2),
+ cell: (x) =>
+ {sizeWithFailures(x.cell.getValue(), x.row.original.summ, bytesStringBase2)}
+
,
},
{
id: "files",
accessorFn: (x) => (x.summ ? x.summ.files : undefined),
header: "Files",
width: 100,
+ cell: (x) =>
+ {x.getValue().toLocaleString()}
+
},
{
id: "dirs",
accessorFn: (x) => (x.summ ? x.summ.dirs : undefined),
header: "Directories",
width: 100,
+ cell: (x) =>
+ {x.getValue().toLocaleString()}
+
},
];
diff --git a/src/css/App.css b/src/css/App.css
index f7640eaa..0e0f4eed 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -97,6 +97,10 @@ body {
background-color: var(--background-color);
}
+#kopia .table .align-right{
+ text-align: right;
+}
+
#kopia nav.navbar {
padding-left: 10px;
padding-right: 10px;
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 4dd8853b..88aca404 100644
--- a/src/pages/SnapshotHistory.jsx
+++ b/src/pages/SnapshotHistory.jsx
@@ -441,17 +441,28 @@ class SnapshotHistoryInternal extends Component {
header: "Size",
accessorFn: (x) => x.summary.size,
width: 100,
- cell: (x) => sizeWithFailures(x.cell.getValue(), x.row.original.summary, bytesStringBase2),
+ cell: (x) =>
+
+ {sizeWithFailures(x.cell.getValue(), x.row.original.summary, bytesStringBase2)}
+
,
},
{
header: "Files",
accessorFn: (x) => x.summary.files,
width: 100,
+ cell: (x) =>
+
+ {x.cell.getValue().toLocaleString()}
+
,
},
{
header: "Dirs",
accessorFn: (x) => x.summary.dirs,
width: 100,
+ cell: (x) =>
+
+ {x.cell.getValue().toLocaleString()}
+
,
},
];
diff --git a/src/pages/Snapshots.jsx b/src/pages/Snapshots.jsx
index cc993623..60d7033b 100644
--- a/src/pages/Snapshots.jsx
+++ b/src/pages/Snapshots.jsx
@@ -341,13 +341,15 @@ export class Snapshots extends Component {
width: 120,
accessorFn: (x) => (x.lastSnapshot ? x.lastSnapshot.stats.totalSize : 0),
cell: (x) =>
- sizeWithFailures(
- x.cell.getValue(),
- x.row.original.lastSnapshot && x.row.original.lastSnapshot.rootEntry
- ? x.row.original.lastSnapshot.rootEntry.summ
- : null,
- bytesStringBase2,
- ),
+
+ {sizeWithFailures(
+ x.cell.getValue(),
+ x.row.original.lastSnapshot && x.row.original.lastSnapshot.rootEntry
+ ? x.row.original.lastSnapshot.rootEntry.summ
+ : null,
+ bytesStringBase2,
+ )}
+
,
},
{
id: "lastSnapshotTime",
From d10667e019dc8476ee32e7c1a04f5966a48416fb Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 01:40:53 +1200
Subject: [PATCH 07/11] fix(ui): fixed byte representation not honored for
snapshot list (#4733)
---
src/pages/SnapshotHistory.jsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 88aca404..8886ccac 100644
--- a/src/pages/SnapshotHistory.jsx
+++ b/src/pages/SnapshotHistory.jsx
@@ -1,5 +1,5 @@
import axios from "axios";
-import React, { Component, useContext } from "react";
+import React, { Component } from "react";
import Badge from "react-bootstrap/Badge";
import Form from "react-bootstrap/Form";
import Row from "react-bootstrap/Row";
@@ -675,6 +675,7 @@ class SnapshotHistoryInternal extends Component {
);
}
}
+SnapshotHistoryInternal.contextType = UIPreferencesContext;
SnapshotHistoryInternal.propTypes = {
host: PropTypes.string,
@@ -687,7 +688,6 @@ SnapshotHistoryInternal.propTypes = {
export function SnapshotHistory(props) {
const navigate = useNavigate();
const location = useLocation();
- useContext(UIPreferencesContext);
-
+
return ;
}
From cacd78c220b20a041b4260441e8cabc0e19b41e2 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 01:30:47 +1200
Subject: [PATCH 08/11] feat(ui): added locale to preferences
---
src/contexts/UIPreferencesContext.tsx | 14 ++++++++++++++
src/pages/Preferences.jsx | 13 ++++++++++++-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/src/contexts/UIPreferencesContext.tsx b/src/contexts/UIPreferencesContext.tsx
index 985f80c9..67d32f1a 100644
--- a/src/contexts/UIPreferencesContext.tsx
+++ b/src/contexts/UIPreferencesContext.tsx
@@ -11,6 +11,7 @@ const DEFAULT_PREFERENCES = {
theme: getDefaultTheme(),
preferWebDav: false,
fontSize: "fs-6",
+ locale: "en-US",
} as SerializedUIPreferences;
const PREFERENCES_URL = "/api/v1/ui-preferences";
@@ -24,11 +25,13 @@ export interface UIPreferences {
get bytesStringBase2(): boolean;
get defaultSnapshotViewAll(): boolean;
get fontSize(): FontSize;
+ get locale(): string;
setTheme: (theme: Theme) => void;
setPageSize: (pageSize: number) => void;
setByteStringBase: (bytesStringBase2: string) => void;
setDefaultSnapshotViewAll: (defaultSnapshotViewAll: boolean) => void;
setFontSize: (size: string) => void;
+ setLocale: (locale: string) => void;
}
interface SerializedUIPreferences {
@@ -37,6 +40,7 @@ interface SerializedUIPreferences {
defaultSnapshotViewAll?: boolean;
theme: Theme;
fontSize: FontSize;
+ locale: string;
}
export interface UIPreferenceProviderProps {
@@ -108,6 +112,12 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
[],
);
+ const setLocale = (input: string) =>
+ setPreferences((oldPreferences) => {
+ return { ...oldPreferences, locale:input };
+ }
+ );
+
useEffect(() => {
axios
.get(PREFERENCES_URL)
@@ -124,6 +134,9 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
} else {
storedPreferences.pageSize = normalizePageSize(storedPreferences.pageSize);
}
+ if (!storedPreferences.locale || ( storedPreferences.locale as string ) === "") {
+ storedPreferences.locale = DEFAULT_PREFERENCES.locale;
+ }
setTheme(storedPreferences.theme);
setFontSize(storedPreferences.fontSize);
setPreferences(storedPreferences);
@@ -164,6 +177,7 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
setByteStringBase,
setDefaultSnapshotViewAll,
setFontSize,
+ setLocale,
} as UIPreferences;
return {props.children};
diff --git a/src/pages/Preferences.jsx b/src/pages/Preferences.jsx
index 2fe976ab..5e08081f 100644
--- a/src/pages/Preferences.jsx
+++ b/src/pages/Preferences.jsx
@@ -12,7 +12,7 @@ import { UIPreferencesContext } from "../contexts/UIPreferencesContext";
* Class that exports preferences
*/
export function Preferences() {
- const { theme, bytesStringBase2, fontSize, setByteStringBase, setTheme, setFontSize } =
+ const { theme, bytesStringBase2, fontSize, locale, setByteStringBase, setTheme, setFontSize, setLocale } =
useContext(UIPreferencesContext);
return (
@@ -62,6 +62,17 @@ export function Preferences() {
+
+ Locale
+ setLocale(e.target.value)}
+ />
+
From dac0262238ad881157fe5509700bfe5561717a96 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 04:03:14 +1200
Subject: [PATCH 09/11] refactor(ui): added locale format util class
---
src/components/DirectoryItems.jsx | 11 ++++++-----
src/pages/SnapshotHistory.jsx | 12 ++++++------
src/utils/formatutils.js | 27 +++++++++++++++++++++++++++
3 files changed, 39 insertions(+), 11 deletions(-)
diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index 8eaf45a2..574386d7 100644
--- a/src/components/DirectoryItems.jsx
+++ b/src/components/DirectoryItems.jsx
@@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router-dom";
import KopiaTable from "./KopiaTable";
-import { objectLink, rfc3339TimestampForDisplay } from "../utils/formatutils";
+import { objectLink, LocaleFormatUtils } from "../utils/formatutils";
import { sizeWithFailures } from "../utils/uiutil";
import { UIPreferencesContext } from "../contexts/UIPreferencesContext";
import PropTypes from "prop-types";
@@ -41,7 +41,8 @@ function directoryLinkOrDownload(x, state) {
export function DirectoryItems({ historyState, items }) {
const context = React.useContext(UIPreferencesContext);
- const { bytesStringBase2 } = context;
+ const { bytesStringBase2, locale } = context;
+ const fmt = new LocaleFormatUtils(locale);
const columns = [
{
id: "name",
@@ -54,7 +55,7 @@ export function DirectoryItems({ historyState, items }) {
accessorFn: (x) => x.mtime,
header: "Last Modification",
width: 200,
- cell: (x) => rfc3339TimestampForDisplay(x.cell.getValue()),
+ cell: (x) => fmt.timestamp(x.cell.getValue()),
},
{
id: "size",
@@ -71,7 +72,7 @@ export function DirectoryItems({ historyState, items }) {
header: "Files",
width: 100,
cell: (x) =>
- {x.getValue().toLocaleString()}
+ {fmt.number(x.getValue())}
},
{
@@ -80,7 +81,7 @@ export function DirectoryItems({ historyState, items }) {
header: "Directories",
width: 100,
cell: (x) =>
- {x.getValue().toLocaleString()}
+ {fmt.number(x.getValue())}
},
];
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 8886ccac..5e51c2b0 100644
--- a/src/pages/SnapshotHistory.jsx
+++ b/src/pages/SnapshotHistory.jsx
@@ -9,7 +9,7 @@ import Spinner from "react-bootstrap/Spinner";
import { Link, useNavigate, useLocation } from "react-router-dom";
import KopiaTable from "../components/KopiaTable";
import { CLIEquivalent } from "../components/CLIEquivalent";
-import { compare, objectLink, parseQuery, rfc3339TimestampForDisplay } from "../utils/formatutils";
+import { compare, objectLink, parseQuery, LocaleFormatUtils } from "../utils/formatutils";
import { errorAlert, redirect, sizeWithFailures } from "../utils/uiutil";
import { sourceQueryStringParams } from "../utils/policyutil";
import { GoBackButton } from "../components/GoBackButton";
@@ -350,7 +350,8 @@ class SnapshotHistoryInternal extends Component {
render() {
let { snapshots, unfilteredCount, uniqueCount, isLoading, error } = this.state;
- const { bytesStringBase2 } = this.context;
+ const { bytesStringBase2, locale } = this.context;
+ const fmt = new LocaleFormatUtils(locale);
if (error) {
return {error.message}
;
}
@@ -385,10 +386,9 @@ class SnapshotHistoryInternal extends Component {
header: "Start time",
width: 200,
cell: (x) => {
- let timestamp = rfc3339TimestampForDisplay(x.row.original.startTime);
return (
- {timestamp}
+ {fmt.timestamp(x.row.original.startTime)}
);
},
@@ -452,7 +452,7 @@ class SnapshotHistoryInternal extends Component {
width: 100,
cell: (x) =>
- {x.cell.getValue().toLocaleString()}
+ {fmt.number(x.cell.getValue())}
,
},
{
@@ -461,7 +461,7 @@ class SnapshotHistoryInternal extends Component {
width: 100,
cell: (x) =>
- {x.cell.getValue().toLocaleString()}
+ {fmt.number(x.cell.getValue())}
,
},
];
diff --git a/src/utils/formatutils.js b/src/utils/formatutils.js
index cb65de00..f2472292 100644
--- a/src/utils/formatutils.js
+++ b/src/utils/formatutils.js
@@ -224,3 +224,30 @@ export function formatDuration(from, to, useMultipleUnits = false) {
return formatMilliseconds(ms, useMultipleUnits);
}
+
+export class LocaleFormatUtils{
+
+ constructor( locale ){
+ if ( !locale || locale === '' ){
+ this.locale = undefined;
+ }else{
+ this.locale = locale;
+ }
+ }
+
+ number(f) {
+ if (isNaN( parseFloat(f) )){
+ return "";
+ }
+ return f.toLocaleString(this.locale);
+ }
+
+ timestamp(ts){
+ if (!ts) {
+ return "";
+ }
+
+ let dt = new Date(ts);
+ return dt.toLocaleString( this.locale );
+ }
+}
\ No newline at end of file
From e6d2fc22df2d700abbd96f7b7e78f94d4ff7727b Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Sun, 27 Jul 2025 19:15:06 +1200
Subject: [PATCH 10/11] feat(ui): added sorting to DirectoryItems Name column
(fixes: https://github.com/kopia/kopia/issues/4684)
---
src/components/DirectoryItems.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index 574386d7..8e294ec0 100644
--- a/src/components/DirectoryItems.jsx
+++ b/src/components/DirectoryItems.jsx
@@ -46,6 +46,7 @@ export function DirectoryItems({ historyState, items }) {
const columns = [
{
id: "name",
+ accessorFn: (x) => objectName(x.name, x.type),
header: "Name",
width: "",
cell: (x) => directoryLinkOrDownload(x.row.original, historyState),
From 9cdf7781a2cf48b24bc10499fc2b67d1164519a2 Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Tue, 7 Oct 2025 04:35:59 +1300
Subject: [PATCH 11/11] ran prettier
---
src/components/DirectoryItems.jsx | 14 +++++---------
src/contexts/UIPreferencesContext.tsx | 7 +++----
src/css/App.css | 2 +-
src/pages/SnapshotHistory.jsx | 17 ++++++-----------
src/pages/Snapshots.jsx | 5 +++--
src/utils/formatutils.js | 19 +++++++++----------
6 files changed, 27 insertions(+), 37 deletions(-)
diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index 8e294ec0..57604e3c 100644
--- a/src/components/DirectoryItems.jsx
+++ b/src/components/DirectoryItems.jsx
@@ -63,27 +63,23 @@ export function DirectoryItems({ historyState, items }) {
accessorFn: (x) => sizeInfo(x),
header: "Size",
width: 100,
- cell: (x) =>
- {sizeWithFailures(x.cell.getValue(), x.row.original.summ, bytesStringBase2)}
-
,
+ cell: (x) => (
+ {sizeWithFailures(x.cell.getValue(), x.row.original.summ, bytesStringBase2)}
+ ),
},
{
id: "files",
accessorFn: (x) => (x.summ ? x.summ.files : undefined),
header: "Files",
width: 100,
- cell: (x) =>
- {fmt.number(x.getValue())}
-
+ cell: (x) => {fmt.number(x.getValue())}
,
},
{
id: "dirs",
accessorFn: (x) => (x.summ ? x.summ.dirs : undefined),
header: "Directories",
width: 100,
- cell: (x) =>
- {fmt.number(x.getValue())}
-
+ cell: (x) => {fmt.number(x.getValue())}
,
},
];
diff --git a/src/contexts/UIPreferencesContext.tsx b/src/contexts/UIPreferencesContext.tsx
index 67d32f1a..d8ff52f9 100644
--- a/src/contexts/UIPreferencesContext.tsx
+++ b/src/contexts/UIPreferencesContext.tsx
@@ -114,9 +114,8 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
const setLocale = (input: string) =>
setPreferences((oldPreferences) => {
- return { ...oldPreferences, locale:input };
- }
- );
+ return { ...oldPreferences, locale: input };
+ });
useEffect(() => {
axios
@@ -134,7 +133,7 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
} else {
storedPreferences.pageSize = normalizePageSize(storedPreferences.pageSize);
}
- if (!storedPreferences.locale || ( storedPreferences.locale as string ) === "") {
+ if (!storedPreferences.locale || (storedPreferences.locale as string) === "") {
storedPreferences.locale = DEFAULT_PREFERENCES.locale;
}
setTheme(storedPreferences.theme);
diff --git a/src/css/App.css b/src/css/App.css
index 0e0f4eed..fd3ea033 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -97,7 +97,7 @@ body {
background-color: var(--background-color);
}
-#kopia .table .align-right{
+#kopia .table .align-right {
text-align: right;
}
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 5e51c2b0..9f353e31 100644
--- a/src/pages/SnapshotHistory.jsx
+++ b/src/pages/SnapshotHistory.jsx
@@ -441,28 +441,23 @@ class SnapshotHistoryInternal extends Component {
header: "Size",
accessorFn: (x) => x.summary.size,
width: 100,
- cell: (x) =>
+ cell: (x) => (
{sizeWithFailures(x.cell.getValue(), x.row.original.summary, bytesStringBase2)}
-
,
+
+ ),
},
{
header: "Files",
accessorFn: (x) => x.summary.files,
width: 100,
- cell: (x) =>
-
- {fmt.number(x.cell.getValue())}
-
,
+ cell: (x) => {fmt.number(x.cell.getValue())}
,
},
{
header: "Dirs",
accessorFn: (x) => x.summary.dirs,
width: 100,
- cell: (x) =>
-
- {fmt.number(x.cell.getValue())}
-
,
+ cell: (x) => {fmt.number(x.cell.getValue())}
,
},
];
@@ -688,6 +683,6 @@ SnapshotHistoryInternal.propTypes = {
export function SnapshotHistory(props) {
const navigate = useNavigate();
const location = useLocation();
-
+
return ;
}
diff --git a/src/pages/Snapshots.jsx b/src/pages/Snapshots.jsx
index 60d7033b..0aadad68 100644
--- a/src/pages/Snapshots.jsx
+++ b/src/pages/Snapshots.jsx
@@ -340,7 +340,7 @@ export class Snapshots extends Component {
header: "Size",
width: 120,
accessorFn: (x) => (x.lastSnapshot ? x.lastSnapshot.stats.totalSize : 0),
- cell: (x) =>
+ cell: (x) => (
{sizeWithFailures(
x.cell.getValue(),
@@ -349,7 +349,8 @@ export class Snapshots extends Component {
: null,
bytesStringBase2,
)}
-
,
+
+ ),
},
{
id: "lastSnapshotTime",
diff --git a/src/utils/formatutils.js b/src/utils/formatutils.js
index f2472292..81b243d9 100644
--- a/src/utils/formatutils.js
+++ b/src/utils/formatutils.js
@@ -225,29 +225,28 @@ export function formatDuration(from, to, useMultipleUnits = false) {
return formatMilliseconds(ms, useMultipleUnits);
}
-export class LocaleFormatUtils{
-
- constructor( locale ){
- if ( !locale || locale === '' ){
+export class LocaleFormatUtils {
+ constructor(locale) {
+ if (!locale || locale === "") {
this.locale = undefined;
- }else{
+ } else {
this.locale = locale;
}
}
-
+
number(f) {
- if (isNaN( parseFloat(f) )){
+ if (isNaN(parseFloat(f))) {
return "";
}
return f.toLocaleString(this.locale);
}
- timestamp(ts){
+ timestamp(ts) {
if (!ts) {
return "";
}
let dt = new Date(ts);
- return dt.toLocaleString( this.locale );
+ return dt.toLocaleString(this.locale);
}
-}
\ No newline at end of file
+}