From 0925fbbad28c7ac58c272f51caa5bf09196996a2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 12 Oct 2023 00:36:53 +1100 Subject: [PATCH] [React] Settings state manager (#5687) * Add interface definition for settings * Fetch global and user settings as state object * Cleanup dead code * Fetch settings when performing token login --- src/frontend/src/components/nav/MainMenu.tsx | 4 +- src/frontend/src/functions/auth.tsx | 9 ++++ src/frontend/src/states/ApiState.tsx | 31 +++---------- src/frontend/src/states/SettingsState.tsx | 49 ++++++++++++++++++++ src/frontend/src/states/UserState.tsx | 2 + src/frontend/src/states/states.tsx | 35 ++++++++++++++ src/frontend/src/views/DesktopAppView.tsx | 13 ++++++ 7 files changed, 117 insertions(+), 26 deletions(-) create mode 100644 src/frontend/src/states/SettingsState.tsx diff --git a/src/frontend/src/components/nav/MainMenu.tsx b/src/frontend/src/components/nav/MainMenu.tsx index c7c308ba13..592daed5b2 100644 --- a/src/frontend/src/components/nav/MainMenu.tsx +++ b/src/frontend/src/components/nav/MainMenu.tsx @@ -16,7 +16,9 @@ import { PlaceholderPill } from '../items/Placeholder'; export function MainMenu() { const { classes, theme } = InvenTreeStyle(); - const [username] = useUserState((state) => [state.user?.name]); + const [username] = useUserState((state) => [ + state.user?.name ?? state.user?.username + ]); return ( diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index c3abec394f..16d3eb7c04 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -7,6 +7,10 @@ import { api } from '../App'; import { ApiPaths, url, useServerApiState } from '../states/ApiState'; import { useLocalState } from '../states/LocalState'; import { useSessionState } from '../states/SessionState'; +import { + useGlobalSettingsState, + useUserSettingsState +} from '../states/SettingsState'; import { useUserState } from '../states/UserState'; export const doClassicLogin = async (username: string, password: string) => { @@ -56,14 +60,19 @@ export const doSimpleLogin = async (email: string) => { return mail; }; +// Perform a login using a token export const doTokenLogin = (token: string) => { const { setToken } = useSessionState.getState(); const { fetchUserState } = useUserState.getState(); const { fetchServerApiState } = useServerApiState.getState(); + const globalSettingsState = useGlobalSettingsState.getState(); + const userSettingsState = useUserSettingsState.getState(); setToken(token); fetchUserState(); fetchServerApiState(); + globalSettingsState.fetchSettings(); + userSettingsState.fetchSettings(); }; export function handleReset(navigate: any, values: { email: string }) { diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index 76dd998527..b4e3122824 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -4,31 +4,6 @@ import { api } from '../App'; import { emptyServerAPI } from '../defaults/defaults'; import { ServerAPIProps, UserProps } from './states'; -interface UserStateProps { - user: UserProps | undefined; - setUser: (newUser: UserProps) => void; - fetchApiState: () => void; -} - -/** - * Global user information state, using Zustand manager - */ -export const useApiState = create((set, get) => ({ - user: undefined, - setUser: (newUser: UserProps) => set({ user: newUser }), - fetchApiState: async () => { - // Fetch user data - await api.get(url(ApiPaths.user_me)).then((response) => { - const user: UserProps = { - name: `${response.data.first_name} ${response.data.last_name}`, - email: response.data.email, - username: response.data.username - }; - set({ user: user }); - }); - } -})); - interface ServerApiStateProps { server: ServerAPIProps; setServer: (newServer: ServerAPIProps) => void; @@ -55,6 +30,8 @@ export enum ApiPaths { user_reset = 'api-user-reset', user_reset_set = 'api-user-reset-set', + settings_global_list = 'api-settings-global-list', + settings_user_list = 'api-settings-user-list', notifications_list = 'api-notifications-list', barcode = 'api-barcode', @@ -102,6 +79,10 @@ export function endpoint(path: ApiPaths): string { return '/auth/password/reset/'; case ApiPaths.user_reset_set: return '/auth/password/reset/confirm/'; + case ApiPaths.settings_global_list: + return 'settings/global/'; + case ApiPaths.settings_user_list: + return 'settings/user/'; case ApiPaths.notifications_list: return 'notifications/'; case ApiPaths.barcode: diff --git a/src/frontend/src/states/SettingsState.tsx b/src/frontend/src/states/SettingsState.tsx new file mode 100644 index 0000000000..6fc4985b58 --- /dev/null +++ b/src/frontend/src/states/SettingsState.tsx @@ -0,0 +1,49 @@ +/** + * State management for remote (server side) settings + */ +import { create } from 'zustand'; + +import { api } from '../App'; +import { ApiPaths, url } from './ApiState'; +import { Setting } from './states'; + +interface SettingsStateProps { + settings: Setting[]; + fetchSettings: () => void; +} + +/** + * State management for global (server side) settings + */ +export const useGlobalSettingsState = create( + (set, get) => ({ + settings: [], + fetchSettings: async () => { + await api + .get(url(ApiPaths.settings_global_list)) + .then((response) => { + set({ settings: response.data }); + }) + .catch((error) => { + console.error('Error fetching global settings:', error); + }); + } + }) +); + +/** + * State management for user (server side) settings + */ +export const useUserSettingsState = create((set, get) => ({ + settings: [], + fetchSettings: async () => { + await api + .get(url(ApiPaths.settings_user_list)) + .then((response) => { + set({ settings: response.data }); + }) + .catch((error) => { + console.error('Error fetching user settings:', error); + }); + } +})); diff --git a/src/frontend/src/states/UserState.tsx b/src/frontend/src/states/UserState.tsx index 898b075ca0..197d876cbb 100644 --- a/src/frontend/src/states/UserState.tsx +++ b/src/frontend/src/states/UserState.tsx @@ -37,6 +37,8 @@ export const useUserState = create((set, get) => ({ .get(url(ApiPaths.user_roles)) .then((response) => { const user: UserProps = get().user as UserProps; + + // Update user with role data user.roles = response.data.roles; user.is_staff = response.data.is_staff ?? false; user.is_superuser = response.data.is_superuser ?? false; diff --git a/src/frontend/src/states/states.tsx b/src/frontend/src/states/states.tsx index 23dbc4f952..beda482676 100644 --- a/src/frontend/src/states/states.tsx +++ b/src/frontend/src/states/states.tsx @@ -29,6 +29,41 @@ export interface ServerAPIProps { active_plugins: PluginProps[]; } +// Type interface defining a single 'setting' object +export interface Setting { + pk: number; + key: string; + value: string; + name: string; + description: string; + type: SettingType; + units: string; + choices: SettingChoice[]; + model_name: string | null; + api_url: string | null; + typ: SettingTyp; + plugin?: string; + method?: string; +} + +export interface SettingChoice { + value: string; + display_name: string; +} + +export enum SettingTyp { + Inventree = 'inventree', + Plugin = 'plugin', + User = 'user', + Notification = 'notification' +} + +export enum SettingType { + Boolean = 'boolean', + Integer = 'integer', + String = 'string' +} + export interface PluginProps { name: string; slug: string; diff --git a/src/frontend/src/views/DesktopAppView.tsx b/src/frontend/src/views/DesktopAppView.tsx index 56bae54a38..379343bc61 100644 --- a/src/frontend/src/views/DesktopAppView.tsx +++ b/src/frontend/src/views/DesktopAppView.tsx @@ -9,12 +9,23 @@ import { url_base } from '../main'; import { routes } from '../router'; import { useLocalState } from '../states/LocalState'; import { useSessionState } from '../states/SessionState'; +import { + useGlobalSettingsState, + useUserSettingsState +} from '../states/SettingsState'; import { useUserState } from '../states/UserState'; export default function DesktopAppView() { const [hostList] = useLocalState((state) => [state.hostList]); const [fetchUserState] = useUserState((state) => [state.fetchUserState]); + const [fetchGlobalSettings] = useGlobalSettingsState((state) => [ + state.fetchSettings + ]); + const [fetchUserSettings] = useUserSettingsState((state) => [ + state.fetchSettings + ]); + // Local state initialization if (Object.keys(hostList).length === 0) { console.log('Loading default host list'); @@ -30,6 +41,8 @@ export default function DesktopAppView() { if (token && !fetchedServerSession) { setFetchedServerSession(true); fetchUserState(); + fetchGlobalSettings(); + fetchUserSettings(); } }, [token, fetchedServerSession]);