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:
@@ -0,0 +1,38 @@
|
||||
.wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.button:focus-visible {
|
||||
outline: 2px solid var(--color-focus-ring);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.pop {
|
||||
position: absolute;
|
||||
left: calc(100% + 4px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--color-success-fg);
|
||||
animation: popFade 1s ease forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes popFade {
|
||||
0% { opacity: 0; transform: translateY(calc(-50% - 4px)); }
|
||||
20% { opacity: 1; transform: translateY(-50%); }
|
||||
70% { opacity: 1; transform: translateY(-50%); }
|
||||
100% { opacity: 0; transform: translateY(calc(-50% - 4px)); }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useState } from 'react';
|
||||
import { ClipboardCheck } from 'lucide-react';
|
||||
import { Badge } from '../Badge/Badge';
|
||||
import styles from './UuidBadge.module.css';
|
||||
|
||||
export interface UuidBadgeProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function UuidBadge({ id }: UuidBadgeProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const short = `${id.slice(0, 4)}…${id.slice(-4)}`;
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
navigator.clipboard.writeText(id).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={styles.wrapper} title={copied ? 'Copied!' : id}>
|
||||
<Badge variant="neutral">
|
||||
<button type="button" className={styles.button} onClick={handleClick}>
|
||||
{short}
|
||||
</button>
|
||||
</Badge>
|
||||
{copied && (
|
||||
<span className={styles.pop} aria-hidden="true">
|
||||
<ClipboardCheck size={13} />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -44,3 +44,6 @@ export type { NotificationProps, NotificationVariant } from './Notification/Noti
|
||||
|
||||
export { Table } from './Table/Table';
|
||||
export type { TableProps, TableColumn } from './Table/Table';
|
||||
|
||||
export { UuidBadge } from './UuidBadge/UuidBadge';
|
||||
export type { UuidBadgeProps } from './UuidBadge/UuidBadge';
|
||||
|
||||
Reference in New Issue
Block a user