feat(export-import): add yaml export/import with id upsert for credentials, snippets, and scenarios

- 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
This commit is contained in:
2026-04-10 13:09:09 +03:00
parent 1164289173
commit 32be7c0a59
54 changed files with 728 additions and 272 deletions
+22 -22
View File
@@ -5,7 +5,7 @@ import {
Get,
HttpCode,
Param,
ParseIntPipe,
ParseUUIDPipe,
Patch,
Post,
Query,
@@ -61,7 +61,7 @@ export class ScenarioController {
@ApiOperation({ summary: "Get a scenario with its steps" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
findOne(@Param("id", ParseIntPipe) id: number) {
findOne(@Param("id", ParseUUIDPipe) id: string) {
return this.scenarioService.findOne(id);
}
@@ -70,7 +70,7 @@ export class ScenarioController {
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
update(
@Param("id", ParseIntPipe) id: number,
@Param("id", ParseUUIDPipe) id: string,
@Body() dto: UpdateScenarioDto,
) {
return this.scenarioService.update(id, dto);
@@ -81,7 +81,7 @@ export class ScenarioController {
@ApiOperation({ summary: "Delete a scenario and all its steps" })
@ApiResponse({ status: 204 })
@ApiResponse({ status: 404, description: "Scenario not found" })
remove(@Param("id", ParseIntPipe) id: number) {
remove(@Param("id", ParseUUIDPipe) id: string) {
return this.scenarioService.remove(id);
}
@@ -89,7 +89,7 @@ export class ScenarioController {
@ApiOperation({ summary: "Export a scenario as a portable JSON payload" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
exportScenario(@Param("id", ParseIntPipe) id: number) {
exportScenario(@Param("id", ParseUUIDPipe) id: string) {
return this.scenarioService.exportScenario(id);
}
@@ -100,7 +100,7 @@ export class ScenarioController {
@ApiResponse({ status: 201, description: "Step created" })
@ApiResponse({ status: 404, description: "Scenario not found" })
createStep(
@Param("id", ParseIntPipe) id: number,
@Param("id", ParseUUIDPipe) id: string,
@Body() dto: CreateScenarioStepDto,
) {
return this.scenarioService.createStep(id, dto);
@@ -111,8 +111,8 @@ export class ScenarioController {
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or step not found" })
findStep(
@Param("id", ParseIntPipe) id: number,
@Param("stepId", ParseIntPipe) stepId: number,
@Param("id", ParseUUIDPipe) id: string,
@Param("stepId", ParseUUIDPipe) stepId: string,
) {
return this.scenarioService.findStep(id, stepId);
}
@@ -122,8 +122,8 @@ export class ScenarioController {
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or step not found" })
updateStep(
@Param("id", ParseIntPipe) id: number,
@Param("stepId", ParseIntPipe) stepId: number,
@Param("id", ParseUUIDPipe) id: string,
@Param("stepId", ParseUUIDPipe) stepId: string,
@Body() dto: UpdateScenarioStepDto,
) {
return this.scenarioService.updateStep(id, stepId, dto);
@@ -135,8 +135,8 @@ export class ScenarioController {
@ApiResponse({ status: 204 })
@ApiResponse({ status: 404, description: "Scenario or step not found" })
removeStep(
@Param("id", ParseIntPipe) id: number,
@Param("stepId", ParseIntPipe) stepId: number,
@Param("id", ParseUUIDPipe) id: string,
@Param("stepId", ParseUUIDPipe) stepId: string,
) {
return this.scenarioService.removeStep(id, stepId);
}
@@ -147,7 +147,7 @@ export class ScenarioController {
@ApiOperation({ summary: "List credentials assigned to a scenario" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
findScenarioCredentials(@Param("id", ParseIntPipe) id: number) {
findScenarioCredentials(@Param("id", ParseUUIDPipe) id: string) {
return this.scenarioService.findScenarioCredentials(id);
}
@@ -157,7 +157,7 @@ export class ScenarioController {
@ApiResponse({ status: 404, description: "Scenario or credential not found" })
@ApiResponse({ status: 409, description: "Alias already used in this scenario" })
addScenarioCredential(
@Param("id", ParseIntPipe) id: number,
@Param("id", ParseUUIDPipe) id: string,
@Body() dto: AddScenarioCredentialDto,
) {
return this.scenarioService.addScenarioCredential(id, dto);
@@ -169,8 +169,8 @@ export class ScenarioController {
@ApiResponse({ status: 204 })
@ApiResponse({ status: 404, description: "Scenario or credential assignment not found" })
removeScenarioCredential(
@Param("id", ParseIntPipe) id: number,
@Param("scCredId", ParseIntPipe) scCredId: number,
@Param("id", ParseUUIDPipe) id: string,
@Param("scCredId", ParseUUIDPipe) scCredId: string,
) {
return this.scenarioService.removeScenarioCredential(id, scCredId);
}
@@ -184,7 +184,7 @@ export class ScenarioController {
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
findRuns(
@Param("id", ParseIntPipe) id: number,
@Param("id", ParseUUIDPipe) id: string,
@Query() query: RunsQueryDto,
) {
return this.scenarioService.findRuns(id, query);
@@ -194,7 +194,7 @@ export class ScenarioController {
@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", ParseIntPipe) id: number) {
createRun(@Param("id", ParseUUIDPipe) id: string) {
return this.scenarioService.createRun(id);
}
@@ -203,8 +203,8 @@ export class ScenarioController {
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or run not found" })
findRun(
@Param("id", ParseIntPipe) id: number,
@Param("runId", ParseIntPipe) runId: number,
@Param("id", ParseUUIDPipe) id: string,
@Param("runId", ParseUUIDPipe) runId: string,
) {
return this.scenarioService.findRun(id, runId);
}
@@ -218,8 +218,8 @@ export class ScenarioController {
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or run not found" })
waitForRun(
@Param("id", ParseIntPipe) id: number,
@Param("runId", ParseIntPipe) runId: number,
@Param("id", ParseUUIDPipe) id: string,
@Param("runId", ParseUUIDPipe) runId: string,
) {
return this.scenarioService.waitForRun(id, runId);
}