From 81d477ec8c0aa71247ed8116e676c10972a0d102 Mon Sep 17 00:00:00 2001 From: Bert Hausmans Date: Wed, 14 Jan 2026 16:36:22 +0100 Subject: [PATCH] Fix TypeScript compilation errors - Add searchReference to ApplicationListItem type - Fix result variable in toApplicationDetails function - Add query helper functions for req.query parameter handling - Fix req.query.* type errors in routes (applications, cache, classifications, objects) - Fix CompletenessCategoryConfig missing id property - Fix number | null type errors in dataService - Add utils/queryHelpers.ts for reusable query parameter helpers --- backend/src/routes/applications.ts | 18 +- backend/src/routes/cache.ts | 17 +- backend/src/routes/classifications.ts | 5 +- backend/src/routes/configuration.ts | 44 +-- backend/src/routes/objects.ts | 13 +- backend/src/services/dataService.ts | 65 ++++- backend/src/types/index.ts | 1 + backend/src/utils/queryHelpers.ts | 34 +++ docs/AZURE-CLI-QUICKSTART.md | 270 ++++++++++++++++++ docs/AZURE-PIPELINE-REPO-TROUBLESHOOTING.md | 250 ++++++++++++++++ docs/AZURE-SERVICE-CONNECTION-AUTH.md | 231 +++++++++++++++ ...ZURE-SERVICE-CONNECTION-TROUBLESHOOTING.md | 237 +++++++++++++++ docs/NEXT-STEPS-ACR-CREATED.md | 39 ++- 13 files changed, 1166 insertions(+), 58 deletions(-) create mode 100644 backend/src/utils/queryHelpers.ts create mode 100644 docs/AZURE-CLI-QUICKSTART.md create mode 100644 docs/AZURE-PIPELINE-REPO-TROUBLESHOOTING.md create mode 100644 docs/AZURE-SERVICE-CONNECTION-AUTH.md create mode 100644 docs/AZURE-SERVICE-CONNECTION-TROUBLESHOOTING.md diff --git a/backend/src/routes/applications.ts b/backend/src/routes/applications.ts index e3dee91..6ef8472 100644 --- a/backend/src/routes/applications.ts +++ b/backend/src/routes/applications.ts @@ -6,6 +6,7 @@ import { logger } from '../services/logger.js'; import { calculateRequiredEffortApplicationManagementWithBreakdown } from '../services/effortCalculation.js'; import { findBIAMatch, loadBIAData, clearBIACache, calculateSimilarity } from '../services/biaMatchingService.js'; import { calculateApplicationCompleteness } from '../services/dataCompletenessConfig.js'; +import { getQueryString } from '../utils/queryHelpers.js'; import type { SearchFilters, ReferenceValue, ClassificationResult, ApplicationDetails, ApplicationStatus } from '../types/index.js'; import type { Server, Flows, Certificate, Domain, AzureSubscription, CMDBObjectTypeName } from '../generated/jira-types.js'; @@ -31,7 +32,7 @@ router.post('/search', async (req: Request, res: Response) => { // Get team dashboard data router.get('/team-dashboard', async (req: Request, res: Response) => { try { - const excludedStatusesParam = req.query.excludedStatuses as string | undefined; + const excludedStatusesParam = getQueryString(req, 'excludedStatuses'); let excludedStatuses: ApplicationStatus[] = []; if (excludedStatusesParam && excludedStatusesParam.trim().length > 0) { @@ -56,7 +57,7 @@ router.get('/team-dashboard', async (req: Request, res: Response) => { // Get team portfolio health metrics router.get('/team-portfolio-health', async (req: Request, res: Response) => { try { - const excludedStatusesParam = req.query.excludedStatuses as string | undefined; + const excludedStatusesParam = getQueryString(req, 'excludedStatuses'); let excludedStatuses: ApplicationStatus[] = []; if (excludedStatusesParam && excludedStatusesParam.trim().length > 0) { @@ -95,7 +96,7 @@ router.get('/business-importance-comparison', async (req: Request, res: Response // Test BIA data loading (for debugging) router.get('/bia-test', async (req: Request, res: Response) => { try { - if (req.query.clear === 'true') { + if (getQueryString(req, 'clear') === 'true') { clearBIACache(); } const biaData = loadBIAData(); @@ -133,7 +134,7 @@ router.get('/bia-debug', async (req: Request, res: Response) => { // Test each sample app for (const app of [...sampleApps, ...testApps]) { - const matchResult = findBIAMatch(app.name, app.searchReference); + const matchResult = findBIAMatch(app.name, app.searchReference ?? null); // Find all potential matches in Excel data for detailed analysis const normalizedAppName = app.name.toLowerCase().trim(); @@ -246,7 +247,7 @@ router.get('/bia-comparison', async (req: Request, res: Response) => { for (const app of applications) { // Find BIA match in Excel - const matchResult = findBIAMatch(app.name, app.searchReference); + const matchResult = findBIAMatch(app.name, app.searchReference ?? null); // Log first few matches for debugging if (comparisonItems.length < 5) { @@ -323,7 +324,7 @@ router.get('/bia-comparison', async (req: Request, res: Response) => { router.get('/:id', async (req: Request, res: Response) => { try { const { id } = req.params; - const mode = req.query.mode as string | undefined; + const mode = getQueryString(req, 'mode'); // Don't treat special routes as application IDs if (id === 'team-dashboard' || id === 'team-portfolio-health' || id === 'business-importance-comparison' || id === 'bia-comparison' || id === 'bia-test' || id === 'calculate-effort' || id === 'search') { @@ -617,8 +618,9 @@ router.get('/:id/related/:objectType', async (req: Request, res: Response) => { } // Get requested attributes from query string - const requestedAttrs = req.query.attributes - ? String(req.query.attributes).split(',').map(a => a.trim()) + const attributesParam = getQueryString(req, 'attributes'); + const requestedAttrs = attributesParam + ? attributesParam.split(',').map(a => a.trim()) : []; // Format response - must match RelatedObjectsResponse type expected by frontend diff --git a/backend/src/routes/cache.ts b/backend/src/routes/cache.ts index c95d5b3..42757c8 100644 --- a/backend/src/routes/cache.ts +++ b/backend/src/routes/cache.ts @@ -8,6 +8,7 @@ import { Router, Request, Response } from 'express'; import { cacheStore } from '../services/cacheStore.js'; import { syncEngine } from '../services/syncEngine.js'; import { logger } from '../services/logger.js'; +import { getQueryString } from '../utils/queryHelpers.js'; import { OBJECT_TYPES } from '../generated/jira-schema.js'; import type { CMDBObjectTypeName } from '../generated/jira-types.js'; @@ -78,10 +79,11 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => { try { const { objectType } = req.params; - // Validate object type - if (!OBJECT_TYPES[objectType]) { + // Validate object type - convert to string if array + const objectTypeStr = Array.isArray(objectType) ? objectType[0] : objectType; + if (!OBJECT_TYPES[objectTypeStr]) { res.status(400).json({ - error: `Unknown object type: ${objectType}`, + error: `Unknown object type: ${objectTypeStr}`, supportedTypes: Object.keys(OBJECT_TYPES), }); return; @@ -89,11 +91,11 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => { logger.info(`Manual sync triggered for ${objectType}`); - const result = await syncEngine.syncType(objectType as CMDBObjectTypeName); + const result = await syncEngine.syncType(objectTypeStr as CMDBObjectTypeName); res.json({ status: 'completed', - objectType, + objectType: objectTypeStr, stats: result, }); } catch (error) { @@ -113,10 +115,11 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => { router.delete('/clear/:objectType', async (req: Request, res: Response) => { try { const { objectType } = req.params; + const objectTypeStr = Array.isArray(objectType) ? objectType[0] : objectType; - if (!OBJECT_TYPES[objectType]) { + if (!OBJECT_TYPES[objectTypeStr]) { res.status(400).json({ - error: `Unknown object type: ${objectType}`, + error: `Unknown object type: ${objectTypeStr}`, supportedTypes: Object.keys(OBJECT_TYPES), }); return; diff --git a/backend/src/routes/classifications.ts b/backend/src/routes/classifications.ts index 7cee8be..b70e801 100644 --- a/backend/src/routes/classifications.ts +++ b/backend/src/routes/classifications.ts @@ -4,6 +4,7 @@ import { dataService } from '../services/dataService.js'; import { databaseService } from '../services/database.js'; import { logger } from '../services/logger.js'; import { config } from '../config/env.js'; +import { getQueryString, getQueryNumber } from '../utils/queryHelpers.js'; const router = Router(); @@ -12,7 +13,7 @@ router.post('/suggest/:id', async (req: Request, res: Response) => { try { const { id } = req.params; // Get provider from query parameter or request body, default to config - const provider = (req.query.provider as AIProvider) || (req.body.provider as AIProvider) || config.defaultAIProvider; + const provider = (getQueryString(req, 'provider') as AIProvider) || (req.body.provider as AIProvider) || config.defaultAIProvider; if (!aiService.isConfigured(provider)) { res.status(503).json({ @@ -70,7 +71,7 @@ router.get('/function/:code', (req: Request, res: Response) => { // Get classification history router.get('/history', async (req: Request, res: Response) => { try { - const limit = parseInt(req.query.limit as string) || 50; + const limit = getQueryNumber(req, 'limit', 50); const history = await databaseService.getClassificationHistory(limit); res.json(history); } catch (error) { diff --git a/backend/src/routes/configuration.ts b/backend/src/routes/configuration.ts index a9253bb..27804d8 100644 --- a/backend/src/routes/configuration.ts +++ b/backend/src/routes/configuration.ts @@ -142,37 +142,39 @@ router.get('/data-completeness', async (req: Request, res: Response) => { description: 'Configuration for Data Completeness Score fields', lastUpdated: new Date().toISOString(), }, - categories: { - general: { + categories: [ + { + id: 'general', name: 'General', description: 'General application information fields', fields: [ - { name: 'Organisation', fieldPath: 'organisation', enabled: true }, - { name: 'ApplicationFunction', fieldPath: 'applicationFunctions', enabled: true }, - { name: 'Status', fieldPath: 'status', enabled: true }, - { name: 'Business Impact Analyse', fieldPath: 'businessImpactAnalyse', enabled: true }, - { name: 'Application Component Hosting Type', fieldPath: 'hostingType', enabled: true }, - { name: 'Supplier Product', fieldPath: 'supplierProduct', enabled: true }, - { name: 'Business Owner', fieldPath: 'businessOwner', enabled: true }, - { name: 'System Owner', fieldPath: 'systemOwner', enabled: true }, - { name: 'Functional Application Management', fieldPath: 'functionalApplicationManagement', enabled: true }, - { name: 'Technical Application Management', fieldPath: 'technicalApplicationManagement', enabled: true }, + { id: 'organisation', name: 'Organisation', fieldPath: 'organisation', enabled: true }, + { id: 'applicationFunctions', name: 'ApplicationFunction', fieldPath: 'applicationFunctions', enabled: true }, + { id: 'status', name: 'Status', fieldPath: 'status', enabled: true }, + { id: 'businessImpactAnalyse', name: 'Business Impact Analyse', fieldPath: 'businessImpactAnalyse', enabled: true }, + { id: 'hostingType', name: 'Application Component Hosting Type', fieldPath: 'hostingType', enabled: true }, + { id: 'supplierProduct', name: 'Supplier Product', fieldPath: 'supplierProduct', enabled: true }, + { id: 'businessOwner', name: 'Business Owner', fieldPath: 'businessOwner', enabled: true }, + { id: 'systemOwner', name: 'System Owner', fieldPath: 'systemOwner', enabled: true }, + { id: 'functionalApplicationManagement', name: 'Functional Application Management', fieldPath: 'functionalApplicationManagement', enabled: true }, + { id: 'technicalApplicationManagement', name: 'Technical Application Management', fieldPath: 'technicalApplicationManagement', enabled: true }, ], }, - applicationManagement: { + { + id: 'applicationManagement', name: 'Application Management', description: 'Application management classification fields', fields: [ - { name: 'ICT Governance Model', fieldPath: 'governanceModel', enabled: true }, - { name: 'Application Management - Application Type', fieldPath: 'applicationType', enabled: true }, - { name: 'Application Management - Hosting', fieldPath: 'applicationManagementHosting', enabled: true }, - { name: 'Application Management - TAM', fieldPath: 'applicationManagementTAM', enabled: true }, - { name: 'Application Management - Dynamics Factor', fieldPath: 'dynamicsFactor', enabled: true }, - { name: 'Application Management - Complexity Factor', fieldPath: 'complexityFactor', enabled: true }, - { name: 'Application Management - Number of Users', fieldPath: 'numberOfUsers', enabled: true }, + { id: 'governanceModel', name: 'ICT Governance Model', fieldPath: 'governanceModel', enabled: true }, + { id: 'applicationType', name: 'Application Management - Application Type', fieldPath: 'applicationType', enabled: true }, + { id: 'applicationManagementHosting', name: 'Application Management - Hosting', fieldPath: 'applicationManagementHosting', enabled: true }, + { id: 'applicationManagementTAM', name: 'Application Management - TAM', fieldPath: 'applicationManagementTAM', enabled: true }, + { id: 'dynamicsFactor', name: 'Application Management - Dynamics Factor', fieldPath: 'dynamicsFactor', enabled: true }, + { id: 'complexityFactor', name: 'Application Management - Complexity Factor', fieldPath: 'complexityFactor', enabled: true }, + { id: 'numberOfUsers', name: 'Application Management - Number of Users', fieldPath: 'numberOfUsers', enabled: true }, ], }, - }, + ], }; res.json(defaultConfig); } diff --git a/backend/src/routes/objects.ts b/backend/src/routes/objects.ts index 4e5aa17..f334635 100644 --- a/backend/src/routes/objects.ts +++ b/backend/src/routes/objects.ts @@ -7,6 +7,7 @@ import { Router, Request, Response } from 'express'; import { cmdbService } from '../services/cmdbService.js'; import { logger } from '../services/logger.js'; +import { getQueryString, getQueryNumber } from '../utils/queryHelpers.js'; import { OBJECT_TYPES } from '../generated/jira-schema.js'; import type { CMDBObject, CMDBObjectTypeName } from '../generated/jira-types.js'; @@ -32,9 +33,9 @@ router.get('/', (req: Request, res: Response) => { router.get('/:type', async (req: Request, res: Response) => { try { const { type } = req.params; - const limit = parseInt(req.query.limit as string) || 1000; - const offset = parseInt(req.query.offset as string) || 0; - const search = req.query.search as string | undefined; + const limit = getQueryNumber(req, 'limit', 1000); + const offset = getQueryNumber(req, 'offset', 0); + const search = getQueryString(req, 'search'); // Validate type if (!OBJECT_TYPES[type]) { @@ -71,7 +72,7 @@ router.get('/:type', async (req: Request, res: Response) => { router.get('/:type/:id', async (req: Request, res: Response) => { try { const { type, id } = req.params; - const forceRefresh = req.query.refresh === 'true'; + const forceRefresh = getQueryString(req, 'refresh') === 'true'; // Validate type if (!OBJECT_TYPES[type]) { @@ -102,7 +103,7 @@ router.get('/:type/:id', async (req: Request, res: Response) => { router.get('/:type/:id/related/:relationType', async (req: Request, res: Response) => { try { const { type, id, relationType } = req.params; - const attribute = req.query.attribute as string | undefined; + const attribute = getQueryString(req, 'attribute'); // Validate types if (!OBJECT_TYPES[type]) { @@ -139,7 +140,7 @@ router.get('/:type/:id/related/:relationType', async (req: Request, res: Respons router.get('/:type/:id/referenced-by/:sourceType', async (req: Request, res: Response) => { try { const { type, id, sourceType } = req.params; - const attribute = req.query.attribute as string | undefined; + const attribute = getQueryString(req, 'attribute'); // Validate types if (!OBJECT_TYPES[type]) { diff --git a/backend/src/services/dataService.ts b/backend/src/services/dataService.ts index 9c5833e..6beb324 100644 --- a/backend/src/services/dataService.ts +++ b/backend/src/services/dataService.ts @@ -289,19 +289,18 @@ async function toApplicationDetails(app: ApplicationComponent): Promise/resourceGroups//providers/Microsoft.ContainerRegistry/registries/zdlas + ``` + +2. **Gebruik deze credentials in Azure DevOps:** + - Kies "Docker Registry" β†’ "Others" + - Vul in: + - Registry URL: `zdlas.azurecr.io` + - Username: (van de Service Principal output) + - Password: (van de Service Principal output) + +--- + +## βœ… Oplossing 6: Gebruik "Others" in plaats van "Azure Container Registry" + +**Als de Azure Container Registry optie niet werkt, gebruik dan "Others":** + +1. **In de service connection wizard:** + - Kies **"Docker Registry"** + - Kies **"Others"** (in plaats van "Azure Container Registry") + +2. **Vul handmatig in:** + - **Docker Registry**: `zdlas.azurecr.io` + - **Docker ID**: (leeg laten of je ACR admin username) + - **Docker Password**: (je ACR admin password) + +3. **Haal ACR credentials op:** + ```bash + # Login bij Azure + az login + + # Haal admin credentials op + az acr credential show --name zdlas + ``` + + Gebruik de `username` en `passwords[0].value` uit de output. + +4. **Save en test** + +**⚠️ Let op:** Met "Others" moet je handmatig credentials beheren. Service Principal is veiliger, maar dit werkt als tijdelijke oplossing. + +--- + +## βœ… Oplossing 7: Check Browser/Network + +**Probleem:** Browser of network issues kunnen de dropdown blokkeren. + +**Oplossingen:** +1. **Probeer een andere browser** (Chrome, Firefox, Edge) +2. **Disable browser extensions** (ad blockers, etc.) +3. **Check network connectivity** naar Azure +4. **Probeer incognito/private mode** + +--- + +## βœ… Oplossing 8: Wacht Even en Probeer Later + +**Soms is het een tijdelijk Azure issue:** + +1. **Wacht 5-10 minuten** +2. **Probeer opnieuw** +3. **Check Azure Status**: https://status.azure.com/ + +--- + +## πŸ” Diagnose Stappen + +**Om te diagnosticeren wat het probleem is:** + +### 1. Check ACR Bestaat en is Toegankelijk + +```bash +# Login bij Azure +az login + +# Check of ACR bestaat +az acr show --name zdlas + +# Check ACR credentials +az acr credential show --name zdlas + +# Check ACR permissions +az acr show --name zdlas --query "networkRuleSet" -o table +``` + +### 2. Check Azure DevOps Subscription Toegang + +1. Ga naar Azure Portal +2. Ga naar je Subscription +3. Check "Access control (IAM)" +4. Check of je account toegang heeft + +### 3. Check Service Principal Permissions + +```bash +# List Service Principals +az ad sp list --display-name "zuyderland-cmdb-acr-sp" -o table + +# Check permissions op ACR +az role assignment list --scope /subscriptions//resourceGroups//providers/Microsoft.ContainerRegistry/registries/zdlas +``` + +--- + +## πŸ’‘ Aanbevolen Aanpak + +**Probeer in deze volgorde:** + +1. βœ… **Refresh de pagina** (Oplossing 2) +2. βœ… **Check subscription toegang** (Oplossing 1) +3. βœ… **Handmatig registry naam invoeren** (Oplossing 4) +4. βœ… **Gebruik "Others" optie** (Oplossing 6) - als tijdelijke oplossing +5. βœ… **Maak handmatig Service Principal** (Oplossing 5) - voor permanente oplossing + +--- + +## 🎯 Quick Fix (Aanbevolen) + +**Als de dropdown niet werkt, gebruik deze workaround:** + +1. **Kies "Docker Registry" β†’ "Others"** +2. **Vul in:** + - Registry URL: `zdlas.azurecr.io` + - Username: (haal op met `az acr credential show --name zdlas`) + - Password: (haal op met `az acr credential show --name zdlas`) +3. **Service connection name**: `zuyderland-cmdb-acr-connection` +4. **Save** + +**Dit werkt altijd, ook als de Azure Container Registry optie niet werkt.** + +**Later kun je:** +- De service connection verwijderen +- Opnieuw aanmaken met "Azure Container Registry" optie (als die dan wel werkt) +- Of de "Others" optie behouden (werkt ook prima) + +--- + +## πŸ“š Meer Informatie + +- [Azure DevOps Service Connections Troubleshooting](https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints) +- [ACR Access Control](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication) +- [Service Principal Permissions](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-roles) + +--- + +## πŸ†˜ Nog Steeds Problemen? + +Als niets werkt: + +1. **Check Azure DevOps logs** (als je toegang hebt) +2. **Contact Azure Support** (als je een support plan hebt) +3. **Gebruik "Others" optie** als workaround (werkt altijd) + +**De "Others" optie is een volledig werkende oplossing**, alleen iets minder geautomatiseerd dan de Azure Container Registry optie. diff --git a/docs/NEXT-STEPS-ACR-CREATED.md b/docs/NEXT-STEPS-ACR-CREATED.md index 482312f..b0576dd 100644 --- a/docs/NEXT-STEPS-ACR-CREATED.md +++ b/docs/NEXT-STEPS-ACR-CREATED.md @@ -83,8 +83,11 @@ Deze connection geeft Azure DevOps toegang tot je ACR. - Klik **"Next"** 6. **Configureer connection** + - **Authentication type**: Kies **"Service Principal"** ⭐ (aanbevolen) + - Dit is de standaard en meest betrouwbare optie + - Azure DevOps maakt automatisch een Service Principal aan - **Azure subscription**: Selecteer je Azure subscription - - **Azure container registry**: Selecteer je ACR uit de dropdown (bijv. `zuyderlandcmdbacr`) + - **Azure container registry**: Selecteer je ACR uit de dropdown (bijv. `zdlas`) - **Service connection name**: `zuyderland-cmdb-acr-connection` - ⚠️ **Belangrijk**: Deze naam moet overeenkomen met `dockerRegistryServiceConnection` in `azure-pipelines.yml`! - **Description**: Optioneel (bijv. "ACR for CMDB GUI production") @@ -92,13 +95,45 @@ Deze connection geeft Azure DevOps toegang tot je ACR. 7. **Save** - Klik **"Save"** (of **"Verify and save"**) - Azure DevOps test automatisch de connection + - Azure DevOps maakt automatisch een Service Principal aan met de juiste permissions + +**πŸ’‘ Waarom Service Principal?** +- βœ… Werkt perfect met Azure DevOps Services (cloud) +- βœ… Eenvoudig - Azure DevOps doet alles automatisch +- βœ… Betrouwbaar - Meest ondersteunde optie +- βœ… Veilig - Credentials worden veilig beheerd + +πŸ“š Zie `docs/AZURE-SERVICE-CONNECTION-AUTH.md` voor details over alle authentication types. **βœ… Service connection is aangemaakt!** **Troubleshooting:** -- Als je ACR niet ziet in de dropdown, check of je de juiste subscription hebt geselecteerd +- **"Loading Registries..." blijft hangen?** + - βœ… Refresh de pagina (F5) + - βœ… Check of je de juiste subscription hebt geselecteerd + - βœ… Wacht 10-30 seconden (kan even duren) + - βœ… **Workaround**: Gebruik "Others" optie (zie hieronder) - Als verificatie faalt, check of je toegang hebt tot de ACR in Azure Portal +**πŸ”§ Workaround: Als dropdown niet werkt, gebruik "Others" optie:** + +1. Kies **"Docker Registry"** β†’ **"Others"** (in plaats van "Azure Container Registry") +2. Vul handmatig in: + - **Docker Registry**: `zdlas.azurecr.io` + - **Docker ID**: (haal op met `az acr credential show --name zdlas`) + - **Docker Password**: (haal op met `az acr credential show --name zdlas`) +3. **Service connection name**: `zuyderland-cmdb-acr-connection` +4. Save + +**Haal credentials op:** +```bash +az login +az acr credential show --name zdlas +# Gebruik "username" en "passwords[0].value" +``` + +πŸ“š Zie `docs/AZURE-SERVICE-CONNECTION-TROUBLESHOOTING.md` voor uitgebreide troubleshooting. + --- ## 🎯 Stap 4: Pipeline Aanmaken en Run