feat: disable plugins by default, require admin approval

This commit is contained in:
Linus Rath
2026-04-02 13:50:02 +02:00
parent 9ee25c930e
commit 6ee0f6a40a
8 changed files with 84 additions and 21 deletions
+28 -5
View File
@@ -20,7 +20,7 @@ const STATUS_COLORS: Record<PluginStatus, string> = {
export function PluginsSettings() {
const { plugins, installPlugin, uninstallPlugin, enablePlugin, disablePlugin, updatePluginSettings, initializePlugins, initialized } = usePluginStore();
const { isFeatureEnabled, isPluginForceEnabled, fetchPolicy, loaded } = usePolicyStore();
const { isFeatureEnabled, isPluginForceEnabled, isPluginApproved, fetchPolicy, loaded } = usePolicyStore();
const [isUploading, setIsUploading] = useState(false);
const [expandedPlugin, setExpandedPlugin] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -75,6 +75,13 @@ export function PluginsSettings() {
return;
}
const requireApproval = isFeatureEnabled('requirePluginApproval');
const isApproved = plugin.adminApproved || plugin.managed || isPluginApproved(plugin.id);
if (!plugin.enabled && requireApproval && !isApproved) {
toast.info(`Plugin "${plugin.name}" requires admin approval before it can be enabled`);
return;
}
if (plugin.enabled) {
disablePlugin(plugin.id);
toast.info(`Plugin "${plugin.name}" disabled`);
@@ -108,20 +115,26 @@ export function PluginsSettings() {
</div>
) : (
<div className="space-y-2">
{plugins.map(plugin => (
{plugins.map(plugin => {
const requireApproval = isFeatureEnabled('requirePluginApproval');
const isApproved = plugin.adminApproved || plugin.managed || isPluginApproved(plugin.id);
const needsApproval = requireApproval && !isApproved;
return (
<PluginCard
key={plugin.id}
plugin={plugin}
isExpanded={expandedPlugin === plugin.id}
isForceEnabled={plugin.forceEnabled || isPluginForceEnabled(plugin.id)}
isManaged={Boolean(plugin.managed)}
needsApproval={needsApproval}
controlsDisabled={!initialized}
onToggleExpand={() => setExpandedPlugin(expandedPlugin === plugin.id ? null : plugin.id)}
onToggle={() => handleToggle(plugin)}
onUninstall={() => handleUninstall(plugin)}
onUpdateSettings={(settings) => updatePluginSettings(plugin.id, settings)}
/>
))}
);
})}
</div>
)}
@@ -166,6 +179,7 @@ interface PluginCardProps {
isExpanded: boolean;
isForceEnabled: boolean;
isManaged: boolean;
needsApproval: boolean;
controlsDisabled: boolean;
onToggleExpand: () => void;
onToggle: () => void;
@@ -173,7 +187,7 @@ interface PluginCardProps {
onUpdateSettings: (settings: Record<string, unknown>) => void;
}
function PluginCard({ plugin, isExpanded, isForceEnabled, isManaged, controlsDisabled, onToggleExpand, onToggle, onUninstall, onUpdateSettings }: PluginCardProps) {
function PluginCard({ plugin, isExpanded, isForceEnabled, isManaged, needsApproval, controlsDisabled, onToggleExpand, onToggle, onUninstall, onUpdateSettings }: PluginCardProps) {
return (
<div className={cn(
'rounded-lg border transition-colors',
@@ -197,6 +211,11 @@ function PluginCard({ plugin, isExpanded, isForceEnabled, isManaged, controlsDis
<Server className="w-2.5 h-2.5" /> Managed
</span>
)}
{needsApproval && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full font-medium bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400">
Awaiting approval
</span>
)}
</div>
<div className="flex items-center gap-2 mt-0.5">
<span className="text-xs text-muted-foreground">{plugin.author}</span>
@@ -206,7 +225,7 @@ function PluginCard({ plugin, isExpanded, isForceEnabled, isManaged, controlsDis
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<ToggleSwitch checked={plugin.enabled} onChange={onToggle} disabled={controlsDisabled || isForceEnabled} />
<ToggleSwitch checked={plugin.enabled} onChange={onToggle} disabled={controlsDisabled || isForceEnabled || needsApproval} />
</div>
</div>
@@ -217,6 +236,10 @@ function PluginCard({ plugin, isExpanded, isForceEnabled, isManaged, controlsDis
<p className="text-xs text-amber-600 dark:text-amber-400">This plugin is forced by an administrator and cannot be disabled or uninstalled.</p>
)}
{needsApproval && (
<p className="text-xs text-orange-600 dark:text-orange-400">This plugin is awaiting admin approval and cannot be enabled until an administrator approves it.</p>
)}
{/* Description */}
{plugin.description && (
<p className="text-xs text-muted-foreground">{plugin.description}</p>