2026-02-04 23:31:53 +01:00
<?php
/**
* @var int $activeUserCount
* @var int $inactiveUserCount
2026-02-11 19:28:12 +01:00
* @var int $activeTenantCount
* @var int $inactiveTenantCount
* @var int $activeDepartmentCount
* @var int $inactiveDepartmentCount
* @var int $activeRoleCount
* @var int $inactiveRoleCount
* @var int $rolesWithoutPermissionsCount
* @var int $permissionsWithoutRolesCount
* @var int $usersWithoutRolesCount
* @var int $usersWithoutTenantsCount
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
* @var bool $ssoStatsAvailable
* @var int $ssoEnabledTenantCount
* @var int $ssoEnforcedTenantCount
* @var float $ssoEnabledTenantPercent
* @var int $usersMicrosoftOnlyCount
* @var int $usersMicrosoftSyncGapsCount
* @var array< int , array < string , mixed > > $ssoTenantRows
* @var array< int , array < string , mixed > > $usersMicrosoftOnlyRows
* @var array< int , array < string , mixed > > $usersMicrosoftSyncGapRows
* @var bool $customFieldStatsAvailable
* @var int $customFieldsSelectionWithoutOptionsCount
* @var int $customFieldTenantsWithSelectionWithoutOptionsCount
* @var int $customFieldsInactiveWithValuesCount
* @var int $customFieldsUnusedActiveCount
* @var string $customFieldsSelectionWithoutOptionsHref
* @var string $customFieldTenantsWithSelectionWithoutOptionsHref
* @var string $customFieldsInactiveWithValuesHref
* @var string $customFieldsUnusedActiveHref
* @var array< int , array < string , mixed > > $customFieldIssueTenantRows
* @var bool $apiStatsAvailable
* @var int $apiRequests24hCount
* @var int $apiRequestsPrev24hCount
* @var float $apiRequestTrendPercent
* @var int $apiErrors4xx24hCount
* @var int $apiErrors5xx24hCount
* @var int $apiP95DurationMs
* @var int $apiTokensExpiring7dCount
* @var array< int , array < string , mixed > > $apiTopErrorRows
* @var array< int , array < string , mixed > > $apiSlowEndpointRows
* @var bool $importStatsAvailable
* @var int $importRuns7dCount
* @var int $importRunsPrev7dCount
* @var float $importRunTrendPercent
* @var int $importRowsCreated7dCount
* @var int $importRowsFailed7dCount
* @var int $importPartialOrFailedRuns7dCount
* @var float $importSuccessRate7dPercent
* @var int $importAverageDurationMs7d
* @var array< int , array < string , mixed > > $importRecentRunRows
* @var array< int , array < string , mixed > > $importProfileRows
* @var array< int , array < string , mixed > > $importTopErrorCodeRows
2026-03-04 15:56:58 +01:00
* @var bool $auditStatsAvailable
* @var bool $systemAuditStatsAvailable
* @var bool $userLifecycleAuditStatsAvailable
* @var int $systemAuditEvents24hCount
* @var int $systemAuditFailed24hCount
* @var int $systemAuditDenied24hCount
* @var int $systemAuditEventsPrev24hCount
* @var float $systemAuditTrendPercent
* @var array< int , array < string , mixed > > $systemAuditTopEventTypeRows
* @var array< int , array < string , mixed > > $systemAuditRecentRiskRows
* @var string $systemAuditAll24hHref
* @var string $systemAuditFailed24hHref
* @var string $systemAuditDenied24hHref
* @var int $lifecycleRuns7dCount
* @var int $lifecycleRunsPrev7dCount
* @var float $lifecycleRunTrendPercent
* @var int $lifecycleFailed7dCount
* @var int $lifecycleSkipped7dCount
* @var int $lifecycleRestore7dCount
* @var array< int , array < string , mixed > > $lifecycleTopReasonRows
* @var array< int , array < string , mixed > > $lifecycleRecentRiskRows
* @var string $lifecycleAll7dHref
* @var string $lifecycleFailed7dHref
* @var string $lifecycleRestore7dHref
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
* @var bool $scheduledStatsAvailable
* @var int $scheduledEnabledJobsCount
* @var int $scheduledOverdueJobsCount
* @var int $scheduledRuns24hCount
* @var int $scheduledFailedRuns24hCount
* @var bool $schedulerRunnerIsActive
* @var string $schedulerRunnerLastHeartbeatAt
* @var string $schedulerRunnerLastResult
* @var string $schedulerRunnerLastErrorCode
* @var int $usersNeverLoggedCount
* @var int $usersUnverifiedCount
2026-02-04 23:31:53 +01:00
* @var int $mailLogSentCount
* @var int $mailLogFailedCount
2026-02-11 19:28:12 +01:00
* @var string $mailLogLastSentAt
* @var array $mailLogRecentFailed
2026-02-04 23:31:53 +01:00
*/
2026-03-04 15:56:58 +01:00
$pageAuth = is_array($viewAuth['page'] ?? null) ? $viewAuth['page'] : [];
$canViewMailLog = (bool) ($pageAuth['can_view_mail_log'] ?? false);
$canViewUsers = (bool) ($pageAuth['can_view_users'] ?? false);
$canViewTenants = (bool) ($pageAuth['can_view_tenants'] ?? false);
refactor(audit): extract audit domain into self-contained module
Move the entire audit subsystem (system audit, API audit, import audit,
user lifecycle audit, frontend telemetry) from core into modules/audit/.
Core decoupling via interface-based injection:
- AuditRecorderInterface replaces SystemAuditService in 10+ core services
- UserLifecycleAuditInterface / ImportAuditInterface for specialized flows
- NullAuditRecorder fallback when audit module is disabled
- ApiBootstrap/ApiResponse use null-safe callable resolvers
Module structure (modules/audit/):
- Manifest with routes, permissions, scheduler jobs, authorization policy
- 9 services, 8 repositories, 6 domain enums, 4 job handlers
- 33 page files, 4 JS files, 8 test files, migration scripts, i18n
Core cleanup:
- OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService
surgically cleaned of audit-specific constants
- Sidebar template cleared of hardcoded audit navigation
- AuditModuleIsolationContractTest ensures no future core→module coupling
All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5
clean, architecture contracts verified.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:12:49 +01:00
$canViewImportAudit = false;
$canViewSystemAudit = false;
$canViewUserLifecycleAudit = false;
2026-03-04 15:56:58 +01:00
$canViewAuditTab = (bool) ($auditStatsAvailable ?? false) & & (
($canViewSystemAudit & & (bool) ($systemAuditStatsAvailable ?? false))
|| ($canViewUserLifecycleAudit & & (bool) ($userLifecycleAuditStatsAvailable ?? false))
);
2026-02-11 19:28:12 +01:00
?>
2026-02-04 23:31:53 +01:00
< div class = "app-dashboard-titlebar" >
< h1 > <?php e ( t ( 'Statistics' )); ?> </ h1 >
< div class = "app-dashboard-titlebar-actions" >
<!-- Optional actions -->
< / div >
< / div >
< hr >
< div class = "app-dashboard" >
feat: extend module platform with UI slots, runtime components, CLI tooling and {{userId}} search support
Completes the generic module platform that enables modules to contribute
UI elements, runtime JS components, and search resources without any
core hardcoding.
New generic UI slot types:
- topbar.right_item: module-contributed topbar buttons
- layout.body_end_template: module-contributed dialog/overlay templates
- layout.head_style: module-contributed global CSS
- runtime.component: declarative JS component registration with phase ordering
New infrastructure:
- ModuleAutoloader: PSR-4 autoloading for module-local PHP classes
- ModuleRuntimePageBuilder: symlinks module pages into runtime directory
- ModuleRuntimeAssetPublisher: publishes module CSS/JS to web/modules/
- ModulePermissionSynchronizer: syncs module permissions to DB
- CLI scripts: module-runtime-sync, module-build, module-migrate,
module-permissions-sync, module-assets-sync
- {{userId}} placeholder in SearchDataService for user-scoped search queries
- Component runtime with phased initialization (early/default/late)
- AppContainer.protectExistingBindings() to prevent module→core overwrites
- Architecture tests: ModuleStructureContractTest, CoreTemplateIsolationTest,
FrontendComponentRuntimeContractTest, AppContainerIsolationContractTest
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:19:56 +01:00
< div class = "app-tabs" data-tabs data-app-component = "tabs" id = "stats-tabs" >
2026-02-04 23:31:53 +01:00
<!-- Tab Navigation -->
< div class = "app-tabs-nav" >
2026-02-11 19:28:12 +01:00
< button data-tab = "users" data-tab-default class = "transparent tab-button" >
<?php e ( t ( 'Users' )); ?>
< / button >
< button data-tab = "tenants" class = "transparent tab-button" >
<?php e ( t ( 'Tenants' )); ?>
< / button >
< button data-tab = "departments" class = "transparent tab-button" >
<?php e ( t ( 'Departments' )); ?>
2026-02-04 23:31:53 +01:00
< / button >
2026-02-11 19:28:12 +01:00
< button data-tab = "roles-permissions" class = "transparent tab-button" >
<?php e ( t ( 'Roles & permissions' )); ?>
< / button >
< button data-tab = "security" class = "transparent tab-button" >
<?php e ( t ( 'Security' )); ?>
2026-02-04 23:31:53 +01:00
< / button >
2026-03-04 15:56:58 +01:00
<?php if ( $canViewAuditTab ) : ?>
< button data-tab = "audit" class = "transparent tab-button" >
<?php e ( t ( 'Audit' )); ?>
< / button >
<?php endif ; ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
<?php if ( $ssoStatsAvailable ) : ?>
< button data-tab = "sso" class = "transparent tab-button" >
<?php e ( t ( 'SSO' )); ?>
< / button >
<?php endif ; ?>
<?php if ( $customFieldStatsAvailable ) : ?>
< button data-tab = "custom-fields" class = "transparent tab-button" >
<?php e ( t ( 'Custom fields' )); ?>
< / button >
<?php endif ; ?>
<?php if ( $apiStatsAvailable ) : ?>
< button data-tab = "api" class = "transparent tab-button" >
<?php e ( t ( 'API' )); ?>
< / button >
<?php endif ; ?>
<?php if ( $importStatsAvailable ) : ?>
< button data-tab = "imports" class = "transparent tab-button" >
<?php e ( t ( 'Imports' )); ?>
< / button >
<?php endif ; ?>
<?php if ( $scheduledStatsAvailable ) : ?>
< button data-tab = "scheduled-jobs" class = "transparent tab-button" >
<?php e ( t ( 'Scheduled jobs' )); ?>
< / button >
<?php endif ; ?>
2026-03-04 15:56:58 +01:00
<?php if ( $canViewMailLog ) : ?>
2026-02-11 19:28:12 +01:00
< button data-tab = "email-security" class = "transparent tab-button" >
<?php e ( t ( 'Email security' )); ?>
2026-02-04 23:31:53 +01:00
< / button >
<?php endif ; ?>
< / div >
2026-02-11 19:28:12 +01:00
<!-- Tab Panel: Users -->
< div data-tab-panel = "users" >
2026-02-04 23:31:53 +01:00
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/users?active=active',
'label' => t('Active users'),
'count' => (string) $activeUserCount,
'icon' => 'bi bi-person-check-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Active users'),
2026-02-04 23:31:53 +01:00
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/users?active=inactive',
'label' => t('Inactive users'),
'count' => (string) $inactiveUserCount,
'icon' => 'bi bi-person-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive users'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/users?login_status=never',
'label' => t('Never logged in'),
'count' => (string) $usersNeverLoggedCount,
'icon' => 'bi bi-person-slash',
2026-02-04 23:31:53 +01:00
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
2026-02-11 19:28:12 +01:00
'tooltip' => t('Never logged in'),
2026-02-04 23:31:53 +01:00
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/users?email_verified=unverified',
'label' => t('Email unverified'),
'count' => (string) $usersUnverifiedCount,
'icon' => 'bi bi-shield-exclamation',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Email unverified'),
2026-02-04 23:31:53 +01:00
]);
?>
< / div >
2026-02-11 19:28:12 +01:00
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Recent logins' )); ?> </ small >
< / div >
<?php if ( ! empty ( $recentLogins )) : ?>
2026-02-04 23:31:53 +01:00
< table >
< thead >
< tr >
2026-02-11 19:28:12 +01:00
< th > <?php e ( t ( 'User' )); ?> </ th >
< th > <?php e ( t ( 'Email' )); ?> </ th >
< th > <?php e ( t ( 'Last login' )); ?> </ th >
2026-02-04 23:31:53 +01:00
< / tr >
< / thead >
< tbody >
2026-02-11 19:28:12 +01:00
<?php foreach ( $recentLogins as $row ) : ?>
2026-02-04 23:31:53 +01:00
< tr >
< td >
2026-02-11 19:28:12 +01:00
<?php $name = trim (( $row [ 'first_name' ] ?? '' ) . ' ' . ( $row [ 'last_name' ] ?? '' )); ?>
<?php if ( $canViewUsers && ! empty ( $row [ 'uuid' ])) : ?>
< a href = "admin/users/edit/ <?php e ( $row [ 'uuid' ]); ?> " > <?php e ( $name ); ?> </ a >
2026-02-04 23:31:53 +01:00
<?php else : ?>
2026-02-11 19:28:12 +01:00
<?php e ( $name ); ?>
2026-02-04 23:31:53 +01:00
<?php endif ; ?>
< / td >
2026-02-11 19:28:12 +01:00
< td > <?php e ( $row [ 'email' ] ?? '' ); ?> </ td >
< td > <?php e ( dt ( $row [ 'last_login_at' ] ?? '' )); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-02-11 19:28:12 +01:00
<?php endif ; ?>
< / div >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Never logged in' )); ?> </ small >
< / div >
<?php if ( ! empty ( $neverLoggedUsers )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'User' )); ?> </ th >
< th > <?php e ( t ( 'Email' )); ?> </ th >
< th > <?php e ( t ( 'Created' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $neverLoggedUsers as $row ) : ?>
< tr >
2026-02-04 23:31:53 +01:00
< td >
2026-02-11 19:28:12 +01:00
<?php $name = trim (( $row [ 'first_name' ] ?? '' ) . ' ' . ( $row [ 'last_name' ] ?? '' )); ?>
<?php if ( $canViewUsers && ! empty ( $row [ 'uuid' ])) : ?>
< a href = "admin/users/edit/ <?php e ( $row [ 'uuid' ]); ?> " > <?php e ( $name ); ?> </ a >
<?php else : ?>
<?php e ( $name ); ?>
<?php endif ; ?>
2026-02-04 23:31:53 +01:00
< / td >
2026-02-11 19:28:12 +01:00
< td > <?php e ( $row [ 'email' ] ?? '' ); ?> </ td >
< td > <?php e ( dt ( $row [ 'created' ] ?? '' )); ?> </ td >
2026-02-04 23:31:53 +01:00
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
2026-02-11 19:28:12 +01:00
<?php else : ?>
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-02-11 19:28:12 +01:00
<?php endif ; ?>
< / div >
2026-02-04 23:31:53 +01:00
< / div >
2026-02-11 19:28:12 +01:00
<!-- Tab Panel: Tenants -->
< div data-tab-panel = "tenants" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/tenants',
'label' => t('Active tenants'),
'count' => (string) $activeTenantCount,
'icon' => 'bi bi-buildings',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Active tenants'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/tenants',
'label' => t('Inactive tenants'),
'count' => (string) $inactiveTenantCount,
'icon' => 'bi bi-building-x',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive tenants'),
]);
?>
2026-02-04 23:31:53 +01:00
< / div >
< / div >
2026-02-11 19:28:12 +01:00
<!-- Tab Panel: Departments -->
< div data-tab-panel = "departments" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/departments?active=active',
'label' => t('Active departments'),
'count' => (string) $activeDepartmentCount,
'icon' => 'bi bi-diagram-3-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Active departments'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/departments?active=inactive',
'label' => t('Inactive departments'),
'count' => (string) $inactiveDepartmentCount,
'icon' => 'bi bi-diagram-3',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive departments'),
]);
?>
< / div >
< / div >
<!-- Tab Panel: Roles & permissions -->
< div data-tab-panel = "roles-permissions" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/roles?active=active',
'label' => t('Active roles'),
'count' => (string) $activeRoleCount,
'icon' => 'bi bi-shield-check',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Active roles'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/roles?active=inactive',
'label' => t('Inactive roles'),
'count' => (string) $inactiveRoleCount,
'icon' => 'bi bi-shield-x',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive roles'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/roles',
'label' => t('Roles without permissions'),
'count' => (string) $rolesWithoutPermissionsCount,
'icon' => 'bi bi-shield-exclamation',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Roles without permissions'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/permissions',
'label' => t('Permissions without roles'),
'count' => (string) $permissionsWithoutRolesCount,
'icon' => 'bi bi-key-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Permissions without roles'),
]);
?>
< / div >
< / div >
<!-- Tab Panel: Security -->
< div data-tab-panel = "security" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/users?active=inactive',
'label' => t('Inactive users'),
'count' => (string) $inactiveUserCount,
'icon' => 'bi bi-person-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive users'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/users',
'label' => t('Users without roles'),
'count' => (string) $usersWithoutRolesCount,
'icon' => 'bi bi-person-dash',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Users without roles'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/users',
'label' => t('Users without tenants'),
'count' => (string) $usersWithoutTenantsCount,
'icon' => 'bi bi-person-slash',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Users without tenants'),
]);
?>
< / div >
< / div >
2026-03-04 15:56:58 +01:00
<?php if ( $canViewAuditTab ) : ?>
<?php
$systemAuditRisk24hCount = (int) $systemAuditFailed24hCount + (int) $systemAuditDenied24hCount;
$lifecycleRisk7dCount = (int) $lifecycleFailed7dCount + (int) $lifecycleSkipped7dCount;
?>
< div data-tab-panel = "audit" >
< div class = "app-tiles" >
<?php if ( $canViewSystemAudit && $systemAuditStatsAvailable ) : ?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-03-04 15:56:58 +01:00
'href' => $systemAuditAll24hHref,
'label' => t('System audit events (24h)'),
'count' => (string) $systemAuditEvents24hCount,
'icon' => 'bi bi-journal-text',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('System audit events (24h)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-03-04 15:56:58 +01:00
'href' => $systemAuditFailed24hHref,
'label' => t('System audit risks (24h)'),
'count' => (string) $systemAuditRisk24hCount,
'icon' => 'bi bi-exclamation-triangle-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('System audit risks (24h)'),
]);
?>
<?php endif ; ?>
<?php if ( $canViewUserLifecycleAudit && $userLifecycleAuditStatsAvailable ) : ?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-03-04 15:56:58 +01:00
'href' => $lifecycleAll7dHref,
'label' => t('Lifecycle runs (7d)'),
'count' => (string) $lifecycleRuns7dCount,
'icon' => 'bi bi-person-gear',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Lifecycle runs (7d)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-03-04 15:56:58 +01:00
'href' => $lifecycleFailed7dHref,
'label' => t('Lifecycle risks (7d)'),
'count' => (string) $lifecycleRisk7dCount,
'icon' => 'bi bi-exclamation-octagon-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Lifecycle risks (7d)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-03-04 15:56:58 +01:00
'href' => $lifecycleRestore7dHref,
'label' => t('Restore actions (7d)'),
'count' => (string) $lifecycleRestore7dCount,
'icon' => 'bi bi-arrow-counterclockwise',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Restore actions (7d)'),
]);
?>
<?php endif ; ?>
< / div >
< div class = "grid" >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Recent system audit risks (24h)' )); ?> </ small >
< / div >
<?php if ( $canViewSystemAudit && $systemAuditStatsAvailable ) : ?>
<?php if ( ! empty ( $systemAuditRecentRiskRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Event type' )); ?> </ th >
< th > <?php e ( t ( 'Status' )); ?> </ th >
< th > <?php e ( t ( 'Channel' )); ?> </ th >
< th > <?php e ( t ( 'Created' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $systemAuditRecentRiskRows as $row ) : ?>
<?php $auditId = ( int ) ( $row [ 'id' ] ?? 0 ); ?>
< tr >
< td >
<?php if ( $auditId > 0 ) : ?>
< a href = "admin/system-audit/view/ <?php e (( string ) $auditId ); ?> " > <?php e (( string ) ( $row [ 'event_type' ] ?? '' )); ?> </ a >
<?php else : ?>
<?php e (( string ) ( $row [ 'event_type' ] ?? '' )); ?>
<?php endif ; ?>
< / td >
< td > <?php e (( string ) ( $row [ 'outcome' ] ?? '' )); ?> </ td >
< td > <?php e (( string ) ( $row [ 'channel' ] ?? '' )); ?> </ td >
< td > <?php e ( dt (( string ) ( $row [ 'created_at' ] ?? '' ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-03-04 15:56:58 +01:00
< / div >
<?php endif ; ?>
<?php elseif ( $canViewUserLifecycleAudit && $userLifecycleAuditStatsAvailable ) : ?>
<?php if ( ! empty ( $lifecycleTopReasonRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Reason code' )); ?> </ th >
< th > <?php e ( t ( 'Count' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $lifecycleTopReasonRows as $row ) : ?>
< tr >
< td > <?php e (( string ) ( $row [ 'reason_code' ] ?? '-' )); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'hit_count' ] ?? 0 ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-03-04 15:56:58 +01:00
< / div >
<?php endif ; ?>
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-03-04 15:56:58 +01:00
< / div >
<?php endif ; ?>
< / div >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Recent lifecycle risks (7d)' )); ?> </ small >
< / div >
<?php if ( $canViewUserLifecycleAudit && $userLifecycleAuditStatsAvailable ) : ?>
<?php if ( ! empty ( $lifecycleRecentRiskRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Action' )); ?> </ th >
< th > <?php e ( t ( 'Status' )); ?> </ th >
< th > <?php e ( t ( 'Trigger' )); ?> </ th >
< th > <?php e ( t ( 'Target' )); ?> </ th >
< th > <?php e ( t ( 'Created' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $lifecycleRecentRiskRows as $row ) : ?>
<?php
$lifecycleId = (int) ($row['id'] ?? 0);
$targetLabel = trim((string) ($row['target_user_uuid'] ?? ''));
if ($targetLabel === '') {
$targetLabel = trim((string) ($row['target_user_email'] ?? ''));
}
if ($targetLabel === '') {
$targetLabel = '-';
}
?>
< tr >
< td >
<?php if ( $lifecycleId > 0 ) : ?>
2026-04-26 18:03:42 +02:00
< a href = "admin/settings/user-lifecycle#lifecycle-audit/ <?php e (( string ) $lifecycleId ); ?> " > <?php e (( string ) ( $row [ 'action' ] ?? '' )); ?> </ a >
2026-03-04 15:56:58 +01:00
<?php else : ?>
<?php e (( string ) ( $row [ 'action' ] ?? '' )); ?>
<?php endif ; ?>
< / td >
< td > <?php e (( string ) ( $row [ 'status' ] ?? '' )); ?> </ td >
< td > <?php e (( string ) ( $row [ 'trigger_type' ] ?? '' )); ?> </ td >
< td > <?php e ( $targetLabel ); ?> </ td >
< td > <?php e ( dt (( string ) ( $row [ 'created_at' ] ?? '' ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-03-04 15:56:58 +01:00
< / div >
<?php endif ; ?>
<?php elseif ( $canViewSystemAudit && $systemAuditStatsAvailable ) : ?>
<?php if ( ! empty ( $systemAuditTopEventTypeRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Event type' )); ?> </ th >
< th > <?php e ( t ( 'Count' )); ?> </ th >
< th > <?php e ( t ( 'Failed or denied' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $systemAuditTopEventTypeRows as $row ) : ?>
< tr >
< td > <?php e (( string ) ( $row [ 'event_type' ] ?? '' )); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'hit_count' ] ?? 0 ))); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'failed_or_denied_count' ] ?? 0 ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-03-04 15:56:58 +01:00
< / div >
<?php endif ; ?>
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
2026-03-04 15:56:58 +01:00
< / div >
<?php endif ; ?>
< / div >
< / div >
< / div >
<?php endif ; ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
<?php if ( $ssoStatsAvailable ) : ?>
< div data-tab-panel = "sso" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/tenants',
'label' => t('SSO-enabled tenants'),
'count' => (string) $ssoEnabledTenantCount,
'icon' => 'bi bi-microsoft',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('SSO-enabled tenants'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/tenants',
'label' => t('SSO-enforced tenants'),
'count' => (string) $ssoEnforcedTenantCount,
'icon' => 'bi bi-shield-lock-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('SSO-enforced tenants'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/tenants',
'label' => t('SSO rollout'),
'count' => number_format($ssoEnabledTenantPercent, 1, '.', '') . '%',
'icon' => 'bi bi-speedometer2',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('SSO rollout'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/users',
'label' => t('Users with Microsoft-only login'),
'count' => (string) $usersMicrosoftOnlyCount,
'icon' => 'bi bi-person-lock',
'iconBg' => '#f2f2f2',
'iconColor' => '#555555',
'tooltip' => t('Users with Microsoft-only login'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/users',
'label' => t('Microsoft profile sync gaps'),
'count' => (string) $usersMicrosoftSyncGapsCount,
'icon' => 'bi bi-person-exclamation',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Microsoft profile sync gaps'),
]);
?>
< / div >
< / div >
<?php endif ; ?>
<?php if ( $customFieldStatsAvailable ) : ?>
< div data-tab-panel = "custom-fields" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => $customFieldsSelectionWithoutOptionsHref,
'label' => t('Selection fields without options'),
'count' => (string) $customFieldsSelectionWithoutOptionsCount,
'icon' => 'bi bi-list-check',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Selection fields without options'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => $customFieldTenantsWithSelectionWithoutOptionsHref,
'label' => t('Tenants with broken selection fields'),
'count' => (string) $customFieldTenantsWithSelectionWithoutOptionsCount,
'icon' => 'bi bi-exclamation-triangle',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Tenants with broken selection fields'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => $customFieldsInactiveWithValuesHref,
'label' => t('Inactive custom fields with values'),
'count' => (string) $customFieldsInactiveWithValuesCount,
'icon' => 'bi bi-archive-fill',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Inactive custom fields with values'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => $customFieldsUnusedActiveHref,
'label' => t('Unused active custom fields'),
'count' => (string) $customFieldsUnusedActiveCount,
'icon' => 'bi bi-slash-circle',
'iconBg' => '#f2f2f2',
'iconColor' => '#555555',
'tooltip' => t('Unused active custom fields'),
]);
?>
< / div >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Affected tenants' )); ?> </ small >
< / div >
<?php if ( ! empty ( $customFieldIssueTenantRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Issue' )); ?> </ th >
< th > <?php e ( t ( 'Tenant' )); ?> </ th >
< th > <?php e ( t ( 'Count' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $customFieldIssueTenantRows as $row ) : ?>
< tr >
< td > <?php e ( $row [ 'issue' ] ?? '' ); ?> </ td >
< td >
<?php $tenantUuid = ( string ) ( $row [ 'tenant_uuid' ] ?? '' ); ?>
<?php $tenantDescription = ( string ) ( $row [ 'tenant_description' ] ?? '' ); ?>
<?php if ( $canViewTenants && $tenantUuid !== '' ) : ?>
< a href = "admin/tenants/edit/ <?php e ( $tenantUuid ); ?> ?tab=custom_fields" > <?php e ( $tenantDescription ); ?> </ a >
<?php else : ?>
<?php e ( $tenantDescription ); ?>
<?php endif ; ?>
< / td >
< td > <?php e (( string ) (( int ) ( $row [ 'issue_count' ] ?? 0 ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
< / div >
<?php endif ; ?>
< div class = "app-stats-table-explanation" >
< small class = "muted" >
<?php e ( t ( 'If a tenant appears here, at least one custom field setup needs review (missing options, inactive fields with values, or unused active fields).' )); ?>
< / small >
< / div >
< / div >
< / div >
<?php endif ; ?>
<?php if ( $apiStatsAvailable ) : ?>
< div data-tab-panel = "api" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/api-audit',
'label' => t('API requests (24h)'),
'count' => (string) $apiRequests24hCount,
'icon' => 'bi bi-bar-chart-line-fill',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('API requests (24h)'),
]);
?>
<?php
$trendLabel = $apiRequestsPrev24hCount > 0
? (($apiRequestTrendPercent > 0 ? '+' : '') . number_format($apiRequestTrendPercent, 1, '.', '') . '%')
: 'n/a';
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/api-audit',
'label' => t('Request trend vs previous 24h'),
'count' => $trendLabel,
'icon' => 'bi bi-graph-up-arrow',
'iconBg' => '#f2f2f2',
'iconColor' => '#555555',
'tooltip' => t('Request trend vs previous 24h'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/api-audit',
'label' => t('4xx errors (24h)'),
'count' => (string) $apiErrors4xx24hCount,
'icon' => 'bi bi-shield-exclamation',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('4xx errors (24h)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/api-audit',
'label' => t('5xx errors (24h)'),
'count' => (string) $apiErrors5xx24hCount,
'icon' => 'bi bi-bug-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('5xx errors (24h)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/api-audit',
'label' => t('P95 response time (ms)'),
'count' => (string) $apiP95DurationMs,
'icon' => 'bi bi-stopwatch-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('P95 response time (ms)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/settings?tab=api',
'label' => t('Tokens expiring in 7 days'),
'count' => (string) $apiTokensExpiring7dCount,
'icon' => 'bi bi-key-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Tokens expiring in 7 days'),
]);
?>
< / div >
< div class = "grid" >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Top API error codes (24h)' )); ?> </ small >
< / div >
<?php if ( ! empty ( $apiTopErrorRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Error code' )); ?> </ th >
< th > <?php e ( t ( 'Count' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $apiTopErrorRows as $row ) : ?>
< tr >
< td > <?php e (( string ) ( $row [ 'error_label' ] ?? '' )); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'hit_count' ] ?? 0 ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
< / div >
<?php endif ; ?>
< / div >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Slowest API endpoints (avg, 24h)' )); ?> </ small >
< / div >
<?php if ( ! empty ( $apiSlowEndpointRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Path' )); ?> </ th >
< th > <?php e ( t ( 'Requests' )); ?> </ th >
< th > <?php e ( t ( 'Average duration (ms)' )); ?> </ th >
< th > <?php e ( t ( 'Maximum duration (ms)' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $apiSlowEndpointRows as $row ) : ?>
< tr >
< td > <?php e (( string ) ( $row [ 'path' ] ?? '' )); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'hit_count' ] ?? 0 ))); ?> </ td >
< td > <?php e ( number_format (( float ) ( $row [ 'avg_duration_ms' ] ?? 0 ), 1 , '.' , '' )); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'max_duration_ms' ] ?? 0 ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
< / div >
<?php endif ; ?>
< / div >
< / div >
< / div >
<?php endif ; ?>
<?php if ( $importStatsAvailable ) : ?>
< div data-tab-panel = "imports" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/import-audit',
'label' => t('Import runs (7d)'),
'count' => (string) $importRuns7dCount,
'icon' => 'bi bi-box-arrow-in-down',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Import runs (7d)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/import-audit',
'label' => t('Created rows (7d)'),
'count' => (string) $importRowsCreated7dCount,
'icon' => 'bi bi-person-plus-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Created rows (7d)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/import-audit',
'label' => t('Failed rows (7d)'),
'count' => (string) $importRowsFailed7dCount,
'icon' => 'bi bi-exclamation-octagon-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Failed rows (7d)'),
]);
?>
<?php
$importSuccessLabel = $importRuns7dCount > 0
? number_format($importSuccessRate7dPercent, 1, '.', '') . '%'
: 'n/a';
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/import-audit',
'label' => t('Success rate (7d)'),
'count' => $importSuccessLabel,
'icon' => 'bi bi-check2-circle',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Success rate (7d)'),
]);
?>
< / div >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Recent import runs' )); ?> </ small >
< / div >
<?php if ( ! empty ( $importRecentRunRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Created' )); ?> </ th >
< th > <?php e ( t ( 'Profile' )); ?> </ th >
< th > <?php e ( t ( 'Status' )); ?> </ th >
< th > <?php e ( t ( 'Rows total' )); ?> </ th >
< th > <?php e ( t ( 'Created count' )); ?> </ th >
< th > <?php e ( t ( 'Failed count' )); ?> </ th >
< th > <?php e ( t ( 'Duration (ms)' )); ?> </ th >
< th > <?php e ( t ( 'User' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $importRecentRunRows as $row ) : ?>
<?php
$profileKey = strtolower(trim((string) ($row['profile_key'] ?? '')));
$profileLabel = $profileKey === 'users'
? t('Users')
: ($profileKey === 'departments' ? t('Departments') : $profileKey);
?>
< tr >
< td >
<?php if ( $canViewImportAudit ) : ?>
< a href = "admin/import-audit/view/ <?php e (( string ) ( $row [ 'id' ] ?? '' )); ?> " >
<?php e ( dt (( string ) ( $row [ 'started_at' ] ?? '' ))); ?>
< / a >
<?php else : ?>
<?php e ( dt (( string ) ( $row [ 'started_at' ] ?? '' ))); ?>
<?php endif ; ?>
< / td >
< td > <?php e ( $profileLabel ); ?> </ td >
< td > <?php e (( string ) ( $row [ 'status' ] ?? '' )); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'rows_total' ] ?? 0 ))); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'created_count' ] ?? 0 ))); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'failed_count' ] ?? 0 ))); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'duration_ms' ] ?? 0 ))); ?> </ td >
< td >
<?php
$userLabel = trim((string) ($row['user_display_name'] ?? ''));
if ($userLabel === '') {
$userLabel = trim((string) ($row['user_email'] ?? ''));
}
if ($userLabel === '') {
$userLabel = '-';
}
?>
<?php e ( $userLabel ); ?>
< / td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
< / div >
<?php endif ; ?>
< / div >
< div class = "grid" >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Imports by profile (7d)' )); ?> </ small >
< / div >
<?php if ( ! empty ( $importProfileRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Profile' )); ?> </ th >
< th > <?php e ( t ( 'Runs' )); ?> </ th >
< th > <?php e ( t ( 'Created rows' )); ?> </ th >
< th > <?php e ( t ( 'Failed rows' )); ?> </ th >
< th > <?php e ( t ( 'Success rate' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $importProfileRows as $row ) : ?>
<?php
$profileKey = strtolower(trim((string) ($row['profile_key'] ?? '')));
$profileLabel = $profileKey === 'users'
? t('Users')
: ($profileKey === 'departments' ? t('Departments') : $profileKey);
?>
< tr >
< td > <?php e ( $profileLabel ); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'run_count' ] ?? 0 ))); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'created_count_sum' ] ?? 0 ))); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'failed_count_sum' ] ?? 0 ))); ?> </ td >
< td > <?php e ( number_format (( float ) ( $row [ 'success_rate' ] ?? 0 ), 1 , '.' , '' ) . '%' ); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
< / div >
<?php endif ; ?>
< / div >
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Top import error codes (7d)' )); ?> </ small >
< / div >
<?php if ( ! empty ( $importTopErrorCodeRows )) : ?>
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Error code' )); ?> </ th >
< th > <?php e ( t ( 'Count' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $importTopErrorCodeRows as $row ) : ?>
< tr >
< td > <?php e (( string ) ( $row [ 'error_code' ] ?? '' )); ?> </ td >
< td > <?php e (( string ) (( int ) ( $row [ 'count' ] ?? 0 ))); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
<?php else : ?>
< div class = "app-stats-table-empty" >
2026-03-11 21:38:15 +01:00
<?php $emptyState = [ 'message' => t ( 'No entries found' ), 'size' => 'compact' ]; require templatePath ( 'partials/app-empty-state.phtml' ); ?>
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
< / div >
<?php endif ; ?>
< / div >
< / div >
< / div >
<?php endif ; ?>
<?php if ( $scheduledStatsAvailable ) : ?>
< div data-tab-panel = "scheduled-jobs" >
<?php
$runnerStatusLabel = $schedulerRunnerIsActive ? t('Active') : t('Inactive');
$runnerTooltipParts = [];
if ($schedulerRunnerLastHeartbeatAt !== '') {
$runnerTooltipParts[] = t('Last heartbeat') . ': ' . dt($schedulerRunnerLastHeartbeatAt);
}
if ($schedulerRunnerLastResult !== '') {
$runnerResultLabel = $schedulerRunnerLastResult;
if ($runnerResultLabel === 'ok') {
$runnerResultLabel = t('Success');
} elseif ($runnerResultLabel === 'lock_not_acquired') {
$runnerResultLabel = t('Runner lock not acquired');
} elseif ($runnerResultLabel === 'unexpected_error') {
$runnerResultLabel = t('Failed');
}
if ($schedulerRunnerLastErrorCode !== '') {
$runnerResultLabel .= ' (' . $schedulerRunnerLastErrorCode . ')';
}
$runnerTooltipParts[] = t('Last runner result') . ': ' . $runnerResultLabel;
}
$runnerTooltip = implode(' | ', $runnerTooltipParts);
?>
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/scheduled-jobs',
'label' => t('Enabled jobs'),
'count' => (string) $scheduledEnabledJobsCount,
'icon' => 'bi bi-check2-square',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Enabled jobs'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/scheduled-jobs',
'label' => t('Overdue jobs'),
'count' => (string) $scheduledOverdueJobsCount,
'icon' => 'bi bi-exclamation-triangle-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Overdue jobs'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/scheduled-jobs',
'label' => t('Scheduler runs (24h)'),
'count' => (string) $scheduledRuns24hCount,
'icon' => 'bi bi-clock-history',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Scheduler runs (24h)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/scheduled-jobs',
'label' => t('Failed scheduler runs (24h)'),
'count' => (string) $scheduledFailedRuns24hCount,
'icon' => 'bi bi-x-octagon-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Failed scheduler runs (24h)'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
'href' => 'admin/scheduled-jobs',
'label' => t('Cron runner active'),
'count' => $runnerStatusLabel,
'icon' => $schedulerRunnerIsActive ? 'bi bi-heart-pulse-fill' : 'bi bi-heartbreak-fill',
'iconBg' => $schedulerRunnerIsActive ? '#d9f2e6' : '#ffe9e9',
'iconColor' => $schedulerRunnerIsActive ? '#1f6a3a' : '#a32e2e',
'tooltip' => $runnerTooltip !== '' ? $runnerTooltip : t('Cron runner active'),
]);
?>
< / div >
< / div >
<?php endif ; ?>
2026-02-11 19:28:12 +01:00
<!-- Tab Panel: Email security -->
2026-03-04 15:56:58 +01:00
<?php if ( $canViewMailLog ) : ?>
2026-02-11 19:28:12 +01:00
< div data-tab-panel = "email-security" >
< div class = "app-tiles" >
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/mail-log?status=failed',
'label' => t('Mail failures'),
'count' => (string) $mailLogFailedCount,
'icon' => 'bi bi-envelope-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Mail failures'),
]);
?>
<?php
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/mail-log?status=sent',
'label' => t('Sent emails'),
'count' => (string) $mailLogSentCount,
'icon' => 'bi bi-envelope-check-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Sent emails'),
]);
?>
<?php
$lastSentLabel = $mailLogLastSentAt ? dt($mailLogLastSentAt) : t('Never');
2026-04-24 16:23:05 +02:00
appTile([
2026-02-11 19:28:12 +01:00
'href' => 'admin/mail-log?status=sent',
'label' => t('Last email sent'),
'count' => $lastSentLabel,
'icon' => 'bi bi-clock-history',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Last email sent'),
]);
?>
< / div >
<?php if ( ! empty ( $mailLogRecentFailed )) : ?>
< div class = "app-stats-table" >
< div class = "app-stats-table-header" >
< small > <?php e ( t ( 'Mail failures' )); ?> </ small >
2026-02-04 23:31:53 +01:00
< / div >
2026-02-11 19:28:12 +01:00
< table >
< thead >
< tr >
< th > <?php e ( t ( 'Recipient' )); ?> </ th >
< th > <?php e ( t ( 'Subject' )); ?> </ th >
< th > <?php e ( t ( 'Error' )); ?> </ th >
< th > <?php e ( t ( 'Created' )); ?> </ th >
< / tr >
< / thead >
< tbody >
<?php foreach ( $mailLogRecentFailed as $row ) : ?>
< tr >
< td > <?php e ( $row [ 'to_email' ] ?? '' ); ?> </ td >
< td > <?php e ( $row [ 'subject' ] ?? '' ); ?> </ td >
< td > <?php e ( $row [ 'error_message' ] ?? '' ); ?> </ td >
< td > <?php e ( dt ( $row [ 'created_at' ] ?? '' )); ?> </ td >
< / tr >
<?php endforeach ; ?>
< / tbody >
< / table >
2026-02-04 23:31:53 +01:00
< / div >
2026-02-11 19:28:12 +01:00
<?php endif ; ?>
2026-02-04 23:31:53 +01:00
< / div >
<?php endif ; ?>
< / div >
2026-02-11 19:28:12 +01:00
< / div >