diff --git a/package.json b/package.json index e26e85ada1..ae1bff3d76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gsa", - "version": "23.3.1-dev1", + "version": "24.0.2-dev1", "description": "Greenbone Security Assistant", "keywords": [ "openvas", @@ -13,7 +13,7 @@ "type": "git", "url": "https://github.com/greenbone/gsa/" }, - "author": "Björn Ricks ", + "author": "Bj\u00f6rn Ricks ", "license": "AGPL-3.0+", "type": "module", "scripts": { @@ -118,4 +118,4 @@ "prettier --write" ] } -} +} \ No newline at end of file diff --git a/public/locales/gsa-de.json b/public/locales/gsa-de.json index a2d4a1aa51..400b6562d1 100644 --- a/public/locales/gsa-de.json +++ b/public/locales/gsa-de.json @@ -440,6 +440,7 @@ "Dashboards": "Dashboards", "Dashboards limit reached": "Dashboardgrenze erreicht", "Date": "Datum", + "Date Format": "Datumsformat", "Day": "Tag", "Debug": "Debug", "Decrease the minimum QoD in the filter settings to 30 percent to see those results.": "Die Minimum-QdE im Filter auf 30 Prozent verringern, um diese Ergebnisse zu sehen.", @@ -1549,6 +1550,7 @@ "Super (Has super access)": "Super (hat Super-Zugriff)", "Support for LDAP is not available.": "Unterstützung für LDAP ist nicht verfügbar.", "Support for RADIUS is not available.": "Unterstützung für RADIUS ist nicht verfügbar.", + "System Default": "Systemstandard", "System Logger": "System-Logger", "System Reports": "Systemberichte", "Synchronization issue: {{error}}": "Synchronisationsproblem: {{error}}", @@ -1742,6 +1744,7 @@ "Tickets by Creation Time (Total: {{count}})": "Tickets nach Erstellungszeit (Gesamt: {{count}})", "Tickets by Status (Total: {{count}})": "Tickets nach Status (Gesamt: {{count}})", "Time": "Zeit", + "Time Format": "Zeitformat", "Timeout": "Timeout", "Timezone": "Zeitzone", "TippingPoint SMS": "TippingPoint SMS", @@ -1810,6 +1813,7 @@ "Uploading": "Hochladen", "Urgency": "Dringlichkeit", "Use LDAPS only": "Ausschließlich LDAPS verwenden", + "Use System Default for Time and Date Format": "Systemstandard für Zeit- und Datumsformat verwenden", "Use workaround for default certificate": "Problemumgehung für Standard-Zertifikat verwenden", "User": "Benutzer", "User ID": "Benutzer-ID", @@ -1872,6 +1876,8 @@ "Wednesday": "Mittwoch", "Week": "Woche", "Weekly": "Wöchentlich", + "Weekday, Month, Day, Year": "Wochentag, Monat, Tag, Jahr", + "Weekday, Day, Month, Year": "Wochentag, Tag, Monat, Jahr", "When changing status to \"closed\", a \"Note for Closed\" is required.": "Wenn der Status zu \"Geschlossen\" geändert wird, ist eine \"Notiz für Geschlossen\" erforderlich.", "When changing status to \"fixed\", a \"Note for Fixed\" is required.": "Wenn der Status zu \"Behoben\" geändert wird, ist eine \"Notiz für Behoben\" erforderlich.", "When changing status to \"open\", a \"Note for Open\" is required.": "Wenn der Status zu \"Offen\" geändert wird, ist eine \"Notiz für Offen\" erforderlich.", diff --git a/src/gmp/commands/users.js b/src/gmp/commands/users.js index d4549a997d..4487e04370 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.userInterfaceDateFormat, + [PARAM_KEYS.TIME]: data.userInterfaceTimeFormat, old_password: data.oldPassword, password: data.newPassword, lang: data.userInterfaceLanguage, diff --git a/src/gmp/gmpsettings.js b/src/gmp/gmpsettings.js index 0a262fbd53..6f07cb73bf 100644 --- a/src/gmp/gmpsettings.js +++ b/src/gmp/gmpsettings.js @@ -43,7 +43,7 @@ const warnDeprecatedSetting = (oldName, newName) => { class GmpSettings { constructor(storage = global.localStorage, options = {}) { const { - enableEPSS = false, + enableEPSS = true, enableGreenboneSensor = false, disableLoginForm = false, enableStoreDebugLog, diff --git a/src/gmp/locale/__tests__/date.js b/src/gmp/locale/__tests__/date.js index c7ab574391..6b81640622 100644 --- a/src/gmp/locale/__tests__/date.js +++ b/src/gmp/locale/__tests__/date.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {describe, test, expect} from '@gsa/testing'; +import {describe, test, expect, beforeEach} from '@gsa/testing'; import date, {setLocale as locale} from '../../models/date'; @@ -55,15 +55,30 @@ describe('shortDate tests', () => { expect(shortDate(d)).toEqual('01/01/2018'); }); - test('should format string', () => { - setLocale('en'); - expect(shortDate('2018-01-01')).toEqual('01/01/2018'); + test('should format date locale', () => { + setLocale('de'); + expect( + shortDate(date('2018-11-24T15:30:00Z'), 'UTC', 'system_default'), + ).toEqual('24.11.2018'); }); - test('should format JS date', () => { - const d = new Date('2018-01-01'); - setLocale('en'); - expect(shortDate(d)).toEqual('01/01/2018'); + describe('shortDate tests', () => { + beforeEach(() => { + setLocale('en'); + }); + + test.each([ + ['2018-01-01', undefined, undefined, '01/01/2018'], + [new Date('2018-01-01'), undefined, undefined, '01/01/2018'], + [new Date('2018-11-24'), undefined, 'wmdy', '11/24/2018'], + [new Date('2018-11-24'), undefined, 'wdmy', '24/11/2018'], + [new Date('2018-11-24'), undefined, 'system_default', '11/24/2018'], + ])( + 'should format date %p with tz %p and userInterfaceDateFormat %p to %p', + (input, tz, userInterfaceDateFormat, expected) => { + expect(shortDate(input, tz, userInterfaceDateFormat)).toEqual(expected); + }, + ); }); }); @@ -83,15 +98,61 @@ describe('longDate tests', () => { expect(longDate(d)).toEqual('Mon, Jan 1, 2018 12:00 AM'); }); - test('should format string', () => { - setLocale('en'); - expect(longDate('2018-01-01')).toEqual('Mon, Jan 1, 2018 12:00 AM'); + test('should format date locale', () => { + setLocale('de'); + expect( + longDate( + date('2018-11-24T15:30:00Z'), + 'UTC', + 'system_default', + 'system_default', + ), + ).toEqual('Sa., 24. Nov. 2018 15:30'); }); - test('should format JS date', () => { - const d = new Date('2018-01-01T00:00:00'); - setLocale('en'); - expect(longDate(d)).toEqual('Mon, Jan 1, 2018 12:00 AM'); + describe('longDate tests', () => { + beforeEach(() => { + setLocale('en'); + }); + + test.each([ + [ + '2018-11-24', + undefined, + undefined, + undefined, + 'Sat, Nov 24, 2018 12:00 AM', + ], + [ + new Date('2018-11-23T00:00:00'), + undefined, + undefined, + undefined, + 'Fri, Nov 23, 2018 12:00 AM', + ], + ['2018-11-24T15:30:00Z', 'UTC', 12, 'wdmy', 'Sat, 24 Nov 2018 3:30 PM'], + ['2018-11-24T15:30:00Z', 'UTC', 24, 'wmdy', 'Sat, Nov 24, 2018 15:30'], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 'system_default', + 'system_default', + 'Sat, Nov 24, 2018 3:30 PM', + ], + ])( + 'should format date %p with tz %p, userInterfaceTimeFormat %p, and userInterfaceDateFormat %p to %p', + ( + input, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, + expected, + ) => { + expect( + longDate(input, tz, userInterfaceTimeFormat, userInterfaceDateFormat), + ).toEqual(expected); + }, + ); }); }); @@ -110,6 +171,73 @@ describe('dateTimeWithTimeZone tests', () => { const d = date('2018-01-01T00:00:00+01:00').tz('CET'); expect(dateTimeWithTimeZone(d)).toEqual('Mon, Jan 1, 2018 12:00 AM CET'); }); + + test('should format date locale', () => { + setLocale('de'); + expect( + dateTimeWithTimeZone( + date('2018-11-24T15:30:00Z'), + 'UTC', + 'system_default', + 'system_default', + ), + ).toEqual('Sa., 24. Nov. 2018 15:30 UTC'); + }); + + describe('dateTimeWithTimeZone tests', () => { + beforeEach(() => { + setLocale('en'); + }); + + test.each([ + [ + new Date('2018-11-23T00:00:00'), + undefined, + undefined, + undefined, + 'Fri, Nov 23, 2018 12:00 AM ', + ], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 12, + 'wdmy', + 'Sat, 24 Nov 2018 3:30 PM UTC', + ], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 24, + 'wmdy', + 'Sat, Nov 24, 2018 15:30 UTC', + ], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 'system_default', + 'system_default', + 'Sat, Nov 24, 2018 3:30 PM UTC', + ], + ])( + 'should format date %p with tz %p, userInterfaceTimeFormat %p, and userInterfaceDateFormat %p to %p', + ( + input, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, + expected, + ) => { + expect( + dateTimeWithTimeZone( + input, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, + ), + ).toEqual(expected); + }, + ); + }); }); // vim: set ts=2 sw=2 tw=80: diff --git a/src/gmp/locale/date.js b/src/gmp/locale/date.js index 253a60b3a3..bb6700e162 100644 --- a/src/gmp/locale/date.js +++ b/src/gmp/locale/date.js @@ -13,6 +13,32 @@ import {setLocale as setMomentLocale, isDate} from 'gmp/models/date'; const log = logger.getLogger('gmp.locale.date'); +export const SYSTEM_DEFAULT = 'system_default'; +const LONG_DATE = 'longDate'; +const SHORT_DATE = 'shortDate'; +const TIME = 'time'; + +export const dateTimeFormatOptions = { + [TIME]: { + options: { + 12: {format: 'h:mm A', label: '12h'}, + 24: {format: 'H:mm', label: '24h'}, + }, + }, + [SHORT_DATE]: { + options: { + wmdy: {format: 'MM/DD/YYYY'}, + wdmy: {format: 'DD/MM/YYYY'}, + }, + }, + [LONG_DATE]: { + options: { + wmdy: {format: 'ddd, MMM D, YYYY', label: 'Weekday, Month, Day, Year'}, + wdmy: {format: 'ddd, D MMM YYYY', label: 'Weekday, Day, Month, Year'}, + }, + }, +}; + export const setLocale = lang => { log.debug('Setting date locale to', lang); setMomentLocale(lang); @@ -34,7 +60,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 +69,103 @@ 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); +/** + * Retrieves the format string based on the category and key. + * + * @param {string} category - The category of the format. + * @param {string} key - The key for the specific format. + * @returns {string|undefined} - The format string if found, otherwise undefined. + */ + +export const getFormatString = (category, key) => { + return dateTimeFormatOptions[category].options[key]?.format; +}; + +/** + * Formats a date with a given time zone and user setting date format. + * + * @param {Date} date - The date to format. + * @param {string} tz - The time zone. + * @param {string} [userInterfaceDateFormat=SYSTEM_DEFAULT] - The user setting date format. + * @returns {string} - The formatted date string. + */ +export const shortDate = ( + date, + tz, + userInterfaceDateFormat = SYSTEM_DEFAULT, +) => { + const dateFormatString = getFormatString(SHORT_DATE, userInterfaceDateFormat); -export const longDate = (date, tz) => dateFormat(date, 'llll', tz); + const formatString = + isDefined(dateFormatString) && userInterfaceDateFormat !== SYSTEM_DEFAULT + ? dateFormatString + : 'L'; -export const dateTimeWithTimeZone = (date, tz) => - dateFormat(date, 'llll z', tz); + return getFormattedDate(date, formatString, tz); +}; -// vim: set ts=2 sw=2 tw=80: +/** + * Formats a date with a given time zone and user setting formats. + * + * @param {Date} date - The date to format. + * @param {string} tz - The time zone. + * @param {string} [userInterfaceTimeFormat=SYSTEM_DEFAULT] - The user setting time format. + * @param {string} [userInterfaceDateFormat=SYSTEM_DEFAULT] - The user setting date format. + * @returns {string} - The formatted date string. + */ + +export const longDate = ( + date, + tz, + userInterfaceTimeFormat = SYSTEM_DEFAULT, + userInterfaceDateFormat = SYSTEM_DEFAULT, +) => { + const dateFormatString = getFormatString(LONG_DATE, userInterfaceDateFormat); + const timeFormatString = getFormatString(TIME, userInterfaceTimeFormat); + + const useDefaultFormat = + userInterfaceTimeFormat === SYSTEM_DEFAULT || + userInterfaceDateFormat === SYSTEM_DEFAULT || + (!isDefined(dateFormatString) && !isDefined(timeFormatString)); + + const formatString = useDefaultFormat + ? 'llll' + : `${dateFormatString} ${timeFormatString}`; + + return getFormattedDate(date, formatString, tz); +}; + +/** + * Formats a date with a given time zone and user setting formats. + * + * @param {Date} date - The date to format. + * @param {string} tz - The time zone. + * @param {string} [userInterfaceTimeFormat=SYSTEM_DEFAULT] - The user setting time format. + * @param {string} [userInterfaceDateFormat=SYSTEM_DEFAULT] - The user setting date format. + * @returns {string} - The formatted date string with time zone. + */ + +export const dateTimeWithTimeZone = ( + date, + tz, + userInterfaceTimeFormat = SYSTEM_DEFAULT, + userInterfaceDateFormat = SYSTEM_DEFAULT, +) => { + const dateFormatString = getFormatString(LONG_DATE, userInterfaceDateFormat); + const timeFormatString = getFormatString(TIME, userInterfaceTimeFormat); + + const useDefaultFormat = + userInterfaceTimeFormat === SYSTEM_DEFAULT || + userInterfaceDateFormat === SYSTEM_DEFAULT || + (!isDefined(dateFormatString) && !isDefined(timeFormatString)); + + const formatString = useDefaultFormat + ? 'llll z' + : `${dateFormatString} ${timeFormatString} z`; + + return getFormattedDate(date, formatString, tz); +}; diff --git a/src/version.js b/src/version.js index 813e3788dd..8a84ece3f0 100644 --- a/src/version.js +++ b/src/version.js @@ -14,7 +14,7 @@ const getMajorMinorVersion = () => { return `${major}.${minor}`; }; -export const VERSION = '23.3.1-dev1'; +export const VERSION = '24.0.2-dev1'; export const RELEASE_VERSION = getMajorMinorVersion(); diff --git a/src/web/components/chart/schedule.jsx b/src/web/components/chart/schedule.jsx index 7422279f34..737cd1afae 100644 --- a/src/web/components/chart/schedule.jsx +++ b/src/web/components/chart/schedule.jsx @@ -10,7 +10,7 @@ import {LinearGradient} from '@visx/gradient'; import {scaleBand, scaleUtc} from 'd3-scale'; import _ from 'gmp/locale'; -import {dateTimeWithTimeZone} from 'gmp/locale/date'; +import {formattedUserSettingDateTimeWithTimeZone} from 'web/utils/userSettingTimeDateFormatters'; import date from 'gmp/models/date'; @@ -63,12 +63,14 @@ const cloneSchedule = (d, start) => { duration === 0 ? _('{{name}} Start: {{date}}', { name: d.label, - date: dateTimeWithTimeZone(start), + date: formattedUserSettingDateTimeWithTimeZone(start), }) : _('{{name}} Start: {{startdate}} End: {{enddate}}', { name: d.label, - startdate: dateTimeWithTimeZone(start), - enddate: dateTimeWithTimeZone(start.clone().add(duration, 'seconds')), + startdate: formattedUserSettingDateTimeWithTimeZone(start), + enddate: formattedUserSettingDateTimeWithTimeZone( + start.clone().add(duration, 'seconds'), + ), }); return { ...d, diff --git a/src/web/components/dashboard/display/created/createdtransform.jsx b/src/web/components/dashboard/display/created/createdtransform.jsx index 53d52cdf9b..8ddcbf8aec 100644 --- a/src/web/components/dashboard/display/created/createdtransform.jsx +++ b/src/web/components/dashboard/display/created/createdtransform.jsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseDate} from 'gmp/parser'; @@ -14,7 +14,7 @@ const transformCreated = (data = {}) => { const createdDate = parseDate(value); return { x: createdDate, - label: shortDate(createdDate), + label: formattedUserSettingShortDate(createdDate), y: parseInt(count), y2: parseInt(c_count), }; 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/components/date/datetime.jsx b/src/web/components/date/datetime.jsx index 5f673c643b..5af2bf60c9 100644 --- a/src/web/components/date/datetime.jsx +++ b/src/web/components/date/datetime.jsx @@ -3,14 +3,19 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {dateTimeWithTimeZone, ensureDate} from 'gmp/locale/date'; +import {ensureDate} from 'gmp/locale/date'; +import {formattedUserSettingDateTimeWithTimeZone} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined, hasValue} from 'gmp/utils/identity'; import PropTypes from 'web/utils/proptypes'; import useUserTimezone from 'web/hooks/useUserTimezone'; -const DateTime = ({formatter = dateTimeWithTimeZone, timezone, date}) => { +const DateTime = ({ + formatter = formattedUserSettingDateTimeWithTimeZone, + timezone, + date, +}) => { date = ensureDate(date); const [userTimezone] = useUserTimezone(); @@ -18,6 +23,7 @@ const DateTime = ({formatter = dateTimeWithTimeZone, timezone, date}) => { if (!hasValue(timezone)) { timezone = userTimezone; } + return !isDefined(date) || !date.isValid() ? null : formatter(date, timezone); }; diff --git a/src/web/components/menu/__tests__/usermenu.jsx b/src/web/components/menu/__tests__/usermenu.jsx deleted file mode 100644 index 2b4038fbdf..0000000000 --- a/src/web/components/menu/__tests__/usermenu.jsx +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-FileCopyrightText: 2024 Greenbone AG - * - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import {describe, test, expect, testing} from '@gsa/testing'; - -import date from 'gmp/models/date'; -import {longDate} from 'gmp/locale/date'; - -import {setSessionTimeout, setUsername} from 'web/store/usersettings/actions'; - -import {fireEvent, rendererWith, screen, act} from 'web/utils/testing'; - -import UserMenu from '../usermenu'; - -describe('UserMenu component tests', () => { - test('should render UserMenu', () => { - const {render} = rendererWith({gmp: {}, router: true, store: true}); - - const {element} = render(); - - expect(element).toBeInTheDocument(); - }); - - test('should render username and sessionTimeout', () => { - const {render, store} = rendererWith({gmp: {}, router: true, store: true}); - const timeout = date('2018-10-10'); - - store.dispatch(setSessionTimeout(timeout)); - store.dispatch(setUsername('foo')); - - const {element} = render(); - - expect(element).toHaveTextContent(longDate(timeout)); - expect(element).toHaveTextContent('foo'); - }); - - test('should route to usersettings on click', () => { - const {render} = rendererWith({ - gmp: {}, - store: true, - router: true, - }); - - const {getByTestId} = render(); - const userSettingsElement = getByTestId('usermenu-settings'); - - fireEvent.click(userSettingsElement); - - expect(window.location.pathname).toMatch('usersettings'); - }); - - test('should logout user on click', async () => { - const doLogout = testing.fn().mockResolvedValue(); - const gmp = { - doLogout, - }; - const {render} = rendererWith({gmp, store: true, router: true}); - - render(); - const userSettingsElement = screen.getByTestId('usermenu-logout'); - - await act(async () => { - fireEvent.click(userSettingsElement); - }); - - expect(gmp.doLogout).toHaveBeenCalled(); - }); - - test('should renew session timeout on click', async () => { - const renewSession = testing - .fn() - .mockResolvedValue({data: '2019-10-10T12:00:00Z'}); - const gmp = { - user: { - renewSession, - }, - }; - const {render} = rendererWith({gmp, store: true, router: true}); - - const {getAllByTestId} = render(); - const icons = getAllByTestId('svg-icon'); - - await act(async () => { - fireEvent.click(icons[3]); - }); - - expect(gmp.user.renewSession).toHaveBeenCalled(); - }); -}); diff --git a/src/web/components/menu/usermenu.jsx b/src/web/components/menu/usermenu.jsx deleted file mode 100644 index d4b6623c52..0000000000 --- a/src/web/components/menu/usermenu.jsx +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-FileCopyrightText: 2024 Greenbone AG - * - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import React from 'react'; - -import {useNavigate} from 'react-router-dom'; - -import styled, {keyframes} from 'styled-components'; - -import _ from 'gmp/locale'; -import {dateTimeWithTimeZone} from 'gmp/locale/date'; - -import LogoutIcon from 'web/components/icon/logouticon'; -import MySettingsIcon from 'web/components/icon/mysettingsicon'; -import RefreshIcon from 'web/components/icon/refreshicon'; -import ScheduleIcon from 'web/components/icon/scheduleicon'; -import UserIcon from 'web/components/icon/usericon'; - -import Divider from 'web/components/layout/divider'; -import Link from 'web/components/link/link'; - -import Theme from 'web/utils/theme'; -import useGmp from 'web/hooks/useGmp'; -import useUserName from 'web/hooks/useUserName'; -import useUserSessionTimeout from 'web/hooks/useUserSessionTimeout'; -import useUserTimezone from 'web/hooks/useUserTimezone'; - -const UserMenu = styled.span` - display: inline-flex; - flex-direction: column; -`; - -const Div = styled.div` - position: relative; - display: none; - ${UserMenu}:hover & { - display: block; - } - animation: ${keyframes({ - '0%': { - transform: 'scale(0.9)', - opacity: 0.2, - }, - '100%': { - transform: 'scale(1.0)', - opacity: 1, - }, - })} - 0.1s ease-in; -`; - -const List = styled.ul` - position: absolute; - margin: 0; - padding: 0; - right: 0; - top: 0; - z-index: ${Theme.Layers.menu}; - list-style: none; - font-size: 10px; - width: 300px; -`; - -const Entry = styled.li` - height: 30px; - width: 300px; - border-left: 1px solid ${Theme.mediumGray}; - border-right: 1px solid ${Theme.mediumGray}; - display: flex; - align-items: center; - background-color: ${Theme.white}; - padding-left: 12px; - - &:hover { - background: ${Theme.green}; - color: ${Theme.white}; - cursor: pointer; - } - &:first-child { - border-top: 1px solid ${Theme.mediumGray}; - cursor: default; - background-color: ${Theme.dialogGray} - &:hover { - color: ${Theme.black}; - } - } - &:nth-child(2) { - cursor: default; - background-color: ${Theme.dialogGray} - &:hover { - color: ${Theme.black}; - } - } - &:last-child { - border-top: 1px solid ${Theme.mediumGray}; - border-bottom: 1px solid ${Theme.mediumGray}; - } -`; - -const StyledUserIcon = styled(UserIcon)` - margin-right: 10px; -`; - -const StyledLink = styled(Link)` - width: 100%; - height: 100%; - &:link, - &:hover, - &:active, - &:visited, - &:hover { - color: inherit; - text-decoration: none; - } -`; - -const UserMenuContainer = () => { - const [sessionTimeout, setSessionTimeout] = useUserSessionTimeout(); - const [userTimezone] = useUserTimezone(); - const [userName] = useUserName(); - const gmp = useGmp(); - const navigate = useNavigate(); - - const handleLogout = event => { - event.preventDefault(); - - gmp.doLogout().then(() => { - navigate('/login?type=logout'); - }); - }; - - const handleRenewSessionTimeout = () => { - gmp.user.renewSession().then(response => setSessionTimeout(response.data)); - }; - - return ( - - -
- - - - - {userName} - - - - - - - {_('Session timeout: {{date}}', { - date: dateTimeWithTimeZone(sessionTimeout, userTimezone), - })} - - - - - - - - - {_('My Settings')} - - - - - - - {_('Log Out')} - - - -
-
- ); -}; - -export default UserMenuContainer; - -// vim: set ts=2 sw=2 tw=80: 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/audits/detailspage.jsx b/src/web/pages/audits/detailspage.jsx index d76a7c685b..bf4577fcef 100644 --- a/src/web/pages/audits/detailspage.jsx +++ b/src/web/pages/audits/detailspage.jsx @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import _ from 'gmp/locale'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -171,25 +170,30 @@ export const ToolBarIcons = ({ id={entity.current_report.id} title={_('Current Report for Audit {{- name}} from {{- date}}', { name: entity.name, - date: shortDate(entity.current_report.scan_start), + date: formattedUserSettingShortDate( + entity.current_report.scan_start, + ), })} > )} - {!isDefined(entity.current_report) && isDefined(entity.last_report) && ( - - - - )} + {!isDefined(entity.current_report) && + isDefined(entity.last_report) && ( + + + + )} { const modified = parseDate(value); return { x: modified, - label: shortDate(modified), + label: formattedUserSettingShortDate(modified), y: parseInt(count), y2: parseInt(c_count), }; diff --git a/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx b/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx index 402b47f962..6ff1d3787e 100644 --- a/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx +++ b/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx @@ -6,7 +6,7 @@ import React from 'react'; import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseDate} from 'gmp/parser'; @@ -38,7 +38,7 @@ const transformModified = (data = {}) => { const modified = parseDate(value); return { x: modified, - label: shortDate(modified), + label: formattedUserSettingShortDate(modified), y: parseInt(count), y2: parseInt(c_count), }; diff --git a/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx b/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx index ebe54913b9..f539b3c351 100644 --- a/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx +++ b/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx @@ -10,7 +10,7 @@ import {withRouter} from 'web/utils/withRouter'; import styled from 'styled-components'; import {_, _l} from 'gmp/locale/lang'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseFloat, parseSeverity} from 'gmp/parser'; @@ -51,7 +51,7 @@ const transformVulnScoreData = (data = {}, {severityClass}) => { const {severity} = stats; const averageSeverity = parseSeverity(severity.mean); const riskFactor = resultSeverityRiskFactor(averageSeverity); - const modifiedDate = longDate(modified); + const modifiedDate = formattedUserSettingLongDate(modified); const toolTip = ( {name}: 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/notes/detailspage.jsx b/src/web/pages/notes/detailspage.jsx index 18c559a7d7..beb0f2b022 100644 --- a/src/web/pages/notes/detailspage.jsx +++ b/src/web/pages/notes/detailspage.jsx @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import {connect} from 'react-redux'; import _ from 'gmp/locale'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -146,7 +145,10 @@ const Details = connect(rootState => ({ isDefined(entity.endTime) && ' ' + _('until {{- enddate}}', { - enddate: longDate(entity.endTime, timezone), + enddate: formattedUserSettingLongDate( + entity.endTime, + timezone, + ), })} diff --git a/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx b/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx index 96c4409643..920246e407 100644 --- a/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx +++ b/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx @@ -10,7 +10,7 @@ import {withRouter} from 'web/utils/withRouter'; import styled from 'styled-components'; import {_, _l} from 'gmp/locale/lang'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseFloat, parseSeverity} from 'gmp/parser'; @@ -37,7 +37,7 @@ const ToolTip = styled.div` line-height: 1.2em; `; -const transformVulnScoreData = (data = {}, {severityClass}) => { +const transformVulnScoreData = (data = {}) => { const {groups = []} = data; const tdata = groups .filter(group => { @@ -51,7 +51,7 @@ const transformVulnScoreData = (data = {}, {severityClass}) => { const {average_severity, average_severity_score} = stats; const averageSeverity = parseSeverity(average_severity.mean); const riskFactor = resultSeverityRiskFactor(averageSeverity); - const modifiedDate = longDate(modified); + const modifiedDate = formattedUserSettingLongDate(modified); const toolTip = ( {name}: diff --git a/src/web/pages/overrides/detailspage.jsx b/src/web/pages/overrides/detailspage.jsx index a081378351..aa47d15b18 100644 --- a/src/web/pages/overrides/detailspage.jsx +++ b/src/web/pages/overrides/detailspage.jsx @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import {connect} from 'react-redux'; import _ from 'gmp/locale'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -149,7 +148,10 @@ const Details = connect(rootState => ({ isDefined(entity.endTime) && ' ' + _('until {{- enddate}}', { - enddate: longDate(entity.endTime, timezone), + enddate: formattedUserSettingLongDate( + entity.endTime, + timezone, + ), })} diff --git a/src/web/pages/reportformats/details.jsx b/src/web/pages/reportformats/details.jsx index d2d4664270..859840594b 100644 --- a/src/web/pages/reportformats/details.jsx +++ b/src/web/pages/reportformats/details.jsx @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import _ from 'gmp/locale'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -72,7 +71,7 @@ const ReportFormatDetails = ({entity, links = true}) => { {renderYesNo(trust.value)} {isDefined(trust.time) && ( - ({shortDate(trust.time)}) + ({formattedUserSettingShortDate(trust.time)}) )} diff --git a/src/web/pages/reportformats/row.jsx b/src/web/pages/reportformats/row.jsx index fdf57d2083..3fd51dcb05 100644 --- a/src/web/pages/reportformats/row.jsx +++ b/src/web/pages/reportformats/row.jsx @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import _ from 'gmp/locale'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import EntityNameTableData from 'web/entities/entitynametabledata'; @@ -82,7 +81,9 @@ const Row = ({ {entity.content_type} {renderYesNo(entity.trust.value)} - {entity.trust.time && ({shortDate(entity.trust.time)})} + {entity.trust.time && ( + ({formattedUserSettingShortDate(entity.trust.time)}) + )} {renderYesNo(entity.isActive())} diff --git a/src/web/pages/reports/dashboard/highresultsdisplay.jsx b/src/web/pages/reports/dashboard/highresultsdisplay.jsx index 79beaafcf3..4f73fb79aa 100644 --- a/src/web/pages/reports/dashboard/highresultsdisplay.jsx +++ b/src/web/pages/reports/dashboard/highresultsdisplay.jsx @@ -6,7 +6,7 @@ import React from 'react'; import {_, _l} from 'gmp/locale/lang'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseFloat, parseDate} from 'gmp/parser'; @@ -33,7 +33,7 @@ const transformHighResults = (data = {}) => { return groups.map(group => { const reportDate = parseDate(group.value); return { - label: longDate(reportDate), + label: formattedUserSettingLongDate(reportDate), x: reportDate, y: parseInt(group.stats.high.max), y2: parseFloat(group.stats.high_per_host.max), diff --git a/src/web/pages/reports/details/tlscertificatestable.jsx b/src/web/pages/reports/details/tlscertificatestable.jsx index 87d2b2d10b..62b2c9f0e4 100644 --- a/src/web/pages/reports/details/tlscertificatestable.jsx +++ b/src/web/pages/reports/details/tlscertificatestable.jsx @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import styled from 'styled-components'; import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import PropTypes from 'web/utils/proptypes'; @@ -123,10 +122,16 @@ const Row = ({ {serial} - + - + { return { label: name, duration, - nextStart: dateTimeWithTimeZone(event.nextDate), + nextStart: formattedUserSettingDateTimeWithTimeZone(event.nextDate), starts: event.getNextDates(endDate), timezone, isInfinite: isDefined(recurrence.isFinite) && !recurrence.isFinite(), diff --git a/src/web/pages/tasks/detailspage.jsx b/src/web/pages/tasks/detailspage.jsx index 5fe2c78b47..178ff8c9d1 100644 --- a/src/web/pages/tasks/detailspage.jsx +++ b/src/web/pages/tasks/detailspage.jsx @@ -6,7 +6,7 @@ import React from 'react'; import _ from 'gmp/locale'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import Filter from 'gmp/models/filter'; @@ -181,7 +181,9 @@ export const ToolBarIcons = ({ id={entity.current_report.id} title={_('Current Report for Task {{- name}} from {{- date}}', { name: entity.name, - date: shortDate(entity.current_report.scan_start), + date: formattedUserSettingShortDate( + entity.current_report.scan_start, + ), })} > @@ -195,7 +197,9 @@ export const ToolBarIcons = ({ id={entity.last_report.id} title={_('Last Report for Task {{- name}} from {{- date}}', { name: entity.name, - date: shortDate(entity.last_report.scan_start), + date: formattedUserSettingShortDate( + entity.last_report.scan_start, + ), })} > diff --git a/src/web/pages/tasks/icons/scheduleicon.jsx b/src/web/pages/tasks/icons/scheduleicon.jsx index fd9b12fc3e..e0cab189eb 100644 --- a/src/web/pages/tasks/icons/scheduleicon.jsx +++ b/src/web/pages/tasks/icons/scheduleicon.jsx @@ -17,7 +17,7 @@ import ScheduleIcon from 'web/components/icon/scheduleicon'; import DetailsLink from 'web/components/link/detailslink'; -import {dateTimeWithTimeZone} from 'gmp/locale/date'; +import {formattedUserSettingDateTimeWithTimeZone} from 'web/utils/userSettingTimeDateFormatters'; import {getTimezone} from 'web/store/usersettings/selectors'; const TaskScheduleIcon = ({size, links = true, schedule, timezone}) => { @@ -47,7 +47,7 @@ const TaskScheduleIcon = ({size, links = true, schedule, timezone}) => { } else if (count === 1) { title = _('View Details of Schedule {{name}} (Next due: {{time}} Once)', { name, - time: dateTimeWithTimeZone(nextDate, timezone), + time: formattedUserSettingDateTimeWithTimeZone(nextDate, timezone), }); } else if (count > 1) { title = _( @@ -55,14 +55,14 @@ const TaskScheduleIcon = ({size, links = true, schedule, timezone}) => { '{{time}}, {{periods}} more times )', { name, - time: dateTimeWithTimeZone(nextDate, timezone), + time: formattedUserSettingDateTimeWithTimeZone(nextDate, timezone), periods: count, }, ); } else { title = _('View Details of Schedule {{name}} (Next due: {{time}})', { name, - time: dateTimeWithTimeZone(nextDate, timezone), + time: formattedUserSettingDateTimeWithTimeZone(nextDate, timezone), }); } diff --git a/src/web/pages/tickets/dashboard/createddisplay.jsx b/src/web/pages/tickets/dashboard/createddisplay.jsx index c79f5266ad..16bc6c96dd 100644 --- a/src/web/pages/tickets/dashboard/createddisplay.jsx +++ b/src/web/pages/tickets/dashboard/createddisplay.jsx @@ -4,7 +4,7 @@ */ import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import date from 'gmp/models/date'; import {TICKETS_FILTER_FILTER} from 'gmp/models/filter'; @@ -63,7 +63,7 @@ export const TicketsCreatedDisplay = createDisplay({ }); export const TicketsCreatedTableDisplay = createDisplay({ - dataRow: row => [row.y, row.y2, shortDate(row.x)], + dataRow: row => [row.y, row.y2, formattedUserSettingShortDate(row.x)], dataTitles: [_l('Created Tickets'), _l('Total Tickets'), _l('Time')], dataTransform: transfromCreated, displayComponent: DataTableDisplay, diff --git a/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx b/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx index ac11cecfa5..40c631cd36 100644 --- a/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx +++ b/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx @@ -6,7 +6,7 @@ import React from 'react'; import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseDate} from 'gmp/parser'; @@ -37,7 +37,7 @@ const transformModified = (data = {}) => { const modified = parseDate(value); return { x: modified, - label: shortDate(modified), + label: formattedUserSettingShortDate(modified), y: parseInt(count), y2: parseInt(c_count), }; diff --git a/src/web/pages/usersettings/dialog.jsx b/src/web/pages/usersettings/dialog.jsx index 1673f37f13..0026e1cfb4 100644 --- a/src/web/pages/usersettings/dialog.jsx +++ b/src/web/pages/usersettings/dialog.jsx @@ -11,7 +11,9 @@ import {connect} from 'react-redux'; import {isDefined} from 'gmp/utils/identity'; -import {parseFloat, parseYesNo} from 'gmp/parser'; +import {parseFloat, parseYesNo, YES_VALUE, NO_VALUE} from 'gmp/parser'; + +import {SYSTEM_DEFAULT} from 'gmp/locale/date'; import SaveDialog from 'web/components/dialog/savedialog'; @@ -39,7 +41,7 @@ const FormGroupSizer = styled(Column)` const fieldsToValidate = ['rowsPerPage']; -let UserSettingsDialog = ({ +const UserSettingsDialogComponent = ({ alerts, credentials, filters, @@ -49,6 +51,9 @@ let UserSettingsDialog = ({ schedules, targets, timezone, + userInterfaceTimeFormat, + userInterfaceDateFormat, + isUserInterfaceTimeDateDefault, userInterfaceLanguage, rowsPerPage, maxRowsPerPage, @@ -102,6 +107,9 @@ let UserSettingsDialog = ({ }) => { const settings = { timezone, + userInterfaceTimeFormat, + userInterfaceDateFormat, + isUserInterfaceTimeDateDefault, oldPassword: '', newPassword: '', confPassword: '', @@ -194,6 +202,11 @@ 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..04e7c8f44f 100644 --- a/src/web/pages/usersettings/generalpart.jsx +++ b/src/web/pages/usersettings/generalpart.jsx @@ -3,12 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - -import React from 'react'; +import {useState} from 'react'; import styled from 'styled-components'; import {parseYesNo, YES_VALUE, NO_VALUE} from 'gmp/parser'; import {isDefined} from 'gmp/utils/identity'; +import {dateTimeFormatOptions, SYSTEM_DEFAULT} from 'gmp/locale/date'; import Checkbox from 'web/components/form/checkbox'; import FormGroup from 'web/components/form/formgroup'; @@ -68,6 +68,9 @@ Notification.propTypes = { const GeneralPart = ({ timezone, + userInterfaceDateFormat, + userInterfaceTimeFormat, + isUserInterfaceTimeDateDefault, oldPassword, newPassword, confPassword, @@ -82,11 +85,82 @@ const GeneralPart = ({ onChange, }) => { const [_] = useTranslation(); + const [prevUserInterfaceTimeFormat, setPrevUserInterfaceTimeFormat] = + useState(undefined); + const [prevUserInterfaceDateFormat, setPrevUserInterfaceDateFormat] = + useState(undefined); + + const getSelectItems = category => { + return Object.entries(dateTimeFormatOptions[category].options).map( + ([value, {label}]) => ({ + value: isNaN(value) ? value : Number(value), + label: _(label), + }), + ); + }; + + const handleSysDefaultChange = event => { + const isSystemDefault = parseYesNo(event); + + const defaultTimeFormat = 24; + const defaultDateFormat = 'wdmy'; + + const currentUserInterfaceTimeFormat = + userInterfaceTimeFormat || defaultTimeFormat; + const currentUserInterfaceDateFormat = + userInterfaceDateFormat || defaultDateFormat; + + if (!isSystemDefault) { + onChange( + prevUserInterfaceTimeFormat || defaultTimeFormat, + 'userInterfaceTimeFormat', + ); + onChange( + prevUserInterfaceDateFormat || defaultDateFormat, + 'userInterfaceDateFormat', + ); + } else { + setPrevUserInterfaceTimeFormat(currentUserInterfaceTimeFormat); + setPrevUserInterfaceDateFormat(currentUserInterfaceDateFormat); + + onChange(SYSTEM_DEFAULT, 'userInterfaceTimeFormat'); + onChange(SYSTEM_DEFAULT, 'userInterfaceDateFormat'); + } + + onChange(isSystemDefault, 'isUserInterfaceTimeDateDefault'); + }; + return ( <> + + + + { - this.closeDialog(); - this.props.setLocale( - userInterfaceLanguage === BROWSER_LANGUAGE - ? undefined - : userInterfaceLanguage, - ); - this.props.setTimezone(timezone); - - this.loadSettings(); - }); + async handleSaveSettings(data) { + try { + const {gmp} = this.props; + + const {userInterfaceLanguage = BROWSER_LANGUAGE, timezone} = data; + + 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 +286,15 @@ class UserSettings extends React.Component { targets, isLoading = true, timezone, + userInterfaceDateFormat = {}, + userInterfaceTimeFormat = {}, + isUserInterfaceTimeDateDefault = { + value: + userInterfaceTimeFormat.value === SYSTEM_DEFAULT && + userInterfaceDateFormat.value === SYSTEM_DEFAULT + ? YES_VALUE + : NO_VALUE, + }, userInterfaceLanguage = {}, rowsPerPage = {}, maxRowsPerPage = {}, @@ -364,11 +385,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 === SYSTEM_DEFAULT + ? _('System Default') + : `${Number(userInterfaceTimeFormat.value)}h`} + + + + {_('Date Format')} + + {userInterfaceDateFormat.value === SYSTEM_DEFAULT + ? _('System Default') + : userInterfaceDateFormat.value} + + + {_('Password')} ******** @@ -723,7 +760,7 @@ class UserSettings extends React.Component { )} - + )} {dialogVisible && !isLoading && ( )} - + ); } } @@ -811,6 +853,7 @@ UserSettings.propTypes = { credentials: PropTypes.array, credentialsFilter: PropTypes.object, cveFilter: PropTypes.object, + userInterfaceDateFormat: PropTypes.oneOf(['wdmy', 'wmdy', SYSTEM_DEFAULT]), defaultAlert: PropTypes.object, defaultEsxiCredential: PropTypes.object, defaultOpenvasScanConfig: PropTypes.object, @@ -828,6 +871,7 @@ UserSettings.propTypes = { dynamicSeverity: PropTypes.object, filters: PropTypes.array, filtersFilter: PropTypes.object, + isUserInterfaceTimeDateDefault: PropTypes.oneOfType([YES_VALUE, NO_VALUE]), gmp: PropTypes.gmp.isRequired, groupsFilter: PropTypes.object, hostsFilter: PropTypes.object, @@ -870,6 +914,7 @@ UserSettings.propTypes = { tasksFilter: PropTypes.object, ticketsFilter: PropTypes.object, timezone: PropTypes.string, + userInterfaceTimeFormat: PropTypes.oneOf([12, 24, SYSTEM_DEFAULT]), tlsCertificatesFilter: PropTypes.object, userInterfaceLanguage: PropTypes.object, usersFilter: PropTypes.object, @@ -879,11 +924,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 +1061,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..733728824f --- /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( + 'userInterfaceDateFormat', + ); + + 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, + ); +};