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

@@ -0,0 +1,176 @@
/**
* ObjectsController - API handlers for object operations
*
* NO SQL, NO parsing - delegates to services.
*/
import { Request, Response } from 'express';
import { logger } from '../../services/logger.js';
import { getServices } from '../../services/ServiceFactory.js';
import type { CMDBObject, CMDBObjectTypeName } from '../../generated/jira-types.js';
import { getParamString, getQueryString, getQueryNumber } from '../../utils/queryHelpers.js';
export class ObjectsController {
/**
* Get a single object by ID or objectKey
* GET /api/v2/objects/:type/:id?refresh=true
* Supports both object ID and objectKey (checks objectKey if ID lookup fails)
*/
async getObject(req: Request, res: Response): Promise<void> {
try {
const type = getParamString(req, 'type');
const idOrKey = getParamString(req, 'id');
const forceRefresh = getQueryString(req, 'refresh') === 'true';
const services = getServices();
// Try to find object ID if idOrKey might be an objectKey
let objectId = idOrKey;
let objRecord = await services.cacheRepo.getObject(idOrKey);
if (!objRecord) {
// Try as objectKey
objRecord = await services.cacheRepo.getObjectByKey(idOrKey);
if (objRecord) {
objectId = objRecord.id;
}
}
// Force refresh if requested
if (forceRefresh && objectId) {
const enabledTypes = await services.schemaRepo.getEnabledObjectTypes();
const enabledTypeSet = new Set(enabledTypes.map(t => t.typeName));
const refreshResult = await services.refreshService.refreshObject(objectId, enabledTypeSet);
if (!refreshResult.success) {
res.status(500).json({ error: refreshResult.error || 'Failed to refresh object' });
return;
}
}
// Get from cache
if (!objectId) {
res.status(404).json({ error: 'Object not found (by ID or key)' });
return;
}
const object = await services.queryService.getObject<CMDBObject>(type as CMDBObjectTypeName, objectId);
if (!object) {
res.status(404).json({ error: 'Object not found' });
return;
}
res.json(object);
} catch (error) {
logger.error('ObjectsController: Failed to get object', error);
res.status(500).json({ error: 'Failed to get object' });
}
}
/**
* Get all objects of a type
* GET /api/v2/objects/:type?limit=100&offset=0&search=term
*/
async getObjects(req: Request, res: Response): Promise<void> {
try {
const type = getParamString(req, 'type');
const limit = getQueryNumber(req, 'limit', 1000);
const offset = getQueryNumber(req, 'offset', 0);
const search = getQueryString(req, 'search');
const services = getServices();
logger.info(`ObjectsController.getObjects: Querying for type="${type}" with limit=${limit}, offset=${offset}, search=${search || 'none'}`);
let objects: CMDBObject[];
if (search) {
objects = await services.queryService.searchByLabel<CMDBObject>(
type as CMDBObjectTypeName,
search,
{ limit, offset }
);
} else {
objects = await services.queryService.getObjects<CMDBObject>(
type as CMDBObjectTypeName,
{ limit, offset }
);
}
const totalCount = await services.queryService.countObjects(type as CMDBObjectTypeName);
logger.info(`ObjectsController.getObjects: Found ${objects.length} objects of type "${type}" (total count: ${totalCount})`);
// If no objects found, provide diagnostic information
if (objects.length === 0) {
// Check what object types actually exist in the database
const db = services.cacheRepo.db;
try {
const availableTypes = await db.query<{ object_type_name: string; count: number }>(
`SELECT object_type_name, COUNT(*) as count
FROM objects
GROUP BY object_type_name
ORDER BY count DESC
LIMIT 10`
);
if (availableTypes.length > 0) {
logger.warn(`ObjectsController.getObjects: No objects found for type "${type}". Available types in database:`, {
requestedType: type,
availableTypes: availableTypes.map(t => ({ typeName: t.object_type_name, count: t.count })),
});
}
} catch (error) {
logger.debug('ObjectsController.getObjects: Failed to query available types', error);
}
}
res.json({
objectType: type,
objects,
count: objects.length,
totalCount,
offset,
limit,
});
} catch (error) {
logger.error('ObjectsController: Failed to get objects', error);
res.status(500).json({ error: 'Failed to get objects' });
}
}
/**
* Update an object
* PUT /api/v2/objects/:type/:id
*/
async updateObject(req: Request, res: Response): Promise<void> {
try {
const type = getParamString(req, 'type');
const id = getParamString(req, 'id');
const updates = req.body as Record<string, unknown>;
const services = getServices();
const result = await services.writeThroughService.updateObject(
type as CMDBObjectTypeName,
id,
updates
);
if (!result.success) {
res.status(400).json({ error: result.error || 'Failed to update object' });
return;
}
// Fetch updated object
const updated = await services.queryService.getObject<CMDBObject>(
type as CMDBObjectTypeName,
id
);
res.json(updated || { success: true });
} catch (error) {
logger.error('ObjectsController: Failed to update object', error);
res.status(500).json({ error: 'Failed to update object' });
}
}
}