first commit
This commit is contained in:
223
ui/lib/store/slices/appSlice.ts
Normal file
223
ui/lib/store/slices/appSlice.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
// Define the shape of our app state
|
||||
export interface AppState {
|
||||
// UI State
|
||||
sidebarCollapsed: boolean;
|
||||
theme: "light" | "dark" | "system";
|
||||
|
||||
// Loading states for global operations
|
||||
isInitializing: boolean;
|
||||
isOnline: boolean;
|
||||
|
||||
// Current user/session info
|
||||
currentUser: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
} | null;
|
||||
|
||||
// Global notifications/toasts
|
||||
notifications: {
|
||||
id: string;
|
||||
type: "success" | "error" | "warning" | "info";
|
||||
title: string;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
read: boolean;
|
||||
}[];
|
||||
|
||||
// Application settings
|
||||
settings: {
|
||||
autoRefresh: boolean;
|
||||
refreshInterval: number; // in seconds
|
||||
maxLogEntries: number;
|
||||
defaultPageSize: number;
|
||||
};
|
||||
|
||||
// Global error state
|
||||
globalError: {
|
||||
message: string;
|
||||
code?: string;
|
||||
timestamp: number;
|
||||
} | null;
|
||||
|
||||
// Selected items
|
||||
selectedItems: {
|
||||
providers: string[];
|
||||
virtualKeys: string[];
|
||||
teams: string[];
|
||||
customers: string[];
|
||||
logs: string[];
|
||||
};
|
||||
|
||||
// Feature flags
|
||||
features: {
|
||||
enableMCP: boolean;
|
||||
enableCaching: boolean;
|
||||
enableLogging: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Define initial state
|
||||
const initialState: AppState = {
|
||||
sidebarCollapsed: false,
|
||||
theme: "system",
|
||||
isInitializing: true,
|
||||
isOnline: true,
|
||||
currentUser: null,
|
||||
notifications: [],
|
||||
settings: {
|
||||
autoRefresh: false,
|
||||
refreshInterval: 30,
|
||||
maxLogEntries: 1000,
|
||||
defaultPageSize: 50,
|
||||
},
|
||||
globalError: null,
|
||||
features: {
|
||||
enableMCP: false,
|
||||
enableCaching: false,
|
||||
enableLogging: false,
|
||||
},
|
||||
selectedItems: {
|
||||
providers: [],
|
||||
virtualKeys: [],
|
||||
teams: [],
|
||||
customers: [],
|
||||
logs: [],
|
||||
},
|
||||
};
|
||||
|
||||
// Create the slice
|
||||
const appSlice = createSlice({
|
||||
name: "app",
|
||||
initialState,
|
||||
reducers: {
|
||||
// UI Actions
|
||||
toggleSidebar: (state) => {
|
||||
state.sidebarCollapsed = !state.sidebarCollapsed;
|
||||
},
|
||||
|
||||
setSidebarCollapsed: (state, action: PayloadAction<boolean>) => {
|
||||
state.sidebarCollapsed = action.payload;
|
||||
},
|
||||
|
||||
setTheme: (state, action: PayloadAction<"light" | "dark" | "system">) => {
|
||||
state.theme = action.payload;
|
||||
},
|
||||
|
||||
// App State Actions
|
||||
setInitializing: (state, action: PayloadAction<boolean>) => {
|
||||
state.isInitializing = action.payload;
|
||||
},
|
||||
|
||||
setOnlineStatus: (state, action: PayloadAction<boolean>) => {
|
||||
state.isOnline = action.payload;
|
||||
},
|
||||
|
||||
// Notification Actions
|
||||
addNotification: (state, action: PayloadAction<Omit<AppState["notifications"][0], "id" | "timestamp" | "read">>) => {
|
||||
const notification = {
|
||||
...action.payload,
|
||||
id: Date.now().toString(),
|
||||
timestamp: Date.now(),
|
||||
read: false,
|
||||
};
|
||||
state.notifications.unshift(notification);
|
||||
|
||||
// Keep only last 50 notifications
|
||||
if (state.notifications.length > 50) {
|
||||
state.notifications = state.notifications.slice(0, 50);
|
||||
}
|
||||
},
|
||||
|
||||
markNotificationRead: (state, action: PayloadAction<string>) => {
|
||||
const notification = state.notifications.find((n) => n.id === action.payload);
|
||||
if (notification) {
|
||||
notification.read = true;
|
||||
}
|
||||
},
|
||||
|
||||
removeNotification: (state, action: PayloadAction<string>) => {
|
||||
state.notifications = state.notifications.filter((n) => n.id !== action.payload);
|
||||
},
|
||||
|
||||
clearAllNotifications: (state) => {
|
||||
state.notifications = [];
|
||||
},
|
||||
|
||||
markAllNotificationsRead: (state) => {
|
||||
state.notifications.forEach((notification) => {
|
||||
notification.read = true;
|
||||
});
|
||||
},
|
||||
|
||||
// Settings Actions
|
||||
updateSettings: (state, action: PayloadAction<Partial<AppState["settings"]>>) => {
|
||||
state.settings = { ...state.settings, ...action.payload };
|
||||
},
|
||||
|
||||
// Error Actions
|
||||
setGlobalError: (state, action: PayloadAction<AppState["globalError"]>) => {
|
||||
state.globalError = action.payload;
|
||||
},
|
||||
|
||||
clearGlobalError: (state) => {
|
||||
state.globalError = null;
|
||||
},
|
||||
|
||||
// Feature Flags Actions
|
||||
updateFeatures: (state, action: PayloadAction<Partial<AppState["features"]>>) => {
|
||||
state.features = { ...state.features, ...action.payload };
|
||||
},
|
||||
// Reset app state (useful for logout)
|
||||
resetAppState: () => initialState,
|
||||
},
|
||||
});
|
||||
|
||||
// Export actions
|
||||
export const {
|
||||
// UI Actions
|
||||
toggleSidebar,
|
||||
setSidebarCollapsed,
|
||||
setTheme,
|
||||
|
||||
// App State Actions
|
||||
setInitializing,
|
||||
setOnlineStatus,
|
||||
|
||||
// Notification Actions
|
||||
addNotification,
|
||||
markNotificationRead,
|
||||
removeNotification,
|
||||
clearAllNotifications,
|
||||
markAllNotificationsRead,
|
||||
|
||||
// Settings Actions
|
||||
updateSettings,
|
||||
|
||||
// Error Actions
|
||||
setGlobalError,
|
||||
clearGlobalError,
|
||||
|
||||
// Feature Flags Actions
|
||||
updateFeatures,
|
||||
|
||||
// Reset
|
||||
resetAppState,
|
||||
} = appSlice.actions;
|
||||
|
||||
// Export reducer
|
||||
export default appSlice.reducer;
|
||||
|
||||
// Selectors
|
||||
export const selectSidebarCollapsed = (state: { app: AppState }) => state.app.sidebarCollapsed;
|
||||
export const selectTheme = (state: { app: AppState }) => state.app.theme;
|
||||
export const selectIsInitializing = (state: { app: AppState }) => state.app.isInitializing;
|
||||
export const selectIsOnline = (state: { app: AppState }) => state.app.isOnline;
|
||||
export const selectCurrentUser = (state: { app: AppState }) => state.app.currentUser;
|
||||
export const selectNotifications = (state: { app: AppState }) => state.app.notifications;
|
||||
export const selectUnreadNotificationsCount = (state: { app: AppState }) => state.app.notifications.filter((n) => !n.read).length;
|
||||
export const selectSettings = (state: { app: AppState }) => state.app.settings;
|
||||
export const selectGlobalError = (state: { app: AppState }) => state.app.globalError;
|
||||
export const selectFeatures = (state: { app: AppState }) => state.app.features;
|
||||
14
ui/lib/store/slices/index.ts
Normal file
14
ui/lib/store/slices/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// App slice exports
|
||||
export * from "./appSlice";
|
||||
export { default as appReducer } from "./appSlice";
|
||||
|
||||
// Provider slice exports
|
||||
export * from "./providerSlice";
|
||||
export { default as providerReducer } from "./providerSlice";
|
||||
|
||||
// Plugin slice exports
|
||||
export * from "./pluginSlice";
|
||||
export { default as pluginReducer } from "./pluginSlice";
|
||||
|
||||
// Enterprise slice exports
|
||||
export * from "@enterprise/lib/store/slices";
|
||||
70
ui/lib/store/slices/pluginSlice.ts
Normal file
70
ui/lib/store/slices/pluginSlice.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Plugin } from "@/lib/types/plugins";
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { pluginsApi } from "../apis";
|
||||
|
||||
interface PluginState {
|
||||
selectedPlugin?: Plugin;
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
const initialState: PluginState = {
|
||||
selectedPlugin: undefined,
|
||||
isDirty: false,
|
||||
};
|
||||
|
||||
const pluginSlice = createSlice({
|
||||
name: "plugin",
|
||||
initialState,
|
||||
reducers: {
|
||||
setPluginFormDirtyState: (state, action: PayloadAction<boolean>) => {
|
||||
state.isDirty = action.payload;
|
||||
},
|
||||
setSelectedPlugin: (state, action: PayloadAction<Plugin | undefined>) => {
|
||||
state.selectedPlugin = action.payload;
|
||||
},
|
||||
},
|
||||
|
||||
extraReducers: (builder) => {
|
||||
// Listen to getPlugins fulfilled to update selected plugin if it has changed
|
||||
builder.addMatcher(pluginsApi.endpoints.getPlugins.matchFulfilled, (state, action) => {
|
||||
const updatedPlugins = action.payload;
|
||||
|
||||
// If we have a selected plugin, check if it has been updated
|
||||
if (state.selectedPlugin && updatedPlugins) {
|
||||
const updatedSelectedPlugin = updatedPlugins.find((plugin) => plugin.name === state.selectedPlugin!.name);
|
||||
// If the selected plugin exists in the updated list, update it
|
||||
if (updatedSelectedPlugin) {
|
||||
state.selectedPlugin = updatedSelectedPlugin;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to updatePlugin fulfilled to update selected plugin if it's the same one
|
||||
builder.addMatcher(pluginsApi.endpoints.updatePlugin.matchFulfilled, (state, action) => {
|
||||
const updatedPluginName = action.meta.arg.originalArgs.name;
|
||||
// If the updated plugin is the currently selected one, update it
|
||||
if (state.selectedPlugin && updatedPluginName === state.selectedPlugin.name) {
|
||||
// Update the selected plugin with the new data
|
||||
const updatedPlugin = action.payload;
|
||||
state.selectedPlugin = updatedPlugin;
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to deletePlugin fulfilled to remove the plugin from the list
|
||||
builder.addMatcher(pluginsApi.endpoints.deletePlugin.matchFulfilled, (state, action) => {
|
||||
const deletedPluginName = action.meta.arg.originalArgs;
|
||||
// If the deleted plugin was selected, clear the selection
|
||||
if (state.selectedPlugin && state.selectedPlugin.name === deletedPluginName) {
|
||||
state.selectedPlugin = undefined;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { setPluginFormDirtyState, setSelectedPlugin } = pluginSlice.actions;
|
||||
|
||||
export default pluginSlice.reducer;
|
||||
|
||||
// Selectors
|
||||
export const selectSelectedPlugin = (state: { plugin: PluginState }) => state.plugin.selectedPlugin;
|
||||
export const selectPluginFormIsDirty = (state: { plugin: PluginState }) => state.plugin.isDirty;
|
||||
80
ui/lib/store/slices/providerSlice.ts
Normal file
80
ui/lib/store/slices/providerSlice.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ModelProvider } from "@/lib/types/config";
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { providersApi } from "../apis";
|
||||
|
||||
interface ProviderState {
|
||||
selectedProvider: ModelProvider | null;
|
||||
isConfigureDialogOpen: boolean;
|
||||
providers: ModelProvider[];
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
const initialState: ProviderState = {
|
||||
selectedProvider: null,
|
||||
isConfigureDialogOpen: false,
|
||||
providers: [],
|
||||
isDirty: false,
|
||||
};
|
||||
|
||||
const providerSlice = createSlice({
|
||||
name: "provider",
|
||||
initialState,
|
||||
reducers: {
|
||||
setProviderFormDirtyState: (state, action: PayloadAction<boolean>) => {
|
||||
state.isDirty = action.payload;
|
||||
},
|
||||
setSelectedProvider: (state, action: PayloadAction<ModelProvider | null>) => {
|
||||
state.selectedProvider = action.payload;
|
||||
},
|
||||
setIsConfigureDialogOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.isConfigureDialogOpen = action.payload;
|
||||
},
|
||||
setProviders: (state, action: PayloadAction<ModelProvider[]>) => {
|
||||
state.providers = action.payload;
|
||||
},
|
||||
openConfigureDialog: (state, action: PayloadAction<ModelProvider | null>) => {
|
||||
state.selectedProvider = action.payload;
|
||||
state.isConfigureDialogOpen = true;
|
||||
},
|
||||
closeConfigureDialog: (state) => {
|
||||
state.selectedProvider = null;
|
||||
state.isConfigureDialogOpen = false;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// Listen to getProviders fulfilled to update selected provider if it has changed
|
||||
builder.addMatcher(providersApi.endpoints.getProviders.matchFulfilled, (state, action) => {
|
||||
const updatedProviders = action.payload;
|
||||
|
||||
// If we have a selected provider, check if it has been updated
|
||||
if (state.selectedProvider && updatedProviders) {
|
||||
const updatedSelectedProvider = updatedProviders.find((provider) => provider.name === state.selectedProvider!.name);
|
||||
// If the selected provider exists in the updated list, update it
|
||||
if (updatedSelectedProvider) {
|
||||
// Check if the provider has actually changed
|
||||
state.selectedProvider = updatedSelectedProvider;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to updateProvider fulfilled to update selected provider if it's the same one
|
||||
builder.addMatcher(providersApi.endpoints.updateProvider.matchFulfilled, (state, action) => {
|
||||
const updatedProvider = action.payload;
|
||||
// If the updated provider is the currently selected one, update it
|
||||
if (state.selectedProvider && updatedProvider.name === state.selectedProvider.name) {
|
||||
state.selectedProvider = updatedProvider;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setProviderFormDirtyState,
|
||||
setSelectedProvider,
|
||||
setIsConfigureDialogOpen,
|
||||
setProviders,
|
||||
openConfigureDialog,
|
||||
closeConfigureDialog,
|
||||
} = providerSlice.actions;
|
||||
|
||||
export default providerSlice.reducer;
|
||||
Reference in New Issue
Block a user