Active status check
This commit is contained in:
Binary file not shown.
24
migrations/versions/4ee23cb29001_merge_heads.py
Normal file
24
migrations/versions/4ee23cb29001_merge_heads.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""merge heads
|
||||
|
||||
Revision ID: 4ee23cb29001
|
||||
Revises: 72ab6c4c6a5f, add_status_details
|
||||
Create Date: 2025-06-09 10:04:48.708415
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4ee23cb29001'
|
||||
down_revision = ('72ab6c4c6a5f', 'add_status_details')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
pass
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
||||
@@ -7,6 +7,7 @@ Create Date: 2024-03-19 10:00:00.000000
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
@@ -17,23 +18,52 @@ depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
conn = op.get_bind()
|
||||
result = conn.execute(text("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'instances'
|
||||
);
|
||||
"""))
|
||||
exists = result.scalar()
|
||||
if not exists:
|
||||
op.create_table('instances',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=100), nullable=False),
|
||||
sa.Column('company', sa.String(length=100), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('company', sa.String(length=255), nullable=False),
|
||||
sa.Column('rooms_count', sa.Integer(), nullable=False, server_default='0'),
|
||||
sa.Column('conversations_count', sa.Integer(), nullable=False, server_default='0'),
|
||||
sa.Column('data_size', sa.Float(), nullable=False, server_default='0.0'),
|
||||
sa.Column('payment_plan', sa.String(length=20), nullable=False, server_default='Basic'),
|
||||
sa.Column('data_size', sa.String(length=50), nullable=False, server_default='0 MB'),
|
||||
sa.Column('payment_plan', sa.String(length=50), nullable=False, server_default='free'),
|
||||
sa.Column('main_url', sa.String(length=255), nullable=False),
|
||||
sa.Column('status', sa.String(length=20), nullable=False, server_default='inactive'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name'),
|
||||
sa.UniqueConstraint('main_url')
|
||||
)
|
||||
|
||||
# Create a trigger to automatically update the updated_at column
|
||||
op.execute("""
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
""")
|
||||
|
||||
op.execute("""
|
||||
CREATE TRIGGER update_instances_updated_at
|
||||
BEFORE UPDATE ON instances
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute("DROP TRIGGER IF EXISTS update_instances_updated_at ON instances")
|
||||
op.execute("DROP FUNCTION IF EXISTS update_updated_at_column()")
|
||||
op.drop_table('instances')
|
||||
24
migrations/versions/add_status_details.py
Normal file
24
migrations/versions/add_status_details.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""add status_details column
|
||||
|
||||
Revision ID: add_status_details
|
||||
Revises: add_instances_table
|
||||
Create Date: 2024-03-19 11:00:00.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'add_status_details'
|
||||
down_revision = 'add_instances_table'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('instances', sa.Column('status_details', sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('instances', 'status_details')
|
||||
19
models.py
19
models.py
@@ -499,16 +499,17 @@ class Instance(db.Model):
|
||||
__tablename__ = 'instances'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
name = db.Column(db.String(100), unique=True, nullable=False)
|
||||
company = db.Column(db.String(100), nullable=False)
|
||||
rooms_count = db.Column(db.Integer, default=0)
|
||||
conversations_count = db.Column(db.Integer, default=0)
|
||||
data_size = db.Column(db.Float, default=0) # in GB
|
||||
payment_plan = db.Column(db.String(50), nullable=False)
|
||||
main_url = db.Column(db.String(255), nullable=False)
|
||||
status = db.Column(db.String(20), default='inactive') # active or inactive
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
rooms_count = db.Column(db.Integer, nullable=False, default=0)
|
||||
conversations_count = db.Column(db.Integer, nullable=False, default=0)
|
||||
data_size = db.Column(db.Float, nullable=False, default=0.0)
|
||||
payment_plan = db.Column(db.String(20), nullable=False, default='Basic')
|
||||
main_url = db.Column(db.String(255), unique=True, nullable=False)
|
||||
status = db.Column(db.String(20), nullable=False, default='inactive')
|
||||
status_details = db.Column(db.Text, nullable=True)
|
||||
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 ON UPDATE CURRENT_TIMESTAMP'))
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Instance {self.name}>'
|
||||
@@ -1,15 +1,16 @@
|
||||
Flask>=2.0.0
|
||||
Flask-SQLAlchemy>=3.0.0
|
||||
Flask-Login>=0.6.0
|
||||
Flask-WTF>=1.0.0
|
||||
Flask-Mail==0.9.1
|
||||
Flask-Migrate>=4.0.0
|
||||
SQLAlchemy>=1.4.0
|
||||
Werkzeug>=2.0.0
|
||||
WTForms==3.1.1
|
||||
Flask-WTF>=1.0.0
|
||||
email-validator==2.1.0.post1
|
||||
python-dotenv>=0.19.0
|
||||
psycopg2-binary==2.9.9
|
||||
gunicorn==21.2.0
|
||||
email_validator==2.1.0.post1
|
||||
Werkzeug>=2.0.0
|
||||
SQLAlchemy>=1.4.0
|
||||
alembic>=1.7.0
|
||||
psycopg2-binary==2.9.9
|
||||
requests>=2.31.0
|
||||
gunicorn==21.2.0
|
||||
prometheus-client>=0.16.0
|
||||
PyJWT>=2.8.0
|
||||
Binary file not shown.
@@ -16,6 +16,8 @@ import csv
|
||||
from flask_wtf.csrf import generate_csrf
|
||||
import json
|
||||
import smtplib
|
||||
import requests
|
||||
from functools import wraps
|
||||
|
||||
# Set up logging to show in console
|
||||
logging.basicConfig(
|
||||
@@ -332,6 +334,30 @@ def init_routes(main_bp):
|
||||
is_admin=current_user.is_admin
|
||||
)
|
||||
|
||||
def check_instance_status(instance):
|
||||
"""Check the status of an instance by contacting its health endpoint"""
|
||||
try:
|
||||
# Construct the health check URL
|
||||
health_url = f"{instance.main_url.rstrip('/')}/health"
|
||||
response = requests.get(health_url, timeout=5)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return {
|
||||
'status': 'active' if data.get('status') == 'healthy' else 'inactive',
|
||||
'details': json.dumps(data) # Convert dictionary to JSON string
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'status': 'inactive',
|
||||
'details': f"Health check returned status code {response.status_code}"
|
||||
}
|
||||
except requests.RequestException as e:
|
||||
return {
|
||||
'status': 'inactive',
|
||||
'details': str(e)
|
||||
}
|
||||
|
||||
@main_bp.route('/instances')
|
||||
@login_required
|
||||
@require_password_change
|
||||
@@ -341,6 +367,15 @@ def init_routes(main_bp):
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
instances = Instance.query.all()
|
||||
|
||||
# Check status for each instance
|
||||
for instance in instances:
|
||||
status_info = check_instance_status(instance)
|
||||
instance.status = status_info['status']
|
||||
instance.status_details = status_info['details']
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return render_template('main/instances.html', instances=instances)
|
||||
|
||||
@main_bp.route('/instances/add', methods=['POST'])
|
||||
@@ -431,6 +466,22 @@ def init_routes(main_bp):
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 400
|
||||
|
||||
@main_bp.route('/instances/<int:instance_id>/status')
|
||||
@login_required
|
||||
def check_status(instance_id):
|
||||
if not os.environ.get('MASTER', 'false').lower() == 'true':
|
||||
return jsonify({'error': 'Access denied'}), 403
|
||||
|
||||
instance = Instance.query.get_or_404(instance_id)
|
||||
status_info = check_instance_status(instance)
|
||||
|
||||
# Update instance status in database
|
||||
instance.status = status_info['status']
|
||||
instance.status_details = status_info['details']
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(status_info)
|
||||
|
||||
UPLOAD_FOLDER = '/app/uploads/profile_pics'
|
||||
if not os.path.exists(UPLOAD_FOLDER):
|
||||
os.makedirs(UPLOAD_FOLDER)
|
||||
|
||||
@@ -57,7 +57,10 @@
|
||||
<td>{{ instance.payment_plan }}</td>
|
||||
<td>{{ instance.main_url }}</td>
|
||||
<td>
|
||||
<span class="badge bg-{{ 'success' if instance.status == 'active' else 'danger' }}">
|
||||
<span class="badge bg-{{ 'success' if instance.status == 'active' else 'danger' }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-instance-id="{{ instance.id }}"
|
||||
title="{{ instance.status_details }}">
|
||||
{{ instance.status|title }}
|
||||
</span>
|
||||
</td>
|
||||
@@ -183,8 +186,68 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal'));
|
||||
editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal'));
|
||||
addExistingInstanceModal = new bootstrap.Modal(document.getElementById('addExistingInstanceModal'));
|
||||
|
||||
// Initialize tooltips
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
|
||||
// Check statuses on page load
|
||||
checkAllInstanceStatuses();
|
||||
|
||||
// Set up periodic status checks (every 30 seconds)
|
||||
setInterval(checkAllInstanceStatuses, 30000);
|
||||
});
|
||||
|
||||
// Function to check status of all instances
|
||||
async function checkAllInstanceStatuses() {
|
||||
const statusBadges = document.querySelectorAll('[data-instance-id]');
|
||||
for (const badge of statusBadges) {
|
||||
const instanceId = badge.dataset.instanceId;
|
||||
await checkInstanceStatus(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check status of a single instance
|
||||
async function checkInstanceStatus(instanceId) {
|
||||
try {
|
||||
const response = await fetch(`/instances/${instanceId}/status`);
|
||||
if (!response.ok) throw new Error('Failed to check instance status');
|
||||
|
||||
const data = await response.json();
|
||||
const badge = document.querySelector(`[data-instance-id="${instanceId}"]`);
|
||||
if (badge) {
|
||||
badge.className = `badge bg-${data.status === 'active' ? 'success' : 'danger'}`;
|
||||
badge.textContent = data.status.charAt(0).toUpperCase() + data.status.slice(1);
|
||||
|
||||
// Parse the JSON string in status_details
|
||||
let tooltipContent = data.status;
|
||||
if (data.status_details) {
|
||||
try {
|
||||
const details = JSON.parse(data.status_details);
|
||||
tooltipContent = `Status: ${details.status}\nTimestamp: ${details.timestamp}`;
|
||||
if (details.database) {
|
||||
tooltipContent += `\nDatabase: ${details.database}`;
|
||||
}
|
||||
} catch (e) {
|
||||
tooltipContent = data.status_details;
|
||||
}
|
||||
}
|
||||
badge.title = tooltipContent;
|
||||
|
||||
// Update tooltip
|
||||
const tooltip = bootstrap.Tooltip.getInstance(badge);
|
||||
if (tooltip) {
|
||||
tooltip.dispose();
|
||||
}
|
||||
new bootstrap.Tooltip(badge);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking instance status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Show modals
|
||||
function showAddInstanceModal() {
|
||||
document.getElementById('addInstanceForm').reset();
|
||||
|
||||
Reference in New Issue
Block a user