Skip to content

Commit

Permalink
Merge branch '3418-available-plugins' into 3418-plugin-installation-s…
Browse files Browse the repository at this point in the history
…creen Issue: #3418
 PR: #3620
  • Loading branch information
VakarisZ committed Aug 29, 2023
2 parents 406d907 + 9dafe57 commit 6e19b73
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def _get_plugin_information_from_request(
raise ValueError(message)

try:
plugin_version = PluginVersion(**plugin_version_arg)
plugin_version = PluginVersion.parse(plugin_version_arg)
except ValueError as err:
message = f"Invalid plugin version argument: {plugin_version_arg}: {err}."
raise ValueError(message)
Expand Down
66 changes: 0 additions & 66 deletions monkey/monkey_island/cc/ui/src/components/pages/MarketplacePage.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, {useState} from 'react';
import React, {useEffect, useState} from 'react';
import Tabs from '@mui/material/Tabs';
import {Tab, Box} from '@mui/material';
import {PluginsContext} from '../ui-components/plugins-marketplace/PluginsContext';
import AvailablePlugins from '../ui-components/plugins-marketplace/AvailablePlugins';
import AuthComponent from '../AuthComponent';

const TabPanel = (props) => {
const {children, value, index, ...other} = props;
Expand Down Expand Up @@ -33,13 +35,38 @@ const MarketplacePage = () => {
const [availablePlugins, setAvailablePlugins] = useState([]);
const [installedPlugins, setInstalledPlugins] = useState([]);
const [tabValue, setTabValue] = useState(0);
const authComponent = new AuthComponent({});

useEffect(() => {
refreshAvailablePlugins();
}, []);

useEffect(() => {
refreshInstalledPlugins();
}, []);

const refreshAvailablePlugins = (forceRefresh: boolean = false) => {
let url = '/api/agent-plugins/available/index';
if (forceRefresh) {
url += '?force_refresh=true';
}
authComponent.authFetch(url, {}, true).then(res => res.json()).then(plugins => {
setAvailablePlugins(plugins.plugins);
});
};

const refreshInstalledPlugins = () => {
authComponent.authFetch('/api/agent-plugins/installed/manifests', {}, true).then(res => res.json()).then(plugins => {
setInstalledPlugins(plugins);
});
};

const handleChange = (_event, newValue) => {
setTabValue(newValue);
};

return (
<PluginsContext.Provider value={{availablePlugins, installedPlugins, setAvailablePlugins, setInstalledPlugins}}>
<PluginsContext.Provider value={{availablePlugins, installedPlugins, refreshAvailablePlugins, refreshInstalledPlugins}}>
<Box className="main col-xl-8 col-lg-8 col-md-9 col-sm-9 offset-xl-2 offset-lg-3 offset-md-3 offset-sm-3">
<h1 className='page-title'>Plugins</h1>
<Box sx={{borderBottom: 1, borderColor: 'divider'}}>
Expand All @@ -53,7 +80,7 @@ const MarketplacePage = () => {
<Tab label="Installed Plugins" {...a11yProps(1)}/>
</Tabs>
</Box>
<TabPanel value={tabValue} index={0}>AvailablePlugins</TabPanel>
<TabPanel value={tabValue} index={0}><AvailablePlugins /></TabPanel>
<TabPanel value={tabValue} index={1}>Installed Plugins</TabPanel>
</Box>
</PluginsContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, {useContext, useState} from 'react';
import {installPlugin} from './mocksHelper';
import {shallowAdditionOfUniqueValueToArray, shallowRemovalOfUniqueValueFromArray} from '../../../utils/objectUtils';
import React, {useContext, useEffect, useState} from 'react';
import {
shallowAdditionOfUniqueValueToArray,
shallowRemovalOfUniqueValueFromArray
} from '../../../utils/objectUtils';
import {PluginsContext} from './PluginsContext';
import {GridActionsCellItem} from '@mui/x-data-grid';
import {nanoid} from 'nanoid';
Expand All @@ -10,26 +12,47 @@ import DownloadingIcon from '@mui/icons-material/Downloading';
import BasePlugins from './BasePlugins';
import SearchBar from '../SearchBar';
import {Box} from '@mui/material';
import AuthComponent from '../../AuthComponent';
import { Button, Col, Row } from 'react-bootstrap';
import RefreshIcon from '@mui/icons-material/Refresh';
import {generatePluginId, installedPluginsToArray, pluginIndexToArray} from './utils';
import '../../../styles/components/plugins-marketplace/AvailablePlugins.scss'


const AvailablePlugins = () => {
const {availablePlugins, setAvailablePlugins} = useContext(PluginsContext);
const {availablePlugins} = useContext(PluginsContext);
const {installedPlugins} = useContext(PluginsContext);
const {refreshAvailablePlugins} = useContext(PluginsContext);
const {refreshInstalledPlugins} = useContext(PluginsContext);
const [displayedPlugins, setDisplayedPlugins] = useState([]);

const [successfullyInstalledPluginsIds, setSuccessfullyInstalledPluginsIds] = useState([]);
const [pluginsInInstallationProcess, setPluginsInInstallationProcess] = useState([]);
const authComponent = new AuthComponent({});

useEffect(() => {
let installedPluginsIds = installedPluginsToArray(installedPlugins).map(generatePluginId);
const installedFilter = (plugin) => !installedPluginsIds.includes(generatePluginId(plugin));
let shownPlugins = pluginIndexToArray(availablePlugins).filter(installedFilter)
setDisplayedPlugins(shownPlugins);
}, [installedPlugins, availablePlugins]);

const onRefreshCallback = () => {
setSuccessfullyInstalledPluginsIds([]);
}

const onInstallClick = (pluginId) => {
const onInstallClick = (pluginId, pluginName, pluginType, pluginVersion) => {
console.log('installing plugin: ', pluginName)

setPluginsInInstallationProcess((prevState) => {
return shallowAdditionOfUniqueValueToArray(prevState, pluginId);
});

installPlugin(pluginId).then(() => {
authComponent.authFetch('/api/install-agent-plugin', {method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({plugin_type: pluginType, name: pluginName, version: pluginVersion})}, true).then(() => {
setSuccessfullyInstalledPluginsIds((prevState) => {
return shallowAdditionOfUniqueValueToArray(prevState, pluginId);
});
refreshInstalledPlugins();
}).catch(() => {
console.log('error installing plugin');
}).finally(() => {
Expand All @@ -39,7 +62,8 @@ const AvailablePlugins = () => {
});
};

const getRowActions = (pluginId) => {
const getRowActions = (row) => {
const pluginId = row.id;
if (pluginsInInstallationProcess.includes(pluginId)) {
return [
<GridActionsCellItem
Expand All @@ -64,23 +88,32 @@ const AvailablePlugins = () => {
]
}

const pluginName = row.name;
const pluginType = row.type;
const pluginVersion = row.version;
return [
<GridActionsCellItem
key={nanoid()}
icon={<FileDownloadIcon/>}
label="Download"
className="textPrimary"
onClick={() => onInstallClick(pluginId)}
onClick={() => onInstallClick(pluginId, pluginName, pluginType, pluginVersion)}
color="inherit"
/>
];
}

return (
<Box>
<SearchBar />
<BasePlugins plugins={availablePlugins}
setPlugins={setAvailablePlugins}
<Row className='grid-tools'>
<Col>
<SearchBar />
</Col>
<Col className='actions'>
<Button onClick={() => refreshAvailablePlugins(true)}><RefreshIcon/></Button>
</Col>
</Row>
<BasePlugins plugins={displayedPlugins}
loadingMessage="Loading all available plugins..."
onRefreshCallback={onRefreshCallback}
getRowActions={getRowActions}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {useEffect, useMemo, useState} from 'react';
import React, {useMemo, useState} from 'react';
import {Box} from '@mui/material';
import XDataGrid from '../XDataGrid';
import {getPlugins, getPluginsGridHeaders, getPluginsGridRows} from './mocksHelper';
import {getPluginsGridHeaders, getPluginsGridRows} from './mocksHelper';
import styles from '../../../styles/components/plugins-marketplace/BasePlugins.module.scss';

const DEFAULT_LOADING_MESSAGE = 'Loading plugins...';
Expand All @@ -12,36 +12,21 @@ const initialState = {
};

const BasePlugins = (props) => {
const {plugins, setPlugins, getRowActions, onRefreshCallback, loadingMessage = DEFAULT_LOADING_MESSAGE} = {...props};
const {plugins, getRowActions, loadingMessage = DEFAULT_LOADING_MESSAGE} = {...props};

const [isLoadingPlugins, setIsLoadingPlugins] = useState(false);

useEffect(() => {
setPluginsList();
}, []);
const [isLoadingPlugins] = useState(false);

const rows = useMemo(() => {
return getPluginsGridRows(plugins);
}, [plugins]);

const setPluginsList = () => {
if(plugins.length === 0) {
setIsLoadingPlugins(true);
getPlugins(false, false, true).then(plugins => {
setPlugins(plugins);
}).finally(() => {
setIsLoadingPlugins(false);
});
}
};

// eslint-disable-next-line no-unused-vars
const onRefreshClick = () => {
setPluginsList();
if(onRefreshCallback) {
onRefreshCallback();
}
}
// // eslint-disable-next-line no-unused-vars
// const onRefreshClick = () => {
// setPluginsList();
// if(onRefreshCallback) {
// onRefreshCallback();
// }
// }

return (
<Box className={styles['plugins-wrapper']}>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, {createContext} from 'react';

type PluginsContextType = {
availablePlugins: any;
installedPlugins: any;
refreshAvailablePlugins: (force: boolean) => void;
refreshInstalledPlugins: () => void;
}

export const PluginsContext = createContext<Partial<PluginsContextType>>({});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {nanoid} from 'nanoid';
import {generatePluginId} from './utils';

const HEADER_SUFFIX = '--header';

Expand All @@ -13,7 +14,7 @@ const generateData = (num, isInstalled= false) => {
const description = `${type} ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum`;
const obj = {
id: nanoid(),
name: `all plugin ${i}`,
name: `plugin_${i}`,
version: versions[Math.floor(Math.random() * versions.length)],
type: type,
author: `Monkey Team - ${nanoid()}`,
Expand Down Expand Up @@ -75,12 +76,14 @@ export const upgradePlugin = (id, success = true) => {
})
}

// Returns GridColDef[]
export const getPluginsGridHeaders = (getRowActions) => [
{headerName: 'Name', field: 'name', sortable: true, filterable: false, flex: 0.4, minWidth: 150, flexValue: 0.5},
{headerName: 'Version', field: 'version', sortable: false, filterable: false, flex: 0.1, minWidth: 100, flexValue: 0.5},
{headerName: 'Type', field: 'type', sortable: true, filterable: false, flex: 0.2, minWidth: 150, flexValue: 0.5},
{headerName: 'Author', field: 'author', sortable: true, filterable: false, minWidth: 150, flex: 0.25, flexValue: 0.5},
{headerName: 'Description', field: 'description', sortable: false, filterable: false, minWidth: 150, flex: 1},
// This column is a GridActionsColDef
{
headerName: '',
field: 'row_actions',
Expand All @@ -90,22 +93,26 @@ export const getPluginsGridHeaders = (getRowActions) => [
flexValue: 0.5,
headerClassName: `row-actions${HEADER_SUFFIX}`,
cellClassName: `row-actions`,
getActions: ({id}) => {
return getRowActions(id);
// params is a GridRowParams
getActions: (params) => {
return getRowActions(params.row);
}
}
]

export const getPluginsGridRows = (pluginsList) => {
return pluginsList?.map(plugin => {
const {id, name, version, type, author, description} = {...plugin};
return {
id: id || nanoid(),
let plugins = [];
for (const plugin of pluginsList) {
const {name, version, type_, author, description} = {...plugin};
plugins.push({
id: generatePluginId(plugin),
name: name,
version: version,
type: type,
type: type_,
author: author,
description: description
}
});
});
}

return plugins;
}
Loading

0 comments on commit 6e19b73

Please sign in to comment.