Files
aiops/frontend/src/store/index.ts
T
attacker a3cd0d3910 feat: PTY kubectl终端 + 侧边栏对齐修复 + 构建缓存清理
**kubectl WebShell 修复:**
- 引入 github.com/creack/pty 库实现真正的伪终端
- 替换 bash --noediting + TERM=dumb 方案为 PTY-backed bash
- 支持 xterm-256color、窗口自适应、readline 交互
- 修复键盘输入无响应问题

**侧边栏优化:**
- 独立菜单项高度从 py-1.5 改为 h-8,与父菜单对齐
- 独立菜单图标从 h-3 w-3 改为 h-3.5 w-3.5,统一 14px

**构建优化:**
- run.sh docker up 增加 docker image prune -f 清理悬空镜像
- 每次部署自动清理 builder cache + dangling images

**认证修复(历史):**
- Token 存储:Zustand + localStorage 双写,http.ts 双源 fallback
- YamlViewer/DeploymentDetail token 来源统一为 Zustand store
2026-06-19 11:22:45 +00:00

162 lines
4.3 KiB
TypeScript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { User } from '@/types';
// 主题状态
interface ThemeState {
sidebarCollapsed: boolean;
toggleSidebar: () => void;
}
export const useThemeStore = create<ThemeState>((set) => ({
sidebarCollapsed: false,
toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
}));
// 用户状态
interface UserState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
theme?: string;
setUser: (user: User | null) => void;
setToken: (token: string | null) => void;
login: (user: User, token: string) => void;
logout: () => void;
}
export const useUserStore = create<UserState>()(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
theme: 'ocean-deep',
setUser: (user) => set({ user }),
setToken: (token) => set({ token }),
login: (user, token) => set({ user, token, isAuthenticated: true }),
logout: () => { localStorage.removeItem('token'); set({ user: null, token: null, isAuthenticated: false }); },
}),
{
name: 'user-storage',
}
)
);
// 菜单状态
interface MenuState {
activeMenu: string;
openMenus: string[];
setActiveMenu: (menu: string) => void;
toggleMenu: (menu: string) => void;
}
export const useMenuStore = create<MenuState>((set) => ({
activeMenu: 'dashboard',
openMenus: [],
setActiveMenu: (menu) => set({ activeMenu: menu }),
toggleMenu: (menu) =>
set((state) => ({
openMenus: state.openMenus.includes(menu)
? state.openMenus.filter((m) => m !== menu)
: [...state.openMenus, menu],
})),
}));
// 标签页状态
interface TabItem {
id: string;
title: string;
path: string;
closable?: boolean;
}
interface TabState {
tabs: TabItem[];
activeTab: string;
addTab: (tab: TabItem) => void;
removeTab: (tabId: string) => void;
setActiveTab: (tabId: string) => void;
}
export const useTabStore = create<TabState>((set) => ({
tabs: [{ id: 'dashboard', title: '仪表盘', path: '/', closable: false }],
activeTab: 'dashboard',
addTab: (tab) =>
set((state) => {
const exists = state.tabs.find((t) => t.id === tab.id);
if (exists) {
return { activeTab: tab.id };
}
return { tabs: [...state.tabs, tab], activeTab: tab.id };
}),
removeTab: (tabId) =>
set((state) => {
const newTabs = state.tabs.filter((t) => t.id !== tabId);
if (state.activeTab === tabId && newTabs.length > 0) {
return { tabs: newTabs, activeTab: newTabs[newTabs.length - 1].id };
}
return { tabs: newTabs };
}),
setActiveTab: (tabId) => set({ activeTab: tabId }),
}));
// 通知状态
interface Notification {
id: string;
type: 'info' | 'success' | 'warning' | 'error';
title: string;
message: string;
timestamp: number;
read: boolean;
}
interface NotificationState {
notifications: Notification[];
unreadCount: number;
addNotification: (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => void;
markAsRead: (id: string) => void;
markAllAsRead: () => void;
removeNotification: (id: string) => void;
}
export const useNotificationStore = create<NotificationState>((set) => ({
notifications: [],
unreadCount: 0,
addNotification: (notification) =>
set((state) => ({
notifications: [
{
...notification,
id: Date.now().toString(),
timestamp: Date.now(),
read: false,
},
...state.notifications,
].slice(0, 100),
unreadCount: state.unreadCount + 1,
})),
markAsRead: (id) =>
set((state) => {
const notification = state.notifications.find((n) => n.id === id);
if (notification && !notification.read) {
return {
notifications: state.notifications.map((n) =>
n.id === id ? { ...n, read: true } : n
),
unreadCount: Math.max(0, state.unreadCount - 1),
};
}
return state;
}),
markAllAsRead: () =>
set((state) => ({
notifications: state.notifications.map((n) => ({ ...n, read: true })),
unreadCount: 0,
})),
removeNotification: (id) =>
set((state) => ({
notifications: state.notifications.filter((n) => n.id !== id),
})),
}));