diff --git a/src/components/DirectoryItems.jsx b/src/components/DirectoryItems.jsx
index d93ac556..57604e3c 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,10 +41,12 @@ 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",
+ accessorFn: (x) => objectName(x.name, x.type),
header: "Name",
width: "",
cell: (x) => directoryLinkOrDownload(x.row.original, historyState),
@@ -54,26 +56,30 @@ 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",
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())}
,
},
{
id: "dirs",
accessorFn: (x) => (x.summ ? x.summ.dirs : undefined),
header: "Directories",
width: 100,
+ cell: (x) => {fmt.number(x.getValue())}
,
},
];
diff --git a/src/contexts/UIPreferencesContext.tsx b/src/contexts/UIPreferencesContext.tsx
index 985f80c9..d8ff52f9 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,11 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
[],
);
+ const setLocale = (input: string) =>
+ setPreferences((oldPreferences) => {
+ return { ...oldPreferences, locale: input };
+ });
+
useEffect(() => {
axios
.get(PREFERENCES_URL)
@@ -124,6 +133,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 +176,7 @@ export function UIPreferenceProvider(props: UIPreferenceProviderProps) {
setByteStringBase,
setDefaultSnapshotViewAll,
setFontSize,
+ setLocale,
} as UIPreferences;
return {props.children};
diff --git a/src/css/App.css b/src/css/App.css
index f7640eaa..fd3ea033 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/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)}
+ />
+
diff --git a/src/pages/SnapshotHistory.jsx b/src/pages/SnapshotHistory.jsx
index 4dd8853b..9f353e31 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";
@@ -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)}
);
},
@@ -441,17 +441,23 @@ 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) => {fmt.number(x.cell.getValue())}
,
},
{
header: "Dirs",
accessorFn: (x) => x.summary.dirs,
width: 100,
+ cell: (x) => {fmt.number(x.cell.getValue())}
,
},
];
@@ -664,6 +670,7 @@ class SnapshotHistoryInternal extends Component {
);
}
}
+SnapshotHistoryInternal.contextType = UIPreferencesContext;
SnapshotHistoryInternal.propTypes = {
host: PropTypes.string,
@@ -676,7 +683,6 @@ SnapshotHistoryInternal.propTypes = {
export function SnapshotHistory(props) {
const navigate = useNavigate();
const location = useLocation();
- useContext(UIPreferencesContext);
return ;
}
diff --git a/src/pages/Snapshots.jsx b/src/pages/Snapshots.jsx
index cc993623..0aadad68 100644
--- a/src/pages/Snapshots.jsx
+++ b/src/pages/Snapshots.jsx
@@ -340,14 +340,17 @@ export class Snapshots extends Component {
header: "Size",
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,
- ),
+ cell: (x) => (
+
+ {sizeWithFailures(
+ x.cell.getValue(),
+ x.row.original.lastSnapshot && x.row.original.lastSnapshot.rootEntry
+ ? x.row.original.lastSnapshot.rootEntry.summ
+ : null,
+ bytesStringBase2,
+ )}
+
+ ),
},
{
id: "lastSnapshotTime",
diff --git a/src/utils/formatutils.js b/src/utils/formatutils.js
index cb65de00..81b243d9 100644
--- a/src/utils/formatutils.js
+++ b/src/utils/formatutils.js
@@ -224,3 +224,29 @@ 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);
+ }
+}