version v2
This commit is contained in:
53
README.md
53
README.md
@@ -10,8 +10,9 @@ DocuPulse is a powerful document management system designed to streamline docume
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js (version 18 or higher)
|
||||
- npm or yarn
|
||||
- Python 3.11 or higher
|
||||
- PostgreSQL 13 or higher
|
||||
- Docker and Docker Compose (for containerized deployment)
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -23,18 +24,50 @@ cd docupulse
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
# or
|
||||
yarn install
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Start the development server:
|
||||
3. Set up environment variables:
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# Copy example environment file
|
||||
cp .env.example .env
|
||||
|
||||
# Set version information for local development
|
||||
python set_version.py
|
||||
```
|
||||
|
||||
4. Initialize the database:
|
||||
```bash
|
||||
flask db upgrade
|
||||
flask create-admin
|
||||
```
|
||||
|
||||
5. Start the development server:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
## Version Tracking
|
||||
|
||||
DocuPulse uses a database-only approach for version tracking:
|
||||
|
||||
- **Environment Variables**: Version information is passed via environment variables (`APP_VERSION`, `GIT_COMMIT`, `GIT_BRANCH`, `DEPLOYED_AT`)
|
||||
- **Database Storage**: Instance version information is stored in the `instances` table
|
||||
- **API Endpoint**: Version information is available via `/api/version`
|
||||
|
||||
### Setting Version Information
|
||||
|
||||
For local development:
|
||||
```bash
|
||||
python set_version.py
|
||||
```
|
||||
|
||||
For production deployments, set the following environment variables:
|
||||
- `APP_VERSION`: Application version/tag
|
||||
- `GIT_COMMIT`: Git commit hash
|
||||
- `GIT_BRANCH`: Git branch name
|
||||
- `DEPLOYED_AT`: Deployment timestamp
|
||||
|
||||
## Features
|
||||
|
||||
- Document upload and management
|
||||
@@ -42,6 +75,8 @@ yarn dev
|
||||
- Secure document storage
|
||||
- User authentication and authorization
|
||||
- Document version control
|
||||
- Multi-tenant instance management
|
||||
- RESTful API
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ services:
|
||||
- POSTGRES_PASSWORD=docupulse_${PORT:-10335}
|
||||
- POSTGRES_DB=docupulse_${PORT:-10335}
|
||||
- MASTER=${ISMASTER:-false}
|
||||
- APP_VERSION=${APP_VERSION:-unknown}
|
||||
- GIT_COMMIT=${GIT_COMMIT:-unknown}
|
||||
- GIT_BRANCH=${GIT_BRANCH:-unknown}
|
||||
- DEPLOYED_AT=${DEPLOYED_AT:-unknown}
|
||||
volumes:
|
||||
- docupulse_uploads:/app/uploads
|
||||
depends_on:
|
||||
|
||||
Binary file not shown.
@@ -761,48 +761,6 @@ def download_docker_compose():
|
||||
else:
|
||||
content = response.text
|
||||
|
||||
# Add version.txt creation to the docker-compose content
|
||||
if commit_hash:
|
||||
# Create version information with both tag and commit hash
|
||||
version_info = {
|
||||
'tag': latest_tag or 'unknown',
|
||||
'commit': commit_hash,
|
||||
'branch': data['branch'],
|
||||
'deployed_at': datetime.utcnow().isoformat()
|
||||
}
|
||||
version_json = json.dumps(version_info, indent=2)
|
||||
|
||||
# Add a command to create version.txt with the version information
|
||||
version_command = f'echo \'{version_json}\' > /app/version.txt'
|
||||
|
||||
# Find the web service and add the command
|
||||
if 'web:' in content:
|
||||
# Add the command to create version.txt before the main command
|
||||
lines = content.split('\n')
|
||||
new_lines = []
|
||||
in_web_service = False
|
||||
command_added = False
|
||||
|
||||
for line in lines:
|
||||
new_lines.append(line)
|
||||
|
||||
if line.strip() == 'web:':
|
||||
in_web_service = True
|
||||
elif in_web_service and line.strip().startswith('command:'):
|
||||
# Add the version.txt creation command before the main command
|
||||
new_lines.append(f' - sh -c "{version_command} && {line.split("command:")[1].strip()}"')
|
||||
command_added = True
|
||||
continue
|
||||
elif in_web_service and line.strip() and not line.startswith(' ') and not line.startswith('#'):
|
||||
# We've left the web service section
|
||||
if not command_added:
|
||||
# If no command was found, add a new command section
|
||||
new_lines.append(f' command: sh -c "{version_command} && python app.py"')
|
||||
command_added = True
|
||||
in_web_service = False
|
||||
|
||||
content = '\n'.join(new_lines)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'content': content,
|
||||
|
||||
@@ -1983,32 +1983,16 @@ def init_routes(main_bp):
|
||||
|
||||
@main_bp.route('/api/version')
|
||||
def api_version():
|
||||
version_file = os.path.join(current_app.root_path, 'version.txt')
|
||||
version = 'unknown'
|
||||
version_data = {}
|
||||
|
||||
if os.path.exists(version_file):
|
||||
with open(version_file, 'r') as f:
|
||||
content = f.read().strip()
|
||||
|
||||
# Try to parse as JSON first (new format)
|
||||
try:
|
||||
version_data = json.loads(content)
|
||||
version = version_data.get('tag', 'unknown')
|
||||
except json.JSONDecodeError:
|
||||
# Fallback to old format (just commit hash)
|
||||
version = content
|
||||
version_data = {
|
||||
'tag': 'unknown',
|
||||
'commit': content,
|
||||
'branch': 'unknown',
|
||||
'deployed_at': 'unknown'
|
||||
}
|
||||
# Get version information from environment variables
|
||||
version = os.getenv('APP_VERSION', 'unknown')
|
||||
commit = os.getenv('GIT_COMMIT', 'unknown')
|
||||
branch = os.getenv('GIT_BRANCH', 'unknown')
|
||||
deployed_at = os.getenv('DEPLOYED_AT', 'unknown')
|
||||
|
||||
return jsonify({
|
||||
'version': version,
|
||||
'tag': version_data.get('tag', 'unknown'),
|
||||
'commit': version_data.get('commit', 'unknown'),
|
||||
'branch': version_data.get('branch', 'unknown'),
|
||||
'deployed_at': version_data.get('deployed_at', 'unknown')
|
||||
'tag': version,
|
||||
'commit': commit,
|
||||
'branch': branch,
|
||||
'deployed_at': deployed_at
|
||||
})
|
||||
59
set_version.py
Normal file
59
set_version.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Utility script to set version environment variables for local development.
|
||||
This replaces the need for version.txt file creation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def get_git_info():
|
||||
"""Get current git commit hash and branch"""
|
||||
try:
|
||||
# Get current commit hash
|
||||
commit_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
|
||||
text=True, stderr=subprocess.DEVNULL).strip()
|
||||
|
||||
# Get current branch
|
||||
branch = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
text=True, stderr=subprocess.DEVNULL).strip()
|
||||
|
||||
# Get latest tag
|
||||
try:
|
||||
latest_tag = subprocess.check_output(['git', 'describe', '--tags', '--abbrev=0'],
|
||||
text=True, stderr=subprocess.DEVNULL).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
latest_tag = 'unknown'
|
||||
|
||||
return {
|
||||
'commit': commit_hash,
|
||||
'branch': branch,
|
||||
'tag': latest_tag
|
||||
}
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return {
|
||||
'commit': 'unknown',
|
||||
'branch': 'unknown',
|
||||
'tag': 'unknown'
|
||||
}
|
||||
|
||||
def set_version_env():
|
||||
"""Set version environment variables"""
|
||||
git_info = get_git_info()
|
||||
|
||||
# Set environment variables
|
||||
os.environ['APP_VERSION'] = git_info['tag']
|
||||
os.environ['GIT_COMMIT'] = git_info['commit']
|
||||
os.environ['GIT_BRANCH'] = git_info['branch']
|
||||
os.environ['DEPLOYED_AT'] = datetime.utcnow().isoformat()
|
||||
|
||||
print("Version environment variables set:")
|
||||
print(f"APP_VERSION: {os.environ['APP_VERSION']}")
|
||||
print(f"GIT_COMMIT: {os.environ['GIT_COMMIT']}")
|
||||
print(f"GIT_BRANCH: {os.environ['GIT_BRANCH']}")
|
||||
print(f"DEPLOYED_AT: {os.environ['DEPLOYED_AT']}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
set_version_env()
|
||||
@@ -451,6 +451,11 @@ async function startLaunch(data) {
|
||||
throw new Error(dockerComposeResult.error || 'Failed to download docker-compose.yml');
|
||||
}
|
||||
|
||||
// Set global version variables for deployment
|
||||
window.currentDeploymentVersion = dockerComposeResult.latest_tag || dockerComposeResult.commit_hash || 'unknown';
|
||||
window.currentDeploymentCommit = dockerComposeResult.commit_hash || 'unknown';
|
||||
window.currentDeploymentBranch = data.branch;
|
||||
|
||||
// Update the step to show success
|
||||
const dockerComposeStep = document.querySelectorAll('.step-item')[7];
|
||||
dockerComposeStep.classList.remove('active');
|
||||
@@ -2551,6 +2556,22 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
||||
{
|
||||
name: 'ISMASTER',
|
||||
value: 'false'
|
||||
},
|
||||
{
|
||||
name: 'APP_VERSION',
|
||||
value: window.currentDeploymentVersion || 'unknown'
|
||||
},
|
||||
{
|
||||
name: 'GIT_COMMIT',
|
||||
value: window.currentDeploymentCommit || 'unknown'
|
||||
},
|
||||
{
|
||||
name: 'GIT_BRANCH',
|
||||
value: window.currentDeploymentBranch || 'unknown'
|
||||
},
|
||||
{
|
||||
name: 'DEPLOYED_AT',
|
||||
value: new Date().toISOString()
|
||||
}
|
||||
]
|
||||
}),
|
||||
@@ -2573,34 +2594,9 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deploying stack:', error);
|
||||
|
||||
// Check if this is a timeout error
|
||||
if (error.name === 'AbortError') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Stack deployment timed out after 10 minutes. The operation may still be in progress.'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if this is a network error
|
||||
if (error.message && error.message.includes('fetch')) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Network error during stack deployment. Please check your connection and try again.'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if this is a response error
|
||||
if (error.message && error.message.includes('Failed to deploy stack')) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Unknown error during stack deployment'
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,38 @@
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Version column styling */
|
||||
.version-badge {
|
||||
font-family: monospace;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.branch-badge {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* Make table responsive */
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Tooltip styling for version info */
|
||||
.tooltip-inner {
|
||||
max-width: 300px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Version comparison styling */
|
||||
.version-outdated {
|
||||
background-color: #fff3cd !important;
|
||||
border-color: #ffeaa7 !important;
|
||||
}
|
||||
|
||||
.version-current {
|
||||
background-color: #d1ecf1 !important;
|
||||
border-color: #bee5eb !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -51,6 +83,8 @@
|
||||
<th>Payment Plan</th>
|
||||
<th>Main URL</th>
|
||||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th>Branch</th>
|
||||
<th>Connection Token</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@@ -82,6 +116,26 @@
|
||||
{{ instance.status|title }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if instance.deployed_version %}
|
||||
<span class="badge bg-info version-badge" data-bs-toggle="tooltip"
|
||||
title="Deployed: {{ instance.deployed_version }}{% if instance.version_checked_at %}<br>Checked: {{ instance.version_checked_at.strftime('%Y-%m-%d %H:%M') }}{% endif %}">
|
||||
{{ instance.deployed_version[:8] if instance.deployed_version != 'unknown' else 'unknown' }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary version-badge">unknown</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if instance.deployed_branch %}
|
||||
<span class="badge bg-light text-dark branch-badge" data-bs-toggle="tooltip"
|
||||
title="Deployed branch: {{ instance.deployed_branch }}">
|
||||
{{ instance.deployed_branch }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary branch-badge">unknown</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if instance.connection_token %}
|
||||
<span class="badge bg-success" data-bs-toggle="tooltip" title="Instance is authenticated">
|
||||
@@ -809,19 +863,19 @@ async function fetchInstanceStats(instanceUrl, instanceId, jwtToken) {
|
||||
|
||||
const row = document.querySelector(`[data-instance-id="${instanceId}"]`).closest('tr');
|
||||
|
||||
// Update rooms count
|
||||
// Update rooms count (3rd column)
|
||||
const roomsCell = row.querySelector('td:nth-child(3)');
|
||||
if (roomsCell) {
|
||||
roomsCell.textContent = data.rooms || '0';
|
||||
}
|
||||
|
||||
// Update conversations count
|
||||
// Update conversations count (4th column)
|
||||
const conversationsCell = row.querySelector('td:nth-child(4)');
|
||||
if (conversationsCell) {
|
||||
conversationsCell.textContent = data.conversations || '0';
|
||||
}
|
||||
|
||||
// Update data usage
|
||||
// Update data usage (5th column)
|
||||
const dataCell = row.querySelector('td:nth-child(5)');
|
||||
if (dataCell) {
|
||||
const dataSize = data.total_storage || 0;
|
||||
@@ -940,8 +994,8 @@ async function fetchCompanyNames() {
|
||||
}))
|
||||
});
|
||||
|
||||
// Changed from nth-child(8) to nth-child(7) since Main URL is the 7th column
|
||||
const urlCell = row.querySelector('td:nth-child(7)');
|
||||
// Main URL is now the 9th column (after adding Version and Branch columns)
|
||||
const urlCell = row.querySelector('td:nth-child(9)');
|
||||
|
||||
if (!urlCell) {
|
||||
console.error(`Could not find URL cell for instance ${instanceId}`);
|
||||
@@ -1056,8 +1110,8 @@ async function editInstance(id) {
|
||||
// Get the name from the first cell
|
||||
const name = row.querySelector('td:first-child').textContent.trim();
|
||||
|
||||
// Get the main URL from the link in the URL cell (7th column)
|
||||
const urlCell = row.querySelector('td:nth-child(7)');
|
||||
// Get the main URL from the link in the URL cell (9th column after adding Version and Branch)
|
||||
const urlCell = row.querySelector('td:nth-child(9)');
|
||||
const urlLink = urlCell.querySelector('a');
|
||||
const mainUrl = urlLink ? urlLink.getAttribute('href') : urlCell.textContent.trim();
|
||||
|
||||
|
||||
@@ -297,6 +297,67 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version Management -->
|
||||
<div class="mb-5">
|
||||
<h5 style="color: var(--primary-color);" class="mb-4">Version Management</h5>
|
||||
|
||||
<!-- Version Tracking -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0" style="color: var(--primary-color);">
|
||||
<i class="fas fa-tags me-2"></i>Version Tracking
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Environment Variables</h6>
|
||||
<ul class="list-unstyled small">
|
||||
<li class="mb-1">• <code>APP_VERSION</code> - Application version/tag</li>
|
||||
<li class="mb-1">• <code>GIT_COMMIT</code> - Git commit hash</li>
|
||||
<li class="mb-1">• <code>GIT_BRANCH</code> - Git branch name</li>
|
||||
<li class="mb-1">• <code>DEPLOYED_AT</code> - Deployment timestamp</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Database Storage</h6>
|
||||
<ul class="list-unstyled small">
|
||||
<li class="mb-1">• Instance version tracking</li>
|
||||
<li class="mb-1">• Version comparison</li>
|
||||
<li class="mb-1">• Update notifications</li>
|
||||
<li class="mb-1">• Deployment history</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version API -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0" style="color: var(--primary-color);">
|
||||
<i class="fas fa-code me-2"></i>Version API
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<h6 class="text-muted mb-2">Endpoint</h6>
|
||||
<code class="bg-light p-2 rounded d-block">GET /api/version</code>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<h6 class="text-muted mb-2">Response</h6>
|
||||
<pre class="bg-light p-2 rounded small"><code>{
|
||||
"version": "v1.2.3",
|
||||
"tag": "v1.2.3",
|
||||
"commit": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
|
||||
"branch": "main",
|
||||
"deployed_at": "2024-01-15T10:30:00.000000"
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
|
||||
55
test_version_api.py
Normal file
55
test_version_api.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the new version API endpoint.
|
||||
This verifies that the database-only version tracking works correctly.
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def test_version_api():
|
||||
"""Test the version API endpoint"""
|
||||
|
||||
# Set test environment variables
|
||||
os.environ['APP_VERSION'] = 'v1.2.3'
|
||||
os.environ['GIT_COMMIT'] = 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0'
|
||||
os.environ['GIT_BRANCH'] = 'main'
|
||||
os.environ['DEPLOYED_AT'] = datetime.utcnow().isoformat()
|
||||
|
||||
print("Testing version API endpoint...")
|
||||
print(f"APP_VERSION: {os.environ['APP_VERSION']}")
|
||||
print(f"GIT_COMMIT: {os.environ['GIT_COMMIT']}")
|
||||
print(f"GIT_BRANCH: {os.environ['GIT_BRANCH']}")
|
||||
print(f"DEPLOYED_AT: {os.environ['DEPLOYED_AT']}")
|
||||
|
||||
try:
|
||||
# Test the API endpoint (assuming it's running on localhost:5000)
|
||||
response = requests.get('http://localhost:5000/api/version')
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("\n✅ Version API test successful!")
|
||||
print("Response:")
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
# Verify the response matches our environment variables
|
||||
assert data['version'] == os.environ['APP_VERSION'], f"Version mismatch: {data['version']} != {os.environ['APP_VERSION']}"
|
||||
assert data['commit'] == os.environ['GIT_COMMIT'], f"Commit mismatch: {data['commit']} != {os.environ['GIT_COMMIT']}"
|
||||
assert data['branch'] == os.environ['GIT_BRANCH'], f"Branch mismatch: {data['branch']} != {os.environ['GIT_BRANCH']}"
|
||||
assert data['deployed_at'] == os.environ['DEPLOYED_AT'], f"Deployed at mismatch: {data['deployed_at']} != {os.environ['DEPLOYED_AT']}"
|
||||
|
||||
print("\n✅ All version information matches environment variables!")
|
||||
|
||||
else:
|
||||
print(f"\n❌ Version API test failed with status code: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("\n❌ Could not connect to the API. Make sure the application is running on localhost:5000")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Test failed with error: {str(e)}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_version_api()
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"tag": "v1.2.3",
|
||||
"commit": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
|
||||
"branch": "main",
|
||||
"deployed_at": "2024-01-15T10:30:00.000000"
|
||||
}
|
||||
Reference in New Issue
Block a user