prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); $stmtExisting->execute([$userId]); $existing = $stmtExisting->fetch(); $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; if (($clientSecret === '' || $clientSecret === '••••••••••••••••') && !empty($existingSettings['client_secret']) && $existingSettings['client_secret'] !== '••••••••••••••••') { $clientSecret = $existingSettings['client_secret']; } $newSettings = array_merge($existingSettings, [ 'client_id' => $clientId, 'client_secret' => $clientSecret, 'tenant_id' => $tenantId, ]); $stmt = $db->prepare(" INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at) VALUES (?, 'OUTLOOK', 1, ?, ?, NOW()) ON DUPLICATE KEY UPDATE is_demo_mode = VALUES(is_demo_mode), settings = VALUES(settings), last_sync_at = NOW() "); $stmt->execute([$userId, $isDemo, json_encode($newSettings)]); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'integration', null, ['provider' => 'OUTLOOK', 'tenant_id' => $tenantId], 'SUCCESS', $adminUser['email']); $message = "Centrale Microsoft 365 instellingen (Agenda, E-mail & Teams) zijn succesvol opgeslagen."; $messageType = 'success'; } // 2. Centraal Jira Bedrijfsdomein opslaan elseif ($action === 'save_jira_domain') { $rawDomain = trim($_POST['jira_domain'] ?? ''); $companyDomain = !empty($rawDomain) ? jira_normalize_domain($rawDomain) : ''; $stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA'"); $stmtExisting->execute([$userId]); $existing = $stmtExisting->fetch(); $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; $existingSettings['jira_domain'] = $companyDomain; $stmt = $db->prepare(" INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at) VALUES (?, 'JIRA', 1, 0, ?, NOW()) ON DUPLICATE KEY UPDATE settings = VALUES(settings), last_sync_at = NOW() "); $stmt->execute([$userId, json_encode($existingSettings)]); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'integration', null, ['provider' => 'JIRA', 'domain' => $companyDomain], 'SUCCESS', $adminUser['email']); $message = "Standaard Atlassian Jira bedrijfsdomein is succesvol opgeslagen."; $messageType = 'success'; } // 2b. Jira Wachtrijfilter toevoegen of bewerken elseif ($action === 'save_jira_queue_filter') { $filterId = (int)($_POST['filter_id'] ?? 0); $filterCode = strtoupper(trim($_POST['filter_code'] ?? '')); $filterName = trim($_POST['filter_name'] ?? ''); $projectKey = strtoupper(trim($_POST['project_key'] ?? '')); $jqlClause = trim($_POST['jql_clause'] ?? ''); $description = trim($_POST['description'] ?? ''); $isActive = isset($_POST['is_active']) ? 1 : 0; $sortOrder = (int)($_POST['sort_order'] ?? 0); if (empty($filterCode) || empty($filterName) || empty($jqlClause)) { $message = "Filtercode, naam en JQL-clausule zijn verplicht."; $messageType = 'error'; } else { if ($filterId > 0) { $stmt = $db->prepare(" UPDATE jira_queue_filters SET filter_code = ?, filter_name = ?, project_key = ?, jql_clause = ?, description = ?, is_active = ?, sort_order = ?, updated_at = NOW() WHERE id = ? "); $stmt->execute([$filterCode, $filterName, $projectKey ?: null, $jqlClause, $description, $isActive, $sortOrder, $filterId]); $message = "Wachtrijfilter '{$filterName}' is succesvol bijgewerkt."; } else { $stmt = $db->prepare(" INSERT INTO jira_queue_filters (filter_code, filter_name, project_key, jql_clause, description, is_active, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE filter_name = VALUES(filter_name), project_key = VALUES(project_key), jql_clause = VALUES(jql_clause), description = VALUES(description), is_active = VALUES(is_active), sort_order = VALUES(sort_order), updated_at = NOW() "); $stmt->execute([$filterCode, $filterName, $projectKey ?: null, $jqlClause, $description, $isActive, $sortOrder]); $message = "Nieuw wachtrijfilter '{$filterName}' is succesvol toegevoegd."; } fociz_log_audit($userId, 'SETTINGS_UPDATED', 'jira_queue_filter', $filterId ?: null, ['filter_code' => $filterCode, 'filter_name' => $filterName], 'SUCCESS', $adminUser['email']); $messageType = 'success'; } } // 2c. Jira Wachtrijfilter verwijderen elseif ($action === 'delete_jira_queue_filter') { $filterId = (int)($_POST['filter_id'] ?? 0); if ($filterId > 0) { $stmt = $db->prepare("DELETE FROM jira_queue_filters WHERE id = ?"); $stmt->execute([$filterId]); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'jira_queue_filter', $filterId, ['action' => 'delete'], 'SUCCESS', $adminUser['email']); $message = "Wachtrijfilter is succesvol verwijderd."; $messageType = 'success'; } } // 2d. Teamleden toewijzen aan Jira Wachtrijfilter elseif ($action === 'save_user_jira_assignments') { $assignments = $_POST['user_filter'] ?? []; if (is_array($assignments)) { $upd = $db->prepare("UPDATE users SET default_jira_filter_code = ? WHERE id = ?"); foreach ($assignments as $uId => $fCode) { $fCode = trim($fCode); $upd->execute([$fCode !== '' ? $fCode : null, (int)$uId]); } fociz_log_audit($userId, 'SETTINGS_UPDATED', 'jira_assignments', null, [], 'SUCCESS', $adminUser['email']); $message = "Standaard Jira wachtrijtoewijzingen voor het team zijn succesvol opgeslagen."; $messageType = 'success'; } } // 3. Organisatie Werkdag Standaarden opslaan (Release v1.4) elseif ($action === 'save_workday_settings') { $workdayStart = trim($_POST['workday_start'] ?? '08:30'); $workdayEnd = trim($_POST['workday_end'] ?? '17:00'); $lunchStart = trim($_POST['lunch_start'] ?? '12:30'); $lunchEnd = trim($_POST['lunch_end'] ?? '13:00'); $pomoFocus = max(10, min(120, (int)($_POST['pomodoro_focus'] ?? 25))); $pomoDeep = max(20, min(180, (int)($_POST['pomodoro_deep'] ?? 50))); $pomoBreak = max(2, min(30, (int)($_POST['pomodoro_break'] ?? 5))); $taskDuration = in_array((int)($_POST['default_task_duration'] ?? 30), [15, 30, 45, 60, 90, 120]) ? (int)$_POST['default_task_duration'] : 30; set_system_setting('workday_start', $workdayStart); set_system_setting('workday_end', $workdayEnd); set_system_setting('lunch_start', $lunchStart); set_system_setting('lunch_end', $lunchEnd); set_system_setting('pomodoro_focus', $pomoFocus); set_system_setting('pomodoro_deep', $pomoDeep); set_system_setting('pomodoro_break', $pomoBreak); set_system_setting('default_task_duration', $taskDuration); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'workday_settings', null, ['workday_start' => $workdayStart, 'workday_end' => $workdayEnd], 'SUCCESS', $adminUser['email']); $message = "Organisatie werkdagstandaarden zijn succesvol bijgewerkt."; $messageType = 'success'; } // 4. AI Co-Pilot instellingen opslaan (Release v1.5) elseif ($action === 'save_ai_settings') { $aiEnabled = isset($_POST['ai_enabled']) ? 1 : 0; $aiProvider = trim($_POST['ai_provider'] ?? 'openai'); $aiModel = trim($_POST['ai_model'] ?? 'gpt-4o-mini'); $apiKeyIn = trim($_POST['ai_api_key'] ?? ''); $currentAi = get_ai_settings(); if ($apiKeyIn !== '' && $apiKeyIn !== '••••••••••••••••') { set_system_setting('ai_api_key', fociz_encrypt($apiKeyIn)); } set_system_setting('ai_enabled', $aiEnabled); set_system_setting('ai_provider', $aiProvider); set_system_setting('ai_model', $aiModel); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'ai_settings', null, ['ai_enabled' => $aiEnabled, 'ai_provider' => $aiProvider, 'ai_model' => $aiModel], 'SUCCESS', $adminUser['email']); $message = "AI Co-Pilot instellingen zijn succesvol opgeslagen."; $messageType = 'success'; } // 5. Test e-mail versturen via SMTP (Release v1.6) elseif ($action === 'send_test_email') { require_once __DIR__ . '/includes/mailer.php'; $testEmail = trim($_POST['test_email'] ?? ''); if (empty($testEmail) || !filter_var($testEmail, FILTER_VALIDATE_EMAIL)) { $message = "Vul een geldig e-mailadres in om een test e-mail te sturen."; $messageType = 'error'; } else { $mailResult = FocizMailer::sendTestEmail($testEmail, $currentUser['name']); if ($mailResult['success']) { $message = "Test e-mail succesvol verzonden via mail.fociz.nl naar " . htmlspecialchars($testEmail) . "!"; $messageType = 'success'; } else { $message = "Fout bij verzenden test e-mail via mail.fociz.nl: " . htmlspecialchars($mailResult['error'] ?? 'Onbekende fout'); $messageType = 'error'; } } } // 6. Sessie- & Beveiligingsinstellingen opslaan (ISO 27001 / NIST SP 800-63B) elseif ($action === 'save_security_settings') { $timeout = (int)($_POST['session_idle_timeout'] ?? 1800); if (!in_array($timeout, [900, 1800, 3600, 7200], true)) { $timeout = 1800; } set_system_setting('session_idle_timeout', $timeout); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'security_settings', null, ['session_idle_timeout' => $timeout], 'SUCCESS', $adminUser['email']); $message = "Sessie- en beveiligingsinstellingen zijn succesvol opgeslagen."; $messageType = 'success'; } } } // Haal actuele configuratie op $appConfig = microsoft_get_app_config(); $companyJiraDomain = jira_get_company_domain(); $workdaySettings = get_workday_settings(); $aiSettings = get_ai_settings(); $sessionIdleTimeout = get_session_idle_timeout(); // Redirect URI $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php'; // Admin Consent URL $tenantForConsent = !empty($appConfig['tenant_id']) ? $appConfig['tenant_id'] : 'common'; $adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($tenantForConsent) . "/adminconsent?client_id=" . urlencode($appConfig['client_id']) . "&redirect_uri=" . urlencode($redirectUri); // Statistieken ophalen voor Systeem & Team Monitor $totalUsers = 0; $totalAdmins = 0; $totalMembers = 0; $pendingInvites = 0; $outlookConnectedCount = 0; $jiraConnectedCount = 0; $recentUsers = []; try { $totalUsers = (int)$db->query("SELECT COUNT(*) FROM users")->fetchColumn(); $totalAdmins = (int)$db->query("SELECT COUNT(*) FROM users WHERE role = 'ADMIN'")->fetchColumn(); $totalMembers = $totalUsers - $totalAdmins; } catch (Throwable $e) { error_log("Fout bij ophalen user stats: " . $e->getMessage()); } try { $pendingInvites = (int)$db->query("SELECT COUNT(*) FROM invites WHERE used_at IS NULL AND expires_at > NOW()")->fetchColumn(); } catch (Throwable $e) { error_log("Fout bij ophalen invites stats: " . $e->getMessage()); ensure_tables_exist($db); } try { $outlookConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'OUTLOOK' AND access_token IS NOT NULL")->fetchColumn(); $jiraConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'JIRA' AND settings LIKE '%jira_api_token%' AND settings NOT LIKE '%\"jira_api_token\":\"\"%'")->fetchColumn(); } catch (Throwable $e) { error_log("Fout bij ophalen integrations stats: " . $e->getMessage()); ensure_tables_exist($db); } try { $recentUsers = $db->query(" SELECT u.id, u.name, u.email, u.role, u.created_at, (SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'OUTLOOK' AND i.access_token IS NOT NULL) as has_outlook, (SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'JIRA' AND i.settings LIKE '%jira_api_token%' AND i.settings NOT LIKE '%\"jira_api_token\":\"\"%') as has_jira FROM users u ORDER BY u.created_at DESC LIMIT 6 ")->fetchAll() ?: []; } catch (Throwable $e) { error_log("Fout bij ophalen recente gebruikers: " . $e->getMessage()); } $allUsersForJira = []; $allJiraFilters = []; try { $allUsersForJira = $db->query("SELECT id, name, email, role, avatar_url, default_jira_filter_code FROM users ORDER BY name ASC")->fetchAll() ?: []; $allJiraFilters = $db->query("SELECT * FROM jira_queue_filters ORDER BY sort_order ASC, id ASC")->fetchAll() ?: []; } catch (Throwable $e) { error_log("Fout bij ophalen jira filters/users: " . $e->getMessage()); } $auditLogs = []; try { $auditLogs = $db->query("SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 50")->fetchAll() ?: []; } catch (Throwable $e) { error_log("Fout bij ophalen audit logs: " . $e->getMessage()); } $pageTitle = 'Systeembeheer & Integraties — Fociz'; require_once __DIR__ . '/includes/header.php'; ?>
Beheerderspaneel • Alleen toegankelijk voor Beheer

