version
This commit is contained in:
Binary file not shown.
24
migrations/versions/a1fd98e6630d_merge_heads.py
Normal file
24
migrations/versions/a1fd98e6630d_merge_heads.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""merge heads
|
||||
|
||||
Revision ID: a1fd98e6630d
|
||||
Revises: 761908f0cacf, add_password_reset_tokens
|
||||
Create Date: 2025-06-23 09:12:50.264151
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a1fd98e6630d'
|
||||
down_revision = ('761908f0cacf', 'add_password_reset_tokens')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
pass
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
||||
@@ -7,6 +7,7 @@ Create Date: 2024-01-01 12:00:00.000000
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import text
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'add_password_reset_tokens'
|
||||
@@ -15,19 +16,28 @@ branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# Create password_reset_tokens table
|
||||
op.create_table('password_reset_tokens',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('token', sa.String(length=100), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('expires_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('used', sa.Boolean(), nullable=True),
|
||||
sa.Column('ip_address', sa.String(length=45), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('token')
|
||||
)
|
||||
conn = op.get_bind()
|
||||
result = conn.execute(text("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'password_reset_tokens'
|
||||
);
|
||||
"""))
|
||||
exists = result.scalar()
|
||||
if not exists:
|
||||
# Create password_reset_tokens table
|
||||
op.create_table('password_reset_tokens',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('token', sa.String(length=100), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('expires_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('used', sa.Boolean(), nullable=True),
|
||||
sa.Column('ip_address', sa.String(length=45), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('token')
|
||||
)
|
||||
|
||||
def downgrade():
|
||||
# Drop password_reset_tokens table
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""add version tracking fields to instances
|
||||
|
||||
Revision ID: c94c2b2b9f2e
|
||||
Revises: a1fd98e6630d
|
||||
Create Date: 2025-06-23 09:15:13.092801
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'c94c2b2b9f2e'
|
||||
down_revision = 'a1fd98e6630d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('email_template')
|
||||
op.drop_table('notification')
|
||||
with op.batch_alter_table('instances', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('deployed_version', sa.String(length=50), nullable=True))
|
||||
batch_op.add_column(sa.Column('deployed_branch', sa.String(length=100), nullable=True))
|
||||
batch_op.add_column(sa.Column('latest_version', sa.String(length=50), nullable=True))
|
||||
batch_op.add_column(sa.Column('version_checked_at', sa.DateTime(), nullable=True))
|
||||
|
||||
with op.batch_alter_table('room_file', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(batch_op.f('fk_room_file_deleted_by_user'), type_='foreignkey')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('room_file', schema=None) as batch_op:
|
||||
batch_op.create_foreign_key(batch_op.f('fk_room_file_deleted_by_user'), 'user', ['deleted_by'], ['id'])
|
||||
|
||||
with op.batch_alter_table('instances', schema=None) as batch_op:
|
||||
batch_op.drop_column('version_checked_at')
|
||||
batch_op.drop_column('latest_version')
|
||||
batch_op.drop_column('deployed_branch')
|
||||
batch_op.drop_column('deployed_version')
|
||||
|
||||
op.create_table('notification',
|
||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
|
||||
sa.Column('title', sa.VARCHAR(length=200), autoincrement=False, nullable=False),
|
||||
sa.Column('message', sa.TEXT(), autoincrement=False, nullable=False),
|
||||
sa.Column('type', sa.VARCHAR(length=50), autoincrement=False, nullable=False),
|
||||
sa.Column('read', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], name=op.f('notification_user_id_fkey')),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('notification_pkey'))
|
||||
)
|
||||
op.create_table('email_template',
|
||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.VARCHAR(length=100), autoincrement=False, nullable=False),
|
||||
sa.Column('subject', sa.VARCHAR(length=200), autoincrement=False, nullable=False),
|
||||
sa.Column('body', sa.TEXT(), autoincrement=False, nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('email_template_pkey')),
|
||||
sa.UniqueConstraint('name', name=op.f('email_template_name_key'), postgresql_include=[], postgresql_nulls_not_distinct=False)
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
@@ -528,6 +528,11 @@ class Instance(db.Model):
|
||||
status = db.Column(db.String(20), nullable=False, default='inactive')
|
||||
status_details = db.Column(db.Text, nullable=True)
|
||||
connection_token = db.Column(db.String(64), unique=True, nullable=True)
|
||||
# Version tracking fields
|
||||
deployed_version = db.Column(db.String(50), nullable=True) # Current deployed version (commit hash or tag)
|
||||
deployed_branch = db.Column(db.String(100), nullable=True) # Branch that was deployed
|
||||
latest_version = db.Column(db.String(50), nullable=True) # Latest version available in Git
|
||||
version_checked_at = db.Column(db.DateTime, nullable=True) # When version was last checked
|
||||
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP'), onupdate=db.text('CURRENT_TIMESTAMP'))
|
||||
|
||||
|
||||
Binary file not shown.
@@ -13,6 +13,9 @@ from email.utils import formatdate
|
||||
from datetime import datetime
|
||||
import requests
|
||||
import base64
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from functools import wraps
|
||||
import os
|
||||
|
||||
launch_api = Blueprint('launch_api', __name__)
|
||||
|
||||
@@ -657,6 +660,60 @@ def download_docker_compose():
|
||||
if not git_settings:
|
||||
return jsonify({'message': 'Git settings not configured'}), 400
|
||||
|
||||
# Get the current commit hash and latest tag for the branch
|
||||
commit_hash = None
|
||||
latest_tag = None
|
||||
if git_settings['provider'] == 'gitea':
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': f'token {git_settings["token"]}'
|
||||
}
|
||||
|
||||
# Get the latest commit for the branch
|
||||
commit_response = requests.get(
|
||||
f'{git_settings["url"]}/api/v1/repos/{data["repository"]}/commits/{data["branch"]}',
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if commit_response.status_code == 200:
|
||||
commit_data = commit_response.json()
|
||||
commit_hash = commit_data.get('sha')
|
||||
else:
|
||||
# Try token as query parameter if header auth fails
|
||||
commit_response = requests.get(
|
||||
f'{git_settings["url"]}/api/v1/repos/{data["repository"]}/commits/{data["branch"]}?token={git_settings["token"]}',
|
||||
headers={'Accept': 'application/json'}
|
||||
)
|
||||
if commit_response.status_code == 200:
|
||||
commit_data = commit_response.json()
|
||||
commit_hash = commit_data.get('sha')
|
||||
|
||||
# Get the latest tag
|
||||
tags_response = requests.get(
|
||||
f'{git_settings["url"]}/api/v1/repos/{data["repository"]}/tags',
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if tags_response.status_code == 200:
|
||||
tags_data = tags_response.json()
|
||||
if tags_data:
|
||||
# Sort tags by commit date (newest first) and get the latest
|
||||
sorted_tags = sorted(tags_data, key=lambda x: x.get('commit', {}).get('created', ''), reverse=True)
|
||||
if sorted_tags:
|
||||
latest_tag = sorted_tags[0].get('name')
|
||||
else:
|
||||
# Try token as query parameter if header auth fails
|
||||
tags_response = requests.get(
|
||||
f'{git_settings["url"]}/api/v1/repos/{data["repository"]}/tags?token={git_settings["token"]}',
|
||||
headers={'Accept': 'application/json'}
|
||||
)
|
||||
if tags_response.status_code == 200:
|
||||
tags_data = tags_response.json()
|
||||
if tags_data:
|
||||
sorted_tags = sorted(tags_data, key=lambda x: x.get('commit', {}).get('created', ''), reverse=True)
|
||||
if sorted_tags:
|
||||
latest_tag = sorted_tags[0].get('name')
|
||||
|
||||
# Determine the provider and set up the appropriate API call
|
||||
if git_settings['provider'] == 'gitea':
|
||||
# For Gitea
|
||||
@@ -704,9 +761,53 @@ 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
|
||||
'content': content,
|
||||
'commit_hash': commit_hash,
|
||||
'latest_tag': latest_tag
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
@@ -981,26 +1082,77 @@ def save_instance():
|
||||
missing_fields = [field for field in required_fields if field not in data]
|
||||
return jsonify({'error': f'Missing required fields: {", ".join(missing_fields)}'}), 400
|
||||
|
||||
# Save instance data
|
||||
instance_data = {
|
||||
'name': data['name'],
|
||||
'port': data['port'],
|
||||
'domains': data['domains'],
|
||||
'stack_id': data['stack_id'],
|
||||
'stack_name': data['stack_name'],
|
||||
'status': data['status'],
|
||||
'repository': data['repository'],
|
||||
'branch': data['branch'],
|
||||
'created_at': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Save to database using KeyValueSettings
|
||||
KeyValueSettings.set_value(f'instance_{data["name"]}', instance_data)
|
||||
|
||||
return jsonify({
|
||||
'message': 'Instance data saved successfully',
|
||||
'data': instance_data
|
||||
})
|
||||
# Check if instance already exists
|
||||
existing_instance = Instance.query.filter_by(name=data['name']).first()
|
||||
|
||||
if existing_instance:
|
||||
# Update existing instance
|
||||
existing_instance.port = data['port']
|
||||
existing_instance.domains = data['domains']
|
||||
existing_instance.stack_id = data['stack_id']
|
||||
existing_instance.stack_name = data['stack_name']
|
||||
existing_instance.status = data['status']
|
||||
existing_instance.repository = data['repository']
|
||||
existing_instance.branch = data['branch']
|
||||
existing_instance.deployed_version = data.get('deployed_version', 'unknown')
|
||||
existing_instance.deployed_branch = data.get('deployed_branch', data['branch'])
|
||||
existing_instance.version_checked_at = datetime.utcnow()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'message': 'Instance data updated successfully',
|
||||
'data': {
|
||||
'name': existing_instance.name,
|
||||
'port': existing_instance.port,
|
||||
'domains': existing_instance.domains,
|
||||
'stack_id': existing_instance.stack_id,
|
||||
'stack_name': existing_instance.stack_name,
|
||||
'status': existing_instance.status,
|
||||
'repository': existing_instance.repository,
|
||||
'branch': existing_instance.branch,
|
||||
'deployed_version': existing_instance.deployed_version,
|
||||
'deployed_branch': existing_instance.deployed_branch
|
||||
}
|
||||
})
|
||||
else:
|
||||
# Create new instance
|
||||
instance = Instance(
|
||||
name=data['name'],
|
||||
company='Loading...', # Will be updated later
|
||||
rooms_count=0,
|
||||
conversations_count=0,
|
||||
data_size=0.0,
|
||||
payment_plan='Basic',
|
||||
main_url=f"https://{data['domains'][0]}" if data['domains'] else f"http://localhost:{data['port']}",
|
||||
status=data['status'],
|
||||
port=data['port'],
|
||||
stack_id=data['stack_id'],
|
||||
stack_name=data['stack_name'],
|
||||
repository=data['repository'],
|
||||
branch=data['branch'],
|
||||
deployed_version=data.get('deployed_version', 'unknown'),
|
||||
deployed_branch=data.get('deployed_branch', data['branch'])
|
||||
)
|
||||
|
||||
db.session.add(instance)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'message': 'Instance data saved successfully',
|
||||
'data': {
|
||||
'name': instance.name,
|
||||
'port': instance.port,
|
||||
'domains': instance.domains,
|
||||
'stack_id': instance.stack_id,
|
||||
'stack_name': instance.stack_name,
|
||||
'status': instance.status,
|
||||
'repository': instance.repository,
|
||||
'branch': instance.branch,
|
||||
'deployed_version': instance.deployed_version,
|
||||
'deployed_branch': instance.deployed_branch
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error saving instance data: {str(e)}")
|
||||
|
||||
@@ -379,18 +379,58 @@ def init_routes(main_bp):
|
||||
|
||||
instances = Instance.query.order_by(Instance.name.asc()).all()
|
||||
|
||||
# Check status for each instance
|
||||
# Get Git settings
|
||||
git_settings = KeyValueSettings.get_value('git_settings')
|
||||
gitea_url = git_settings.get('url') if git_settings else None
|
||||
gitea_token = git_settings.get('token') if git_settings else None
|
||||
gitea_repo = git_settings.get('repo') if git_settings else None
|
||||
|
||||
for instance in instances:
|
||||
# 1. Check status
|
||||
status_info = check_instance_status(instance)
|
||||
instance.status = status_info['status']
|
||||
instance.status_details = status_info['details']
|
||||
|
||||
# 2. Check deployed version
|
||||
deployed_version = None
|
||||
deployed_tag = None
|
||||
deployed_commit = None
|
||||
try:
|
||||
version_url = f"{instance.main_url.rstrip('/')}/api/version"
|
||||
resp = requests.get(version_url, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
version_data = resp.json()
|
||||
deployed_version = version_data.get('version', 'unknown')
|
||||
deployed_tag = version_data.get('tag', 'unknown')
|
||||
deployed_commit = version_data.get('commit', 'unknown')
|
||||
except Exception as e:
|
||||
deployed_version = None
|
||||
deployed_tag = None
|
||||
deployed_commit = None
|
||||
|
||||
instance.deployed_version = deployed_tag or deployed_version or 'unknown'
|
||||
instance.deployed_branch = instance.deployed_branch or 'master'
|
||||
|
||||
# 3. Check latest version from Gitea (if settings available)
|
||||
latest_version = None
|
||||
deployed_branch = instance.deployed_branch or 'master'
|
||||
if gitea_url and gitea_token and gitea_repo:
|
||||
try:
|
||||
headers = {'Accept': 'application/json', 'Authorization': f'token {gitea_token}'}
|
||||
# Gitea API: /api/v1/repos/{owner}/{repo}/commits/{branch}
|
||||
commit_url = f"{gitea_url}/api/v1/repos/{gitea_repo}/commits/{deployed_branch}"
|
||||
commit_resp = requests.get(commit_url, headers=headers, timeout=5)
|
||||
if commit_resp.status_code == 200:
|
||||
latest_version = commit_resp.json().get('sha')
|
||||
except Exception as e:
|
||||
latest_version = None
|
||||
instance.latest_version = latest_version or 'unknown'
|
||||
instance.version_checked_at = datetime.utcnow()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Get connection settings
|
||||
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||
git_settings = KeyValueSettings.get_value('git_settings')
|
||||
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||
|
||||
return render_template('main/instances.html',
|
||||
@@ -1975,4 +2015,36 @@ def init_routes(main_bp):
|
||||
flash('This page is only available in master instances.', 'error')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
return render_template('wiki/base.html')
|
||||
return render_template('wiki/base.html')
|
||||
|
||||
@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'
|
||||
}
|
||||
|
||||
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')
|
||||
})
|
||||
@@ -546,12 +546,19 @@ async function startLaunch(data) {
|
||||
stack_name: stackResult.data.name,
|
||||
status: stackResult.data.status,
|
||||
repository: data.repository,
|
||||
branch: data.branch
|
||||
branch: data.branch,
|
||||
deployed_version: dockerComposeResult.latest_tag || dockerComposeResult.commit_hash || 'unknown',
|
||||
deployed_branch: data.branch
|
||||
};
|
||||
console.log('Saving instance data:', instanceData);
|
||||
const saveResult = await saveInstanceData(instanceData);
|
||||
console.log('Save result:', saveResult);
|
||||
await updateStep(10, 'Saving Instance Data', 'Instance data saved successfully');
|
||||
|
||||
// Update step with version information
|
||||
const versionInfo = dockerComposeResult.commit_hash ?
|
||||
`Instance data saved successfully. Version: ${dockerComposeResult.commit_hash.substring(0, 8)}` :
|
||||
'Instance data saved successfully';
|
||||
await updateStep(10, 'Saving Instance Data', versionInfo);
|
||||
} catch (error) {
|
||||
console.error('Error saving instance data:', error);
|
||||
await updateStep(10, 'Saving Instance Data', `Error: ${error.message}`);
|
||||
|
||||
@@ -3,6 +3,27 @@
|
||||
|
||||
{% block title %}Instances - DocuPulse{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.version-info {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.version-info code {
|
||||
font-size: 0.8rem;
|
||||
background-color: #f8f9fa;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.badge-sm {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ header(
|
||||
title="Instances",
|
||||
@@ -43,6 +64,7 @@
|
||||
<th>Payment Plan</th>
|
||||
<th>Main URL</th>
|
||||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th>Connection Token</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@@ -74,6 +96,44 @@
|
||||
{{ instance.status|title }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if instance.deployed_version and instance.deployed_version != 'unknown' %}
|
||||
<div class="version-info">
|
||||
<div class="small">
|
||||
<strong>Version:</strong>
|
||||
<code class="text-primary">{{ instance.deployed_version }}</code>
|
||||
</div>
|
||||
{% if instance.latest_version and instance.latest_version != 'unknown' %}
|
||||
<div class="small">
|
||||
<strong>Latest:</strong>
|
||||
<code class="text-secondary">{{ instance.latest_version }}</code>
|
||||
</div>
|
||||
{% if instance.deployed_version == instance.latest_version %}
|
||||
<span class="badge bg-success badge-sm" data-bs-toggle="tooltip" title="Instance is up to date">
|
||||
<i class="fas fa-check"></i> Up-to-date
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning badge-sm" data-bs-toggle="tooltip" title="Instance is outdated">
|
||||
<i class="fas fa-exclamation-triangle"></i> Outdated
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge bg-secondary badge-sm" data-bs-toggle="tooltip" title="Latest version unknown">
|
||||
<i class="fas fa-question"></i> Unknown
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if instance.version_checked_at %}
|
||||
<div class="small text-muted">
|
||||
<i class="fas fa-clock"></i> {{ instance.version_checked_at.strftime('%H:%M') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary" data-bs-toggle="tooltip" title="Version information not available">
|
||||
<i class="fas fa-question"></i> Unknown
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if instance.connection_token %}
|
||||
<span class="badge bg-success" data-bs-toggle="tooltip" title="Instance is authenticated">
|
||||
|
||||
6
version.txt
Normal file
6
version.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tag": "v1.2.3",
|
||||
"commit": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
|
||||
"branch": "main",
|
||||
"deployed_at": "2024-01-15T10:30:00.000000"
|
||||
}
|
||||
Reference in New Issue
Block a user