Compare commits

..

2 Commits

Author SHA1 Message Date
8873886f5d Implement code-splitting to reduce initial bundle size
- Convert all route components to lazy-loaded with React.lazy()
- Add Suspense boundaries with loading fallback components
- Configure manual chunks in Vite for better code organization:
  - Separate React vendor libraries (react-vendor)
  - Group components by feature (reports, settings, admin, apps, auth)
  - Isolate other node_modules (vendor)
- Reduce initial bundle from ~1,080 kB to under 500 kB
- Components now load on-demand when routes are accessed
- Improves initial page load performance and caching
2026-01-22 22:58:19 +01:00
57e4adc69c Remove JIRA_SCHEMA_ID from entire application
- Remove JIRA_SCHEMA_ID from all documentation, config files, and scripts
- Update generate-schema.ts to always auto-discover schemas dynamically
- Runtime application already discovers schemas via /objectschema/list API
- Build script now automatically selects schema with most objects
- Remove JIRA_SCHEMA_ID from docker-compose.yml, Azure setup scripts, and all docs
- Application is now fully schema-agnostic and discovers schemas automatically
2026-01-22 22:56:29 +01:00
16 changed files with 229 additions and 138 deletions

View File

@@ -156,7 +156,6 @@ Dutch hospital reference architecture with 90+ application functions organized i
```env
# Jira Data Center
JIRA_HOST=https://jira.zuyderland.nl
JIRA_SCHEMA_ID=<schema_id>
# Jira Authentication Method: 'pat' or 'oauth'
JIRA_AUTH_METHOD=pat # Choose: 'pat' (Personal Access Token) or 'oauth' (OAuth 2.0)

View File

@@ -10,6 +10,11 @@
* and their attributes, ensuring the data model is always in sync with the
* actual CMDB configuration.
*
* Schema Discovery:
* - Automatically discovers available schemas via /objectschema/list
* - Selects the schema with the most objects (or the first one if counts unavailable)
* - The runtime application also discovers schemas dynamically
*
* Usage: npm run generate-schema
*/
@@ -38,7 +43,6 @@ for (const envPath of envPaths) {
// Configuration
const JIRA_HOST = process.env.JIRA_HOST || '';
const JIRA_PAT = process.env.JIRA_PAT || '';
const JIRA_SCHEMA_ID = process.env.JIRA_SCHEMA_ID || '';
const OUTPUT_DIR = path.resolve(__dirname, '../src/generated');
@@ -255,6 +259,36 @@ class JiraSchemaFetcher {
}
}
/**
* List all available schemas
*/
async listSchemas(): Promise<JiraObjectSchema[]> {
try {
const response = await fetch(`${this.baseUrl}/objectschema/list`, {
headers: this.headers,
});
if (!response.ok) {
console.error(`Failed to list schemas: ${response.status} ${response.statusText}`);
return [];
}
const result = await response.json();
// Handle both array and object responses
if (Array.isArray(result)) {
return result;
} else if (result && typeof result === 'object' && 'objectschemas' in result) {
return result.objectschemas || [];
}
return [];
} catch (error) {
console.error(`Error listing schemas:`, error);
return [];
}
}
/**
* Test the connection
*/
@@ -819,17 +853,10 @@ async function main() {
process.exit(1);
}
if (!JIRA_SCHEMA_ID) {
console.error('❌ ERROR: JIRA_SCHEMA_ID environment variable is required');
console.error(' Set this in your .env file: JIRA_SCHEMA_ID=6');
process.exit(1);
}
if (envLoaded) {
console.log(`🔧 Environment: ${envLoaded}`);
}
console.log(`📡 Jira Host: ${JIRA_HOST}`);
console.log(`📋 Schema ID: ${JIRA_SCHEMA_ID}`);
console.log(`📁 Output Dir: ${OUTPUT_DIR}`);
console.log('');
@@ -852,20 +879,41 @@ async function main() {
console.log('✅ Connection successful');
console.log('');
// Fetch schema info
console.log('📋 Fetching schema information...');
const schema = await fetcher.fetchSchema(JIRA_SCHEMA_ID);
if (!schema) {
console.error(`❌ Failed to fetch schema ${JIRA_SCHEMA_ID}`);
// Discover schema automatically
console.log('📋 Discovering available schemas...');
const schemas = await fetcher.listSchemas();
if (schemas.length === 0) {
console.error('❌ No schemas found');
console.error(' Please ensure Jira Assets is configured and accessible');
process.exit(1);
}
// Select the schema with the most objects (or the first one if counts unavailable)
const schema = schemas.reduce((prev, current) => {
const prevCount = prev.objectCount || 0;
const currentCount = current.objectCount || 0;
return currentCount > prevCount ? current : prev;
});
const selectedSchemaId = schema.id.toString();
console.log(` Found ${schemas.length} schema(s)`);
if (schemas.length > 1) {
console.log(' Available schemas:');
schemas.forEach(s => {
const marker = s.id === schema.id ? ' → ' : ' ';
console.log(`${marker}${s.id}: ${s.name} (${s.objectSchemaKey}) - ${s.objectCount || 0} objects`);
});
console.log(` Using schema: ${schema.name} (ID: ${selectedSchemaId})`);
}
console.log(` Schema: ${schema.name} (${schema.objectSchemaKey})`);
console.log(` Total objects: ${schema.objectCount || 'unknown'}`);
console.log('');
// Fetch ALL object types from the schema
console.log('📦 Fetching all object types from schema...');
const allObjectTypes = await fetcher.fetchAllObjectTypes(JIRA_SCHEMA_ID);
const allObjectTypes = await fetcher.fetchAllObjectTypes(selectedSchemaId);
if (allObjectTypes.length === 0) {
console.error('❌ No object types found in schema');

View File

@@ -34,7 +34,6 @@ services:
# Optional Jira/AI variables (set in .env file or environment)
- JIRA_HOST=${JIRA_HOST}
- JIRA_PAT=${JIRA_PAT}
- JIRA_SCHEMA_ID=${JIRA_SCHEMA_ID}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
volumes:
- ./backend/src:/app/src

View File

@@ -134,7 +134,6 @@ The following environment variables have been **REMOVED** from the codebase and
- `SESSION_SECRET`: Should be a secure random string in production (generate with `openssl rand -base64 32`)
- `ENCRYPTION_KEY`: Must be exactly 32 bytes when base64 decoded (generate with `openssl rand -base64 32`)
- `JIRA_SCHEMA_ID`: Required for Jira Assets integration
### Application Branding

View File

@@ -78,7 +78,6 @@ These secrets should be stored in Azure Key Vault:
| `SessionSecret` | Session encryption secret | `a1b2c3d4e5f6...` (32+ chars) |
| `JiraOAuthClientId` | Jira OAuth Client ID | `OAuthClientId123` |
| `JiraOAuthClientSecret` | Jira OAuth Client Secret | `OAuthSecret456` |
| `JiraSchemaId` | Jira Assets Schema ID | `schema-123` |
| `DatabasePassword` | PostgreSQL admin password | `SecurePassword123!` |
---
@@ -171,7 +170,6 @@ az webapp log tail --name cmdb-frontend-prod --resource-group rg-cmdb-insight-pr
- `JIRA_OAUTH_CLIENT_ID` (from Key Vault)
- `JIRA_OAUTH_CLIENT_SECRET` (from Key Vault)
- `JIRA_OAUTH_CALLBACK_URL`
- `JIRA_SCHEMA_ID` (from Key Vault)
- `SESSION_SECRET` (from Key Vault)
- `FRONTEND_URL`
- `APPINSIGHTS_INSTRUMENTATIONKEY`

View File

@@ -170,7 +170,6 @@ az webapp config appsettings set \
DATABASE_PASSWORD=your-database-password \
DATABASE_SSL=true \
JIRA_BASE_URL=https://jira.zuyderland.nl \
JIRA_SCHEMA_ID=your-schema-id \
JIRA_PAT=your-pat-token \
SESSION_SECRET=$(openssl rand -hex 32) \
FRONTEND_URL=https://zdl-cmdb-insight-prd-frontend-webapp.azurewebsites.net
@@ -195,7 +194,6 @@ az webapp config appsettings set \
PORT=3001 \
DATABASE_TYPE=sqlite \
JIRA_BASE_URL=https://jira.zuyderland.nl \
JIRA_SCHEMA_ID=your-schema-id \
JIRA_PAT=your-pat-token \
SESSION_SECRET=$(openssl rand -hex 32) \
FRONTEND_URL=https://zdl-cmdb-insight-prd-frontend-webapp.azurewebsites.net
@@ -251,7 +249,6 @@ az keyvault create \
```bash
az keyvault secret set --vault-name kv-cmdb-insight-prod --name JiraPat --value "your-token"
az keyvault secret set --vault-name kv-cmdb-insight-prod --name SessionSecret --value "$(openssl rand -hex 32)"
az keyvault secret set --vault-name kv-cmdb-insight-prod --name JiraSchemaId --value "your-schema-id"
```
### Stap 3: Grant Access
@@ -294,8 +291,7 @@ az webapp config appsettings set \
--resource-group zdl-cmdb-insight-prd-euwe-rg \
--settings \
JIRA_PAT="@Microsoft.KeyVault(SecretUri=https://kv-cmdb-insight-prod.vault.azure.net/secrets/JiraPat/)" \
SESSION_SECRET="@Microsoft.KeyVault(SecretUri=https://kv-cmdb-insight-prod.vault.azure.net/secrets/SessionSecret/)" \
JIRA_SCHEMA_ID="@Microsoft.KeyVault(SecretUri=https://kv-cmdb-insight-prod.vault.azure.net/secrets/JiraSchemaId/)"
SESSION_SECRET="@Microsoft.KeyVault(SecretUri=https://kv-cmdb-insight-prod.vault.azure.net/secrets/SessionSecret/)"
```
---

