diff --git a/src/atoms/pagination.ts b/src/atoms/pagination.ts index 343018e9..4542ab23 100644 --- a/src/atoms/pagination.ts +++ b/src/atoms/pagination.ts @@ -1,3 +1,4 @@ +import { nsLocalJSONStorage } from '@gpustack/core-ui/utils'; import { getDefaultStore } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; @@ -8,7 +9,7 @@ export interface PaginationState { export const paginationAtom = atomWithStorage>( 'paginationStatus', {}, - undefined, + nsLocalJSONStorage, { getOnInit: true } ); diff --git a/src/atoms/settings.ts b/src/atoms/settings.ts index d917a3bf..9fbc178d 100644 --- a/src/atoms/settings.ts +++ b/src/atoms/settings.ts @@ -1,4 +1,5 @@ import { COLOR_PRIMARY } from '@/config/theme'; +import { nsLocal, nsLocalJSONStorage } from '@gpustack/core-ui/utils'; import { atom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; @@ -23,9 +24,7 @@ export const defaultSettings: UserSettings = { export const getStorageUserSettings = () => { if (typeof window === 'undefined') return defaultSettings; try { - const savedSettings = JSON.parse( - localStorage.getItem('userSettings') || '{}' - ); + const savedSettings = JSON.parse(nsLocal.get('userSettings') || '{}'); return { ...defaultSettings, ...savedSettings @@ -35,9 +34,13 @@ export const getStorageUserSettings = () => { } }; -export const userSettingsAtom = atomWithStorage('userSettings', { - ...getStorageUserSettings() -}); +export const userSettingsAtom = atomWithStorage( + 'userSettings', + { + ...getStorageUserSettings() + }, + nsLocalJSONStorage +); export const userSettingsHelperAtom = atom( (get) => get(userSettingsAtom), @@ -59,6 +62,6 @@ export const hideModalTemporarilyAtom = atom(false); export const collapsedMenuGroupsAtom = atomWithStorage( 'collapsedMenuGroups', [], - undefined, + nsLocalJSONStorage, { getOnInit: true } ); diff --git a/src/atoms/tab-active.ts b/src/atoms/tab-active.ts index 67a6a98a..e2567c5e 100644 --- a/src/atoms/tab-active.ts +++ b/src/atoms/tab-active.ts @@ -1,9 +1,11 @@ +import { nsLocalJSONStorage } from '@gpustack/core-ui/utils'; import { getDefaultStore } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; export const tabActiveAtom = atomWithStorage>( 'tabActiveStatus', - new Map() + new Map(), + nsLocalJSONStorage ); export const setActiveStatus = (key: string, value: any) => { diff --git a/src/atoms/user.ts b/src/atoms/user.ts index 027d07d1..2046cf31 100644 --- a/src/atoms/user.ts +++ b/src/atoms/user.ts @@ -1,7 +1,12 @@ +import { nsLocal, nsLocalJSONStorage } from '@gpustack/core-ui/utils'; import { atom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; -export const userAtom = atomWithStorage('userInfo', null); +export const userAtom = atomWithStorage( + 'userInfo', + null, + nsLocalJSONStorage +); // Backs the `currentOrganizationId` localStorage key. Stays null in // builds with no Org context (single-tenant), and is shared with any @@ -9,7 +14,8 @@ export const userAtom = atomWithStorage('userInfo', null); // without one side having to import from the other. export const currentOrganizationIdAtom = atomWithStorage( 'currentOrganizationId', - null + null, + nsLocalJSONStorage ); export const GPUStackVersionAtom = atom<{ @@ -70,7 +76,7 @@ export const getCurrentOrgNamespace = ( const getStoredCurrentOrgId = (): number | null => { try { - const raw = localStorage.getItem('currentOrganizationId'); + const raw = nsLocal.get('currentOrganizationId'); if (!raw) return null; const value = JSON.parse(raw); return typeof value === 'number' ? value : null; @@ -108,7 +114,7 @@ export const getOrgById = ( const target = String(id); for (const key of ORG_CACHE_KEYS) { try { - const raw = localStorage.getItem(key); + const raw = nsLocal.get(key); if (!raw) continue; const list = JSON.parse(raw) as CachedOrg[]; if (!Array.isArray(list)) continue; diff --git a/src/atoms/utils/index.ts b/src/atoms/utils/index.ts index e9b64eb5..760336a9 100644 --- a/src/atoms/utils/index.ts +++ b/src/atoms/utils/index.ts @@ -1,18 +1,17 @@ import { defaultSettings } from '@/atoms/settings'; +import { nsLocal } from '@gpustack/core-ui/utils'; import { getDefaultStore } from 'jotai'; export const clearStorageUserSettings = () => { try { - const savedSettings = JSON.parse( - localStorage.getItem('userSettings') || '{}' - ); + const savedSettings = JSON.parse(nsLocal.get('userSettings') || '{}'); // colorPrimary is an enterprise-wide branding setting (set by admins // and applied by `onAppInit` from /enterprise/settings), not a per-user // preference. Preserve it across login — otherwise the next layout // mount triggers `atomWithStorage.onMount`, re-reads localStorage, // and falls back to the default color until a full page refresh // re-runs `applyEnterpriseSettings`. - localStorage.setItem( + nsLocal.set( 'userSettings', JSON.stringify({ ...savedSettings, @@ -26,7 +25,7 @@ export const clearStorageUserSettings = () => { export const resetStorageUserSettings = () => { try { - localStorage.setItem( + nsLocal.set( 'userSettings', JSON.stringify({ ...defaultSettings, diff --git a/src/layouts/github-star.tsx b/src/layouts/github-star.tsx index 2d03e910..9b5e88e0 100644 --- a/src/layouts/github-star.tsx +++ b/src/layouts/github-star.tsx @@ -1,5 +1,6 @@ import externalLinks from '@/constants/external-links'; import { GithubFilled } from '@ant-design/icons'; +import { nsLocal } from '@gpustack/core-ui/utils'; import { useIntl } from '@umijs/max'; import { Tooltip } from 'antd'; import { useEffect, useState } from 'react'; @@ -66,7 +67,7 @@ type CacheEntry = { value: number; time: number }; const readCache = (): CacheEntry | null => { try { - const raw = localStorage.getItem(CACHE_KEY); + const raw = nsLocal.get(CACHE_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (typeof parsed?.value !== 'number' || typeof parsed?.time !== 'number') { @@ -80,10 +81,7 @@ const readCache = (): CacheEntry | null => { const writeCache = (value: number) => { try { - localStorage.setItem( - CACHE_KEY, - JSON.stringify({ value, time: Date.now() }) - ); + nsLocal.set(CACHE_KEY, JSON.stringify({ value, time: Date.now() })); } catch { // ignore quota errors } diff --git a/src/utils/access-probes.ts b/src/utils/access-probes.ts index f45c7b04..274196e4 100644 --- a/src/utils/access-probes.ts +++ b/src/utils/access-probes.ts @@ -10,6 +10,7 @@ import { queryClusterList } from '@/pages/cluster-management/apis'; import { ProviderValueMap } from '@/pages/cluster-management/config'; import { queryResourceEvents } from '@/pages/usage/apis/resource'; +import { nsSession } from '@gpustack/core-ui/utils'; // Probes the caller's cluster list once so access predicates can gate // GPU Service (Kubernetes-only). Cheap (one list request) and never @@ -33,20 +34,13 @@ export const probeHasKubernetesCluster = async (): Promise< const value = (res?.items ?? []).some( (c) => c?.provider === ProviderValueMap.Kubernetes ); - try { - window.sessionStorage.setItem(HAS_K8S_CLUSTER_KEY, JSON.stringify(value)); - } catch { - // sessionStorage may be unavailable (Safari private mode); the - // access predicate already handles a missing value as "unknown". - } + // nsSession swallows write errors (Safari private mode); the access + // predicate already handles a missing value as "unknown". + nsSession.set(HAS_K8S_CLUSTER_KEY, JSON.stringify(value)); return value; } catch (error) { console.error('probeHasKubernetesCluster error', error); - try { - window.sessionStorage.removeItem(HAS_K8S_CLUSTER_KEY); - } catch { - // ignore - } + nsSession.remove(HAS_K8S_CLUSTER_KEY); return undefined; } }; @@ -69,22 +63,12 @@ export const probeHasResourceEvents = async (): Promise< } ); const value = (res?.pagination?.total ?? 0) > 0; - try { - window.sessionStorage.setItem( - HAS_RESOURCE_EVENTS_KEY, - JSON.stringify(value) - ); - } catch { - // sessionStorage may be unavailable; predicate treats missing as unknown. - } + // nsSession swallows write errors; predicate treats missing as unknown. + nsSession.set(HAS_RESOURCE_EVENTS_KEY, JSON.stringify(value)); return value; } catch (error) { console.error('probeHasResourceEvents error', error); - try { - window.sessionStorage.removeItem(HAS_RESOURCE_EVENTS_KEY); - } catch { - // ignore - } + nsSession.remove(HAS_RESOURCE_EVENTS_KEY); return undefined; } }; diff --git a/src/utils/localstore/index.ts b/src/utils/localstore/index.ts index ae662f36..63abe941 100644 --- a/src/utils/localstore/index.ts +++ b/src/utils/localstore/index.ts @@ -1,3 +1,4 @@ +import { NS_STORE_NAME } from '@gpustack/core-ui/utils'; import localStore from './store'; const IS_FIRST_LOGIN = 'is_first_login'; @@ -5,7 +6,7 @@ const IS_FIRST_LOGIN = 'is_first_login'; const REMEMBER_ME_KEY = 'r_m'; const CRYPT_TEXT = 'seal'; -const store = localStore.createInstance({ name: '_xWXJKJ_S1Sna_' }); +const store = localStore.createInstance({ name: NS_STORE_NAME }); // Kept to remove credentials saved by the old "remember me" feature. const removeRememberMe = (key: string) => { @@ -37,9 +38,9 @@ const writeColumnSettings = (key: string, data: any) => { export { CRYPT_TEXT, IS_FIRST_LOGIN, - REMEMBER_ME_KEY, readColumnSettings, readState, + REMEMBER_ME_KEY, removeRememberMe, writeColumnSettings, writeState