Problems: Allow HTML in description (#1559)

* Problems: allow HTML in problem description, closes #1557

* Don't replace new line with <br>

* Replace line brakes with <br> if allowDangerousHTML enabled

* List layout: only show html formatted description if option is enabled
This commit is contained in:
Alexander Zobnin
2023-01-20 15:11:12 +01:00
committed by GitHub
parent a5c239f77b
commit bc62e35477
6 changed files with 58 additions and 14 deletions

View File

@@ -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', '<br>');
}

View File

@@ -128,16 +128,26 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
)}
<span className="alert-rule-item__time">{panelOptions.ageField && 'for ' + age}</span>
{panelOptions.descriptionField && !panelOptions.descriptionAtNewLine && (
<span className="zbx-description" dangerouslySetInnerHTML={{ __html: problem.comments }} />
<>
{panelOptions.allowDangerousHTML ? (
<span className="zbx-description" dangerouslySetInnerHTML={{ __html: problem.comments }} />
) : (
<span className="zbx-description">{problem.comments}</span>
)}
</>
)}
</div>
{panelOptions.descriptionField && panelOptions.descriptionAtNewLine && (
<div className="alert-rule-item__text zbx-description--newline">
<span
className="alert-rule-item__info zbx-description"
dangerouslySetInnerHTML={{ __html: problem.comments }}
/>
{panelOptions.allowDangerousHTML ? (
<span
className="alert-rule-item__info zbx-description"
dangerouslySetInnerHTML={{ __html: problem.comments }}
/>
) : (
<span className="alert-rule-item__info zbx-description">{problem.comments}</span>
)}
</div>
)}
</div>

View File

@@ -25,6 +25,7 @@ interface Props extends RTRow<ProblemDTO> {
timeRange: TimeRange;
showTimeline?: boolean;
panelId?: number;
allowDangerousHTML?: boolean;
getProblemEvents: (problem: ProblemDTO) => Promise<ZBXEvent[]>;
getProblemAlerts: (problem: ProblemDTO) => Promise<ZBXAlert[]>;
getScripts: (problem: ProblemDTO) => Promise<ZBXScript[]>;
@@ -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 ? (
<span dangerouslySetInnerHTML={{ __html: problem.comments }} />
) : (
<span>{problem.comments}</span>
);
return (
<div className={`problem-details-container ${displayClass}`}>
@@ -162,22 +170,21 @@ export const ProblemDetails = ({
</div>
{problem.comments && (
<div className="problem-description-row">
<div className="problem-description">
<Tooltip placement="right" content={<span dangerouslySetInnerHTML={{ __html: problem.comments }} />}>
<div className={styles.problemDescription}>
<Tooltip placement="right" content={problemDescriptionEl}>
<span className="description-label">Description:&nbsp;</span>
</Tooltip>
{/* <span>{problem.comments}</span> */}
<span dangerouslySetInnerHTML={{ __html: problem.comments }} />
{problemDescriptionEl}
</div>
</div>
)}
{problem.items && (
<div className="problem-description-row">
<div>
<ProblemExpression problem={problem} />
</div>
)}
{problem.hosts && (
<div className="problem-description-row">
<div>
<ProblemHostsDescription hosts={problem.hosts} />
</div>
)}
@@ -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%);
}
`,
});

View File

@@ -245,6 +245,7 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
rootWidth={this.rootWidth}
timeRange={this.props.timeRange}
showTimeline={panelOptions.problemTimeline}
allowDangerousHTML={panelOptions.allowDangerousHTML}
panelId={this.props.panelId}
getProblemEvents={this.props.getProblemEvents}
getProblemAlerts={this.props.getProblemAlerts}

View File

@@ -90,6 +90,12 @@ export const plugin = new PanelPlugin<ProblemsPanelOptions, {}>(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',

View File

@@ -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<ProblemsPanelOptions> = {
customLastChangeFormat: false,
lastChangeFormat: '',
resizedColumns: [],
allowDangerousHTML: false,
// Triggers severity and colors
triggerSeverity: getDefaultSeverity(),
okEventColor: 'rgb(56, 189, 113)',