UI styling improvements: dashboard headers and navigation

- Restore blue PageHeader on Dashboard (/app-components)
- Update homepage (/) with subtle header design without blue bar
- Add uniform PageHeader styling to application edit page
- Fix Rapporten link on homepage to point to /reports overview
- Improve header descriptions spacing for better readability
This commit is contained in:
2026-01-21 03:24:56 +01:00
parent e276e77fbc
commit cdee0e8819
138 changed files with 24551 additions and 3352 deletions

View File

@@ -6,7 +6,6 @@ import cookieParser from 'cookie-parser';
import { config, validateConfig } from './config/env.js';
import { logger } from './services/logger.js';
import { dataService } from './services/dataService.js';
import { syncEngine } from './services/syncEngine.js';
import { cmdbService } from './services/cmdbService.js';
import applicationsRouter from './routes/applications.js';
import classificationsRouter from './routes/classifications.js';
@@ -22,6 +21,8 @@ import searchRouter from './routes/search.js';
import cacheRouter from './routes/cache.js';
import objectsRouter from './routes/objects.js';
import schemaRouter from './routes/schema.js';
import dataValidationRouter from './routes/dataValidation.js';
import schemaConfigurationRouter from './routes/schemaConfiguration.js';
import { runMigrations } from './services/database/migrations.js';
// Validate configuration
@@ -63,8 +64,10 @@ app.use(authMiddleware);
// Set user token and settings on services for each request
app.use(async (req, res, next) => {
// Set user's OAuth token if available (for OAuth sessions)
let userToken: string | null = null;
if (req.accessToken) {
cmdbService.setUserToken(req.accessToken);
userToken = req.accessToken;
}
// Set user's Jira PAT and AI keys if user is authenticated and has local account
@@ -75,15 +78,12 @@ app.use(async (req, res, next) => {
if (settings?.jira_pat) {
// Use user's Jira PAT from profile settings (preferred for writes)
cmdbService.setUserToken(settings.jira_pat);
userToken = settings.jira_pat;
} else if (config.jiraServiceAccountToken) {
// Fallback to service account token if user doesn't have PAT configured
// This allows writes to work when JIRA_SERVICE_ACCOUNT_TOKEN is set in .env
cmdbService.setUserToken(config.jiraServiceAccountToken);
userToken = config.jiraServiceAccountToken;
logger.debug('Using service account token as fallback (user PAT not configured)');
} else {
// No token available - clear token
cmdbService.setUserToken(null);
}
// Store user settings in request for services to access
@@ -92,18 +92,35 @@ app.use(async (req, res, next) => {
// If user settings can't be loaded, try service account token as fallback
logger.debug('Failed to load user settings:', error);
if (config.jiraServiceAccountToken) {
cmdbService.setUserToken(config.jiraServiceAccountToken);
userToken = config.jiraServiceAccountToken;
logger.debug('Using service account token as fallback (user settings load failed)');
} else {
cmdbService.setUserToken(null);
}
}
}
// Set token on old services (for backward compatibility)
if (userToken) {
cmdbService.setUserToken(userToken);
} else {
// No user authenticated - clear token
cmdbService.setUserToken(null);
}
// Set token on new V2 infrastructure client (if feature flag enabled)
if (process.env.USE_V2_API === 'true') {
try {
const { jiraAssetsClient } = await import('./infrastructure/jira/JiraAssetsClient.js');
jiraAssetsClient.setRequestToken(userToken);
// Clear token after response
res.on('finish', () => {
jiraAssetsClient.clearRequestToken();
});
} catch (error) {
// V2 API not loaded - ignore
}
}
// Clear token after response is sent
// Clear token after response is sent (for old services)
res.on('finish', () => {
cmdbService.clearUserToken();
});
@@ -119,8 +136,8 @@ app.get('/health', async (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
dataSource: dataService.isUsingJiraAssets() ? 'jira-assets-cached' : 'mock-data',
jiraConnected: dataService.isUsingJiraAssets() ? jiraConnected : null,
dataSource: 'jira-assets-cached', // Always uses Jira Assets (mock data removed)
jiraConnected: jiraConnected,
aiConfigured: true, // AI is configured per-user in profile settings
cache: {
isWarm: cacheStatus.isWarm,
@@ -152,6 +169,38 @@ app.use('/api/search', searchRouter);
app.use('/api/cache', cacheRouter);
app.use('/api/objects', objectsRouter);
app.use('/api/schema', schemaRouter);
app.use('/api/data-validation', dataValidationRouter);
app.use('/api/schema-configuration', schemaConfigurationRouter);
// V2 API routes (new refactored architecture) - Feature flag: USE_V2_API
const useV2Api = process.env.USE_V2_API === 'true';
const useV2ApiEnv = process.env.USE_V2_API || 'not set';
logger.info(`V2 API feature flag: USE_V2_API=${useV2ApiEnv} (enabled: ${useV2Api})`);
if (useV2Api) {
try {
logger.debug('Loading V2 API routes from ./api/routes/v2.js...');
const v2Router = (await import('./api/routes/v2.js')).default;
if (!v2Router) {
logger.error('❌ V2 API router is undefined - route file did not export default router');
} else {
app.use('/api/v2', v2Router);
logger.info('✅ V2 API routes enabled and mounted at /api/v2');
logger.debug('V2 API router type:', typeof v2Router, 'is function:', typeof v2Router === 'function');
}
} catch (error) {
logger.error('❌ Failed to load V2 API routes', error);
if (error instanceof Error) {
logger.error('Error details:', {
message: error.message,
stack: error.stack,
name: error.name,
});
}
}
} else {
logger.info(` V2 API routes disabled (USE_V2_API=${useV2ApiEnv}, set USE_V2_API=true to enable)`);
}
// Error handling
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -164,7 +213,20 @@ app.use((err: Error, req: express.Request, res: express.Response, next: express.
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Not found' });
// Provide helpful error messages for V2 API routes
if (req.path.startsWith('/api/v2/')) {
const useV2Api = process.env.USE_V2_API === 'true';
if (!useV2Api) {
res.status(404).json({
error: 'V2 API routes are not enabled',
message: 'Please set USE_V2_API=true in environment variables and restart the server to use V2 API endpoints.',
path: req.path,
});
return;
}
}
res.status(404).json({ error: 'Not found', path: req.path });
});
// Start server
@@ -173,26 +235,51 @@ app.listen(PORT, async () => {
logger.info(`Server running on http://localhost:${PORT}`);
logger.info(`Environment: ${config.nodeEnv}`);
logger.info(`AI Classification: Configured per-user in profile settings`);
logger.info(`Jira Assets: ${config.jiraSchemaId ? 'Schema configured - users configure PAT in profile' : 'Schema not configured'}`);
// Run database migrations
// Log V2 API feature flag status
const useV2ApiEnv = process.env.USE_V2_API || 'not set';
const useV2ApiEnabled = process.env.USE_V2_API === 'true';
logger.info(`V2 API Feature Flag: USE_V2_API=${useV2ApiEnv} (${useV2ApiEnabled ? '✅ ENABLED' : '❌ DISABLED'})`);
// Check if schemas exist in database
// Note: Schemas table may not exist yet if schema hasn't been initialized
let hasSchemas = false;
try {
await runMigrations();
logger.info('Database migrations completed');
const { normalizedCacheStore } = await import('./services/normalizedCacheStore.js');
const db = (normalizedCacheStore as any).db;
if (db) {
await db.ensureInitialized?.();
try {
const schemaRow = await db.queryOne<{ count: number }>(
`SELECT COUNT(*) as count FROM schemas`
);
hasSchemas = (schemaRow?.count || 0) > 0;
} catch (tableError: any) {
// If schemas table doesn't exist yet, that's okay - schema hasn't been initialized
if (tableError?.message?.includes('does not exist') ||
tableError?.message?.includes('relation') ||
tableError?.code === '42P01') { // PostgreSQL: undefined table
logger.debug('Schemas table does not exist yet (will be created by migrations)');
hasSchemas = false;
} else {
throw tableError; // Re-throw other errors
}
}
}
} catch (error) {
logger.error('Failed to run database migrations', error);
logger.debug('Failed to check if schemas exist in database (table may not exist yet)', error);
}
// Initialize sync engine if Jira schema is configured
// Note: Sync engine will only sync when users with configured Jira PATs make requests
// This prevents unauthorized Jira API calls
if (config.jiraSchemaId) {
try {
await syncEngine.initialize();
logger.info('Sync Engine: Initialized (sync on-demand per user request)');
} catch (error) {
logger.error('Failed to initialize sync engine', error);
}
logger.info(`Jira Assets: ${hasSchemas ? 'Schemas configured in database - users configure PAT in profile' : 'No schemas configured - use Schema Configuration page to discover schemas'}`);
logger.info('Sync: All syncs must be triggered manually from the GUI (no auto-start)');
logger.info('Data: All data comes from Jira Assets API (mock data removed)');
// Run database migrations FIRST to create schemas table before other services try to use it
try {
logger.info('Running database migrations...');
await runMigrations();
logger.info('✅ Database migrations completed');
} catch (error) {
logger.error('❌ Failed to run database migrations', error);
}
});
@@ -200,8 +287,7 @@ app.listen(PORT, async () => {
const shutdown = () => {
logger.info('Shutdown signal received: stopping services...');
// Stop sync engine
syncEngine.stop();
// Note: No sync engine to stop - syncs are only triggered from GUI
logger.info('Services stopped, exiting');
process.exit(0);