Skip to content

Commit 1e3c713

Browse files
ousmaneogally47laura-b-g
authored
add Inputs notifications for failed, setup and stopped state and improve input creation (#24283)
* add Inputs notifications for failed, setup and stopped state * fix lint * improve input creation - open wizard after saving input * remove navigation * remove filter copy * adjust to upstream changes * Update InputsPage.tsx * Update graylog2-web-interface/src/util/DocsHelper.ts Co-authored-by: Laura Bergenthal-Grotlüschen <laura.bergenthalgrotlueschen@graylog.com> * reset field after opening wizard * fix typo * add column width --------- Co-authored-by: Mohamed OULD HOCINE <106236152+gally47@users.noreply.github.com> Co-authored-by: Laura Bergenthal-Grotlüschen <laura.bergenthalgrotlueschen@graylog.com>
1 parent f1b9835 commit 1e3c713

File tree

9 files changed

+194
-36
lines changed

9 files changed

+194
-36
lines changed

graylog2-web-interface/src/components/inputs/CreateInputControl.tsx

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ import useLocation from 'routing/useLocation';
3131
import { Col, Row, Button } from 'components/bootstrap';
3232
import { Select } from 'components/common';
3333
import { InputForm } from 'components/inputs';
34-
import type { ConfiguredInput } from 'components/messageloaders/Types';
34+
import type { ConfiguredInput, Input } from 'components/messageloaders/Types';
3535
import useInputTypes from 'components/inputs/useInputTypes';
3636
import { KEY_PREFIX } from 'hooks/usePaginatedInputs';
37+
import useFeature from 'hooks/useFeature';
38+
import { INPUT_SETUP_MODE_FEATURE_FLAG, InputSetupWizard } from 'components/inputs/InputSetupWizard';
3739

3840
const StyledForm = styled.form`
3941
display: flex;
@@ -52,10 +54,27 @@ const CreateInputControl = () => {
5254
const [selectedInput, setSelectedInput] = useState<string | undefined>(undefined);
5355
const [selectedInputDefinition, setSelectedInputDefinition] = useState<InputDescription | undefined>(undefined);
5456
const [customInputConfiguration, setCustomInputConfiguration] = useState(undefined);
57+
const [showWizard, setShowWizard] = useState<boolean>(false);
58+
const [createdInputId, setCreatedInputId] = useState<string | null>(null);
59+
const [createdInputData, setCreatedInputData] = useState<ConfiguredInput | null>(null);
5560
const sendTelemetry = useSendTelemetry();
5661
const { pathname } = useLocation();
5762
const inputTypes = useInputTypes();
5863
const queryClient = useQueryClient();
64+
const inputSetupFeatureFlagIsEnabled = useFeature(INPUT_SETUP_MODE_FEATURE_FLAG);
65+
66+
const openWizard = (inputId: string, inputData: ConfiguredInput) => {
67+
setCreatedInputId(inputId);
68+
setCreatedInputData(inputData);
69+
setShowWizard(true);
70+
};
71+
72+
const closeWizard = () => {
73+
setShowWizard(false);
74+
setCreatedInputId(null);
75+
setCreatedInputData(null);
76+
};
77+
5978
const resetFields = () => {
6079
setSelectedInput(undefined);
6180
setSelectedInputDefinition(undefined);
@@ -119,12 +138,30 @@ const CreateInputControl = () => {
119138
app_action_value: 'input-create',
120139
});
121140

122-
InputsActions.create(data).then(() => {
123-
resetFields();
141+
InputsActions.create(data).then((response: { id: string }) => {
124142
queryClient.invalidateQueries({ queryKey: KEY_PREFIX });
143+
144+
if (inputSetupFeatureFlagIsEnabled && response?.id) {
145+
setTimeout(() => openWizard(response.id, data), 500);
146+
}
147+
148+
resetFields();
125149
});
126150
};
127151

152+
const createInputForWizard = () => {
153+
if (!createdInputId || !createdInputData) return null;
154+
155+
return {
156+
id: createdInputId,
157+
title: createdInputData.title,
158+
type: selectedInput,
159+
attributes: createdInputData.configuration,
160+
global: createdInputData.global || false,
161+
node: createdInputData.node || null,
162+
} as Input;
163+
};
164+
128165
const CustomInputsConfiguration = customInputConfiguration ? customInputConfiguration.component : null;
129166

130167
return (
@@ -165,6 +202,9 @@ const CreateInputControl = () => {
165202
/>
166203
)
167204
))}
205+
{inputSetupFeatureFlagIsEnabled && showWizard && createdInputId && (
206+
<InputSetupWizard input={createInputForWizard()} show={showWizard} onClose={closeWizard} />
207+
)}
168208
</Col>
169209
</Row>
170210
);

graylog2-web-interface/src/components/inputs/InputSetupWizard/Wizard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const Wizard = ({ show, input, onClose }: Props) => {
8585
const orderedStepsConfig = orderedSteps.map((step) => steps[step]);
8686

8787
return (
88-
<Modal show onHide={onClose} backdrop={false}>
88+
<Modal show onHide={onClose}>
8989
<Modal.Header>Input Setup Wizard</Modal.Header>
9090
<Modal.Body>
9191
<InputSetupWizardStepsProvider>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (C) 2020 Graylog, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*/
17+
import * as React from 'react';
18+
import { useEffect, useMemo } from 'react';
19+
import styled, { css } from 'styled-components';
20+
21+
import { Alert, Row, Col } from 'components/bootstrap';
22+
import useInputsStates from 'hooks/useInputsStates';
23+
import type { InputStates, InputState } from 'hooks/useInputsStates';
24+
import { useStore } from 'stores/connect';
25+
import { InputsStore, InputsActions } from 'stores/inputs/InputsStore';
26+
27+
const INPUT_STATES = {
28+
FAILED: 'FAILED',
29+
FAILING: 'FAILING',
30+
SETUP: 'SETUP',
31+
} as const;
32+
const StyledAlert = styled(Alert)(
33+
({ theme }) => css`
34+
margin-top: 10px;
35+
36+
i {
37+
color: ${theme.colors.gray[10]};
38+
}
39+
40+
form {
41+
margin-bottom: 0;
42+
}
43+
`,
44+
);
45+
46+
const hasInputInState = (inputStates: InputStates, targetStates: InputState | Array<InputState>) => {
47+
const statesToCheck = Array.isArray(targetStates) ? targetStates : [targetStates];
48+
49+
for (const nodeStates of Object.values(inputStates)) {
50+
for (const inputState of Object.values(nodeStates)) {
51+
if (statesToCheck.includes(inputState.state)) {
52+
return true;
53+
}
54+
}
55+
}
56+
57+
return false;
58+
};
59+
60+
const InputsNotifications = () => {
61+
const { data: inputStates, isLoading } = useInputsStates();
62+
const inputs = useStore(InputsStore, (state) => state.inputs);
63+
64+
useEffect(() => {
65+
InputsActions.list();
66+
}, []);
67+
68+
const notifications = useMemo(() => {
69+
if (isLoading || !inputs || !inputStates) return null;
70+
71+
return {
72+
hasStoppedInputs: inputs.some((input) => !inputStates[input.id]),
73+
hasFailedInputs: hasInputInState(inputStates, [INPUT_STATES.FAILED, INPUT_STATES.FAILING]),
74+
hasSetupInputs: hasInputInState(inputStates, INPUT_STATES.SETUP),
75+
};
76+
}, [inputs, inputStates, isLoading]);
77+
78+
const { hasStoppedInputs, hasFailedInputs, hasSetupInputs } = notifications;
79+
80+
if (!hasStoppedInputs && !hasFailedInputs && !hasSetupInputs) {
81+
return null;
82+
};
83+
84+
return (
85+
<Row className="content">
86+
<Col md={12}>
87+
{hasFailedInputs && (
88+
<StyledAlert bsStyle="danger" title="Some inputs are in failed state.">
89+
One or more inputs are currently in failed state. Failed or failing inputs will not receive traffic until
90+
fixed.
91+
</StyledAlert>
92+
)}
93+
{hasSetupInputs && (
94+
<StyledAlert bsStyle="warning" title="Some inputs are in setup mode.">
95+
One or more inputs are currently in setup mode. Inputs will not receive traffic until started.
96+
</StyledAlert>
97+
)}
98+
{hasStoppedInputs && (
99+
<StyledAlert bsStyle="warning" title="Some inputs are stopped.">
100+
One or more inputs are currently stopped. Stopped Inputs will not receive traffic until started.
101+
</StyledAlert>
102+
)}
103+
</Col>
104+
</Row>
105+
);
106+
};
107+
108+
export default InputsNotifications;

graylog2-web-interface/src/components/inputs/InputsOveriew/ColumnRenderers.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,46 +42,53 @@ const customColumnRenderers = ({ inputTypes, inputStates }: Props): ColumnRender
4242
<TitleCell input={input} />
4343
</ExpandedSectionToggleWrapper>
4444
),
45+
width: 0.5
4546
},
4647
type: {
4748
renderCell: (type: string, input: InputSummary) => (
4849
<ExpandedSectionToggleWrapper id={input.id}>
4950
<TypeCell type={type} inputTypes={inputTypes} />
5051
</ExpandedSectionToggleWrapper>
5152
),
53+
width: 0.5,
5254
},
5355
desired_state: {
5456
renderCell: (_state: string, input: InputSummary) => (
5557
<ExpandedSectionToggleWrapper id={input.id}>
5658
<InputStateBadge input={input} inputStates={inputStates} />
5759
</ExpandedSectionToggleWrapper>
5860
),
61+
staticWidth: 130,
5962
},
6063
node_id: {
61-
renderCell: (_node: string, input: InputSummary) => (
64+
renderCell: (_type: string, input: InputSummary) => (
6265
<ExpandedSectionToggleWrapper id={input.id}>
6366
<NodeCell input={input} />
6467
</ExpandedSectionToggleWrapper>
6568
),
69+
staticWidth: 130,
6670
},
6771
traffic: {
6872
renderCell: (_traffic: string, input: InputSummary) => (
6973
<ExpandedSectionToggleWrapper id={input.id}>
7074
<ThroughputCell input={input} />
7175
</ExpandedSectionToggleWrapper>
7276
),
77+
staticWidth: 130,
7378
},
7479
address: {
7580
renderCell: (_address: string, input: InputSummary) => (
7681
<ExpandedSectionToggleWrapper id={input.id}>
7782
{input.attributes?.bind_address || 'N/A'}
7883
</ExpandedSectionToggleWrapper>
7984
),
85+
staticWidth: 100,
8086
},
8187
port: {
8288
renderCell: (_port: string, input: InputSummary) => (
8389
<ExpandedSectionToggleWrapper id={input.id}>{input.attributes?.port || 'N/A'}</ExpandedSectionToggleWrapper>
8490
),
91+
staticWidth: 100,
8592
},
8693
},
8794
});

graylog2-web-interface/src/components/inputs/InputsOveriew/Constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ const getInputsTableElements = () => {
3131
'address',
3232
'port',
3333
],
34+
defaultColumnOrder: ['title', 'type', 'direction', 'desired_state', 'traffic', 'node_id', 'address', 'port'],
3435
};
35-
const columnsOrder = ['title', 'type', 'direction', 'desired_state', 'traffic', 'node_id', 'address', 'port'];
3636
const additionalAttributes = [
3737
{ id: 'traffic', title: 'Traffic' },
3838
{ id: 'address', title: 'Address' },
@@ -41,7 +41,6 @@ const getInputsTableElements = () => {
4141

4242
return {
4343
tableLayout,
44-
columnsOrder,
4544
additionalAttributes,
4645
};
4746
};

graylog2-web-interface/src/components/inputs/InputsOveriew/InputsOverview.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const entityName = 'input';
5252

5353
const InputsOverview = ({ node = undefined, inputTypeDescriptions, inputTypes }: Props) => {
5454
const { data: inputStates } = useInputsStates();
55-
const { columnsOrder, tableLayout, additionalAttributes } = getInputsTableElements();
55+
const { tableLayout, additionalAttributes } = getInputsTableElements();
5656
const { entityActions, expandedSections } = useTableElements({
5757
inputTypes,
5858
inputTypeDescriptions,
@@ -77,7 +77,6 @@ const InputsOverview = ({ node = undefined, inputTypeDescriptions, inputTypes }:
7777
)}
7878
<PaginatedEntityTable<Input>
7979
humanName="inputs"
80-
columnsOrder={columnsOrder}
8180
additionalAttributes={additionalAttributes}
8281
queryHelpComponent={<QueryHelper entityName={entityName} />}
8382
entityActions={entityActions}

graylog2-web-interface/src/pages/InputsPage.tsx

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import { Row, Col } from 'components/bootstrap';
2020
import { DocumentTitle, PageHeader, Spinner } from 'components/common';
2121
import AppConfig from 'util/AppConfig';
2222
import { Link } from 'components/common/router';
23+
import DocsHelper from 'util/DocsHelper';
2324
import useProductName from 'brand-customization/useProductName';
2425
import { InputsOverview } from 'components/inputs/InputsOveriew';
2526
import useInputTypes from 'hooks/useInputTypes';
2627
import useInputTypesDescriptions from 'hooks/useInputTypesDescriptions';
28+
import InputsNotifications from 'components/inputs/InputsNotifications';
2729

2830
const isCloud = AppConfig.isCloud();
2931

@@ -38,32 +40,34 @@ const InputsPage = () => {
3840

3941
return (
4042
<DocumentTitle title="Inputs">
41-
<div>
42-
<PageHeader title="Inputs">
43-
{isCloud ? (
44-
<>
45-
<p>
46-
{' '}
47-
{productName} cloud accepts data via inputs. There are many types of inputs to choose from, but only
48-
some can run directly in the cloud. You can launch and terminate them on this page.
49-
</p>
50-
<p>
51-
If you are missing an input type on this page&apos;s list of available inputs, you can start the input
52-
on a <Link to="/system/forwarders">Forwarder</Link>.
53-
</p>
54-
</>
55-
) : (
56-
<span>
57-
{productName} nodes accept data via inputs. Launch or terminate as many inputs as you want here.
58-
</span>
59-
)}
60-
</PageHeader>
61-
<Row className="content">
62-
<Col md={12}>
63-
<InputsOverview inputTypeDescriptions={inputTypeDescriptions} inputTypes={inputTypes} />
64-
</Col>
65-
</Row>
66-
</div>
43+
<InputsNotifications />
44+
<PageHeader
45+
title="Inputs"
46+
documentationLink={{
47+
title: 'Inputs documentation',
48+
path: DocsHelper.PAGES.INPUTS,
49+
}}>
50+
{isCloud ? (
51+
<>
52+
<p>
53+
{' '}
54+
{productName} cloud accepts data via inputs. There are many types of inputs to choose from, but only some
55+
can run directly in the cloud. You can launch and terminate them on this page.
56+
</p>
57+
<p>
58+
If you are missing an input type on this page&apos;s list of available inputs, you can start the input on
59+
a <Link to='/system/forwarders'>Forwarder</Link>.
60+
</p>
61+
</>
62+
) : (
63+
<span>{productName} nodes accept data via inputs. Launch or terminate as many inputs as you want here.</span>
64+
)}
65+
</PageHeader>
66+
<Row className="content">
67+
<Col md={12}>
68+
<InputsOverview inputTypeDescriptions={inputTypeDescriptions} inputTypes={inputTypes} />
69+
</Col>
70+
</Row>
6771
</DocumentTitle>
6872
);
6973
};

graylog2-web-interface/src/stores/inputs/InputsStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type InputsActionsType = {
2727
list: () => Promise<{ inputs: Array<Input>; total: number }>;
2828
get: (id: string) => Promise<Input>;
2929
getOptional: (id: string, showError: boolean) => Promise<Input>;
30-
create: (input: ConfiguredInput) => Promise<void>;
30+
create: (input: ConfiguredInput) => Promise<{ id: string }>;
3131
delete: (input: Input) => Promise<void>;
3232
update: (id: string, input: ConfiguredInput) => Promise<void>;
3333
};

graylog2-web-interface/src/util/DocsHelper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const defaultPages = {
4747
LICENSE_MANAGEMENT: 'setting_up_graylog/operations_license_management.html',
4848
LOAD_BALANCERS: 'setting_up_graylog/load_balancer_integration.html',
4949
LOOKUPTABLES: 'making_sense_of_your_log_data/lookup_tables.html',
50+
INPUTS: 'getting_in_log_data/inputs.html',
5051
OPERATIONS_CHANGELOG: 'changelogs/operations_changelog.html',
5152
OPEN_SEARCH_SETUP: 'setting_up_graylog/opensearch.htm#InstallingOpenSearch',
5253
PAGE_FLEXIBLE_DATE_CONVERTER: 'making_sense_of_your_log_data/extractors.htm#Normalization',

0 commit comments

Comments
 (0)