|
@@ -0,0 +1,279 @@
|
|
|
|
|
+"use client";
|
|
|
|
|
+
|
|
|
|
|
+import React, { ReactNode, useCallback, useEffect, useState } from "react";
|
|
|
|
|
+import { get, post, del, put } from "../../lib/api";
|
|
|
|
|
+
|
|
|
|
|
+export interface Settings {
|
|
|
|
|
+ [key: string]: any;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface Dataset {
|
|
|
|
|
+ [path: string]: any;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface DatasetsConfig {
|
|
|
|
|
+ [datasetName: string]: Dataset;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface QueueConfig {
|
|
|
|
|
+ maxConcurrent?: number;
|
|
|
|
|
+ maxRetries?: number;
|
|
|
|
|
+ [key: string]: any;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface WatcherConfig {
|
|
|
|
|
+ enabled?: boolean;
|
|
|
|
|
+ [key: string]: any;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface AppContextType {
|
|
|
|
|
+ // Data
|
|
|
|
|
+ settings: Settings | null;
|
|
|
|
|
+ datasetsConfig: DatasetsConfig | null;
|
|
|
|
|
+ queueConfig: QueueConfig | null;
|
|
|
|
|
+ watcherConfig: WatcherConfig | null;
|
|
|
|
|
+ datasets: string[] | null;
|
|
|
|
|
+
|
|
|
|
|
+ // Loading states
|
|
|
|
|
+ isLoading: boolean;
|
|
|
|
|
+ isInitialized: boolean;
|
|
|
|
|
+
|
|
|
|
|
+ // Errors
|
|
|
|
|
+ error: Error | null;
|
|
|
|
|
+
|
|
|
|
|
+ // Mutation functions
|
|
|
|
|
+ updateSetting: (key: string, value: any) => Promise<void>;
|
|
|
|
|
+ deleteSetting: (key: string) => Promise<void>;
|
|
|
|
|
+ updateDatasets: (datasets: DatasetsConfig) => Promise<void>;
|
|
|
|
|
+ updateQueueConfig: (config: QueueConfig) => Promise<void>;
|
|
|
|
|
+ updateWatcherConfig: (config: WatcherConfig) => Promise<void>;
|
|
|
|
|
+
|
|
|
|
|
+ // Refresh functions
|
|
|
|
|
+ refreshSettings: () => Promise<void>;
|
|
|
|
|
+ refreshDatasets: () => Promise<void>;
|
|
|
|
|
+ refreshAll: () => Promise<void>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const AppContext = React.createContext<AppContextType | undefined>(undefined);
|
|
|
|
|
+
|
|
|
|
|
+export function AppProvider({ children }: { children: ReactNode }) {
|
|
|
|
|
+ const [settings, setSettings] = useState<Settings | null>(null);
|
|
|
|
|
+ const [datasetsConfig, setDatasetsConfig] = useState<DatasetsConfig | null>(
|
|
|
|
|
+ null
|
|
|
|
|
+ );
|
|
|
|
|
+ const [queueConfig, setQueueConfig] = useState<QueueConfig | null>(null);
|
|
|
|
|
+ const [watcherConfig, setWatcherConfig] = useState<WatcherConfig | null>(
|
|
|
|
|
+ null
|
|
|
|
|
+ );
|
|
|
|
|
+ const [datasets, setDatasets] = useState<string[] | null>(null);
|
|
|
|
|
+ const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
+ const [isInitialized, setIsInitialized] = useState(false);
|
|
|
|
|
+ const [error, setError] = useState<Error | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ // Load all initial data
|
|
|
|
|
+ const initializeData = useCallback(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ setIsLoading(true);
|
|
|
|
|
+ setError(null);
|
|
|
|
|
+
|
|
|
|
|
+ // Load settings
|
|
|
|
|
+ const settingsData = await get("/config/settings");
|
|
|
|
|
+ setSettings(settingsData || {});
|
|
|
|
|
+
|
|
|
|
|
+ // Extract specific configs from settings
|
|
|
|
|
+ if (settingsData) {
|
|
|
|
|
+ const queue = settingsData.queue || {};
|
|
|
|
|
+ const watcher = settingsData.watcher || {};
|
|
|
|
|
+ const datasetsData = settingsData.datasets || {};
|
|
|
|
|
+
|
|
|
|
|
+ setQueueConfig(queue);
|
|
|
|
|
+ setWatcherConfig(watcher);
|
|
|
|
|
+ setDatasetsConfig(datasetsData);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Load datasets list
|
|
|
|
|
+ const datasetsList = await get("/files/all-datasets");
|
|
|
|
|
+ setDatasets(datasetsList || []);
|
|
|
|
|
+
|
|
|
|
|
+ setIsInitialized(true);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ console.error("Failed to initialize app context:", error);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ // Initial load
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ initializeData();
|
|
|
|
|
+ }, [initializeData]);
|
|
|
|
|
+
|
|
|
|
|
+ // Listen for WebSocket events
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ const handleSettingsUpdate = () => {
|
|
|
|
|
+ initializeData();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleTaskUpdate = () => {
|
|
|
|
|
+ // Refetch datasets in case new ones were created
|
|
|
|
|
+ initializeData();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleFileUpdate = () => {
|
|
|
|
|
+ // Refetch datasets in case new ones were created
|
|
|
|
|
+ initializeData();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ window.addEventListener("settingsUpdate", handleSettingsUpdate as EventListener);
|
|
|
|
|
+ window.addEventListener("taskUpdate", handleTaskUpdate as EventListener);
|
|
|
|
|
+ window.addEventListener("fileUpdate", handleFileUpdate as EventListener);
|
|
|
|
|
+
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ window.removeEventListener("settingsUpdate", handleSettingsUpdate as EventListener);
|
|
|
|
|
+ window.removeEventListener("taskUpdate", handleTaskUpdate as EventListener);
|
|
|
|
|
+ window.removeEventListener("fileUpdate", handleFileUpdate as EventListener);
|
|
|
|
|
+ };
|
|
|
|
|
+ }, [initializeData]);
|
|
|
|
|
+
|
|
|
|
|
+ // Mutation functions
|
|
|
|
|
+ const updateSetting = useCallback(
|
|
|
|
|
+ async (key: string, value: any) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await post("/config/settings", { [key]: value });
|
|
|
|
|
+ setSettings((prev) =>
|
|
|
|
|
+ prev ? { ...prev, [key]: value } : { [key]: value }
|
|
|
|
|
+ );
|
|
|
|
|
+ await initializeData();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ [initializeData]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const deleteSetting = useCallback(
|
|
|
|
|
+ async (key: string) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await del(`/config/settings/${key}`);
|
|
|
|
|
+ setSettings((prev) => {
|
|
|
|
|
+ if (!prev) return null;
|
|
|
|
|
+ const updated = { ...prev };
|
|
|
|
|
+ delete updated[key];
|
|
|
|
|
+ return updated;
|
|
|
|
|
+ });
|
|
|
|
|
+ await initializeData();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ [initializeData]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const updateDatasets = useCallback(
|
|
|
|
|
+ async (datasetsData: DatasetsConfig) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ setDatasetsConfig(datasetsData);
|
|
|
|
|
+ await updateSetting("datasets", datasetsData);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ [updateSetting]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const updateQueueConfig = useCallback(
|
|
|
|
|
+ async (config: QueueConfig) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ setQueueConfig(config);
|
|
|
|
|
+ await updateSetting("queue", config);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ [updateSetting]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const updateWatcherConfig = useCallback(
|
|
|
|
|
+ async (config: WatcherConfig) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ setWatcherConfig(config);
|
|
|
|
|
+ await updateSetting("watcher", config);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ [updateSetting]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const refreshSettings = useCallback(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const settingsData = await get("/config/settings");
|
|
|
|
|
+ setSettings(settingsData || {});
|
|
|
|
|
+ if (settingsData) {
|
|
|
|
|
+ setQueueConfig(settingsData.queue || {});
|
|
|
|
|
+ setWatcherConfig(settingsData.watcher || {});
|
|
|
|
|
+ setDatasetsConfig(settingsData.datasets || {});
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ const refreshDatasets = useCallback(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const datasetsList = await get("/files/all-datasets");
|
|
|
|
|
+ setDatasets(datasetsList || []);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
+ setError(error);
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ const refreshAll = useCallback(async () => {
|
|
|
|
|
+ await initializeData();
|
|
|
|
|
+ }, [initializeData]);
|
|
|
|
|
+
|
|
|
|
|
+ const value: AppContextType = {
|
|
|
|
|
+ settings,
|
|
|
|
|
+ datasetsConfig,
|
|
|
|
|
+ queueConfig,
|
|
|
|
|
+ watcherConfig,
|
|
|
|
|
+ datasets,
|
|
|
|
|
+ isLoading,
|
|
|
|
|
+ isInitialized,
|
|
|
|
|
+ error,
|
|
|
|
|
+ updateSetting,
|
|
|
|
|
+ deleteSetting,
|
|
|
|
|
+ updateDatasets,
|
|
|
|
|
+ updateQueueConfig,
|
|
|
|
|
+ updateWatcherConfig,
|
|
|
|
|
+ refreshSettings,
|
|
|
|
|
+ refreshDatasets,
|
|
|
|
|
+ refreshAll
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function useAppContext() {
|
|
|
|
|
+ const context = React.useContext(AppContext);
|
|
|
|
|
+ if (context === undefined) {
|
|
|
|
|
+ throw new Error("useAppContext must be used within an AppProvider");
|
|
|
|
|
+ }
|
|
|
|
|
+ return context;
|
|
|
|
|
+}
|