View File

@@ -72,7 +72,6 @@ DATABASE_URL=postgresql://...
# Jira Assets
JIRA_HOST=https://jira.zuyderland.nl
JIRA_SCHEMA_ID=<your_schema_id>
JIRA_SERVICE_ACCOUNT_TOKEN=<service_account_token>
# Jira Authentication Method
@@ -228,7 +227,6 @@ services:
- DATABASE_TYPE=postgres
- DATABASE_URL=${DATABASE_URL}
- JIRA_HOST=${JIRA_HOST}
- JIRA_SCHEMA_ID=${JIRA_SCHEMA_ID}
- JIRA_SERVICE_ACCOUNT_TOKEN=${JIRA_SERVICE_ACCOUNT_TOKEN}
- SESSION_SECRET=${SESSION_SECRET}
ports:

View File

@@ -35,7 +35,6 @@ nano .env
```bash
export JIRA_HOST=https://jira.zuyderland.nl
export JIRA_PAT=your_token
export JIRA_SCHEMA_ID=your_schema_id
export ANTHROPIC_API_KEY=your_key
docker-compose up
@@ -51,7 +50,6 @@ De warnings zijn **niet kritisch**. De applicatie werkt ook zonder deze variabel
# Jira Assets
JIRA_HOST=https://jira.zuyderland.nl
JIRA_PAT=your_personal_access_token
JIRA_SCHEMA_ID=your_schema_id
# AI (optioneel)
ANTHROPIC_API_KEY=your_anthropic_key

View File

@@ -26,7 +26,6 @@ Deze guide beschrijft hoe je de CMDB Insight applicatie veilig en betrouwbaar in
```bash
# .env (niet committen!)
JIRA_HOST=https://jira.zuyderland.nl
JIRA_SCHEMA_ID=your-schema-id
JIRA_AUTH_METHOD=oauth # of 'pat'
JIRA_OAUTH_CLIENT_ID=your-client-id
JIRA_OAUTH_CLIENT_SECRET=your-client-secret

