🏡 index : quine-global/quine-anaconda-webui.git

/*
 * Copyright (C) 2023 Red Hat, Inc.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

import { useCallback, useReducer } from "react";

/* Initial state for the storeage store substate */
export const storageInitialState = {
    appliedPartitioning: null,
    deviceTrees: {
        "": {
            actions: [],
            devices: {},
            mountPoints: {},
        },
    },
    diskSelection: {
        ignoredDisks: [],
        selectedDisks: [],
        usableDisks: []
    },
    luks: {
        confirmPassphrase: "",
        encrypted: false,
        passphrase: "",
    },
    mountPoints: {},
    partitioning: {},
    storageScenarioId: null,
};

/* Initial state for the localization store substate */
export const localizationInitialState = {
    commonLocales: [],
    compositorLayouts: [],
    keyboardLayouts: [],
    language: "",
    languageKickstarted: false,
    languages: {},
    plannedVconsole: undefined,
    plannedXlayouts: undefined,
    userConfigured: false,
    xlayouts: undefined,
};

/* Initial state for the boss store substate */
export const bossInitialState = {
    installationStatus: null,
    pendingError: { message: "", type: "" },
};

/* Intial state for the network store substate */
export const networkInitialState = {
    connected: null
};

/* Intial state for the runtime store substate */
export const runtimeInitialState = {
    connected: null,
};

/* Initial state for the timezone store substate */
export const timezoneInitialState = {
    allValidTimezones: {},
    kickstarted: false,
    timezone: "",
    userConfigured: false,
};

export const miscInitialState = {
    isFetching: false,
};

/* Initial state for the payload store substate */
export const payloadInitialState = {
    environments: [],
    groups: [],
    packagesKickstarted: false,
    selection: null,
    type: null,
};

/* Initial state for the users store substate */
/* FIXME: This is not storing information from the anaconda backend, but also non-submitted user input */
/* The Store is meant to store information from the backend only */
export const usersInitialState = {
    adminUserExists: false,
    canModifyRootConfiguration: true,
    canModifyUserConfiguration: true,
    confirmPassword: "",
    isRootEnabled: false,
    isRootPasswordSet: false,
    password: "",
    rootConfirmPassword: "",
    rootPassword: "",
    users: [],
};

/* Initial state for the global store */
export const initialState = {
    boss: bossInitialState,
    localization: localizationInitialState,
    misc: miscInitialState,
    network: networkInitialState,
    payload: payloadInitialState,
    runtime: runtimeInitialState,
    storage: storageInitialState,
    timezone: timezoneInitialState,
    users: usersInitialState,
};

/* Custom hook to use the reducer with async actions */
export const useReducerWithThunk = (reducer, initialState) => {
    const [state, dispatch] = useReducer(reducer, initialState);

    function customDispatch (action) {
        if (typeof action === "function") {
            return action(customDispatch);
        } else {
            dispatch(action);
        }
    }

    // Memoize so you can include it in the dependency array without causing infinite loops
    // eslint-disable-next-line react-hooks/exhaustive-deps
    const stableDispatch = useCallback(customDispatch, [dispatch]);

    return [state, stableDispatch];
};

export const reducer = (state, action) => {
    return ({
        boss: bossReducer(state.boss, action),
        localization: localizationReducer(state.localization, action),
        misc: miscReducer(state.misc, action),
        network: networkReducer(state.network, action),
        payload: payloadReducer(state.payload, action),
        runtime: runtimeReducer(state.runtime, action),
        storage: storageReducer(state.storage, action),
        timezone: timezoneReducer(state.timezone, action),
        users: usersReducer(state.users, action)
    });
};

export const storageReducer = (state = storageInitialState, action) => {
    if (action.type === "GET_DEVICES_DATA") {
        return {
            ...state,
            deviceTrees: {
                ...state.deviceTrees,
                [state.appliedPartitioning ? state.partitioning.deviceTree.path : ""]: {
                    actions: action.payload.actions,
                    devices: action.payload.devices,
                    existingSystems: action.payload.existingSystems,
                    mountPoints: action.payload.mountPoints,
                }
            }
        };
    } else if (action.type === "GET_DISK_SELECTION") {
        return { ...state, diskSelection: action.payload.diskSelection };
    } else if (action.type === "GET_PARTITIONING_DATA") {
        return {
            ...state,
            partitioning: {
                ...state.partitioning,
                ...action.payload.partitioningData,
                deviceTree: action.payload.deviceTree,
                storageScenarioId: state.storageScenarioId
            }
        };
    } else if (action.type === "SET_APPLIED_PARTITIONING") {
        return { ...state, appliedPartitioning: action.payload.appliedPartitioning };
    } else if (action.type === "SET_STORAGE_SCENARIO") {
        return { ...state, storageScenarioId: action.payload.scenario };
    } else if (action.type === "SET_LUKS_ENCRYPTION_DATA") {
        const newLuksState = {
            ...state.luks,
            ...action.payload,
        };

        if (action.payload.encrypted === false) {
            newLuksState.passphrase = "";
            newLuksState.confirmPassphrase = "";
        }

        return {
            ...state,
            luks: newLuksState,
        };
    } else {
        return state;
    }
};

