From bc62e354774db45bfffbddde7f034e6ba83ce9ec Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 20 Jan 2023 15:11:12 +0100 Subject: [PATCH] Problems: Allow HTML in description (#1559) * Problems: allow HTML in problem description, closes #1557 * Don't replace new line with
* Replace line brakes with
if allowDangerousHTML enabled * List layout: only show html formatted description if option is enabled --- src/panel-triggers/ProblemsPanel.tsx | 2 +- .../components/AlertList/AlertCard.tsx | 20 ++++++--- .../components/Problems/ProblemDetails.tsx | 41 +++++++++++++++---- .../components/Problems/Problems.tsx | 1 + src/panel-triggers/module.tsx | 6 +++ src/panel-triggers/types.ts | 2 + 6 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/panel-triggers/ProblemsPanel.tsx b/src/panel-triggers/ProblemsPanel.tsx index 8588881..0eb4eca 100644 --- a/src/panel-triggers/ProblemsPanel.tsx +++ b/src/panel-triggers/ProblemsPanel.tsx @@ -96,7 +96,7 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => { } // Handle multi-line description - if (trigger.comments) { + if (trigger.comments && options.allowDangerousHTML) { trigger.comments = trigger.comments.replace('\n', '
'); } diff --git a/src/panel-triggers/components/AlertList/AlertCard.tsx b/src/panel-triggers/components/AlertList/AlertCard.tsx index adbbf8c..3f6e5f1 100644 --- a/src/panel-triggers/components/AlertList/AlertCard.tsx +++ b/src/panel-triggers/components/AlertList/AlertCard.tsx @@ -128,16 +128,26 @@ export default class AlertCard extends PureComponent { )} {panelOptions.ageField && 'for ' + age} {panelOptions.descriptionField && !panelOptions.descriptionAtNewLine && ( - + <> + {panelOptions.allowDangerousHTML ? ( + + ) : ( + {problem.comments} + )} + )} {panelOptions.descriptionField && panelOptions.descriptionAtNewLine && (
- + {panelOptions.allowDangerousHTML ? ( + + ) : ( + {problem.comments} + )}
)} diff --git a/src/panel-triggers/components/Problems/ProblemDetails.tsx b/src/panel-triggers/components/Problems/ProblemDetails.tsx index 90e5b3f..adcb000 100644 --- a/src/panel-triggers/components/Problems/ProblemDetails.tsx +++ b/src/panel-triggers/components/Problems/ProblemDetails.tsx @@ -25,6 +25,7 @@ interface Props extends RTRow { timeRange: TimeRange; showTimeline?: boolean; panelId?: number; + allowDangerousHTML?: boolean; getProblemEvents: (problem: ProblemDTO) => Promise; getProblemAlerts: (problem: ProblemDTO) => Promise; getScripts: (problem: ProblemDTO) => Promise; @@ -39,6 +40,7 @@ export const ProblemDetails = ({ timeRange, showTimeline, panelId, + allowDangerousHTML, getProblemAlerts, getProblemEvents, getScripts, @@ -100,13 +102,19 @@ export const ProblemDetails = ({ const age = moment.unix(problem.timestamp).fromNow(true); const showAcknowledges = problem.acknowledges && problem.acknowledges.length !== 0; const problemSeverity = Number(problem.severity); + const styles = useStyles2(getStyles); let dsName: string = original.datasource as string; if ((original.datasource as DataSourceRef)?.uid) { const dsInstance = getDataSourceSrv().getInstanceSettings((original.datasource as DataSourceRef).uid); dsName = dsInstance.name; } - const styles = useStyles2(getStyles); + + const problemDescriptionEl = allowDangerousHTML ? ( + + ) : ( + {problem.comments} + ); return (
@@ -162,22 +170,21 @@ export const ProblemDetails = ({
{problem.comments && (
-
- }> +
+ Description:  - {/* {problem.comments} */} - + {problemDescriptionEl}
)} {problem.items && ( -
+
)} {problem.hosts && ( -
+
)} @@ -239,9 +246,27 @@ const getStyles = (theme: GrafanaTheme2) => ({ position: relative; flex: 10 1 auto; // padding: 0.5rem 1rem 0.5rem 1.2rem; - padding: ${theme.spacing(0.5)} ${theme.spacing(1)} ${theme.spacing(0.5)} ${theme.spacing(1.2)} + padding: ${theme.spacing(0.5)} ${theme.spacing(1)} ${theme.spacing(0.5)} ${theme.spacing(1.2)}; display: flex; flex-direction: column; // white-space: pre-line; + font-size: ${theme.typography.bodySmall.fontSize}; + `, + problemDescription: css` + position: relative; + max-height: 6rem; + min-height: 3rem; + overflow: hidden; + + &:after { + content: ''; + text-align: right; + position: inherit; + bottom: 0; + right: 0; + width: 70%; + height: 1.5rem; + background: linear-gradient(to right, rgba(0, 0, 0, 0), ${theme.colors.background.canvas} 50%); + } `, }); diff --git a/src/panel-triggers/components/Problems/Problems.tsx b/src/panel-triggers/components/Problems/Problems.tsx index 45955b0..ae275fc 100644 --- a/src/panel-triggers/components/Problems/Problems.tsx +++ b/src/panel-triggers/components/Problems/Problems.tsx @@ -245,6 +245,7 @@ export default class ProblemList extends PureComponent(ProblemsPanel) }, showIf: (options) => options.customLastChangeFormat, }) + .addBooleanSwitch({ + path: 'allowDangerousHTML', + name: 'Allow HTML', + description: `Format problem description and other data as HTML. Use with caution, it's potential cross-site scripting (XSS) vulnerability.`, + defaultValue: defaultPanelOptions.allowDangerousHTML, + }) .addCustomEditor({ id: 'resetColumns', path: 'resizedColumns', diff --git a/src/panel-triggers/types.ts b/src/panel-triggers/types.ts index 3ef6493..7951b0c 100644 --- a/src/panel-triggers/types.ts +++ b/src/panel-triggers/types.ts @@ -34,6 +34,7 @@ export interface ProblemsPanelOptions { customLastChangeFormat?: boolean; lastChangeFormat?: string; resizedColumns?: RTResized; + allowDangerousHTML: boolean; // Triggers severity and colors triggerSeverity: TriggerSeverity[]; okEventColor: TriggerColor; @@ -81,6 +82,7 @@ export const defaultPanelOptions: Partial = { customLastChangeFormat: false, lastChangeFormat: '', resizedColumns: [], + allowDangerousHTML: false, // Triggers severity and colors triggerSeverity: getDefaultSeverity(), okEventColor: 'rgb(56, 189, 113)',