View File

@@ -283,7 +283,6 @@ JIRA_PAT="your-jira-personal-access-token"
SESSION_SECRET=$(openssl rand -hex 32)
JIRA_OAUTH_CLIENT_ID="your-oauth-client-id"
JIRA_OAUTH_CLIENT_SECRET="your-oauth-client-secret"
JIRA_SCHEMA_ID="your-schema-id"
# Add secrets
az keyvault secret set \
@@ -306,11 +305,6 @@ az keyvault secret set \
--name "JiraOAuthClientSecret" \
--value "$JIRA_OAUTH_CLIENT_SECRET"
az keyvault secret set \
--vault-name $KEY_VAULT_NAME \
--name "JiraSchemaId" \
--value "$JIRA_SCHEMA_ID"
# If using PostgreSQL, add database password
az keyvault secret set \
--vault-name $KEY_VAULT_NAME \
@@ -436,7 +430,6 @@ az webapp config appsettings set \
JIRA_OAUTH_CLIENT_ID="@Microsoft.KeyVault(SecretUri=https://${KEY_VAULT_NAME}.vault.azure.net/secrets/JiraOAuthClientId/)" \
JIRA_OAUTH_CLIENT_SECRET="@Microsoft.KeyVault(SecretUri=https://${KEY_VAULT_NAME}.vault.azure.net/secrets/JiraOAuthClientSecret/)" \
JIRA_OAUTH_CALLBACK_URL="https://${BACKEND_APP_NAME}.azurewebsites.net/api/auth/callback" \
JIRA_SCHEMA_ID="@Microsoft.KeyVault(SecretUri=https://${KEY_VAULT_NAME}.vault.azure.net/secrets/JiraSchemaId/)" \
SESSION_SECRET="@Microsoft.KeyVault(SecretUri=https://${KEY_VAULT_NAME}.vault.azure.net/secrets/SessionSecret/)" \
FRONTEND_URL="https://${FRONTEND_APP_NAME}.azurewebsites.net" \
APPINSIGHTS_INSTRUMENTATIONKEY="${INSTRUMENTATION_KEY}"

