- all entities export a kind field (credential/snippet/scenario) for safe type checking on import - import upserts by id: overwrites if id exists, creates with explicit id otherwise - scenario export now includes id and step ids; import deletes old steps before recreating - add GET /:id/export and POST /import endpoints to credential and snippet controllers - add UuidBadge ui component: shortens uuid to first+last 4 hex chars, tooltip, clipboard copy with animated ClipboardCheck icon pop - apply UuidBadge across all entity id display sites (detail pages, card footers, table columns) - add export/import buttons to CredentialsPage, SnippetsPage, CredentialDetailPage, SnippetDetailPage
227 lines
8.1 KiB
TypeScript
227 lines
8.1 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpCode,
|
|
Param,
|
|
ParseUUIDPipe,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
} from "@nestjs/common";
|
|
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
|
import { ScenarioService } from "./scenario.service";
|
|
import { CreateScenarioDto } from "./dto/create-scenario.dto";
|
|
import { UpdateScenarioDto } from "./dto/update-scenario.dto";
|
|
import { CreateScenarioStepDto } from "./dto/create-scenario-step.dto";
|
|
import { UpdateScenarioStepDto } from "./dto/update-scenario-step.dto";
|
|
import { AddScenarioCredentialDto } from "./dto/add-scenario-credential.dto";
|
|
import { PaginationQueryDto } from "./dto/pagination-query.dto";
|
|
import { ScenarioOrderBy } from "./scenario.service";
|
|
import { RunsQueryDto } from "./dto/runs-query.dto";
|
|
import { ScenarioExportDto } from "./dto/scenario-export.dto";
|
|
|
|
@ApiTags("scenarios")
|
|
@Controller("scenarios")
|
|
export class ScenarioController {
|
|
constructor(private readonly scenarioService: ScenarioService) {}
|
|
|
|
// ── Scenarios ─────────────────────────────────────────────────────────────
|
|
|
|
@Post()
|
|
@ApiOperation({ summary: "Create a scenario" })
|
|
@ApiResponse({ status: 201, description: "Scenario created" })
|
|
create(@Body() dto: CreateScenarioDto) {
|
|
return this.scenarioService.create(dto);
|
|
}
|
|
|
|
@Post("import")
|
|
@ApiOperation({ summary: "Import a scenario from an export payload" })
|
|
@ApiResponse({ status: 201, description: "Scenario imported" })
|
|
importScenario(@Body() dto: ScenarioExportDto) {
|
|
return this.scenarioService.importScenario(dto);
|
|
}
|
|
|
|
@Get()
|
|
@ApiOperation({ summary: "List all scenarios (paginated)" })
|
|
@ApiResponse({ status: 200 })
|
|
findAll(@Query() query: PaginationQueryDto<ScenarioOrderBy>) {
|
|
return this.scenarioService.findAll(query);
|
|
}
|
|
|
|
@Get("runs")
|
|
@ApiOperation({ summary: "List runs across all scenarios (paginated, filterable by status)" })
|
|
@ApiResponse({ status: 200 })
|
|
findAllRuns(@Query() query: RunsQueryDto) {
|
|
return this.scenarioService.findAllRuns(query);
|
|
}
|
|
|
|
@Get(":id")
|
|
@ApiOperation({ summary: "Get a scenario with its steps" })
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
findOne(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.scenarioService.findOne(id);
|
|
}
|
|
|
|
@Patch(":id")
|
|
@ApiOperation({ summary: "Update a scenario" })
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
update(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateScenarioDto,
|
|
) {
|
|
return this.scenarioService.update(id, dto);
|
|
}
|
|
|
|
@Delete(":id")
|
|
@HttpCode(204)
|
|
@ApiOperation({ summary: "Delete a scenario and all its steps" })
|
|
@ApiResponse({ status: 204 })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
remove(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.scenarioService.remove(id);
|
|
}
|
|
|
|
@Get(":id/export")
|
|
@ApiOperation({ summary: "Export a scenario as a portable JSON payload" })
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
exportScenario(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.scenarioService.exportScenario(id);
|
|
}
|
|
|
|
// ── Steps ─────────────────────────────────────────────────────────────────
|
|
|
|
@Post(":id/steps")
|
|
@ApiOperation({ summary: "Add a step to a scenario" })
|
|
@ApiResponse({ status: 201, description: "Step created" })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
createStep(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: CreateScenarioStepDto,
|
|
) {
|
|
return this.scenarioService.createStep(id, dto);
|
|
}
|
|
|
|
@Get(":id/steps/:stepId")
|
|
@ApiOperation({ summary: "Get a single step" })
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario or step not found" })
|
|
findStep(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("stepId", ParseUUIDPipe) stepId: string,
|
|
) {
|
|
return this.scenarioService.findStep(id, stepId);
|
|
}
|
|
|
|
@Patch(":id/steps/:stepId")
|
|
@ApiOperation({ summary: "Update a step" })
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario or step not found" })
|
|
updateStep(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("stepId", ParseUUIDPipe) stepId: string,
|
|
@Body() dto: UpdateScenarioStepDto,
|
|
) {
|
|
return this.scenarioService.updateStep(id, stepId, dto);
|
|
}
|
|
|
|
@Delete(":id/steps/:stepId")
|
|
@HttpCode(204)
|
|
@ApiOperation({ summary: "Delete a step" })
|
|
@ApiResponse({ status: 204 })
|
|
@ApiResponse({ status: 404, description: "Scenario or step not found" })
|
|
removeStep(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("stepId", ParseUUIDPipe) stepId: string,
|
|
) {
|
|
return this.scenarioService.removeStep(id, stepId);
|
|
}
|
|
|
|
// ── Credentials ───────────────────────────────────────────────────────────
|
|
|
|
@Get(":id/credentials")
|
|
@ApiOperation({ summary: "List credentials assigned to a scenario" })
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
findScenarioCredentials(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.scenarioService.findScenarioCredentials(id);
|
|
}
|
|
|
|
@Post(":id/credentials")
|
|
@ApiOperation({ summary: "Add a credential to a scenario with an alias" })
|
|
@ApiResponse({ status: 201, description: "Credential added" })
|
|
@ApiResponse({ status: 404, description: "Scenario or credential not found" })
|
|
@ApiResponse({ status: 409, description: "Alias already used in this scenario" })
|
|
addScenarioCredential(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: AddScenarioCredentialDto,
|
|
) {
|
|
return this.scenarioService.addScenarioCredential(id, dto);
|
|
}
|
|
|
|
@Delete(":id/credentials/:scCredId")
|
|
@HttpCode(204)
|
|
@ApiOperation({ summary: "Remove a credential from a scenario" })
|
|
@ApiResponse({ status: 204 })
|
|
@ApiResponse({ status: 404, description: "Scenario or credential assignment not found" })
|
|
removeScenarioCredential(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("scCredId", ParseUUIDPipe) scCredId: string,
|
|
) {
|
|
return this.scenarioService.removeScenarioCredential(id, scCredId);
|
|
}
|
|
|
|
// ── Runs ──────────────────────────────────────────────────────────────────
|
|
|
|
@Get(":id/runs")
|
|
@ApiOperation({
|
|
summary: "List runs for a scenario (paginated, filterable by status)",
|
|
})
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
findRuns(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Query() query: RunsQueryDto,
|
|
) {
|
|
return this.scenarioService.findRuns(id, query);
|
|
}
|
|
|
|
@Post(":id/run")
|
|
@ApiOperation({ summary: "Create a new run for a scenario" })
|
|
@ApiResponse({ status: 201, description: "Run created with step runs" })
|
|
@ApiResponse({ status: 404, description: "Scenario not found" })
|
|
createRun(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.scenarioService.createRun(id);
|
|
}
|
|
|
|
@Get(":id/run/:runId")
|
|
@ApiOperation({ summary: "Get a specific run with step runs and logs" })
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario or run not found" })
|
|
findRun(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("runId", ParseUUIDPipe) runId: string,
|
|
) {
|
|
return this.scenarioService.findRun(id, runId);
|
|
}
|
|
|
|
@Post(":id/run/:runId/wait")
|
|
@HttpCode(200)
|
|
@ApiOperation({
|
|
summary:
|
|
"Block until the run reaches pass or fail (max 5 min), then return run with logs",
|
|
})
|
|
@ApiResponse({ status: 200 })
|
|
@ApiResponse({ status: 404, description: "Scenario or run not found" })
|
|
waitForRun(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("runId", ParseUUIDPipe) runId: string,
|
|
) {
|
|
return this.scenarioService.waitForRun(id, runId);
|
|
}
|
|
}
|