diff --git a/frontend/src/components/PluginWarnings.jsx b/frontend/src/components/PluginWarnings.jsx new file mode 100644 index 00000000..90b7db47 --- /dev/null +++ b/frontend/src/components/PluginWarnings.jsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { Box, Text } from '@mantine/core'; +import { AlertTriangle, Info, OctagonAlert } from 'lucide-react'; +import dispatcharrLogo from '../images/logo.png'; + +export const PluginSecurityWarning = ({ children }) => ( + + + + + + {children} + + +); + +export const PluginSupportDisclaimer = () => ( + + + Dispatcharr + + + Dispatcharr community support cannot assist with third-party plugin + issues. For help, use the plugin's Discord thread or submit an issue + on the plugin's repository. + + +); + +export const PluginDowngradeWarning = ({ children }) => ( + + + + + + {children} + + +); + +export const PluginInfoNote = ({ children }) => ( + + + + + + {children} + + +); + +export const PluginRestartWarning = () => ( + + + + + + Importing a plugin may briefly restart the backend (you might see a + temporary disconnect). Please wait a few seconds and the app will + reconnect automatically. + + +); diff --git a/frontend/src/components/cards/AvailablePluginCard.jsx b/frontend/src/components/cards/AvailablePluginCard.jsx index 5866f51a..7e594428 100644 --- a/frontend/src/components/cards/AvailablePluginCard.jsx +++ b/frontend/src/components/cards/AvailablePluginCard.jsx @@ -27,6 +27,12 @@ import { ShieldCheck, Trash2, } from 'lucide-react'; +import { + PluginDowngradeWarning, + PluginInfoNote, + PluginSecurityWarning, + PluginSupportDisclaimer, +} from '../PluginWarnings.jsx'; import { useNavigate, useLocation } from 'react-router-dom'; import API from '../../api'; import { usePluginStore } from '../../store/plugins'; @@ -730,6 +736,13 @@ const AvailablePluginCard = ({ stop working with future versions of Dispatcharr. It is recommended to look for an alternative. + + Plugins run server-side code with full access to your Dispatcharr + instance and its data. Only install plugins from developers you + trust. Malicious plugins could read or modify data, call internal + APIs, or perform unwanted actions. + + Do you still want to proceed? @@ -830,38 +843,37 @@ const AvailablePluginCard = ({ )} . - + Plugins run server-side code with full access to your Dispatcharr instance and its data. Only install plugins from developers you trust. Malicious plugins could read or modify data, call internal APIs, or perform unwanted actions. - + + {isDowngrade && ( - - Warning: Downgrading may cause issues with saved - settings or data. - + + Downgrading may cause issues with saved settings or data. + )} {isBadSig && ( - - Warning: This repository has an invalid or unverified - signature. Installing plugins from unverified sources may be - risky. - + + This repository has an invalid or unverified signature. + Installing plugins from unverified sources may be risky. + )} {plugin.install_status === 'unmanaged' && ( - - Note: This plugin was installed manually. Installing - from this repo will bring it under repo management and enable - future update checks. - + + This plugin was installed manually. Installing from this repo + will bring it under repo management and enable future update + checks. + )} {plugin.install_status === 'different_repo' && ( - - Note: This plugin is currently managed by{' '} + + This plugin is currently managed by{' '} {plugin.installed_source_repo_name || 'another repo'}. Installing will transfer management to this repo. - + )} Are you sure you want to proceed? diff --git a/frontend/src/components/cards/PluginCard.jsx b/frontend/src/components/cards/PluginCard.jsx index 9cecc966..f1b5d5f6 100644 --- a/frontend/src/components/cards/PluginCard.jsx +++ b/frontend/src/components/cards/PluginCard.jsx @@ -17,7 +17,7 @@ import { Text, Tooltip, } from '@mantine/core'; -import { Ban, Check, FlaskConical, Info, RefreshCw, Settings, Trash2, Zap } from 'lucide-react'; +import { Ban, Check, Download, FlaskConical, Info, RefreshCw, Settings, Trash2, Zap } from 'lucide-react'; import { getConfirmationDetails } from '../../utils/cards/PluginCardUtils.js'; import { SUBSCRIPTION_EVENTS } from '../../constants.js'; import useSettingsStore from '../../store/settings.jsx'; @@ -25,6 +25,11 @@ import { usePluginStore } from '../../store/plugins.jsx'; import API from '../../api'; import PluginDetailPanel from '../PluginDetailPanel.jsx'; import { compareVersions } from '../pluginUtils.js'; +import { + PluginDowngradeWarning, + PluginSecurityWarning, + PluginSupportDisclaimer, +} from '../PluginWarnings.jsx'; const PluginFieldList = ({ plugin, settings, updateField }) => { return plugin.fields.map((f) => ( @@ -128,6 +133,8 @@ const PluginCard = ({ const [selectedVersion, setSelectedVersion] = useState(null); const [installing, setInstalling] = useState(false); const [uninstalling] = useState(false); + const [installConfirmOpen, setInstallConfirmOpen] = useState(false); + const [pendingInstallParams, setPendingInstallParams] = useState(null); const installPlugin = usePluginStore((s) => s.installPlugin); @@ -311,14 +318,18 @@ const PluginCard = ({ }; const handleDetailInstall = async (params) => { + setPendingInstallParams(params); + setInstallConfirmOpen(true); + }; + + const confirmAndInstall = async () => { + if (!pendingInstallParams) return; + const params = pendingInstallParams; const selVer = params.version; const isDown = plugin.version && compareVersions(selVer, plugin.version) < 0; const action = isDown ? 'downgrade' : 'update'; - const confirmed = await onRequestConfirm( - `${isDown ? 'Downgrade' : 'Update'} ${plugin.name}?`, - `${isDown ? 'Downgrade' : 'Update'} from v${plugin.version} to v${selVer}?` - ); - if (!confirmed) return; + setInstallConfirmOpen(false); + setPendingInstallParams(null); setInstalling(true); try { const result = await installPlugin(params); @@ -616,6 +627,71 @@ const PluginCard = ({ )} + + {/* Install confirmation modal */} + {(() => { + const selVer = pendingInstallParams?.version; + const isDown = plugin.version && selVer && compareVersions(selVer, plugin.version) < 0; + const actionLabel = isDown ? 'Downgrade' : 'Update'; + return ( + { + setInstallConfirmOpen(false); + setPendingInstallParams(null); + }} + zIndex={300} + title={ + + {isDown + ? + : } + Confirm {actionLabel} + + } + size="sm" + > + + + You are about to {actionLabel.toLowerCase()} {plugin.name}{' '} + from v{plugin.version} to v{selVer}. + + + Plugins run server-side code with full access to your Dispatcharr + instance and its data. Only install plugins from developers you + trust. Malicious plugins could read or modify data, call internal + APIs, or perform unwanted actions. + + + {isDown && ( + + Downgrading may cause issues with saved settings or data. + + )} + Are you sure you want to proceed? + + + + + + + ); + })()} ); }; diff --git a/frontend/src/pages/Plugins.jsx b/frontend/src/pages/Plugins.jsx index 36002776..98b8d926 100644 --- a/frontend/src/pages/Plugins.jsx +++ b/frontend/src/pages/Plugins.jsx @@ -41,6 +41,11 @@ import { } from '../utils/pages/PluginsUtils.js'; import { RefreshCcw, Search } from 'lucide-react'; import ErrorBoundary from '../components/ErrorBoundary.jsx'; +import { + PluginRestartWarning, + PluginSecurityWarning, + PluginSupportDisclaimer, +} from '../components/PluginWarnings.jsx'; const PluginCard = React.lazy( () => import('../components/cards/PluginCard.jsx') ); @@ -426,11 +431,8 @@ export default function PluginsPage() { Upload a ZIP containing your plugin folder or package. - - Importing a plugin may briefly restart the backend (you might see a - temporary disconnect). Please wait a few seconds and the app will - reconnect automatically. - + + files[0] && setImportFile(files[0])} onReject={() => {}} @@ -536,16 +538,14 @@ export default function PluginsPage() { zIndex={300} > - + Plugins run server-side code with full access to your Dispatcharr instance and its data. Only enable plugins from developers you - trust. - - - Why: Malicious plugins could read or modify data, call internal + trust. Malicious plugins could read or modify data, call internal APIs, or perform unwanted actions. Review the source or trust the author before enabling. - + +