New: Add headers setting in webhook connection

pull/7376/head
Gauthier 3 months ago committed by GitHub
parent 6677fd1116
commit 78fb20282d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -10,6 +10,7 @@ import CaptchaInput from './CaptchaInput';
import CheckInput from './CheckInput';
import { FormInputButtonProps } from './FormInputButton';
import FormInputHelpText from './FormInputHelpText';
import KeyValueListInput from './KeyValueListInput';
import NumberInput from './NumberInput';
import OAuthInput from './OAuthInput';
import PasswordInput from './PasswordInput';
@ -47,6 +48,9 @@ function getComponent(type: InputType) {
case inputTypes.DEVICE:
return DeviceInput;
case inputTypes.KEY_VALUE_LIST:
return KeyValueListInput;
case inputTypes.MONITOR_EPISODES_SELECT:
return MonitorEpisodesSelectInput;

@ -0,0 +1,21 @@
.inputContainer {
composes: input from '~Components/Form/Input.css';
position: relative;
min-height: 35px;
height: auto;
&.isFocused {
outline: 0;
border-color: var(--inputFocusBorderColor);
box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor);
}
}
.hasError {
composes: hasError from '~Components/Form/Input.css';
}
.hasWarning {
composes: hasWarning from '~Components/Form/Input.css';
}

@ -0,0 +1,10 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'hasError': string;
'hasWarning': string;
'inputContainer': string;
'isFocused': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -0,0 +1,104 @@
import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import { InputOnChange } from 'typings/inputs';
import KeyValueListInputItem from './KeyValueListInputItem';
import styles from './KeyValueListInput.css';
interface KeyValue {
key: string;
value: string;
}
export interface KeyValueListInputProps {
className?: string;
name: string;
value: KeyValue[];
hasError?: boolean;
hasWarning?: boolean;
keyPlaceholder?: string;
valuePlaceholder?: string;
onChange: InputOnChange<KeyValue[]>;
}
function KeyValueListInput({
className = styles.inputContainer,
name,
value = [],
hasError = false,
hasWarning = false,
keyPlaceholder,
valuePlaceholder,
onChange,
}: KeyValueListInputProps): JSX.Element {
const [isFocused, setIsFocused] = useState(false);
const handleItemChange = useCallback(
(index: number | null, itemValue: KeyValue) => {
const newValue = [...value];
if (index === null) {
newValue.push(itemValue);
} else {
newValue.splice(index, 1, itemValue);
}
onChange({ name, value: newValue });
},
[value, name, onChange]
);
const handleRemoveItem = useCallback(
(index: number) => {
const newValue = [...value];
newValue.splice(index, 1);
onChange({ name, value: newValue });
},
[value, name, onChange]
);
const onFocus = useCallback(() => setIsFocused(true), []);
const onBlur = useCallback(() => {
setIsFocused(false);
const newValue = value.reduce((acc: KeyValue[], v) => {
if (v.key || v.value) {
acc.push(v);
}
return acc;
}, []);
if (newValue.length !== value.length) {
onChange({ name, value: newValue });
}
}, [value, name, onChange]);
return (
<div
className={classNames(
className,
isFocused && styles.isFocused,
hasError && styles.hasError,
hasWarning && styles.hasWarning
)}
>
{[...value, { key: '', value: '' }].map((v, index) => (
<KeyValueListInputItem
key={index}
index={index}
keyValue={v.key}
value={v.value}
keyPlaceholder={keyPlaceholder}
valuePlaceholder={valuePlaceholder}
isNew={index === value.length}
onChange={handleItemChange}
onRemove={handleRemoveItem}
onFocus={onFocus}
onBlur={onBlur}
/>
))}
</div>
);
}
export default KeyValueListInput;

@ -0,0 +1,35 @@
.itemContainer {
display: flex;
margin-bottom: 3px;
border-bottom: 1px solid var(--inputBorderColor);
&:last-child {
margin-bottom: 0;
border-bottom: 0;
}
}
.keyInputWrapper {
flex: 1 0 0;
}
.valueInputWrapper {
flex: 1 0 0;
min-width: 40px;
}
.buttonWrapper {
flex: 0 0 22px;
}
.keyInput,
.valueInput {
width: 100%;
border: none;
background-color: transparent;
color: var(--textColor);
&::placeholder {
color: var(--helpTextColor);
}
}

