4c50c0aae6
- Implemented `google_drive_callback.html` for processing Google Drive authorization, including UI for success and error states. - Added JavaScript functionality for exchanging authorization codes, saving settings, and handling folder selection. - Created `google_drive_callback_error.html` to display error messages during the authorization process.
847 lines
36 KiB
HTML
847 lines
36 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Google Drive Setup{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mx-auto px-4 py-8">
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold mb-2">Google Drive Integration Setup</h1>
|
|
<p class="text-gray-600 mb-4">
|
|
Configure the Google Drive integration for DocuElevate using our setup wizard.
|
|
</p>
|
|
|
|
<div class="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 my-4" role="alert">
|
|
<p class="font-bold">Current Status:</p>
|
|
<p>Google Drive integration is
|
|
{% if is_configured %}
|
|
<span class="text-green-700 font-semibold">configured</span>.
|
|
{% else %}
|
|
<span class="text-red-700 font-semibold">not configured</span>.
|
|
{% endif %}
|
|
</p>
|
|
{% if use_oauth %}
|
|
<p class="mt-2"><strong>Authentication Type:</strong> OAuth Flow (User Account)</p>
|
|
{% else %}
|
|
<p class="mt-2"><strong>Authentication Type:</strong> Service Account (Server-to-Server)</p>
|
|
{% endif %}
|
|
{% if folder_id %}
|
|
<p class="mt-2"><strong>Target folder ID:</strong> {{ folder_id }}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 my-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-yellow-700">
|
|
<strong>Note:</strong> There are two ways to authenticate with Google Drive. This wizard will guide you through OAuth setup, which is recommended for most users. For service account setup, please refer to the documentation.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<h2 class="text-2xl font-semibold">Authentication Method</h2>
|
|
|
|
{% if is_configured %}
|
|
<span class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
|
Configured
|
|
</span>
|
|
{% else %}
|
|
<span class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
|
Not Configured
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="flex space-x-4 mb-6">
|
|
<button id="oauth-tab-btn" class="px-4 py-2 rounded-md {{ 'text-white bg-blue-600' if use_oauth else 'text-gray-700 bg-gray-200' }}">
|
|
OAuth User Account
|
|
</button>
|
|
<button id="sa-tab-btn" class="px-4 py-2 rounded-md {{ 'text-white bg-blue-600' if not use_oauth else 'text-gray-700 bg-gray-200' }}">
|
|
Service Account
|
|
</button>
|
|
</div>
|
|
|
|
<!-- OAuth Tab Content -->
|
|
<div id="oauth-tab" class="{{ 'block' if use_oauth else 'hidden' }}">
|
|
<div class="mb-6">
|
|
<h3 class="text-xl font-medium mb-4">Step 1: Create a Google Cloud Project</h3>
|
|
<ol class="list-decimal ml-6 space-y-3">
|
|
<li>Go to the <a href="https://console.cloud.google.com/" target="_blank" class="text-blue-600 hover:underline">Google Cloud Console</a></li>
|
|
<li>Create a new project or select an existing one</li>
|
|
<li>Make note of your project ID</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<h3 class="text-xl font-medium mb-4">Step 2: Enable the Google Drive API</h3>
|
|
<ol class="list-decimal ml-6 space-y-3">
|
|
<li>In your project, go to "APIs & Services" > "Library"</li>
|
|
<li>Search for "Google Drive API" and select it</li>
|
|
<li>Click the "Enable" button</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<h3 class="text-xl font-medium mb-4">Step 3: Configure OAuth Consent Screen</h3>
|
|
<ol class="list-decimal ml-6 space-y-3">
|
|
<li>In "APIs & Services", go to "OAuth consent screen"</li>
|
|
<li>Select "External" user type (or "Internal" if this is for an organization)</li>
|
|
<li>Fill out the required application information:
|
|
<ul class="list-disc ml-6 mt-1">
|
|
<li>App name: "DocuElevate" (or your preferred name)</li>
|
|
<li>User support email: Your email</li>
|
|
<li>Application homepage link: Your application URL</li>
|
|
<li>Authorized domains: Your domain</li>
|
|
<li>Developer contact information: Your email</li>
|
|
</ul>
|
|
</li>
|
|
<li>Click "Save and Continue"</li>
|
|
<li>On the Scopes screen, click "Add or Remove Scopes" and add:
|
|
<ul class="list-disc ml-6 mt-1">
|
|
<li><code class="bg-gray-100 p-1">https://www.googleapis.com/auth/drive.file</code></li>
|
|
</ul>
|
|
</li>
|
|
<li>Click "Save and Continue" through the rest of the setup</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<h3 class="text-xl font-medium mb-4">Step 4: Create OAuth Credentials</h3>
|
|
<ol class="list-decimal ml-6 space-y-3">
|
|
<li>Go to "APIs & Services" > "Credentials"</li>
|
|
<li>Click "Create Credentials" > "OAuth client ID"</li>
|
|
<li>Select "Web application" for Application type</li>
|
|
<li>Add a name (e.g., "DocuElevate Web Client")</li>
|
|
<li>Under "Authorized redirect URIs" add:
|
|
<div class="bg-gray-100 p-2 my-2 rounded text-sm">
|
|
{{ request.url.scheme }}://{{ request.url.netloc }}/google-drive-callback
|
|
</div>
|
|
</li>
|
|
<li>Click "Create"</li>
|
|
<li>Copy your Client ID and Client Secret</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<h3 class="text-xl font-medium mb-4">Step 5: Complete OAuth Setup</h3>
|
|
<p class="mb-4">Now enter your Client ID and Client Secret below, and we'll help you complete the OAuth flow. You can set the folder ID after authentication is complete.</p>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label for="client-id" class="block text-sm font-medium text-gray-700">Client ID</label>
|
|
<input type="text" id="client-id" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Enter your OAuth Client ID" value="{{ client_id_value }}">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="client-secret" class="block text-sm font-medium text-gray-700">Client Secret</label>
|
|
<input type="password" id="client-secret" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Enter your OAuth Client Secret" value="{{ client_secret_value }}">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="folder-id" class="block text-sm font-medium text-gray-700">Folder ID (Optional)</label>
|
|
<input type="text" id="folder-id" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Enter your Google Drive folder ID" value="{{ folder_id }}">
|
|
<p class="text-xs text-gray-500 mt-1">Optional: You can set this after authentication if you prefer</p>
|
|
</div>
|
|
|
|
<div>
|
|
<button id="select-folder-btn" class="mb-2 inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
|
<svg class="h-5 w-5 mr-2 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1H8a3 3 0 00-3 3v1.5a1.5 1.5 0 01-3 0V6z" clip-rule="evenodd" />
|
|
<path d="M6 12a2 2 0 012-2h8a2 2 0 012 2v2a2 2 0 01-2 2H2h2a2 2 0 002-2v-2z" />
|
|
</svg>
|
|
Select Folder with Picker
|
|
</button>
|
|
</div>
|
|
|
|
<div>
|
|
<button id="start-oauth-flow" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
Start Authentication Flow
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Service Account Tab Content -->
|
|
<div id="sa-tab" class="{{ 'block' if not use_oauth else 'hidden' }}">
|
|
<div class="mb-6">
|
|
<h3 class="text-xl font-medium mb-4">Service Account Configuration</h3>
|
|
|
|
<p class="mb-4">Service accounts allow for server-to-server authentication without user involvement. This method is useful for background tasks.</p>
|
|
|
|
<div class="bg-blue-50 border-l-4 border-blue-400 p-4 mb-6">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-blue-700">
|
|
Service account configuration requires creating a service account in Google Cloud Console and uploading credentials. For detailed instructions, refer to the documentation.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label for="sa-folder-id" class="block text-sm font-medium text-gray-700">Folder ID</label>
|
|
<input type="text" id="sa-folder-id" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Enter your Google Drive folder ID" value="{{ folder_id }}">
|
|
<p class="text-xs text-gray-500 mt-1">This is the ID from the Google Drive folder URL where files will be saved</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="sa-credentials" class="block text-sm font-medium text-gray-700">Service Account Credentials</label>
|
|
<div class="mt-1">
|
|
<p class="text-xs text-gray-500 mb-2">
|
|
{% if has_credentials_json %}
|
|
Service account credentials are configured in your .env file.
|
|
{% else %}
|
|
No service account credentials found. You need to manually add them to your .env file.
|
|
{% endif %}
|
|
</p>
|
|
<a href="/docs/GoogleDriveServiceAccount.md" target="_blank" class="text-blue-600 hover:underline text-sm">
|
|
View Service Account Setup Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<button id="save-sa-settings" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
Save Service Account Settings
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Token validation and status -->
|
|
<div id="token-status" class="mt-6 bg-white shadow-md rounded-lg p-6 {{ 'hidden' if not is_configured else '' }}">
|
|
<h2 class="text-xl font-semibold mb-4">Connection Status</h2>
|
|
|
|
<div class="rounded-md {{ 'bg-green-50' if is_configured else 'bg-yellow-50' }} p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
{% if is_configured %}
|
|
<svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
</svg>
|
|
{% else %}
|
|
<svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
</svg>
|
|
{% endif %}
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium {{ 'text-green-800' if is_configured else 'text-yellow-800' }}">
|
|
{% if is_configured %}
|
|
Google Drive integration is properly configured! Your credentials are valid.
|
|
{% else %}
|
|
Google Drive integration is not completely configured or credentials may be invalid.
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 flex space-x-3">
|
|
<button id="test-connection" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
|
Test Connection
|
|
</button>
|
|
<button id="refresh-token-btn" class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
|
Refresh Token
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Configuration for Worker Nodes section -->
|
|
<div id="oauth-env-section" class="mt-6 p-4 bg-gray-100 rounded-md {{ 'hidden' if not use_oauth else '' }}">
|
|
<h3 class="font-medium text-lg mb-2">OAuth Configuration for Worker Nodes</h3>
|
|
<p class="text-sm text-gray-600 mb-3">
|
|
Copy these environment variables to configure all worker nodes:
|
|
</p>
|
|
|
|
<div class="relative">
|
|
<pre id="oauth-env-vars" class="bg-gray-800 text-green-400 text-sm p-3 rounded overflow-x-auto"><code>GOOGLE_DRIVE_USE_OAUTH=true
|
|
GOOGLE_DRIVE_CLIENT_ID={{ client_id_value }}
|
|
GOOGLE_DRIVE_CLIENT_SECRET={{ client_secret_value|default('YOUR_CLIENT_SECRET', true) }}
|
|
GOOGLE_DRIVE_REFRESH_TOKEN={{ refresh_token_value|default('YOUR_REFRESH_TOKEN', true) }}
|
|
GOOGLE_DRIVE_FOLDER_ID={{ folder_id|default('YOUR_FOLDER_ID', true) }}</code></pre>
|
|
|
|
<button id="copy-oauth-env-vars" class="absolute top-2 right-2 bg-gray-700 hover:bg-gray-600 text-white text-xs py-1 px-2 rounded">
|
|
Copy
|
|
</button>
|
|
</div>
|
|
|
|
<p class="text-xs text-gray-500 mt-2">
|
|
Add these variables to your .env file or environment configuration.
|
|
</p>
|
|
</div>
|
|
|
|
<div id="sa-env-section" class="mt-6 p-4 bg-gray-100 rounded-md {{ 'hidden' if use_oauth else '' }}">
|
|
<h3 class="font-medium text-lg mb-2">Service Account Configuration for Worker Nodes</h3>
|
|
<p class="text-sm text-gray-600 mb-3">
|
|
Copy these environment variables to configure all worker nodes:
|
|
</p>
|
|
|
|
<div class="relative">
|
|
<pre id="sa-env-vars" class="bg-gray-800 text-green-400 text-sm p-3 rounded overflow-x-auto"><code>GOOGLE_DRIVE_USE_OAUTH=false
|
|
GOOGLE_DRIVE_FOLDER_ID={{ folder_id|default('YOUR_FOLDER_ID', true) }}
|
|
# Make sure to add your GOOGLE_DRIVE_CREDENTIALS_JSON to your .env file</code></pre>
|
|
|
|
<button id="copy-sa-env-vars" class="absolute top-2 right-2 bg-gray-700 hover:bg-gray-600 text-white text-xs py-1 px-2 rounded">
|
|
Copy
|
|
</button>
|
|
</div>
|
|
|
|
<p class="text-xs text-gray-500 mt-2">
|
|
Add these variables to your .env file along with your service account credentials JSON.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow-md rounded-lg p-6 mt-8 mb-8">
|
|
<div class="flex items-start">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h3 class="text-lg font-medium">Need More Information?</h3>
|
|
<p class="mt-1 text-sm text-gray-600">
|
|
For detailed instructions and troubleshooting, refer to the
|
|
<a href="/docs/GoogleDriveSetup.md" target="_blank" class="text-blue-600 hover:underline">Google Drive Setup Documentation</a>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-8">
|
|
<a href="/status" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
Back to Status
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Result Modal -->
|
|
<div id="resultModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden overflow-y-auto h-full w-full z-50" aria-modal="true" role="dialog">
|
|
<div class="relative top-20 mx-auto p-5 border w-11/12 md:w-1/2 lg:w-1/3 shadow-lg rounded-md bg-white">
|
|
<div class="mt-3 text-center">
|
|
<div id="modalIcon" class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-4">
|
|
<!-- Icon will be injected by JS -->
|
|
</div>
|
|
<h3 id="modalTitle" class="text-lg leading-6 font-medium text-gray-900">Success</h3>
|
|
<div class="mt-2 px-7 py-3">
|
|
<p id="modalMessage" class="text-sm text-gray-500">
|
|
Operation completed successfully.
|
|
</p>
|
|
</div>
|
|
<div class="items-center px-4 py-3">
|
|
<button id="modalClose" class="px-4 py-2 bg-blue-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-300">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Tab elements
|
|
const oauthTabBtn = document.getElementById('oauth-tab-btn');
|
|
const saTabBtn = document.getElementById('sa-tab-btn');
|
|
const oauthTab = document.getElementById('oauth-tab');
|
|
const saTab = document.getElementById('sa-tab');
|
|
|
|
// Form elements
|
|
const clientIdInput = document.getElementById('client-id');
|
|
const clientSecretInput = document.getElementById('client-secret');
|
|
const folderIdInput = document.getElementById('folder-id');
|
|
const saFolderIdInput = document.getElementById('sa-folder-id');
|
|
const startOauthFlowBtn = document.getElementById('start-oauth-flow');
|
|
const saveSaSettingsBtn = document.getElementById('save-sa-settings');
|
|
const selectFolderBtn = document.getElementById('select-folder-btn');
|
|
|
|
// Status and test elements
|
|
const tokenStatus = document.getElementById('token-status');
|
|
const testConnectionBtn = document.getElementById('test-connection');
|
|
const refreshTokenBtn = document.getElementById('refresh-token-btn');
|
|
|
|
// Environment sections
|
|
const oauthEnvSection = document.getElementById('oauth-env-section');
|
|
const saEnvSection = document.getElementById('sa-env-section');
|
|
|
|
// Modal elements
|
|
const resultModal = document.getElementById('resultModal');
|
|
const modalTitle = document.getElementById('modalTitle');
|
|
const modalMessage = document.getElementById('modalMessage');
|
|
const modalIcon = document.getElementById('modalIcon');
|
|
const modalClose = document.getElementById('modalClose');
|
|
|
|
// Google Picker variables
|
|
let pickerApiLoaded = false;
|
|
let pickerOAuthToken = null;
|
|
|
|
// Google Picker API functions
|
|
function loadPickerApi() {
|
|
gapi.load('picker', {
|
|
'callback': function() {
|
|
pickerApiLoaded = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Load the Google API Loader script if select folder button exists
|
|
if (selectFolderBtn) {
|
|
const script = document.createElement('script');
|
|
script.src = 'https://apis.google.com/js/api.js';
|
|
script.onload = function() {
|
|
loadPickerApi();
|
|
};
|
|
document.body.appendChild(script);
|
|
|
|
// Load the GSI Client for OAuth
|
|
const gsiScript = document.createElement('script');
|
|
gsiScript.src = 'https://accounts.google.com/gsi/client';
|
|
document.body.appendChild(gsiScript);
|
|
}
|
|
|
|
// Function to create and show the Google Picker
|
|
function createPicker() {
|
|
const clientId = clientIdInput.value.trim();
|
|
if (!clientId) {
|
|
showModal('error', 'Missing Client ID', 'Please enter your Google OAuth Client ID first.');
|
|
return;
|
|
}
|
|
|
|
// Check if we already have an active access token
|
|
fetch('/api/google-drive/get-token-info')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success' && data.access_token) {
|
|
// If we have a valid access token, use it directly
|
|
showPicker(clientId, data.access_token);
|
|
} else {
|
|
// If no valid token exists, inform the user they need to authenticate first
|
|
showModal('error', 'Authentication Required',
|
|
'You need to complete OAuth authentication before selecting a folder. ' +
|
|
'Please click "Start Authentication Flow" first and then select a folder after authentication.');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error checking token:', error);
|
|
showModal('error', 'Authentication Required',
|
|
'Unable to verify authentication status. Please complete the OAuth flow first.');
|
|
});
|
|
}
|
|
|
|
function showPicker(clientId, oauthToken) {
|
|
if (!oauthToken) {
|
|
showModal('error', 'Authentication Required', 'No access token available. Please authenticate first.');
|
|
return;
|
|
}
|
|
|
|
// Use the folders view specifically
|
|
const folderView = new google.picker.DocsView(google.picker.ViewId.FOLDERS)
|
|
.setIncludeFolders(true)
|
|
.setSelectFolderEnabled(true)
|
|
.setMode(google.picker.DocsViewMode.LIST); // Use LIST mode which doesn't require broader permissions
|
|
|
|
const picker = new google.picker.PickerBuilder()
|
|
.addView(folderView)
|
|
.setOAuthToken(oauthToken)
|
|
.setOrigin(window.location.protocol + '//' + window.location.host)
|
|
.setTitle('Select a folder for DocuElevate')
|
|
.enableFeature(google.picker.Feature.NAV_HIDDEN) // Hide navigation
|
|
.disableFeature(google.picker.Feature.MULTISELECT_ENABLED) // Disable multi-select
|
|
.setSelectableMimeTypes('application/vnd.google-apps.folder') // Allow only folder selection
|
|
.setCallback(pickerCallback)
|
|
.build();
|
|
|
|
picker.setVisible(true);
|
|
}
|
|
|
|
function pickerCallback(data) {
|
|
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
|
|
const folder = data[google.picker.Response.DOCUMENTS][0];
|
|
const folderId = folder[google.picker.Document.ID];
|
|
const folderName = folder[google.picker.Document.NAME];
|
|
|
|
// Update the folder ID input
|
|
folderIdInput.value = folderId;
|
|
if (saFolderIdInput) {
|
|
saFolderIdInput.value = folderId;
|
|
}
|
|
|
|
// Automatically save the folder ID to ensure it's stored
|
|
saveFolderId(folderId);
|
|
|
|
// Show success message
|
|
showModal('success', 'Folder Selected', `You selected folder: "${folderName}" (ID: ${folderId}) and saved it to your configuration.`);
|
|
}
|
|
}
|
|
|
|
// New function to save folder ID after picker selection
|
|
function saveFolderId(folderId) {
|
|
// Check which tab is active to determine if we're using OAuth or Service Account
|
|
const isOauthActive = !oauthTab.classList.contains('hidden');
|
|
|
|
// Prepare form data
|
|
const formData = new FormData();
|
|
formData.append('folder_id', folderId);
|
|
formData.append('use_oauth', isOauthActive ? 'true' : 'false');
|
|
|
|
// If using OAuth, also include client credentials if available
|
|
if (isOauthActive) {
|
|
const clientId = clientIdInput.value.trim();
|
|
const clientSecret = clientSecretInput.value.trim();
|
|
|
|
if (clientId) formData.append('client_id', clientId);
|
|
if (clientSecret) formData.append('client_secret', clientSecret);
|
|
}
|
|
|
|
// Send the folder ID to be saved server-side
|
|
fetch('/api/google-drive/save-settings', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
return response.json().then(err => {
|
|
throw new Error(err.detail || 'Failed to save folder ID');
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status !== 'success') {
|
|
console.error('Error saving folder ID:', data.message);
|
|
} else {
|
|
console.log('Folder ID saved successfully');
|
|
// Make the token status visible if it was hidden
|
|
tokenStatus.classList.remove('hidden');
|
|
|
|
// Update environment variables display if they exist
|
|
updateEnvVarsDisplay();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error saving folder ID:', error);
|
|
});
|
|
}
|
|
|
|
// Function to update environment variables display
|
|
function updateEnvVarsDisplay() {
|
|
const folderId = folderIdInput.value || saFolderIdInput.value || 'YOUR_FOLDER_ID';
|
|
|
|
// Update OAuth env vars if the element exists
|
|
const oauthEnvVarsCode = document.querySelector('#oauth-env-vars code');
|
|
if (oauthEnvVarsCode) {
|
|
const clientId = clientIdInput.value || 'YOUR_CLIENT_ID';
|
|
const clientSecret = clientSecretInput.value ? 'YOUR_CLIENT_SECRET' : 'YOUR_CLIENT_SECRET';
|
|
|
|
oauthEnvVarsCode.textContent = `GOOGLE_DRIVE_USE_OAUTH=true
|
|
GOOGLE_DRIVE_CLIENT_ID=${clientId}
|
|
GOOGLE_DRIVE_CLIENT_SECRET=${clientSecret}
|
|
GOOGLE_DRIVE_REFRESH_TOKEN=YOUR_REFRESH_TOKEN
|
|
GOOGLE_DRIVE_FOLDER_ID=${folderId}`;
|
|
}
|
|
|
|
// Update Service Account env vars if the element exists
|
|
const saEnvVarsCode = document.querySelector('#sa-env-vars code');
|
|
if (saEnvVarsCode) {
|
|
saEnvVarsCode.textContent = `GOOGLE_DRIVE_USE_OAUTH=false
|
|
GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
|
# Make sure to add your GOOGLE_DRIVE_CREDENTIALS_JSON to your .env file`;
|
|
}
|
|
}
|
|
|
|
// Modal functions
|
|
function showModal(status, title, message) {
|
|
modalTitle.textContent = title;
|
|
modalMessage.textContent = message;
|
|
|
|
// Set the appropriate icon
|
|
if (status === 'success') {
|
|
modalIcon.innerHTML = `
|
|
<svg class="h-6 w-6 text-green-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
`;
|
|
modalIcon.className = 'mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-4';
|
|
} else {
|
|
modalIcon.innerHTML = `
|
|
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
`;
|
|
modalIcon.className = 'mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4';
|
|
}
|
|
|
|
resultModal.classList.remove('hidden');
|
|
}
|
|
|
|
function hideModal() {
|
|
resultModal.classList.add('hidden');
|
|
}
|
|
|
|
// Close modal when clicking the close button or outside
|
|
modalClose.addEventListener('click', hideModal);
|
|
resultModal.addEventListener('click', function(e) {
|
|
if (e.target === resultModal) {
|
|
hideModal();
|
|
}
|
|
});
|
|
|
|
// Attach Google Picker button event listener
|
|
if (selectFolderBtn) {
|
|
selectFolderBtn.addEventListener('click', createPicker);
|
|
}
|
|
|
|
// Tab switching
|
|
oauthTabBtn.addEventListener('click', function() {
|
|
oauthTabBtn.className = 'px-4 py-2 rounded-md text-white bg-blue-600';
|
|
saTabBtn.className = 'px-4 py-2 rounded-md text-gray-700 bg-gray-200';
|
|
oauthTab.classList.remove('hidden');
|
|
saTab.classList.add('hidden');
|
|
oauthEnvSection.classList.remove('hidden');
|
|
saEnvSection.classList.add('hidden');
|
|
});
|
|
|
|
saTabBtn.addEventListener('click', function() {
|
|
saTabBtn.className = 'px-4 py-2 rounded-md text-white bg-blue-600';
|
|
oauthTabBtn.className = 'px-4 py-2 rounded-md text-gray-700 bg-gray-200';
|
|
saTab.classList.remove('hidden');
|
|
oauthTab.classList.add('hidden');
|
|
saEnvSection.classList.remove('hidden');
|
|
oauthEnvSection.classList.add('hidden');
|
|
});
|
|
|
|
// Sync folder IDs between tabs
|
|
folderIdInput.addEventListener('input', function() {
|
|
if (saFolderIdInput) {
|
|
saFolderIdInput.value = folderIdInput.value;
|
|
}
|
|
});
|
|
|
|
if (saFolderIdInput) {
|
|
saFolderIdInput.addEventListener('input', function() {
|
|
folderIdInput.value = saFolderIdInput.value;
|
|
});
|
|
}
|
|
|
|
// Start OAuth flow button
|
|
if (startOauthFlowBtn) {
|
|
startOauthFlowBtn.addEventListener('click', function() {
|
|
const clientId = clientIdInput.value.trim();
|
|
const clientSecret = clientSecretInput.value.trim();
|
|
const folderId = folderIdInput.value.trim();
|
|
|
|
if (!clientId) {
|
|
showModal('error', 'Validation Error', 'Please enter your Client ID');
|
|
return;
|
|
}
|
|
|
|
if (!clientSecret) {
|
|
showModal('error', 'Validation Error', 'Please enter your Client Secret');
|
|
return;
|
|
}
|
|
|
|
// Don't require folder ID, make it optional
|
|
|
|
// Save values to session storage for use after redirect
|
|
sessionStorage.setItem('google_drive_client_id', clientId);
|
|
sessionStorage.setItem('google_drive_client_secret', clientSecret);
|
|
if (folderId) {
|
|
sessionStorage.setItem('google_drive_folder_id', folderId);
|
|
}
|
|
sessionStorage.setItem('google_drive_use_oauth', 'true');
|
|
|
|
// Create redirect URI
|
|
const redirectUri = `${window.location.origin}/google-drive-callback`;
|
|
|
|
// Redirect to auth start endpoint
|
|
window.location.href = `/google-drive-auth-start?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
});
|
|
}
|
|
|
|
// Save service account settings button
|
|
if (saveSaSettingsBtn) {
|
|
saveSaSettingsBtn.addEventListener('click', function() {
|
|
const folderId = saFolderIdInput.value.trim();
|
|
|
|
if (!folderId) {
|
|
showModal('error', 'Validation Error', 'Please enter your Google Drive Folder ID');
|
|
return;
|
|
}
|
|
|
|
// Prepare form data
|
|
const formData = new FormData();
|
|
formData.append('folder_id', folderId);
|
|
formData.append('use_oauth', 'false');
|
|
|
|
// Send update request
|
|
const originalText = saveSaSettingsBtn.textContent;
|
|
saveSaSettingsBtn.innerHTML = '<span class="animate-spin inline-block mr-2">⟳</span> Saving...';
|
|
saveSaSettingsBtn.disabled = true;
|
|
|
|
fetch('/api/google-drive/save-settings', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
return response.json().then(err => {
|
|
throw new Error(err.detail || 'Failed to save settings');
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
showModal('success', 'Settings Saved', 'Google Drive service account settings have been saved');
|
|
tokenStatus.classList.remove('hidden');
|
|
|
|
// Update environment variables display
|
|
const saEnvVarsCode = document.querySelector('#sa-env-vars code');
|
|
if (saEnvVarsCode) {
|
|
saEnvVarsCode.textContent = `GOOGLE_DRIVE_USE_OAUTH=false
|
|
GOOGLE_DRIVE_FOLDER_ID=${folderId}
|
|
# Make sure to add your GOOGLE_DRIVE_CREDENTIALS_JSON to your .env file`;
|
|
}
|
|
} else {
|
|
showModal('error', 'Error', data.message || 'Failed to save settings');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showModal('error', 'Error', error.message);
|
|
})
|
|
.finally(() => {
|
|
saveSaSettingsBtn.textContent = originalText;
|
|
saveSaSettingsBtn.disabled = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Test connection button
|
|
if (testConnectionBtn) {
|
|
testConnectionBtn.addEventListener('click', function() {
|
|
const originalText = testConnectionBtn.textContent;
|
|
testConnectionBtn.innerHTML = '<span class="animate-spin inline-block mr-2">⟳</span> Testing...';
|
|
testConnectionBtn.disabled = true;
|
|
|
|
fetch('/api/google-drive/test-token')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
showModal('success', 'Connection Test Successful', data.message);
|
|
} else {
|
|
if (data.needs_reauth) {
|
|
showModal('error', 'Authentication Required', 'Your token has expired or is invalid. Please reconfigure the connection.');
|
|
} else {
|
|
showModal('error', 'Connection Test Failed', data.message);
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showModal('error', 'Connection Error', 'Error testing connection: ' + error.message);
|
|
})
|
|
.finally(() => {
|
|
testConnectionBtn.textContent = originalText;
|
|
testConnectionBtn.disabled = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Refresh token button
|
|
if (refreshTokenBtn) {
|
|
refreshTokenBtn.addEventListener('click', function() {
|
|
showModal('info', 'Confirm', 'This will start a new OAuth flow to obtain a fresh token. Continue?');
|
|
modalClose.textContent = "Cancel";
|
|
|
|
// Add a confirm button
|
|
const confirmBtn = document.createElement('button');
|
|
confirmBtn.className = 'ml-2 px-4 py-2 bg-green-500 text-white text-base font-medium rounded-md shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-300';
|
|
confirmBtn.textContent = 'Continue';
|
|
confirmBtn.addEventListener('click', function() {
|
|
hideModal();
|
|
|
|
// Check if we have necessary info before starting flow
|
|
if (!clientIdInput.value.trim() || !clientSecretInput.value.trim()) {
|
|
showModal('error', 'Missing Information', 'Please enter your Client ID and Client Secret first');
|
|
return;
|
|
}
|
|
|
|
startOauthFlowBtn.click();
|
|
});
|
|
|
|
// Add to modal
|
|
modalClose.parentNode.appendChild(confirmBtn);
|
|
|
|
// Clean up modal when closed
|
|
const onModalClose = function() {
|
|
if (confirmBtn.parentNode) {
|
|
confirmBtn.parentNode.removeChild(confirmBtn);
|
|
}
|
|
modalClose.textContent = "Close";
|
|
resultModal.removeEventListener('hidden', onModalClose);
|
|
};
|
|
|
|
resultModal.addEventListener('hidden', onModalClose);
|
|
});
|
|
}
|
|
|
|
// Copy environment variables buttons
|
|
const copyOAuthEnvVarsBtn = document.getElementById('copy-oauth-env-vars');
|
|
const copySAEnvVarsBtn = document.getElementById('copy-sa-env-vars');
|
|
|
|
if (copyOAuthEnvVarsBtn) {
|
|
copyOAuthEnvVarsBtn.addEventListener('click', function() {
|
|
const envVarsText = document.getElementById('oauth-env-vars').textContent;
|
|
navigator.clipboard.writeText(envVarsText)
|
|
.then(() => {
|
|
copyOAuthEnvVarsBtn.textContent = 'Copied!';
|
|
copyOAuthEnvVarsBtn.classList.add('bg-green-700');
|
|
setTimeout(() => {
|
|
copyOAuthEnvVarsBtn.textContent = 'Copy';
|
|
copyOAuthEnvVarsBtn.classList.remove('bg-green-700');
|
|
}, 2000);
|
|
})
|
|
.catch(err => {
|
|
console.error('Failed to copy: ', err);
|
|
});
|
|
});
|
|
}
|
|
|
|
if (copySAEnvVarsBtn) {
|
|
copySAEnvVarsBtn.addEventListener('click', function() {
|
|
const envVarsText = document.getElementById('sa-env-vars').textContent;
|
|
navigator.clipboard.writeText(envVarsText)
|
|
.then(() => {
|
|
copySAEnvVarsBtn.textContent = 'Copied!';
|
|
copySAEnvVarsBtn.classList.add('bg-green-700');
|
|
setTimeout(() => {
|
|
copySAEnvVarsBtn.textContent = 'Copy';
|
|
copySAEnvVarsBtn.classList.remove('bg-green-700');
|
|
}, 2000);
|
|
})
|
|
.catch(err => {
|
|
console.error('Failed to copy: ', err);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|