export const localizationReducer = (state = localizationInitialState, action) => {
    if (action.type === "GET_LANGUAGE_DATA") {
        return { ...state, languages: { ...state.languages, ...action.payload.languageData } };
    } else if (action.type === "GET_COMMON_LOCALES") {
        return { ...state, commonLocales: action.payload.commonLocales };
    } else if (action.type === "GET_LANGUAGE") {
        return { ...state, language: action.payload.language };
    } else if (action.type === "GET_KEYBOARD_LAYOUTS") {
        return { ...state, keyboardLayouts: action.payload.keyboardLayouts };
    } else if (action.type === "GET_PLANNED_KEYBOARD_CONFIGURATION") {
        return {
            ...state,
            plannedVconsole: action.payload.plannedVconsole,
            plannedXlayouts: action.payload.plannedXlayouts,
            xlayouts: action.payload.xlayouts,
        };
    } else if (action.type === "SET_LANGUAGE_KICKSTARTED") {
        return { ...state, languageKickstarted: action.payload.languageKickstarted };
    } else if (action.type === "SET_LANGUAGE_USER_CONFIGURED") {
        return { ...state, userConfigured: true };
    } else {
        return state;
    }
};

export const bossReducer = (state = bossInitialState, action) => {
    if (action.type === "GET_INSTALLATION_STATUS") {
        return { ...state, installationStatus: action.payload.status };
    } else if (action.type === "GET_PENDING_ERROR") {
        return { ...state, pendingError: { message: action.payload.message, type: action.payload.type } };
    } else {
        return state;
    }
};

export const networkReducer = (state = networkInitialState, action) => {
    if (action.type === "GET_NETWORK_CONNECTED") {
        return { ...state, connected: action.payload.connected };
    } else if (action.type === "GET_NETWORK_HOSTNAME") {
        return { ...state, hostname: action.payload.hostname };
    } else {
        return state;
    }
};

const miscReducer = (state = miscInitialState, action) => {
    if (action.type === "SET_IS_FETCHING") {
        return { ...state, isFetching: action.payload.isFetching };
    } else {
        return state;
    }
};

export const runtimeReducer = (state = runtimeInitialState, action) => {
    if (action.type === "GET_RUNTIME_PASSWORD_POLICIES") {
        return { ...state, passwordPolicies: action.payload.passwordPolicies };
    } else {
        return state;
    }
};

export const timezoneReducer = (state = timezoneInitialState, action) => {
    if (action.type === "SET_TIMEZONE") {
        const { kickstarted, timezone } = action.payload;
        return {
            ...state,
            ...(kickstarted !== undefined && { kickstarted }),
            ...(timezone !== undefined && { timezone }),
        };
    } else if (action.type === "SET_TIMEZONE_USER_CONFIGURED") {
        return { ...state, userConfigured: true };
    } else if (action.type === "SET_ALL_VALID_TIMEZONES") {
        return { ...state, allValidTimezones: action.payload.allValidTimezones };
    } else {
        return state;
    }
};

export const payloadReducer = (state = payloadInitialState, action) => {
    if (action.type === "SET_PAYLOAD_SELECTION") {
        return {
            ...state,
            selection: action.payload.selection,
            ...(action.payload.packagesKickstarted !== undefined
                ? { packagesKickstarted: action.payload.packagesKickstarted }
                : {}),
        };
    } else if (action.type === "SET_PAYLOAD_ENVIRONMENTS") {
        return { ...state, environments: action.payload.environments || [] };
    } else if (action.type === "SET_PAYLOAD_TYPE") {
        return { ...state, type: action.payload.type };
    } else if (action.type === "SET_PAYLOAD_GROUPS") {
        return { ...state, groups: action.payload.groups };
    } else {
        return state;
    }
};

export const usersReducer = (state = usersInitialState, action) => {
    if (action.type === "SET_USERS") {
        const { firstUser, ...rest } = action.payload;
        let next = { ...state, ...rest };
        if (firstUser !== undefined) {
            const existing = state.users ?? [];
            const [currentFirst = {}, ...tail] = existing;
            const updatedFirst = {
                ...currentFirst,
                gecos: firstUser.gecos,
                name: firstUser.name,
            };
            const updatedUsers = (firstUser.name || firstUser.gecos || tail.length > 0)
                ? [updatedFirst, ...tail]
                : [];
            next = {
                ...next,
                users: updatedUsers,
            };
            if (firstUser.password !== undefined) {
                next.password = firstUser.password;
            }
            if (firstUser.confirmPassword !== undefined) {
                next.confirmPassword = firstUser.confirmPassword;
            }
        }
        return next;
    } else if (action.type === "SET_USER_CONFIGURATION_POLICY") {
        return { ...state, ...action.payload };
    } else {
        return state;
    }
};