Route guards, WS lifecycle, ticket stats. Refs EventHub/EventHubFrontAdmin#3
This commit is contained in:
+71
-69
@@ -1,70 +1,72 @@
|
||||
import { create } from 'zustand';
|
||||
import { Admin } from '../types/api';
|
||||
import { authApi } from '../api/authApi';
|
||||
import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from '../utils/constants';
|
||||
|
||||
interface AuthState {
|
||||
user: Admin | null;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
isInitialized: boolean;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
checkAuth: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
user: null,
|
||||
accessToken: localStorage.getItem(ACCESS_TOKEN_KEY),
|
||||
refreshToken: localStorage.getItem(REFRESH_TOKEN_KEY),
|
||||
isAuthenticated: false,
|
||||
isInitialized: false,
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
const { token, refresh_token, user } = await authApi.login(email, password);
|
||||
localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, refresh_token);
|
||||
set({
|
||||
accessToken: token,
|
||||
refreshToken: refresh_token,
|
||||
user,
|
||||
isAuthenticated: true,
|
||||
isInitialized: true,
|
||||
});
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
set({
|
||||
user: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isInitialized: true,
|
||||
});
|
||||
},
|
||||
|
||||
checkAuth: async () => {
|
||||
const token = localStorage.getItem(ACCESS_TOKEN_KEY);
|
||||
if (!token) {
|
||||
set({ isInitialized: true });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const user = await authApi.getMe();
|
||||
set({ user, isAuthenticated: true, isInitialized: true });
|
||||
} catch {
|
||||
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
set({
|
||||
user: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isInitialized: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
import { create } from 'zustand';
|
||||
import { Admin } from '../types/api';
|
||||
import { authApi } from '../api/authApi';
|
||||
import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from '../utils/constants';
|
||||
import { useMetricsStore } from './metricsStore';
|
||||
|
||||
interface AuthState {
|
||||
user: Admin | null;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
isInitialized: boolean;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
checkAuth: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
user: null,
|
||||
accessToken: localStorage.getItem(ACCESS_TOKEN_KEY),
|
||||
refreshToken: localStorage.getItem(REFRESH_TOKEN_KEY),
|
||||
isAuthenticated: false,
|
||||
isInitialized: false,
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
const { token, refresh_token, user } = await authApi.login(email, password);
|
||||
localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, refresh_token);
|
||||
set({
|
||||
accessToken: token,
|
||||
refreshToken: refresh_token,
|
||||
user,
|
||||
isAuthenticated: true,
|
||||
isInitialized: true,
|
||||
});
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
useMetricsStore.getState().reset();
|
||||
set({
|
||||
user: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isInitialized: true,
|
||||
});
|
||||
},
|
||||
|
||||
checkAuth: async () => {
|
||||
const token = localStorage.getItem(ACCESS_TOKEN_KEY);
|
||||
if (!token) {
|
||||
set({ isInitialized: true });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const user = await authApi.getMe();
|
||||
set({ user, isAuthenticated: true, isInitialized: true });
|
||||
} catch {
|
||||
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
set({
|
||||
user: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isInitialized: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,89 @@
|
||||
import { create } from 'zustand';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export interface NodeMetric {
|
||||
active_sessions: number;
|
||||
io_in: number;
|
||||
io_out: number;
|
||||
memory_atom: number;
|
||||
memory_binary: number;
|
||||
memory_ets: number;
|
||||
memory_processes: number;
|
||||
memory_total: number;
|
||||
mnesia_commits: number;
|
||||
mnesia_failures: number;
|
||||
node: string;
|
||||
process_count: number;
|
||||
run_queue: number;
|
||||
table_sizes: Record<string, number>;
|
||||
timestamp: string;
|
||||
ws_connections: number;
|
||||
cpu_utilization: number;
|
||||
memory_available: number;
|
||||
}
|
||||
|
||||
interface MetricsState {
|
||||
current: NodeMetric | null;
|
||||
previous: NodeMetric | null;
|
||||
allHistory: NodeMetric[];
|
||||
visibleHistory: NodeMetric[];
|
||||
setCurrent: (metric: NodeMetric) => void;
|
||||
setAllHistory: (history: NodeMetric[]) => void;
|
||||
addToAllHistory: (metric: NodeMetric) => void;
|
||||
filterVisibleHistory: (minutes: number) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const MAX_HISTORY = 500; // жесткий лимит, чтобы избежать ошибок памяти
|
||||
|
||||
export const useMetricsStore = create<MetricsState>((set, get) => ({
|
||||
current: null,
|
||||
previous: null,
|
||||
allHistory: [],
|
||||
visibleHistory: [],
|
||||
|
||||
setCurrent: (metric) =>
|
||||
set((state) => ({
|
||||
previous: state.current,
|
||||
current: metric,
|
||||
})),
|
||||
|
||||
setAllHistory: (history) =>
|
||||
set({
|
||||
allHistory: history.slice(-MAX_HISTORY),
|
||||
visibleHistory: history.slice(-MAX_HISTORY),
|
||||
}),
|
||||
|
||||
addToAllHistory: (metric) =>
|
||||
set((state) => {
|
||||
const newAll = [...state.allHistory, metric].slice(-MAX_HISTORY);
|
||||
return { allHistory: newAll };
|
||||
}),
|
||||
|
||||
filterVisibleHistory: (minutes) => {
|
||||
const { allHistory } = get();
|
||||
if (allHistory.length === 0) return;
|
||||
|
||||
const now = dayjs();
|
||||
const from = now.subtract(minutes, 'minute');
|
||||
|
||||
const filtered = allHistory.filter((m) => {
|
||||
const t = dayjs(m.timestamp);
|
||||
return t.isAfter(from);
|
||||
});
|
||||
|
||||
let step = 1;
|
||||
if (filtered.length > 300) step = Math.ceil(filtered.length / 300);
|
||||
const thinned = filtered.filter((_, i) => i % step === 0);
|
||||
|
||||
set({ visibleHistory: thinned });
|
||||
},
|
||||
|
||||
reset: () =>
|
||||
set({
|
||||
current: null,
|
||||
previous: null,
|
||||
allHistory: [],
|
||||
visibleHistory: [],
|
||||
}),
|
||||
}));
|
||||
Reference in New Issue
Block a user