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
@@ -135,7 +135,7 @@ export class McpService {
{
description: "Delete a session by numeric ID (closes it first if open)",
inputSchema: {
id: z.number().int().describe("Session ID to delete"),
id: z.string().uuid().describe("Session ID to delete"),
},
},
async ({ id }) => {
@@ -200,7 +200,7 @@ export class McpService {
{
description: "Get an environment record by ID",
inputSchema: {
id: z.number().int().describe("Environment ID"),
id: z.string().uuid().describe("Environment ID"),
},
},
async ({ id }) => {
@@ -258,7 +258,7 @@ export class McpService {
{
description: "Update an existing environment (name and/or urls)",
inputSchema: {
id: z.number().int().describe("Environment ID to update"),
id: z.string().uuid().describe("Environment ID to update"),
name: z.string().optional().describe("New name"),
urls: z
.record(z.string(), z.string())
@@ -289,7 +289,7 @@ export class McpService {
{
description: "Delete an environment by ID",
inputSchema: {
id: z.number().int().describe("Environment ID to delete"),
id: z.string().uuid().describe("Environment ID to delete"),
},
},
async ({ id }) => {
@@ -443,7 +443,7 @@ export class McpService {
{
description: "Get a scenario with its steps by ID",
inputSchema: {
id: z.number().int().describe("Scenario ID"),
id: z.string().uuid().describe("Scenario ID"),
},
},
async ({ id }) => {
@@ -493,7 +493,7 @@ export class McpService {
{
description: "Update a scenario name",
inputSchema: {
id: z.number().int().describe("Scenario ID"),
id: z.string().uuid().describe("Scenario ID"),
name: z.string().optional().describe("New name"),
},
},
@@ -519,7 +519,7 @@ export class McpService {
{
description: "Delete a scenario by ID",
inputSchema: {
id: z.number().int().describe("Scenario ID"),
id: z.string().uuid().describe("Scenario ID"),
},
},
async ({ id }) => {
@@ -544,7 +544,7 @@ export class McpService {
{
description: "Add a step to a scenario",
inputSchema: {
scenarioId: z.number().int().describe("Parent scenario ID"),
scenarioId: z.string().uuid().describe("Parent scenario ID"),
order: z
.number()
.int()
@@ -582,8 +582,8 @@ export class McpService {
{
description: "Get a single step of a scenario",
inputSchema: {
scenarioId: z.number().int().describe("Scenario ID"),
stepId: z.number().int().describe("Step ID"),
scenarioId: z.string().uuid().describe("Scenario ID"),
stepId: z.string().uuid().describe("Step ID"),
},
},
async ({ scenarioId, stepId }) => {
@@ -606,8 +606,8 @@ export class McpService {
{
description: "Update a step within a scenario",
inputSchema: {
scenarioId: z.number().int().describe("Scenario ID"),
stepId: z.number().int().describe("Step ID"),
scenarioId: z.string().uuid().describe("Scenario ID"),
stepId: z.string().uuid().describe("Step ID"),
order: z
.number()
.int()
@@ -647,8 +647,8 @@ export class McpService {
{
description: "Delete a step from a scenario",
inputSchema: {
scenarioId: z.number().int().describe("Scenario ID"),
stepId: z.number().int().describe("Step ID"),
scenarioId: z.string().uuid().describe("Scenario ID"),
stepId: z.string().uuid().describe("Step ID"),
},
},
async ({ scenarioId, stepId }) => {
@@ -677,7 +677,7 @@ export class McpService {
description:
"List runs for a scenario (paginated, optionally filtered by status)",
inputSchema: {
scenarioId: z.number().int().describe("Scenario ID"),
scenarioId: z.string().uuid().describe("Scenario ID"),
status: z
.enum(["pending", "in_progress", "pass", "fail"])
.optional()
@@ -720,7 +720,7 @@ export class McpService {
{
description: "Trigger an immediate run of a scenario by ID",
inputSchema: {
id: z.number().int().describe("Scenario ID to run"),
id: z.string().uuid().describe("Scenario ID to run"),
},
},
async ({ id }) => {
@@ -744,8 +744,8 @@ export class McpService {
description:
"Get a specific scenario run with all step runs and their outputs",
inputSchema: {
scenarioId: z.number().int().describe("Scenario ID"),
runId: z.number().int().describe("Run ID"),
scenarioId: z.string().uuid().describe("Scenario ID"),
runId: z.string().uuid().describe("Run ID"),
},
},
async ({ scenarioId, runId }) => {
@@ -769,8 +769,8 @@ export class McpService {
description:
"Block until a scenario run reaches pass or fail (max 5 min), then return the full run with step outputs",
inputSchema: {
scenarioId: z.number().int().describe("Scenario ID"),
runId: z.number().int().describe("Run ID"),
scenarioId: z.string().uuid().describe("Scenario ID"),
runId: z.string().uuid().describe("Run ID"),
},
},
async ({ scenarioId, runId }) => {
@@ -794,7 +794,7 @@ export class McpService {
description:
"Export a scenario as a portable JSON payload (name + steps)",
inputSchema: {
id: z.number().int().describe("Scenario ID to export"),
id: z.string().uuid().describe("Scenario ID to export"),
},
},
async ({ id }) => {
@@ -826,7 +826,7 @@ export class McpService {
z.object({
order: z.number().int().min(0).describe("Execution order"),
type: z.enum(["login", "exec", "sign"]).describe("Step type"),
sessionName: z.string().describe("Session name"),
title: z.string().nullable().optional().describe("Step title"),
execCode: z
.string()
.nullable()