Improve auth timeout handling (related to #705)
This commit is contained in:
parent
4f950de29d
commit
3fa7356ff3
11 changed files with 87 additions and 97 deletions
|
|
@ -40,6 +40,7 @@ export class NodesAPI {
|
|||
nodeCount?: number;
|
||||
clusterNodeCount?: number;
|
||||
datastoreCount?: number;
|
||||
warnings?: string[];
|
||||
}> {
|
||||
return apiFetchJSON(`${this.baseUrl}/test-connection`, {
|
||||
method: 'POST',
|
||||
|
|
@ -51,6 +52,7 @@ export class NodesAPI {
|
|||
status: string;
|
||||
message?: string;
|
||||
latency?: number;
|
||||
warnings?: string[];
|
||||
}> {
|
||||
return apiFetchJSON(`${this.baseUrl}/${nodeId}/test`, {
|
||||
method: 'POST',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createMemo, createSignal, createEffect, on, Show, For } from 'solid-js';
|
||||
import { createMemo, createSignal, createEffect, Show, For } from 'solid-js';
|
||||
import type { VM, Container } from '@/types/api';
|
||||
import { formatBytes, formatPercent, formatUptime } from '@/utils/format';
|
||||
import { MetricBar } from './MetricBar';
|
||||
|
|
@ -60,7 +60,6 @@ interface GuestRowProps {
|
|||
}
|
||||
|
||||
export function GuestRow(props: GuestRowProps) {
|
||||
const initialGuestId = buildGuestId(props.guest);
|
||||
const guestId = createMemo(() => buildGuestId(props.guest));
|
||||
const isEditingUrl = createMemo(() => currentlyEditingGuestId() === guestId());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, JSX, createSignal, ErrorBoundary as SolidErrorBoundary } from 'solid-js';
|
||||
import { Component, JSX, ErrorBoundary as SolidErrorBoundary } from 'solid-js';
|
||||
import { logError } from '@/utils/logger';
|
||||
import { SectionHeader } from '@/components/shared/SectionHeader';
|
||||
|
||||
|
|
@ -9,8 +9,6 @@ interface ErrorBoundaryProps {
|
|||
}
|
||||
|
||||
const DefaultErrorFallback: Component<{ error: Error; reset: () => void }> = (props) => {
|
||||
const [details, setDetails] = createSignal(false);
|
||||
|
||||
return (
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900 p-4">
|
||||
<div class="max-w-md w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ export const FirstRunSetup: Component<{ force?: boolean; showLegacyBanner?: bool
|
|||
const [copied, setCopied] = createSignal<'password' | 'token' | null>(null);
|
||||
const [themeMode, setThemeMode] = createSignal<'system' | 'light' | 'dark'>('system');
|
||||
const [bootstrapToken, setBootstrapToken] = createSignal('');
|
||||
const [isUnlocking, setIsUnlocking] = createSignal(false);
|
||||
const [isUnlocked, setIsUnlocked] = createSignal(false);
|
||||
const [bootstrapTokenPath, setBootstrapTokenPath] = createSignal<string>('');
|
||||
const [isDocker, setIsDocker] = createSignal<boolean>(false);
|
||||
|
|
@ -371,10 +370,10 @@ IMPORTANT: Keep these credentials secure!
|
|||
<button
|
||||
type="button"
|
||||
onClick={handleUnlock}
|
||||
disabled={isUnlocking() || !bootstrapToken().trim()}
|
||||
disabled={!bootstrapToken().trim()}
|
||||
class="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white rounded-lg font-medium transition-colors disabled:cursor-not-allowed"
|
||||
>
|
||||
{isUnlocking() ? 'Unlocking...' : 'Unlock Wizard'}
|
||||
Unlock Wizard
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
|||
const [quickSetupCommand, setQuickSetupCommand] = createSignal('');
|
||||
const [quickSetupToken, setQuickSetupToken] = createSignal('');
|
||||
const [quickSetupExpiry, setQuickSetupExpiry] = createSignal<number | null>(null);
|
||||
const showTemperatureMonitoringSection = () =>
|
||||
typeof props.temperatureMonitoringEnabled === 'boolean';
|
||||
const temperatureMonitoringEnabledValue = () => props.temperatureMonitoringEnabled ?? true;
|
||||
const quickSetupExpiryLabel = () => {
|
||||
const expiry = quickSetupExpiry();
|
||||
if (!expiry) {
|
||||
|
|
@ -1764,40 +1767,39 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={typeof props.temperatureMonitoringEnabled === 'boolean'}>
|
||||
{() => {
|
||||
const enabled = props.temperatureMonitoringEnabled ?? true;
|
||||
return (
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-3 text-sm text-gray-700 shadow-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">Temperature monitoring</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Uses the Pulse sensors key or proxy to read CPU/NVMe temperatures for this node. Disable if you don't need temperature data or haven't deployed the proxy yet.
|
||||
</p>
|
||||
</div>
|
||||
<TogglePrimitive
|
||||
checked={enabled}
|
||||
onChange={(event) => {
|
||||
props.onToggleTemperatureMonitoring?.(event.currentTarget.checked);
|
||||
}}
|
||||
disabled={props.temperatureMonitoringLocked || props.savingTemperatureSetting}
|
||||
ariaLabel={enabled ? 'Disable temperature monitoring' : 'Enable temperature monitoring'}
|
||||
/>
|
||||
</div>
|
||||
<Show when={!enabled}>
|
||||
<p class="mt-3 rounded border border-blue-200 bg-blue-50 p-2 text-xs text-blue-700 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-200">
|
||||
Pulse will skip SSH temperature polling for this node. Existing dashboard readings will stop refreshing.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={props.temperatureMonitoringLocked}>
|
||||
<p class="mt-3 rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-800 dark:border-amber-700 dark:bg-amber-900/20 dark:text-amber-200">
|
||||
Locked by environment variables. Remove the override (ENABLE_TEMPERATURE_MONITORING) and restart Pulse to manage it in the UI.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={showTemperatureMonitoringSection()}>
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-3 text-sm text-gray-700 shadow-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">Temperature monitoring</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Uses the Pulse sensors key or proxy to read CPU/NVMe temperatures for this node. Disable if you don't need temperature data or haven't deployed the proxy yet.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
<TogglePrimitive
|
||||
checked={temperatureMonitoringEnabledValue()}
|
||||
onChange={(event) => {
|
||||
props.onToggleTemperatureMonitoring?.(event.currentTarget.checked);
|
||||
}}
|
||||
disabled={props.temperatureMonitoringLocked || props.savingTemperatureSetting}
|
||||
ariaLabel={
|
||||
temperatureMonitoringEnabledValue()
|
||||
? 'Disable temperature monitoring'
|
||||
: 'Enable temperature monitoring'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Show when={!temperatureMonitoringEnabledValue()}>
|
||||
<p class="mt-3 rounded border border-blue-200 bg-blue-50 p-2 text-xs text-blue-700 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-200">
|
||||
Pulse will skip SSH temperature polling for this node. Existing dashboard readings will stop refreshing.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={props.temperatureMonitoringLocked}>
|
||||
<p class="mt-3 rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-800 dark:border-amber-700 dark:bg-amber-900/20 dark:text-amber-200">
|
||||
Locked by environment variables. Remove the override (ENABLE_TEMPERATURE_MONITORING) and restart Pulse to manage it in the UI.
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
onCleanup,
|
||||
on,
|
||||
} from 'solid-js';
|
||||
import type { JSX } from 'solid-js';
|
||||
import { useNavigate, useLocation } from '@solidjs/router';
|
||||
import { useWebSocket } from '@/App';
|
||||
import { showSuccess, showError, showWarning } from '@/utils/toast';
|
||||
|
|
@ -535,6 +534,14 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
const [showDeleteNodeModal, setShowDeleteNodeModal] = createSignal(false);
|
||||
const [nodePendingDelete, setNodePendingDelete] = createSignal<NodeConfigWithStatus | null>(null);
|
||||
const [deleteNodeLoading, setDeleteNodeLoading] = createSignal(false);
|
||||
const isNodeModalVisible = (type: 'pve' | 'pbs' | 'pmg') =>
|
||||
Boolean(showNodeModal() && currentNodeType() === type);
|
||||
const resolveTemperatureMonitoringEnabled = (node?: NodeConfigWithStatus | null) => {
|
||||
if (node && typeof node.temperatureMonitoringEnabled === 'boolean') {
|
||||
return node.temperatureMonitoringEnabled;
|
||||
}
|
||||
return temperatureMonitoringEnabled();
|
||||
};
|
||||
const [initialLoadComplete, setInitialLoadComplete] = createSignal(false);
|
||||
const [discoveryScanStatus, setDiscoveryScanStatus] = createSignal<DiscoveryScanStatus>({
|
||||
scanning: false,
|
||||
|
|
@ -6714,7 +6721,7 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
</Show>
|
||||
|
||||
{/* Node Modal - Use separate modals for PVE and PBS to ensure clean state */}
|
||||
<Show when={showNodeModal() && currentNodeType() === 'pve'}>
|
||||
<Show when={isNodeModalVisible('pve')}>
|
||||
<NodeModal
|
||||
isOpen={true}
|
||||
resetKey={modalResetKey()}
|
||||
|
|
@ -6727,11 +6734,9 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
nodeType="pve"
|
||||
editingNode={editingNode()?.type === 'pve' ? (editingNode() ?? undefined) : undefined}
|
||||
securityStatus={securityStatus() ?? undefined}
|
||||
temperatureMonitoringEnabled={
|
||||
editingNode()?.temperatureMonitoringEnabled !== undefined
|
||||
? editingNode()!.temperatureMonitoringEnabled
|
||||
: temperatureMonitoringEnabled()
|
||||
}
|
||||
temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled(
|
||||
editingNode()?.type === 'pve' ? editingNode() : null,
|
||||
)}
|
||||
temperatureMonitoringLocked={temperatureMonitoringLocked()}
|
||||
savingTemperatureSetting={savingTemperatureSetting()}
|
||||
onToggleTemperatureMonitoring={
|
||||
|
|
@ -6788,7 +6793,7 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
</Show>
|
||||
|
||||
{/* PBS Node Modal - Separate instance to prevent contamination */}
|
||||
<Show when={showNodeModal() && currentNodeType() === 'pbs'}>
|
||||
<Show when={isNodeModalVisible('pbs')}>
|
||||
<NodeModal
|
||||
isOpen={true}
|
||||
resetKey={modalResetKey()}
|
||||
|
|
@ -6801,11 +6806,9 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
nodeType="pbs"
|
||||
editingNode={editingNode()?.type === 'pbs' ? (editingNode() ?? undefined) : undefined}
|
||||
securityStatus={securityStatus() ?? undefined}
|
||||
temperatureMonitoringEnabled={
|
||||
editingNode()?.temperatureMonitoringEnabled !== undefined
|
||||
? editingNode()!.temperatureMonitoringEnabled
|
||||
: temperatureMonitoringEnabled()
|
||||
}
|
||||
temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled(
|
||||
editingNode()?.type === 'pbs' ? editingNode() : null,
|
||||
)}
|
||||
temperatureMonitoringLocked={temperatureMonitoringLocked()}
|
||||
savingTemperatureSetting={savingTemperatureSetting()}
|
||||
onToggleTemperatureMonitoring={
|
||||
|
|
@ -6861,7 +6864,7 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
</Show>
|
||||
|
||||
{/* PMG Node Modal */}
|
||||
<Show when={showNodeModal() && currentNodeType() === 'pmg'}>
|
||||
<Show when={isNodeModalVisible('pmg')}>
|
||||
<NodeModal
|
||||
isOpen={true}
|
||||
resetKey={modalResetKey()}
|
||||
|
|
@ -6873,11 +6876,9 @@ const Settings: Component<SettingsProps> = (props) => {
|
|||
nodeType="pmg"
|
||||
editingNode={editingNode()?.type === 'pmg' ? (editingNode() ?? undefined) : undefined}
|
||||
securityStatus={securityStatus() ?? undefined}
|
||||
temperatureMonitoringEnabled={
|
||||
editingNode()?.temperatureMonitoringEnabled !== undefined
|
||||
? editingNode()!.temperatureMonitoringEnabled
|
||||
: temperatureMonitoringEnabled()
|
||||
}
|
||||
temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled(
|
||||
editingNode()?.type === 'pmg' ? editingNode() : null,
|
||||
)}
|
||||
temperatureMonitoringLocked={temperatureMonitoringLocked()}
|
||||
savingTemperatureSetting={savingTemperatureSetting()}
|
||||
onToggleTemperatureMonitoring={
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { createSignal, Show, onMount, onCleanup, createEffect } from 'solid-js';
|
||||
import { createSignal, Show, onCleanup, createEffect } from 'solid-js';
|
||||
import { UpdatesAPI, type UpdateStatus } from '@/api/updates';
|
||||
import { apiFetch } from '@/utils/apiClient';
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
interface UpdateProgressModalProps {
|
||||
|
|
@ -16,7 +17,6 @@ export function UpdateProgressModal(props: UpdateProgressModalProps) {
|
|||
const [hasError, setHasError] = createSignal(false);
|
||||
const [isRestarting, setIsRestarting] = createSignal(false);
|
||||
const [wsDisconnected, setWsDisconnected] = createSignal(false);
|
||||
const [usingSSE, setUsingSSE] = createSignal(false);
|
||||
let pollInterval: number | undefined;
|
||||
let healthCheckTimer: number | undefined;
|
||||
let healthCheckAttempts = 0;
|
||||
|
|
@ -30,12 +30,12 @@ export function UpdateProgressModal(props: UpdateProgressModalProps) {
|
|||
};
|
||||
|
||||
const closeSSE = () => {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
eventSource = undefined;
|
||||
setUsingSSE(false);
|
||||
logger.info('SSE connection closed');
|
||||
if (!eventSource) {
|
||||
return;
|
||||
}
|
||||
eventSource.close();
|
||||
eventSource = undefined;
|
||||
logger.info('SSE connection closed');
|
||||
};
|
||||
|
||||
const setupSSE = () => {
|
||||
|
|
@ -45,7 +45,6 @@ export function UpdateProgressModal(props: UpdateProgressModalProps) {
|
|||
try {
|
||||
// Create EventSource connection to SSE endpoint
|
||||
eventSource = new EventSource('/api/updates/stream');
|
||||
setUsingSSE(true);
|
||||
|
||||
eventSource.onopen = () => {
|
||||
logger.info('SSE connection established');
|
||||
|
|
@ -73,7 +72,7 @@ export function UpdateProgressModal(props: UpdateProgressModalProps) {
|
|||
if (updateStatus.status === 'completed' && !updateStatus.error) {
|
||||
closeSSE();
|
||||
// Verify backend health and reload
|
||||
fetch('/api/health', { cache: 'no-store' })
|
||||
apiFetch('/api/health', { cache: 'no-store' })
|
||||
.then((healthCheck) => {
|
||||
if (healthCheck.ok) {
|
||||
logger.info('Update completed, backend healthy, reloading...');
|
||||
|
|
@ -157,7 +156,7 @@ export function UpdateProgressModal(props: UpdateProgressModalProps) {
|
|||
}
|
||||
// Verify backend is healthy and reload
|
||||
try {
|
||||
const healthCheck = await fetch('/api/health', { cache: 'no-store' });
|
||||
const healthCheck = await apiFetch('/api/health', { cache: 'no-store' });
|
||||
if (healthCheck.ok) {
|
||||
logger.info('Update completed, backend healthy, reloading...');
|
||||
window.location.reload();
|
||||
|
|
@ -214,7 +213,7 @@ export function UpdateProgressModal(props: UpdateProgressModalProps) {
|
|||
let isHealthy = false;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/health', { cache: 'no-store' });
|
||||
const response = await apiFetch('/api/health', { cache: 'no-store' });
|
||||
if (response.ok) {
|
||||
isHealthy = true;
|
||||
}
|
||||
|
|
@ -258,7 +257,7 @@ export function UpdateProgressModal(props: UpdateProgressModalProps) {
|
|||
// Give it a moment for the backend to fully initialize
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/health', { cache: 'no-store' });
|
||||
const response = await apiFetch('/api/health', { cache: 'no-store' });
|
||||
if (response.ok) {
|
||||
logger.info('Backend healthy after websocket reconnect, reloading...');
|
||||
window.location.reload();
|
||||
|
|
|
|||
|
|
@ -651,20 +651,20 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
|||
: 'text-green-600 dark:text-green-400';
|
||||
|
||||
const temp = node!.temperature;
|
||||
const cpuMin = temp?.cpuMin;
|
||||
const cpuMax = temp?.cpuMaxRecord;
|
||||
const hasMinMax =
|
||||
typeof cpuMin === 'number' &&
|
||||
cpuMin > 0 &&
|
||||
typeof cpuMax === 'number' &&
|
||||
cpuMax > 0;
|
||||
const cpuMinValue =
|
||||
typeof temp?.cpuMin === 'number' && temp.cpuMin > 0 ? temp.cpuMin : null;
|
||||
const cpuMaxValue =
|
||||
typeof temp?.cpuMaxRecord === 'number' && temp.cpuMaxRecord > 0
|
||||
? temp.cpuMaxRecord
|
||||
: null;
|
||||
const hasMinMax = cpuMinValue !== null && cpuMaxValue !== null;
|
||||
|
||||
const gpus = temp?.gpu ?? [];
|
||||
const hasGPU = gpus.length > 0;
|
||||
|
||||
if (hasMinMax || hasGPU) {
|
||||
const min = Math.round(cpuMin);
|
||||
const max = Math.round(cpuMax);
|
||||
const min = Math.round(cpuMinValue!);
|
||||
const max = Math.round(cpuMaxValue!);
|
||||
|
||||
const getTooltipColor = (temp: number) => {
|
||||
if (temp >= 80) return 'text-red-400';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* Optimized for rendering many sparklines simultaneously in tables.
|
||||
*/
|
||||
|
||||
import { onMount, onCleanup, createEffect, createSignal, Component, Show } from 'solid-js';
|
||||
import { onCleanup, createEffect, createSignal, Component, Show } from 'solid-js';
|
||||
import { Portal } from 'solid-js/web';
|
||||
import type { MetricSnapshot } from '@/stores/metricsHistory';
|
||||
import { scheduleSparkline } from '@/utils/canvasRenderQueue';
|
||||
|
|
@ -202,12 +202,6 @@ export const Sparkline: Component<SparklineProps> = (props) => {
|
|||
|
||||
// Redraw when data or dimensions change
|
||||
createEffect(() => {
|
||||
// Capture current values to avoid stale closures
|
||||
const currentData = props.data;
|
||||
const currentWidth = width();
|
||||
const currentHeight = height();
|
||||
const currentMetric = props.metric;
|
||||
|
||||
// Unregister previous draw callback if it exists
|
||||
if (unregister) {
|
||||
unregister();
|
||||
|
|
@ -230,7 +224,6 @@ export const Sparkline: Component<SparklineProps> = (props) => {
|
|||
const rect = canvasRef.getBoundingClientRect();
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const w = width();
|
||||
const h = height();
|
||||
|
||||
const data = props.data;
|
||||
const values = data.map(d => d[props.metric]);
|
||||
|
|
@ -244,10 +237,6 @@ export const Sparkline: Component<SparklineProps> = (props) => {
|
|||
const timestamp = data[nearestIndex].timestamp;
|
||||
|
||||
// Calculate absolute viewport position for portal
|
||||
const minValue = 0;
|
||||
const maxValue = Math.max(100, ...values);
|
||||
const y = h - ((value - minValue) / (maxValue - minValue)) * h;
|
||||
|
||||
setHoveredPoint({
|
||||
value,
|
||||
timestamp,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import { recordMetric } from './metricsHistory';
|
|||
import { getMetricsViewMode } from './metricsViewMode';
|
||||
import { buildMetricKey } from '@/utils/metricsKeys';
|
||||
import { logger } from '@/utils/logger';
|
||||
import type { Node, VM, Container, DockerHost } from '@/types/api';
|
||||
|
||||
const SAMPLE_INTERVAL_MS = 30 * 1000; // 30 seconds
|
||||
|
||||
|
|
@ -110,9 +109,9 @@ function sampleMetrics(): void {
|
|||
|
||||
const contCpu = container.cpuPercent || 0;
|
||||
const contMem = container.memoryPercent || 0;
|
||||
const contDisk = container.disk && container.disk.total > 0
|
||||
? (container.disk.used / container.disk.total) * 100
|
||||
: 0;
|
||||
const usableTotal = container.rootFilesystemBytes ?? 0;
|
||||
const writable = container.writableLayerBytes ?? 0;
|
||||
const contDisk = usableTotal > 0 ? (writable / usableTotal) * 100 : 0;
|
||||
|
||||
const containerKey = buildMetricKey('dockerContainer', container.id);
|
||||
recordMetric(containerKey, contCpu, contMem, contDisk);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
import { logger } from '@/utils/logger';
|
||||
import { STORAGE_KEYS } from '@/utils/localStorage';
|
||||
|
||||
const AUTH_STORAGE_KEY = STORAGE_KEYS.AUTH;
|
||||
|
||||
const getSessionStorage = (): Storage | undefined => {
|
||||
if (typeof window === 'undefined') {
|
||||
return undefined;
|
||||
|
|
|
|||
Loading…
Reference in a new issue