uptime / app.py
wpms's picture
Update app.py
e1bb15b verified
Raw History Blame Contribute Delete
17.1 kB
from flask import Flask, render_template_string, jsonify, request
import requests
import time
import json
import os
from datetime import datetime
from threading import Thread
import schedule
app = Flask(__name__)
websites_file = 'websites.json'
history_file = 'history.json'
# Default websites - edit these to add your own
default_websites = [
{"id": 1, "name": "mc", "url": "https://wpms-wpms.hf.space", "status": "unknown", "uptime": 100, "response_time": 0, "last_checked": "Never"},
{"id": 2, "name": "mcfun", "url": "https://wpms-wpmfun.hf.space", "status": "unknown", "uptime": 100, "response_time": 0, "last_checked": "Never"},
{"id": 3, "name": "uptimer", "url": "https://wpms-uptime.hf.space", "status": "unknown", "uptime": 100, "response_time": 0, "last_checked": "Never"}
]
def load_websites():
if os.path.exists(websites_file):
try:
with open(websites_file, 'r') as f:
return json.load(f)
except:
return default_websites.copy()
return default_websites.copy()
def save_websites(websites):
with open(websites_file, 'w') as f:
json.dump(websites, f, indent=2)
def load_history():
if os.path.exists(history_file):
try:
with open(history_file, 'r') as f:
return json.load(f)
except:
return {}
return {}
def save_history(history):
with open(history_file, 'w') as f:
json.dump(history, f, indent=2)
def check_website(website):
try:
start_time = time.time()
response = requests.get(website['url'], timeout=30, allow_redirects=True)
response_time = round((time.time() - start_time) * 1000, 2)
if response.status_code < 400:
return {
'status': 'up',
'response_time': response_time,
'status_code': response.status_code
}
else:
return {
'status': 'down',
'response_time': response_time,
'status_code': response.status_code
}
except requests.exceptions.RequestException as e:
return {
'status': 'down',
'response_time': 0,
'error': str(e)
}
def check_all_websites():
websites = load_websites()
history = load_history()
for website in websites:
result = check_website(website)
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
website['last_checked'] = now
website['response_time'] = result['response_time']
if result['status'] == 'up':
website['status'] = 'up'
else:
website['status'] = 'down'
site_id = str(website['id'])
if site_id not in history:
history[site_id] = []
history[site_id].append({
'timestamp': now,
'status': result['status'],
'response_time': result['response_time']
})
if len(history[site_id]) > 1000:
history[site_id] = history[site_id][-1000:]
if history[site_id]:
up_count = sum(1 for h in history[site_id] if h['status'] == 'up')
website['uptime'] = round((up_count / len(history[site_id])) * 100, 2)
save_websites(websites)
save_history(history)
print(f"[{now}] Checked {len(websites)} websites")
def run_scheduler():
schedule.every(5).minutes.do(check_all_websites)
while True:
schedule.run_pending()
time.sleep(1)
# Initialize
websites = load_websites()
if not websites:
websites = default_websites.copy()
save_websites(websites)
scheduler_thread = Thread(target=run_scheduler, daemon=True)
scheduler_thread.start()
check_all_websites()
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Uptime Robot - Website Monitor</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container { max-width: 1200px; margin: 0 auto; }
.header {
text-align: center; color: white; margin-bottom: 40px; padding: 20px;
}
.header h1 { font-size: 3rem; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
.header p { font-size: 1.2rem; opacity: 0.9; }
.stats-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px; margin-bottom: 30px;
}
.stat-card {
background: white; border-radius: 15px; padding: 25px;
text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,0.2);
transition: transform 0.3s ease;
}
.stat-card:hover { transform: translateY(-5px); }
.stat-card i { font-size: 2.5rem; margin-bottom: 15px; }
.stat-card.up i { color: #28a745; }
.stat-card.down i { color: #dc3545; }
.stat-card.total i { color: #007bff; }
.stat-card.uptime i { color: #ffc107; }
.stat-card h3 { font-size: 2rem; margin-bottom: 5px; color: #333; }
.stat-card p { color: #666; font-size: 1rem; }
.websites-list {
background: white; border-radius: 15px; padding: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.websites-list h2 {
margin-bottom: 25px; color: #333;
display: flex; align-items: center;
justify-content: space-between; flex-wrap: wrap; gap: 15px;
}
.website-card {
border: 2px solid #f0f0f0; border-radius: 12px;
padding: 20px; margin-bottom: 15px;
transition: all 0.3s; position: relative; overflow: hidden;
}
.website-card::before {
content: ''; position: absolute; left: 0; top: 0; bottom: 0;
width: 5px; transition: background-color 0.3s;
}
.website-card.up::before { background-color: #28a745; }
.website-card.down::before { background-color: #dc3545; }
.website-card.unknown::before { background-color: #6c757d; }
.website-card:hover {
border-color: #667eea; transform: translateX(5px);
}
.website-header {
display: flex; justify-content: space-between;
align-items: center; margin-bottom: 15px;
flex-wrap: wrap; gap: 10px;
}
.website-name { font-size: 1.3rem; font-weight: bold; color: #333; }
.website-url {
color: #667eea; text-decoration: none; font-size: 0.95rem;
word-break: break-all;
}
.website-url:hover { text-decoration: underline; }
.status-badge {
padding: 6px 15px; border-radius: 20px;
font-size: 0.85rem; font-weight: bold; text-transform: uppercase;
}
.status-up { background: #d4edda; color: #155724; }
.status-down { background: #f8d7da; color: #721c24; }
.status-unknown { background: #e2e3e5; color: #383d41; }
.website-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px; margin-top: 15px;
}
.website-stat {
text-align: center; padding: 10px;
background: #f8f9fa; border-radius: 8px;
}
.website-stat-value { font-size: 1.5rem; font-weight: bold; color: #333; }
.website-stat-label { font-size: 0.85rem; color: #666; margin-top: 5px; }
.progress-bar {
width: 100%; height: 8px; background: #e9ecef;
border-radius: 4px; overflow: hidden; margin-top: 10px;
}
.progress-fill {
height: 100%; border-radius: 4px; transition: width 0.5s ease;
}
.progress-fill.high { background: linear-gradient(90deg, #28a745, #20c997); }
.progress-fill.medium { background: linear-gradient(90deg, #ffc107, #ff9800); }
.progress-fill.low { background: linear-gradient(90deg, #dc3545, #ff5722); }
.loading { text-align: center; padding: 40px; color: #666; }
.loading i { font-size: 3rem; animation: spin 1s linear infinite; }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.empty-state { text-align: center; padding: 60px 20px; color: #666; }
.empty-state i { font-size: 4rem; margin-bottom: 20px; color: #ddd; }
.toast {
position: fixed; bottom: 20px; right: 20px;
padding: 15px 25px; border-radius: 10px;
color: white; font-weight: bold; z-index: 1000;
animation: slideIn 0.3s ease;
}
@keyframes slideIn { from { transform: translateX(400px); } to { transform: translateX(0); } }
.toast.success { background: #28a745; }
.toast.error { background: #dc3545; }
@media (max-width: 768px) {
.header h1 { font-size: 2rem; }
.website-header { flex-direction: column; align-items: flex-start; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-heartbeat"></i> Uptime Robot</h1>
<p>Monitor your websites 24/7</p>
</div>
<div class="stats-grid" id="statsGrid">
<div class="stat-card total">
<i class="fas fa-globe"></i>
<h3 id="totalSites">0</h3>
<p>Total Websites</p>
</div>
<div class="stat-card up">
<i class="fas fa-check-circle"></i>
<h3 id="upSites">0</h3>
<p>Websites Up</p>
</div>
<div class="stat-card down">
<i class="fas fa-times-circle"></i>
<h3 id="downSites">0</h3>
<p>Websites Down</p>
</div>
<div class="stat-card uptime">
<i class="fas fa-chart-line"></i>
<h3 id="avgUptime">100%</h3>
<p>Average Uptime</p>
</div>
</div>
<div class="websites-list">
<h2>
<span><i class="fas fa-list"></i> Monitored Websites</span>
<button class="btn btn-success btn-sm" onclick="checkAllNow()">
<i class="fas fa-sync-alt"></i> Check All Now
</button>
</h2>
<div id="websitesContainer">
<div class="loading">
<i class="fas fa-spinner"></i>
<p>Loading websites...</p>
</div>
</div>
</div>
</div>
<div id="toastContainer"></div>
<script>
let websites = [];
document.addEventListener('DOMContentLoaded', loadWebsites);
setInterval(loadWebsites, 30000);
async function loadWebsites() {
try {
const response = await fetch('/api/websites');
websites = await response.json();
renderWebsites();
updateStats();
} catch (error) {
showToast('Error loading websites', 'error');
}
}
function renderWebsites() {
const container = document.getElementById('websitesContainer');
if (websites.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-inbox"></i>
<h3>No websites configured</h3>
<p>Edit app.py to add your websites</p>
</div>
`;
return;
}
container.innerHTML = websites.map(site => `
<div class="website-card ${site.status}">
<div class="website-header">
<div>
<div class="website-name">${escapeHtml(site.name)}</div>
<a href="${escapeHtml(site.url)}" target="_blank" class="website-url">
<i class="fas fa-external-link-alt"></i> ${escapeHtml(site.url)}
</a>
</div>
<span class="status-badge status-${site.status}">
<i class="fas fa-${site.status === 'up' ? 'check' : site.status === 'down' ? 'times' : 'question'}"></i>
${site.status.toUpperCase()}
</span>
</div>
<div class="progress-bar">
<div class="progress-fill ${site.uptime >= 99 ? 'high' : site.uptime >= 95 ? 'medium' : 'low'}"
style="width: ${site.uptime}%"></div>
</div>
<div class="website-stats">
<div class="website-stat">
<div class="website-stat-value">${site.uptime}%</div>
<div class="website-stat-label">Uptime</div>
</div>
<div class="website-stat">
<div class="website-stat-value">${site.response_time}ms</div>
<div class="website-stat-label">Response Time</div>
</div>
<div class="website-stat">
<div class="website-stat-value">${formatTime(site.last_checked)}</div>
<div class="website-stat-label">Last Checked</div>
</div>
</div>
</div>
`).join('');
}
function updateStats() {
const total = websites.length;
const up = websites.filter(w => w.status === 'up').length;
const down = websites.filter(w => w.status === 'down').length;
const avgUptime = total > 0 ? (websites.reduce((sum, w) => sum + w.uptime, 0) / total).toFixed(2) : 100;
document.getElementById('totalSites').textContent = total;
document.getElementById('upSites').textContent = up;
document.getElementById('downSites').textContent = down;
document.getElementById('avgUptime').textContent = avgUptime + '%';
}
async function checkAllNow() {
const btn = document.querySelector('.btn-success');
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Checking...';
btn.disabled = true;
try {
const response = await fetch('/api/check-now', {
method: 'POST'
});
if (response.ok) {
showToast('All websites checked!', 'success');
loadWebsites();
} else {
showToast('Failed to check websites', 'error');
}
} catch (error) {
showToast('Error checking websites', 'error');
} finally {
btn.innerHTML = '<i class="fas fa-sync-alt"></i> Check All Now';
btn.disabled = false;
}
}
function showToast(message, type) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `<i class="fas fa-${type === 'success' ? 'check' : 'exclamation-triangle'}"></i> ${message}`;
container.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function formatTime(time) {
if (time === 'Never') return 'Never';
const date = new Date(time);
const now = new Date();
const diff = Math.floor((now - date) / 1000);
if (diff < 60) return 'Just now';
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
return `${Math.floor(diff / 86400)}d ago`;
}
</script>
</body>
</html>
"""
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/api/websites')
def get_websites():
return jsonify(load_websites())
@app.route('/api/check-now', methods=['POST'])
def check_now():
check_all_websites()
return jsonify({'message': 'Check completed', 'websites': load_websites()})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860, debug=False)