186 lines
8.1 KiB
TypeScript
186 lines
8.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|||
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
||
|
|
import { ArrowLeft, Plus, Edit, Trash2, Check, X } from 'lucide-react';
|
||
|
|
import { adminModuleApi } from '../AdminModuleApi';
|
||
|
|
import type { ModuleEnvironment, Module } from '../AdminModuleTypes';
|
||
|
|
import EnvironmentForm from './EnvironmentForm';
|
||
|
|
|
||
|
|
const ModuleEnvironments = () => {
|
||
|
|
const { moduleId } = useParams<{ moduleId: string }>();
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const [module, setModule] = useState<Module | null>(null);
|
||
|
|
const [environments, setEnvironments] = useState<ModuleEnvironment[]>([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [showForm, setShowForm] = useState(false);
|
||
|
|
const [selectedEnv, setSelectedEnv] = useState<ModuleEnvironment | null>(null);
|
||
|
|
|
||
|
|
const fetchData = async () => {
|
||
|
|
if (!moduleId) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const [moduleData, envsData] = await Promise.all([
|
||
|
|
adminModuleApi.getModule(moduleId),
|
||
|
|
adminModuleApi.listEnvironments(moduleId)
|
||
|
|
]);
|
||
|
|
setModule(moduleData);
|
||
|
|
setEnvironments(envsData);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to fetch data', error);
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
fetchData();
|
||
|
|
}, [moduleId]);
|
||
|
|
|
||
|
|
const handleSetDefault = async (envId: string) => {
|
||
|
|
if (!moduleId) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
await adminModuleApi.setDefaultEnvironment(moduleId, envId);
|
||
|
|
fetchData();
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to set default', error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleDelete = async (env: ModuleEnvironment) => {
|
||
|
|
if (!moduleId) return;
|
||
|
|
if (!confirm(`Delete environment "${env.slug}"?`)) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
await adminModuleApi.deleteEnvironment(moduleId, env.id);
|
||
|
|
fetchData();
|
||
|
|
} catch (error: any) {
|
||
|
|
alert(error.response?.data?.detail || 'Failed to delete environment');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return <div className="p-6">Loading...</div>;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="p-6 space-y-6">
|
||
|
|
<div className="flex items-center gap-4">
|
||
|
|
<button
|
||
|
|
onClick={() => navigate('/admin/modules')}
|
||
|
|
className="p-2 hover:bg-gray-100 rounded-lg"
|
||
|
|
>
|
||
|
|
<ArrowLeft size={20} />
|
||
|
|
</button>
|
||
|
|
<div className="flex-1">
|
||
|
|
<h1 className="text-3xl font-bold text-(--text-primary)">
|
||
|
|
{module?.module_name} - Environments
|
||
|
|
</h1>
|
||
|
|
<p className="text-(--text-secondary) mt-1">Configure environment-specific settings</p>
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
onClick={() => { setSelectedEnv(null); setShowForm(true); }}
|
||
|
|
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
||
|
|
>
|
||
|
|
<Plus size={20} />
|
||
|
|
Add Environment
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||
|
|
{environments.map((env) => (
|
||
|
|
<div key={env.id} className="bg-(--card-bg) border border-(--card-border) rounded-lg p-6 space-y-4">
|
||
|
|
<div className="flex items-start justify-between">
|
||
|
|
<div>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<h3 className="text-lg font-semibold">{env.slug}</h3>
|
||
|
|
{env.is_default && (
|
||
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-700 text-xs font-medium rounded">
|
||
|
|
Default
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
{env.is_active ? (
|
||
|
|
<Check className="w-5 h-5 text-green-600" />
|
||
|
|
) : (
|
||
|
|
<X className="w-5 h-5 text-gray-400" />
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="space-y-2 text-sm">
|
||
|
|
<div>
|
||
|
|
<span className="text-gray-500">Frontend:</span>
|
||
|
|
<p className="text-(--text-primary) font-mono text-xs break-all">{env.frontend_base_url}</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<span className="text-gray-500">Backend:</span>
|
||
|
|
<p className="text-(--text-primary) font-mono text-xs break-all">{env.backend_base_url}</p>
|
||
|
|
</div>
|
||
|
|
<div className="grid grid-cols-2 gap-2">
|
||
|
|
<div>
|
||
|
|
<span className="text-gray-500">Trust:</span>
|
||
|
|
<p className="text-(--text-primary)">{env.trust_type}</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<span className="text-gray-500">SSO Path:</span>
|
||
|
|
<p className="text-(--text-primary) font-mono text-xs">{env.sso_entry_path}</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center gap-2 pt-2 border-t">
|
||
|
|
{!env.is_default && (
|
||
|
|
<button
|
||
|
|
onClick={() => handleSetDefault(env.id)}
|
||
|
|
className="flex-1 px-3 py-2 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 text-sm"
|
||
|
|
>
|
||
|
|
Set Default
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
<button
|
||
|
|
onClick={() => { setSelectedEnv(env); setShowForm(true); }}
|
||
|
|
className="flex-1 px-3 py-2 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 text-sm flex items-center justify-center gap-2"
|
||
|
|
>
|
||
|
|
<Edit size={16} />
|
||
|
|
Edit
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={() => handleDelete(env)}
|
||
|
|
className="flex-1 px-3 py-2 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 text-sm flex items-center justify-center gap-2"
|
||
|
|
>
|
||
|
|
<Trash2 size={16} />
|
||
|
|
Delete
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{environments.length === 0 && (
|
||
|
|
<div className="bg-(--card-bg) border border-(--card-border) rounded-lg p-12 text-center">
|
||
|
|
<p className="text-(--text-secondary) mb-4">No environments configured</p>
|
||
|
|
<button
|
||
|
|
onClick={() => { setSelectedEnv(null); setShowForm(true); }}
|
||
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
||
|
|
>
|
||
|
|
Add First Environment
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{showForm && moduleId && (
|
||
|
|
<EnvironmentForm
|
||
|
|
moduleId={moduleId}
|
||
|
|
environment={selectedEnv}
|
||
|
|
onClose={(saved) => {
|
||
|
|
setShowForm(false);
|
||
|
|
setSelectedEnv(null);
|
||
|
|
if (saved) fetchData();
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default ModuleEnvironments;
|