feat: plugin install/uninstall lifecycle + UI buttons
- AppConfig: add InstalledPlugins []string - Manager.Discover(): no config dependency, all plugins start inactive - Manager.SyncConfig(): apply installed/enabled state from AppConfig - Manager.Enable(): works for plugins without on_install hook - Manager.Install/Uninstall(): run on_install/on_uninstall hooks - ActivatePlugin: skip if HasInstall && !Installed - ReloadPlugins: Discover → SyncConfig → InitRuntimes - Bindings: InstallPlugin, UninstallPlugin - SettingsPlugins: install/uninstall buttons, toggle only after install - Calendar: migration moved from on_init to on_install, on_uninstall drops tables - Tests: all 12 pass (manager + runtime + calendar)
This commit is contained in:
@@ -18,6 +18,8 @@ type PluginDTO struct {
|
||||
Author string `json:"author,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Active bool `json:"active"`
|
||||
Installed bool `json:"installed"`
|
||||
HasInstall bool `json:"hasInstall"`
|
||||
HasPanel bool `json:"hasPanel"`
|
||||
HasSettings bool `json:"hasSettings"`
|
||||
UIContribs UIContribDTO `json:"uiContribs"`
|
||||
@@ -89,6 +91,8 @@ func (a *App) ListPlugins() []PluginDTO {
|
||||
Author: p.Meta.Author,
|
||||
Description: p.Meta.Description,
|
||||
Active: active,
|
||||
Installed: p.Installed,
|
||||
HasInstall: p.HasInstall,
|
||||
HasPanel: hasPanel,
|
||||
UIContribs: contribs,
|
||||
})
|
||||
@@ -97,11 +101,22 @@ func (a *App) ListPlugins() []PluginDTO {
|
||||
}
|
||||
|
||||
// SetPluginEnabled persists the enabled/disabled state and applies it to the runtime.
|
||||
// Returns error if the plugin is not installed but has install lifecycle.
|
||||
func (a *App) SetPluginEnabled(name string, enabled bool) error {
|
||||
if a.plugins == nil {
|
||||
return fmt.Errorf("plugin manager not ready")
|
||||
}
|
||||
|
||||
if enabled {
|
||||
if err := a.plugins.Enable(name); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := a.plugins.Disable(name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
appCfg, _ := config.LoadAppConfig()
|
||||
if appCfg == nil {
|
||||
appCfg = config.DefaultAppConfig()
|
||||
@@ -207,26 +222,39 @@ func (a *App) ReloadPlugins() error {
|
||||
log.Print("[plugins] reload requested")
|
||||
a.plugins.CloseRuntimes()
|
||||
|
||||
appCfg, _ := config.LoadAppConfig()
|
||||
if appCfg == nil {
|
||||
appCfg = config.DefaultAppConfig()
|
||||
}
|
||||
enabledSet := make(map[string]bool)
|
||||
for _, name := range appCfg.EnabledPlugins {
|
||||
enabledSet[name] = true
|
||||
}
|
||||
a.plugins.Discover()
|
||||
a.plugins.InitRuntimes()
|
||||
|
||||
appCfg, _ := config.LoadAppConfig()
|
||||
a.plugins.SyncConfig(appCfg)
|
||||
|
||||
// Apply enable/disable state from config: deactivate everything not in enabled set
|
||||
for _, p := range a.plugins.Plugins() {
|
||||
if !enabledSet[p.Meta.Name] {
|
||||
if !p.Active || !p.Installed {
|
||||
a.plugins.DeactivatePlugin(p.Meta.Name)
|
||||
}
|
||||
}
|
||||
|
||||
a.plugins.InitRuntimes()
|
||||
a.plugins.CallInitHooks()
|
||||
a.plugins.StartSchedulers()
|
||||
log.Print("[plugins] reload complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstallPlugin creates plugin's database tables and defaults via on_install hook.
|
||||
// Does NOT activate the plugin — use SetPluginEnabled after.
|
||||
func (a *App) InstallPlugin(name string) error {
|
||||
if a.plugins == nil {
|
||||
return fmt.Errorf("plugin manager not ready")
|
||||
}
|
||||
return a.plugins.Install(name)
|
||||
}
|
||||
|
||||
// UninstallPlugin drops plugin's database tables and cleans data via on_uninstall hook.
|
||||
// Disables the plugin first if active. Does NOT delete plugin files from disk.
|
||||
func (a *App) UninstallPlugin(name string) error {
|
||||
if a.plugins == nil {
|
||||
return fmt.Errorf("plugin manager not ready")
|
||||
}
|
||||
return a.plugins.Uninstall(name)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user