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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user