/* * Copyright (C) 2025 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import { setAllValidTimezonesAction, setTimezoneAction, } from "../actions/timezone-actions.js"; import { error } from "../helpers/log.js"; import { _callClient, _getProperty, _setProperty } from "./helpers.js"; const OBJECT_PATH = "/org/fedoraproject/Anaconda/Modules/Timezone"; const INTERFACE_NAME = "org.fedoraproject.Anaconda.Modules.Timezone"; const KICKSTART_MODULE_INTERFACE = "org.fedoraproject.Anaconda.Modules"; /** * Helper for DBus Timezone API. */ const callClient = (...args) => { return _callClient(TimezoneClient, OBJECT_PATH, INTERFACE_NAME, ...args); }; const setProperty = (...args) => { return _setProperty(TimezoneClient, OBJECT_PATH, INTERFACE_NAME, ...args); }; const getProperty = (...args) => { return _getProperty(TimezoneClient, OBJECT_PATH, INTERFACE_NAME, ...args); }; export class TimezoneClient { constructor (address, dispatch) { if (TimezoneClient.instance && (!address || TimezoneClient.instance.address === address)) { return TimezoneClient.instance; } TimezoneClient.instance?.client.close(); TimezoneClient.instance = this; this.client = cockpit.dbus( INTERFACE_NAME, { address, bus: "none", superuser: "try" } ); this.address = address; this.dispatch = dispatch; } async init (args = {}) { // eslint-disable-line no-unused-vars -- optional bootstrap args from Application this.client.addEventListener("close", () => error("Timezone client closed")); // You can subscribe to DBus signals here if needed. this.startEventMonitor(); const [timezone, allValidTimezones, kickstarted] = await Promise.all([ getTimezone(), getAllValidTimezones(), getKickstarted(), ]); this.dispatch(setTimezoneAction({ kickstarted: Boolean(kickstarted), timezone })); this.dispatch(setAllValidTimezonesAction({ allValidTimezones })); } stopEventMonitor () { this._subscription?.remove(); } startEventMonitor () { this._subscription = this.client.subscribe( { }, async (path, iface, signal, args) => { switch (signal) { case "PropertiesChanged": if (args[0] === INTERFACE_NAME && Object.hasOwn(args[1], "Timezone")) { this.dispatch(setTimezoneAction({ timezone: args[1].Timezone.v })); } break; } } ); } } /** * Gets the current timezone string. * @returns {Promise} Resolves with the current timezone (e.g. 'Europe/Prague') */ export const getTimezone = () => { return getProperty("Timezone"); }; /** * Whether the timezone module was configured from kickstart * @returns {Promise} */ export const getKickstarted = () => { return _getProperty(TimezoneClient, OBJECT_PATH, KICKSTART_MODULE_INTERFACE, "Kickstarted"); }; /** * Sets the system timezone (priority 90 = user action). * @param {Object} params * @param {string} params.timezone - The timezone to set (e.g. 'Europe/Prague') * @returns {Promise} */ export const setTimezone = ({ timezone }) => { return callClient("SetTimezoneWithPriority", [timezone, 90]); }; /** * Sets the system timezone with a custom priority. * @param {Object} params * @param {string} params.timezone - The timezone to set (e.g. 'Europe/Prague') * @param {number} params.priority - The priority (e.g. 90) * @returns {Promise} */ export const setTimezoneWithPriority = ({ priority, timezone }) => { return callClient("SetTimezoneWithPriority", [timezone, priority]); }; /** * Gets a list of all valid timezones, grouped by region. * @returns {Promise} Resolves with a dictionary {Region: [cities...]} */ export const getAllValidTimezones = () => { return callClient("GetAllValidTimezones"); }; /** * Returns true if the hardware clock is set to UTC. * @returns {Promise} Resolves with true if set to UTC, false otherwise. */ export const getIsUTC = () => { return getProperty("IsUTC"); }; /** * Sets whether the hardware clock is set to UTC. * @param {Object} params * @param {boolean} params.isUTC * @returns {Promise} */ export const setIsUTC = ({ isUTC }) => { return setProperty("IsUTC", cockpit.variant("b", isUTC)); }; /** * Returns true if NTP is enabled. * @returns {Promise} Resolves with true if NTP is enabled. */ export const getNTPEnabled = () => { return getProperty("NTPEnabled"); }; /** * Enables or disables NTP service. * @param {Object} params * @param {boolean} params.enabled * @returns {Promise} */ export const setNTPEnabled = ({ enabled }) => { return setProperty("NTPEnabled", cockpit.variant("b", enabled)); }; /** * Gets all configured NTP/time sources. * @returns {Promise} Resolves with an array of time source objects. */ export const getTimeSources = () => { return getProperty("TimeSources"); }; /** * Sets the list of time sources. * @param {Object} params * @param {Array} params.sources - Array of TimeSourceData tuples * @returns {Promise} */ export const setTimeSources = ({ sources }) => { return setProperty("TimeSources", cockpit.variant("aa{sv}", sources)); }; /** * Adds a single custom NTP time source to the list of configured sources. * @param {Object} params * @param {string} params.hostname - NTP server hostname (e.g. 'ntp.example.com') * @param {boolean} [params.isPool] - Whether this is a pool (true) or a single server (false) * @returns {Promise} */ export const addTimeSource = async ({ hostname, isPool = false, options = [] }) => { const existing = await getTimeSources(); const newSource = { hostname: cockpit.variant("s", hostname), options: cockpit.variant("as", options), type: cockpit.variant("s", isPool ? "POOL" : "NTP"), }; const updatedSources = [...existing, newSource]; return setTimeSources({ sources: updatedSources }); }; /** * Gets NTP servers listed in the chrony configuration file. * @returns {Promise} Resolves with an array of TimeSourceData structures. */ export const getTimeServersFromConfig = () => { return getProperty("TimeServersFromConfig"); }; /** * Starts a geolocation task. * @returns {Promise} Resolves with the task object path. */ export const startGeolocationWithTask = () => { return callClient("StartGeolocationWithTask"); }; /** * Gets geolocation result data, if available. * @returns {Promise} Resolves with geolocation result data. */ export const getGeolocationResult = () => { return getProperty("GeolocationResult"); }; /** * Gets the current local date and time as an ISO8601 string. * @returns {Promise} Resolves with date/time in ISO8601 format (e.g. '2024-06-05T13:23') */ export const getSystemDateTime = () => { return callClient("GetSystemDateTime"); }; /** * Sets the system date and time using an ISO8601 string. * @param {string} dateTimeSpec - Date and time (e.g. '2024-06-05T13:23') * @returns {Promise} */ export const setSystemDateTime = ({ dateTimeSpec }) => { return callClient("SetSystemDateTime", [dateTimeSpec]); }; /** * Checks if the specified NTP server is reachable. * @param {string} hostname - The NTP server to check (e.g. 'ntp.example.com') * @param {boolean} isNTS - Whether to check for NTS (Network Time Security) */ export const checkNTPServer = ({ hostname, isNTS }) => { return callClient("CheckNTPServer", [hostname, isNTS]); };