diff --git a/src/site/src/components/routes/settings/alerts-history-data-table.tsx b/src/site/src/components/routes/settings/alerts-history-data-table.tsx
index 5f064d7..2cdd94f 100644
--- a/src/site/src/components/routes/settings/alerts-history-data-table.tsx
+++ b/src/site/src/components/routes/settings/alerts-history-data-table.tsx
@@ -1,26 +1,16 @@
-import { pb } from "@/lib/api"
-import { cn, formatDuration, formatShortDate } from "@/lib/utils"
-import { alertInfo } from "@/lib/alerts"
-import { AlertsHistoryRecord } from "@/types"
+import { t } from "@lingui/core/macro"
+import { Trans } from "@lingui/react/macro"
import {
+ type ColumnFiltersState,
+ flexRender,
getCoreRowModel,
+ getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
- getFilteredRowModel,
+ type SortingState,
useReactTable,
- flexRender,
- ColumnFiltersState,
- SortingState,
- VisibilityState,
+ type VisibilityState,
} from "@tanstack/react-table"
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
-import { Button, buttonVariants } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { alertsHistoryColumns } from "../../alerts-history-columns"
-import { Checkbox } from "@/components/ui/checkbox"
-import { memo, useEffect, useState } from "react"
-import { Label } from "@/components/ui/label"
-import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"
import {
ChevronLeftIcon,
ChevronRightIcon,
@@ -29,9 +19,7 @@ import {
DownloadIcon,
Trash2Icon,
} from "lucide-react"
-import { Trans } from "@lingui/react/macro"
-import { t } from "@lingui/core/macro"
-import { useToast } from "@/components/ui/use-toast"
+import { memo, useEffect, useState } from "react"
import {
AlertDialog,
AlertDialogAction,
@@ -43,6 +31,18 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
+import { Button, buttonVariants } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
+import { useToast } from "@/components/ui/use-toast"
+import { alertInfo } from "@/lib/alerts"
+import { pb } from "@/lib/api"
+import { cn, formatDuration, formatShortDate } from "@/lib/utils"
+import type { AlertsHistoryRecord } from "@/types"
+import { alertsHistoryColumns } from "../../alerts-history-columns"
const SectionIntro = memo(() => {
return (
diff --git a/src/site/src/components/routes/settings/config-yaml.tsx b/src/site/src/components/routes/settings/config-yaml.tsx
index 2a3b8d8..4cbd251 100644
--- a/src/site/src/components/routes/settings/config-yaml.tsx
+++ b/src/site/src/components/routes/settings/config-yaml.tsx
@@ -1,15 +1,15 @@
import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
-import { Separator } from "@/components/ui/separator"
-import { Button } from "@/components/ui/button"
import { redirectPage } from "@nanostores/router"
-import { $router } from "@/components/router"
+import clsx from "clsx"
import { AlertCircleIcon, FileSlidersIcon, LoaderCircleIcon } from "lucide-react"
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { useState } from "react"
+import { $router } from "@/components/router"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import { Button } from "@/components/ui/button"
+import { Separator } from "@/components/ui/separator"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/use-toast"
-import clsx from "clsx"
import { isAdmin, pb } from "@/lib/api"
export default function ConfigYaml() {
diff --git a/src/site/src/components/routes/settings/general.tsx b/src/site/src/components/routes/settings/general.tsx
index 7b10214..6d8e4cd 100644
--- a/src/site/src/components/routes/settings/general.tsx
+++ b/src/site/src/components/routes/settings/general.tsx
@@ -1,18 +1,18 @@
-import { Trans } from "@lingui/react/macro"
+/** biome-ignore-all lint/correctness/useUniqueElementIds: component is only rendered once */
+import { Trans, useLingui } from "@lingui/react/macro"
+import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from "lucide-react"
+import { useState } from "react"
import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { chartTimeData } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
-import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from "lucide-react"
-import { UserSettings } from "@/types"
-import { saveSettings } from "./layout"
-import { useState } from "react"
-import languages from "@/lib/languages"
+import { HourFormat, Unit } from "@/lib/enums"
import { dynamicActivate } from "@/lib/i18n"
-import { useLingui } from "@lingui/react/macro"
-import { Input } from "@/components/ui/input"
-import { Unit } from "@/lib/enums"
+import languages from "@/lib/languages"
+import { chartTimeData, currentHour12 } from "@/lib/utils"
+import type { UserSettings } from "@/types"
+import { saveSettings } from "./layout"
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
const [isLoading, setIsLoading] = useState(false)
@@ -82,24 +82,46 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
Adjust display options for charts.
-
diff --git a/src/site/src/components/routes/settings/layout.tsx b/src/site/src/components/routes/settings/layout.tsx
index d64ce6b..e1ea4b8 100644
--- a/src/site/src/components/routes/settings/layout.tsx
+++ b/src/site/src/components/routes/settings/layout.tsx
@@ -1,18 +1,17 @@
import { t } from "@lingui/core/macro"
-import { Trans } from "@lingui/react/macro"
+import { Trans, useLingui } from "@lingui/react/macro"
+import { useStore } from "@nanostores/react"
+import { getPagePath, redirectPage } from "@nanostores/router"
+import { AlertOctagonIcon, BellIcon, FileSlidersIcon, FingerprintIcon, SettingsIcon } from "lucide-react"
import { lazy, useEffect } from "react"
+import { $router } from "@/components/router.tsx"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx"
+import { toast } from "@/components/ui/use-toast.ts"
+import { pb } from "@/lib/api"
+import { $userSettings } from "@/lib/stores.ts"
+import type { UserSettings } from "@/types"
import { Separator } from "../../ui/separator"
import { SidebarNav } from "./sidebar-nav.tsx"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx"
-import { useStore } from "@nanostores/react"
-import { $router } from "@/components/router.tsx"
-import { getPagePath, redirectPage } from "@nanostores/router"
-import { BellIcon, FileSlidersIcon, FingerprintIcon, SettingsIcon, AlertOctagonIcon } from "lucide-react"
-import { $userSettings } from "@/lib/stores.ts"
-import { toast } from "@/components/ui/use-toast.ts"
-import { UserSettings } from "@/types"
-import { useLingui } from "@lingui/react/macro"
-import { pb } from "@/lib/api"
const generalSettingsImport = () => import("./general.tsx")
const notificationsSettingsImport = () => import("./notifications.tsx")
@@ -93,9 +92,10 @@ export default function SettingsLayout() {
const page = useStore($router)
+ // biome-ignore lint/correctness/useExhaustiveDependencies: no dependencies
useEffect(() => {
- document.title = t`Settings` + " / Beszel"
- // @ts-ignore redirect to account page if no page is specified
+ document.title = `${t`Settings`} / Beszel`
+ // @ts-expect-error redirect to account page if no page is specified
if (!page?.params?.name) {
redirectPage($router, "settings", { name: "general" })
}
diff --git a/src/site/src/components/routes/settings/notifications.tsx b/src/site/src/components/routes/settings/notifications.tsx
index 9bc3082..e9ec35f 100644
--- a/src/site/src/components/routes/settings/notifications.tsx
+++ b/src/site/src/components/routes/settings/notifications.tsx
@@ -1,19 +1,19 @@
import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { Separator } from "@/components/ui/separator"
-import { Card } from "@/components/ui/card"
import { BellIcon, LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from "lucide-react"
-import { ChangeEventHandler, useEffect, useState } from "react"
-import { toast } from "@/components/ui/use-toast"
-import { InputTags } from "@/components/ui/input-tags"
-import { UserSettings } from "@/types"
-import { saveSettings } from "./layout"
+import { type ChangeEventHandler, useEffect, useState } from "react"
import * as v from "valibot"
import { prependBasePath } from "@/components/router"
+import { Button } from "@/components/ui/button"
+import { Card } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import { InputTags } from "@/components/ui/input-tags"
+import { Label } from "@/components/ui/label"
+import { Separator } from "@/components/ui/separator"
+import { toast } from "@/components/ui/use-toast"
import { isAdmin, pb } from "@/lib/api"
+import type { UserSettings } from "@/types"
+import { saveSettings } from "./layout"
interface ShoutrrrUrlCardProps {
url: string
@@ -127,7 +127,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
Beszel uses{" "}
-
+
Shoutrrr
{" "}
to integrate with popular notification services.
diff --git a/src/site/src/components/routes/settings/sidebar-nav.tsx b/src/site/src/components/routes/settings/sidebar-nav.tsx
index f77f5b9..5dc6805 100644
--- a/src/site/src/components/routes/settings/sidebar-nav.tsx
+++ b/src/site/src/components/routes/settings/sidebar-nav.tsx
@@ -1,11 +1,11 @@
-import React from "react"
-import { cn } from "@/lib/utils"
-import { isAdmin, isReadOnlyUser } from "@/lib/api"
-import { buttonVariants } from "../../ui/button"
-import { $router, Link, navigate } from "../../router"
import { useStore } from "@nanostores/react"
+import type React from "react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Separator } from "@/components/ui/separator"
+import { isAdmin, isReadOnlyUser } from "@/lib/api"
+import { cn } from "@/lib/utils"
+import { $router, Link, navigate } from "../../router"
+import { buttonVariants } from "../../ui/button"
interface SidebarNavProps extends React.HTMLAttributes {
items: {
diff --git a/src/site/src/components/routes/settings/tokens-fingerprints.tsx b/src/site/src/components/routes/settings/tokens-fingerprints.tsx
index d734ac9..502525b 100644
--- a/src/site/src/components/routes/settings/tokens-fingerprints.tsx
+++ b/src/site/src/components/routes/settings/tokens-fingerprints.tsx
@@ -1,9 +1,6 @@
-import { Trans, useLingui } from "@lingui/react/macro"
import { t } from "@lingui/core/macro"
-import { $publicKey } from "@/lib/stores"
-import { memo, useEffect, useMemo, useState } from "react"
-import { Table, TableCell, TableHead, TableBody, TableRow, TableHeader } from "@/components/ui/table"
-import { FingerprintRecord } from "@/types"
+import { Trans, useLingui } from "@lingui/react/macro"
+import { redirectPage } from "@nanostores/router"
import {
CopyIcon,
FingerprintIcon,
@@ -13,9 +10,17 @@ import {
ServerIcon,
Trash2Icon,
} from "lucide-react"
-import { toast } from "@/components/ui/use-toast"
-import { cn, copyToClipboard, generateToken, getHubURL, tokenMap } from "@/lib/utils"
-import { isReadOnlyUser, pb } from "@/lib/api"
+import { memo, useEffect, useMemo, useState } from "react"
+import {
+ copyDockerCompose,
+ copyDockerRun,
+ copyLinuxCommand,
+ copyWindowsCommand,
+ type DropdownItem,
+ InstallDropdown,
+} from "@/components/install-dropdowns"
+import { $router } from "@/components/router"
+import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
@@ -23,20 +28,15 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
-import { Button } from "@/components/ui/button"
+import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "@/components/ui/icons"
import { Separator } from "@/components/ui/separator"
import { Switch } from "@/components/ui/switch"
-import {
- copyDockerCompose,
- copyDockerRun,
- copyLinuxCommand,
- copyWindowsCommand,
- DropdownItem,
- InstallDropdown,
-} from "@/components/install-dropdowns"
-import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "@/components/ui/icons"
-import { redirectPage } from "@nanostores/router"
-import { $router } from "@/components/router"
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
+import { toast } from "@/components/ui/use-toast"
+import { isReadOnlyUser, pb } from "@/lib/api"
+import { $publicKey } from "@/lib/stores"
+import { cn, copyToClipboard, generateToken, getHubURL, tokenMap } from "@/lib/utils"
+import type { FingerprintRecord } from "@/types"
const pbFingerprintOptions = {
expand: "system",
diff --git a/src/site/src/lib/enums.ts b/src/site/src/lib/enums.ts
index 7fd219b..178c6b0 100644
--- a/src/site/src/lib/enums.ts
+++ b/src/site/src/lib/enums.ts
@@ -46,3 +46,10 @@ export enum BatteryState {
Discharging,
Idle,
}
+
+/** Time format */
+export enum HourFormat {
+ // Default = "Default",
+ "12h" = "12h",
+ "24h" = "24h",
+}
diff --git a/src/site/src/lib/stores.ts b/src/site/src/lib/stores.ts
index 917ac66..9cc564b 100644
--- a/src/site/src/lib/stores.ts
+++ b/src/site/src/lib/stores.ts
@@ -1,4 +1,4 @@
-import { atom, computed, map, type ReadableAtom } from "nanostores"
+import { atom, computed, listenKeys, map, type ReadableAtom } from "nanostores"
import type { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types"
import { pb } from "./api"
import { Unit } from "./enums"
@@ -50,7 +50,7 @@ export const $userSettings = map({
unitTemp: Unit.Celsius,
})
// update chart time on change
-$userSettings.subscribe((value) => $chartTime.set(value.chartTime))
+listenKeys($userSettings, ["chartTime"], ({ chartTime }) => $chartTime.set(chartTime))
/** Container chart filter */
export const $containerFilter = atom("")
diff --git a/src/site/src/lib/time.ts b/src/site/src/lib/time.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/site/src/lib/utils.ts b/src/site/src/lib/utils.ts
index e7d42e0..fd24866 100644
--- a/src/site/src/lib/utils.ts
+++ b/src/site/src/lib/utils.ts
@@ -6,8 +6,9 @@ import { twMerge } from "tailwind-merge"
import { prependBasePath } from "@/components/router"
import { toast } from "@/components/ui/use-toast"
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
-import { MeterState, Unit } from "./enums"
+import { HourFormat, MeterState, Unit } from "./enums"
import { $copyContent, $userSettings } from "./stores"
+import { listenKeys } from "nanostores"
export const FAVICON_DEFAULT = "favicon.svg"
export const FAVICON_GREEN = "favicon-green.svg"
@@ -36,24 +37,47 @@ export async function copyToClipboard(content: string) {
}
}
-const hourWithMinutesFormatter = new Intl.DateTimeFormat(undefined, {
- hour: "numeric",
- minute: "numeric",
-})
+// Create formatters directly without intermediate containers
+const createHourWithMinutesFormatter = (hour12?: boolean) =>
+ new Intl.DateTimeFormat(undefined, {
+ hour: "numeric",
+ minute: "numeric",
+ hour12,
+ })
+
+const createShortDateFormatter = (hour12?: boolean) =>
+ new Intl.DateTimeFormat(undefined, {
+ day: "numeric",
+ month: "short",
+ hour: "numeric",
+ minute: "numeric",
+ hour12,
+ })
+
+// Initialize formatters with default values
+let hourWithMinutesFormatter = createHourWithMinutesFormatter()
+let shortDateFormatter = createShortDateFormatter()
+
+export const currentHour12 = () => shortDateFormatter.resolvedOptions().hour12
+
export const hourWithMinutes = (timestamp: string) => {
return hourWithMinutesFormatter.format(new Date(timestamp))
}
-const shortDateFormatter = new Intl.DateTimeFormat(undefined, {
- day: "numeric",
- month: "short",
- hour: "numeric",
- minute: "numeric",
-})
export const formatShortDate = (timestamp: string) => {
return shortDateFormatter.format(new Date(timestamp))
}
+// Update the time formatters if user changes hourFormat
+listenKeys($userSettings, ["hourFormat"], ({ hourFormat }) => {
+ if (!hourFormat) return
+ const newHour12 = hourFormat === HourFormat["12h"]
+ if (currentHour12() !== newHour12) {
+ hourWithMinutesFormatter = createHourWithMinutesFormatter(newHour12)
+ shortDateFormatter = createShortDateFormatter(newHour12)
+ }
+})
+
const dayFormatter = new Intl.DateTimeFormat(undefined, {
day: "numeric",
month: "short",
diff --git a/src/site/src/types.d.ts b/src/site/src/types.d.ts
index cf47909..ea76f57 100644
--- a/src/site/src/types.d.ts
+++ b/src/site/src/types.d.ts
@@ -1,5 +1,5 @@
-import { RecordModel } from "pocketbase"
-import { Unit, Os, BatteryState } from "./lib/enums"
+import type { RecordModel } from "pocketbase"
+import type { Unit, Os, BatteryState, HourFormat } from "./lib/enums"
// global window properties
declare global {
@@ -238,6 +238,7 @@ export interface UserSettings {
unitDisk?: Unit
colorWarn?: number
colorCrit?: number
+ hourFormat?: HourFormat
}
type ChartDataContainer = {
@@ -262,17 +263,17 @@ export interface ChartData {
chartTime: ChartTimes
}
-interface AlertInfo {
- name: () => string
- unit: string
- icon: any
- desc: () => string
- max?: number
- min?: number
- step?: number
- start?: number
- /** Single value description (when there's only one value, like status) */
- singleDesc?: () => string
-}
+// interface AlertInfo {
+// name: () => string
+// unit: string
+// icon: any
+// desc: () => string
+// max?: number
+// min?: number
+// step?: number
+// start?: number
+// /** Single value description (when there's only one value, like status) */
+// singleDesc?: () => string
+// }
export type AlertMap = Record>