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:
@@ -478,6 +478,12 @@ func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug s
|
||||
data["HasOptionalConfig"] = found.Meta.HasOptionalConfig()
|
||||
data["EffectiveSubdomain"] = effectiveSubdomain
|
||||
|
||||
// App-to-app integrations
|
||||
if found.Meta.HasIntegrations() && s.integrationMgr != nil {
|
||||
data["HasIntegrations"] = true
|
||||
data["Integrations"] = s.integrationMgr.ListForProvider(found.Meta.Slug)
|
||||
}
|
||||
|
||||
// Geo-restriction per-app data
|
||||
geo := s.settings.GetGeoRestriction()
|
||||
if geo != nil && geo.Enabled && s.cfg.Infrastructure.CFAPIToken != "" {
|
||||
@@ -1615,6 +1621,10 @@ func (s *Server) SyncFileBrowserMounts() {
|
||||
s.logger.Printf("[ERROR] Failed to recreate FileBrowser: %s — %v", string(out), err)
|
||||
} else {
|
||||
s.logger.Printf("[INFO] FileBrowser mounts synced — %d storage path(s), config updated", len(paths))
|
||||
// Re-apply active integrations (config regeneration overwrites config.yaml)
|
||||
if s.integrationMgr != nil {
|
||||
go s.integrationMgr.OnStackStart(context.Background(), "filebrowser")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/assets"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/integrations"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/monitor"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/notify"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/scheduler"
|
||||
@@ -74,6 +75,9 @@ type Server struct {
|
||||
// Asset syncer for Hub-managed assets (optional)
|
||||
assetsSyncer *assets.Syncer
|
||||
|
||||
// App-to-app integration manager (optional)
|
||||
integrationMgr *integrations.Manager
|
||||
|
||||
// Debug mode support
|
||||
logBuffer *LogBuffer
|
||||
debugCallbacks *DebugCallbacks
|
||||
@@ -163,6 +167,11 @@ func (s *Server) SetAssetsSyncer(as *assets.Syncer) {
|
||||
s.assetsSyncer = as
|
||||
}
|
||||
|
||||
// SetIntegrationManager sets the app-to-app integration manager.
|
||||
func (s *Server) SetIntegrationManager(mgr *integrations.Manager) {
|
||||
s.integrationMgr = mgr
|
||||
}
|
||||
|
||||
// SetLogBuffer sets the in-memory log ring buffer for the debug log viewer.
|
||||
func (s *Server) SetLogBuffer(lb *LogBuffer) {
|
||||
s.logBuffer = lb
|
||||
|
||||
@@ -179,6 +179,67 @@ async function saveOptionalConfig(stackName) {
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{if .HasIntegrations}}
|
||||
<div class="app-optional-config">
|
||||
<h3>Integrációk</h3>
|
||||
<p class="config-group-desc">
|
||||
Más telepített alkalmazásokkal való összekapcsolás. Az integráció automatikusan felfüggesztődik, ha bármelyik alkalmazás leáll, és újraaktiválódik indításkor.
|
||||
</p>
|
||||
|
||||
{{range .Integrations}}
|
||||
<div class="config-field" style="display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.75rem 0;border-bottom:1px solid var(--border)">
|
||||
<div style="flex:1">
|
||||
<strong>{{.Label}}</strong>
|
||||
<p class="config-field-help" style="margin:0">{{.Description}}</p>
|
||||
{{if not .TargetDeployed}}
|
||||
<span class="badge badge-muted" style="margin-top:.25rem;display:inline-block">Nincs telepítve</span>
|
||||
{{else if not .TargetRunning}}
|
||||
<span class="badge badge-orphaned" style="margin-top:.25rem;display:inline-block">Célalkalmazás leállítva</span>
|
||||
{{else if eq .Status "error"}}
|
||||
<span class="badge badge-orphaned" style="margin-top:.25rem;display:inline-block">Hiba</span>
|
||||
{{else if .Enabled}}
|
||||
<span class="badge badge-ok" style="margin-top:.25rem;display:inline-block">Aktív</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox"
|
||||
{{if .Enabled}}checked{{end}}
|
||||
{{if not .TargetDeployed}}disabled title="A célalkalmazás nincs telepítve"{{end}}
|
||||
{{if and .TargetDeployed (not .TargetRunning)}}disabled title="A célalkalmazás nem fut"{{end}}
|
||||
onchange="toggleIntegration('{{$.Stack.Name}}', '{{.Target}}', this.checked, this)">
|
||||
<span class="toggle-label"></span>
|
||||
</label>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function toggleIntegration(provider, target, enable, checkbox) {
|
||||
checkbox.disabled = true;
|
||||
try {
|
||||
var resp = await fetch('/api/integrations/' + provider + '/' + target, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({enable: enable})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (!data.ok) {
|
||||
checkbox.checked = !enable;
|
||||
alert(data.error || 'Hiba történt');
|
||||
} else {
|
||||
// Reload to update status badges
|
||||
setTimeout(function(){ location.reload(); }, 500);
|
||||
return;
|
||||
}
|
||||
} catch(err) {
|
||||
checkbox.checked = !enable;
|
||||
alert('Hálózati hiba');
|
||||
}
|
||||
checkbox.disabled = false;
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{if .GeoGlobalEnabled}}
|
||||
<div class="app-optional-config">
|
||||
<h3>Földrajzi korlátozás</h3>
|
||||
|
||||
Reference in New Issue
Block a user