GamerC0der commited on
Commit
68d0aa2
·
verified ·
1 Parent(s): 1be5b26

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -82
app.py CHANGED
@@ -1,147 +1,269 @@
1
  import os
 
 
2
  import psutil
3
- import psycopg2
4
- from flask import Flask, jsonify, request
5
 
6
  app = Flask(__name__)
7
 
8
- DB_CONFIG = {
9
- "host": os.getenv("DB_HOST", "localhost"),
10
- "port": int(os.getenv("DB_PORT", 5432)),
11
- "dbname": os.getenv("DB_NAME", "mydb"),
12
- "user": os.getenv("DB_USER", "myuser"),
13
- "password": os.getenv("DB_PASSWORD", "mypassword"),
14
  }
15
 
16
- EXPECTED_LIMITS = {
17
- "cpu_cores": 1,
18
- "ram_gb": 8,
19
- "storage_gb": 20,
20
- "max_db_connections": 50,
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- def get_db_connection():
25
- return psycopg2.connect(**DB_CONFIG)
 
 
 
 
 
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  def get_system_stats():
29
  cpu_percent = psutil.cpu_percent(interval=0.1)
30
  cpu_count = psutil.cpu_count(logical=False) or psutil.cpu_count()
31
-
32
  vm = psutil.virtual_memory()
33
  ram_total_gb = vm.total / (1024**3)
34
  ram_used_gb = (vm.total - vm.available) / (1024**3)
35
  ram_percent = vm.percent
36
-
37
  disk = psutil.disk_usage("/")
38
  disk_total_gb = disk.total / (1024**3)
39
  disk_used_gb = disk.used / (1024**3)
40
  disk_percent = disk.percent
41
-
42
  return {
43
  "cpu": {
44
  "usage_percent": cpu_percent,
45
  "cores_detected": cpu_count,
46
- "expected_cores": EXPECTED_LIMITS["cpu_cores"],
47
  },
48
  "ram": {
49
  "total_gb": ram_total_gb,
50
  "used_gb": ram_used_gb,
51
  "usage_percent": ram_percent,
52
- "expected_gb": EXPECTED_LIMITS["ram_gb"],
53
  },
54
  "disk": {
55
  "total_gb": disk_total_gb,
56
  "used_gb": disk_used_gb,
57
  "usage_percent": disk_percent,
58
- "expected_gb": EXPECTED_LIMITS["storage_gb"],
59
  },
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- def get_db_stats():
 
 
64
  try:
65
- conn = get_db_connection()
66
  cur = conn.cursor()
67
- cur.execute("SELECT COUNT(*) FROM pg_stat_activity;")
68
- current_connections = cur.fetchone()[0]
69
- cur.execute("SHOW max_connections;")
70
- max_connections = int(cur.fetchone()[0])
71
  cur.close()
72
  conn.close()
73
-
74
- return {
75
- "current_connections": current_connections,
76
- "max_connections": max_connections,
77
- "expected_max_connections": EXPECTED_LIMITS["max_db_connections"],
78
- }
79
  except Exception as e:
80
- return {
81
- "error": str(e),
82
- "current_connections": None,
83
- "max_connections": None,
84
- }
85
-
86
-
87
- @app.route("/")
88
- def index():
89
- system_stats = get_system_stats()
90
- db_stats = get_db_stats()
91
-
92
- doc = {
93
- "description": "PostgreSQL + Flask service exposed on /api",
94
- "endpoints": {
95
- "/": {
96
- "method": "GET",
97
- "description": "System + DB stats and endpoint documentation",
98
- },
99
- "/api": {
100
- "method": "GET",
101
- "description": "Simple API root, returns health and DB connectivity",
102
- },
103
- "/api/query": {
104
- "method": "POST",
105
- "description": "Execute an arbitrary SQL query. Body: JSON {\"query\": \"SELECT ...\"}",
106
- },
107
- },
108
- "expected_resources": EXPECTED_LIMITS,
109
- "system_stats": system_stats,
110
- "db_stats": db_stats,
111
- }
112
 
113
- return jsonify(doc)
 
 
 
 
 
 
 
 
 
 
114
 
 
 
115
 
116
- @app.route("/api")
117
- def api_root():
118
  try:
119
- conn = get_db_connection()
 
120
  cur = conn.cursor()
121
- cur.execute("SELECT 1;")
122
- result = cur.fetchone()[0]
123
  cur.close()
124
  conn.close()
125
- db_status = "ok" if result == 1 else "unexpected_result"
126
  except Exception as e:
127
- db_status = f"error: {e}"
 
 
 
 
 
 
128
 
129
  return jsonify({
130
- "status": "ok",
131
- "db_status": db_status,
 
 
132
  })
133
 
 
 
 
 
134
 
135
- @app.route("/api/query", methods=["POST"])
136
- def api_query():
137
  data = request.get_json(silent=True) or {}
138
  query = data.get("query")
139
-
140
  if not query:
141
  return jsonify({"status": "error", "error": "Missing 'query' in JSON body"}), 400
142
 
 
143
  try:
144
- conn = get_db_connection()
145
  cur = conn.cursor()
146
  cur.execute(query)
147
 
@@ -157,7 +279,6 @@ def api_query():
157
 
158
  cur.close()
159
  conn.close()
160
-
161
  return jsonify({
162
  "status": "ok",
163
  "rows": rows,
@@ -168,6 +289,5 @@ def api_query():
168
  "error": str(e),
169
  }), 500
170
 
171
-
172
  if __name__ == "__main__":
173
  app.run(host="0.0.0.0", port=7860)
 
1
  import os
2
+ import time
3
+ import uuid
4
  import psutil
5
+ import mysql.connector
6
+ from flask import Flask, jsonify, request, abort, render_template_string
7
 
8
  app = Flask(__name__)
9
 
10
+ MYSQL_CONFIG = {
11
+ "host": os.getenv("MYSQL_HOST", "localhost"),
12
+ "port": int(os.getenv("MYSQL_PORT", 3306)),
13
+ "user": os.getenv("MYSQL_USER", "root"),
14
+ "password": os.getenv("MYSQL_PASSWORD", "password"),
 
15
  }
16
 
17
+ MIN_DB_MB = 500
18
+ MAX_DB_MB = 5000
19
+ TOTAL_MAX_MB = 25000
20
+
21
+ start_time = time.time()
22
+ db_registry = {}
23
+
24
+ DASHBOARD_TEMPLATE = """
25
+ <!doctype html>
26
+ <html>
27
+ <head>
28
+ <title>MySQL DB Manager Dashboard</title>
29
+ <style>
30
+ body { font-family: Arial, sans-serif; margin: 20px; }
31
+ h1, h2 { margin-bottom: 0.3em; }
32
+ .section { margin-bottom: 2em; }
33
+ table { border-collapse: collapse; width: 100%; margin-top: 0.5em; }
34
+ th, td { border: 1px solid #ddd; padding: 6px 8px; font-size: 14px; }
35
+ th { background: #f5f5f5; text-align: left; }
36
+ code { background: #f0f0f0; padding: 2px 4px; }
37
+ .small { font-size: 12px; color: #666; }
38
+ input[type="text"], input[type="number"] { padding: 4px; }
39
+ button { padding: 6px 10px; cursor: pointer; }
40
+ .error { color: #b00020; }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <h1>MySQL DB Manager Dashboard</h1>
45
+
46
+ <div class="section">
47
+ <h2>System & Storage</h2>
48
+ <p><b>Uptime:</b> {{ uptime_seconds }} seconds</p>
49
+ <p><b>CPU usage:</b> {{ system_stats.cpu.usage_percent }}% (cores: {{ system_stats.cpu.cores_detected }})</p>
50
+ <p><b>RAM usage:</b> {{ '%.2f' % system_stats.ram.used_gb }} GB / {{ '%.2f' % system_stats.ram.total_gb }} GB ({{ system_stats.ram.usage_percent }}%)</p>
51
+ <p><b>Disk usage:</b> {{ '%.2f' % system_stats.disk.used_gb }} GB / {{ '%.2f' % system_stats.disk.total_gb }} GB ({{ system_stats.disk.usage_percent }}%)</p>
52
+ <p><b>DB Allocated Size:</b> {{ total_allocated_mb }} MB / {{ total_max_mb }} MB</p>
53
+ </div>
54
+
55
+ <div class="section">
56
+ <h2>Create New Database</h2>
57
+ <form id="create-form">
58
+ <label>Database size (MB, {{ min_db_mb }}-{{ max_db_mb }}):</label>
59
+ <input type="number" id="size_mb" value="{{ min_db_mb }}" min="{{ min_db_mb }}" max="{{ max_db_mb }}">
60
+ <button type="submit">Create</button>
61
+ <span class="small">This reserves capacity; size is not strictly enforced by MySQL itself.</span>
62
+ <div class="error" id="create-error"></div>
63
+ <div id="create-success" class="small"></div>
64
+ </form>
65
+ </div>
66
 
67
+ <div class="section">
68
+ <h2>Existing Databases</h2>
69
+ {% if dbs %}
70
+ <table>
71
+ <tr>
72
+ <th>UUID</th>
73
+ <th>DB Name</th>
74
+ <th>Allocated Size (MB)</th>
75
+ <th>Created At (unix)</th>
76
+ <th>Example Query Endpoint</th>
77
+ </tr>
78
+ {% for db in dbs %}
79
+ <tr>
80
+ <td><code>{{ db.uuid }}</code></td>
81
+ <td><code>{{ db.name }}</code></td>
82
+ <td>{{ db.size_mb }}</td>
83
+ <td>{{ db.created_at }}</td>
84
+ <td><code>/api/{{ db.uuid }}/query</code></td>
85
+ </tr>
86
+ {% endfor %}
87
+ </table>
88
+ {% else %}
89
+ <p>No databases created yet.</p>
90
+ {% endif %}
91
+ </div>
92
 
93
+ <div class="section">
94
+ <h2>API Reference</h2>
95
+ <ul>
96
+ <li><code>GET /health</code> – returns uptime and basic health info</li>
97
+ <li><code>POST /api/db</code> – create a new DB. Body JSON: <code>{"size_mb": 500}</code></li>
98
+ <li><code>POST /api/&lt;uuid&gt;/query</code> – run a SQL query on that DB. Body JSON: <code>{"query": "SELECT 1"}</code></li>
99
+ </ul>
100
+ </div>
101
 
102
+ <script>
103
+ document.getElementById('create-form').addEventListener('submit', async function(e) {
104
+ e.preventDefault();
105
+ const sizeMb = parseInt(document.getElementById('size_mb').value, 10);
106
+ const errEl = document.getElementById('create-error');
107
+ const okEl = document.getElementById('create-success');
108
+ errEl.textContent = '';
109
+ okEl.textContent = '';
110
+ try {
111
+ const res = await fetch('/api/db', {
112
+ method: 'POST',
113
+ headers: {'Content-Type': 'application/json'},
114
+ body: JSON.stringify({ size_mb: sizeMb })
115
+ });
116
+ const data = await res.json();
117
+ if (!res.ok) {
118
+ errEl.textContent = data.error || 'Error';
119
+ } else {
120
+ okEl.textContent = 'Created DB ' + data.db_name + ' with UUID ' + data.uuid +
121
+ '. Endpoint: /api/' + data.uuid + '/query';
122
+ setTimeout(() => location.reload(), 1000);
123
+ }
124
+ } catch (e) {
125
+ errEl.textContent = 'Request failed';
126
+ }
127
+ });
128
+ </script>
129
+
130
+ </body>
131
+ </html>
132
+ """
133
+
134
+ def get_mysql_connection(database=None):
135
+ cfg = MYSQL_CONFIG.copy()
136
+ if database:
137
+ cfg["database"] = database
138
+ return mysql.connector.connect(**cfg)
139
 
140
  def get_system_stats():
141
  cpu_percent = psutil.cpu_percent(interval=0.1)
142
  cpu_count = psutil.cpu_count(logical=False) or psutil.cpu_count()
 
143
  vm = psutil.virtual_memory()
144
  ram_total_gb = vm.total / (1024**3)
145
  ram_used_gb = (vm.total - vm.available) / (1024**3)
146
  ram_percent = vm.percent
 
147
  disk = psutil.disk_usage("/")
148
  disk_total_gb = disk.total / (1024**3)
149
  disk_used_gb = disk.used / (1024**3)
150
  disk_percent = disk.percent
 
151
  return {
152
  "cpu": {
153
  "usage_percent": cpu_percent,
154
  "cores_detected": cpu_count,
 
155
  },
156
  "ram": {
157
  "total_gb": ram_total_gb,
158
  "used_gb": ram_used_gb,
159
  "usage_percent": ram_percent,
 
160
  },
161
  "disk": {
162
  "total_gb": disk_total_gb,
163
  "used_gb": disk_used_gb,
164
  "usage_percent": disk_percent,
 
165
  },
166
  }
167
 
168
+ def total_allocated_mb():
169
+ return sum(db["size_mb"] for db in db_registry.values())
170
+
171
+ @app.route("/")
172
+ def dashboard():
173
+ system_stats = get_system_stats()
174
+ uptime_seconds = int(time.time() - start_time)
175
+ dbs = [
176
+ {
177
+ "uuid": k,
178
+ "name": v["db_name"],
179
+ "size_mb": v["size_mb"],
180
+ "created_at": int(v["created_at"]),
181
+ }
182
+ for k, v in db_registry.items()
183
+ ]
184
+ return render_template_string(
185
+ DASHBOARD_TEMPLATE,
186
+ system_stats=system_stats,
187
+ uptime_seconds=uptime_seconds,
188
+ total_allocated_mb=total_allocated_mb(),
189
+ total_max_mb=TOTAL_MAX_MB,
190
+ min_db_mb=MIN_DB_MB,
191
+ max_db_mb=MAX_DB_MB,
192
+ dbs=dbs,
193
+ )
194
 
195
+ @app.route("/health")
196
+ def health():
197
+ uptime_seconds = int(time.time() - start_time)
198
  try:
199
+ conn = get_mysql_connection()
200
  cur = conn.cursor()
201
+ cur.execute("SELECT 1")
202
+ cur.fetchone()
 
 
203
  cur.close()
204
  conn.close()
205
+ db_status = "ok"
 
 
 
 
 
206
  except Exception as e:
207
+ db_status = f"error: {e}"
208
+ return jsonify({
209
+ "status": "ok",
210
+ "uptime_seconds": uptime_seconds,
211
+ "db_status": db_status,
212
+ "total_allocated_mb": total_allocated_mb(),
213
+ "total_max_mb": TOTAL_MAX_MB,
214
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ @app.route("/api/db", methods=["POST"])
217
+ def create_db():
218
+ data = request.get_json(silent=True) or {}
219
+ size_mb = data.get("size_mb")
220
+ if not isinstance(size_mb, (int, float)):
221
+ return jsonify({"error": "size_mb must be a number"}), 400
222
+ size_mb = int(size_mb)
223
+ if size_mb < MIN_DB_MB or size_mb > MAX_DB_MB:
224
+ return jsonify({"error": f"size_mb must be between {MIN_DB_MB} and {MAX_DB_MB} MB"}), 400
225
+ if total_allocated_mb() + size_mb > TOTAL_MAX_MB:
226
+ return jsonify({"error": "Total allocated size would exceed 25GB limit"}), 400
227
 
228
+ db_uuid = str(uuid.uuid4())
229
+ db_name = f"db_{db_uuid.replace('-', '')[:16]}"
230
 
 
 
231
  try:
232
+ conn = get_mysql_connection()
233
+ conn.autocommit = True
234
  cur = conn.cursor()
235
+ cur.execute(f"CREATE DATABASE `{db_name}`")
 
236
  cur.close()
237
  conn.close()
 
238
  except Exception as e:
239
+ return jsonify({"error": f"Failed to create database: {e}"}), 500
240
+
241
+ db_registry[db_uuid] = {
242
+ "db_name": db_name,
243
+ "size_mb": size_mb,
244
+ "created_at": time.time(),
245
+ }
246
 
247
  return jsonify({
248
+ "uuid": db_uuid,
249
+ "db_name": db_name,
250
+ "size_mb": size_mb,
251
+ "query_endpoint": f"/api/{db_uuid}/query",
252
  })
253
 
254
+ @app.route("/api/<db_uuid>/query", methods=["POST"])
255
+ def db_query(db_uuid):
256
+ if db_uuid not in db_registry:
257
+ abort(404, description="Unknown database UUID")
258
 
 
 
259
  data = request.get_json(silent=True) or {}
260
  query = data.get("query")
 
261
  if not query:
262
  return jsonify({"status": "error", "error": "Missing 'query' in JSON body"}), 400
263
 
264
+ db_name = db_registry[db_uuid]["db_name"]
265
  try:
266
+ conn = get_mysql_connection(database=db_name)
267
  cur = conn.cursor()
268
  cur.execute(query)
269
 
 
279
 
280
  cur.close()
281
  conn.close()
 
282
  return jsonify({
283
  "status": "ok",
284
  "rows": rows,
 
289
  "error": str(e),
290
  }), 500
291
 
 
292
  if __name__ == "__main__":
293
  app.run(host="0.0.0.0", port=7860)