Systeembeheer & Integraties

Beheer hier de centrale organisatiekoppelingen zoals Microsoft 365 Azure App Registratie en Jira, inspecteer de teamstatus en configureer toekomstige modules.

Totaal Teamleden Beheerders • Collega's
Outlook Live Gekoppeld
Jira Gekoppeld
Openstaande Invites Beheer uitnodigingen →

Microsoft 365 Koppeling (Agenda, E-mail & Teams)

Geconfigureerd Configuratie Vereist

Centrale OAuth 2.0 koppeling voor Outlook Agenda, E-mail (Postvak IN) en Microsoft Teams

Admin Consent Verlenen
Kopieer dit exact naar de Azure Web Redirect URIs
Vul uw domeinnaam in (planningsagenda.nl) of de Directory (tenant) ID uit Azure. Let op: Gebruik géén 'common' voor single-tenant apps.
Wordt veilig en versleuteld opgeslagen
Zodra opgeslagen en goedgekeurd kunnen al uw collega's met 1 klik hun Outlook agenda én e-mail koppelen.

Atlassian Jira Organisatie-instellingen

Actief Optioneel

Standaard Jira Cloud domein voor het hele team

Wanneer u dit instelt, wordt dit domein automatisch vooraf ingevuld op de profielpagina van alle collega's. Zij hoeven dan enkel nog hun eigen persoonlijke e-mailadres en Atlassian API-token in te voeren.

