import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Settings, ExternalLink, Pencil, Trash2, Plus, Globe, Download } from 'lucide-react'; import { parse as yamlParse } from 'yaml'; import { stringify as yamlStringify } from 'yaml'; import { environments } from '../../api'; import type { Environment } from '../../api'; import { Breadcrumbs, Button, Card, ContextMenu, DescriptionList, Modal, Timestamp, UuidBadge, } from '../../ui'; import styles from '../Page.module.css'; function EnvironmentCard({ env, onDelete }: { env: Environment; onDelete: (id: string) => void }) { const { t } = useTranslation(); const navigate = useNavigate(); const dataEntries = Object.entries(env.data ?? {}).filter(([, v]) => v); const handleExport = async () => { const data = await environments.exportEnvironment(env.id); const blob = new Blob([yamlStringify(data)], { type: 'application/yaml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `environment-${env.name}.yaml`; a.click(); URL.revokeObjectURL(url); }; const header = (
{env.name}
e.stopPropagation()}> } items={[ { label: t('environments.action_view'), icon: , onClick: () => navigate(`/environments/${env.id}`), }, { label: t('environments.action_edit'), icon: , onClick: () => navigate(`/environments/${env.id}/edit`), }, { label: t('environments.action_export'), icon: , onClick: () => void handleExport(), }, { label: t('environments.action_delete'), icon: , variant: 'danger', onClick: () => onDelete(env.id), }, ]} />
); const footer = (
); return ( navigate(`/environments/${env.id}`)} > {dataEntries.length > 0 && ( ({ term: key, detail: value }))} /> )} {dataEntries.length === 0 && (

{t('environments.no_data')}

)}
); } function AddEnvironmentCard() { const { t } = useTranslation(); const navigate = useNavigate(); return (
); } export function EnvironmentsPage() { const { t } = useTranslation(); const navigate = useNavigate(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [deleteId, setDeleteId] = useState(null); const [deleting, setDeleting] = useState(false); const fileInputRef = useRef(null); const load = () => { environments .list() .then((res) => setItems(res.data)) .catch((err: Error) => setError(err.message)) .finally(() => setLoading(false)); }; useEffect(() => { load(); }, []); const requestDelete = (id: string) => { setDeleteId(id); }; const handleDelete = async () => { if (!deleteId) return; setDeleting(true); try { await environments.remove(deleteId); setItems((prev) => prev.filter((e) => e.id !== deleteId)); setDeleteId(null); } finally { setDeleting(false); } }; const handleImport = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; e.target.value = ''; try { const text = await file.text(); const payload = yamlParse(text) as unknown; const imported = await environments.importEnvironment(payload); navigate(`/environments/${imported.id}`); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } }; return (
}]} />
{loading ? (

{t('environments.loading')}

) : (
{items.map((env) => ( ))}
)} !deleting && setDeleteId(null)} footer={( <> )} > Are you sure you want to delete this environment?
); }