New: Improve Plugin Installation and Removal Process

Fixes restart loops
reduces github bans
improves UX with messaging for restart
improves version notes
This commit is contained in:
bakerboy448
2025-09-07 17:53:26 -05:00
committed by retrodadson
parent fcfc60a276
commit d9bbed6875
9 changed files with 292 additions and 65 deletions
+48 -2
View File
@@ -7,11 +7,13 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import PluginRow from './PluginRow';
const columns = [
@@ -78,7 +80,15 @@ class Plugins extends Component {
isInstallingPlugin,
onInstallPluginPress,
isUninstallingPlugin,
onUninstallPluginPress
onUninstallPluginPress,
isRestartRequiredModalOpen,
onCloseRestartRequiredModal,
pluginOwner,
pluginName,
pluginVersion,
pluginAction,
pluginDetailsUrl,
pluginBranch
} = this.props;
const {
@@ -87,6 +97,23 @@ class Plugins extends Component {
const noPlugins = isPopulated && !error && !items.length;
// Build modal title and message
let modalTitle = translate('RestartRequired');
let modalMessage = translate('LidarrRequiresRestartToApplyPluginChanges');
if (pluginOwner && pluginName && pluginAction) {
const versionText = pluginVersion ? ` v${pluginVersion}` : '';
const branchText = pluginBranch ? ` (${pluginBranch})` : '';
const actionText = pluginAction === 'install' ? 'installed' : 'uninstalled';
modalTitle = `Plugin ${actionText} - ${translate('RestartRequired')}`;
if (pluginDetailsUrl) {
modalMessage = `Plugin: [${pluginOwner}/${pluginName}]${versionText}${branchText}\n\nInstalled from:\n${pluginDetailsUrl}\n\nPlease restart Lidarr to apply changes.`;
} else {
modalMessage = `Plugin: [${pluginOwner}/${pluginName}]${versionText}${branchText}\n\nPlugin ${actionText} successfully.\n\nPlease restart Lidarr to apply changes.`;
}
}
return (
<PageContent title="Plugins">
<PageContentBody>
@@ -148,6 +175,17 @@ class Plugins extends Component {
</Table>
}
</FieldSet>
<ConfirmModal
isOpen={isRestartRequiredModalOpen}
kind={kinds.INFO}
title={modalTitle}
message={modalMessage}
confirmLabel={translate('Ok')}
hideCancelButton={true}
onConfirm={onCloseRestartRequiredModal}
onCancel={onCloseRestartRequiredModal}
/>
</PageContentBody>
</PageContent>
);
@@ -162,7 +200,15 @@ Plugins.propTypes = {
isInstallingPlugin: PropTypes.bool.isRequired,
onInstallPluginPress: PropTypes.func.isRequired,
isUninstallingPlugin: PropTypes.bool.isRequired,
onUninstallPluginPress: PropTypes.func.isRequired
onUninstallPluginPress: PropTypes.func.isRequired,
isRestartRequiredModalOpen: PropTypes.bool,
onCloseRestartRequiredModal: PropTypes.func,
pluginOwner: PropTypes.string,
pluginName: PropTypes.string,
pluginVersion: PropTypes.string,
pluginAction: PropTypes.string,
pluginDetailsUrl: PropTypes.string,
pluginBranch: PropTypes.string
};
export default Plugins;
@@ -38,6 +38,20 @@ class PluginsConnector extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isRestartRequiredModalOpen: false,
pluginOwner: '',
pluginName: '',
pluginVersion: '',
pluginAction: '',
pluginDetailsUrl: '',
pluginBranch: ''
};
}
componentDidMount() {
registerPagePopulator(this.repopulate);
@@ -59,27 +73,108 @@ class PluginsConnector extends Component {
// Listeners
onInstallPluginPress = (url) => {
this.currentPluginOperation = { action: 'install', url };
this.props.dispatchExecuteCommand({
name: commandNames.INSTALL_PLUGIN,
githubUrl: url
githubUrl: url,
commandFinished: this.onPluginCommandFinished
});
};
onUninstallPluginPress = (url) => {
this.currentPluginOperation = { action: 'uninstall', url };
this.props.dispatchExecuteCommand({
name: commandNames.UNINSTALL_PLUGIN,
githubUrl: url
githubUrl: url,
commandFinished: this.onPluginCommandFinished
});
};
onPluginCommandFinished = (command) => {
let pluginOwner = '';
let pluginName = '';
let pluginVersion = '';
let pluginAction = '';
let pluginDetailsUrl = '';
let pluginBranch = '';
if (this.currentPluginOperation && command) {
const url = this.currentPluginOperation.url;
const match = url.match(/github\.com\/([^/]+)\/([^/]+)/);
if (match) {
[, pluginOwner, pluginName] = match;
pluginAction = this.currentPluginOperation.action;
// Extract branch from GitHub URL
if (url.includes('/tree/')) {
const branchMatch = url.match(/\/tree\/([^/]+)/);
if (branchMatch) {
pluginBranch = branchMatch[1];
}
}
if (this.currentPluginOperation.action === 'install') {
if (command && command.message) {
const pluginMatch = command.message.match(/Plugin \[([^/]+)\/([^\]]+)\] v([0-9.]+) installed/);
if (pluginMatch) {
pluginVersion = pluginMatch[3];
}
console.log('Plugin match result:', pluginMatch);
}
pluginDetailsUrl = url;
} else {
if (command && command.message) {
const pluginMatch = command.message.match(/Plugin \[([^/]+)\/([^\]]+)\] v([0-9.]+) uninstalled/);
if (pluginMatch) {
pluginVersion = pluginMatch[3];
}
}
}
}
}
this.setState({
isRestartRequiredModalOpen: true,
pluginOwner,
pluginName,
pluginVersion,
pluginAction,
pluginDetailsUrl,
pluginBranch
});
this.repopulate();
};
onCloseRestartRequiredModal = () => {
this.setState({
isRestartRequiredModalOpen: false,
pluginOwner: '',
pluginName: '',
pluginVersion: '',
pluginAction: '',
pluginDetailsUrl: '',
pluginBranch: ''
});
this.currentPluginOperation = null;
};
//
// Render
render() {
return (
<Plugins
isRestartRequiredModalOpen={this.state.isRestartRequiredModalOpen}
pluginOwner={this.state.pluginOwner}
pluginName={this.state.pluginName}
pluginVersion={this.state.pluginVersion}
pluginAction={this.state.pluginAction}
pluginDetailsUrl={this.state.pluginDetailsUrl}
pluginBranch={this.state.pluginBranch}
onInstallPluginPress={this.onInstallPluginPress}
onUninstallPluginPress={this.onUninstallPluginPress}
onCloseRestartRequiredModal={this.onCloseRestartRequiredModal}
{...this.props}
/>
);
@@ -89,7 +184,8 @@ class PluginsConnector extends Component {
PluginsConnector.propTypes = {
dispatchFetchInstalledPlugins: PropTypes.func.isRequired,
dispatchExecuteCommand: PropTypes.func.isRequired
dispatchExecuteCommand: PropTypes.func.isRequired,
items: PropTypes.array
};
export default connect(createMapStateToProps, mapDispatchToProps)(PluginsConnector);