1626 lines
69 KiB
HTML
1626 lines
69 KiB
HTML
{% extends "common/base.html" %}
|
|
{% from "components/header.html" import header %}
|
|
|
|
{% block title %}{{ instance.name }} - DocuPulse{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}?v={{ 'css/settings.css'|asset_version }}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
{{ header(
|
|
title="Instance Details",
|
|
description=instance.name + " for " + instance.company,
|
|
icon="fa-server",
|
|
buttons=[
|
|
{
|
|
'text': 'Back to Instances',
|
|
'url': '/instances',
|
|
'icon': 'fa-arrow-left',
|
|
'class': 'btn-secondary'
|
|
}
|
|
]
|
|
) }}
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="row mb-4">
|
|
<!-- Activity Status Card -->
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="status-icon-wrapper">
|
|
<i class="fas fa-circle-notch fa-spin {% if instance.status == 'active' %}text-success{% else %}text-danger{% endif %} fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h5 class="card-title mb-1">Activity Status</h5>
|
|
<p class="card-text mb-0">
|
|
<span class="badge bg-{{ 'success' if instance.status == 'active' else 'danger' }}">
|
|
{{ instance.status|title }}
|
|
</span>
|
|
{% if instance.status_details %}
|
|
<small class="text-muted d-block mt-1">{{ instance.status_details }}</small>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Authentication Status Card -->
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="status-icon-wrapper">
|
|
<i class="fas fa-shield-alt {% if instance.connection_token %}text-success{% else %}text-warning{% endif %} fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h5 class="card-title mb-1">Authentication Status</h5>
|
|
<p class="card-text mb-0">
|
|
<span class="badge bg-{{ 'success' if instance.connection_token else 'warning' }}">
|
|
{{ 'Authenticated' if instance.connection_token else 'Not Authenticated' }}
|
|
</span>
|
|
{% if not instance.connection_token %}
|
|
<small class="text-muted d-block mt-1">This instance needs to be authenticated</small>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs Section -->
|
|
<div class="card">
|
|
<div class="card-header bg-white">
|
|
<ul class="nav nav-tabs card-header-tabs" id="instanceTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="overview-tab" data-bs-toggle="tab" data-bs-target="#overview" type="button" role="tab" aria-controls="overview" aria-selected="true">
|
|
<i class="fas fa-building me-2"></i>Company Information
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="contacts-tab" data-bs-toggle="tab" data-bs-target="#contacts" type="button" role="tab" aria-controls="contacts" aria-selected="false">
|
|
<i class="fas fa-users me-2"></i>Contacts
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="logs-tab" data-bs-toggle="tab" data-bs-target="#logs" type="button" role="tab" aria-controls="logs" aria-selected="false">
|
|
<i class="fas fa-history me-2"></i>Activity Logs
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="mails-tab" data-bs-toggle="tab" data-bs-target="#mails" type="button" role="tab" aria-controls="mails" aria-selected="false">
|
|
<i class="fas fa-envelope me-2"></i>Mail Logs
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="tab-content" id="instanceTabsContent">
|
|
<!-- Overview Tab -->
|
|
<div class="tab-pane fade show active" id="overview" role="tabpanel" aria-labelledby="overview-tab">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h5 class="mb-3">Company Details</h5>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;" id="company-name-label">Company Name:</div>
|
|
<div class="company-value" id="company-name-value">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Industry:</div>
|
|
<div class="company-value" id="company-industry">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Description:</div>
|
|
<div class="company-value" id="company-description">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Version:</div>
|
|
<div class="company-value" id="instance-version-value">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Payment Plan:</div>
|
|
<div class="company-value" id="instance-payment-plan-value">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h5 class="mb-3">Contact Information</h5>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Email:</div>
|
|
<div class="company-value" id="company-email">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Phone:</div>
|
|
<div class="company-value" id="company-phone">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Website:</div>
|
|
<div class="company-value" id="company-website">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex">
|
|
<div class="text-muted me-2" style="min-width: 120px;">Address:</div>
|
|
<div class="company-value" id="company-address">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contacts Tab -->
|
|
<div class="tab-pane fade" id="contacts" role="tabpanel" aria-labelledby="contacts-tab">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h5 class="mb-0">Contact List</h5>
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addContactModal">
|
|
<i class="fas fa-plus me-2"></i>Add Contact
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Contacts Table -->
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Email</th>
|
|
<th>Phone</th>
|
|
<th>Company</th>
|
|
<th>Position</th>
|
|
<th>Role</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="contactsTableBody">
|
|
<tr>
|
|
<td colspan="7" class="text-center">Loading contacts...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logs Tab -->
|
|
<div class="tab-pane fade" id="logs" role="tabpanel" aria-labelledby="logs-tab">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h5 class="card-title mb-0">Activity Log</h5>
|
|
<div class="d-flex gap-2">
|
|
<select class="form-select form-select-sm" id="logCategory">
|
|
<option value="">All Categories</option>
|
|
<option value="AUTH">Authentication</option>
|
|
<option value="FILE">File Operations</option>
|
|
<option value="USER">User Management</option>
|
|
<option value="SYSTEM">System</option>
|
|
</select>
|
|
<select class="form-select form-select-sm" id="dateRangeFilter">
|
|
<option value="24h">Last 24 Hours</option>
|
|
<option value="7d" selected>Last 7 Days</option>
|
|
<option value="30d">Last 30 Days</option>
|
|
<option value="all">All Time</option>
|
|
</select>
|
|
<button class="btn btn-secondary btn-sm" id="clearLogFiltersBtn">
|
|
<i class="fas fa-times me-1"></i>Clear Filters
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logs Table -->
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Timestamp</th>
|
|
<th>Event Type</th>
|
|
<th>User</th>
|
|
<th>IP Address</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="logsTableBody">
|
|
<!-- Logs will be loaded here via JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<nav aria-label="Logs pagination" class="mt-4">
|
|
<ul class="pagination justify-content-center" id="logsPagination">
|
|
<!-- Pagination will be loaded here via JavaScript -->
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mails Tab -->
|
|
<div class="tab-pane fade" id="mails" role="tabpanel" aria-labelledby="mails-tab">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h5 class="card-title mb-0">Mail Log</h5>
|
|
<div class="d-flex gap-2">
|
|
<select class="form-select form-select-sm" id="statusFilter">
|
|
<option value="">All Statuses</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="sent">Sent</option>
|
|
<option value="failed">Failed</option>
|
|
</select>
|
|
<select class="form-select form-select-sm" id="dateRangeFilter">
|
|
<option value="24h">Last 24 Hours</option>
|
|
<option value="7d" selected>Last 7 Days</option>
|
|
<option value="30d">Last 30 Days</option>
|
|
<option value="all">All Time</option>
|
|
</select>
|
|
<button class="btn btn-secondary btn-sm" id="clearFiltersBtn">
|
|
<i class="fas fa-times me-1"></i>Clear Filters
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mail Table -->
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Created At</th>
|
|
<th>Recipient</th>
|
|
<th>Subject</th>
|
|
<th>Sent At</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="mailsTableBody">
|
|
<!-- Mails will be loaded here via JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<nav aria-label="Mails pagination" class="mt-4">
|
|
<ul class="pagination justify-content-center" id="mailsPagination">
|
|
<!-- Pagination will be loaded here via JavaScript -->
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Contact Modal -->
|
|
<div class="modal fade" id="addContactModal" tabindex="-1" aria-labelledby="addContactModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="addContactModalLabel">Add New Contact</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="addContactForm" onsubmit="addContact(event)">
|
|
<div class="mb-3">
|
|
<label for="name" class="form-label">Name</label>
|
|
<input type="text" class="form-control" id="name" name="name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="email" class="form-label">Email</label>
|
|
<input type="email" class="form-control" id="email" name="email" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="phone" class="form-label">Phone</label>
|
|
<input type="tel" class="form-control" id="phone" name="phone">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="company" class="form-label">Company</label>
|
|
<input type="text" class="form-control" id="company" name="company">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="position" class="form-label">Position</label>
|
|
<input type="text" class="form-control" id="position" name="position">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="role" class="form-label">Role</label>
|
|
<select class="form-select" id="role" name="role" required>
|
|
<option value="user">Standard User</option>
|
|
<option value="manager">Manager</option>
|
|
<option value="admin">Administrator</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="status" class="form-label">Status</label>
|
|
<select class="form-select" id="status" name="status">
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
<div class="text-end">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Add Contact</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Contact Modal -->
|
|
<div class="modal fade" id="editContactModal" tabindex="-1" aria-labelledby="editContactModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="editContactModalLabel">Edit Contact</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="editContactForm" onsubmit="updateContact(event)">
|
|
<input type="hidden" id="edit-contact-id" name="id">
|
|
<div class="mb-3">
|
|
<label for="edit-name" class="form-label">Name</label>
|
|
<input type="text" class="form-control" id="edit-name" name="name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="edit-email" class="form-label">Email</label>
|
|
<input type="email" class="form-control" id="edit-email" name="email" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="edit-phone" class="form-label">Phone</label>
|
|
<input type="tel" class="form-control" id="edit-phone" name="phone">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="edit-company" class="form-label">Company</label>
|
|
<input type="text" class="form-control" id="edit-company" name="company">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="edit-position" class="form-label">Position</label>
|
|
<input type="text" class="form-control" id="edit-position" name="position">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="edit-role" class="form-label">Role</label>
|
|
<select class="form-select" id="edit-role" name="role" required>
|
|
<option value="user">Standard User</option>
|
|
<option value="manager">Manager</option>
|
|
<option value="admin">Administrator</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="edit-status" class="form-label">Status</label>
|
|
<select class="form-select" id="edit-status" name="status">
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
<div class="text-end">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Contact Modal -->
|
|
<div class="modal fade" id="deleteContactModal" tabindex="-1" aria-labelledby="deleteContactModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="deleteContactModalLabel">Delete Contact</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Are you sure you want to delete this contact? This action cannot be undone.</p>
|
|
<div class="text-end">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-danger" id="confirmDelete">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Details Modal -->
|
|
<div class="modal fade" id="logDetailsModal" tabindex="-1" aria-labelledby="logDetailsModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="logDetailsModalLabel">Log Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="logDetailsContent">
|
|
<!-- Log details will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mail Details Modal -->
|
|
<div class="modal fade" id="mailDetailsModal" tabindex="-1" aria-labelledby="mailDetailsModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="mailDetailsModalLabel">Mail Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="mailDetailsContent">
|
|
<!-- Mail details will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Basic test to verify script execution
|
|
console.log('Script block loaded');
|
|
|
|
// Test function to verify DOM elements exist
|
|
function testElements() {
|
|
console.log('Testing DOM elements...');
|
|
const elements = [
|
|
'company-name-value',
|
|
'company-industry',
|
|
'company-description',
|
|
'company-email',
|
|
'company-phone',
|
|
'company-website',
|
|
'company-address'
|
|
];
|
|
|
|
elements.forEach(id => {
|
|
const element = document.getElementById(id);
|
|
console.log(`Element ${id} exists:`, !!element);
|
|
});
|
|
}
|
|
|
|
let statusUpdateInterval;
|
|
|
|
// Function to check instance status
|
|
async function checkInstanceStatus() {
|
|
console.log('checkInstanceStatus called');
|
|
try {
|
|
const response = await fetch(`/instances/{{ instance.id }}/status`);
|
|
if (!response.ok) throw new Error('Failed to check instance status');
|
|
|
|
const data = await response.json();
|
|
console.log('Status data:', data);
|
|
|
|
// Update activity status
|
|
const activityIcon = document.querySelector('.card:first-child .fa-circle-notch');
|
|
const activityBadge = document.querySelector('.card:first-child .badge');
|
|
const activityDetails = document.querySelector('.card:first-child .text-muted');
|
|
|
|
if (activityIcon) {
|
|
activityIcon.className = `fas fa-circle-notch fa-spin ${data.status === 'active' ? 'text-success' : 'text-danger'} fa-2x`;
|
|
}
|
|
if (activityBadge) {
|
|
activityBadge.className = `badge bg-${data.status === 'active' ? 'success' : 'danger'}`;
|
|
activityBadge.textContent = data.status.charAt(0).toUpperCase() + data.status.slice(1);
|
|
}
|
|
if (activityDetails) {
|
|
// Only update if we have status details
|
|
if (data.status_details) {
|
|
activityDetails.textContent = data.status_details;
|
|
activityDetails.style.display = 'block';
|
|
} else {
|
|
activityDetails.style.display = 'none';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking instance status:', error);
|
|
}
|
|
}
|
|
|
|
// Function to check authentication status
|
|
async function checkAuthStatus() {
|
|
console.log('checkAuthStatus called');
|
|
try {
|
|
const response = await fetch(`/instances/{{ instance.id }}/auth-status`);
|
|
if (!response.ok) throw new Error('Failed to check authentication status');
|
|
|
|
const data = await response.json();
|
|
console.log('Auth data:', data);
|
|
|
|
// Update authentication status
|
|
const authIcon = document.querySelector('.card:nth-child(2) .fa-shield-alt');
|
|
const authBadge = document.querySelector('.card:nth-child(2) .badge');
|
|
const authDetails = document.querySelector('.card:nth-child(2) .text-muted');
|
|
|
|
if (authIcon) {
|
|
authIcon.className = `fas fa-shield-alt ${data.authenticated ? 'text-success' : 'text-warning'} fa-2x`;
|
|
}
|
|
if (authBadge) {
|
|
authBadge.className = `badge bg-${data.authenticated ? 'success' : 'warning'}`;
|
|
authBadge.textContent = data.authenticated ? 'Authenticated' : 'Not Authenticated';
|
|
}
|
|
if (authDetails) {
|
|
authDetails.textContent = data.authenticated ? '' : 'This instance needs to be authenticated';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking authentication status:', error);
|
|
}
|
|
}
|
|
|
|
// Function to fetch company information
|
|
async function fetchCompanyInfo() {
|
|
console.log('fetchCompanyInfo called');
|
|
try {
|
|
// First get JWT token
|
|
console.log('Getting management token...');
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
console.log('Token response status:', tokenResponse.status);
|
|
if (!tokenResponse.ok) {
|
|
console.error('Failed to get management token:', tokenResponse.status);
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
console.log('Token data received:', tokenData);
|
|
|
|
if (!tokenData.token) {
|
|
console.error('No token in response');
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
// Then fetch settings using the JWT token
|
|
console.log('Fetching settings from:', `{{ instance.main_url }}/api/admin/settings`);
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/settings`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
console.log('Settings response status:', response.status);
|
|
if (!response.ok) {
|
|
console.error('Settings fetch failed:', response.status);
|
|
throw new Error('Failed to fetch company information');
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('Settings data received:', data);
|
|
|
|
// Update company name separately with debugging
|
|
const companyNameElement = document.getElementById('company-name-value');
|
|
const companyNameLabel = document.getElementById('company-name-label');
|
|
|
|
// Ensure label text is set and maintained
|
|
if (companyNameLabel) {
|
|
companyNameLabel.textContent = 'Company Name:';
|
|
}
|
|
|
|
if (companyNameElement) {
|
|
companyNameElement.textContent = data.company_name || 'Not set';
|
|
}
|
|
|
|
// Update other company details
|
|
const updateElement = (id, value) => {
|
|
const element = document.getElementById(id);
|
|
if (element && element.classList.contains('company-value')) {
|
|
element.textContent = value || 'Not set';
|
|
}
|
|
};
|
|
|
|
// Update other company details
|
|
updateElement('company-industry', data.company_industry);
|
|
updateElement('company-description', data.company_description);
|
|
|
|
// Update contact information
|
|
const emailElement = document.getElementById('company-email');
|
|
const phoneElement = document.getElementById('company-phone');
|
|
const websiteElement = document.getElementById('company-website');
|
|
|
|
// Update email with mailto link
|
|
if (emailElement && emailElement.classList.contains('company-value')) {
|
|
if (data.company_email && data.company_email !== 'Not set') {
|
|
emailElement.innerHTML = `<a href="mailto:${data.company_email}" class="text-decoration-none" style="color: var(--primary-color);">
|
|
<i class="fas fa-envelope me-2"></i>${data.company_email}
|
|
</a>`;
|
|
} else {
|
|
emailElement.textContent = 'Not set';
|
|
}
|
|
}
|
|
|
|
// Update phone with tel link
|
|
if (phoneElement && phoneElement.classList.contains('company-value')) {
|
|
if (data.company_phone && data.company_phone !== 'Not set') {
|
|
phoneElement.innerHTML = `<a href="tel:${data.company_phone}" class="text-decoration-none" style="color: var(--primary-color);">
|
|
<i class="fas fa-phone me-2"></i>${data.company_phone}
|
|
</a>`;
|
|
} else {
|
|
phoneElement.textContent = 'Not set';
|
|
}
|
|
}
|
|
|
|
// Update website with external link
|
|
if (websiteElement && websiteElement.classList.contains('company-value')) {
|
|
if (data.company_website && data.company_website !== 'Not set') {
|
|
// Ensure website has http/https prefix
|
|
let websiteUrl = data.company_website;
|
|
if (!websiteUrl.startsWith('http://') && !websiteUrl.startsWith('https://')) {
|
|
websiteUrl = 'https://' + websiteUrl;
|
|
}
|
|
websiteElement.innerHTML = `<a href="${websiteUrl}" target="_blank" rel="noopener noreferrer" class="text-decoration-none" style="color: var(--primary-color);">
|
|
<i class="fas fa-globe me-2"></i>${data.company_website}
|
|
</a>`;
|
|
} else {
|
|
websiteElement.textContent = 'Not set';
|
|
}
|
|
}
|
|
|
|
// Format address
|
|
const addressParts = [
|
|
data.company_address,
|
|
data.company_city,
|
|
data.company_state,
|
|
data.company_zip,
|
|
data.company_country
|
|
].filter(Boolean);
|
|
updateElement('company-address', addressParts.length ? addressParts.join(', ') : 'Not set');
|
|
|
|
console.log('Company info update complete');
|
|
} catch (error) {
|
|
console.error('Error in fetchCompanyInfo:', error);
|
|
// Show error state in all fields
|
|
const fields = ['company-name-value', 'company-industry', 'company-description',
|
|
'company-email', 'company-phone', 'company-website', 'company-address'];
|
|
fields.forEach(field => {
|
|
const element = document.getElementById(field);
|
|
if (element && element.classList.contains('company-value')) {
|
|
element.textContent = 'Error loading data';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Function to update all statuses
|
|
async function updateAllStatuses() {
|
|
console.log('Starting updateAllStatuses...');
|
|
try {
|
|
await Promise.all([
|
|
checkInstanceStatus(),
|
|
checkAuthStatus(),
|
|
fetchCompanyInfo()
|
|
]);
|
|
console.log('All statuses updated successfully');
|
|
} catch (error) {
|
|
console.error('Error in updateAllStatuses:', error);
|
|
}
|
|
}
|
|
|
|
// Initialize status updates
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('DOM loaded, initializing updates...');
|
|
testElements(); // Test if elements exist
|
|
// Initial update
|
|
updateAllStatuses();
|
|
|
|
// Set up periodic updates (every 30 seconds)
|
|
statusUpdateInterval = setInterval(updateAllStatuses, 30000);
|
|
console.log('Periodic updates initialized');
|
|
});
|
|
|
|
// Clean up interval when page is unloaded
|
|
window.addEventListener('beforeunload', function() {
|
|
if (statusUpdateInterval) {
|
|
clearInterval(statusUpdateInterval);
|
|
}
|
|
});
|
|
|
|
// Contacts Management Functions
|
|
async function fetchContacts() {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/contacts`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch contacts');
|
|
}
|
|
|
|
const contacts = await response.json();
|
|
const contactsList = document.getElementById('contactsTableBody');
|
|
contactsList.innerHTML = '';
|
|
|
|
contacts.forEach(contact => {
|
|
const row = document.createElement('tr');
|
|
|
|
// Role badge color mapping
|
|
const roleBadgeClass = {
|
|
'admin': 'bg-danger',
|
|
'manager': 'bg-warning',
|
|
'user': 'bg-info'
|
|
}[contact.role] || 'bg-secondary';
|
|
|
|
// Status badge color mapping
|
|
const statusBadgeClass = contact.is_active ? 'bg-success' : 'bg-secondary';
|
|
|
|
// Format phone number for tel: link (remove any non-digit characters)
|
|
const phoneNumber = contact.phone ? contact.phone.replace(/\D/g, '') : '';
|
|
|
|
row.innerHTML = `
|
|
<td>${contact.username} ${contact.last_name}</td>
|
|
<td>
|
|
<a href="mailto:${contact.email}" class="text-decoration-none">
|
|
<span class="badge bg-primary">
|
|
<i class="fas fa-envelope me-1"></i>${contact.email}
|
|
</span>
|
|
</a>
|
|
</td>
|
|
<td>
|
|
${contact.phone ? `
|
|
<a href="tel:${phoneNumber}" class="text-decoration-none">
|
|
<span class="badge bg-secondary">
|
|
<i class="fas fa-phone me-1"></i>${contact.phone}
|
|
</span>
|
|
</a>
|
|
` : '-'}
|
|
</td>
|
|
<td>${contact.company || '-'}</td>
|
|
<td>${contact.position || '-'}</td>
|
|
<td><span class="badge ${roleBadgeClass}">${contact.role.charAt(0).toUpperCase() + contact.role.slice(1)}</span></td>
|
|
<td><span class="badge ${statusBadgeClass}">${contact.is_active ? 'Active' : 'Inactive'}</span></td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editContact(${contact.id})">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteContact(${contact.id})">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
`;
|
|
contactsList.appendChild(row);
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching contacts:', error);
|
|
showToast('Error loading contacts', 'error');
|
|
}
|
|
}
|
|
|
|
async function addContact(event) {
|
|
event.preventDefault();
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const formData = new FormData(event.target);
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/contacts`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
},
|
|
body: JSON.stringify({
|
|
username: formData.get('name'),
|
|
email: formData.get('email'),
|
|
phone: formData.get('phone'),
|
|
company: formData.get('company'),
|
|
position: formData.get('position'),
|
|
role: formData.get('role'),
|
|
is_active: formData.get('status') === 'active'
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to add contact');
|
|
}
|
|
|
|
// Close modal and refresh list
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('addContactModal'));
|
|
modal.hide();
|
|
fetchContacts();
|
|
|
|
// Show success message
|
|
showToast('Contact added successfully', 'success');
|
|
} catch (error) {
|
|
console.error('Error adding contact:', error);
|
|
showToast('Error adding contact', 'error');
|
|
}
|
|
}
|
|
|
|
async function editContact(id) {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/contacts/${id}`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch contact details');
|
|
}
|
|
|
|
const contact = await response.json();
|
|
|
|
// Populate form
|
|
document.getElementById('edit-contact-id').value = contact.id;
|
|
document.getElementById('edit-name').value = contact.username;
|
|
document.getElementById('edit-email').value = contact.email || '';
|
|
document.getElementById('edit-phone').value = contact.phone || '';
|
|
document.getElementById('edit-company').value = contact.company || '';
|
|
document.getElementById('edit-position').value = contact.position || '';
|
|
document.getElementById('edit-role').value = contact.role;
|
|
document.getElementById('edit-status').value = contact.is_active ? 'active' : 'inactive';
|
|
|
|
// Show modal
|
|
const modal = new bootstrap.Modal(document.getElementById('editContactModal'));
|
|
modal.show();
|
|
} catch (error) {
|
|
console.error('Error fetching contact details:', error);
|
|
showToast('Error loading contact details', 'error');
|
|
}
|
|
}
|
|
|
|
async function updateContact(event) {
|
|
event.preventDefault();
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const formData = new FormData(event.target);
|
|
const id = formData.get('id');
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/contacts/${id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
},
|
|
body: JSON.stringify({
|
|
username: formData.get('name'),
|
|
email: formData.get('email'),
|
|
phone: formData.get('phone'),
|
|
company: formData.get('company'),
|
|
position: formData.get('position'),
|
|
role: formData.get('role'),
|
|
is_active: formData.get('status') === 'active'
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update contact');
|
|
}
|
|
|
|
// Close modal and refresh list
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('editContactModal'));
|
|
modal.hide();
|
|
fetchContacts();
|
|
|
|
// Show success message
|
|
showToast('Contact updated successfully', 'success');
|
|
} catch (error) {
|
|
console.error('Error updating contact:', error);
|
|
showToast('Error updating contact', 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteContact(id) {
|
|
// Store the contact ID for the delete confirmation
|
|
const deleteModal = new bootstrap.Modal(document.getElementById('deleteContactModal'));
|
|
document.getElementById('confirmDelete').onclick = async () => {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/contacts/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete contact');
|
|
}
|
|
|
|
// Close modal and refresh list
|
|
deleteModal.hide();
|
|
fetchContacts();
|
|
|
|
// Show success message
|
|
showToast('Contact deleted successfully', 'success');
|
|
} catch (error) {
|
|
console.error('Error deleting contact:', error);
|
|
showToast('Error deleting contact', 'error');
|
|
}
|
|
};
|
|
|
|
// Show the delete confirmation modal
|
|
deleteModal.show();
|
|
}
|
|
|
|
// Helper function to show toast messages
|
|
function showToast(title, message, type = 'info') {
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast align-items-center text-white bg-${type} border-0`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.setAttribute('aria-live', 'assertive');
|
|
toast.setAttribute('aria-atomic', 'true');
|
|
|
|
toast.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
<strong>${title}</strong><br>
|
|
${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
`;
|
|
|
|
const toastContainer = document.getElementById('toastContainer') || (() => {
|
|
const container = document.createElement('div');
|
|
container.id = 'toastContainer';
|
|
container.className = 'toast-container position-fixed bottom-0 end-0 p-3';
|
|
document.body.appendChild(container);
|
|
return container;
|
|
})();
|
|
|
|
toastContainer.appendChild(toast);
|
|
const bsToast = new bootstrap.Toast(toast);
|
|
bsToast.show();
|
|
|
|
toast.addEventListener('hidden.bs.toast', () => {
|
|
toast.remove();
|
|
});
|
|
}
|
|
|
|
// Event Listeners
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// ... existing event listeners ...
|
|
|
|
// Fetch contacts when contacts tab is shown
|
|
document.getElementById('contacts-tab').addEventListener('shown.bs.tab', fetchContacts);
|
|
|
|
// Fetch logs when logs tab is shown
|
|
document.getElementById('logs-tab').addEventListener('shown.bs.tab', fetchLogs);
|
|
|
|
// Fetch mails when mails tab is shown
|
|
document.getElementById('mails-tab').addEventListener('shown.bs.tab', fetchMails);
|
|
|
|
// Debug the company name label
|
|
const label = document.getElementById('company-name-label');
|
|
if (label) {
|
|
console.log('Company name label found:', label);
|
|
|
|
// Create a MutationObserver to watch for changes
|
|
const observer = new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(mutation) {
|
|
console.log('Label changed:', mutation);
|
|
console.log('Old value:', mutation.oldValue);
|
|
console.log('New value:', label.textContent);
|
|
});
|
|
});
|
|
|
|
// Start observing
|
|
observer.observe(label, {
|
|
attributes: true,
|
|
childList: true,
|
|
characterData: true,
|
|
subtree: true
|
|
});
|
|
} else {
|
|
console.log('Company name label not found');
|
|
}
|
|
});
|
|
|
|
// Function to fetch logs
|
|
async function fetchLogs() {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/logs`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch logs');
|
|
}
|
|
|
|
const data = await response.json();
|
|
const logsTableBody = document.getElementById('logsTableBody');
|
|
logsTableBody.innerHTML = '';
|
|
|
|
// Fetch all users to map IDs to names
|
|
const usersResponse = await fetch(`{{ instance.main_url }}/api/admin/contacts`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!usersResponse.ok) {
|
|
throw new Error('Failed to fetch users');
|
|
}
|
|
|
|
const usersData = await usersResponse.json();
|
|
const userMap = new Map(usersData.map(user => [user.id, `${user.username} ${user.last_name}`]));
|
|
|
|
data.events.forEach(event => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${new Date(event.timestamp).toLocaleString()}</td>
|
|
<td>${event.event_type || '-'}</td>
|
|
<td>${event.user_id ? userMap.get(event.user_id) || `User ${event.user_id}` : '-'}</td>
|
|
<td>${event.ip_address || '-'}</td>
|
|
`;
|
|
logsTableBody.appendChild(row);
|
|
});
|
|
|
|
// Update pagination
|
|
updateLogsPagination(data.current_page, data.pages);
|
|
} catch (error) {
|
|
console.error('Error fetching logs:', error);
|
|
showToast('Error loading logs', 'error');
|
|
}
|
|
}
|
|
|
|
// Function to fetch mails
|
|
async function fetchMails() {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/mail-logs`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch mail logs');
|
|
}
|
|
|
|
const data = await response.json();
|
|
const mailsTableBody = document.querySelector('#mails .table tbody');
|
|
mailsTableBody.innerHTML = '';
|
|
|
|
data.mails.forEach(mail => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${new Date(mail.created_at).toLocaleString()}</td>
|
|
<td>${mail.recipient}</td>
|
|
<td>${mail.subject}</td>
|
|
<td>${mail.sent_at ? new Date(mail.sent_at).toLocaleString() : '-'}</td>
|
|
`;
|
|
mailsTableBody.appendChild(row);
|
|
});
|
|
|
|
// Update pagination
|
|
updateMailsPagination(data.current_page, data.pages);
|
|
} catch (error) {
|
|
console.error('Error fetching mail logs:', error);
|
|
showToast('Error loading mail logs', 'error');
|
|
}
|
|
}
|
|
|
|
// Helper function to get mail status badge class
|
|
function getMailStatusBadgeClass(status) {
|
|
const classes = {
|
|
'pending': 'warning',
|
|
'sent': 'success',
|
|
'failed': 'danger'
|
|
};
|
|
return classes[status] || 'secondary';
|
|
}
|
|
|
|
// Function to update logs pagination
|
|
function updateLogsPagination(currentPage, totalPages) {
|
|
const pagination = document.getElementById('logsPagination');
|
|
if (!pagination) return;
|
|
|
|
pagination.innerHTML = '';
|
|
|
|
// Previous button
|
|
const prevLi = document.createElement('li');
|
|
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
|
|
prevLi.innerHTML = `
|
|
<button class="page-link" onclick="changeLogsPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>
|
|
<i class="fas fa-chevron-left"></i>
|
|
</button>
|
|
`;
|
|
pagination.appendChild(prevLi);
|
|
|
|
// Page numbers
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
const li = document.createElement('li');
|
|
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
|
|
li.innerHTML = `
|
|
<button class="page-link" onclick="changeLogsPage(${i})">${i}</button>
|
|
`;
|
|
pagination.appendChild(li);
|
|
}
|
|
|
|
// Next button
|
|
const nextLi = document.createElement('li');
|
|
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
|
|
nextLi.innerHTML = `
|
|
<button class="page-link" onclick="changeLogsPage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>
|
|
<i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
`;
|
|
pagination.appendChild(nextLi);
|
|
}
|
|
|
|
// Function to update mails pagination
|
|
function updateMailsPagination(currentPage, totalPages) {
|
|
const pagination = document.querySelector('#mails .pagination');
|
|
if (!pagination) return;
|
|
|
|
pagination.innerHTML = '';
|
|
|
|
// Previous button
|
|
const prevLi = document.createElement('li');
|
|
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
|
|
prevLi.innerHTML = `
|
|
<button class="page-link" onclick="changeMailsPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>
|
|
<i class="fas fa-chevron-left"></i>
|
|
</button>
|
|
`;
|
|
pagination.appendChild(prevLi);
|
|
|
|
// Page numbers
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
const li = document.createElement('li');
|
|
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
|
|
li.innerHTML = `
|
|
<button class="page-link" onclick="changeMailsPage(${i})">${i}</button>
|
|
`;
|
|
pagination.appendChild(li);
|
|
}
|
|
|
|
// Next button
|
|
const nextLi = document.createElement('li');
|
|
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
|
|
nextLi.innerHTML = `
|
|
<button class="page-link" onclick="changeMailsPage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>
|
|
<i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
`;
|
|
pagination.appendChild(nextLi);
|
|
}
|
|
|
|
// Function to change logs page
|
|
async function changeLogsPage(page) {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/logs?page=${page}`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch logs');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Fetch all users to map IDs to names
|
|
const usersResponse = await fetch(`{{ instance.main_url }}/api/admin/contacts`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!usersResponse.ok) {
|
|
throw new Error('Failed to fetch users');
|
|
}
|
|
|
|
const usersData = await usersResponse.json();
|
|
const userMap = new Map(usersData.map(user => [user.id, `${user.username} ${user.last_name}`]));
|
|
|
|
const logsTableBody = document.getElementById('logsTableBody');
|
|
logsTableBody.innerHTML = '';
|
|
|
|
data.events.forEach(event => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${new Date(event.timestamp).toLocaleString()}</td>
|
|
<td>${event.event_type || '-'}</td>
|
|
<td>${event.user_id ? userMap.get(event.user_id) || `User ${event.user_id}` : '-'}</td>
|
|
<td>${event.ip_address || '-'}</td>
|
|
`;
|
|
logsTableBody.appendChild(row);
|
|
});
|
|
|
|
// Update pagination
|
|
updateLogsPagination(data.current_page, data.pages);
|
|
} catch (error) {
|
|
console.error('Error changing logs page:', error);
|
|
showToast('Error loading logs', 'error');
|
|
}
|
|
}
|
|
|
|
// Function to change mails page
|
|
async function changeMailsPage(page) {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/mail-logs?page=${page}`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch mail logs');
|
|
}
|
|
|
|
const data = await response.json();
|
|
const mailsTableBody = document.querySelector('#mails .table tbody');
|
|
mailsTableBody.innerHTML = '';
|
|
|
|
data.mails.forEach(mail => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${new Date(mail.created_at).toLocaleString()}</td>
|
|
<td>${mail.recipient}</td>
|
|
<td>${mail.subject}</td>
|
|
<td>${mail.sent_at ? new Date(mail.sent_at).toLocaleString() : '-'}</td>
|
|
`;
|
|
mailsTableBody.appendChild(row);
|
|
});
|
|
|
|
// Update pagination
|
|
updateMailsPagination(data.current_page, data.pages);
|
|
} catch (error) {
|
|
console.error('Error changing mail logs page:', error);
|
|
showToast('Error loading mail logs', 'error');
|
|
}
|
|
}
|
|
|
|
// Function to view mail details
|
|
async function viewMailDetails(mailId) {
|
|
try {
|
|
// First get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error('Failed to get management token');
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) {
|
|
throw new Error('No token received');
|
|
}
|
|
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/mail-logs/${mailId}`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch mail details');
|
|
}
|
|
|
|
const mail = await response.json();
|
|
|
|
// Update modal content
|
|
document.getElementById('modalRecipient').textContent = mail.recipient;
|
|
document.getElementById('modalStatus').innerHTML = `
|
|
<span class="badge ${getMailStatusBadgeClass(mail.status)}">${mail.status}</span>
|
|
`;
|
|
document.getElementById('modalTemplate').textContent = mail.template_id ? mail.template_id : '-';
|
|
document.getElementById('modalCreatedAt').textContent = new Date(mail.created_at).toLocaleString();
|
|
document.getElementById('modalSentAt').textContent = mail.sent_at ? new Date(mail.sent_at).toLocaleString() : '-';
|
|
document.getElementById('modalSubject').textContent = mail.subject;
|
|
document.getElementById('modalBody').innerHTML = mail.body;
|
|
|
|
// Show modal
|
|
const modal = new bootstrap.Modal(document.getElementById('mailDetailsModal'));
|
|
modal.show();
|
|
} catch (error) {
|
|
console.error('Error viewing mail details:', error);
|
|
showToast('Error loading mail details', 'error');
|
|
}
|
|
}
|
|
|
|
// Function to view log details
|
|
function viewLogDetails(event) {
|
|
const modal = new bootstrap.Modal(document.getElementById('logDetailsModal'));
|
|
const content = document.getElementById('logDetailsContent');
|
|
|
|
// Format the details in a readable way
|
|
let details = event.details;
|
|
let formattedDetails = 'No details available';
|
|
|
|
if (details) {
|
|
if (typeof details === 'string') {
|
|
formattedDetails = details.replace(/\n/g, '<br>');
|
|
} else if (typeof details === 'object') {
|
|
formattedDetails = JSON.stringify(details, null, 2).replace(/\n/g, '<br>');
|
|
} else {
|
|
formattedDetails = String(details);
|
|
}
|
|
}
|
|
|
|
content.innerHTML = `
|
|
<div class="mb-3">
|
|
<strong>Timestamp:</strong> ${new Date(event.timestamp).toLocaleString()}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Event Type:</strong> ${event.event_type || '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>User:</strong> ${event.user_id ? userMap.get(event.user_id) || `User ${event.user_id}` : '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>IP Address:</strong> ${event.ip_address || '-'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Details:</strong>
|
|
<div class="mt-2 p-3 bg-light rounded">
|
|
${formattedDetails}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
modal.show();
|
|
}
|
|
|
|
// Add event listener for clear filters button
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// ... existing event listeners ...
|
|
|
|
const clearLogFiltersBtn = document.getElementById('clearLogFiltersBtn');
|
|
if (clearLogFiltersBtn) {
|
|
clearLogFiltersBtn.addEventListener('click', function() {
|
|
document.getElementById('logCategory').value = '';
|
|
document.getElementById('dateRangeFilter').value = '7d';
|
|
fetchLogs();
|
|
});
|
|
}
|
|
});
|
|
|
|
// Function to fetch version and payment plan info
|
|
async function fetchInstanceVersionAndPlan() {
|
|
const versionEl = document.getElementById('instance-version-value');
|
|
const planEl = document.getElementById('instance-payment-plan-value');
|
|
versionEl.textContent = 'Loading...';
|
|
planEl.textContent = 'Loading...';
|
|
try {
|
|
// Get JWT token
|
|
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': '{{ instance.connection_token }}',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
if (!tokenResponse.ok) throw new Error('Failed to get management token');
|
|
const tokenData = await tokenResponse.json();
|
|
if (!tokenData.token) throw new Error('No token received');
|
|
// Fetch version info
|
|
const response = await fetch(`{{ instance.main_url }}/api/admin/version-info`, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${tokenData.token}`
|
|
}
|
|
});
|
|
if (!response.ok) throw new Error('Failed to fetch version info');
|
|
const data = await response.json();
|
|
versionEl.textContent = data.app_version || 'Unknown';
|
|
planEl.textContent = data.pricing_tier_name || 'Unknown';
|
|
} catch (error) {
|
|
versionEl.textContent = 'Error';
|
|
planEl.textContent = 'Error';
|
|
console.error('Error fetching version/plan info:', error);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// ... existing code ...
|
|
fetchInstanceVersionAndPlan();
|
|
});
|
|
</script>
|
|
{% endblock %} |