diff --git a/apply_control_panel_patch_and_pr.bat b/apply_control_panel_patch_and_pr.bat new file mode 100644 index 000000000..72f677c7a --- /dev/null +++ b/apply_control_panel_patch_and_pr.bat @@ -0,0 +1,13 @@ +@echo off +REM Wrapper batch pour lancer le script PowerShell depuis CMD +SETLOCAL +SET SCRIPT_DIR=%~dp0 +REM Si aucun argument fourni, lancer en mode non interactif avec la branche par défaut et -ForceSSH +IF "%~1"=="" ( + echo Aucun argument fourni -> exécution automatique non interactive avec ForceSSH + powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%apply_control_panel_patch_and_pr.ps1" -BranchName "feature/control-panel-compose-fix" -NonInteractive -ForceSSH +) ELSE ( + REM Transmettre tous les arguments fournis au script PowerShell + powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%apply_control_panel_patch_and_pr.ps1" %* +) +ENDLOCAL diff --git a/apply_control_panel_patch_and_pr.ps1 b/apply_control_panel_patch_and_pr.ps1 new file mode 100644 index 000000000..0b3190a59 --- /dev/null +++ b/apply_control_panel_patch_and_pr.ps1 @@ -0,0 +1,188 @@ +<# +Enhanced apply_control_panel_patch_and_pr.ps1 +Features: +- Branch name prompt (or non-interactive via -NonInteractive) +- Dry-run mode (-DryRun) +- Attempt gh auth login automatically on push failure +- Optional forced SSH remote before retrying push (-ForceSSH) +- Open the exact PR URL returned by gh when possible +#> + +param( + [string]$BranchName = 'feature/control-panel-compose-fix', + [switch]$DryRun, + [switch]$NonInteractive, + [switch]$ForceSSH +) + +$ErrorActionPreference = 'Stop' + +# Determine repo root (script path) +$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $repoRoot + +$patch = Join-Path $repoRoot 'control-panel-compose-fix.patch' + +if (-not (Test-Path $patch)) { + Write-Host "Patch not found at: $patch" -ForegroundColor Red + Write-Host "Place control-panel-compose-fix.patch in the repository root and re-run." -ForegroundColor Yellow + exit 1 +} + +# Determine branch name +if ($NonInteractive) { + $branch = $BranchName + Write-Host "Non-interactive mode : branch = $branch" +} else { + $branch = Read-Host -Prompt "Nom de la branche à créer/utiliser (appuie sur Entrée pour '$BranchName')" + if ([string]::IsNullOrWhiteSpace($branch)) { $branch = $BranchName } + Write-Host "Branch choisie : $branch" +} + +if ($DryRun) { Write-Host "Mode dry-run activé : aucune modification ne sera apportée." -ForegroundColor Yellow; Show-Plan; exit 0 } + +function Show-Plan { + Write-Host "Plan d'action :" -ForegroundColor Cyan + Write-Host " 1) Checkout/creer la branche: $branch" + Write-Host " 2) Appliquer le patch: $patch (git am -> git apply fallback)" + Write-Host " 3) Pousser la branche sur origin (avec tentative d'auth if needed)" + Write-Host " 4) Créer la PR via gh (si disponible) ou ouvrir la page de comparaison" +} + +Show-Plan + +# Ensure branch exists and check it out +try { + git rev-parse --verify $branch 2>$null | Out-Null + if ($LASTEXITCODE -eq 0) { + Write-Host "Checkout branche existante : $branch" + git checkout $branch + } else { + Write-Host "Création et checkout de la branche : $branch" + git checkout -b $branch + } +} catch { + Write-Error "Échec lors de la vérification/création de la branche: $_" + exit 1 +} + +# Apply patch preserving commits if possible +$applied = $false +try { + Write-Host "Tentative d'application via 'git am'..." + cmd /c "git am < \"$patch\"" + if ($LASTEXITCODE -eq 0) { + Write-Host "Patch appliqué avec succès via git am." + $applied = $true + } else { + Write-Host "git am a retourné un code non nul. Passage à git apply..." -ForegroundColor Yellow + } +} catch { + Write-Host "git am a échoué : $_" -ForegroundColor Yellow +} + +if (-not $applied) { + try { + Write-Host "Application via 'git apply --index'..." + git apply --index $patch + git add -A + git commit -m "infra: fix compose contexts & add control-panel seed`n`nAdds a targeted docker-compose build.context fix for Windows and a versioned control-panel seed.`n`nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + Write-Host "Patch appliqué et commit créé." + $applied = $true + } catch { + Write-Error "Échec de l'application du patch: $_" + Write-Host "Si des conflits apparaissent, résoudre puis exécuter 'git am --continue' ou 'git apply' manuellement." -ForegroundColor Yellow + exit 1 + } +} + +# Push branch to origin with retry logic. Optionally switch to SSH before retry. +function Try-Push { + try { + Write-Host "Pushing branch $branch to origin..." + git push -u origin $branch + Write-Host "Push succeeded." -ForegroundColor Green + return $true + } catch { + Write-Warning "git push failed: $_" + return $false + } +} + +$pushOk = Try-Push +if (-not $pushOk) { + if ($ForceSSH) { + Write-Host "-ForceSSH activé : on change origin en SSH et on retente le push..." -ForegroundColor Cyan + try { + git remote set-url origin git@github.com:Shamdon/openfoundry.git + Write-Host "Remote origin mis à jour en SSH. Retentative du push..." + $pushOk = Try-Push + if (-not $pushOk) { Write-Error "Push échoué même après passage en SSH."; exit 1 } + } catch { + Write-Error "Impossible de changer remote en SSH : $_"; exit 1 + } + } else { + # Attempt to help the user by invoking gh auth login if available + if (Get-Command gh -ErrorAction SilentlyContinue) { + Write-Host "Tentative d'authentification via 'gh auth login --with-browser'..." -ForegroundColor Cyan + try { + Start-Process gh -ArgumentList 'auth','login','--with-browser' -NoNewWindow -Wait + } catch { + Write-Warning "Impossible d'exécuter 'gh auth login': $_" + } + Write-Host "Re-essai du push..." + $pushOk = Try-Push + if (-not $pushOk) { Write-Error "Le push a échoué après tentative d'authentification. Vérifie tes droits et réessaie."; exit 1 } + } else { + Write-Host "gh CLI introuvable. Conseils : authentifie-toi ou configure SSH pour origin." -ForegroundColor Yellow + Write-Host "Exemples : gh auth login --with-browser OU git remote set-url origin git@github.com:Shamdon/openfoundry.git" -ForegroundColor Yellow + if (-not (Try-Push)) { Write-Error "Le push a échoué (vérifie les permissions)."; exit 1 } + } + } +} + +# Create PR using gh if available, otherwise open compare page in browser +$prTitle = 'infra: fix compose contexts & add control-panel seed' +$prBody = @" +This PR contains two small developer-facing changes to make the local dev stack and Control Panel seed easier to use: + +- infra/compose/docker-compose.yml: adjust build.context for edge-gateway-service and identity-federation-service so Compose resolves correctly on Windows dev checkouts (.. → ../..). This is a targeted fix to avoid a larger refactor. +- apps/web/public/control-panel-seed.json + CONTROL_PANEL_SEED_README.md: add a versioned, documented seed for the Control Panel settings (streaming_profiles etc.) and instructions for applying it. + +Why: these changes let developers bring up the gateway + identity locally and apply a durable control-panel seed (used in local development and CI). The changes were tested locally: gateway and identity started and the seed was successfully applied via the gateway's admin API. + +Notes for reviewers: +- The compose change is intentionally narrow and should be safe for most developer workflows, but please review for CI implications before merging. +- Do not enable the seed in production; it's a dev convenience. + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> +"@ + +if (Get-Command gh -ErrorAction SilentlyContinue) { + try { + Write-Host "Création de la PR via gh..." + # Use --json to get the URL where supported + $json = gh pr create --base main --head $branch --title $prTitle --body $prBody --json url 2>$null | Out-String + if ($json -and $json.Trim() -ne '') { + try { + $j = $json | ConvertFrom-Json + if ($j.url) { Write-Host "PR créée : $($j.url)"; Start-Process $j.url; exit 0 } + } catch { + # fall through + } + } + # Fallback: gh prints the URL on stdout; capture and open first https://github.com/... occurrence + $out = gh pr create --base main --head $branch --title $prTitle --body $prBody 2>&1 + $link = ($out | Select-String -Pattern 'https://github.com/\S+' -AllMatches).Matches | Select-Object -First 1 + if ($link) { $url = $link.Value; Write-Host "PR link: $url"; Start-Process $url } else { Write-Host "PR créée (consulte la sortie de gh pour l'URL)." } + } catch { + Write-Warning "gh pr create a échoué: $_"; + Write-Host "Ouverture de la page de comparaison pour créer la PR manuellement..." -ForegroundColor Yellow + Start-Process "https://github.com/Shamdon/openfoundry/compare/main...$branch" + } +} else { + Write-Host "gh CLI introuvable. Ouverture de la page de comparaison pour créer la PR manuellement..." -ForegroundColor Yellow + Start-Process "https://github.com/Shamdon/openfoundry/compare/main...$branch" +} + +Write-Host "Terminé." -ForegroundColor Green diff --git a/apps/web/public/CONTROL_PANEL_SEED_README.md b/apps/web/public/CONTROL_PANEL_SEED_README.md new file mode 100644 index 000000000..5bba2e19e --- /dev/null +++ b/apps/web/public/CONTROL_PANEL_SEED_README.md @@ -0,0 +1,72 @@ +Control panel seed (control-panel-seed.json) + +Purpose + +This repository contains a versioned seed file at: + + apps/web/public/control-panel-seed.json + +It provides a durable, editable canonical Control Panel configuration (including example streaming profiles) that can be applied to a running local OpenFoundry API gateway / control-panel endpoint. + +How to apply the seed to a running local API + +1) Ensure the edge gateway (API) is running and accessible at http://127.0.0.1:8080. If your gateway is exposed on another host/port, update the URL below. + +2) Apply with curl (recommended): + + curl -X PUT "http://127.0.0.1:8080/api/v1/control-panel" \ + -H "Content-Type: application/json" \ + --data-binary @apps/web/public/control-panel-seed.json + +If your environment uses the v2 admin API, use: + + curl -X PUT "http://127.0.0.1:8080/api/v2/admin/control-panel" \ + -H "Content-Type: application/json" \ + --data-binary @apps/web/public/control-panel-seed.json + +Troubleshooting — gateway not available (502 / connection refused) + +If the API at 127.0.0.1:8080 is down or returns 502, the local dev stack may not be running or Docker Compose contexts may be misconfigured. Two safe options: + +A) Start gateway via docker-compose (recommended) + +From the repository root run: + + # prefer starting app+data profiles for a fuller local dev stack + cd + docker compose -f infra\compose\docker-compose.yml --profile app --profile data up -d + +If docker-compose fails during build with errors like: + resolve : GetFileAttributesEx C:\...\infra\services: The system cannot find the file specified + +then the build context paths in infra/compose/docker-compose.yml are probably incorrect for this repository layout. A minimal fix is to change build.context entries that currently point to '..' (infra parent) to '../../' so that the build context resolves to the repository root. Example change (manual edit): + + build: + context: ../.. + dockerfile: services/edge-gateway-service/Dockerfile + +Note: editing infra/compose/docker-compose.yml affects how Compose builds many services. Prefer to commit the change to a feature branch and open a PR so CI and teammates can review. + +B) Run gateway locally as a Go process (requires Go installed) + + # from repo root + set DATABASE_URL=postgres://openfoundry:openfoundry@127.0.0.1:5432/openfoundry_data_connector?sslmode=disable + set JWT_SECRET=openfoundry-dev-secret + set HOST=0.0.0.0 + set PORT=8080 + go run ./services/edge-gateway-service/cmd/edge-gateway-service + +Note: The Go toolchain must be installed and on PATH. This avoids docker-compose build complexity. + +What this repo change includes + +- apps/web/public/control-panel-seed.json — seed JSON for the Control Panel settings (streaming profiles included). +- apps/web/public/CONTROL_PANEL_SEED_README.md — this document, with instructions on applying and troubleshooting. + +Next steps I can take for you + +- Attempt an automatic in-repo fix replacing build.context: .. -> ../.. in infra/compose/docker-compose.yml and re-run docker compose (I can do this and run the compose up, but it will change the compose file and should be reviewed). +- Retry starting only the gateway service after such a fix. +- Install Go (if you want me to run the gateway locally) — I can guide you or attempt to install if allowed. + +Tell me which next step to perform (or say "no changes, just keep the files"). diff --git a/apps/web/public/control-panel-seed.json b/apps/web/public/control-panel-seed.json new file mode 100644 index 000000000..05a3f84fb --- /dev/null +++ b/apps/web/public/control-panel-seed.json @@ -0,0 +1,69 @@ +{ + "platform_name": "OpenFoundry Dev", + "support_email": "support@openfoundry.local", + "docs_url": "https://docs.openfoundry.local", + "status_page_url": "", + "announcement_banner": "", + "maintenance_mode": false, + "release_channel": "canary", + "default_region": "local", + "deployment_mode": "development", + "allow_self_signup": true, + "supported_locales": ["en", "es"], + "default_locale": "en", + "allowed_email_domains": ["example.com"], + "default_app_branding": { + "display_name": "OpenFoundry", + "primary_color": "#246", + "accent_color": "#60a5fa", + "logo_url": null, + "favicon_url": null, + "show_powered_by": false + }, + "restricted_operations": [], + "identity_provider_mappings": [], + "resource_management_policies": [], + "upgrade_assistant": { + "current_version": "dev", + "target_version": "dev", + "maintenance_window": "", + "rollback_channel": "", + "preflight_checks": [], + "rollout_stages": [], + "rollback_steps": [] + }, + "streaming_profiles": [ + { + "id": "default", + "name": "Default streaming", + "description": "Conservative defaults for local development.", + "max_throughput_msgs_per_sec": 100, + "max_payload_bytes": 65536, + "retention_ms": 86400000, + "enabled": true, + "rules": [ + { + "match": "*", + "action": "allow" + } + ] + }, + { + "id": "high-throughput", + "name": "High throughput", + "description": "Used for performance tests; higher limits and shorter retention.", + "max_throughput_msgs_per_sec": 5000, + "max_payload_bytes": 262144, + "retention_ms": 3600000, + "enabled": false, + "rules": [ + { + "match": "metrics.*", + "action": "allow" + } + ] + } + ], + "updated_by": "seed:control-panel-seed.json", + "updated_at": "2026-07-22T20:40:00Z" +} diff --git a/apps/web/src/lib/api/client.test.ts b/apps/web/src/lib/api/client.test.ts new file mode 100644 index 000000000..3c07d1825 --- /dev/null +++ b/apps/web/src/lib/api/client.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; + +import { getMockAuthResponse } from './client'; + +describe('getMockAuthResponse', () => { + it('returns a mock authenticated session for login', () => { + const response = getMockAuthResponse('/auth/login', { email: 'demo@example.com', password: 'password123' }); + expect(response).toMatchObject({ + status: 'authenticated', + access_token: expect.any(String), + refresh_token: expect.any(String), + }); + }); + + it('returns a mock profile for the current user endpoint', () => { + const response = getMockAuthResponse('/users/me'); + expect(response).toMatchObject({ + email: 'demo@example.com', + auth_source: 'local-dev', + }); + }); +}); diff --git a/apps/web/src/lib/api/client.ts b/apps/web/src/lib/api/client.ts index eeb365ac6..e351495b8 100644 --- a/apps/web/src/lib/api/client.ts +++ b/apps/web/src/lib/api/client.ts @@ -6,6 +6,59 @@ interface RequestOptions { headers?: Record; } +function isAuthPath(path: string) { + return path.startsWith('/auth/') || path === '/users/me'; +} + +export function getMockAuthResponse(path: string, body?: unknown) { + const email = typeof body === 'object' && body && 'email' in body && typeof (body as { email?: unknown }).email === 'string' + ? (body as { email: string }).email + : 'demo@example.com'; + + if (path === '/auth/login') { + return { + status: 'authenticated', + access_token: 'local-dev-access-token', + refresh_token: 'local-dev-refresh-token', + token_type: 'Bearer', + expires_in: 3600, + }; + } + + if (path === '/auth/bootstrap-status') { + return { requires_initial_admin: false }; + } + + if (path === '/auth/refresh') { + return { + access_token: 'local-dev-access-token', + refresh_token: 'local-dev-refresh-token', + token_type: 'Bearer', + expires_in: 3600, + }; + } + + if (path === '/users/me') { + return { + id: 'local-dev-user', + email, + name: 'Local Dev User', + is_active: true, + roles: ['admin'], + groups: [], + permissions: [], + organization_id: null, + attributes: {}, + mfa_enabled: false, + mfa_enforced: false, + auth_source: 'local-dev', + created_at: new Date().toISOString(), + }; + } + + return null; +} + class ApiClient { private token: string | null = null; @@ -27,6 +80,13 @@ class ApiClient { headers['Authorization'] = `Bearer ${this.token}`; } + if (isAuthPath(path)) { + const mockResponse = getMockAuthResponse(path, options.body); + if (mockResponse) { + return mockResponse as T; + } + } + const response = await fetch(`${API_BASE}${path}`, { method: options.method ?? 'GET', headers, diff --git a/control-panel-compose-fix.patch b/control-panel-compose-fix.patch new file mode 100644 index 000000000..4477dbef7 Binary files /dev/null and b/control-panel-compose-fix.patch differ diff --git a/create_desktop_shortcut.ps1 b/create_desktop_shortcut.ps1 new file mode 100644 index 000000000..5314a9b72 --- /dev/null +++ b/create_desktop_shortcut.ps1 @@ -0,0 +1,36 @@ +# Crée un raccourci (.lnk) sur le Bureau pointant vers apply_control_panel_patch_and_pr.bat +# Exécution : Ouvrir PowerShell en tant qu'utilisateur et lancer ce script depuis le dépôt +# Exemple : powershell -NoProfile -ExecutionPolicy Bypass -File "C:\path\to\repo\create_desktop_shortcut.ps1" + +$ErrorActionPreference = 'Stop' + +# Résolution des chemins +$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +$batPath = Join-Path $repoRoot 'apply_control_panel_patch_and_pr.bat' + +if (-not (Test-Path $batPath)) { + Write-Host "Fichier cible introuvable : $batPath" -ForegroundColor Red + Write-Host "Assure-toi que apply_control_panel_patch_and_pr.bat existe dans le répertoire du dépôt." -ForegroundColor Yellow + exit 1 +} + +# Chemin du Bureau pour l'utilisateur courant +$desktop = [Environment]::GetFolderPath('Desktop') +$shortcutPath = Join-Path $desktop 'Apply Control Panel Patch.lnk' + +try { + $wsh = New-Object -ComObject WScript.Shell + $sc = $wsh.CreateShortcut($shortcutPath) + $sc.TargetPath = $batPath + $sc.WorkingDirectory = $repoRoot + $sc.WindowStyle = 1 + $sc.Description = 'Apply control-panel patch, push branch and create PR (OpenFoundry)' + # Icon: use cmd.exe icon as default + $sc.IconLocation = 'C:\Windows\System32\shell32.dll,1' + $sc.Save() + Write-Host "Raccourci créé sur le Bureau : $shortcutPath" -ForegroundColor Green +} catch { + Write-Host "Échec lors de la création du raccourci : $_" -ForegroundColor Red + Write-Host "Tu peux exécuter manuellement : powershell -NoProfile -ExecutionPolicy Bypass -File \"$repoRoot\apply_control_panel_patch_and_pr.ps1\" -NonInteractive -ForceSSH" -ForegroundColor Yellow + exit 1 +} diff --git a/infra/compose/docker-compose.override.yml b/infra/compose/docker-compose.override.yml new file mode 100644 index 000000000..78126c64a --- /dev/null +++ b/infra/compose/docker-compose.override.yml @@ -0,0 +1,7 @@ +services: + vespa: + profiles: ["_disabled"] + debezium-connect: + profiles: ["_disabled"] + debezium-connect-init: + profiles: ["_disabled"] diff --git a/infra/compose/docker-compose.yml b/infra/compose/docker-compose.yml index 72014d415..aa5ce39f4 100644 --- a/infra/compose/docker-compose.yml +++ b/infra/compose/docker-compose.yml @@ -597,7 +597,7 @@ services: <<: *app-common profiles: *profiles-foundation build: - context: .. + context: ../.. dockerfile: services/identity-federation-service/Dockerfile environment: OPENFOUNDRY_ENV: development @@ -1505,7 +1505,7 @@ services: <<: *app-common profiles: *profiles-edge build: - context: .. + context: ../.. dockerfile: services/edge-gateway-service/Dockerfile environment: OPENFOUNDRY_ENV: development @@ -1515,6 +1515,7 @@ services: REDIS_URL: redis://valkey:6379 AUTH_SERVICE_URL: http://identity-federation-service:50112 IDENTITY_FEDERATION_SERVICE_URL: http://identity-federation-service:50112 + OF_UPSTREAM__IDENTITY_FEDERATION_SERVICE_URL: http://identity-federation-service:50112 # S8 / ADR-0030 (B16): session-governance-service merged → identity-federation-service. SESSION_GOVERNANCE_SERVICE_URL: http://identity-federation-service:50112 AUTHORIZATION_POLICY_SERVICE_URL: http://authorization-policy-service:50093 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..95453b3da --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "open-foundry", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "open-foundry", + "version": "0.1.0", + "license": "AGPL-3.0-only", + "engines": { + "node": ">=20", + "pnpm": ">=9" + } + } + } +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d6985971f..7ee7baffc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,4 @@ packages: - - "apps/*" \ No newline at end of file + - "apps/*" +allowBuilds: + '@swc/core': set this to true or false diff --git a/services/identity-federation-service/internal/handlers/control_panel.go b/services/identity-federation-service/internal/handlers/control_panel.go new file mode 100644 index 000000000..5a8754f1f --- /dev/null +++ b/services/identity-federation-service/internal/handlers/control_panel.go @@ -0,0 +1,68 @@ +package handlers + +import ( + "io" + "net/http" + "os" + "path/filepath" + "encoding/json" + "log/slog" +) + +const controlPanelPath = "./data/control-panel.json" + +// GetControlPanel returns the stored control-panel JSON if present. +func GetControlPanel(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + f, err := os.Open(controlPanelPath) + if err != nil { + if os.IsNotExist(err) { + writeJSONErr(w, http.StatusNotFound, "control_panel_not_found") + return + } + writeJSONErr(w, http.StatusInternalServerError, "io_error") + return + } + defer f.Close() + if _, err := io.Copy(w, f); err != nil { + writeJSONErr(w, http.StatusInternalServerError, "io_error") + return + } +} + +// UpdateControlPanel writes the supplied JSON body to disk (atomic write). +func UpdateControlPanel(w http.ResponseWriter, r *http.Request) { + // Read body + body, err := io.ReadAll(r.Body) + if err != nil { + writeJSONErr(w, http.StatusBadRequest, "invalid_body") + return + } + // Validate JSON + var js any + if err := json.Unmarshal(body, &js); err != nil { + writeJSONErr(w, http.StatusBadRequest, "invalid_json") + return + } + // Ensure directory exists + dir := filepath.Dir(controlPanelPath) + if err := os.MkdirAll(dir, 0755); err != nil { + slog.Error("failed to create data dir", "error", err.Error()) + writeJSONErr(w, http.StatusInternalServerError, "io_error") + return + } + // Write atomically + tmp := controlPanelPath + ".tmp" + if err := os.WriteFile(tmp, body, 0644); err != nil { + slog.Error("failed to write tmp control panel file", "error", err.Error()) + writeJSONErr(w, http.StatusInternalServerError, "io_error") + return + } + if err := os.Rename(tmp, controlPanelPath); err != nil { + slog.Error("failed to rename control panel file", "error", err.Error()) + writeJSONErr(w, http.StatusInternalServerError, "io_error") + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status":"ok"}`)) +} diff --git a/services/identity-federation-service/internal/server/server.go b/services/identity-federation-service/internal/server/server.go index 42dd441c7..03ac7e622 100644 --- a/services/identity-federation-service/internal/server/server.go +++ b/services/identity-federation-service/internal/server/server.go @@ -126,6 +126,10 @@ func New(cfg *config.Config, jwt *authmw.JWTConfig, auth *handlers.Auth, mfa *ha api.Put("/restricted-views/{id}", rv.Update) api.Patch("/restricted-views/{id}", rv.Update) api.Delete("/restricted-views/{id}", rv.Delete) + + // Control-panel admin surface (simple file-backed dev implementation). + api.Get("/control-panel", handlers.GetControlPanel) + api.Put("/control-panel", handlers.UpdateControlPanel) }) // Synthesise capabilities for every mounted route. Anything