diff --git a/src/gmp/commands/users.js b/src/gmp/commands/users.js
index d4549a997d..7aab548cd3 100644
--- a/src/gmp/commands/users.js
+++ b/src/gmp/commands/users.js
@@ -70,6 +70,11 @@ export const DEFAULT_FILTER_SETTINGS = {
vulnerability: '17c9d269-95e7-4bfa-b1b2-bc106a2175c7',
};
+const PARAM_KEYS = {
+ DATE: 'date_format',
+ TIME: 'time_format',
+};
+
const saveDefaultFilterSettingId = entityType =>
`settings_filter:${DEFAULT_FILTER_SETTINGS[entityType]}`;
@@ -251,9 +256,12 @@ export class UserCommand extends EntityCommand {
saveSettings(data) {
log.debug('Saving settings', data);
+
return this.httpPost({
cmd: 'save_my_settings',
text: data.timezone,
+ [PARAM_KEYS.DATE]: data.dateFormat,
+ [PARAM_KEYS.TIME]: data.timeFormat,
old_password: data.oldPassword,
password: data.newPassword,
lang: data.userInterfaceLanguage,
diff --git a/src/gmp/locale/date.js b/src/gmp/locale/date.js
index 253a60b3a3..6d03596357 100644
--- a/src/gmp/locale/date.js
+++ b/src/gmp/locale/date.js
@@ -34,7 +34,7 @@ export const ensureDate = date => {
return date;
};
-export const dateFormat = (date, format, tz) => {
+export const getFormattedDate = (date, format, tz) => {
date = ensureDate(date);
if (!isDefined(date)) {
return undefined;
@@ -43,14 +43,49 @@ export const dateFormat = (date, format, tz) => {
if (isDefined(tz)) {
date.tz(tz);
}
+
return date.format(format);
};
-export const shortDate = (date, tz) => dateFormat(date, 'L', tz);
+export const dateTimeFormatOptions = {
+ time: {12: 'h:mm A', 24: 'H:mm'},
+ date: {wmdy: 'ddd, MMM D, YYYY', wdmy: 'ddd, D MMM YYYY'},
+};
+
+export const shortDate = (date, tz, userInterfaceDateFormat) => {
+ const formatString = dateTimeFormatOptions.date[userInterfaceDateFormat]
+ ? 'DD/MM/YYYY'
+ : 'L';
+
+ return getFormattedDate(date, formatString, tz);
+};
+
+export const longDate = (
+ date,
+ tz,
+ userInterfaceTimeFormat,
+ userInterfaceDateFormat,
+) => {
+ const formatString =
+ dateTimeFormatOptions.date[userInterfaceDateFormat] &&
+ dateTimeFormatOptions.time[userInterfaceTimeFormat]
+ ? `${dateTimeFormatOptions.date[userInterfaceDateFormat]} ${dateTimeFormatOptions.time[userInterfaceTimeFormat]} z`
+ : 'llll';
-export const longDate = (date, tz) => dateFormat(date, 'llll', tz);
+ return getFormattedDate(date, formatString, tz);
+};
-export const dateTimeWithTimeZone = (date, tz) =>
- dateFormat(date, 'llll z', tz);
+export const dateTimeWithTimeZone = (
+ date,
+ tz,
+ userInterfaceTimeFormat,
+ userInterfaceDateFormat,
+) => {
+ const formatString =
+ dateTimeFormatOptions.date[userInterfaceDateFormat] &&
+ dateTimeFormatOptions.time[userInterfaceTimeFormat]
+ ? `${dateTimeFormatOptions.date[userInterfaceDateFormat]} ${dateTimeFormatOptions.time[userInterfaceTimeFormat]} z`
+ : 'llll z';
-// vim: set ts=2 sw=2 tw=80:
+ return getFormattedDate(date, formatString, tz);
+};
diff --git a/src/web/components/date/__tests__/datetime.jsx b/src/web/components/date/__tests__/datetime.jsx
index edcf0bfaeb..55933e789b 100644
--- a/src/web/components/date/__tests__/datetime.jsx
+++ b/src/web/components/date/__tests__/datetime.jsx
@@ -4,7 +4,14 @@
*/
/* eslint-disable no-console */
-import {describe, test, expect, testing} from '@gsa/testing';
+import {
+ describe,
+ test,
+ expect,
+ testing,
+ beforeAll,
+ afterAll,
+} from '@gsa/testing';
import Date from 'gmp/models/date';
@@ -13,10 +20,35 @@ import {rendererWith} from 'web/utils/testing';
import {setTimezone} from 'web/store/usersettings/actions';
import DateTime from '../datetime';
+import {loadingActions} from 'web/store/usersettings/defaults/actions';
+
+const getSetting = testing.fn().mockResolvedValue({});
+
+const gmp = {
+ user: {
+ getSetting,
+ },
+};
describe('DateTime render tests', () => {
+ let originalSetItem;
+
+ beforeAll(() => {
+ originalSetItem = localStorage.setItem;
+ localStorage.setItem = testing.fn();
+ });
+
+ afterAll(() => {
+ localStorage.setItem = originalSetItem;
+ });
test('should render nothing if date is undefined', () => {
- const {render} = rendererWith({store: true});
+ const {render, store} = rendererWith({gmp, store: true});
+ store.dispatch(
+ loadingActions.success({
+ userinterfacetimeformat: {value: 12},
+ userinterfacedateformat: {value: 'wdmy'},
+ }),
+ );
const {element} = render();
@@ -25,10 +57,16 @@ describe('DateTime render tests', () => {
test('should render nothing for invalid date', () => {
// deactivate console.warn for test
- const consolewarn = console.warn;
+ const consoleWarn = console.warn;
console.warn = () => {};
- const {render} = rendererWith({store: true});
+ const {render, store} = rendererWith({gmp, store: true});
+ store.dispatch(
+ loadingActions.success({
+ userinterfacetimeformat: {value: 12},
+ userinterfacedateformat: {value: 'wdmy'},
+ }),
+ );
const date = Date('foo');
@@ -38,12 +76,13 @@ describe('DateTime render tests', () => {
expect(element).toBeNull();
- console.warn = consolewarn;
+ console.warn = consoleWarn;
});
test('should call formatter', () => {
const formatter = testing.fn().mockReturnValue('foo');
- const {render, store} = rendererWith({store: true});
+
+ const {render, store} = rendererWith({gmp, store: true});
const date = Date('2019-01-01T12:00:00Z');
@@ -51,6 +90,9 @@ describe('DateTime render tests', () => {
store.dispatch(setTimezone('CET'));
+ localStorage.setItem('userInterfaceTimeFormat', 12);
+ localStorage.setItem('userInterfaceDateFormat', 'wdmy');
+
const {baseElement} = render(
,
);
@@ -59,17 +101,49 @@ describe('DateTime render tests', () => {
expect(baseElement).toHaveTextContent('foo');
});
- test('should render with default formatter', () => {
- const {render, store} = rendererWith({store: true});
+ test.each([
+ [
+ 'should render with default formatter',
+ {
+ userinterfacetimeformat: {value: undefined},
+ userinterfacedateformat: {value: undefined},
+ },
+ 'Tue, Jan 1, 2019 1:00 PM CET',
+ ],
+ [
+ 'should render with 24 h and WeekDay, Month, Day, Year formatter',
+ {
+ userinterfacetimeformat: {value: 24},
+ userinterfacedateformat: {value: 'wmdy'},
+ },
+ 'Tue, Jan 1, 2019 13:00 CET',
+ ],
+ [
+ 'should render with 12 h and WeekDay, Day, Month, Year formatter',
+ {
+ userinterfacetimeformat: {value: 12},
+ userinterfacedateformat: {value: 'wdmy'},
+ },
+ 'Tue, 1 Jan 2019 1:00 PM CET',
+ ],
+ ])('%s', (_, settings, expectedText) => {
+ const {render, store} = rendererWith({gmp, store: true});
+
+ localStorage.setItem(
+ 'userInterfaceTimeFormat',
+ settings.userinterfacetimeformat.value,
+ );
+ localStorage.setItem(
+ 'userInterfaceDateFormat',
+ settings.userinterfacedateformat.value,
+ );
const date = Date('2019-01-01T12:00:00Z');
-
expect(date.isValid()).toEqual(true);
store.dispatch(setTimezone('CET'));
const {baseElement} = render();
-
- expect(baseElement).toHaveTextContent('Tue, Jan 1, 2019 1:00 PM CET');
+ expect(baseElement).toHaveTextContent(expectedText);
});
});
diff --git a/src/web/hooks/__tests__/useUserSessionTimeout.jsx b/src/web/hooks/__tests__/useUserSessionTimeout.jsx
index 3cd07b265c..eea92334d0 100644
--- a/src/web/hooks/__tests__/useUserSessionTimeout.jsx
+++ b/src/web/hooks/__tests__/useUserSessionTimeout.jsx
@@ -5,7 +5,7 @@
import {describe, test, expect} from '@gsa/testing';
-import {dateFormat} from 'gmp/locale/date';
+import {getFormattedDate} from 'gmp/locale/date';
import date from 'gmp/models/date';
import {setSessionTimeout as setSessionTimeoutAction} from 'web/store/usersettings/actions';
@@ -21,7 +21,7 @@ const TestUserSessionTimeout = () => {
onClick={() => setSessionTimeout(date('2020-03-10'))}
onKeyDown={() => {}}
>
- {dateFormat(sessionTimeout, 'DD-MM-YY')}
+ {getFormattedDate(sessionTimeout, 'DD-MM-YY')}
);
};
diff --git a/src/web/pages/login/loginpage.jsx b/src/web/pages/login/loginpage.jsx
index 74d867c631..c261029269 100644
--- a/src/web/pages/login/loginpage.jsx
+++ b/src/web/pages/login/loginpage.jsx
@@ -94,38 +94,46 @@ class LoginPage extends React.Component {
this.login(gmp.settings.guestUsername, gmp.settings.guestPassword);
}
- login(username, password) {
+ async login(username, password) {
const {gmp} = this.props;
- gmp.login(username, password).then(
- data => {
- const {locale, timezone, sessionTimeout} = data;
-
- const {location, navigate} = this.props;
-
- this.props.setTimezone(timezone);
- this.props.setLocale(locale);
- this.props.setSessionTimeout(sessionTimeout);
- this.props.setUsername(username);
- // must be set before changing the location
- this.props.setIsLoggedIn(true);
-
- if (
- location &&
- location.state &&
- location.state.next &&
- location.state.next !== location.pathname
- ) {
- navigate(location.state.next, {replace: true});
- } else {
- navigate('/dashboards', {replace: true});
- }
- },
- rej => {
- log.error(rej);
- this.setState({error: rej});
- },
- );
+ try {
+ const data = await gmp.login(username, password);
+
+ const {location, navigate} = this.props;
+ const {locale, timezone, sessionTimeout} = data;
+
+ this.props.setTimezone(timezone);
+ this.props.setLocale(locale);
+ this.props.setSessionTimeout(sessionTimeout);
+ this.props.setUsername(username);
+ // must be set before changing the location
+ this.props.setIsLoggedIn(true);
+
+ if (location?.state?.next && location.state.next !== location.pathname) {
+ navigate(location.state.next, {replace: true});
+ } else {
+ navigate('/dashboards', {replace: true});
+ }
+ } catch (error) {
+ log.error(error);
+ this.setState({error});
+ }
+
+ try {
+ const userSettings = await gmp.user.currentSettings();
+
+ localStorage.setItem(
+ 'userInterfaceTimeFormat',
+ userSettings.data.userinterfacetimeformat.value,
+ );
+ localStorage.setItem(
+ 'userInterfaceDateFormat',
+ userSettings.data.userinterfacedateformat.value,
+ );
+ } catch (error) {
+ log.error(error);
+ }
}
componentDidMount() {
diff --git a/src/web/pages/usersettings/dialog.jsx b/src/web/pages/usersettings/dialog.jsx
index 1673f37f13..a2172e1cd3 100644
--- a/src/web/pages/usersettings/dialog.jsx
+++ b/src/web/pages/usersettings/dialog.jsx
@@ -39,7 +39,7 @@ const FormGroupSizer = styled(Column)`
const fieldsToValidate = ['rowsPerPage'];
-let UserSettingsDialog = ({
+const UserSettingsDialogComponent = ({
alerts,
credentials,
filters,
@@ -49,6 +49,8 @@ let UserSettingsDialog = ({
schedules,
targets,
timezone,
+ userInterfaceTimeFormat,
+ userInterfaceDateFormat,
userInterfaceLanguage,
rowsPerPage,
maxRowsPerPage,
@@ -102,6 +104,8 @@ let UserSettingsDialog = ({
}) => {
const settings = {
timezone,
+ userInterfaceTimeFormat,
+ userInterfaceDateFormat,
oldPassword: '',
newPassword: '',
confPassword: '',
@@ -194,6 +198,8 @@ let UserSettingsDialog = ({
{
+const UserSettingsDialog = connect(rootState => {
const entities = isDefined(rootState.entities) ? rootState.entities : [];
return {
entities,
};
-})(UserSettingsDialog);
+})(UserSettingsDialogComponent);
export default UserSettingsDialog;
diff --git a/src/web/pages/usersettings/generalpart.jsx b/src/web/pages/usersettings/generalpart.jsx
index c8470ae5af..236ceb1f3f 100644
--- a/src/web/pages/usersettings/generalpart.jsx
+++ b/src/web/pages/usersettings/generalpart.jsx
@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
import React from 'react';
import styled from 'styled-components';
@@ -68,6 +67,8 @@ Notification.propTypes = {
const GeneralPart = ({
timezone,
+ userInterfaceDateFormat,
+ userInterfaceTimeFormat,
oldPassword,
newPassword,
confPassword,
@@ -82,11 +83,35 @@ const GeneralPart = ({
onChange,
}) => {
const [_] = useTranslation();
+
return (
<>
+
+
+
+
+
{
- this.closeDialog();
- this.props.setLocale(
- userInterfaceLanguage === BROWSER_LANGUAGE
- ? undefined
- : userInterfaceLanguage,
+ this.handleInteraction();
+ await gmp.user.saveSetting(
+ 'd9857b7c-1159-4193-9bc0-18fae5473a69',
+ data.userInterfaceDateFormat,
+ );
+ await gmp.user.saveSetting(
+ '11deb7ff-550b-4950-aacf-06faeb7c61b9',
+ data.userInterfaceTimeFormat,
);
- this.props.setTimezone(timezone);
- this.loadSettings();
- });
+ await gmp.user.saveSettings(data).then(() => {
+ this.closeDialog();
+ this.props.setLocale(
+ userInterfaceLanguage === BROWSER_LANGUAGE
+ ? undefined
+ : userInterfaceLanguage,
+ );
+ this.props.setTimezone(timezone);
+
+ localStorage.setItem(
+ 'userInterfaceTimeFormat',
+ data.userInterfaceTimeFormat,
+ );
+ localStorage.setItem(
+ 'userInterfaceDateFormat',
+ data.userInterfaceDateFormat,
+ );
+
+ this.loadSettings();
+ });
+ } catch (error) {
+ console.error(error);
+ }
}
handleValueChange(value, name) {
@@ -274,6 +295,8 @@ class UserSettings extends React.Component {
targets,
isLoading = true,
timezone,
+ userInterfaceDateFormat = {},
+ userInterfaceTimeFormat = {},
userInterfaceLanguage = {},
rowsPerPage = {},
maxRowsPerPage = {},
@@ -364,11 +387,10 @@ class UserSettings extends React.Component {
nvtFilter = hasValue(nvtFilter) ? nvtFilter : {};
certBundFilter = hasValue(certBundFilter) ? certBundFilter : {};
dfnCertFilter = hasValue(dfnCertFilter) ? dfnCertFilter : {};
-
const openVasScanners = scanners.filter(openVasScannersFilter);
return (
-
+ <>
) : (
-
+ <>
{_('Timezone')}
{timezone}
+
+ {_('Time Format')}
+
+ {userInterfaceTimeFormat.value}h
+
+
+
+ {_('Date Format')}
+ {userInterfaceDateFormat.value}
+
+
{_('Password')}
********
@@ -723,7 +756,7 @@ class UserSettings extends React.Component {
)}
-
+ >
)}
{dialogVisible && !isLoading && (
)}
-
+ >
);
}
}
@@ -811,6 +846,7 @@ UserSettings.propTypes = {
credentials: PropTypes.array,
credentialsFilter: PropTypes.object,
cveFilter: PropTypes.object,
+ userInterfaceDateFormat: PropTypes.oneOf(['wdmy', 'wmdy']),
defaultAlert: PropTypes.object,
defaultEsxiCredential: PropTypes.object,
defaultOpenvasScanConfig: PropTypes.object,
@@ -870,6 +906,7 @@ UserSettings.propTypes = {
tasksFilter: PropTypes.object,
ticketsFilter: PropTypes.object,
timezone: PropTypes.string,
+ userInterfaceTimeFormat: PropTypes.oneOf([12, 24]),
tlsCertificatesFilter: PropTypes.object,
userInterfaceLanguage: PropTypes.object,
usersFilter: PropTypes.object,
@@ -879,11 +916,21 @@ UserSettings.propTypes = {
const mapStateToProps = rootState => {
const userDefaultsSelector = getUserSettingsDefaults(rootState);
+
const userDefaultFilterSelector = getUserSettingsDefaultFilter(rootState);
const userInterfaceLanguage = userDefaultsSelector.getByName(
'userinterfacelanguage',
);
+
+ const userInterfaceTimeFormat = userDefaultsSelector.getByName(
+ 'userinterfacetimeformat',
+ );
+
+ const userInterfaceDateFormat = userDefaultsSelector.getByName(
+ 'userinterfacedateformat',
+ );
+
const rowsPerPage = userDefaultsSelector.getByName('rowsperpage');
const detailsExportFileName = userDefaultsSelector.getByName(
'detailsexportfilename',
@@ -1006,6 +1053,8 @@ const mapStateToProps = rootState => {
schedules: schedulesSel.getEntities(ALL_FILTER),
targets: targetsSel.getEntities(ALL_FILTER),
timezone: getTimezone(rootState),
+ userInterfaceTimeFormat,
+ userInterfaceDateFormat,
userInterfaceLanguage,
rowsPerPage,
detailsExportFileName,
diff --git a/src/web/store/usersettings/reducers.js b/src/web/store/usersettings/reducers.js
index 311422ea19..9110a1eebd 100644
--- a/src/web/store/usersettings/reducers.js
+++ b/src/web/store/usersettings/reducers.js
@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
import {combineReducers} from 'web/store/utils';
import defaults from './defaults/reducers';
diff --git a/src/web/store/usersettings/selectors.js b/src/web/store/usersettings/selectors.js
index 28e99689f1..215d38ef8c 100644
--- a/src/web/store/usersettings/selectors.js
+++ b/src/web/store/usersettings/selectors.js
@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
export const getReportComposerDefaults = rootState => {
const {userSettings = {}} = rootState;
const {reportComposerDefaults} = userSettings;
diff --git a/src/web/utils/render.jsx b/src/web/utils/render.jsx
index f5a5097c92..6abb0ec7fa 100644
--- a/src/web/utils/render.jsx
+++ b/src/web/utils/render.jsx
@@ -3,13 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
import {format} from 'd3-format';
import React from 'react';
import {_} from 'gmp/locale/lang';
-import {dateFormat} from 'gmp/locale/date';
+import {getFormattedDate} from 'gmp/locale/date';
import {isDefined, isFunction, isObject} from 'gmp/utils/identity';
import {isEmpty, shorten, split} from 'gmp/utils/string';
@@ -521,12 +520,12 @@ export const generateFilename = ({
mTime = currentTime;
}
- const percentC = dateFormat(cTime, 'YYYYMMDD');
- const percentc = dateFormat(cTime, 'HHMMSS');
- const percentD = dateFormat(currentTime, 'YYYYMMDD');
- const percentt = dateFormat(currentTime, 'HHMMSS');
- const percentM = dateFormat(mTime, 'YYYYMMDD');
- const percentm = dateFormat(mTime, 'HHMMSS');
+ const percentC = getFormattedDate(cTime, 'YYYYMMDD');
+ const percentc = getFormattedDate(cTime, 'HHMMSS');
+ const percentD = getFormattedDate(currentTime, 'YYYYMMDD');
+ const percentt = getFormattedDate(currentTime, 'HHMMSS');
+ const percentM = getFormattedDate(mTime, 'YYYYMMDD');
+ const percentm = getFormattedDate(mTime, 'HHMMSS');
const percentN = isDefined(resourceName) ? resourceName : resourceType;
const fileNameMap = {
diff --git a/src/web/utils/userSettingTimeDateFormatters.js b/src/web/utils/userSettingTimeDateFormatters.js
new file mode 100644
index 0000000000..cfd2d4d457
--- /dev/null
+++ b/src/web/utils/userSettingTimeDateFormatters.js
@@ -0,0 +1,40 @@
+/* SPDX-FileCopyrightText: 2024 Greenbone AG
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import {longDate, shortDate, dateTimeWithTimeZone} from 'gmp/locale/date';
+
+export const formattedUserSettingShortDate = (date, tz) => {
+ const userInterfaceDateFormat = localStorage.getItem(
+ 'userInterfaceTimeFormat',
+ );
+
+ return shortDate(date, tz, userInterfaceDateFormat);
+};
+
+export const formattedUserSettingLongDate = (date, tz) => {
+ const userInterfaceDateFormat = localStorage.getItem(
+ 'userInterfaceDateFormat',
+ );
+
+ const userInterfaceTimeFormat = localStorage.getItem(
+ 'userInterfaceTimeFormat',
+ );
+ return longDate(date, tz, userInterfaceTimeFormat, userInterfaceDateFormat);
+};
+
+export const formattedUserSettingDateTimeWithTimeZone = (date, tz) => {
+ const userInterfaceDateFormat = localStorage.getItem(
+ 'userInterfaceDateFormat',
+ );
+ const userInterfaceTimeFormat = localStorage.getItem(
+ 'userInterfaceTimeFormat',
+ );
+ return dateTimeWithTimeZone(
+ date,
+ tz,
+ userInterfaceTimeFormat,
+ userInterfaceDateFormat,
+ );
+};