feat(scenarios): link scenarios to a default environment

- add optional ManyToOne relation from scenario to environment entity
- expose environmentId in create/update DTOs, service, and MCP tools
- pre-select linked environment in run modals on detail and list pages
- add environment selector to create/edit scenario forms
- show linked environment as a navigable link on scenario detail page
This commit is contained in:
2026-04-15 00:05:55 +03:00
parent e6aacc899d
commit a779ab4667
12 changed files with 109 additions and 19 deletions
@@ -1,9 +1,10 @@
import { useState, SubmitEvent } from 'react';
import { useEffect, useState, SubmitEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { X, Save, ClipboardList } from 'lucide-react';
import { scenarios } from '../../api';
import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui';
import { scenarios, environments } from '../../api';
import type { Environment } from '../../api';
import { Breadcrumbs, Button, Card, Input, CodeEditor, Select, useToast } from '../../ui';
import styles from '../Page.module.css';
export function CreateScenarioPage() {
@@ -13,9 +14,15 @@ export function CreateScenarioPage() {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [environmentId, setEnvironmentId] = useState('');
const [envs, setEnvs] = useState<Environment[]>([]);
const [nameError, setNameError] = useState('');
const [saving, setSaving] = useState(false);
useEffect(() => {
environments.list(1, 200).then((r) => setEnvs(r?.data ?? [])).catch(() => {});
}, []);
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
if (!name.trim()) {
@@ -24,7 +31,7 @@ export function CreateScenarioPage() {
}
setSaving(true);
try {
const scenario = await scenarios.create(name.trim(), description.trim() || undefined);
const scenario = await scenarios.create(name.trim(), description.trim() || undefined, environmentId || undefined);
toast.success(t('scenarios.created'));
navigate(`/scenarios/${scenario.id}`);
} catch (err) {
@@ -74,6 +81,15 @@ export function CreateScenarioPage() {
rows={8}
/>
</div>
<Select
label={t('scenarios.form_environment')}
value={environmentId}
onChange={(e) => setEnvironmentId(e.target.value)}
options={[
{ value: '', label: t('scenarios.form_environment_none') },
...envs.map((env) => ({ value: env.id, label: env.name })),
]}
/>
</div>
<div className={styles.formActions}>