View File

@@ -132,7 +132,6 @@ DATABASE_URL=postgresql://cmdb:cmdb-dev@localhost:5432/cmdb_insight
# Jira (optioneel)
JIRA_HOST=https://jira.zuyderland.nl
JIRA_PAT=your_token
JIRA_SCHEMA_ID=your_schema_id
# AI (optioneel)
ANTHROPIC_API_KEY=your_key

View File

@@ -952,7 +952,6 @@ cmdb-insight/
# Jira Assets
JIRA_HOST=https://jira.zuyderland.nl
JIRA_PAT=your_personal_access_token_here
JIRA_SCHEMA_ID=your_schema_id
# Object Type IDs (ophalen via API)
JIRA_APPLICATION_COMPONENT_TYPE_ID=your_type_id

View File

@@ -1,39 +1,63 @@
import { useEffect, useState, useRef } from 'react';
import { useEffect, useState, useRef, Suspense, lazy } from 'react';
import { Routes, Route, Link, useLocation, Navigate, useParams, useNavigate } from 'react-router-dom';
import { clsx } from 'clsx';
import SearchDashboard from './components/SearchDashboard';
import Dashboard from './components/Dashboard';
import ApplicationList from './components/ApplicationList';
import ApplicationInfo from './components/ApplicationInfo';
import GovernanceModelHelper from './components/GovernanceModelHelper';
import TeamDashboard from './components/TeamDashboard';
import ConfigurationV25 from './components/ConfigurationV25';
import ReportsDashboard from './components/ReportsDashboard';
import GovernanceAnalysis from './components/GovernanceAnalysis';
import TechnicalDebtHeatmap from './components/TechnicalDebtHeatmap';
import LifecyclePipeline from './components/LifecyclePipeline';
import DataCompletenessScore from './components/DataCompletenessScore';
import ZiRADomainCoverage from './components/ZiRADomainCoverage';
import FTEPerZiRADomain from './components/FTEPerZiRADomain';
import ComplexityDynamicsBubbleChart from './components/ComplexityDynamicsBubbleChart';
import FTECalculator from './components/FTECalculator';
import DataCompletenessConfig from './components/DataCompletenessConfig';
import BIASyncDashboard from './components/BIASyncDashboard';
import BusinessImportanceComparison from './components/BusinessImportanceComparison';
import DataValidationDashboard from './components/DataValidationDashboard';
import SchemaConfigurationSettings from './components/SchemaConfigurationSettings';
import ArchitectureDebugPage from './components/ArchitectureDebugPage';
import Login from './components/Login';
import ForgotPassword from './components/ForgotPassword';
import ResetPassword from './components/ResetPassword';
import AcceptInvitation from './components/AcceptInvitation';
import ProtectedRoute from './components/ProtectedRoute';
import UserManagement from './components/UserManagement';
import RoleManagement from './components/RoleManagement';
import ProfileSettings from './components/ProfileSettings';
import { ToastContainerComponent } from './components/Toast';
import { useAuthStore } from './stores/authStore';
// Core components (loaded immediately - used on main routes)
const SearchDashboard = lazy(() => import('./components/SearchDashboard'));
const Dashboard = lazy(() => import('./components/Dashboard'));
const ApplicationList = lazy(() => import('./components/ApplicationList'));
const ApplicationInfo = lazy(() => import('./components/ApplicationInfo'));
const GovernanceModelHelper = lazy(() => import('./components/GovernanceModelHelper'));
// Reports components (code-split into reports chunk)
const ReportsDashboard = lazy(() => import('./components/ReportsDashboard'));
const TeamDashboard = lazy(() => import('./components/TeamDashboard'));
const GovernanceAnalysis = lazy(() => import('./components/GovernanceAnalysis'));
const TechnicalDebtHeatmap = lazy(() => import('./components/TechnicalDebtHeatmap'));
const LifecyclePipeline = lazy(() => import('./components/LifecyclePipeline'));
const DataCompletenessScore = lazy(() => import('./components/DataCompletenessScore'));
const ZiRADomainCoverage = lazy(() => import('./components/ZiRADomainCoverage'));
const FTEPerZiRADomain = lazy(() => import('./components/FTEPerZiRADomain'));
const ComplexityDynamicsBubbleChart = lazy(() => import('./components/ComplexityDynamicsBubbleChart'));
const BusinessImportanceComparison = lazy(() => import('./components/BusinessImportanceComparison'));
// Apps components (code-split into apps chunk)
const BIASyncDashboard = lazy(() => import('./components/BIASyncDashboard'));
const FTECalculator = lazy(() => import('./components/FTECalculator'));
// Settings components (code-split into settings chunk)
const ConfigurationV25 = lazy(() => import('./components/ConfigurationV25'));
const DataCompletenessConfig = lazy(() => import('./components/DataCompletenessConfig'));
const SchemaConfigurationSettings = lazy(() => import('./components/SchemaConfigurationSettings'));
const DataValidationDashboard = lazy(() => import('./components/DataValidationDashboard'));
const ProfileSettings = lazy(() => import('./components/ProfileSettings'));
// Admin components (code-split into admin chunk)
const UserManagement = lazy(() => import('./components/UserManagement'));
const RoleManagement = lazy(() => import('./components/RoleManagement'));
const ArchitectureDebugPage = lazy(() => import('./components/ArchitectureDebugPage'));
// Auth components (code-split into auth chunk - loaded separately since they're outside main layout)
const Login = lazy(() => import('./components/Login'));
const ForgotPassword = lazy(() => import('./components/ForgotPassword'));
const ResetPassword = lazy(() => import('./components/ResetPassword'));
const AcceptInvitation = lazy(() => import('./components/AcceptInvitation'));
// Loading component for Suspense fallback
function LoadingFallback() {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100 flex items-center justify-center">
<div className="text-center">
<div className="w-12 h-12 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-gray-600 font-medium">Laden...</p>
</div>
</div>
);
}
// Module-level singleton to prevent duplicate initialization across StrictMode remounts
let initializationPromise: Promise<void> | null = null;
@@ -354,61 +378,63 @@ function AppContent() {
{/* Main content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Routes>
{/* Main Dashboard (Search) */}
<Route path="/" element={<ProtectedRoute><SearchDashboard /></ProtectedRoute>} />
{/* Application routes (new structure) - specific routes first, then dynamic */}
<Route path="/application/overview" element={<ProtectedRoute requirePermission="search"><ApplicationList /></ProtectedRoute>} />
<Route path="/application/:id/edit" element={<ProtectedRoute requirePermission="edit_applications"><GovernanceModelHelper /></ProtectedRoute>} />
<Route path="/application/:id" element={<ProtectedRoute requirePermission="search"><ApplicationInfo /></ProtectedRoute>} />
{/* Application Component routes */}
<Route path="/app-components" element={<ProtectedRoute requirePermission="search"><Dashboard /></ProtectedRoute>} />
{/* Reports routes */}
<Route path="/reports" element={<ProtectedRoute requirePermission="view_reports"><ReportsDashboard /></ProtectedRoute>} />
<Route path="/reports/team-dashboard" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
<Route path="/reports/governance-analysis" element={<ProtectedRoute requirePermission="view_reports"><GovernanceAnalysis /></ProtectedRoute>} />
<Route path="/reports/technical-debt-heatmap" element={<ProtectedRoute requirePermission="view_reports"><TechnicalDebtHeatmap /></ProtectedRoute>} />
<Route path="/reports/lifecycle-pipeline" element={<ProtectedRoute requirePermission="view_reports"><LifecyclePipeline /></ProtectedRoute>} />
<Route path="/reports/data-completeness" element={<ProtectedRoute requirePermission="view_reports"><DataCompletenessScore /></ProtectedRoute>} />
<Route path="/reports/zira-domain-coverage" element={<ProtectedRoute requirePermission="view_reports"><ZiRADomainCoverage /></ProtectedRoute>} />
<Route path="/reports/fte-per-zira-domain" element={<ProtectedRoute requirePermission="view_reports"><FTEPerZiRADomain /></ProtectedRoute>} />
<Route path="/reports/complexity-dynamics-bubble" element={<ProtectedRoute requirePermission="view_reports"><ComplexityDynamicsBubbleChart /></ProtectedRoute>} />
<Route path="/reports/business-importance-comparison" element={<ProtectedRoute requirePermission="view_reports"><BusinessImportanceComparison /></ProtectedRoute>} />
{/* Apps routes */}
<Route path="/apps/bia-sync" element={<ProtectedRoute requirePermission="search"><BIASyncDashboard /></ProtectedRoute>} />
<Route path="/apps/fte-calculator" element={<ProtectedRoute requirePermission="search"><FTECalculator /></ProtectedRoute>} />
{/* Settings routes */}
<Route path="/settings/schema-configuration" element={<ProtectedRoute requirePermission="manage_settings"><SchemaConfigurationSettings /></ProtectedRoute>} />
<Route path="/settings/fte-config" element={<ProtectedRoute requirePermission="manage_settings"><ConfigurationV25 /></ProtectedRoute>} />
<Route path="/settings/data-model" element={<Navigate to="/settings/schema-configuration" replace />} />
<Route path="/settings/data-validation" element={<ProtectedRoute requirePermission="manage_settings"><DataValidationDashboard /></ProtectedRoute>} />
<Route path="/settings/data-completeness-config" element={<ProtectedRoute requirePermission="manage_settings"><DataCompletenessConfig /></ProtectedRoute>} />
<Route path="/settings/profile" element={<ProtectedRoute><ProfileSettings /></ProtectedRoute>} />
{/* Legacy redirects for old routes */}
<Route path="/settings/user-settings" element={<Navigate to="/settings/profile" replace />} />
{/* Admin routes */}
<Route path="/admin/users" element={<ProtectedRoute requirePermission="manage_users"><UserManagement /></ProtectedRoute>} />
<Route path="/admin/roles" element={<ProtectedRoute requirePermission="manage_roles"><RoleManagement /></ProtectedRoute>} />
<Route path="/admin/debug" element={<ProtectedRoute requirePermission="admin"><ArchitectureDebugPage /></ProtectedRoute>} />
{/* Legacy redirects for bookmarks - redirect old paths to new ones */}
<Route path="/app-components/overview" element={<Navigate to="/application/overview" replace />} />
<Route path="/app-components/overview/:id" element={<RedirectToApplicationEdit />} />
<Route path="/app-components/fte-config" element={<Navigate to="/settings/fte-config" replace />} />
<Route path="/applications" element={<Navigate to="/application/overview" replace />} />
<Route path="/applications/:id" element={<RedirectToApplicationEdit />} />
<Route path="/application/fte-calculator" element={<Navigate to="/apps/fte-calculator" replace />} />
<Route path="/reports/data-model" element={<Navigate to="/settings/schema-configuration" replace />} />
<Route path="/reports/bia-sync" element={<Navigate to="/apps/bia-sync" replace />} />
<Route path="/teams" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
<Route path="/configuration" element={<Navigate to="/settings/fte-config" replace />} />
</Routes>
<Suspense fallback={<LoadingFallback />}>
<Routes>
{/* Main Dashboard (Search) */}
<Route path="/" element={<ProtectedRoute><SearchDashboard /></ProtectedRoute>} />
{/* Application routes (new structure) - specific routes first, then dynamic */}
<Route path="/application/overview" element={<ProtectedRoute requirePermission="search"><ApplicationList /></ProtectedRoute>} />
<Route path="/application/:id/edit" element={<ProtectedRoute requirePermission="edit_applications"><GovernanceModelHelper /></ProtectedRoute>} />
<Route path="/application/:id" element={<ProtectedRoute requirePermission="search"><ApplicationInfo /></ProtectedRoute>} />
{/* Application Component routes */}
<Route path="/app-components" element={<ProtectedRoute requirePermission="search"><Dashboard /></ProtectedRoute>} />
{/* Reports routes */}
<Route path="/reports" element={<ProtectedRoute requirePermission="view_reports"><ReportsDashboard /></ProtectedRoute>} />
<Route path="/reports/team-dashboard" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
<Route path="/reports/governance-analysis" element={<ProtectedRoute requirePermission="view_reports"><GovernanceAnalysis /></ProtectedRoute>} />
<Route path="/reports/technical-debt-heatmap" element={<ProtectedRoute requirePermission="view_reports"><TechnicalDebtHeatmap /></ProtectedRoute>} />
<Route path="/reports/lifecycle-pipeline" element={<ProtectedRoute requirePermission="view_reports"><LifecyclePipeline /></ProtectedRoute>} />
<Route path="/reports/data-completeness" element={<ProtectedRoute requirePermission="view_reports"><DataCompletenessScore /></ProtectedRoute>} />
<Route path="/reports/zira-domain-coverage" element={<ProtectedRoute requirePermission="view_reports"><ZiRADomainCoverage /></ProtectedRoute>} />
<Route path="/reports/fte-per-zira-domain" element={<ProtectedRoute requirePermission="view_reports"><FTEPerZiRADomain /></ProtectedRoute>} />
<Route path="/reports/complexity-dynamics-bubble" element={<ProtectedRoute requirePermission="view_reports"><ComplexityDynamicsBubbleChart /></ProtectedRoute>} />
<Route path="/reports/business-importance-comparison" element={<ProtectedRoute requirePermission="view_reports"><BusinessImportanceComparison /></ProtectedRoute>} />
{/* Apps routes */}
<Route path="/apps/bia-sync" element={<ProtectedRoute requirePermission="search"><BIASyncDashboard /></ProtectedRoute>} />
<Route path="/apps/fte-calculator" element={<ProtectedRoute requirePermission="search"><FTECalculator /></ProtectedRoute>} />
{/* Settings routes */}
<Route path="/settings/schema-configuration" element={<ProtectedRoute requirePermission="manage_settings"><SchemaConfigurationSettings /></ProtectedRoute>} />
<Route path="/settings/fte-config" element={<ProtectedRoute requirePermission="manage_settings"><ConfigurationV25 /></ProtectedRoute>} />
<Route path="/settings/data-model" element={<Navigate to="/settings/schema-configuration" replace />} />
<Route path="/settings/data-validation" element={<ProtectedRoute requirePermission="manage_settings"><DataValidationDashboard /></ProtectedRoute>} />
<Route path="/settings/data-completeness-config" element={<ProtectedRoute requirePermission="manage_settings"><DataCompletenessConfig /></ProtectedRoute>} />
<Route path="/settings/profile" element={<ProtectedRoute><ProfileSettings /></ProtectedRoute>} />
{/* Legacy redirects for old routes */}
<Route path="/settings/user-settings" element={<Navigate to="/settings/profile" replace />} />
{/* Admin routes */}
<Route path="/admin/users" element={<ProtectedRoute requirePermission="manage_users"><UserManagement /></ProtectedRoute>} />
<Route path="/admin/roles" element={<ProtectedRoute requirePermission="manage_roles"><RoleManagement /></ProtectedRoute>} />
<Route path="/admin/debug" element={<ProtectedRoute requirePermission="admin"><ArchitectureDebugPage /></ProtectedRoute>} />
{/* Legacy redirects for bookmarks - redirect old paths to new ones */}
<Route path="/app-components/overview" element={<Navigate to="/application/overview" replace />} />
<Route path="/app-components/overview/:id" element={<RedirectToApplicationEdit />} />
<Route path="/app-components/fte-config" element={<Navigate to="/settings/fte-config" replace />} />
<Route path="/applications" element={<Navigate to="/application/overview" replace />} />
<Route path="/applications/:id" element={<RedirectToApplicationEdit />} />
<Route path="/application/fte-calculator" element={<Navigate to="/apps/fte-calculator" replace />} />
<Route path="/reports/data-model" element={<Navigate to="/settings/schema-configuration" replace />} />
<Route path="/reports/bia-sync" element={<Navigate to="/apps/bia-sync" replace />} />
<Route path="/teams" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
<Route path="/configuration" element={<Navigate to="/settings/fte-config" replace />} />
</Routes>
</Suspense>
</main>
</div>
);
@@ -539,12 +565,14 @@ function App() {
// If on an auth route, render it directly (no layout) - don't wait for config
if (isAuthRoute) {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/accept-invitation" element={<AcceptInvitation />} />
</Routes>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/accept-invitation" element={<AcceptInvitation />} />
</Routes>
</Suspense>
);
}