@ -0,0 +1,12 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'buttonWrapper': string;
'itemContainer': string;
'keyInput': string;
'keyInputWrapper': string;
'valueInput': string;
'valueInputWrapper': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -0,0 +1,89 @@
import React, { useCallback } from 'react';
import IconButton from 'Components/Link/IconButton';
import { icons } from 'Helpers/Props';
import TextInput from './TextInput';
import styles from './KeyValueListInputItem.css';
interface KeyValueListInputItemProps {
index: number;
keyValue: string;
value: string;
keyPlaceholder?: string;
valuePlaceholder?: string;
isNew: boolean;
onChange: (index: number, itemValue: { key: string; value: string }) => void;
onRemove: (index: number) => void;
onFocus: () => void;
onBlur: () => void;
}
function KeyValueListInputItem({
index,
keyValue,
value,
keyPlaceholder = 'Key',
valuePlaceholder = 'Value',
isNew,
onChange,
onRemove,
onFocus,
onBlur,
}: KeyValueListInputItemProps): JSX.Element {
const handleKeyChange = useCallback(
({ value: keyValue }: { value: string }) => {
onChange(index, { key: keyValue, value });
},
[index, value, onChange]
);
const handleValueChange = useCallback(
({ value }: { value: string }) => {
onChange(index, { key: keyValue, value });
},
[index, keyValue, onChange]
);
const handleRemovePress = useCallback(() => {
onRemove(index);
}, [index, onRemove]);
return (
<div className={styles.itemContainer}>
<div className={styles.keyInputWrapper}>
<TextInput
className={styles.keyInput}
name="key"
value={keyValue}
placeholder={keyPlaceholder}
onChange={handleKeyChange}
onFocus={onFocus}
onBlur={onBlur}
/>
</div>
<div className={styles.valueInputWrapper}>
<TextInput
className={styles.valueInput}
name="value"
value={value}
placeholder={valuePlaceholder}
onChange={handleValueChange}
onFocus={onFocus}
onBlur={onBlur}
/>
</div>
<div className={styles.buttonWrapper}>
{isNew ? null : (
<IconButton
name={icons.REMOVE}
tabIndex={-1}
onPress={handleRemovePress}
/>
)}
</div>
</div>
);
}
export default KeyValueListInputItem;

@ -14,6 +14,8 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.CHECK;
case 'device':
return inputTypes.DEVICE;
case 'keyValueList':
return inputTypes.KEY_VALUE_LIST;
case 'password':
return inputTypes.PASSWORD;
case 'number':

@ -2,6 +2,7 @@ export const AUTO_COMPLETE = 'autoComplete';
export const CAPTCHA = 'captcha';
export const CHECK = 'check';
export const DEVICE = 'device';
export const KEY_VALUE_LIST = 'keyValueList';
export const MONITOR_EPISODES_SELECT = 'monitorEpisodesSelect';
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
export const FLOAT = 'float';
@ -31,6 +32,7 @@ export const all = [
CAPTCHA,
CHECK,
DEVICE,
KEY_VALUE_LIST,
MONITOR_EPISODES_SELECT,
MONITOR_NEW_ITEMS_SELECT,
FLOAT,

@ -34,7 +34,8 @@ namespace NzbDrone.Common.Reflection
|| type == typeof(string)
|| type == typeof(DateTime)
|| type == typeof(Version)
|| type == typeof(decimal);
|| type == typeof(decimal)
|| (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>));
}
public static bool IsReadable(this PropertyInfo propertyInfo)

@ -101,7 +101,8 @@ namespace NzbDrone.Core.Annotations
TagSelect,
RootFolder,
QualityProfile,
SeriesTag
SeriesTag,
KeyValueList,
}
public enum HiddenType

@ -1436,6 +1436,7 @@
"NotificationsSettingsWebhookMethod": "Method",
"NotificationsSettingsWebhookMethodHelpText": "Which HTTP method to use submit to the Webservice",
"NotificationsSettingsWebhookUrl": "Webhook URL",
"NotificationsSettingsWebhookHeaders": "Headers",
"NotificationsSignalSettingsGroupIdPhoneNumber": "Group ID / Phone Number",
"NotificationsSignalSettingsGroupIdPhoneNumberHelpText": "Group ID / Phone Number of the receiver",
"NotificationsSignalSettingsPasswordHelpText": "Password used to authenticate requests toward signal-api",

@ -43,6 +43,11 @@ namespace NzbDrone.Core.Notifications.Webhook
request.Credentials = new BasicNetworkCredential(settings.Username, settings.Password);
}
foreach (var header in settings.Headers)
{
request.Headers.Add(header.Key, header.Value);
}
_httpClient.Execute(request);
}
catch (HttpException ex)

@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
@ -20,6 +21,7 @@ namespace NzbDrone.Core.Notifications.Webhook
public WebhookSettings()
{
Method = Convert.ToInt32(WebhookMethod.POST);
Headers = new List<KeyValuePair<string, string>>();
}
[FieldDefinition(0, Label = "NotificationsSettingsWebhookUrl", Type = FieldType.Url)]
@ -34,6 +36,9 @@ namespace NzbDrone.Core.Notifications.Webhook
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "NotificationsSettingsWebhookHeaders", Type = FieldType.KeyValueList, Advanced = true)]
public IEnumerable<KeyValuePair<string, string>> Headers { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

Loading…
Cancel
Save