Jira Wachtrijfilters & Toewijzingen

ServiceDesk & Teams

Beheer aangepaste JQL-wachtrijfilters (zoals DX en SD) en wijs teamleden toe aan hun standaard wachtrij.

Geconfigureerde Wachtrijfilters ()

Worden getoond als snelle filtertabs op de Jira pagina

Nog geen wachtrijfilters ingesteld. Klik op "Nieuw Wachtrijfilter" om te beginnen.

Project: Alle projecten Inactief

JQL:
Standaard gekoppeld aan: Nog niemand specifiek toegewezen

Team Wachtrijmatrix: Standaard Wachtrij per Collega

Wijs collega's toe aan hun primaire wachtrij (bijv. DX of SD). Wanneer zij de Jira pagina openen, wordt deze wachtrij direct als standaard getoond.

Teamlid E-mailadres Rol Standaard Wachtrijfilter
Avatar

Werkdag & Dagplanning Standaarden

Centrale kantoortijden, lunchpauzes en Pomodoro presets voor het team

Release v1.4 Actief
Kantooruren / Tijdlijn Bereik

Bepaalt het standaard start- en einduur op de dagelijkse tijdlijn.

Automatisch Lunchpauze Blok

Wordt visueel gemarkeerd als pauzeblok op de tijdlijn.

