chore(ui): Refactor action and connector form fields into single component
This commit is contained in:
15
jsconfig.json
Normal file
15
jsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"target": "ES2022",
|
||||
"jsx": "react",
|
||||
"allowImportingTsExtensions": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import GithubIssueCommenterAction from './actions/GithubIssueCommenterAction';
|
||||
import GithubRepositoryAction from './actions/GithubRepositoryAction';
|
||||
import TwitterPostAction from './actions/TwitterPostAction';
|
||||
import SendMailAction from './actions/SendMailAction';
|
||||
import GenerateImageAction from './actions/GenerateImageAction';
|
||||
|
||||
/**
|
||||
* ActionForm component for configuring an action
|
||||
@@ -103,46 +104,7 @@ const ActionForm = ({ actions = [], onChange, onRemove, onAdd }) => {
|
||||
case 'send-mail':
|
||||
return <SendMailAction {...actionProps} />;
|
||||
case 'generate_image':
|
||||
return (
|
||||
<div className="generate-image-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`apiKey${index}`}>OpenAI API Key</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`apiKey${index}`}
|
||||
value={getConfigValue(action, 'apiKey', '')}
|
||||
onChange={(e) => onActionConfigChange(index, 'apiKey', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="sk-..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`apiURL${index}`}>API URL (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`apiURL${index}`}
|
||||
value={getConfigValue(action, 'apiURL', 'https://api.openai.com/v1')}
|
||||
onChange={(e) => onActionConfigChange(index, 'apiURL', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`model${index}`}>Model</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`model${index}`}
|
||||
value={getConfigValue(action, 'model', 'dall-e-3')}
|
||||
onChange={(e) => onActionConfigChange(index, 'model', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="dall-e-3"
|
||||
/>
|
||||
<small className="form-text text-muted">Image generation model (e.g., dall-e-3)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <GenerateImageAction {...actionProps} />;
|
||||
default:
|
||||
return <FallbackAction {...actionProps} />;
|
||||
}
|
||||
|
||||
44
webui/react-ui/src/components/actions/BaseAction.jsx
Normal file
44
webui/react-ui/src/components/actions/BaseAction.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import FormFieldDefinition from '../common/FormFieldDefinition';
|
||||
|
||||
/**
|
||||
* Base action component that renders form fields based on field definitions
|
||||
*
|
||||
* @param {Object} props Component props
|
||||
* @param {number} props.index Action index
|
||||
* @param {Function} props.onActionConfigChange Handler for config changes
|
||||
* @param {Function} props.getConfigValue Helper to get config values
|
||||
* @param {Array} props.fields Field definitions for this action
|
||||
*/
|
||||
const BaseAction = ({
|
||||
index,
|
||||
onActionConfigChange,
|
||||
getConfigValue,
|
||||
fields = []
|
||||
}) => {
|
||||
// Create an object with all the current values
|
||||
const currentValues = {};
|
||||
|
||||
// Pre-populate with current values or defaults
|
||||
fields.forEach(field => {
|
||||
currentValues[field.name] = getConfigValue(field.name, field.defaultValue);
|
||||
});
|
||||
|
||||
// Handle field value changes
|
||||
const handleFieldChange = (name, value) => {
|
||||
onActionConfigChange(name, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="action-template">
|
||||
<FormFieldDefinition
|
||||
fields={fields}
|
||||
values={currentValues}
|
||||
onChange={handleFieldChange}
|
||||
idPrefix={`action${index}_`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseAction;
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* Generate Image action component
|
||||
*/
|
||||
const GenerateImageAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
// Field definitions for Generate Image action
|
||||
const fields = [
|
||||
{
|
||||
name: 'apiKey',
|
||||
label: 'OpenAI API Key',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'sk-...',
|
||||
helpText: 'Your OpenAI API key for image generation',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'apiURL',
|
||||
label: 'API URL',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'http://localai:8081',
|
||||
helpText: 'OpenAI compatible API endpoint URL',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'model',
|
||||
label: 'Model',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'dall-e-3',
|
||||
helpText: 'Image generation model',
|
||||
required: false,
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GenerateImageAction;
|
||||
@@ -1,61 +1,57 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* GitHub Issue Closer action component
|
||||
*/
|
||||
const GithubIssueCloserAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
// Field definitions for GitHub Issue Closer action
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'GitHub Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'ghp_...',
|
||||
helpText: 'Personal access token with repo scope',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Repository Owner',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'username or organization',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
label: 'Repository Name',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'repository-name',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'customActionName',
|
||||
label: 'Custom Action Name (Optional)',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'close_github_issue',
|
||||
helpText: 'Custom name for this action (optional)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="github-issue-closer-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubToken${index}`}>GitHub Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubToken${index}`}
|
||||
value={getConfigValue('token', '')}
|
||||
onChange={(e) => onActionConfigChange('token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="ghp_..."
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Personal access token with repo scope</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubOwner${index}`}>Repository Owner</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubOwner${index}`}
|
||||
value={getConfigValue('owner', '')}
|
||||
onChange={(e) => onActionConfigChange('owner', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubRepo${index}`}>Repository Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubRepo${index}`}
|
||||
value={getConfigValue('repository', '')}
|
||||
onChange={(e) => onActionConfigChange('repository', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="repository-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`customActionName${index}`}>Custom Action Name (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`customActionName${index}`}
|
||||
value={getConfigValue('customActionName', '')}
|
||||
onChange={(e) => onActionConfigChange('customActionName', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="close_github_issue"
|
||||
/>
|
||||
<small className="form-text text-muted">Custom name for this action (optional)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,61 +1,57 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* GitHub Issue Commenter action component
|
||||
*/
|
||||
const GithubIssueCommenterAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
// Field definitions for GitHub Issue Commenter action
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'GitHub Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'ghp_...',
|
||||
helpText: 'Personal access token with repo scope',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Repository Owner',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'username or organization',
|
||||
helpText: 'Owner of the repository',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
label: 'Repository Name',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'repository-name',
|
||||
helpText: 'Name of the repository',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'customActionName',
|
||||
label: 'Custom Action Name (Optional)',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'comment_on_github_issue',
|
||||
helpText: 'Custom name for this action (optional)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="github-issue-commenter-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubToken${index}`}>GitHub Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubToken${index}`}
|
||||
value={getConfigValue('token', '')}
|
||||
onChange={(e) => onActionConfigChange('token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="ghp_..."
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Personal access token with repo scope</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubOwner${index}`}>Repository Owner</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubOwner${index}`}
|
||||
value={getConfigValue('owner', '')}
|
||||
onChange={(e) => onActionConfigChange('owner', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubRepo${index}`}>Repository Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubRepo${index}`}
|
||||
value={getConfigValue('repository', '')}
|
||||
onChange={(e) => onActionConfigChange('repository', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="repository-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`customActionName${index}`}>Custom Action Name (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`customActionName${index}`}
|
||||
value={getConfigValue('customActionName', '')}
|
||||
onChange={(e) => onActionConfigChange('customActionName', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="comment_on_github_issue"
|
||||
/>
|
||||
<small className="form-text text-muted">Custom name for this action (optional)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,74 +1,66 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* GitHub Issue Labeler action component
|
||||
*/
|
||||
const GithubIssueLabelerAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
// Field definitions for GitHub Issue Labeler action
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'GitHub Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'ghp_...',
|
||||
helpText: 'Personal access token with repo scope',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Repository Owner',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'username or organization',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
label: 'Repository Name',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'repository-name',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'availableLabels',
|
||||
label: 'Available Labels',
|
||||
type: 'text',
|
||||
defaultValue: 'bug,enhancement',
|
||||
placeholder: 'bug,enhancement,documentation',
|
||||
helpText: 'Comma-separated list of available labels',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'customActionName',
|
||||
label: 'Custom Action Name (Optional)',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'add_label_to_issue',
|
||||
helpText: 'Custom name for this action (optional)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="github-issue-labeler-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubToken${index}`}>GitHub Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubToken${index}`}
|
||||
value={getConfigValue('token', '')}
|
||||
onChange={(e) => onActionConfigChange('token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="ghp_..."
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Personal access token with repo scope</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubOwner${index}`}>Repository Owner</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubOwner${index}`}
|
||||
value={getConfigValue('owner', '')}
|
||||
onChange={(e) => onActionConfigChange('owner', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubRepo${index}`}>Repository Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubRepo${index}`}
|
||||
value={getConfigValue('repository', '')}
|
||||
onChange={(e) => onActionConfigChange('repository', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="repository-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`availableLabels${index}`}>Available Labels</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`availableLabels${index}`}
|
||||
value={getConfigValue('availableLabels', 'bug,enhancement')}
|
||||
onChange={(e) => onActionConfigChange('availableLabels', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="bug,enhancement,documentation"
|
||||
/>
|
||||
<small className="form-text text-muted">Comma-separated list of available labels</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`customActionName${index}`}>Custom Action Name (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`customActionName${index}`}
|
||||
value={getConfigValue('customActionName', '')}
|
||||
onChange={(e) => onActionConfigChange('customActionName', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="add_label_to_issue"
|
||||
/>
|
||||
<small className="form-text text-muted">Custom name for this action (optional)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,61 +1,57 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* GitHub Issue Opener action component
|
||||
*/
|
||||
const GithubIssueOpenerAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
// Field definitions for GitHub Issue Opener action
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'GitHub Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'ghp_...',
|
||||
helpText: 'Personal access token with repo scope',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Repository Owner',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'username or organization',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
label: 'Repository Name',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'repository-name',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'customActionName',
|
||||
label: 'Custom Action Name (Optional)',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'open_github_issue',
|
||||
helpText: 'Custom name for this action (optional)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="github-issue-opener-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubToken${index}`}>GitHub Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubToken${index}`}
|
||||
value={getConfigValue('token', '')}
|
||||
onChange={(e) => onActionConfigChange('token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="ghp_..."
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Personal access token with repo scope</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubOwner${index}`}>Repository Owner</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubOwner${index}`}
|
||||
value={getConfigValue('owner', '')}
|
||||
onChange={(e) => onActionConfigChange('owner', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubRepo${index}`}>Repository Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubRepo${index}`}
|
||||
value={getConfigValue('repository', '')}
|
||||
onChange={(e) => onActionConfigChange('repository', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="repository-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`customActionName${index}`}>Custom Action Name (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`customActionName${index}`}
|
||||
value={getConfigValue('customActionName', '')}
|
||||
onChange={(e) => onActionConfigChange('customActionName', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="open_github_issue"
|
||||
/>
|
||||
<small className="form-text text-muted">Custom name for this action (optional)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* GitHub Repository action component for repository-related actions
|
||||
@@ -8,58 +9,53 @@ import React from 'react';
|
||||
* - github-readme
|
||||
*/
|
||||
const GithubRepositoryAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
// Field definitions for GitHub Repository action
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'GitHub Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'ghp_...',
|
||||
helpText: 'Personal access token with repo scope',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Repository Owner',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'username or organization',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
label: 'Repository Name',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'repository-name',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'customActionName',
|
||||
label: 'Custom Action Name (Optional)',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'github_repo_action',
|
||||
helpText: 'Custom name for this action (optional)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="github-repository-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubToken${index}`}>GitHub Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubToken${index}`}
|
||||
value={getConfigValue('token', '')}
|
||||
onChange={(e) => onActionConfigChange('token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="ghp_..."
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Personal access token with repo scope</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubOwner${index}`}>Repository Owner</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubOwner${index}`}
|
||||
value={getConfigValue('owner', '')}
|
||||
onChange={(e) => onActionConfigChange('owner', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubRepo${index}`}>Repository Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubRepo${index}`}
|
||||
value={getConfigValue('repository', '')}
|
||||
onChange={(e) => onActionConfigChange('repository', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="repository-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`customActionName${index}`}>Custom Action Name (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`customActionName${index}`}
|
||||
value={getConfigValue('customActionName', '')}
|
||||
onChange={(e) => onActionConfigChange('customActionName', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="github_repo_action"
|
||||
/>
|
||||
<small className="form-text text-muted">Custom name for this action (optional)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,74 +1,66 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* SendMail action component
|
||||
*/
|
||||
const SendMailAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
// Field definitions for SendMail action
|
||||
const fields = [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
defaultValue: '',
|
||||
placeholder: 'your-email@example.com',
|
||||
helpText: 'Email address to send from',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'SMTP username (often same as email)',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
type: 'password',
|
||||
defaultValue: '',
|
||||
placeholder: 'SMTP password or app password',
|
||||
helpText: 'For Gmail, use an app password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'smtpHost',
|
||||
label: 'SMTP Host',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'smtp.gmail.com',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'smtpPort',
|
||||
label: 'SMTP Port',
|
||||
type: 'text',
|
||||
defaultValue: '587',
|
||||
placeholder: '587',
|
||||
helpText: 'Common ports: 587 (TLS), 465 (SSL)',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="send-mail-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`email${index}`}>Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id={`email${index}`}
|
||||
value={getConfigValue('email', '')}
|
||||
onChange={(e) => onActionConfigChange('email', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="your-email@example.com"
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Email address to send from</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`username${index}`}>Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`username${index}`}
|
||||
value={getConfigValue('username', '')}
|
||||
onChange={(e) => onActionConfigChange('username', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="SMTP username (often same as email)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`password${index}`}>Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id={`password${index}`}
|
||||
value={getConfigValue('password', '')}
|
||||
onChange={(e) => onActionConfigChange('password', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="SMTP password or app password"
|
||||
/>
|
||||
<small className="form-text text-muted">For Gmail, use an app password</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`smtpHost${index}`}>SMTP Host</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`smtpHost${index}`}
|
||||
value={getConfigValue('smtpHost', '')}
|
||||
onChange={(e) => onActionConfigChange('smtpHost', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="smtp.gmail.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`smtpPort${index}`}>SMTP Port</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`smtpPort${index}`}
|
||||
value={getConfigValue('smtpPort', '587')}
|
||||
onChange={(e) => onActionConfigChange('smtpPort', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="587"
|
||||
/>
|
||||
<small className="form-text text-muted">Common ports: 587 (TLS), 465 (SSL)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,40 +1,38 @@
|
||||
import React from 'react';
|
||||
import BaseAction from './BaseAction';
|
||||
|
||||
/**
|
||||
* Twitter Post action component
|
||||
*/
|
||||
const TwitterPostAction = ({ index, onActionConfigChange, getConfigValue }) => {
|
||||
return (
|
||||
<div className="twitter-post-action">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`twitterToken${index}`}>Twitter API Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`twitterToken${index}`}
|
||||
value={getConfigValue('token', '')}
|
||||
onChange={(e) => onActionConfigChange('token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="Twitter API token"
|
||||
/>
|
||||
<small className="form-text text-muted">Twitter API token with posting permissions</small>
|
||||
</div>
|
||||
// Field definitions for Twitter Post action
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'Twitter API Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'Twitter API token',
|
||||
helpText: 'Twitter API token with posting permissions',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'noCharacterLimits',
|
||||
label: 'Disable character limit (280 characters)',
|
||||
type: 'checkbox',
|
||||
defaultValue: 'false',
|
||||
helpText: 'Enable to bypass the 280 character limit check',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<div className="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`noCharacterLimits${index}`}
|
||||
checked={getConfigValue('noCharacterLimits', '') === 'true'}
|
||||
onChange={(e) => onActionConfigChange('noCharacterLimits', e.target.checked ? 'true' : 'false')}
|
||||
className="form-check-input"
|
||||
return (
|
||||
<BaseAction
|
||||
index={index}
|
||||
onActionConfigChange={onActionConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={`noCharacterLimits${index}`}>
|
||||
Disable character limit (280 characters)
|
||||
</label>
|
||||
<small className="form-text text-muted d-block">Enable to bypass the 280 character limit check</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
107
webui/react-ui/src/components/common/FormField.jsx
Normal file
107
webui/react-ui/src/components/common/FormField.jsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Reusable form field component that handles different input types
|
||||
*
|
||||
* @param {Object} props Component props
|
||||
* @param {string} props.id Unique identifier for the input
|
||||
* @param {string} props.label Label text for the field
|
||||
* @param {string} props.type Input type (text, checkbox, select, textarea)
|
||||
* @param {string|boolean} props.value Current value of the field
|
||||
* @param {Function} props.onChange Handler for value changes
|
||||
* @param {string} props.placeholder Placeholder text
|
||||
* @param {string} props.helpText Help text to display below the field
|
||||
* @param {Array} props.options Options for select inputs
|
||||
* @param {boolean} props.required Whether the field is required
|
||||
*/
|
||||
const FormField = ({
|
||||
id,
|
||||
label,
|
||||
type = 'text',
|
||||
value,
|
||||
onChange,
|
||||
placeholder = '',
|
||||
helpText = '',
|
||||
options = [],
|
||||
required = false,
|
||||
}) => {
|
||||
// Render different input types
|
||||
const renderInput = () => {
|
||||
switch (type) {
|
||||
case 'checkbox':
|
||||
return (
|
||||
<div className="form-check">
|
||||
<label className="checkbox-label" htmlFor={id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
checked={value === true || value === 'true'}
|
||||
onChange={(e) => onChange(e.target.checked ? 'true' : 'false')}
|
||||
/>
|
||||
{label}
|
||||
</label>
|
||||
{helpText && <small className="form-text text-muted d-block">{helpText}</small>}
|
||||
</div>
|
||||
);
|
||||
case 'select':
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}</label>
|
||||
<select
|
||||
id={id}
|
||||
value={value || ''}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="form-control"
|
||||
required={required}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{helpText && <small className="form-text text-muted">{helpText}</small>}
|
||||
</>
|
||||
);
|
||||
case 'textarea':
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}</label>
|
||||
<textarea
|
||||
id={id}
|
||||
value={value || ''}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="form-control"
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
/>
|
||||
{helpText && <small className="form-text text-muted">{helpText}</small>}
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}</label>
|
||||
<input
|
||||
type={type}
|
||||
id={id}
|
||||
value={value || ''}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="form-control"
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
/>
|
||||
{helpText && <small className="form-text text-muted">{helpText}</small>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-group mb-3">
|
||||
{renderInput()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormField;
|
||||
42
webui/react-ui/src/components/common/FormFieldDefinition.jsx
Normal file
42
webui/react-ui/src/components/common/FormFieldDefinition.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import FormField from './FormField';
|
||||
|
||||
/**
|
||||
* Component that renders a form based on a field definition
|
||||
*
|
||||
* @param {Object} props Component props
|
||||
* @param {Array} props.fields Array of field definitions
|
||||
* @param {Object} props.values Current values for the fields
|
||||
* @param {Function} props.onChange Handler for field value changes
|
||||
* @param {string} props.idPrefix Prefix for field IDs
|
||||
*/
|
||||
const FormFieldDefinition = ({
|
||||
fields,
|
||||
values,
|
||||
onChange,
|
||||
idPrefix = '',
|
||||
}) => {
|
||||
// Ensure values is an object
|
||||
const safeValues = values || {};
|
||||
|
||||
return (
|
||||
<div className="form-fields">
|
||||
{fields.map((field) => (
|
||||
<FormField
|
||||
key={field.name}
|
||||
id={`${idPrefix}${field.name}`}
|
||||
label={field.label}
|
||||
type={field.type}
|
||||
value={safeValues[field.name] !== undefined ? safeValues[field.name] : field.defaultValue}
|
||||
onChange={(value) => onChange(field.name, value)}
|
||||
placeholder={field.placeholder || ''}
|
||||
helpText={field.helpText || ''}
|
||||
options={field.options || []}
|
||||
required={field.required || false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormFieldDefinition;
|
||||
46
webui/react-ui/src/components/connectors/BaseConnector.jsx
Normal file
46
webui/react-ui/src/components/connectors/BaseConnector.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import FormFieldDefinition from '../common/FormFieldDefinition';
|
||||
|
||||
/**
|
||||
* Base connector component that renders form fields based on field definitions
|
||||
*
|
||||
* @param {Object} props Component props
|
||||
* @param {Object} props.connector Connector data
|
||||
* @param {number} props.index Connector index
|
||||
* @param {Function} props.onConnectorConfigChange Handler for config changes
|
||||
* @param {Function} props.getConfigValue Helper to get config values
|
||||
* @param {Array} props.fields Field definitions for this connector
|
||||
*/
|
||||
const BaseConnector = ({
|
||||
connector,
|
||||
index,
|
||||
onConnectorConfigChange,
|
||||
getConfigValue,
|
||||
fields = []
|
||||
}) => {
|
||||
// Create an object with all the current values
|
||||
const currentValues = {};
|
||||
|
||||
// Pre-populate with current values or defaults
|
||||
fields.forEach(field => {
|
||||
currentValues[field.name] = getConfigValue(connector, field.name, field.defaultValue);
|
||||
});
|
||||
|
||||
// Handle field value changes
|
||||
const handleFieldChange = (name, value) => {
|
||||
onConnectorConfigChange(index, name, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<FormFieldDefinition
|
||||
fields={fields}
|
||||
values={currentValues}
|
||||
onChange={handleFieldChange}
|
||||
idPrefix={`connector${index}_`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseConnector;
|
||||
@@ -1,37 +1,40 @@
|
||||
import React from 'react';
|
||||
import BaseConnector from './BaseConnector';
|
||||
|
||||
/**
|
||||
* Discord connector template
|
||||
*/
|
||||
const DiscordConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`discordToken${index}`}>Discord Bot Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`discordToken${index}`}
|
||||
value={getConfigValue(connector, 'token', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="Bot token from Discord Developer Portal"
|
||||
/>
|
||||
<small className="form-text text-muted">Get this from the Discord Developer Portal</small>
|
||||
</div>
|
||||
// Field definitions for Discord connector
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'Discord Bot Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'Bot token from Discord Developer Portal',
|
||||
helpText: 'Get this from the Discord Developer Portal',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'defaultChannel',
|
||||
label: 'Default Channel',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: '123456789012345678',
|
||||
helpText: 'Channel ID to always answer even if not mentioned',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`discordDefaultChannel${index}`}>Default Channel</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`discordDefaultChannel${index}`}
|
||||
value={getConfigValue(connector, 'defaultChannel', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'defaultChannel', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="123456789012345678"
|
||||
return (
|
||||
<BaseConnector
|
||||
connector={connector}
|
||||
index={index}
|
||||
onConnectorConfigChange={onConnectorConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Channel ID to always answer even if not mentioned</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,74 +1,70 @@
|
||||
import React from 'react';
|
||||
import BaseConnector from './BaseConnector';
|
||||
|
||||
/**
|
||||
* GitHub Issues connector template
|
||||
*/
|
||||
const GithubIssuesConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
|
||||
// Field definitions for GitHub Issues connector
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'GitHub Personal Access Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'ghp_...',
|
||||
helpText: 'Personal access token with repo scope',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Repository Owner',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'username or organization',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
label: 'Repository Name',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'repository-name',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'replyIfNoReplies',
|
||||
label: 'Reply Behavior',
|
||||
type: 'select',
|
||||
defaultValue: 'false',
|
||||
options: [
|
||||
{ value: 'false', label: 'Reply to all issues' },
|
||||
{ value: 'true', label: 'Only reply to issues with no comments' },
|
||||
],
|
||||
helpText: '',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'pollInterval',
|
||||
label: 'Poll Interval',
|
||||
type: 'text',
|
||||
defaultValue: '10m',
|
||||
placeholder: '10m',
|
||||
helpText: 'How often to check for new issues (e.g., 10m, 1h)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubToken${index}`}>GitHub Personal Access Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubToken${index}`}
|
||||
value={getConfigValue(connector, 'token', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="ghp_..."
|
||||
<BaseConnector
|
||||
connector={connector}
|
||||
index={index}
|
||||
onConnectorConfigChange={onConnectorConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Personal access token with repo scope</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubOwner${index}`}>Repository Owner</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubOwner${index}`}
|
||||
value={getConfigValue(connector, 'owner', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'owner', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubRepo${index}`}>Repository Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubRepo${index}`}
|
||||
value={getConfigValue(connector, 'repository', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'repository', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="repository-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`replyIfNoReplies${index}`}>Reply Behavior</label>
|
||||
<select
|
||||
id={`replyIfNoReplies${index}`}
|
||||
value={getConfigValue(connector, 'replyIfNoReplies', 'false')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'replyIfNoReplies', e.target.value)}
|
||||
className="form-control"
|
||||
>
|
||||
<option value="false">Reply to all issues</option>
|
||||
<option value="true">Only reply to issues with no comments</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`pollInterval${index}`}>Poll Interval</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`pollInterval${index}`}
|
||||
value={getConfigValue(connector, 'pollInterval', '10m')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'pollInterval', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="10m"
|
||||
/>
|
||||
<small className="form-text text-muted">How often to check for new issues (e.g., 10m, 1h)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,74 +1,70 @@
|
||||
import React from 'react';
|
||||
import BaseConnector from './BaseConnector';
|
||||
|
||||
/**
|
||||
* GitHub PRs connector template
|
||||
*/
|
||||
const GithubPRsConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
|
||||
// Field definitions for GitHub PRs connector
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'GitHub Personal Access Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'ghp_...',
|
||||
helpText: 'Personal access token with repo scope',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Repository Owner',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'username or organization',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
label: 'Repository Name',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'repository-name',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'replyIfNoReplies',
|
||||
label: 'Reply Behavior',
|
||||
type: 'select',
|
||||
defaultValue: 'false',
|
||||
options: [
|
||||
{ value: 'false', label: 'Reply to all PRs' },
|
||||
{ value: 'true', label: 'Only reply to PRs with no comments' },
|
||||
],
|
||||
helpText: '',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'pollInterval',
|
||||
label: 'Poll Interval',
|
||||
type: 'text',
|
||||
defaultValue: '10m',
|
||||
placeholder: '10m',
|
||||
helpText: 'How often to check for new PRs (e.g., 10m, 1h)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubToken${index}`}>GitHub Personal Access Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubToken${index}`}
|
||||
value={getConfigValue(connector, 'token', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="ghp_..."
|
||||
<BaseConnector
|
||||
connector={connector}
|
||||
index={index}
|
||||
onConnectorConfigChange={onConnectorConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Personal access token with repo scope</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubOwner${index}`}>Repository Owner</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubOwner${index}`}
|
||||
value={getConfigValue(connector, 'owner', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'owner', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`githubRepo${index}`}>Repository Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`githubRepo${index}`}
|
||||
value={getConfigValue(connector, 'repository', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'repository', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="repository-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`replyIfNoReplies${index}`}>Reply Behavior</label>
|
||||
<select
|
||||
id={`replyIfNoReplies${index}`}
|
||||
value={getConfigValue(connector, 'replyIfNoReplies', 'false')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'replyIfNoReplies', e.target.value)}
|
||||
className="form-control"
|
||||
>
|
||||
<option value="false">Reply to all PRs</option>
|
||||
<option value="true">Only reply to PRs with no comments</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`pollInterval${index}`}>Poll Interval</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`pollInterval${index}`}
|
||||
value={getConfigValue(connector, 'pollInterval', '10m')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'pollInterval', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="10m"
|
||||
/>
|
||||
<small className="form-text text-muted">How often to check for new PRs (e.g., 10m, 1h)</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,75 +1,66 @@
|
||||
import React from 'react';
|
||||
import BaseConnector from './BaseConnector';
|
||||
|
||||
/**
|
||||
* IRC connector template
|
||||
*/
|
||||
const IRCConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
|
||||
// Field definitions for IRC connector
|
||||
const fields = [
|
||||
{
|
||||
name: 'server',
|
||||
label: 'IRC Server',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'irc.libera.chat',
|
||||
helpText: 'IRC server address',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'port',
|
||||
label: 'Port',
|
||||
type: 'text',
|
||||
defaultValue: '6667',
|
||||
placeholder: '6667',
|
||||
helpText: 'IRC server port',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'nickname',
|
||||
label: 'Nickname',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'MyAgentBot',
|
||||
helpText: 'Bot nickname',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'channel',
|
||||
label: 'Channel',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: '#channel1',
|
||||
helpText: 'Channel to join',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'alwaysReply',
|
||||
label: 'Always Reply',
|
||||
type: 'checkbox',
|
||||
defaultValue: 'false',
|
||||
helpText: 'If checked, the agent will reply to all messages in the channel',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`ircServer${index}`}>IRC Server</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`ircServer${index}`}
|
||||
value={getConfigValue(connector, 'server', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'server', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="irc.libera.chat"
|
||||
<BaseConnector
|
||||
connector={connector}
|
||||
index={index}
|
||||
onConnectorConfigChange={onConnectorConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`ircPort${index}`}>Port</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`ircPort${index}`}
|
||||
value={getConfigValue(connector, 'port', '6667')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'port', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="6667"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`ircNick${index}`}>Nickname</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`ircNick${index}`}
|
||||
value={getConfigValue(connector, 'nickname', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'nickname', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="MyAgentBot"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`ircChannels${index}`}>Channel</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`ircChannels${index}`}
|
||||
value={getConfigValue(connector, 'channel', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'channel', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="#channel1"
|
||||
/>
|
||||
<small className="form-text text-muted">Channel to join</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<div className="form-check">
|
||||
<label className="checkbox-label" htmlFor={`ircAlwaysReply${index}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`ircAlwaysReply${index}`}
|
||||
checked={getConfigValue(connector, 'alwaysReply', '') === 'true'}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'alwaysReply', e.target.checked ? 'true' : 'false')}
|
||||
/>
|
||||
Always Reply
|
||||
</label>
|
||||
<small className="form-text text-muted d-block">If checked, the agent will reply to all messages in the channel</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,66 +1,57 @@
|
||||
import React from 'react';
|
||||
import BaseConnector from './BaseConnector';
|
||||
|
||||
/**
|
||||
* Slack connector template
|
||||
*/
|
||||
const SlackConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
|
||||
// Field definitions for Slack connector
|
||||
const fields = [
|
||||
{
|
||||
name: 'appToken',
|
||||
label: 'Slack App Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'xapp-...',
|
||||
helpText: 'App-level token starting with xapp-',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'botToken',
|
||||
label: 'Slack Bot Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'xoxb-...',
|
||||
helpText: 'Bot token starting with xoxb-',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'channelID',
|
||||
label: 'Slack Channel ID',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'C1234567890',
|
||||
helpText: 'Optional: Specific channel ID to join',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'alwaysReply',
|
||||
label: 'Always Reply',
|
||||
type: 'checkbox',
|
||||
defaultValue: 'false',
|
||||
helpText: 'If checked, the agent will reply to all messages in the channel',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`slackAppToken${index}`}>Slack App Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`slackAppToken${index}`}
|
||||
value={getConfigValue(connector, 'appToken', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'appToken', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="xapp-..."
|
||||
<BaseConnector
|
||||
connector={connector}
|
||||
index={index}
|
||||
onConnectorConfigChange={onConnectorConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">App-level token starting with xapp-</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`slackBotToken${index}`}>Slack Bot Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`slackBotToken${index}`}
|
||||
value={getConfigValue(connector, 'botToken', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'botToken', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="xoxb-..."
|
||||
/>
|
||||
<small className="form-text text-muted">Bot token starting with xoxb-</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`slackChannelID${index}`}>Slack Channel ID</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`slackChannelID${index}`}
|
||||
value={getConfigValue(connector, 'channelID', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'channelID', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="C1234567890"
|
||||
/>
|
||||
<small className="form-text text-muted">Optional: Specific channel ID to join</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<div className="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`slackAlwaysReply${index}`}
|
||||
checked={getConfigValue(connector, 'alwaysReply', '') === 'true'}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'alwaysReply', e.target.checked ? 'true' : 'false')}
|
||||
className="form-check-input"
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={`slackAlwaysReply${index}`}>
|
||||
Always Reply
|
||||
</label>
|
||||
<small className="form-text text-muted d-block">If checked, the agent will reply to all messages in the channel</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import React from 'react';
|
||||
import BaseConnector from './BaseConnector';
|
||||
|
||||
/**
|
||||
* Telegram connector template
|
||||
*/
|
||||
const TelegramConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
|
||||
// Field definitions for Telegram connector
|
||||
const fields = [
|
||||
{
|
||||
name: 'token',
|
||||
label: 'Telegram Bot Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11',
|
||||
helpText: 'Get this from @BotFather on Telegram',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`telegramToken${index}`}>Telegram Bot Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`telegramToken${index}`}
|
||||
value={getConfigValue(connector, 'token', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'token', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
||||
<BaseConnector
|
||||
connector={connector}
|
||||
index={index}
|
||||
onConnectorConfigChange={onConnectorConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
<small className="form-text text-muted">Get this from @BotFather on Telegram</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,71 +1,67 @@
|
||||
import React from 'react';
|
||||
import BaseConnector from './BaseConnector';
|
||||
|
||||
/**
|
||||
* Twitter connector template
|
||||
*/
|
||||
const TwitterConnector = ({ connector, index, onConnectorConfigChange, getConfigValue }) => {
|
||||
// Field definitions for Twitter connector
|
||||
const fields = [
|
||||
{
|
||||
name: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'Twitter API Key',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'apiSecret',
|
||||
label: 'API Secret',
|
||||
type: 'password',
|
||||
defaultValue: '',
|
||||
placeholder: 'Twitter API Secret',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'accessToken',
|
||||
label: 'Access Token',
|
||||
type: 'text',
|
||||
defaultValue: '',
|
||||
placeholder: 'Twitter Access Token',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'accessSecret',
|
||||
label: 'Access Token Secret',
|
||||
type: 'password',
|
||||
defaultValue: '',
|
||||
placeholder: 'Twitter Access Token Secret',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'bearerToken',
|
||||
label: 'Bearer Token',
|
||||
type: 'password',
|
||||
defaultValue: '',
|
||||
placeholder: 'Twitter Bearer Token',
|
||||
helpText: '',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="connector-template">
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`twitterApiKey${index}`}>API Key</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`twitterApiKey${index}`}
|
||||
value={getConfigValue(connector, 'apiKey', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'apiKey', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="Twitter API Key"
|
||||
<BaseConnector
|
||||
connector={connector}
|
||||
index={index}
|
||||
onConnectorConfigChange={onConnectorConfigChange}
|
||||
getConfigValue={getConfigValue}
|
||||
fields={fields}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`twitterApiSecret${index}`}>API Secret</label>
|
||||
<input
|
||||
type="password"
|
||||
id={`twitterApiSecret${index}`}
|
||||
value={getConfigValue(connector, 'apiSecret', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'apiSecret', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="Twitter API Secret"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`twitterAccessToken${index}`}>Access Token</label>
|
||||
<input
|
||||
type="text"
|
||||
id={`twitterAccessToken${index}`}
|
||||
value={getConfigValue(connector, 'accessToken', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'accessToken', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="Twitter Access Token"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`twitterAccessSecret${index}`}>Access Token Secret</label>
|
||||
<input
|
||||
type="password"
|
||||
id={`twitterAccessSecret${index}`}
|
||||
value={getConfigValue(connector, 'accessSecret', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'accessSecret', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="Twitter Access Token Secret"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group mb-3">
|
||||
<label htmlFor={`twitterBearerToken${index}`}>Bearer Token</label>
|
||||
<input
|
||||
type="password"
|
||||
id={`twitterBearerToken${index}`}
|
||||
value={getConfigValue(connector, 'bearerToken', '')}
|
||||
onChange={(e) => onConnectorConfigChange(index, 'bearerToken', e.target.value)}
|
||||
className="form-control"
|
||||
placeholder="Twitter Bearer Token"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user