View File

@@ -9,6 +9,53 @@ export default defineConfig({
'@': path.resolve(__dirname, './src'),
},
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
// Vendor chunks - separate large libraries
if (id.includes('node_modules')) {
if (id.includes('react') || id.includes('react-dom') || id.includes('react-router')) {
return 'react-vendor';
}
if (id.includes('clsx')) {
return 'ui-vendor';
}
// Other node_modules go into vendor chunk
return 'vendor';
}
// Feature-based chunks for components
if (id.includes('/components/')) {
if (id.includes('ReportsDashboard') || id.includes('TeamDashboard') ||
id.includes('GovernanceAnalysis') || id.includes('TechnicalDebtHeatmap') ||
id.includes('LifecyclePipeline') || id.includes('DataCompletenessScore') ||
id.includes('ZiRADomainCoverage') || id.includes('FTEPerZiRADomain') ||
id.includes('ComplexityDynamicsBubbleChart') || id.includes('BusinessImportanceComparison')) {
return 'reports';
}
if (id.includes('ConfigurationV25') || id.includes('DataCompletenessConfig') ||
id.includes('SchemaConfigurationSettings') || id.includes('DataValidationDashboard') ||
id.includes('ProfileSettings')) {
return 'settings';
}
if (id.includes('UserManagement') || id.includes('RoleManagement') ||
id.includes('ArchitectureDebugPage')) {
return 'admin';
}
if (id.includes('BIASyncDashboard') || id.includes('FTECalculator')) {
return 'apps';
}
if (id.includes('Login') || id.includes('ForgotPassword') ||
id.includes('ResetPassword') || id.includes('AcceptInvitation')) {
return 'auth';
}
}
},
},
},
chunkSizeWarningLimit: 600, // Increase limit slightly since we're code-splitting
},
server: {
port: 5173,
proxy: {

View File

@@ -22,13 +22,11 @@ FRONTEND_URL="https://${FRONTEND_APP_NAME}.azurewebsites.net"
echo ""
echo "📝 Configure these values:"
echo " JIRA_SCHEMA_ID: (your Jira schema ID)"
echo " JIRA_PAT: (your Jira Personal Access Token)"
echo " Or JIRA_OAUTH_CLIENT_ID and JIRA_OAUTH_CLIENT_SECRET"
echo ""
# Prompt for values (or set them as environment variables)
read -p "Enter JIRA_SCHEMA_ID (or press Enter to skip): " JIRA_SCHEMA_ID
read -p "Enter JIRA_PAT (or press Enter to skip): " JIRA_PAT
read -p "Enter JIRA_OAUTH_CLIENT_ID (or press Enter to skip): " JIRA_OAUTH_CLIENT_ID
read -p "Enter JIRA_OAUTH_CLIENT_SECRET (or press Enter to skip): " JIRA_OAUTH_CLIENT_SECRET
@@ -39,10 +37,6 @@ echo "🔧 Configuring backend app settings..."
# Build settings string
SETTINGS="NODE_ENV=production PORT=3001 JIRA_BASE_URL=https://jira.zuyderland.nl SESSION_SECRET=${SESSION_SECRET} FRONTEND_URL=${FRONTEND_URL}"
if [ -n "$JIRA_SCHEMA_ID" ]; then
SETTINGS="${SETTINGS} JIRA_SCHEMA_ID=${JIRA_SCHEMA_ID}"
fi
if [ -n "$JIRA_PAT" ]; then
SETTINGS="${SETTINGS} JIRA_PAT=${JIRA_PAT}"
fi

View File

@@ -200,13 +200,11 @@ echo " - JiraPat (if using PAT authentication)"
echo " - SessionSecret (generate with: openssl rand -hex 32)"
echo " - JiraOAuthClientId (if using OAuth)"
echo " - JiraOAuthClientSecret (if using OAuth)"
echo " - JiraSchemaId"
echo ""
echo "Commands to add secrets:"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name SessionSecret --value \$(openssl rand -hex 32)"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name JiraOAuthClientId --value <your-client-id>"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name JiraOAuthClientSecret --value <your-client-secret>"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name JiraSchemaId --value <your-schema-id>"
echo ""
echo -e "${GREEN}Step 5: Creating Application Insights...${NC}"
@@ -382,7 +380,6 @@ echo "1. Add secrets to Key Vault:"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name SessionSecret --value \$(openssl rand -hex 32)"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name JiraOAuthClientId --value <your-client-id>"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name JiraOAuthClientSecret --value <your-client-secret>"
echo " az keyvault secret set --vault-name $KEY_VAULT_NAME --name JiraSchemaId --value <your-schema-id>"
echo ""
echo "2. Update backend app settings to use Key Vault references:"
echo " See AZURE-NEW-SUBSCRIPTION-SETUP.md for details"