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:
@@ -326,8 +326,9 @@ router.get('/bia-comparison', async (req: Request, res: Response) => {
|
||||
// Query params:
|
||||
// - mode=edit: Force refresh from Jira for editing (includes _jiraUpdatedAt for conflict detection)
|
||||
router.get('/:id', async (req: Request, res: Response) => {
|
||||
const id = getParamString(req, 'id');
|
||||
|
||||
try {
|
||||
const id = getParamString(req, 'id');
|
||||
const mode = getQueryString(req, 'mode');
|
||||
|
||||
// Don't treat special routes as application IDs
|
||||
@@ -342,7 +343,7 @@ router.get('/:id', async (req: Request, res: Response) => {
|
||||
: await dataService.getApplicationById(id);
|
||||
|
||||
if (!application) {
|
||||
res.status(404).json({ error: 'Application not found' });
|
||||
res.status(404).json({ error: 'Application not found', id });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -355,8 +356,15 @@ router.get('/:id', async (req: Request, res: Response) => {
|
||||
|
||||
res.json(applicationWithCompleteness);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get application', error);
|
||||
res.status(500).json({ error: 'Failed to get application' });
|
||||
logger.error(`Failed to get application ${id}`, error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
const errorDetails = error instanceof Error && error.stack ? error.stack : String(error);
|
||||
logger.debug(`Error details for application ${id}:`, errorDetails);
|
||||
res.status(500).json({
|
||||
error: 'Failed to get application',
|
||||
details: errorMessage,
|
||||
id: id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -625,34 +633,101 @@ router.get('/:id/related/:objectType', async (req: Request, res: Response) => {
|
||||
type RelatedObjectType = Server | Flows | Certificate | Domain | AzureSubscription;
|
||||
let relatedObjects: RelatedObjectType[] = [];
|
||||
|
||||
// Get requested attributes from query string (needed for fallback)
|
||||
const attributesParam = getQueryString(req, 'attributes');
|
||||
const requestedAttrs = attributesParam
|
||||
? attributesParam.split(',').map(a => a.trim())
|
||||
: [];
|
||||
|
||||
logger.debug(`Getting related objects for application ${id}, objectType: ${objectType}, typeName: ${typeName}, requestedAttrs: ${requestedAttrs.join(',') || 'none'}`);
|
||||
|
||||
// First try to get from cache
|
||||
switch (typeName) {
|
||||
case 'Server':
|
||||
relatedObjects = await cmdbService.getReferencingObjects<Server>(id, 'Server');
|
||||
logger.debug(`Found ${relatedObjects.length} Servers referencing application ${id} in cache`);
|
||||
break;
|
||||
case 'Flows': {
|
||||
// Flows reference ApplicationComponents via Source and Target attributes
|
||||
// We need to find Flows where this ApplicationComponent is the target of the reference
|
||||
relatedObjects = await cmdbService.getReferencingObjects<Flows>(id, 'Flows');
|
||||
logger.debug(`Found ${relatedObjects.length} Flows referencing application ${id} in cache`);
|
||||
break;
|
||||
}
|
||||
case 'Certificate':
|
||||
relatedObjects = await cmdbService.getReferencingObjects<Certificate>(id, 'Certificate');
|
||||
logger.debug(`Found ${relatedObjects.length} Certificates referencing application ${id} in cache`);
|
||||
break;
|
||||
case 'Domain':
|
||||
relatedObjects = await cmdbService.getReferencingObjects<Domain>(id, 'Domain');
|
||||
logger.debug(`Found ${relatedObjects.length} Domains referencing application ${id} in cache`);
|
||||
break;
|
||||
case 'AzureSubscription':
|
||||
relatedObjects = await cmdbService.getReferencingObjects<AzureSubscription>(id, 'AzureSubscription');
|
||||
logger.debug(`Found ${relatedObjects.length} AzureSubscriptions referencing application ${id} in cache`);
|
||||
break;
|
||||
default:
|
||||
relatedObjects = [];
|
||||
logger.warn(`Unknown object type for related objects: ${typeName}`);
|
||||
}
|
||||
|
||||
// If no objects found in cache, try to fetch from Jira directly as fallback
|
||||
// This helps when relations haven't been synced yet
|
||||
if (relatedObjects.length === 0) {
|
||||
try {
|
||||
// Get application to get its objectKey
|
||||
const app = await cmdbService.getObject('ApplicationComponent', id);
|
||||
if (!app) {
|
||||
logger.warn(`Application ${id} not found in cache, cannot fetch related objects from Jira`);
|
||||
} else if (!app.objectKey) {
|
||||
logger.warn(`Application ${id} has no objectKey, cannot fetch related objects from Jira`);
|
||||
} else {
|
||||
logger.info(`No related ${typeName} objects found in cache for application ${id} (${app.objectKey}), trying Jira directly...`);
|
||||
const { jiraAssetsService } = await import('../services/jiraAssets.js');
|
||||
// Use the Jira object type name from schema (not our internal typeName)
|
||||
const { OBJECT_TYPES } = await import('../generated/jira-schema.js');
|
||||
const jiraTypeDef = OBJECT_TYPES[typeName];
|
||||
const jiraObjectTypeName = jiraTypeDef?.name || objectType;
|
||||
logger.debug(`Using Jira object type name: "${jiraObjectTypeName}" for internal type "${typeName}"`);
|
||||
const jiraResult = await jiraAssetsService.getRelatedObjects(app.objectKey, jiraObjectTypeName, requestedAttrs);
|
||||
|
||||
logger.debug(`Jira query returned ${jiraResult?.objects?.length || 0} objects`);
|
||||
|
||||
if (jiraResult && jiraResult.objects && jiraResult.objects.length > 0) {
|
||||
logger.info(`Found ${jiraResult.objects.length} related ${typeName} objects from Jira, caching them...`);
|
||||
|
||||
// Batch fetch and cache all objects at once (much more efficient)
|
||||
const objectIds = jiraResult.objects.map(obj => obj.id.toString());
|
||||
const cachedObjects = await cmdbService.batchFetchAndCacheObjects(typeName as CMDBObjectTypeName, objectIds);
|
||||
|
||||
logger.info(`Successfully batch cached ${cachedObjects.length} of ${jiraResult.objects.length} related ${typeName} objects`);
|
||||
|
||||
// Use cached objects, fallback to minimal objects from Jira result if not found
|
||||
const cachedById = new Map(cachedObjects.map(obj => [obj.id, obj]));
|
||||
relatedObjects = jiraResult.objects.map((jiraObj) => {
|
||||
const cached = cachedById.get(jiraObj.id.toString());
|
||||
if (cached) {
|
||||
return cached as RelatedObjectType;
|
||||
}
|
||||
// Fallback: create minimal object from Jira result
|
||||
logger.debug(`Creating minimal object for ${jiraObj.id} (${jiraObj.key}) as cache lookup failed`);
|
||||
return {
|
||||
id: jiraObj.id.toString(),
|
||||
objectKey: jiraObj.key,
|
||||
label: jiraObj.label,
|
||||
_objectType: typeName,
|
||||
} as RelatedObjectType;
|
||||
});
|
||||
|
||||
logger.info(`Loaded ${relatedObjects.length} related ${typeName} objects (${relatedObjects.filter(o => o).length} valid)`);
|
||||
} else {
|
||||
logger.info(`No related ${typeName} objects found in Jira for application ${app.objectKey}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to fetch related ${typeName} objects from Jira as fallback for application ${id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get requested attributes from query string
|
||||
const attributesParam = getQueryString(req, 'attributes');
|
||||
const requestedAttrs = attributesParam
|
||||
? attributesParam.split(',').map(a => a.trim())
|
||||
: [];
|
||||
|
||||
// Format response - must match RelatedObjectsResponse type expected by frontend
|
||||
const objects = relatedObjects.map(obj => {
|
||||
|
||||
Reference in New Issue
Block a user