Compare commits

...

1 Commits

Author SHA1 Message Date
Tim Levett 99639cf229 Stored Notifications: Add copy button 2026-01-12 15:42:40 -06:00
2 changed files with 60 additions and 2 deletions
@@ -1,9 +1,10 @@
import { css } from '@emotion/css';
import { formatDistanceToNow } from 'date-fns';
import { ReactNode } from 'react';
import { ReactNode, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Card, Checkbox, useTheme2 } from '@grafana/ui';
import { t } from '@grafana/i18n';
import { Card, Checkbox, IconButton, Tooltip, useTheme2 } from '@grafana/ui';
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
@@ -14,6 +15,7 @@ export interface Props {
onClick: () => void;
severity?: AlertVariant;
title: string;
text?: string;
timestamp?: number;
traceId?: string;
}
@@ -25,11 +27,51 @@ export const StoredNotificationItem = ({
onClick,
severity = 'error',
title,
text,
traceId,
timestamp,
}: Props) => {
const theme = useTheme2();
const styles = getStyles(theme);
const [hasCopied, setHasCopied] = useState(false);
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation();
const parts: string[] = [];
parts.push(`Title: ${title}`);
if (text) {
parts.push(`Message: ${text}`);
}
if (traceId) {
parts.push(`Trace ID: ${traceId}`);
}
if (timestamp) {
const date = new Date(timestamp);
parts.push(`Timestamp: ${date.toISOString()}`);
}
const textToCopy = parts.join('\n');
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(textToCopy);
} else {
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = textToCopy;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
setHasCopied(true);
setTimeout(() => setHasCopied(false), 2000);
} catch (err) {
console.error('Failed to copy text:', err);
}
};
return (
<Card noMargin className={className} onClick={onClick}>
@@ -42,6 +84,21 @@ export const StoredNotificationItem = ({
{traceId && <span>{`Trace ID: ${traceId}`}</span>}
{timestamp && formatDistanceToNow(timestamp, { addSuffix: true })}
</Card.Tags>
<Card.SecondaryActions>
<Tooltip
content={
hasCopied
? t('notifications.stored-notification-item.copied', 'Copied')
: t('notifications.stored-notification-item.copy-to-clipboard', 'Copy to clipboard')
}
>
<IconButton
name="copy"
onClick={handleCopy}
aria-label={t('notifications.stored-notification-item.copy-to-clipboard', 'Copy to clipboard')}
/>
</Tooltip>
</Card.SecondaryActions>
</Card>
);
};
@@ -90,6 +90,7 @@ export function StoredNotifications() {
onClick={() => handleCheckboxToggle(notif.id)}
severity={notif.severity}
title={notif.title}
text={notif.text}
timestamp={notif.timestamp}
traceId={notif.traceId}
>