feat: app-to-app integration framework + OnlyOffice handlers
Generic integration system for connecting deployed apps via toggle UI. First handlers: OnlyOffice→FileBrowser (config.yaml patch) and OnlyOffice→Nextcloud (occ CLI). Lifecycle hooks auto-suspend on stop and re-apply on start. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,17 @@ type Settings struct {
|
||||
|
||||
// Geo-restriction settings (Cloudflare WAF rules)
|
||||
GeoRestriction *GeoRestriction `json:"geo_restriction,omitempty"`
|
||||
|
||||
// App-to-app integration state (e.g., "onlyoffice:filebrowser" → state)
|
||||
Integrations map[string]IntegrationState `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
// IntegrationState holds the state of a provider:target integration pair.
|
||||
type IntegrationState struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
EnabledAt string `json:"enabled_at,omitempty"` // RFC3339
|
||||
Status string `json:"status,omitempty"` // "active", "error", "disabled", "provider_stopped", "target_unavailable"
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
}
|
||||
|
||||
// AppBackupPrefs holds per-app backup toggle state.
|
||||
@@ -924,3 +935,65 @@ func (s *Settings) SetGeoSyncState(zoneID, rulesetID, syncError string) error {
|
||||
}
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// --- App-to-app integrations ---
|
||||
|
||||
// GetIntegrationState returns the state for a specific integration key (e.g., "onlyoffice:filebrowser").
|
||||
func (s *Settings) GetIntegrationState(key string) (IntegrationState, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
if s.Integrations == nil {
|
||||
return IntegrationState{}, false
|
||||
}
|
||||
state, ok := s.Integrations[key]
|
||||
return state, ok
|
||||
}
|
||||
|
||||
// SetIntegrationState updates (or creates) the state for a single integration key.
|
||||
func (s *Settings) SetIntegrationState(key string, state IntegrationState) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.Integrations == nil {
|
||||
s.Integrations = make(map[string]IntegrationState)
|
||||
}
|
||||
s.Integrations[key] = state
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// RemoveIntegrationState removes an integration key entirely.
|
||||
func (s *Settings) RemoveIntegrationState(key string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.Integrations != nil {
|
||||
delete(s.Integrations, key)
|
||||
}
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// GetIntegrationsForProvider returns all integration states where key starts with "provider:".
|
||||
func (s *Settings) GetIntegrationsForProvider(provider string) map[string]IntegrationState {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
prefix := provider + ":"
|
||||
result := make(map[string]IntegrationState)
|
||||
for k, v := range s.Integrations {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetIntegrationsForTarget returns all integration states where key ends with ":target".
|
||||
func (s *Settings) GetIntegrationsForTarget(target string) map[string]IntegrationState {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
suffix := ":" + target
|
||||
result := make(map[string]IntegrationState)
|
||||
for k, v := range s.Integrations {
|
||||
if strings.HasSuffix(k, suffix) {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user