339 lines
11 KiB
TypeScript
339 lines
11 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { inventoryApi } from '@/lib/api';
|
|
import { toast } from 'react-hot-toast';
|
|
|
|
export function useAdmin() {
|
|
const [users, setUsers] = useState<any[]>([]);
|
|
const [categories, setCategories] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const [ldapConfig, setLdapConfig] = useState<any>({
|
|
ldap_enabled: false,
|
|
server_uri: '',
|
|
base_dn: '',
|
|
user_template: '',
|
|
groups_dn: '',
|
|
use_tls: false,
|
|
role_mappings: []
|
|
});
|
|
const [testingLdap, setTestingLdap] = useState(false);
|
|
|
|
const [editingUser, setEditingUser] = useState<any | null>(null);
|
|
const [editUserForm, setEditUserForm] = useState({ username: '', password: '', role: 'user' });
|
|
|
|
const [editingCategory, setEditingCategory] = useState<any | null>(null);
|
|
const [editCatForm, setEditCatForm] = useState({ name: '', description: '' });
|
|
|
|
const [backups, setBackups] = useState<any[]>([]);
|
|
const [dbStats, setDbStats] = useState({ backup_count: 0, total_size_bytes: 0 });
|
|
const [dbSettings, setDbSettings] = useState({ retention_count: 10, schedule_hour: 3, schedule_freq_days: 1 });
|
|
const [isBackingUp, setIsBackingUp] = useState(false);
|
|
const [isImporting, setIsImporting] = useState(false);
|
|
|
|
const [aiPrompt, setAiPrompt] = useState("");
|
|
const [aiConfig, setAiConfig] = useState<any>(null);
|
|
const [aiKeys, setAiKeys] = useState({ gemini: '', claude: '' });
|
|
const [isSavingPrompt, setIsSavingPrompt] = useState(false);
|
|
const [isSavingKeys, setIsSavingKeys] = useState(false);
|
|
const [isTestingKeys, setIsTestingKeys] = useState<{gemini: boolean, claude: boolean}>(
|
|
{gemini: false, claude: false}
|
|
);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const loadData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const [u, c, l, b, s, st, promptRes, ai] = await Promise.all([
|
|
inventoryApi.getUsers(),
|
|
inventoryApi.getCategories(),
|
|
inventoryApi.getLdapConfig(),
|
|
inventoryApi.getDbBackups(),
|
|
inventoryApi.getDbStats(),
|
|
inventoryApi.getDbSettings(),
|
|
inventoryApi.getAiPrompt(),
|
|
inventoryApi.getAiConfig()
|
|
]);
|
|
setUsers(u);
|
|
setCategories(c);
|
|
if (l && l.server_uri) setLdapConfig(l);
|
|
setBackups(b);
|
|
setDbStats(s);
|
|
setDbSettings(st);
|
|
setAiPrompt(promptRes.value);
|
|
setAiConfig(ai);
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
toast.error("Failed to load admin data");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleAddUser = async () => {
|
|
const name = window.prompt("Enter new username:");
|
|
if (!name) return;
|
|
const pwd = window.prompt("Enter password for " + name + ":");
|
|
if (!pwd) return;
|
|
|
|
try {
|
|
await inventoryApi.createUser({ username: name, password: pwd, role: 'user' });
|
|
toast.success("User created successfully");
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Failed to create user");
|
|
}
|
|
};
|
|
|
|
const handleDeleteUser = async (id: number, username: string) => {
|
|
if (username === 'Admin') return;
|
|
if (!window.confirm(`Delete user ${username}?`)) return;
|
|
|
|
try {
|
|
await inventoryApi.deleteUser(id);
|
|
toast.success("User removed");
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Delete failed");
|
|
}
|
|
};
|
|
|
|
const handleUpdateUserSubmit = async () => {
|
|
if (!editingUser) return;
|
|
try {
|
|
const payload: any = { username: editUserForm.username, role: editUserForm.role };
|
|
if (editUserForm.password) payload.password = editUserForm.password;
|
|
|
|
await inventoryApi.updateUser(editingUser.id, payload);
|
|
toast.success("User updated successfully");
|
|
setEditingUser(null);
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Update failed");
|
|
}
|
|
};
|
|
|
|
const handleUpdateAiProvider = async (provider: string) => {
|
|
try {
|
|
await inventoryApi.updateAiProvider(provider);
|
|
toast.success(`AI provider switched to ${provider}`);
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Failed to switch AI provider");
|
|
}
|
|
};
|
|
|
|
const handleSaveAiKeys = async () => {
|
|
if (!aiKeys.gemini && !aiKeys.claude) {
|
|
toast.error("Enter at least one API key to save");
|
|
return;
|
|
}
|
|
|
|
setIsSavingKeys(true);
|
|
try {
|
|
await inventoryApi.updateAiKeys({
|
|
gemini_api_key: aiKeys.gemini || undefined,
|
|
claude_api_key: aiKeys.claude || undefined
|
|
});
|
|
toast.success("AI Configuration keys updated successfully");
|
|
setAiKeys({ gemini: '', claude: '' });
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Failed to update AI keys");
|
|
} finally {
|
|
setIsSavingKeys(false);
|
|
}
|
|
};
|
|
|
|
const handleTestAiKey = async (provider: 'gemini' | 'claude') => {
|
|
const keyToTest = provider === 'gemini' ? aiKeys.gemini : aiKeys.claude;
|
|
|
|
setIsTestingKeys(prev => ({ ...prev, [provider]: true }));
|
|
try {
|
|
const res = await inventoryApi.testAiKey(provider, keyToTest);
|
|
toast.success(res.message || `${provider.toUpperCase()} connection successful!`);
|
|
} catch (err: any) {
|
|
const msg = err.response?.data?.detail || `Failed to test ${provider} key`;
|
|
toast.error(msg);
|
|
} finally {
|
|
setIsTestingKeys(prev => ({ ...prev, [provider]: false }));
|
|
}
|
|
};
|
|
|
|
const handleAddCategory = async () => {
|
|
const name = window.prompt("New category name:");
|
|
if (!name) return;
|
|
const desc = window.prompt("Description (optional):");
|
|
|
|
try {
|
|
await inventoryApi.createCategory({ name, description: desc });
|
|
toast.success("Category added");
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Failed to add category");
|
|
}
|
|
};
|
|
|
|
const handleUpdateCategorySubmit = async () => {
|
|
if (!editingCategory) return;
|
|
try {
|
|
await inventoryApi.updateCategory(editingCategory.id, editCatForm);
|
|
toast.success("Category updated");
|
|
setEditingCategory(null);
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Update failed");
|
|
}
|
|
};
|
|
|
|
const handleDeleteCategory = async (id: number, name: string) => {
|
|
if (!window.confirm(`Delete category ${name}?`)) return;
|
|
|
|
try {
|
|
await inventoryApi.deleteCategory(id);
|
|
toast.success("Category removed");
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error(err.response?.data?.detail || "Delete failed");
|
|
}
|
|
};
|
|
|
|
const handleCreateBackup = async () => {
|
|
setIsBackingUp(true);
|
|
try {
|
|
await inventoryApi.triggerBackup();
|
|
toast.success("Snapshot created successfully");
|
|
loadData();
|
|
} catch (err: any) {
|
|
toast.error("Backup failed");
|
|
} finally {
|
|
setIsBackingUp(false);
|
|
}
|
|
};
|
|
|
|
const handleUpdateDbSettings = async (newSettings: any) => {
|
|
try {
|
|
await inventoryApi.updateDbSettings(newSettings);
|
|
toast.success("System policy updated");
|
|
setDbSettings(newSettings);
|
|
} catch (err: any) {
|
|
toast.error("Failed to update settings");
|
|
}
|
|
};
|
|
|
|
const handleUpdatePrompt = async () => {
|
|
setIsSavingPrompt(true);
|
|
try {
|
|
await inventoryApi.updateAiPrompt(aiPrompt);
|
|
toast.success("AI Extraction Prompt updated");
|
|
} catch (err: any) {
|
|
toast.error("Failed to update AI prompt");
|
|
} finally {
|
|
setIsSavingPrompt(false);
|
|
}
|
|
};
|
|
|
|
const handleUpdateLdap = async () => {
|
|
setLoading(true);
|
|
try {
|
|
await inventoryApi.updateLdapConfig(ldapConfig);
|
|
toast.success("Enterprise configuration updated");
|
|
} catch (err: any) {
|
|
toast.error("Failed to update LDAP config");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleTestLdap = async () => {
|
|
setTestingLdap(true);
|
|
try {
|
|
const res = await inventoryApi.testLdapConnection(ldapConfig);
|
|
if (res.status === 'success') {
|
|
toast.success(res.message || "LDAP Connection Successful!");
|
|
} else {
|
|
toast.error(`LDAP Error: ${res.message || "Unknown error"}`);
|
|
}
|
|
} catch (err: any) {
|
|
toast.error(`Connection failed: ${err.response?.data?.detail || err.message}`);
|
|
} finally {
|
|
setTestingLdap(false);
|
|
}
|
|
};
|
|
|
|
const handleRestore = async (filename: string) => {
|
|
if (!window.confirm(`DANGEROUS: Restore database from ${filename}? Current data will be replaced. A rollback snapshot will be created automatically.`)) return;
|
|
const loadingToast = toast.loading("Restoring database...");
|
|
try {
|
|
await inventoryApi.restoreDatabase(filename);
|
|
toast.success("Database restored! Reloading system...", { id: loadingToast });
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} catch (err: any) {
|
|
toast.error("Restore failed", { id: loadingToast });
|
|
}
|
|
};
|
|
|
|
const handleExportDb = async () => {
|
|
try {
|
|
const blob = await inventoryApi.exportDb();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `inventory_backup_${new Date().toISOString().split('T')[0]}.db`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
toast.success("Database exported successfully");
|
|
} catch (err: any) {
|
|
toast.error("Export failed");
|
|
}
|
|
};
|
|
|
|
const handleImportDb = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
if (!window.confirm("DANGEROUS: You are about to REPLACE the entire database with this file. All current data will be lost. Proceed?")) {
|
|
e.target.value = '';
|
|
return;
|
|
}
|
|
setIsImporting(true);
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
const loadingToast = toast.loading("Importing database...");
|
|
try {
|
|
await inventoryApi.importDb(formData);
|
|
toast.success("Database imported! Reloading system...", { id: loadingToast });
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} catch (err: any) {
|
|
toast.error("Import failed: " + (err.response?.data?.detail || err.message), { id: loadingToast });
|
|
} finally {
|
|
setIsImporting(false);
|
|
e.target.value = '';
|
|
}
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
import('@/lib/auth').then(m => m.clearAuth());
|
|
window.location.href = '/login';
|
|
};
|
|
|
|
return {
|
|
users, categories, loading,
|
|
ldapConfig, setLdapConfig, testingLdap,
|
|
editingUser, setEditingUser, editUserForm, setEditUserForm,
|
|
editingCategory, setEditingCategory, editCatForm, setEditCatForm,
|
|
backups, dbStats, dbSettings, isBackingUp, isImporting,
|
|
aiPrompt, setAiPrompt, aiConfig, aiKeys, setAiKeys,
|
|
isSavingPrompt, isSavingKeys, isTestingKeys,
|
|
refreshData: loadData,
|
|
handleAddUser, handleDeleteUser, handleUpdateUserSubmit,
|
|
handleUpdateLdap, handleTestLdap,
|
|
handleUpdateAiProvider, handleSaveAiKeys, handleTestAiKey, handleUpdatePrompt,
|
|
handleAddCategory, handleUpdateCategorySubmit, handleDeleteCategory,
|
|
handleCreateBackup, handleUpdateDbSettings, handleRestore, handleExportDb, handleImportDb,
|
|
handleLogout
|
|
};
|
|
}
|