mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -08:00
Adds support for rules screen in react-ui (#6503)
* base Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * base of rules page Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * initial version Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * removed unused function Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * version 1 Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions. Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * new fetching pattern Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com>
This commit is contained in:
parent
d996ba20ec
commit
c1e49d50c5
|
@ -897,33 +897,39 @@ type RuleGroup struct {
|
||||||
// In order to preserve rule ordering, while exposing type (alerting or recording)
|
// In order to preserve rule ordering, while exposing type (alerting or recording)
|
||||||
// specific properties, both alerting and recording rules are exposed in the
|
// specific properties, both alerting and recording rules are exposed in the
|
||||||
// same array.
|
// same array.
|
||||||
Rules []rule `json:"rules"`
|
Rules []rule `json:"rules"`
|
||||||
Interval float64 `json:"interval"`
|
Interval float64 `json:"interval"`
|
||||||
|
EvaluationTime float64 `json:"evaluationTime"`
|
||||||
|
LastEvaluation time.Time `json:"lastEvaluation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rule interface{}
|
type rule interface{}
|
||||||
|
|
||||||
type alertingRule struct {
|
type alertingRule struct {
|
||||||
// State can be "pending", "firing", "inactive".
|
// State can be "pending", "firing", "inactive".
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Duration float64 `json:"duration"`
|
Duration float64 `json:"duration"`
|
||||||
Labels labels.Labels `json:"labels"`
|
Labels labels.Labels `json:"labels"`
|
||||||
Annotations labels.Labels `json:"annotations"`
|
Annotations labels.Labels `json:"annotations"`
|
||||||
Alerts []*Alert `json:"alerts"`
|
Alerts []*Alert `json:"alerts"`
|
||||||
Health rules.RuleHealth `json:"health"`
|
Health rules.RuleHealth `json:"health"`
|
||||||
LastError string `json:"lastError,omitempty"`
|
LastError string `json:"lastError,omitempty"`
|
||||||
|
EvaluationTime float64 `json:"evaluationTime"`
|
||||||
|
LastEvaluation time.Time `json:"lastEvaluation"`
|
||||||
// Type of an alertingRule is always "alerting".
|
// Type of an alertingRule is always "alerting".
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type recordingRule struct {
|
type recordingRule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Labels labels.Labels `json:"labels,omitempty"`
|
Labels labels.Labels `json:"labels,omitempty"`
|
||||||
Health rules.RuleHealth `json:"health"`
|
Health rules.RuleHealth `json:"health"`
|
||||||
LastError string `json:"lastError,omitempty"`
|
LastError string `json:"lastError,omitempty"`
|
||||||
|
EvaluationTime float64 `json:"evaluationTime"`
|
||||||
|
LastEvaluation time.Time `json:"lastEvaluation"`
|
||||||
// Type of a recordingRule is always "recording".
|
// Type of a recordingRule is always "recording".
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
@ -943,10 +949,12 @@ func (api *API) rules(r *http.Request) apiFuncResult {
|
||||||
|
|
||||||
for i, grp := range ruleGroups {
|
for i, grp := range ruleGroups {
|
||||||
apiRuleGroup := &RuleGroup{
|
apiRuleGroup := &RuleGroup{
|
||||||
Name: grp.Name(),
|
Name: grp.Name(),
|
||||||
File: grp.File(),
|
File: grp.File(),
|
||||||
Interval: grp.Interval().Seconds(),
|
Interval: grp.Interval().Seconds(),
|
||||||
Rules: []rule{},
|
Rules: []rule{},
|
||||||
|
EvaluationTime: grp.GetEvaluationDuration().Seconds(),
|
||||||
|
LastEvaluation: grp.GetEvaluationTimestamp(),
|
||||||
}
|
}
|
||||||
for _, r := range grp.Rules() {
|
for _, r := range grp.Rules() {
|
||||||
var enrichedRule rule
|
var enrichedRule rule
|
||||||
|
@ -961,28 +969,32 @@ func (api *API) rules(r *http.Request) apiFuncResult {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
enrichedRule = alertingRule{
|
enrichedRule = alertingRule{
|
||||||
State: rule.State().String(),
|
State: rule.State().String(),
|
||||||
Name: rule.Name(),
|
Name: rule.Name(),
|
||||||
Query: rule.Query().String(),
|
Query: rule.Query().String(),
|
||||||
Duration: rule.Duration().Seconds(),
|
Duration: rule.Duration().Seconds(),
|
||||||
Labels: rule.Labels(),
|
Labels: rule.Labels(),
|
||||||
Annotations: rule.Annotations(),
|
Annotations: rule.Annotations(),
|
||||||
Alerts: rulesAlertsToAPIAlerts(rule.ActiveAlerts()),
|
Alerts: rulesAlertsToAPIAlerts(rule.ActiveAlerts()),
|
||||||
Health: rule.Health(),
|
Health: rule.Health(),
|
||||||
LastError: lastError,
|
LastError: lastError,
|
||||||
Type: "alerting",
|
EvaluationTime: rule.GetEvaluationDuration().Seconds(),
|
||||||
|
LastEvaluation: rule.GetEvaluationTimestamp(),
|
||||||
|
Type: "alerting",
|
||||||
}
|
}
|
||||||
case *rules.RecordingRule:
|
case *rules.RecordingRule:
|
||||||
if !returnRecording {
|
if !returnRecording {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
enrichedRule = recordingRule{
|
enrichedRule = recordingRule{
|
||||||
Name: rule.Name(),
|
Name: rule.Name(),
|
||||||
Query: rule.Query().String(),
|
Query: rule.Query().String(),
|
||||||
Labels: rule.Labels(),
|
Labels: rule.Labels(),
|
||||||
Health: rule.Health(),
|
Health: rule.Health(),
|
||||||
LastError: lastError,
|
LastError: lastError,
|
||||||
Type: "recording",
|
EvaluationTime: rule.GetEvaluationDuration().Seconds(),
|
||||||
|
LastEvaluation: rule.GetEvaluationTimestamp(),
|
||||||
|
Type: "recording",
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err := errors.Errorf("failed to assert type of rule '%v'", rule.Name())
|
err := errors.Errorf("failed to assert type of rule '%v'", rule.Name())
|
||||||
|
|
|
@ -237,3 +237,14 @@ button.execute-btn {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
max-height: 20px;
|
max-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rules-head {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule_cell {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
display: block;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
|
@ -3,21 +3,10 @@ import { Badge } from 'reactstrap';
|
||||||
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
|
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
|
||||||
import Checkbox from '../../components/Checkbox';
|
import Checkbox from '../../components/Checkbox';
|
||||||
import { isPresent } from '../../utils';
|
import { isPresent } from '../../utils';
|
||||||
|
import { Rule } from '../../types/types';
|
||||||
|
|
||||||
export type RuleState = keyof RuleStatus<any>;
|
export type RuleState = keyof RuleStatus<any>;
|
||||||
|
|
||||||
export interface Rule {
|
|
||||||
alerts: Alert[];
|
|
||||||
annotations: Record<string, string>;
|
|
||||||
duration: number;
|
|
||||||
health: string;
|
|
||||||
labels: Record<string, string>;
|
|
||||||
name: string;
|
|
||||||
query: string;
|
|
||||||
state: RuleState;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RuleStatus<T> {
|
export interface RuleStatus<T> {
|
||||||
firing: T;
|
firing: T;
|
||||||
pending: T;
|
pending: T;
|
||||||
|
@ -29,7 +18,7 @@ export interface AlertsProps {
|
||||||
statsCount: RuleStatus<number>;
|
statsCount: RuleStatus<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Alert {
|
export interface Alert {
|
||||||
labels: Record<string, string>;
|
labels: Record<string, string>;
|
||||||
state: RuleState;
|
state: RuleState;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import React, { FC, useState, Fragment } from 'react';
|
import React, { FC, useState, Fragment } from 'react';
|
||||||
import { Link } from '@reach/router';
|
import { Link } from '@reach/router';
|
||||||
import { Alert, Collapse, Table, Badge } from 'reactstrap';
|
import { Alert, Collapse, Table, Badge } from 'reactstrap';
|
||||||
import { Rule, RuleStatus } from './AlertContents';
|
import { RuleStatus } from './AlertContents';
|
||||||
|
import { Rule } from '../../types/types';
|
||||||
import { faChevronDown, faChevronRight } from '@fortawesome/free-solid-svg-icons';
|
import { faChevronDown, faChevronRight } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { createExpressionLink } from '../../utils/index';
|
||||||
|
|
||||||
interface CollapsibleAlertPanelProps {
|
interface CollapsibleAlertPanelProps {
|
||||||
rule: Rule;
|
rule: Rule;
|
||||||
|
@ -16,10 +18,6 @@ const alertColors: RuleStatus<string> = {
|
||||||
inactive: 'success',
|
inactive: 'success',
|
||||||
};
|
};
|
||||||
|
|
||||||
const createExpressionLink = (expr: string) => {
|
|
||||||
return `../graph?g0.expr=${encodeURIComponent(expr)}&g0.tab=1&g0.stacked=0&g0.range_input=1h`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CollapsibleAlertPanel: FC<CollapsibleAlertPanelProps> = ({ rule, showAnnotations }) => {
|
const CollapsibleAlertPanel: FC<CollapsibleAlertPanelProps> = ({ rule, showAnnotations }) => {
|
||||||
const [open, toggle] = useState(false);
|
const [open, toggle] = useState(false);
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { RouteComponentProps } from '@reach/router';
|
import { RouteComponentProps } from '@reach/router';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
import PathPrefixProps from '../../types/PathPrefixProps';
|
||||||
import { Alert } from 'reactstrap';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
|
import { RulesMap, RulesContent } from './RulesContent';
|
||||||
|
|
||||||
const Rules: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => (
|
const RulesWithStatusIndicator = withStatusIndicator(RulesContent);
|
||||||
<>
|
|
||||||
<h2>Rules</h2>
|
const Rules: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
||||||
<Alert color="warning">
|
const { response, error, isLoading } = useFetch<RulesMap>(`${pathPrefix}/api/v1/rules`);
|
||||||
This page is still under construction. Please try it in the <a href={`${pathPrefix}/rules`}>Classic UI</a>.
|
|
||||||
</Alert>
|
return <RulesWithStatusIndicator response={response} error={error} isLoading={isLoading} />;
|
||||||
</>
|
};
|
||||||
);
|
|
||||||
|
|
||||||
export default Rules;
|
export default Rules;
|
||||||
|
|
131
web/ui/react-app/src/pages/rules/RulesContent.tsx
Normal file
131
web/ui/react-app/src/pages/rules/RulesContent.tsx
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { RouteComponentProps } from '@reach/router';
|
||||||
|
import { APIResponse } from '../../hooks/useFetch';
|
||||||
|
import { Alert, Table, Badge } from 'reactstrap';
|
||||||
|
import { Link } from '@reach/router';
|
||||||
|
import { formatRelative, createExpressionLink, humanizeDuration } from '../../utils';
|
||||||
|
import { Rule } from '../../types/types';
|
||||||
|
import { now } from 'moment';
|
||||||
|
|
||||||
|
interface RulesContentProps {
|
||||||
|
response: APIResponse<RulesMap>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RuleGroup {
|
||||||
|
name: string;
|
||||||
|
file: string;
|
||||||
|
rules: Rule[];
|
||||||
|
evaluationTime: string;
|
||||||
|
lastEvaluation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RulesMap {
|
||||||
|
groups: RuleGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const GraphExpressionLink: FC<{ expr: string; title: string }> = props => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<strong>{props.title}:</strong>
|
||||||
|
<Link className="ml-4" to={createExpressionLink(props.expr)}>
|
||||||
|
{props.expr}
|
||||||
|
</Link>
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RulesContent: FC<RouteComponentProps & RulesContentProps> = ({ response }) => {
|
||||||
|
const getBadgeColor = (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'ok':
|
||||||
|
return 'success';
|
||||||
|
|
||||||
|
case 'err':
|
||||||
|
return 'danger';
|
||||||
|
|
||||||
|
case 'unknown':
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (response.data) {
|
||||||
|
const groups: RuleGroup[] = response.data.groups;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2>Rules</h2>
|
||||||
|
{groups.map((g, i) => {
|
||||||
|
return (
|
||||||
|
<Table bordered key={i}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colSpan={3}>
|
||||||
|
<a href={'#' + g.name}>
|
||||||
|
<h2 id={g.name}>{g.name}</h2>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h2>{formatRelative(g.lastEvaluation, now())} ago</h2>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h2>{humanizeDuration(parseFloat(g.evaluationTime) * 1000)}</h2>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr className="font-weight-bold">
|
||||||
|
<td>Rule</td>
|
||||||
|
<td>State</td>
|
||||||
|
<td>Error</td>
|
||||||
|
<td>Last Evaluation</td>
|
||||||
|
<td>Evaluation Time</td>
|
||||||
|
</tr>
|
||||||
|
{g.rules.map((r, i) => {
|
||||||
|
return (
|
||||||
|
<tr key={i}>
|
||||||
|
{r.alerts ? (
|
||||||
|
<td style={{ backgroundColor: '#F5F5F5' }} className="rule_cell">
|
||||||
|
<GraphExpressionLink title="alert" expr={r.name} />
|
||||||
|
<GraphExpressionLink title="expr" expr={r.query} />
|
||||||
|
<div>
|
||||||
|
<strong>labels:</strong>
|
||||||
|
{Object.entries(r.labels).map(([key, value]) => (
|
||||||
|
<div className="ml-4" key={key}>
|
||||||
|
{key}: {value}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>annotations:</strong>
|
||||||
|
{Object.entries(r.annotations).map(([key, value]) => (
|
||||||
|
<div className="ml-4" key={key}>
|
||||||
|
{key}: {value}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
) : (
|
||||||
|
<td style={{ backgroundColor: '#F5F5F5' }}>
|
||||||
|
<GraphExpressionLink title="record" expr={r.name} />
|
||||||
|
<GraphExpressionLink title="expr" expr={r.query} />
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td>
|
||||||
|
<Badge color={getBadgeColor(r.health)}>{r.health.toUpperCase()}</Badge>
|
||||||
|
</td>
|
||||||
|
<td>{r.lastError ? <Alert color="danger">{r.lastError}</Alert> : null}</td>
|
||||||
|
<td>{formatRelative(r.lastEvaluation, now())} ago</td>
|
||||||
|
<td>{humanizeDuration(parseFloat(r.evaluationTime) * 1000)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Alert, RuleState } from '../pages/alerts/AlertContents';
|
||||||
|
|
||||||
export interface Metric {
|
export interface Metric {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
@ -7,3 +9,18 @@ export interface QueryParams {
|
||||||
endTime: number;
|
endTime: number;
|
||||||
resolution: number;
|
resolution: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Rule {
|
||||||
|
alerts: Alert[];
|
||||||
|
annotations: Record<string, string>;
|
||||||
|
duration: number;
|
||||||
|
evaluationTime: string;
|
||||||
|
health: string;
|
||||||
|
labels: Record<string, string>;
|
||||||
|
lastError?: string;
|
||||||
|
lastEvaluation: string;
|
||||||
|
name: string;
|
||||||
|
query: string;
|
||||||
|
state: RuleState;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
|
@ -197,3 +197,7 @@ export const toQueryString = ({ key, options }: PanelMeta) => {
|
||||||
export const encodePanelOptionsToQueryString = (panels: PanelMeta[]) => {
|
export const encodePanelOptionsToQueryString = (panels: PanelMeta[]) => {
|
||||||
return `?${panels.map(toQueryString).join('&')}`;
|
return `?${panels.map(toQueryString).join('&')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createExpressionLink = (expr: string) => {
|
||||||
|
return `../graph?g0.expr=${encodeURIComponent(expr)}&g0.tab=1&g0.stacked=0&g0.range_input=1h`;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue