110 lines
4.9 KiB
TypeScript
110 lines
4.9 KiB
TypeScript
import React from 'react';
|
|
import { Save, AlertCircle, CheckCircle, Globe2 } from 'lucide-react';
|
|
import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi';
|
|
import { Button } from './ui/button';
|
|
import { Card } from './ui/card';
|
|
import { Input } from './ui/input';
|
|
|
|
interface ConfigItem {
|
|
key: string;
|
|
value: string;
|
|
description: string;
|
|
}
|
|
|
|
export const GlobalConfigManager = () => {
|
|
const [configs, setConfigs] = React.useState<ConfigItem[]>([]);
|
|
const [loading, setLoading] = React.useState(true);
|
|
const [saving, setSaving] = React.useState(false);
|
|
const [message, setMessage] = React.useState<{ type: 'success' | 'error', text: string } | null>(null);
|
|
|
|
const fetchConfigs = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await fetchDynamicConfigItems();
|
|
setConfigs(data || []);
|
|
} catch (error: any) {
|
|
console.error('Error fetching global config:', error);
|
|
setMessage({ type: 'error', text: error?.message || 'Dynamic config service unavailable.' });
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
fetchConfigs();
|
|
}, []);
|
|
|
|
const handleChange = (key: string, value: string) => {
|
|
setConfigs(prev => prev.map(item => item.key === key ? { ...item, value } : item));
|
|
};
|
|
|
|
const handleSave = async (item: ConfigItem) => {
|
|
setSaving(true);
|
|
try {
|
|
await upsertDynamicConfigItems([item]);
|
|
setMessage({ type: 'success', text: `${item.key} updated successfully.` });
|
|
setTimeout(() => setMessage(null), 3000);
|
|
} catch (error: any) {
|
|
setMessage({ type: 'error', text: `Failed to save ${item.key}: ${error.message}` });
|
|
}
|
|
setSaving(false);
|
|
};
|
|
|
|
if (loading) return <div className="p-4 text-[var(--muted-foreground)]">Loading system configuration...</div>;
|
|
|
|
return (
|
|
<Card className="rounded-xl p-6">
|
|
<h3 className="mb-4 flex items-center gap-2 text-lg font-semibold text-[var(--foreground)]">
|
|
<Globe2 size={18} className="text-[var(--accent)]" />
|
|
Global Bot Configuration
|
|
</h3>
|
|
|
|
{message && (
|
|
<div className={`mb-4 flex items-center gap-2 rounded-lg border p-3 text-sm ${message.type === 'success' ? 'border-green-500/20 bg-green-500/10 text-green-600 dark:text-green-400' : 'border-red-500/20 bg-red-500/10 text-red-600 dark:text-red-400'
|
|
}`}>
|
|
{message.type === 'success' ? <CheckCircle size={16} /> : <AlertCircle size={16} />}
|
|
{message.text}
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-4">
|
|
{configs.map((config) => (
|
|
<div key={config.key} className="flex flex-col gap-1 border-b border-[var(--border)] pb-4 last:border-0 last:pb-0">
|
|
<div className="flex items-center justify-between">
|
|
<label className="text-xs font-bold uppercase tracking-wider text-[var(--accent)]">{config.key}</label>
|
|
<Button
|
|
onClick={() => handleSave(config)}
|
|
disabled={saving}
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-7 rounded px-2 text-xs"
|
|
>
|
|
<Save size={12} /> Save
|
|
</Button>
|
|
</div>
|
|
<Input
|
|
type="text"
|
|
value={config.value}
|
|
onChange={(e) => handleChange(config.key, e.target.value)}
|
|
className="h-10 rounded px-3 py-2 text-sm"
|
|
/>
|
|
<p className="text-[10px] italic text-[var(--muted-foreground)]">{config.description || 'System setting used by the trading core.'}</p>
|
|
</div>
|
|
))}
|
|
|
|
{configs.length === 0 && (
|
|
<div className="rounded-lg border border-dashed border-[var(--border)] py-8 text-center">
|
|
<p className="mb-2 text-sm text-[var(--muted-foreground)]">No dynamic configuration entries found.</p>
|
|
<p className="text-xs text-[var(--muted-foreground)]">The bot is currently using .env defaults.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mt-6 border-t border-[var(--border)] pt-4">
|
|
<p className="text-[10px] leading-relaxed text-[var(--muted-foreground)]">
|
|
<strong>Note:</strong> Changes to global variables usually require a bot restart to take full effect (e.g. changing intervals or providers). Symbols may update dynamically depending on strategy implementation.
|
|
</p>
|
|
</div>
|
|
</Card>
|
|
);
|
|
};
|