Focus & Pomodoro Presets (minuten)
Standaard Taakduur bij Aanmaken

Selectie bij het toevoegen van nieuwe dagtaken.

AI & Brainstorm Co-Pilot (Google Gemini Pro)

Intelligente assistentie voor post-it ontleding, categorisatie, ideegeneratie en slimme planning

Zero-Data Leakage
Strikte Zero-Data Leakage Privacy Garantie

Er wordt onder géén enkele voorwaarde data uit Jira tickets, Outlook e-mails of Microsoft Teams naar de AI verzonden. De ingebouwde privacy firewall onderschept en filtert automatisch ticketnummers, Atlassian-links, mailheaders en communicatietraces vóór verzending.

Haal een gratis of betaalde Gemini API sleutel via Google AI Studio →

SMTP Mail Server & Notificaties

Uitgaande e-mailserver voor wachtwoord-vergeten, uitnodigingen en notificaties

mail.fociz.nl:587 • Actief
SMTP Host & Poort: : (STARTTLS)
SMTP Gebruiker:
Zichtbare Afzender (From): <>
Antwoordadres (Reply-To):

Directe SMTP Verbindingstest

Verstuur een realtime testmail met het Fociz HTML-sjabloon om de verbinding, STARTTLS-handshake en aflevering direct te verifiëren.

