diff --git a/README.md b/README.md index 8b3c074f..cf407039 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ Docker Swarm Dashboard supports environment variables for configuration * `DSD_HANDLE_LOGS`: Set to `false` to prevent fetching and displaying logs. * `DSD_DASHBOARD_LAYOUT`: Default dashboard layout. Either `row` (default) or `column`. * `DSD_HIDE_SERVICE_STATES`: Comma-separated list of states to not show in the main dashboard. +* `LOCALE`: Timestamp format based on a [BCP 47](https://www.rfc-editor.org/bcp/bcp47.txt) language tag. +* `TZ`: [IANA Time zone](https://www.iana.org/time-zones) to display timestamps in. ### Pull Image from ghcr.io ``` diff --git a/app-src/src/common/DefaultDateTimeFormat.js b/app-src/src/common/DefaultDateTimeFormat.js index e54f6286..d866fde5 100644 --- a/app-src/src/common/DefaultDateTimeFormat.js +++ b/app-src/src/common/DefaultDateTimeFormat.js @@ -8,6 +8,9 @@ const DEFAULT_DATE_TIME_FORMAT = { second: '2-digit', } -export const toDefaultDateTimeString = (date) => { - return date.toLocaleString(undefined, DEFAULT_DATE_TIME_FORMAT) +export const toDefaultDateTimeString = (date, locale, timeZone) => { + return date.toLocaleString( + locale ? locale : undefined, + timeZone ? { timeZone: timeZone } : DEFAULT_DATE_TIME_FORMAT, + ) } diff --git a/app-src/src/components/ServiceStatusBadge.js b/app-src/src/components/ServiceStatusBadge.js index 795bc827..d032eeb9 100644 --- a/app-src/src/components/ServiceStatusBadge.js +++ b/app-src/src/components/ServiceStatusBadge.js @@ -2,6 +2,8 @@ import { Badge, OverlayTrigger, Tooltip } from 'react-bootstrap' import { getStyleClassForState } from '../Helper' import PropTypes from 'prop-types' import { toDefaultDateTimeString } from '../common/DefaultDateTimeFormat' +import { useAtomValue } from 'jotai' +import { dashboardSettingsAtom } from '../common/store/atoms' const ServiceStatusBadge = ({ id, @@ -14,6 +16,7 @@ const ServiceStatusBadge = ({ if (hiddenStates.includes(serviceState)) { return } + const dashBoardSettings = useAtomValue(dashboardSettingsAtom) if (createdAt || updatedAt || serviceError) { return ( {createdAt && ( - Created at: {toDefaultDateTimeString(new Date(createdAt))} + Created at:{' '} + {toDefaultDateTimeString( + new Date(createdAt), + dashBoardSettings.locale, + dashBoardSettings.timeZone, + )} +
+ {createdAt}
)} {updatedAt && ( - Updated at: {toDefaultDateTimeString(new Date(updatedAt))} + Updated at:{' '} + {toDefaultDateTimeString( + new Date(updatedAt), + dashBoardSettings.locale, + dashBoardSettings.timeZone, + )}
)} diff --git a/app-src/src/components/StacksComponent.js b/app-src/src/components/StacksComponent.js index 74d056d1..982c5120 100644 --- a/app-src/src/components/StacksComponent.js +++ b/app-src/src/components/StacksComponent.js @@ -4,6 +4,7 @@ import { toDefaultDateTimeString } from '../common/DefaultDateTimeFormat' import { currentVariantAtom, currentVariantClassesAtom, + dashboardSettingsAtom, stacksAtom, viewAtom, } from '../common/store/atoms' @@ -12,6 +13,7 @@ import { servicesDetailId } from '../common/navigationConstants' function StacksComponent() { const currentVariant = useAtomValue(currentVariantAtom) const currentVariantClasses = useAtomValue(currentVariantClassesAtom) + const dashBoardSettings = useAtomValue(dashboardSettingsAtom) let stacks const stacksData = useAtomValue(stacksAtom) @@ -28,8 +30,20 @@ function StacksComponent() { {service['ShortName'] ? service['ShortName'] : service['ServiceName']} {service['Replication']} - {toDefaultDateTimeString(new Date(service['Created']))} - {toDefaultDateTimeString(new Date(service['Updated']))} + + {toDefaultDateTimeString( + new Date(service['Created']), + dashBoardSettings.locale, + dashBoardSettings.timeZone, + )} + + + {toDefaultDateTimeString( + new Date(service['Updated']), + dashBoardSettings.locale, + dashBoardSettings.timeZone, + )} + )) } diff --git a/app-src/src/components/TasksComponent.js b/app-src/src/components/TasksComponent.js index f6db4047..e855643d 100644 --- a/app-src/src/components/TasksComponent.js +++ b/app-src/src/components/TasksComponent.js @@ -5,6 +5,7 @@ import { nodesDetailId, servicesDetailId } from '../common/navigationConstants' import { currentVariantAtom, currentVariantClassesAtom, + dashboardSettingsAtom, tableSizeAtom, tasksAtomNew, viewAtom, @@ -16,6 +17,7 @@ function TasksComponent() { const currentVariant = useAtomValue(currentVariantAtom) const currentVariantClasses = useAtomValue(currentVariantClassesAtom) const tableSize = useAtomValue(tableSizeAtom) + const dashBoardSettings = useAtomValue(dashboardSettingsAtom) let rows @@ -25,7 +27,13 @@ function TasksComponent() { key={'tasksTable-' + task['ID']} className={task['State'] === 'failed' ? 'table-danger' : null} > - {toDefaultDateTimeString(new Date(task['Timestamp']))} + + {toDefaultDateTimeString( + new Date(task['Timestamp']), + dashBoardSettings.locale, + dashBoardSettings.timeZone, + )} + diff --git a/server-src/dashboardsettingshandler.go b/server-src/dashboardsettingshandler.go index d47db111..a3ccd8b6 100644 --- a/server-src/dashboardsettingshandler.go +++ b/server-src/dashboardsettingshandler.go @@ -12,12 +12,16 @@ type dashboardSettings struct { ShowLogsButton bool `json:"showLogsButton"` DefaultLayout string `json:"defaultLayout"` HiddenServiceStates []string `json:"hiddenServiceStates"` + TimeZone *string `json:"timeZone"` + Locale *string `json:"locale"` } var ( handlingLogs = true dashboardLayout = "row" hiddenServiceStates = make([]string, 0) + timeZone = new(string) + locale = new(string) ) func init() { @@ -37,6 +41,14 @@ func init() { } } + if timeZoneEnvValue, timeZoneSet := os.LookupEnv("TZ"); timeZoneSet { + timeZone = &timeZoneEnvValue + } + + if localeEnvValue, localeSet := os.LookupEnv("LOCALE"); localeSet { + locale = &localeEnvValue + } + } func dashboardSettingsHandler(w http.ResponseWriter, _ *http.Request) { @@ -44,6 +56,8 @@ func dashboardSettingsHandler(w http.ResponseWriter, _ *http.Request) { handlingLogs, dashboardLayout, hiddenServiceStates, + timeZone, + locale, }) w.Header().Set("Content-Type", "application/json") w.Write(jsonString)