mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-12 15:29:23 -05:00
9449a46a42
- Add Linear import (GraphQL, personal API key, optional team key filter). - Centralize integration HTTP via integration_session and session_request. - Add integration_sync_context for project/task refs and custom_fields metadata. - Refactor Asana, GitHub, GitLab, Jira, Trello, ActivityWatch, and QuickBooks to use helpers. - Extend integration UI, settings, and scheduled sync behavior as needed.
174 lines
12 KiB
HTML
174 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ integration.name }} - {{ _('Integration') }} - {{ app_name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold">{{ integration.name }}</h1>
|
|
<p class="text-text-muted-light dark:text-text-muted-dark">{{ integration.provider|title }}</p>
|
|
</div>
|
|
<a href="{{ url_for('integrations.list_integrations') }}" class="bg-gray-200 dark:bg-gray-700 px-4 py-2 rounded-lg mt-4 md:mt-0">{{ _('Back to Integrations') }}</a>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<div class="lg:col-span-2 space-y-6">
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-border-dark shadow-sm">
|
|
<h2 class="text-lg font-semibold mb-4">{{ _('Integration Status') }}</h2>
|
|
<dl class="space-y-3">
|
|
<div>
|
|
<dt class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">{{ _('Status') }}</dt>
|
|
<dd>
|
|
{% if integration.is_active %}
|
|
<span class="px-2 py-1 text-xs rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">{{ _('Active') }}</span>
|
|
{% else %}
|
|
<span class="px-2 py-1 text-xs rounded bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200">{{ _('Inactive') }}</span>
|
|
{% endif %}
|
|
</dd>
|
|
</div>
|
|
{% if credentials %}
|
|
<div>
|
|
<dt class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">{{ _('Connected') }}</dt>
|
|
<dd class="text-text-light dark:text-text-dark">{{ _('Yes') }}</dd>
|
|
</div>
|
|
{% if credentials.expires_at %}
|
|
<div>
|
|
<dt class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">{{ _('Token Expires') }}</dt>
|
|
<dd class="text-text-light dark:text-text-dark">{{ credentials.expires_at|user_datetime }}</dd>
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div>
|
|
<dt class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">{{ _('Connected') }}</dt>
|
|
<dd class="text-text-light dark:text-text-dark">{{ _('No') }}</dd>
|
|
</div>
|
|
{% endif %}
|
|
{% if integration.last_sync_at %}
|
|
<div>
|
|
<dt class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">{{ _('Last Sync') }}</dt>
|
|
<dd class="text-text-light dark:text-text-dark">
|
|
{{ integration.last_sync_at|user_datetime }}
|
|
{% if integration.last_sync_status %}
|
|
{% if integration.last_sync_status == 'success' %}
|
|
<span class="ml-2 px-2 py-1 text-xs rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">{{ _('Success') }}</span>
|
|
{% elif integration.last_sync_status == 'error' %}
|
|
<span class="ml-2 px-2 py-1 text-xs rounded bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200">{{ _('Error') }}</span>
|
|
{% else %}
|
|
<span class="ml-2 px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">{{ _('Pending') }}</span>
|
|
{% endif %}
|
|
{% endif %}
|
|
</dd>
|
|
</div>
|
|
{% endif %}
|
|
{% if integration.last_error %}
|
|
<div>
|
|
<dt class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">{{ _('Last Error') }}</dt>
|
|
<dd class="text-text-light dark:text-text-dark text-sm text-red-600 dark:text-red-400">{{ integration.last_error[:100] }}{% if integration.last_error|length > 100 %}...{% endif %}</dd>
|
|
</div>
|
|
{% endif %}
|
|
</dl>
|
|
</div>
|
|
|
|
<!-- Sync Events Log -->
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-border-dark shadow-sm mt-6">
|
|
<h2 class="text-lg font-semibold mb-4">{{ _('Sync History') }}</h2>
|
|
{% if recent_events %}
|
|
<div class="space-y-2 max-h-[32rem] overflow-y-auto">
|
|
{% for event in recent_events %}
|
|
<div class="border-b border-border-light dark:border-border-dark pb-2 last:border-0">
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-2 flex-wrap">
|
|
<span class="text-sm font-medium text-text-light dark:text-text-dark">{{ event.event_type|replace('_', ' ')|title }}</span>
|
|
{% if event.status == 'success' %}
|
|
<span class="px-2 py-0.5 text-xs rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">{{ _('Success') }}</span>
|
|
{% elif event.status == 'error' %}
|
|
<span class="px-2 py-0.5 text-xs rounded bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200">{{ _('Error') }}</span>
|
|
{% else %}
|
|
<span class="px-2 py-0.5 text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">{{ _('Pending') }}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if event.message %}
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mt-1 break-words">{{ event.message }}</p>
|
|
{% endif %}
|
|
{% if event.event_metadata %}
|
|
<details class="mt-2">
|
|
<summary class="text-xs text-primary cursor-pointer hover:underline">{{ _('Details') }}</summary>
|
|
<pre class="mt-2 text-xs bg-gray-100 dark:bg-gray-900 p-2 rounded overflow-x-auto max-h-48 overflow-y-auto">{{ event.event_metadata | tojson }}</pre>
|
|
</details>
|
|
{% endif %}
|
|
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ event.created_at|user_datetime }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-text-muted-light dark:text-text-muted-dark">{{ _('No sync events yet') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-border-dark shadow-sm">
|
|
<h3 class="text-lg font-semibold mb-4">{{ _('Actions') }}</h3>
|
|
{% if connector_error %}
|
|
<div class="mb-4 p-3 bg-red-100 dark:bg-red-900/20 border border-red-300 dark:border-red-800 rounded-lg">
|
|
<p class="text-sm text-red-800 dark:text-red-200">
|
|
<i class="fas fa-exclamation-triangle mr-2"></i>
|
|
{{ _('Connector Error') }}: {{ connector_error }}
|
|
</p>
|
|
{% if integration.provider == 'caldav_calendar' %}
|
|
<a href="{{ url_for('integrations.caldav_setup') }}" class="mt-2 inline-block text-sm text-red-800 dark:text-red-200 hover:underline">
|
|
{{ _('Configure CalDAV Integration') }} →
|
|
</a>
|
|
{% elif integration.provider == 'activitywatch' %}
|
|
<a href="{{ url_for('integrations.activitywatch_setup') }}" class="mt-2 inline-block text-sm text-red-800 dark:text-red-200 hover:underline">
|
|
{{ _('Configure ActivityWatch') }} →
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
<div class="space-y-2">
|
|
{% if connector %}
|
|
<form method="POST" action="{{ url_for('integrations.test_integration', integration_id=integration.id) }}" class="mb-2">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="w-full bg-blue-600 text-white px-4 py-2 min-h-[44px] rounded-lg hover:bg-blue-700 transition-colors {% if not connector %}opacity-50 cursor-not-allowed{% endif %}" {% if not connector %}disabled{% endif %}>
|
|
<i class="fas fa-vial mr-2"></i>{{ _('Test Connection') }}
|
|
</button>
|
|
</form>
|
|
<form method="POST" action="{{ url_for('integrations.sync_integration', integration_id=integration.id) }}" class="mb-2">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="w-full bg-green-600 text-white px-4 py-2 min-h-[44px] rounded-lg hover:bg-green-700 transition-colors {% if not connector %}opacity-50 cursor-not-allowed{% endif %}" {% if not connector %}disabled{% endif %}>
|
|
<i class="fas fa-sync mr-2"></i>{{ _('Sync Now') }}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
<form method="POST" action="{{ url_for('integrations.reset_integration', integration_id=integration.id) }}" onsubmit="return confirm('{{ _('Are you sure you want to reset this integration? This will remove all credentials and configuration.') }}')" class="mb-2">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="w-full bg-orange-600 text-white px-4 py-2 min-h-[44px] rounded-lg hover:bg-orange-700 transition-colors">
|
|
<i class="fas fa-redo mr-2"></i>{{ _('Reset') }}
|
|
</button>
|
|
</form>
|
|
<form method="POST" action="{{ url_for('integrations.delete_integration', integration_id=integration.id) }}" onsubmit="return confirm('{{ _('Are you sure you want to delete this integration? This action cannot be undone.') }}')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="w-full bg-red-600 text-white px-4 py-2 min-h-[44px] rounded-lg hover:bg-red-700 transition-colors">
|
|
<i class="fas fa-trash mr-2"></i>{{ _('Delete') }}
|
|
</button>
|
|
</form>
|
|
{% if integration.provider == 'caldav_calendar' %}
|
|
<a href="{{ url_for('integrations.caldav_setup') }}" class="block w-full bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors text-center">
|
|
<i class="fas fa-cog mr-2"></i>{{ _('Edit Configuration') }}
|
|
</a>
|
|
{% elif integration.provider == 'activitywatch' %}
|
|
<a href="{{ url_for('integrations.activitywatch_setup') }}" class="block w-full bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors text-center">
|
|
<i class="fas fa-cog mr-2"></i>{{ _('Edit Configuration') }}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|