diff --git a/src/gmp/commands/__tests__/timezones.test.ts b/src/gmp/commands/__tests__/timezones.test.ts new file mode 100644 index 0000000000..30bf3916ea --- /dev/null +++ b/src/gmp/commands/__tests__/timezones.test.ts @@ -0,0 +1,70 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, test, expect} from '@gsa/testing'; +import {createResponse, createHttp} from 'gmp/commands/testing'; +import TimezonesCommand from 'gmp/commands/timezones'; + +describe('TimezonesCommand tests', () => { + test('should return timezones list', async () => { + const response = createResponse({ + get_timezones: { + get_timezones_response: { + timezone: [ + {name: 'UTC'}, + {name: 'Europe/Berlin'}, + {name: 'America/New_York'}, + {name: 'Asia/Tokyo'}, + ], + }, + }, + }); + + const fakeHttp = createHttp(response); + + const cmd = new TimezonesCommand(fakeHttp); + const resp = await cmd.get(); + + expect(fakeHttp.request).toHaveBeenCalledWith('get', { + args: { + cmd: 'get_timezones', + }, + }); + + const {data} = resp; + expect(data).toHaveLength(4); + expect(data).toEqual([ + 'UTC', + 'Europe/Berlin', + 'America/New_York', + 'Asia/Tokyo', + ]); + }); + + test('should handle empty timezones list', async () => { + const response = createResponse({ + get_timezones: { + get_timezones_response: { + timezone: [], + }, + }, + }); + + const fakeHttp = createHttp(response); + + const cmd = new TimezonesCommand(fakeHttp); + const resp = await cmd.get(); + + expect(fakeHttp.request).toHaveBeenCalledWith('get', { + args: { + cmd: 'get_timezones', + }, + }); + + const {data} = resp; + expect(data).toHaveLength(0); + expect(data).toEqual([]); + }); +}); diff --git a/src/gmp/commands/timezones.ts b/src/gmp/commands/timezones.ts new file mode 100644 index 0000000000..141b5b6dab --- /dev/null +++ b/src/gmp/commands/timezones.ts @@ -0,0 +1,25 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import HttpCommand from 'gmp/commands/http'; +import type Http from 'gmp/http/http'; +import {map} from 'gmp/utils/array'; + +class TimezonesCommand extends HttpCommand { + constructor(http: Http) { + super(http, {cmd: 'get_timezones'}); + } + + async get() { + const response = await this.httpGetWithTransform(); + const {data} = response; + const {timezone: timezones} = + // @ts-expect-error + data.get_timezones.get_timezones_response; + return response.set(map(timezones, tz => tz.name)); + } +} + +export default TimezonesCommand; diff --git a/src/gmp/gmp.ts b/src/gmp/gmp.ts index 8fb7f4670e..7ca78c5bc9 100644 --- a/src/gmp/gmp.ts +++ b/src/gmp/gmp.ts @@ -69,6 +69,7 @@ import TargetCommand from 'gmp/commands/target'; import TargetsCommand from 'gmp/commands/targets'; import TaskCommand from 'gmp/commands/task'; import TasksCommand from 'gmp/commands/tasks'; +import TimezonesCommand from 'gmp/commands/timezones'; import TrashCanCommand from 'gmp/commands/trashcan'; import UserCommand from 'gmp/commands/user'; import UsersCommand from 'gmp/commands/users'; @@ -139,6 +140,7 @@ class Gmp { readonly targets: TargetsCommand; readonly task: TaskCommand; readonly tasks: TasksCommand; + readonly timezones: TimezonesCommand; readonly trashcan: TrashCanCommand; readonly user: UserCommand; readonly users: UsersCommand; @@ -205,6 +207,7 @@ class Gmp { this.targets = new TargetsCommand(this.http); this.task = new TaskCommand(this.http); this.tasks = new TasksCommand(this.http); + this.timezones = new TimezonesCommand(this.http); this.trashcan = new TrashCanCommand(this.http); this.user = new UserCommand(this.http); this.users = new UsersCommand(this.http); diff --git a/src/web/components/form/TimeZoneSelect.tsx b/src/web/components/form/TimeZoneSelect.tsx index 95e8af25eb..c36be63be1 100644 --- a/src/web/components/form/TimeZoneSelect.tsx +++ b/src/web/components/form/TimeZoneSelect.tsx @@ -6,6 +6,7 @@ import {useMemo} from 'react'; import timezones, {DEFAULT_TIMEZONE} from 'gmp/time-zones'; import Select, {type SelectProps} from 'web/components/form/Select'; +import {useGetTimezones} from 'web/hooks/use-query/timezones'; interface TimeZoneSelectProps extends Omit< SelectProps, @@ -18,13 +19,18 @@ const TimeZoneSelectComponent = ({ value = DEFAULT_TIMEZONE, ...props }: TimeZoneSelectProps) => { + const {data: fetchedTimezones} = useGetTimezones(); + + // Use fetched timezones if available, otherwise fall back to hardcoded list + const timezoneList = fetchedTimezones ?? timezones; + const timezoneItems = useMemo( () => - timezones.map(name => ({ + timezoneList.map(name => ({ label: name, value: name, })), - [], + [timezoneList], ); return