Download app.py from wpms/uptime: direct link, hf CLI and curl.
- Browser
- Download file 17.1 kB
-
https://huggingface.co/spaces/wpms/uptime/resolve/main/app.py
- Command line
-
hf download hf://spaces/wpms/uptime/app.py
-
curl -L -o app.py https://huggingface.co/spaces/wpms/uptime/resolve/main/app.py
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> | |
| """ | |
| def index(): | |
| return render_template_string(HTML_TEMPLATE) | |
| def get_websites(): | |
| return jsonify(load_websites()) | |
| 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) | |