node-red-contrib-chatbot
Version:
REDBot a Chat bot for a full featured chat bot for Telegram, Facebook Messenger and Slack. Almost no coding skills required
314 lines (300 loc) • 11.7 kB
JavaScript
import React, { useMemo, useState, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Notification, Message, FlexboxGrid, Input, Checkbox } from 'rsuite';
import { useMutation, useApolloClient } from 'react-apollo';
import useFetch from 'use-http';
import ModalLoader from '../../../src/components/loader-modal';
import PageContainer from '../../../src/components/page-container';
import Breadcrumbs from '../../../src/components/breadcrumbs';
import Confirm from '../../../src/components/confirm';
import ShowError from '../../../src/components/show-error';
import useSettings from '../../../src/hooks/settings';
import { TableFilters } from '../../../src/components';
import useMCContext from '../../../src/hooks/mc-context';
import { INSTALL_PLUGIN, CHATBOT, UNISTALL_PLUGIN, UPDATE_PLUGIN } from '../queries';
import PluginPanel from './plugin-panel';
import _ from 'lodash';
const PLUGINS_QUERY = {
query: `query {
plugins(
filter: { status: { _eq: "published" } },
limit: 100
) {
id
title
status
description
repository
flow
url
tags
version
configuration
content_slug
content_body
content_title
name
status,
redbot_version,
user_created {
nickname,
url,
first_name,
last_name
}
}
}`,
variables: {}
};
const usePlugins = ({ onCompleted = () => {} } = {}) => {
const [
install,
{ loading: installLoading, error: installError },
] = useMutation(INSTALL_PLUGIN, { onCompleted });
const [
uninstall,
{ loading: uninstallLoading, error: uninstallError },
] = useMutation(UNISTALL_PLUGIN, { onCompleted });
const [
update,
{ loading: updateLoading, error: updateError },
] = useMutation(UPDATE_PLUGIN, { onCompleted });
return {
saving: installLoading || uninstallLoading || updateLoading,
error: installError || uninstallError || updateError,
install,
uninstall,
update
};
};
const filtersSchema = [
{
type: 'string',
name: 'name',
control: Input,
label: 'Search plugin'
}
];
const CheckTree = ({ value = [], onChange, data }) => {
return (
<div>
{data.map(item => (
<Checkbox
key={item.value}
checked={value != null && value.includes(item.value)}
onChange={() => {
if (value != null && value.includes(item.value)) {
onChange(value.filter(keyword => keyword != item.value))
} else if (value != null && !value.includes(item.value)) {
onChange([...value, item.value]);
} else {
onChange([item.value]);
}
}}
>
<span className="plugin-keyword">
{item.label} <span className="count">{item.count}</span>
</span>
</Checkbox>
))}
</div>
);
};
CheckTree.propTypes = {
value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func,
data: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string,
count: PropTypes.number
}))
};
const PluginsManager = ({ dispatch }) => {
const { environment } = useSettings();
const { state } = useMCContext();
const [error, setError] = useState(null);
const [filters, setFilters] = useState({});
const client = useApolloClient();
// fetch plugins from redbot dashboard
const { data, error: fetchError } = useFetch('https://dashboard.red-bot.io/graphql', {
method: 'post',
body: JSON.stringify(PLUGINS_QUERY),
cachePolicy: 'network-only'
}, []);
const plugins = data != null && data.data != null ? data.data.plugins : undefined;
const { install, uninstall, update, saving, error: pluginError } = usePlugins({
onCompleted: async () => {
try {
const response = await client.query({
query: CHATBOT,
variables: { chatbotId: state.chatbotId },
fetchPolicy: 'network-only'
});
dispatch({ type: 'updateChatbot', chatbot: response.data.chatbot });
} catch(e) {
setError(e);
}
}
});
const pageError = error || fetchError || pluginError;
const loading = plugins == null;
// collect all keywords
const keywordsData = useMemo(() => {
if (plugins != null) {
const keywords = {};
plugins.forEach(plugin => {
if (!_.isEmpty(plugin.tags)) {
plugin.tags
.forEach(keyword => keywords[keyword] = keywords[keyword] != null ? keywords[keyword] + 1 : 1);
}
});
return Object.keys(keywords).sort().map(key => ({
value: key,
label: key,
count: keywords[key]
}));
}
}, [plugins]);
return (
<PageContainer className="page-plugins">
<Breadcrumbs pages={['Plugins']}/>
<FlexboxGrid justify="space-between">
<FlexboxGrid.Item colspan={17} style={{ paddingTop: '20px', paddingLeft: '20px' }}>
{pageError != null && (
<ShowError
onClear={() => window.location.reload()}
error={pageError}
/>
)}
{environment === 'development' && (
<Message
type="warning"
title="Development mode"
description={<p>
<strong>Mission Control</strong> is runnin in <em>development mode</em>, all plugins are loaded with <code>import ... from ... </code> defined in
the file <code>./plugins.js</code>, any changes to a plugin will cause reload, installing and uninstallig plugins
in this page will not affect the plugins actually loaded.<br />
Run <code>npm run start:dev</code> to select whic plugin to run in development.
</p>}
/>
)}
{loading && pageError == null && <ModalLoader />}
{!loading && !_.isEmpty(plugins) && (
<Fragment>
<div className="plugins">
{plugins
.filter(plugin => _.isEmpty(filters.name) || plugin.name.toLowerCase().includes(filters.name.toLowerCase()))
.filter(plugin => _.isEmpty(filters.keywords) || _.intersection(filters.keywords, plugin.tags).length !== 0)
.map(plugin => (
<PluginPanel
key={plugin.name}
plugin={plugin}
plugins={plugins}
disabled={saving || loading}
onInstall={async plugin => {
if (await Confirm(
<div>Install plugin <strong>{plugin.name}</strong> ?</div>,
{ okLabel: 'Ok, install'}
)) {
try {
await install({ variables: {
plugin: plugin.name,
url: plugin.url,
version: plugin.version,
initialConfiguration: plugin.configuration,
initialContent: !_.isEmpty(plugin.content_title) ? {
title: plugin.content_title,
slug: plugin.content_slug,
body: plugin.content_body
} : undefined,
chatbotId: state.chatbotId,
pluginId: plugin.id
}});
Notification.success({
placement: 'topStart',
title: 'Installed', description: `Plugin "${plugin.name}" installed succesfully` });
} catch(e) {
Notification.error({ placement: 'topStart', title: 'Error', description: `Something went wrong trying to install the plugin "${plugin.name}"` });
}
}
}}
onUpdate={async plugin => {
if (await Confirm(
<div>Update plugin <strong>{plugin.name}</strong> to version <em>{plugin.version}</em>?</div>,
{ okLabel: 'Ok, update'}
)) {
try {
await update({ variables: {
plugin: plugin.name,
url: plugin.url,
version: plugin.version,
initialConfiguration: plugin.initialConfiguration,
chatbotId: state.chatbotId
}});
Notification.success({
placement: 'topStart',
title: 'Updated', description: `Plugin "${plugin.name}" updated succesfully to version ${plugin.version}` });
} catch(e) {
Notification.error({ placement: 'topStart', title: 'Error', description: `Something went wrong trying to install the plugin "${plugin.name}"` });
}
}
}}
onUninstall={async plugin => {
if (await Confirm(
<div>Uninstall plugin <strong>{plugin.name}</strong> ?</div>,
{ okLabel: 'Ok, uninstall'}
)) {
try {
await uninstall({ variables: { plugin: plugin.name, chatbotId: state.chatbotId }});
Notification.success({ placement: 'topStart', title: 'Unistalled', description: `Plugin "${plugin.name}" uninstalled succesfully` });
} catch(e) {
Notification.error({ placement: 'topStart', title: 'Error', description: `Something went wrong trying to uninstall the plugin "${plugin.name}"` });
}
}
}}
/>
))
}
</div>
</Fragment>
)}
</FlexboxGrid.Item>
<FlexboxGrid.Item colspan={7} className="right-column">
{keywordsData != null && (
<Fragment>
<TableFilters
filters={filters}
schema={filtersSchema}
onChange={filters => setFilters(filters)}
/>
<div style={{ marginTop: '15px' }}>
<strong>Keywords</strong>
{!_.isEmpty(filters.keywords) && (
<span className="clear-button">
(<a href="#" onClick={e => {
e.preventDefault();
setFilters({ ...filters, keywords: null });
}}>clear</a>)
</span>
)}
</div>
<CheckTree
data={keywordsData}
value={filters.keywords}
onChange={keywords => setFilters({ ...filters, keywords })}
renderTreeNode={item => (
<span className="plugin-keyword">
{item.label} <span className="count">{item.count}</span>
</span>
)}
/>
</Fragment>
)}
</FlexboxGrid.Item>
</FlexboxGrid>
</PageContainer>
);
};
PluginsManager.propTypes = {
dispatch: PropTypes.func
};
export default PluginsManager;