Sessie Inactiviteit & Beveiligingsbeleid

Automatische afmelding bij inactiviteit conform NIST SP 800-63B, OWASP & ISO 27001

SOC 2 / ISO 27001 Compliant
Actieve beveiligingsmaatregelen:
  • Interactieve waarschuwing: 2 minuten (120 sec) voor time-out verschijnt een aftelmodal. Actieve gebruikers kunnen met 1 klik ingelogd blijven.
  • Periodieke Sessierotatie: Sessie-ID's worden conform OWASP ASVS v4.0 automatisch elke 30 minuten geroteerd tegen session hijacking.
  • Absolute daglimiet: Maximaal 10 uur aaneengesloten sessie per kalenderdag; om middernacht vervalt elke sessie automatisch.
  • Tabblad-synchronisatie: Activiteit in één browsertabblad verlengt automatisch alle open Fociz-tabbladen.
Systeem & Beveiliging
Tweestapsverificatie (2FA) Verplicht
Sessie Idle Timeout min
Sessie ID Rotatie Elke 30 min
Wachtwoordbeleid NIST 12+ tekens
Database Versie MariaDB 10.x
Push Notificaties Actief
Graph API Sync Live v1.0
Compliance & Veiligheidslogboek (SOC 2 / ISO 27001)

Onwijzigbare Audit Trail

Centrale registratie van alle inlogpogingen, 2FA-verificaties, IP-detecties en beheerdersacties conform NIST SP 800-63B.

Live Audit Logging Actief
'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20', 'FAILED', 'FAILURE' => 'bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20', 'WARNING' => 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20', 'PENDING' => 'bg-sky-500/10 text-sky-600 dark:text-sky-400 border-sky-500/20', default => 'bg-slate-500/10 text-slate-600 dark:text-slate-400 border-slate-500/20', }; $action = $log['action'] ?? ''; $actionBadge = match($action) { 'LOGIN_SUCCESS' => 'bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 border-emerald-200 dark:border-emerald-800/60', 'LOGIN_FAILED', 'LOGIN_MFA_FAILED' => 'bg-rose-50 dark:bg-rose-950/40 text-rose-700 dark:text-rose-300 border-rose-200 dark:border-rose-800/60', 'LOGIN_NEW_IP_CHALLENGE', 'IP_VERIFIED' => 'bg-sky-50 dark:bg-sky-950/40 text-sky-700 dark:text-sky-300 border-sky-200 dark:border-sky-800/60', 'SETTINGS_UPDATED' => 'bg-purple-50 dark:bg-purple-950/40 text-purple-700 dark:text-purple-300 border-purple-200 dark:border-purple-800/60', 'USER_INVITED', 'INVITE_RESENT', 'USER_REGISTERED_VIA_INVITE' => 'bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300 border-indigo-200 dark:border-indigo-800/60', 'USER_DELETED', 'INVITE_REVOKED' => 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-300 border-amber-200 dark:border-amber-800/60', default => 'bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-700', }; $detailsStr = ''; if (!empty($log['details'])) { $decoded = json_decode($log['details'], true); if (is_array($decoded)) { $parts = []; foreach ($decoded as $k => $v) { if (is_scalar($v)) { $parts[] = "{$k}: {$v}"; } } $detailsStr = implode(', ', $parts); } else { $detailsStr = (string)$log['details']; } } ?>
Tijdstip Gebruiker / Doel Actie IP-adres Status Details
Nog geen beveiligingsgebeurtenissen geregistreerd.