mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
feat(ui): allow configuring default number of alerts to display per group
This commit is contained in:
@@ -52,7 +52,10 @@ class App extends Component {
|
||||
settingsStore={this.settingsStore}
|
||||
/>
|
||||
<Provider alertStore={this.alertStore}>
|
||||
<Grid alertStore={this.alertStore} />
|
||||
<Grid
|
||||
alertStore={this.alertStore}
|
||||
settingsStore={this.settingsStore}
|
||||
/>
|
||||
</Provider>
|
||||
<Fetcher
|
||||
alertStore={this.alertStore}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, action } from "mobx";
|
||||
import { observable, action, toJS } from "mobx";
|
||||
|
||||
import hash from "object-hash";
|
||||
|
||||
@@ -12,31 +12,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
|
||||
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { GroupHeader } from "./GroupHeader";
|
||||
import { Alert } from "./Alert";
|
||||
import { GroupFooter } from "./GroupFooter";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const initialAlertsToRender = 5;
|
||||
|
||||
// Used to calculate step size when loading more alerts.
|
||||
// Step is calculated from the excesive alert count
|
||||
// (what's > initialAlertsToRender) by dividing it into 5 clicks.
|
||||
// Don't use step lower than 5, too much clicking if we have a group of 9:
|
||||
// * we'll show initially 5
|
||||
// * step would be 1
|
||||
// * 4 extra clicks to see the entire group
|
||||
// but ensure that step wouldn't push us above totalSize
|
||||
// With 9 alerts and rendering 5 initially we want to show extra 9 after one
|
||||
// click, and when user clicks showLess we want to go back to 5.
|
||||
function getStepSize(totalSize) {
|
||||
return Math.min(
|
||||
Math.max(Math.round((totalSize - initialAlertsToRender) / 5), 5),
|
||||
totalSize - initialAlertsToRender
|
||||
);
|
||||
}
|
||||
|
||||
const LoadButton = ({ icon, action }) => {
|
||||
return (
|
||||
<button type="button" className="btn btn-sm py-0 bg-white" onClick={action}>
|
||||
@@ -54,9 +36,22 @@ const AlertGroup = observer(
|
||||
static propTypes = {
|
||||
afterUpdate: PropTypes.func.isRequired,
|
||||
group: PropTypes.object.isRequired,
|
||||
showAlertmanagers: PropTypes.bool.isRequired
|
||||
showAlertmanagers: PropTypes.bool.isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.defaultRenderCount = toJS(
|
||||
props.settingsStore.alertGroupConfig.config.defaultRenderCount
|
||||
);
|
||||
|
||||
this.renderConfig = observable({
|
||||
alertsToRender: this.defaultRenderCount
|
||||
});
|
||||
}
|
||||
|
||||
// store collapse state, alert groups can be collapsed to only show
|
||||
// the header, this is controlled by UI element on the header itself, so
|
||||
// this observable needs to be passed down to it
|
||||
@@ -73,14 +68,10 @@ const AlertGroup = observer(
|
||||
{ name: "Collpase toggle" }
|
||||
);
|
||||
|
||||
renderConfig = observable({
|
||||
alertsToRender: initialAlertsToRender
|
||||
});
|
||||
|
||||
loadMore = action(() => {
|
||||
const { group } = this.props;
|
||||
|
||||
const step = getStepSize(group.alerts.length);
|
||||
const step = this.getStepSize(group.alerts.length);
|
||||
|
||||
// show cur+step, but not more that total alert count
|
||||
this.renderConfig.alertsToRender = Math.min(
|
||||
@@ -92,7 +83,7 @@ const AlertGroup = observer(
|
||||
loadLess = action(() => {
|
||||
const { group } = this.props;
|
||||
|
||||
const step = getStepSize(group.alerts.length);
|
||||
const step = this.getStepSize(group.alerts.length);
|
||||
|
||||
// show cur-step, but not less than 1
|
||||
this.renderConfig.alertsToRender = Math.max(
|
||||
@@ -101,6 +92,25 @@ const AlertGroup = observer(
|
||||
);
|
||||
});
|
||||
|
||||
// Used to calculate step size when loading more alerts.
|
||||
// Step is calculated from the excesive alert count
|
||||
// (what's > defaultRenderCount) by dividing it into 5 clicks.
|
||||
// Don't use step lower than 5, too much clicking if we have a group of 9:
|
||||
// * we'll show initially 5
|
||||
// * step would be 1
|
||||
// * 4 extra clicks to see the entire group
|
||||
// but ensure that step wouldn't push us above totalSize
|
||||
// With 9 alerts and rendering 5 initially we want to show extra 9 after one
|
||||
// click, and when user clicks showLess we want to go back to 5.
|
||||
getStepSize(totalSize) {
|
||||
const val = Math.min(
|
||||
Math.max(Math.round((totalSize - this.defaultRenderCount) / 5), 5),
|
||||
totalSize - this.defaultRenderCount
|
||||
);
|
||||
console.info("getStepSize => " + val);
|
||||
return val;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// whenever grid component re-renders we need to ensure that grid elements
|
||||
// are packed correctly
|
||||
@@ -158,7 +168,7 @@ const AlertGroup = observer(
|
||||
afterUpdate={afterUpdate}
|
||||
/>
|
||||
))}
|
||||
{group.alerts.length > initialAlertsToRender ? (
|
||||
{group.alerts.length > this.defaultRenderCount ? (
|
||||
<li className="list-group-item border-0 p-0 text-center">
|
||||
<LoadButton icon={faMinus} action={this.loadLess} />
|
||||
<small className="text-muted mx-2">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { AlertGroup } from "./AlertGroup";
|
||||
import { GridSizesConfig } from "./Constants";
|
||||
|
||||
@@ -18,7 +19,8 @@ import "./index.css";
|
||||
const AlertGrid = observer(
|
||||
class AlertGrid extends Component {
|
||||
static propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired
|
||||
};
|
||||
|
||||
// store reference to generated masonry component so we can call it
|
||||
@@ -70,7 +72,7 @@ const AlertGrid = observer(
|
||||
}
|
||||
|
||||
render() {
|
||||
const { alertStore } = this.props;
|
||||
const { alertStore, settingsStore } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -102,6 +104,7 @@ const AlertGrid = observer(
|
||||
alertStore.data.upstreams.instances.length > 1
|
||||
}
|
||||
afterUpdate={this.masonryRepack}
|
||||
settingsStore={settingsStore}
|
||||
/>
|
||||
))}
|
||||
</MasonryInfiniteScroller>
|
||||
|
||||
@@ -4,6 +4,7 @@ import PropTypes from "prop-types";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { AlertGrid } from "./AlertGrid";
|
||||
import { FatalError } from "./FatalError";
|
||||
import { UpstreamError } from "./UpstreamError";
|
||||
@@ -11,11 +12,12 @@ import { UpstreamError } from "./UpstreamError";
|
||||
const Grid = observer(
|
||||
class Grid extends Component {
|
||||
static propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { alertStore } = this.props;
|
||||
const { alertStore, settingsStore } = this.props;
|
||||
|
||||
if (alertStore.status.error) {
|
||||
return <FatalError message={alertStore.status.error} />;
|
||||
@@ -33,7 +35,7 @@ const Grid = observer(
|
||||
/>
|
||||
))}
|
||||
|
||||
<AlertGrid alertStore={alertStore} />
|
||||
<AlertGrid alertStore={alertStore} settingsStore={settingsStore} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observable, action, toJS } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import InputRange from "react-input-range";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
import "./InputRange.css";
|
||||
|
||||
const AlertGroupConfiguration = observer(
|
||||
class AlertGroupConfiguration extends Component {
|
||||
static propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.config = observable({
|
||||
defaultRenderCount: toJS(
|
||||
props.settingsStore.alertGroupConfig.config.defaultRenderCount
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
onChange = action(value => {
|
||||
this.config.defaultRenderCount = value;
|
||||
});
|
||||
|
||||
onChangeComplete = action(value => {
|
||||
const { settingsStore } = this.props;
|
||||
|
||||
settingsStore.alertGroupConfig.update({ defaultRenderCount: value });
|
||||
});
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="form-group text-center">
|
||||
<label className="mb-4">
|
||||
Default number of alerts to show per group
|
||||
</label>
|
||||
<InputRange
|
||||
minValue={1}
|
||||
maxValue={10}
|
||||
step={1}
|
||||
value={this.config.defaultRenderCount}
|
||||
id="formControlRange"
|
||||
formatLabel={this.formatLabel}
|
||||
onChange={this.onChange}
|
||||
onChangeComplete={this.onChangeComplete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export { AlertGroupConfiguration };
|
||||
@@ -8,10 +8,10 @@ import InputRange from "react-input-range";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
import "./Configuration.css";
|
||||
import "./InputRange.css";
|
||||
|
||||
const Configuration = observer(
|
||||
class Configuration extends Component {
|
||||
const FetchConfiguration = observer(
|
||||
class FetchConfiguration extends Component {
|
||||
static propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired
|
||||
};
|
||||
@@ -40,24 +40,22 @@ const Configuration = observer(
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form className="px-3">
|
||||
<div className="form-group text-center">
|
||||
<label className="mb-4">Refresh interval</label>
|
||||
<InputRange
|
||||
minValue={10}
|
||||
maxValue={120}
|
||||
step={10}
|
||||
value={this.config.fetchInterval}
|
||||
id="formControlRange"
|
||||
formatLabel={this.formatLabel}
|
||||
onChange={this.onChange}
|
||||
onChangeComplete={this.onChangeComplete}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div className="form-group text-center">
|
||||
<label className="mb-4">Refresh interval</label>
|
||||
<InputRange
|
||||
minValue={10}
|
||||
maxValue={120}
|
||||
step={10}
|
||||
value={this.config.fetchInterval}
|
||||
id="formControlRange"
|
||||
formatLabel={this.formatLabel}
|
||||
onChange={this.onChange}
|
||||
onChangeComplete={this.onChangeComplete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export { Configuration };
|
||||
export { FetchConfiguration };
|
||||
19
ui/src/Components/MainModal/Configuration/index.js
Normal file
19
ui/src/Components/MainModal/Configuration/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { FetchConfiguration } from "./FetchConfiguration";
|
||||
import { AlertGroupConfiguration } from "./AlertGroupConfiguration";
|
||||
|
||||
const Configuration = ({ settingsStore }) => (
|
||||
<form className="px-3">
|
||||
<FetchConfiguration settingsStore={settingsStore} />
|
||||
<div className="mt-5" />
|
||||
<AlertGroupConfiguration settingsStore={settingsStore} />
|
||||
</form>
|
||||
);
|
||||
Configuration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired
|
||||
};
|
||||
|
||||
export { Configuration };
|
||||
@@ -32,10 +32,25 @@ class FetchConfig {
|
||||
});
|
||||
}
|
||||
|
||||
class AlertGroupConfig {
|
||||
config = localStored(
|
||||
"alertGroupConfig",
|
||||
{ defaultRenderCount: 5 },
|
||||
{ delay: 100 }
|
||||
);
|
||||
|
||||
update = action(data => {
|
||||
for (const [key, val] of Object.entries(data)) {
|
||||
this.config[key] = val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Settings {
|
||||
constructor() {
|
||||
this.savedFilters = new SavedFilters();
|
||||
this.fetchConfig = new FetchConfig();
|
||||
this.alertGroupConfig = new AlertGroupConfig();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user