feat(scenarios): add step title, scenario credentials, and environment context in executor

- add nullable title column to scenario steps; exposed in create/edit forms and step table
- add scenario-credential join (many-to-many with CredentialEntity) with CRUD endpoints
- expose environment URLs in code executor helpers via helpers.env and helpers.getEnvUrl()
- resolve and cache environment per run from the login step's environmentName in scheduler
- add section spacing and step table title column to ScenarioDetailPage
This commit is contained in:
2026-04-09 23:37:21 +03:00
parent 1f3a604940
commit 1efbbb38a3
34 changed files with 1362 additions and 14 deletions
+49 -1
View File
@@ -1,7 +1,9 @@
import type {
PaginatedResponse,
Credential,
Environment,
KeysResponse,
ScenarioCredential,
Session,
Scenario,
ScenarioRun,
@@ -30,6 +32,32 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
return JSON.parse(text) as T;
}
// ── Credentials ───────────────────────────────────────────────────────────────
export const credentials = {
list(page = 1, limit = 50): Promise<PaginatedResponse<Credential>> {
return request(`/credentials?page=${page}&limit=${limit}`);
},
get(id: number): Promise<Credential> {
return request(`/credentials/${id}`);
},
create(name: string, data?: string): Promise<Credential> {
return request('/credentials', {
method: 'POST',
body: JSON.stringify({ name, data }),
});
},
update(id: number, patch: Partial<Pick<Credential, 'name' | 'data'>>): Promise<Credential> {
return request(`/credentials/${id}`, {
method: 'PATCH',
body: JSON.stringify(patch),
});
},
remove(id: number): Promise<void> {
return request(`/credentials/${id}`, { method: 'DELETE' });
},
};
// ── Environments ──────────────────────────────────────────────────────────────
export const environments = {
@@ -132,9 +160,28 @@ export const runs = {
},
};
// ── Scenario Steps ────────────────────────────────────────────────────────────
// ── Scenario Credentials ──────────────────────────────────────────────────────
export const scenarioCredentials = {
list(scenarioId: number): Promise<ScenarioCredential[]> {
return request(`/scenarios/${scenarioId}/credentials`);
},
add(scenarioId: number, credentialId: number, alias: string): Promise<ScenarioCredential> {
return request(`/scenarios/${scenarioId}/credentials`, {
method: 'POST',
body: JSON.stringify({ credentialId, alias }),
});
},
remove(scenarioId: number, scCredId: number): Promise<void> {
return request(`/scenarios/${scenarioId}/credentials/${scCredId}`, {
method: 'DELETE',
});
},
};
// ── Scenario Steps ────────────────────────────────────────────────────────────
export interface CreateStepPayload {
title?: string;
order: number;
type: 'login' | 'exec' | 'sign';
sessionName: string;
@@ -143,6 +190,7 @@ export interface CreateStepPayload {
}
export interface UpdateStepPayload {
title?: string;
order?: number;
type?: 'login' | 'exec' | 'sign';
sessionName?: string;
+22
View File
@@ -24,6 +24,17 @@ export interface Environment {
updatedAt: string;
}
// ── Credentials ───────────────────────────────────────────────────────────────
export interface Credential {
id: number;
name: string;
data: string | null;
lastUsedAt: string | null;
createdAt: string;
updatedAt: string;
}
// ── Keys ──────────────────────────────────────────────────────────────────────
export interface KeysResponse {
@@ -53,6 +64,7 @@ export interface ScenarioStep {
scenarioId: number;
order: number;
type: StepType;
title: string | null;
sessionName: string;
execCode: string | null;
validateCode: string | null;
@@ -60,10 +72,20 @@ export interface ScenarioStep {
updatedAt: string;
}
export interface ScenarioCredential {
id: number;
scenarioId: number;
credentialId: number;
alias: string;
credential: Credential;
createdAt: string;
}
export interface Scenario {
id: number;
name: string;
steps?: ScenarioStep[];
scenarioCredentials?: ScenarioCredential[];
createdAt: string;
updatedAt: string;
}