mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-24 07:10:21 -05:00
5d4e693a2b
Introduce a guided LDAP configuration wizard mirroring the OIDC flow: five-step UI with server/TLS, bind, directory layout, groups and AUTH_METHOD, then optional connection test and .env / Docker Compose generation for copy-paste deployment. - Refactor LDAPService.test_connection to accept an optional config mapping so the wizard can test draft values without merging live env secrets; keep POST /admin/ldap/test on current_app.config. - Add GET /admin/ldap/setup-wizard plus POST endpoints for test, validate, and generate-config (manage_settings, rate limited). - Surface an LDAP card with status badge and wizard link on the integrations list for admins and manage_settings users. - Add tests for validate, generate, and wizard test delegation.
176 lines
9.8 KiB
HTML
176 lines
9.8 KiB
HTML
{% extends "base.html" %}
|
|
{% from "components/ui.html" import page_header, empty_state %}
|
|
|
|
{% block title %}{{ _('Integrations') }} - {{ app_name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
{% set breadcrumbs = [
|
|
{'text': 'Integrations'}
|
|
] %}
|
|
|
|
{{ page_header(
|
|
icon_class='fas fa-plug',
|
|
title_text='Integrations',
|
|
subtitle_text='Connect with third-party services to extend functionality',
|
|
breadcrumbs=breadcrumbs
|
|
) }}
|
|
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-border-dark shadow-sm mb-6">
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mb-4">
|
|
{{ _('Connect your Time Tracker with external services. Configure integrations below to sync data, automate workflows, and enhance productivity.') }}
|
|
</p>
|
|
</div>
|
|
|
|
{% if current_user.is_admin or current_user.has_permission('manage_oidc') %}
|
|
<!-- OIDC Authentication Setup -->
|
|
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm hover:shadow-lg transition-shadow overflow-hidden mb-6 border-2 border-primary/20">
|
|
<div class="p-6">
|
|
<div class="flex flex-col sm:flex-row items-start justify-between gap-3 mb-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
|
|
<i class="fas fa-shield-alt text-primary text-xl"></i>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<h3 class="text-lg font-semibold text-text-light dark:text-text-dark">{{ _('OIDC / Single Sign-On') }}</h3>
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mt-1">
|
|
{{ _('Configure OpenID Connect authentication for Single Sign-On (SSO) with your identity provider') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 mb-4">
|
|
{% set auth_method = config.get('AUTH_METHOD', 'local') or 'local' %}
|
|
{% set oidc_enabled = auth_method.lower() in ['oidc', 'both', 'all'] %}
|
|
{% if oidc_enabled %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 font-medium">
|
|
<i class="fas fa-check-circle mr-1"></i>{{ _('Enabled') }}
|
|
</span>
|
|
{% else %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200 font-medium">
|
|
<i class="fas fa-circle mr-1"></i>{{ _('Not Configured') }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<a href="{{ url_for('admin.oidc_setup_wizard') }}"
|
|
class="block w-full bg-primary hover:bg-primary/90 text-white px-4 py-2.5 min-h-[44px] rounded-lg transition-colors text-center font-medium inline-flex items-center justify-center">
|
|
<i class="fas fa-magic mr-2"></i>{{ _('Setup Wizard') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if current_user.is_admin or current_user.has_permission('manage_settings') %}
|
|
<!-- LDAP authentication setup -->
|
|
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm hover:shadow-lg transition-shadow overflow-hidden mb-6 border-2 border-primary/20">
|
|
<div class="p-6">
|
|
<div class="flex flex-col sm:flex-row items-start justify-between gap-3 mb-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
|
|
<i class="fas fa-address-book text-primary text-xl"></i>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<h3 class="text-lg font-semibold text-text-light dark:text-text-dark">{{ _('LDAP') }}</h3>
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mt-1">
|
|
{{ _('Configure directory authentication via environment variables; use the wizard to build a working .env snippet and test connectivity.') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 mb-4">
|
|
{% set auth_method_ldap = config.get('AUTH_METHOD', 'local') or 'local' %}
|
|
{% set ldap_auth_enabled = auth_method_ldap.lower() in ['ldap', 'all'] %}
|
|
{% if ldap_auth_enabled %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 font-medium">
|
|
<i class="fas fa-check-circle mr-1"></i>{{ _('Enabled') }}
|
|
</span>
|
|
{% else %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200 font-medium">
|
|
<i class="fas fa-circle mr-1"></i>{{ _('Not configured') }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<a href="{{ url_for('admin.ldap_setup_wizard') }}"
|
|
class="block w-full bg-primary hover:bg-primary/90 text-white px-4 py-2.5 min-h-[44px] rounded-lg transition-colors text-center font-medium inline-flex items-center justify-center">
|
|
<i class="fas fa-magic mr-2"></i>{{ _('Setup Wizard') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
|
|
{% for provider in available_providers %}
|
|
{% set existing_integration = integrations|selectattr('provider', 'equalto', provider.provider)|first if integrations else none %}
|
|
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
|
|
<div class="p-6">
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
|
|
<i class="fas fa-{{ provider.icon }} text-primary text-xl"></i>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<h3 class="text-lg font-semibold text-text-light dark:text-text-dark truncate">{{ provider.display_name }}</h3>
|
|
{% if provider.description %}
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mt-1 line-clamp-2">{{ provider.description }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 mb-4">
|
|
{% if existing_integration %}
|
|
{% if existing_integration.is_active %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 font-medium">
|
|
<i class="fas fa-check-circle mr-1"></i>{{ _('Connected') }}
|
|
</span>
|
|
{% else %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-medium">
|
|
<i class="fas fa-exclamation-circle mr-1"></i>{{ _('Not Connected') }}
|
|
</span>
|
|
{% endif %}
|
|
{% if existing_integration.is_global %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
|
{{ _('Global') }}
|
|
</span>
|
|
{% endif %}
|
|
{% if existing_integration.last_sync_at %}
|
|
<span class="text-xs text-text-muted-light dark:text-text-muted-dark">
|
|
{{ _('Synced') }} {{ existing_integration.last_sync_at|user_date }}
|
|
</span>
|
|
{% endif %}
|
|
{% else %}
|
|
<span class="px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200 font-medium">
|
|
<i class="fas fa-circle mr-1"></i>{{ _('Not Set Up') }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
{% if has_setup_wizard(provider.provider) %}
|
|
<a href="{{ url_for('integrations.setup_wizard', provider=provider.provider) }}"
|
|
class="block w-full bg-primary hover:bg-primary/90 text-white px-4 py-2.5 rounded-lg transition-colors text-center font-medium">
|
|
<i class="fas fa-magic mr-2"></i>{{ _('Setup Wizard') }}
|
|
</a>
|
|
{% endif %}
|
|
<a href="{{ url_for('integrations.manage_integration', provider=provider.provider) }}"
|
|
class="block w-full {% if existing_integration and existing_integration.is_active %}bg-gray-600 hover:bg-gray-700{% else %}{% if has_setup_wizard(provider.provider) %}bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600{% else %}bg-primary hover:bg-primary/90{% endif %}{% endif %} text-white px-4 py-2.5 rounded-lg transition-colors text-center font-medium">
|
|
{% if existing_integration %}
|
|
{% if existing_integration.is_active %}
|
|
<i class="fas fa-cog mr-2"></i>{{ _('Manage') }}
|
|
{% else %}
|
|
<i class="fas fa-link mr-2"></i>{{ _('Connect') }}
|
|
{% endif %}
|
|
{% else %}
|
|
<i class="fas fa-plus mr-2"></i>{{ _('Setup') }}
|
|
{% endif %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endblock %}
|