zeltera commited on
Commit
0c03a43
Β·
verified Β·
1 Parent(s): 7a82799

Upload 10 files

Browse files
Files changed (10) hide show
  1. .gitignore +17 -0
  2. Dockerfile +16 -0
  3. README.md +33 -11
  4. app.py +416 -0
  5. requirements.txt +0 -0
  6. static/script.js +211 -0
  7. static/style.css +217 -0
  8. templates/index.html +169 -0
  9. templates/login.html +337 -0
  10. templates/register.html +402 -0
.gitignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ venv/
6
+
7
+ # Database
8
+ *.db
9
+ instance/
10
+
11
+ # IDE
12
+ .vscode/
13
+ .idea/
14
+
15
+ # OS
16
+ .DS_Store
17
+ Thumbs.db
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install dependencies
6
+ COPY requirements.txt .
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ # Copy all files
10
+ COPY . .
11
+
12
+ # Expose port (HF Spaces uses 7860)
13
+ EXPOSE 7860
14
+
15
+ # Run the app
16
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,11 +1,33 @@
1
- ---
2
- title: Ai Cms Platform
3
- emoji: πŸš€
4
- colorFrom: blue
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: AI CMS Platform
3
+ emoji: πŸš€
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # AI CMS Platform
12
+
13
+ A dynamic API builder powered by AI prompts.
14
+
15
+ ## Features
16
+
17
+ - πŸ€– Create API endpoints with AI prompts
18
+ - πŸ“Š Monitor usage and costs
19
+ - πŸ” Secure with API keys
20
+ - πŸ“₯ Export logs to CSV
21
+
22
+ ## Usage
23
+
24
+ 1. Register an account
25
+ 2. Create your first API endpoint
26
+ 3. Test with Postman or curl
27
+ 4. Monitor usage in dashboard
28
+
29
+ ## Tech Stack
30
+
31
+ - Flask (Python)
32
+ - SQLite Database
33
+ - Modern UI with CSS3
app.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, send_file, flash
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
4
+ from flask_bcrypt import Bcrypt
5
+ from datetime import datetime, timedelta
6
+ from flask_cors import CORS
7
+ import time
8
+ import random
9
+ import json
10
+ import csv
11
+ import io
12
+ import secrets
13
+ import os
14
+
15
+ app = Flask(__name__)
16
+ CORS(app, supports_credentials=True)
17
+ # --- CONFIGURATION ---
18
+ app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///ai_cms.db')
19
+ app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-change-in-production')
20
+ app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
21
+ 'pool_recycle': 300,
22
+ 'pool_pre_ping': True,
23
+ }
24
+
25
+ db = SQLAlchemy(app)
26
+ bcrypt = Bcrypt(app)
27
+ login_manager = LoginManager(app)
28
+ login_manager.login_view = 'login'
29
+
30
+ # --- DATABASE MODELS ---
31
+
32
+ class User(UserMixin, db.Model):
33
+ __tablename__ = 'users'
34
+
35
+ id = db.Column(db.Integer, primary_key=True)
36
+ username = db.Column(db.String(80), unique=True, nullable=False)
37
+ password_hash = db.Column(db.String(120), nullable=False)
38
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
39
+ is_admin = db.Column(db.Boolean, default=False)
40
+
41
+ def set_password(self, password):
42
+ self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
43
+
44
+ def check_password(self, password):
45
+ return bcrypt.check_password_hash(self.password_hash, password)
46
+
47
+ class APIKey(db.Model):
48
+ __tablename__ = 'api_keys'
49
+
50
+ id = db.Column(db.Integer, primary_key=True)
51
+ key = db.Column(db.String(64), unique=True, nullable=False)
52
+ user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
53
+ name = db.Column(db.String(100))
54
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
55
+ last_used = db.Column(db.DateTime)
56
+ is_active = db.Column(db.Boolean, default=True)
57
+ usage_count = db.Column(db.Integer, default=0)
58
+
59
+ user = db.relationship('User', backref='api_keys')
60
+
61
+ @staticmethod
62
+ def generate_key():
63
+ return secrets.token_hex(32)
64
+
65
+ def to_dict(self):
66
+ return {
67
+ 'id': self.id,
68
+ 'name': self.name,
69
+ 'key': self.key,
70
+ 'created_at': self.created_at.strftime("%Y-%m-%d %H:%M:%S"),
71
+ 'last_used': self.last_used.strftime("%Y-%m-%d %H:%M:%S") if self.last_used else 'Never',
72
+ 'usage_count': self.usage_count,
73
+ 'is_active': self.is_active
74
+ }
75
+
76
+ class APIEndpoint(db.Model):
77
+ __tablename__ = 'api_endpoints'
78
+
79
+ id = db.Column(db.Integer, primary_key=True)
80
+ user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
81
+ route = db.Column(db.String(100), nullable=False)
82
+ model = db.Column(db.String(50), nullable=False)
83
+ prompt = db.Column(db.Text, nullable=False)
84
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
85
+ is_active = db.Column(db.Boolean, default=True)
86
+
87
+ total_calls = db.Column(db.Integer, default=0)
88
+ total_tokens = db.Column(db.Integer, default=0)
89
+ total_cost = db.Column(db.Float, default=0.0)
90
+
91
+ user = db.relationship('User', backref='endpoints')
92
+ # Add cascade delete for logs
93
+ logs = db.relationship('APILog', backref='endpoint', lazy=True, cascade='all, delete-orphan')
94
+
95
+ def to_dict(self):
96
+ return {
97
+ 'id': self.id,
98
+ 'route': self.route,
99
+ 'model': self.model,
100
+ 'prompt': self.prompt,
101
+ 'created_at': self.created_at.strftime("%Y-%m-%d %H:%M:%S"),
102
+ 'is_active': self.is_active,
103
+ 'total_calls': self.total_calls,
104
+ 'total_tokens': self.total_tokens,
105
+ 'total_cost': round(self.total_cost, 4)
106
+ }
107
+
108
+ class APILog(db.Model):
109
+ __tablename__ = 'api_logs'
110
+
111
+ id = db.Column(db.Integer, primary_key=True)
112
+ endpoint_id = db.Column(db.Integer, db.ForeignKey('api_endpoints.id', ondelete='CASCADE'), nullable=False)
113
+ api_key_id = db.Column(db.Integer, db.ForeignKey('api_keys.id', ondelete='SET NULL'), nullable=True)
114
+ timestamp = db.Column(db.DateTime, default=datetime.utcnow)
115
+ input_data = db.Column(db.Text)
116
+ output_data = db.Column(db.Text)
117
+ tokens_used = db.Column(db.Integer)
118
+ latency = db.Column(db.Float)
119
+ cost = db.Column(db.Float)
120
+ status_code = db.Column(db.Integer, default=200)
121
+
122
+ api_key = db.relationship('APIKey', backref='logs')
123
+
124
+ def to_dict(self):
125
+ return {
126
+ 'id': self.id,
127
+ 'endpoint_id': self.endpoint_id,
128
+ 'timestamp': self.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
129
+ 'tokens_used': self.tokens_used,
130
+ 'latency': round(self.latency, 3),
131
+ 'cost': round(self.cost, 4),
132
+ 'status_code': self.status_code
133
+ }
134
+
135
+ # --- LOGIN MANAGER ---
136
+ @login_manager.user_loader
137
+ def load_user(user_id):
138
+ return User.query.get(int(user_id))
139
+
140
+ # --- MOCK AI RESPONSE ---
141
+ def generate_mock_response(prompt, input_data):
142
+ time.sleep(random.uniform(0.5, 1.5))
143
+ return f"AI Response based on prompt: '{prompt[:30]}...' and input: {json.dumps(input_data)}"
144
+
145
+ # --- AUTH ROUTES ---
146
+
147
+ @app.route('/login', methods=['GET', 'POST'])
148
+ def login():
149
+ if current_user.is_authenticated:
150
+ return redirect(url_for('dashboard'))
151
+
152
+ if request.method == 'POST':
153
+ data = request.json if request.is_json else request.form
154
+ username = data.get('username')
155
+ password = data.get('password')
156
+
157
+ user = User.query.filter_by(username=username).first()
158
+
159
+ if user and user.check_password(password):
160
+ login_user(user)
161
+ if request.is_json:
162
+ return jsonify({"status": "success", "message": "Login successful"})
163
+ return redirect(url_for('dashboard'))
164
+
165
+ if request.is_json:
166
+ return jsonify({"status": "error", "message": "Invalid credentials"}), 401
167
+ flash('Invalid username or password', 'error')
168
+
169
+ return render_template('login.html')
170
+
171
+ @app.route('/register', methods=['GET', 'POST'])
172
+ def register():
173
+ if current_user.is_authenticated:
174
+ return redirect(url_for('dashboard'))
175
+
176
+ if request.method == 'POST':
177
+ data = request.json if request.is_json else request.form
178
+ username = data.get('username')
179
+ password = data.get('password')
180
+
181
+ if User.query.filter_by(username=username).first():
182
+ if request.is_json:
183
+ return jsonify({"status": "error", "message": "Username exists"}), 400
184
+ flash('Username already exists', 'error')
185
+ return render_template('register.html')
186
+
187
+ user = User(username=username)
188
+ user.set_password(password)
189
+
190
+ # First user becomes admin
191
+ if User.query.count() == 0:
192
+ user.is_admin = True
193
+
194
+ db.session.add(user)
195
+ db.session.commit()
196
+
197
+ if request.is_json:
198
+ return jsonify({"status": "success", "message": "Registration successful"})
199
+ return redirect(url_for('login'))
200
+
201
+ return render_template('register.html')
202
+
203
+ @app.route('/logout')
204
+ @login_required
205
+ def logout():
206
+ logout_user()
207
+ return redirect(url_for('login'))
208
+
209
+ # --- FRONTEND ROUTES ---
210
+
211
+ @app.route('/')
212
+ @login_required
213
+ def dashboard():
214
+ return render_template('index.html')
215
+
216
+ # --- CMS API ENDPOINTS ---
217
+
218
+ @app.route('/api/cms/create', methods=['POST'])
219
+ @login_required
220
+ def create_api():
221
+ data = request.json
222
+
223
+ existing = APIEndpoint.query.filter_by(route=data['route'], user_id=current_user.id).first()
224
+ if existing:
225
+ return jsonify({"status": "error", "message": "Route already exists"}), 400
226
+
227
+ new_api = APIEndpoint(
228
+ user_id=current_user.id,
229
+ route=data['route'],
230
+ model=data['model'],
231
+ prompt=data['prompt']
232
+ )
233
+
234
+ db.session.add(new_api)
235
+ db.session.commit()
236
+
237
+ return jsonify({"status": "success", "message": "API Created Successfully", "api": new_api.to_dict()})
238
+
239
+ @app.route('/api/cms/list', methods=['GET'])
240
+ @login_required
241
+ def list_apis():
242
+ apis = APIEndpoint.query.filter_by(user_id=current_user.id).all()
243
+ return jsonify([api.to_dict() for api in apis])
244
+
245
+ @app.route('/api/cms/stats', methods=['GET'])
246
+ @login_required
247
+ def get_stats():
248
+ apis = APIEndpoint.query.filter_by(user_id=current_user.id).all()
249
+
250
+ total_calls = sum(api.total_calls for api in apis)
251
+ total_tokens = sum(api.total_tokens for api in apis)
252
+ total_cost = sum(api.total_cost for api in apis)
253
+
254
+ return jsonify({
255
+ "total_calls": total_calls,
256
+ "total_tokens": total_tokens,
257
+ "total_cost": round(total_cost, 4),
258
+ "active_apis": len([api for api in apis if api.is_active])
259
+ })
260
+
261
+ @app.route('/api/cms/logs', methods=['GET'])
262
+ @login_required
263
+ def get_logs():
264
+ user_endpoint_ids = [api.id for api in APIEndpoint.query.filter_by(user_id=current_user.id).all()]
265
+ logs = APILog.query.filter(APILog.endpoint_id.in_(user_endpoint_ids)).order_by(APILog.timestamp.desc()).limit(50).all()
266
+ return jsonify([log.to_dict() for log in logs])
267
+
268
+ @app.route('/api/cms/delete/<int:api_id>', methods=['DELETE'])
269
+ @login_required
270
+ def delete_api(api_id):
271
+ api = APIEndpoint.query.filter_by(id=api_id, user_id=current_user.id).first_or_404()
272
+ db.session.delete(api)
273
+ db.session.commit()
274
+ return jsonify({"status": "success", "message": "API Deleted"})
275
+
276
+ @app.route('/api/cms/export', methods=['GET'])
277
+ @login_required
278
+ def export_logs():
279
+ user_endpoint_ids = [api.id for api in APIEndpoint.query.filter_by(user_id=current_user.id).all()]
280
+ logs = APILog.query.filter(APILog.endpoint_id.in_(user_endpoint_ids)).order_by(APILog.timestamp.desc()).all()
281
+
282
+ output = io.StringIO()
283
+ writer = csv.writer(output)
284
+ writer.writerow(['Timestamp', 'Endpoint', 'Tokens', 'Latency', 'Cost', 'Status'])
285
+
286
+ for log in logs:
287
+ writer.writerow([
288
+ log.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
289
+ log.endpoint.route,
290
+ log.tokens_used,
291
+ round(log.latency, 3),
292
+ round(log.cost, 4),
293
+ log.status_code
294
+ ])
295
+
296
+ output.seek(0)
297
+ return send_file(
298
+ io.BytesIO(output.getvalue().encode('utf-8')),
299
+ mimetype='text/csv',
300
+ as_attachment=True,
301
+ download_name=f'api_logs_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'
302
+ )
303
+
304
+ # --- API KEY MANAGEMENT ---
305
+
306
+ @app.route('/api/cms/keys', methods=['GET'])
307
+ @login_required
308
+ def list_keys():
309
+ keys = APIKey.query.filter_by(user_id=current_user.id).all()
310
+ return jsonify([k.to_dict() for k in keys])
311
+
312
+ @app.route('/api/cms/keys', methods=['POST'])
313
+ @login_required
314
+ def create_key():
315
+ data = request.json
316
+ new_key = APIKey(
317
+ user_id=current_user.id,
318
+ key=APIKey.generate_key(),
319
+ name=data.get('name', 'Unnamed Key')
320
+ )
321
+ db.session.add(new_key)
322
+ db.session.commit()
323
+ return jsonify({"status": "success", "key": new_key.key})
324
+
325
+ @app.route('/api/cms/keys/<int:key_id>', methods=['DELETE'])
326
+ @login_required
327
+ def delete_key(key_id):
328
+ key = APIKey.query.filter_by(id=key_id, user_id=current_user.id).first_or_404()
329
+ db.session.delete(key)
330
+ db.session.commit()
331
+ return jsonify({"status": "success", "message": "Key Deleted"})
332
+
333
+ @app.route('/api/cms/keys/<int:key_id>/toggle', methods=['POST'])
334
+ @login_required
335
+ def toggle_key(key_id):
336
+ key = APIKey.query.filter_by(id=key_id, user_id=current_user.id).first_or_404()
337
+ key.is_active = not key.is_active
338
+ db.session.commit()
339
+ return jsonify({"status": "success", "is_active": key.is_active})
340
+
341
+ # --- DYNAMIC USER API ENDPOINTS ---
342
+
343
+ @app.route('/user-api/<path:route>', methods=['GET', 'POST'])
344
+ def dynamic_endpoint(route):
345
+ api_key = request.headers.get('X-API-Key') or request.args.get('api_key')
346
+
347
+ target_api = APIEndpoint.query.filter_by(route=route, is_active=True).first()
348
+
349
+ if not target_api:
350
+ return jsonify({"error": "Endpoint not found"}), 404
351
+
352
+ api_key_obj = None
353
+ if api_key:
354
+ api_key_obj = APIKey.query.filter_by(key=api_key, is_active=True).first()
355
+ if not api_key_obj:
356
+ return jsonify({"error": "Invalid API Key"}), 401
357
+
358
+ api_key_obj.last_used = datetime.utcnow()
359
+ api_key_obj.usage_count += 1
360
+
361
+ start_time = time.time()
362
+ input_data = request.json if request.is_json else request.args.to_dict()
363
+
364
+ response_text = generate_mock_response(target_api.prompt, input_data)
365
+
366
+ latency = time.time() - start_time
367
+ tokens_used = len(response_text) // 4
368
+ cost = (tokens_used / 1000) * 0.002
369
+
370
+ target_api.total_calls += 1
371
+ target_api.total_tokens += tokens_used
372
+ target_api.total_cost += cost
373
+
374
+ log = APILog(
375
+ endpoint_id=target_api.id,
376
+ api_key_id=api_key_obj.id if api_key_obj else None,
377
+ input_data=json.dumps(input_data),
378
+ output_data=response_text,
379
+ tokens_used=tokens_used,
380
+ latency=latency,
381
+ cost=cost,
382
+ status_code=200
383
+ )
384
+
385
+ db.session.add(log)
386
+ db.session.commit()
387
+
388
+ return jsonify({
389
+ "response": response_text,
390
+ "meta": {
391
+ "model": target_api.model,
392
+ "latency": round(latency, 3),
393
+ "tokens": tokens_used,
394
+ "cost": round(cost, 4)
395
+ }
396
+ })
397
+
398
+ # --- INITIALIZE DATABASE ---
399
+ def init_db():
400
+ with app.app_context():
401
+ db.create_all()
402
+
403
+ if User.query.count() == 0:
404
+ admin = User(username='admin', is_admin=True)
405
+ admin.set_password('admin123')
406
+ db.session.add(admin)
407
+ db.session.commit()
408
+ print("βœ… Default admin created (username: admin, password: admin123)")
409
+
410
+ print("βœ… Database initialized!")
411
+
412
+ if __name__ == '__main__':
413
+ init_db()
414
+ # Hugging Face Spaces uses port 7860
415
+ port = int(os.environ.get('PORT', 7860))
416
+ app.run(host='0.0.0.0', port=port, debug=False)
requirements.txt ADDED
Binary file (816 Bytes). View file
 
static/script.js ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Navigation Logic
2
+ function showSection(sectionId) {
3
+ document.querySelectorAll('.section').forEach(el => el.classList.remove('active'));
4
+ document.querySelectorAll('.nav-links li').forEach(el => el.classList.remove('active'));
5
+
6
+ document.getElementById(sectionId).classList.add('active');
7
+ event.target.classList.add('active');
8
+
9
+ if(sectionId === 'dashboard') loadStats();
10
+ if(sectionId === 'list') loadApis();
11
+ }
12
+
13
+ // Load Stats
14
+ async function loadStats() {
15
+ const res = await fetch('/api/cms/stats');
16
+ const data = await res.json();
17
+
18
+ document.getElementById('stat-calls').innerText = data.total_calls;
19
+ document.getElementById('stat-tokens').innerText = data.total_tokens;
20
+ document.getElementById('stat-cost').innerText = '$' + data.total_cost.toFixed(4);
21
+ document.getElementById('stat-apis').innerText = data.active_apis;
22
+ }
23
+
24
+ // Create API
25
+ document.getElementById('create-form').addEventListener('submit', async (e) => {
26
+ e.preventDefault();
27
+
28
+ const payload = {
29
+ route: document.getElementById('api-route').value,
30
+ model: document.getElementById('api-model').value,
31
+ prompt: document.getElementById('api-prompt').value
32
+ };
33
+
34
+ const res = await fetch('/api/cms/create', {
35
+ method: 'POST',
36
+ headers: {'Content-Type': 'application/json'},
37
+ body: JSON.stringify(payload)
38
+ });
39
+
40
+ if(res.ok) {
41
+ alert('API Created! You can now POST to: http://localhost:5000/user-api/' + payload.route);
42
+ document.getElementById('create-form').reset();
43
+ showSection('list');
44
+ }
45
+ });
46
+
47
+ async function loadLogs() {
48
+ const res = await fetch('/api/cms/logs');
49
+ const logs = await res.json();
50
+ const container = document.getElementById('logs-container');
51
+
52
+ if (logs.length === 0) {
53
+ container.innerHTML = '<p class="sub-text">No API calls yet</p>';
54
+ return;
55
+ }
56
+
57
+ let html = '<table style="width:100%; font-size:0.85rem;">';
58
+ html += '<tr><th>Time</th><th>Tokens</th><th>Latency</th><th>Cost</th></tr>';
59
+
60
+ logs.slice(0, 10).forEach(log => {
61
+ html += `
62
+ <tr>
63
+ <td>${log.timestamp.split(' ')[1]}</td>
64
+ <td>${log.tokens_used}</td>
65
+ <td>${log.latency}s</td>
66
+ <td>$${log.cost}</td>
67
+ </tr>
68
+ `;
69
+ });
70
+
71
+ html += '</table>';
72
+ container.innerHTML = html;
73
+ }
74
+
75
+ // Update showSection function to load logs
76
+ function showSection(sectionId) {
77
+ document.querySelectorAll('.section').forEach(el => el.classList.remove('active'));
78
+ document.querySelectorAll('.nav-links li').forEach(el => el.classList.remove('active'));
79
+
80
+ document.getElementById(sectionId).classList.add('active');
81
+ event.target.classList.add('active');
82
+
83
+ if(sectionId === 'dashboard') {
84
+ loadStats();
85
+ loadLogs();
86
+ }
87
+ if(sectionId === 'list') loadApis();
88
+ }
89
+
90
+ // Load API List
91
+ async function loadApis() {
92
+ const res = await fetch('/api/cms/list');
93
+ const apis = await res.json();
94
+ const tbody = document.getElementById('api-table-body');
95
+
96
+ tbody.innerHTML = '';
97
+
98
+ apis.forEach(api => {
99
+ const row = `
100
+ <tr>
101
+ <td><span class="endpoint-url">/user-api/${api.route}</span></td>
102
+ <td>${api.model}</td>
103
+ <td>${api.total_calls}</td>
104
+ <td>
105
+ <button onclick="testApi('${api.route}')" style="padding:5px; cursor:pointer;">Test</button>
106
+ <button onclick="deleteApi(${api.id})" style="padding:5px; cursor:pointer; color:red;">Delete</button>
107
+ </td>
108
+ </tr>
109
+ `;
110
+ tbody.innerHTML += row;
111
+ });
112
+ }
113
+
114
+ // Add delete function
115
+ async function deleteApi(id) {
116
+ if(confirm('Are you sure you want to delete this API?')) {
117
+ await fetch(`/api/cms/delete/${id}`, { method: 'DELETE' });
118
+ loadApis();
119
+ loadStats();
120
+ }
121
+ }
122
+
123
+ // API Keys Functions
124
+ async function loadKeys() {
125
+ const res = await fetch('/api/cms/keys');
126
+ const keys = await res.json();
127
+ const tbody = document.getElementById('keys-table-body');
128
+
129
+ tbody.innerHTML = '';
130
+
131
+ keys.forEach(key => {
132
+ const row = `
133
+ <tr>
134
+ <td>${key.name}</td>
135
+ <td><code style="background:#eee; padding:2px 6px; font-size:0.75rem;">${key.key}</code></td>
136
+ <td>${key.usage_count}</td>
137
+ <td><span style="color:${key.is_active ? 'green' : 'red'}">● ${key.is_active ? 'Active' : 'Inactive'}</span></td>
138
+ <td>
139
+ <button onclick="toggleKey(${key.id})" style="padding:5px; cursor:pointer;">${key.is_active ? 'Deactivate' : 'Activate'}</button>
140
+ <button onclick="deleteKey(${key.id})" style="padding:5px; cursor:pointer; color:red;">Delete</button>
141
+ </td>
142
+ </tr>
143
+ `;
144
+ tbody.innerHTML += row;
145
+ });
146
+ }
147
+
148
+ async function createKey() {
149
+ const name = document.getElementById('key-name').value || 'Unnamed Key';
150
+
151
+ const res = await fetch('/api/cms/keys', {
152
+ method: 'POST',
153
+ headers: {'Content-Type': 'application/json'},
154
+ body: JSON.stringify({name})
155
+ });
156
+
157
+ const data = await res.json();
158
+
159
+ if (res.ok) {
160
+ alert('New API Key Generated:\n\n' + data.key + '\n\nSave this key! It won\'t be shown again.');
161
+ document.getElementById('key-name').value = '';
162
+ loadKeys();
163
+ }
164
+ }
165
+
166
+ async function toggleKey(id) {
167
+ await fetch(`/api/cms/keys/${id}/toggle`, {method: 'POST'});
168
+ loadKeys();
169
+ }
170
+
171
+ async function deleteKey(id) {
172
+ if(confirm('Delete this API Key?')) {
173
+ await fetch(`/api/cms/keys/${id}`, {method: 'DELETE'});
174
+ loadKeys();
175
+ }
176
+ }
177
+
178
+ // Export Function
179
+ function exportLogs() {
180
+ window.open('/api/cms/export', '_blank');
181
+ }
182
+
183
+ // Update showSection to handle keys
184
+ function showSection(sectionId) {
185
+ document.querySelectorAll('.section').forEach(el => el.classList.remove('active'));
186
+ document.querySelectorAll('.nav-links li').forEach(el => el.classList.remove('active'));
187
+
188
+ document.getElementById(sectionId).classList.add('active');
189
+ event.target.classList.add('active');
190
+
191
+ if(sectionId === 'dashboard') {
192
+ loadStats();
193
+ loadLogs();
194
+ }
195
+ if(sectionId === 'list') loadApis();
196
+ if(sectionId === 'keys') loadKeys();
197
+ }
198
+
199
+ // Simple Test Function
200
+ async function testApi(route) {
201
+ const res = await fetch(`/user-api/${route}`, {
202
+ method: 'POST',
203
+ headers: {'Content-Type': 'application/json'},
204
+ body: JSON.stringify({test: "data"})
205
+ });
206
+ const data = await res.json();
207
+ alert("Response:\n" + JSON.stringify(data, null, 2));
208
+ }
209
+
210
+ // Initial Load
211
+ loadStats();
static/style.css ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg-color: #F0F4F8; /* Light Blue/Grey background */
3
+ --card-bg: #FFFFFF;
4
+ --primary: #0055FF; /* Electric Blue */
5
+ --primary-hover: #0044CC;
6
+ --text-main: #000000;
7
+ --text-sub: #555555;
8
+ --border: #000000;
9
+ --border-width: 3px; /* Bold Stroke */
10
+ --shadow: 6px 6px 0px #000000; /* Hard Shadow */
11
+ }
12
+
13
+ * {
14
+ box-sizing: border-box;
15
+ margin: 0;
16
+ padding: 0;
17
+ font-family: 'Inter', sans-serif;
18
+ }
19
+
20
+ body {
21
+ background-color: var(--bg-color);
22
+ color: var(--text-main);
23
+ display: flex;
24
+ min-height: 100vh;
25
+ }
26
+
27
+ /* Sidebar */
28
+ .sidebar {
29
+ width: 250px;
30
+ background: var(--card-bg);
31
+ border-right: var(--border-width) solid var(--border);
32
+ padding: 2rem;
33
+ display: flex;
34
+ flex-direction: column;
35
+ }
36
+
37
+ .logo {
38
+ font-size: 1.5rem;
39
+ font-weight: 800;
40
+ margin-bottom: 3rem;
41
+ letter-spacing: -1px;
42
+ }
43
+
44
+ .logo span {
45
+ color: var(--primary);
46
+ }
47
+
48
+ .nav-links {
49
+ list-style: none;
50
+ }
51
+
52
+ .nav-links li {
53
+ padding: 1rem;
54
+ margin-bottom: 0.5rem;
55
+ font-weight: 600;
56
+ cursor: pointer;
57
+ border: 2px solid transparent;
58
+ transition: all 0.2s;
59
+ }
60
+
61
+ .nav-links li:hover, .nav-links li.active {
62
+ border: 2px solid var(--border);
63
+ background: var(--primary);
64
+ color: white;
65
+ box-shadow: 4px 4px 0px var(--border);
66
+ transform: translate(-2px, -2px);
67
+ }
68
+
69
+ /* Main Content */
70
+ .content {
71
+ flex: 1;
72
+ padding: 3rem;
73
+ overflow-y: auto;
74
+ }
75
+
76
+ .section {
77
+ display: none;
78
+ animation: fadeIn 0.3s ease;
79
+ }
80
+
81
+ .section.active {
82
+ display: block;
83
+ }
84
+
85
+ @keyframes fadeIn {
86
+ from { opacity: 0; transform: translateY(10px); }
87
+ to { opacity: 1; transform: translateY(0); }
88
+ }
89
+
90
+ header {
91
+ margin-bottom: 2rem;
92
+ border-bottom: 2px solid var(--border);
93
+ padding-bottom: 1rem;
94
+ }
95
+
96
+ h1 {
97
+ font-size: 2.5rem;
98
+ font-weight: 800;
99
+ letter-spacing: -1px;
100
+ text-transform: uppercase;
101
+ }
102
+
103
+ p {
104
+ color: var(--text-sub);
105
+ font-size: 0.9rem;
106
+ }
107
+
108
+ /* Cards & UI Elements */
109
+ .card {
110
+ background: var(--card-bg);
111
+ border: var(--border-width) solid var(--border);
112
+ box-shadow: var(--shadow);
113
+ padding: 1.5rem;
114
+ margin-bottom: 1.5rem;
115
+ border-radius: 4px; /* Slight rounding */
116
+ }
117
+
118
+ .stats-grid {
119
+ display: grid;
120
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
121
+ gap: 1.5rem;
122
+ margin-bottom: 2rem;
123
+ }
124
+
125
+ .stat-card {
126
+ display: flex;
127
+ flex-direction: column;
128
+ justify-content: center;
129
+ }
130
+
131
+ .label {
132
+ font-size: 0.8rem;
133
+ font-weight: 600;
134
+ text-transform: uppercase;
135
+ color: var(--text-sub);
136
+ margin-bottom: 0.5rem;
137
+ }
138
+
139
+ .value {
140
+ font-size: 2rem;
141
+ font-weight: 800;
142
+ color: var(--primary);
143
+ }
144
+
145
+ /* Forms */
146
+ .form-group {
147
+ margin-bottom: 1.5rem;
148
+ }
149
+
150
+ label {
151
+ display: block;
152
+ font-weight: 700;
153
+ margin-bottom: 0.5rem;
154
+ }
155
+
156
+ input, select, textarea {
157
+ width: 100%;
158
+ padding: 1rem;
159
+ border: 2px solid var(--border);
160
+ background: #FAFAFA;
161
+ font-size: 1rem;
162
+ outline: none;
163
+ transition: 0.2s;
164
+ }
165
+
166
+ input:focus, select:focus, textarea:focus {
167
+ background: #E0F0FF; /* Light Blue focus */
168
+ border-color: var(--primary);
169
+ }
170
+
171
+ .btn-primary {
172
+ width: 100%;
173
+ padding: 1rem;
174
+ background: var(--primary);
175
+ color: white;
176
+ border: 2px solid var(--border);
177
+ font-weight: 700;
178
+ cursor: pointer;
179
+ box-shadow: 4px 4px 0px var(--border);
180
+ transition: all 0.2s;
181
+ }
182
+
183
+ .btn-primary:active {
184
+ transform: translate(2px, 2px);
185
+ box-shadow: 2px 2px 0px var(--border);
186
+ }
187
+
188
+ /* Table */
189
+ table {
190
+ width: 100%;
191
+ border-collapse: collapse;
192
+ }
193
+
194
+ th, td {
195
+ text-align: left;
196
+ padding: 1rem;
197
+ border-bottom: 2px solid var(--border);
198
+ }
199
+
200
+ th {
201
+ background: #000;
202
+ color: #fff;
203
+ text-transform: uppercase;
204
+ font-size: 0.8rem;
205
+ }
206
+
207
+ td {
208
+ font-weight: 600;
209
+ }
210
+
211
+ .endpoint-url {
212
+ font-family: monospace;
213
+ background: #eee;
214
+ padding: 2px 6px;
215
+ border-radius: 4px;
216
+ font-size: 0.8rem;
217
+ }
templates/index.html ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI CMS Platform</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
9
+ </head>
10
+ <body>
11
+
12
+ <!-- Sidebar Navigation -->
13
+ <nav class="sidebar">
14
+ <div class="logo">AI<span>CMS</span></div>
15
+ <ul class="nav-links">
16
+ <li onclick="showSection('dashboard')" class="active">Dashboard</li>
17
+ <li onclick="showSection('create')">Create API</li>
18
+ <li onclick="showSection('list')">API List</li>
19
+ <li onclick="showSection('keys')">API Keys</li>
20
+ </ul>
21
+ </nav>
22
+
23
+ <!-- Main Content -->
24
+ <main class="content">
25
+
26
+ <!-- SECTION: DASHBOARD -->
27
+ <div class="card recent-logs">
28
+ <h3>Recent API Calls</h3>
29
+ <div id="logs-container" style="margin-top: 1rem;">
30
+ <p class="sub-text">Loading logs...</p>
31
+ </div>
32
+ </div>
33
+ <section id="dashboard" class="section active">
34
+ <header>
35
+ <h1>Overview</h1>
36
+ <p>Monitor your AI endpoints in real-time.</p>
37
+ </header>
38
+
39
+ <div class="stats-grid">
40
+ <div class="card stat-card">
41
+ <span class="label">Total Calls</span>
42
+ <span class="value" id="stat-calls">0</span>
43
+ </div>
44
+ <div class="card stat-card">
45
+ <span class="label">Tokens Used</span>
46
+ <span class="value" id="stat-tokens">0</span>
47
+ </div>
48
+ <div class="card stat-card">
49
+ <span class="label">Est. Cost</span>
50
+ <span class="value" id="stat-cost">$0.00</span>
51
+ </div>
52
+ <div class="card stat-card">
53
+ <span class="label">Active Endpoints</span>
54
+ <span class="value" id="stat-apis">0</span>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="card recent-logs">
59
+ <h3>System Status</h3>
60
+ <p class="status-ok">● System Operational</p>
61
+ <p class="sub-text">All dynamic routes are live.</p>
62
+ </div>
63
+ </section>
64
+
65
+ <!-- SECTION: CREATE API -->
66
+ <section id="create" class="section">
67
+ <header>
68
+ <h1>Create New Endpoint</h1>
69
+ <p>Define logic using natural language.</p>
70
+ </header>
71
+
72
+ <form id="create-form" class="card form-card">
73
+ <div class="form-group">
74
+ <label>Endpoint Route</label>
75
+ <input type="text" id="api-route" placeholder="e.g. summarize-text" required>
76
+ </div>
77
+
78
+ <div class="form-group">
79
+ <label>AI Model</label>
80
+ <select id="api-model">
81
+ <option value="gpt-4o">GPT-4o</option>
82
+ <option value="gpt-3.5-turbo">GPT-3.5 Turbo</option>
83
+ <option value="claude-3">Claude 3.5 Sonnet</option>
84
+ <option value="llama-3">Llama 3 (Local)</option>
85
+ </select>
86
+ </div>
87
+
88
+ <div class="form-group">
89
+ <label>System Prompt (Logic)</label>
90
+ <textarea id="api-prompt" rows="5" placeholder="e.g. You are a helpful assistant that summarizes text..." required></textarea>
91
+ </div>
92
+
93
+ <button type="submit" class="btn-primary">Deploy Endpoint</button>
94
+ </form>
95
+ </section>
96
+
97
+ <!-- SECTION: API LIST -->
98
+ <section id="list" class="section">
99
+ <header>
100
+ <h1>Active Endpoints</h1>
101
+ <p>Copy the URL below to test in Postman.</p>
102
+ </header>
103
+
104
+ <div class="card table-card">
105
+ <table>
106
+ <thead>
107
+ <tr>
108
+ <th>Route</th>
109
+ <th>Model</th>
110
+ <th>Calls</th>
111
+ <th>Action</th>
112
+ </tr>
113
+ </thead>
114
+ <tbody id="api-table-body">
115
+ <!-- Populated by JS -->
116
+ </tbody>
117
+ </table>
118
+ </div>
119
+ </section>
120
+ <section id="keys" class="section">
121
+ <header>
122
+ <h1>API Keys</h1>
123
+ <p>Manage keys for securing your endpoints.</p>
124
+ </header>
125
+
126
+ <div class="card form-card" style="margin-bottom: 1.5rem;">
127
+ <div class="form-group">
128
+ <label>Key Name</label>
129
+ <input type="text" id="key-name" placeholder="e.g. Production Key">
130
+ </div>
131
+ <button onclick="createKey()" class="btn-primary">Generate New Key</button>
132
+ </div>
133
+
134
+ <div class="card table-card">
135
+ <table>
136
+ <thead>
137
+ <tr>
138
+ <th>Name</th>
139
+ <th>Key</th>
140
+ <th>Usage</th>
141
+ <th>Status</th>
142
+ <th>Action</th>
143
+ </tr>
144
+ </thead>
145
+ <tbody id="keys-table-body"></tbody>
146
+ </table>
147
+ </div>
148
+ </section>
149
+
150
+ <div class="card recent-logs" style="margin-top: 1rem;">
151
+ <div style="display: flex; justify-content: space-between; align-items: center;">
152
+ <h3>Recent API Calls</h3>
153
+ <button onclick="exportLogs()" class="btn-primary" style="width: auto; padding: 0.5rem 1rem;">πŸ“₯ Export CSV</button>
154
+ </div>
155
+ <div id="logs-container" style="margin-top: 1rem;">
156
+ <p class="sub-text">Loading logs...</p>
157
+ </div>
158
+ </div>
159
+
160
+ <!-- Add Logout in Sidebar -->
161
+ <div style="margin-top: auto; padding-top: 2rem; border-top: 2px solid var(--border);">
162
+ <a href="/logout" style="color: var(--text-main); text-decoration: none; font-weight: 600;">Logout β†’</a>
163
+ </div>
164
+
165
+ </main>
166
+
167
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
168
+ </body>
169
+ </html>
templates/login.html ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login - AI CMS</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Inter', sans-serif;
18
+ min-height: 100vh;
19
+ display: flex;
20
+ justify-content: center;
21
+ align-items: center;
22
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #0a0a0a 100%);
23
+ overflow: hidden;
24
+ position: relative;
25
+ }
26
+
27
+ /* Animated Background */
28
+ .bg-animation {
29
+ position: fixed;
30
+ top: 0;
31
+ left: 0;
32
+ width: 100%;
33
+ height: 100%;
34
+ z-index: 0;
35
+ overflow: hidden;
36
+ }
37
+
38
+ .bg-animation span {
39
+ position: absolute;
40
+ display: block;
41
+ width: 20px;
42
+ height: 20px;
43
+ background: rgba(0, 85, 255, 0.1);
44
+ animation: float 25s linear infinite;
45
+ bottom: -150px;
46
+ border-radius: 4px;
47
+ }
48
+
49
+ .bg-animation span:nth-child(1) { left: 5%; width: 80px; height: 80px; animation-delay: 0s; }
50
+ .bg-animation span:nth-child(2) { left: 15%; width: 40px; height: 40px; animation-delay: 2s; animation-duration: 18s; }
51
+ .bg-animation span:nth-child(3) { left: 25%; width: 60px; height: 60px; animation-delay: 4s; }
52
+ .bg-animation span:nth-child(4) { left: 35%; width: 100px; height: 100px; animation-delay: 0s; animation-duration: 22s; }
53
+ .bg-animation span:nth-child(5) { left: 45%; width: 50px; height: 50px; animation-delay: 6s; }
54
+ .bg-animation span:nth-child(6) { left: 55%; width: 90px; height: 90px; animation-delay: 3s; }
55
+ .bg-animation span:nth-child(7) { left: 65%; width: 30px; height: 30px; animation-delay: 7s; }
56
+ .bg-animation span:nth-child(8) { left: 75%; width: 70px; height: 70px; animation-delay: 2s; animation-duration: 20s; }
57
+ .bg-animation span:nth-child(9) { left: 85%; width: 45px; height: 45px; animation-delay: 5s; }
58
+ .bg-animation span:nth-child(10) { left: 95%; width: 85px; height: 85px; animation-delay: 1s; }
59
+
60
+ @keyframes float {
61
+ 0% {
62
+ transform: translateY(0) rotate(0deg);
63
+ opacity: 1;
64
+ }
65
+ 100% {
66
+ transform: translateY(-1200px) rotate(720deg);
67
+ opacity: 0;
68
+ }
69
+ }
70
+
71
+ /* Grid Pattern Overlay */
72
+ .bg-grid {
73
+ position: fixed;
74
+ top: 0;
75
+ left: 0;
76
+ width: 100%;
77
+ height: 100%;
78
+ background-image:
79
+ linear-gradient(rgba(0, 85, 255, 0.03) 1px, transparent 1px),
80
+ linear-gradient(90deg, rgba(0, 85, 255, 0.03) 1px, transparent 1px);
81
+ background-size: 50px 50px;
82
+ z-index: 1;
83
+ }
84
+
85
+ /* 3D Card Container */
86
+ .auth-container {
87
+ position: relative;
88
+ z-index: 10;
89
+ perspective: 1000px;
90
+ padding: 2rem;
91
+ }
92
+
93
+ .auth-card {
94
+ width: 100%;
95
+ max-width: 500px;
96
+ padding: 3rem;
97
+ background: rgba(255, 255, 255, 0.95);
98
+ border: 3px solid #000000;
99
+ box-shadow:
100
+ 15px 15px 0px #000000,
101
+ 0 0 40px rgba(0, 85, 255, 0.3);
102
+ border-radius: 8px;
103
+ transform-style: preserve-3d;
104
+ animation: cardFloat 6s ease-in-out infinite;
105
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
106
+ }
107
+
108
+ .auth-card:hover {
109
+ transform: translate(-3px, -3px) rotateX(2deg) rotateY(-2deg);
110
+ box-shadow:
111
+ 18px 18px 0px #000000,
112
+ 0 0 60px rgba(0, 85, 255, 0.5);
113
+ }
114
+
115
+ @keyframes cardFloat {
116
+ 0%, 100% { transform: translateY(0); }
117
+ 50% { transform: translateY(-10px); }
118
+ }
119
+
120
+ .auth-card h1 {
121
+ text-align: center;
122
+ margin-bottom: 0.5rem;
123
+ font-size: 3rem;
124
+ letter-spacing: -2px;
125
+ font-weight: 800;
126
+ transform: translateZ(20px);
127
+ }
128
+
129
+ .auth-card .subtitle {
130
+ text-align: center;
131
+ color: #555555;
132
+ margin-bottom: 2.5rem;
133
+ font-size: 1rem;
134
+ transform: translateZ(10px);
135
+ }
136
+
137
+ .form-group {
138
+ margin-bottom: 1.5rem;
139
+ transform: translateZ(5px);
140
+ }
141
+
142
+ label {
143
+ display: block;
144
+ font-weight: 700;
145
+ margin-bottom: 0.5rem;
146
+ font-size: 0.9rem;
147
+ text-transform: uppercase;
148
+ letter-spacing: 0.5px;
149
+ }
150
+
151
+ input {
152
+ width: 100%;
153
+ padding: 1rem;
154
+ border: 2px solid #000000;
155
+ background: #FAFAFA;
156
+ font-size: 1rem;
157
+ outline: none;
158
+ transition: all 0.2s;
159
+ border-radius: 4px;
160
+ }
161
+
162
+ input:focus {
163
+ background: #E0F0FF;
164
+ border-color: #0055FF;
165
+ box-shadow: 0 0 0 3px rgba(0, 85, 255, 0.2);
166
+ }
167
+
168
+ .btn-primary {
169
+ width: 100%;
170
+ padding: 1rem;
171
+ background: #0055FF;
172
+ color: white;
173
+ border: 2px solid #000000;
174
+ font-weight: 700;
175
+ cursor: pointer;
176
+ box-shadow: 4px 4px 0px #000000;
177
+ transition: all 0.2s;
178
+ font-size: 1rem;
179
+ text-transform: uppercase;
180
+ letter-spacing: 1px;
181
+ border-radius: 4px;
182
+ margin-top: 1rem;
183
+ }
184
+
185
+ .btn-primary:hover {
186
+ background: #0044CC;
187
+ transform: translate(-2px, -2px);
188
+ box-shadow: 6px 6px 0px #000000;
189
+ }
190
+
191
+ .btn-primary:active {
192
+ transform: translate(2px, 2px);
193
+ box-shadow: 2px 2px 0px #000000;
194
+ }
195
+
196
+ .auth-links {
197
+ text-align: center;
198
+ margin-top: 2rem;
199
+ padding-top: 1.5rem;
200
+ border-top: 2px solid #000000;
201
+ transform: translateZ(10px);
202
+ }
203
+
204
+ .auth-links a {
205
+ color: #0055FF;
206
+ text-decoration: none;
207
+ font-weight: 600;
208
+ transition: all 0.2s;
209
+ }
210
+
211
+ .auth-links a:hover {
212
+ text-decoration: underline;
213
+ color: #0044CC;
214
+ }
215
+
216
+ .error-message {
217
+ background: #f8d7da;
218
+ border: 2px solid #dc3545;
219
+ padding: 1rem;
220
+ margin-bottom: 1rem;
221
+ border-radius: 4px;
222
+ text-align: center;
223
+ font-weight: 600;
224
+ color: #721c24;
225
+ animation: shake 0.5s ease;
226
+ }
227
+
228
+ @keyframes shake {
229
+ 0%, 100% { transform: translateX(0); }
230
+ 25% { transform: translateX(-5px); }
231
+ 75% { transform: translateX(5px); }
232
+ }
233
+
234
+ /* Logo Animation */
235
+ .logo-text {
236
+ display: inline-block;
237
+ animation: logoPulse 2s ease-in-out infinite;
238
+ }
239
+
240
+ @keyframes logoPulse {
241
+ 0%, 100% { transform: scale(1); }
242
+ 50% { transform: scale(1.02); }
243
+ }
244
+ </style>
245
+ </head>
246
+ <body>
247
+ <!-- Animated Background -->
248
+ <div class="bg-animation">
249
+ <span></span>
250
+ <span></span>
251
+ <span></span>
252
+ <span></span>
253
+ <span></span>
254
+ <span></span>
255
+ <span></span>
256
+ <span></span>
257
+ <span></span>
258
+ <span></span>
259
+ </div>
260
+
261
+ <!-- Grid Overlay -->
262
+ <div class="bg-grid"></div>
263
+
264
+ <div class="auth-container">
265
+ <div class="card auth-card">
266
+ <h1><span class="logo-text">AI</span><span style="color:#0055FF">CMS</span></h1>
267
+ <p class="subtitle">Sign in to your account</p>
268
+
269
+ <div id="message-container"></div>
270
+
271
+ <form id="login-form">
272
+ <div class="form-group">
273
+ <label>Username</label>
274
+ <input type="text" id="username" placeholder="Enter your username" required autocomplete="username">
275
+ </div>
276
+ <div class="form-group">
277
+ <label>Password</label>
278
+ <input type="password" id="password" placeholder="Enter your password" required autocomplete="current-password">
279
+ </div>
280
+ <button type="submit" class="btn-primary">Sign In</button>
281
+ </form>
282
+
283
+ <div class="auth-links">
284
+ <p>Don't have an account? <a href="/register">Create one here</a></p>
285
+ </div>
286
+ </div>
287
+ </div>
288
+
289
+ <script>
290
+ // 3D Tilt Effect
291
+ const card = document.querySelector('.auth-card');
292
+ const container = document.querySelector('.auth-container');
293
+
294
+ container.addEventListener('mousemove', (e) => {
295
+ const rect = container.getBoundingClientRect();
296
+ const x = e.clientX - rect.left;
297
+ const y = e.clientY - rect.top;
298
+ const centerX = rect.width / 2;
299
+ const centerY = rect.height / 2;
300
+ const rotateX = (y - centerY) / 20;
301
+ const rotateY = (centerX - x) / 20;
302
+
303
+ card.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
304
+ });
305
+
306
+ container.addEventListener('mouseleave', () => {
307
+ card.style.transform = 'rotateX(0) rotateY(0)';
308
+ });
309
+
310
+ // Login Form
311
+ document.getElementById('login-form').addEventListener('submit', async (e) => {
312
+ e.preventDefault();
313
+
314
+ const res = await fetch('/login', {
315
+ method: 'POST',
316
+ headers: {'Content-Type': 'application/json'},
317
+ body: JSON.stringify({
318
+ username: document.getElementById('username').value,
319
+ password: document.getElementById('password').value
320
+ })
321
+ });
322
+
323
+ const data = await res.json();
324
+ const messageContainer = document.getElementById('message-container');
325
+
326
+ if (res.ok) {
327
+ messageContainer.innerHTML = '<div style="background:#d4edda;border:2px solid #28a745;padding:1rem;margin-bottom:1rem;border-radius:4px;text-align:center;font-weight:600;color:#155724;">Login successful! Redirecting...</div>';
328
+ setTimeout(() => {
329
+ window.location.href = '/';
330
+ }, 1000);
331
+ } else {
332
+ messageContainer.innerHTML = `<div class="error-message">${data.message}</div>`;
333
+ }
334
+ });
335
+ </script>
336
+ </body>
337
+ </html>
templates/register.html ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Register - AI CMS</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Inter', sans-serif;
18
+ min-height: 100vh;
19
+ display: flex;
20
+ justify-content: center;
21
+ align-items: center;
22
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #0a0a0a 100%);
23
+ overflow: hidden;
24
+ position: relative;
25
+ }
26
+
27
+ /* Animated Background */
28
+ .bg-animation {
29
+ position: fixed;
30
+ top: 0;
31
+ left: 0;
32
+ width: 100%;
33
+ height: 100%;
34
+ z-index: 0;
35
+ overflow: hidden;
36
+ }
37
+
38
+ .bg-animation span {
39
+ position: absolute;
40
+ display: block;
41
+ width: 20px;
42
+ height: 20px;
43
+ background: rgba(0, 85, 255, 0.1);
44
+ animation: float 25s linear infinite;
45
+ bottom: -150px;
46
+ border-radius: 4px;
47
+ }
48
+
49
+ .bg-animation span:nth-child(1) { left: 5%; width: 80px; height: 80px; animation-delay: 0s; }
50
+ .bg-animation span:nth-child(2) { left: 15%; width: 40px; height: 40px; animation-delay: 2s; animation-duration: 18s; }
51
+ .bg-animation span:nth-child(3) { left: 25%; width: 60px; height: 60px; animation-delay: 4s; }
52
+ .bg-animation span:nth-child(4) { left: 35%; width: 100px; height: 100px; animation-delay: 0s; animation-duration: 22s; }
53
+ .bg-animation span:nth-child(5) { left: 45%; width: 50px; height: 50px; animation-delay: 6s; }
54
+ .bg-animation span:nth-child(6) { left: 55%; width: 90px; height: 90px; animation-delay: 3s; }
55
+ .bg-animation span:nth-child(7) { left: 65%; width: 30px; height: 30px; animation-delay: 7s; }
56
+ .bg-animation span:nth-child(8) { left: 75%; width: 70px; height: 70px; animation-delay: 2s; animation-duration: 20s; }
57
+ .bg-animation span:nth-child(9) { left: 85%; width: 45px; height: 45px; animation-delay: 5s; }
58
+ .bg-animation span:nth-child(10) { left: 95%; width: 85px; height: 85px; animation-delay: 1s; }
59
+
60
+ @keyframes float {
61
+ 0% {
62
+ transform: translateY(0) rotate(0deg);
63
+ opacity: 1;
64
+ }
65
+ 100% {
66
+ transform: translateY(-1200px) rotate(720deg);
67
+ opacity: 0;
68
+ }
69
+ }
70
+
71
+ /* Grid Pattern Overlay */
72
+ .bg-grid {
73
+ position: fixed;
74
+ top: 0;
75
+ left: 0;
76
+ width: 100%;
77
+ height: 100%;
78
+ background-image:
79
+ linear-gradient(rgba(0, 85, 255, 0.03) 1px, transparent 1px),
80
+ linear-gradient(90deg, rgba(0, 85, 255, 0.03) 1px, transparent 1px);
81
+ background-size: 50px 50px;
82
+ z-index: 1;
83
+ }
84
+
85
+ /* 3D Card Container */
86
+ .auth-container {
87
+ position: relative;
88
+ z-index: 10;
89
+ perspective: 1000px;
90
+ padding: 2rem;
91
+ }
92
+
93
+ .auth-card {
94
+ width: 100%;
95
+ max-width: 550px;
96
+ padding: 3rem;
97
+ background: rgba(255, 255, 255, 0.95);
98
+ border: 3px solid #000000;
99
+ box-shadow:
100
+ 15px 15px 0px #000000,
101
+ 0 0 40px rgba(0, 85, 255, 0.3);
102
+ border-radius: 8px;
103
+ transform-style: preserve-3d;
104
+ animation: cardFloat 6s ease-in-out infinite;
105
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
106
+ }
107
+
108
+ .auth-card:hover {
109
+ transform: translate(-3px, -3px) rotateX(2deg) rotateY(-2deg);
110
+ box-shadow:
111
+ 18px 18px 0px #000000,
112
+ 0 0 60px rgba(0, 85, 255, 0.5);
113
+ }
114
+
115
+ @keyframes cardFloat {
116
+ 0%, 100% { transform: translateY(0); }
117
+ 50% { transform: translateY(-10px); }
118
+ }
119
+
120
+ .auth-card h1 {
121
+ text-align: center;
122
+ margin-bottom: 0.5rem;
123
+ font-size: 3rem;
124
+ letter-spacing: -2px;
125
+ font-weight: 800;
126
+ transform: translateZ(20px);
127
+ }
128
+
129
+ .auth-card .subtitle {
130
+ text-align: center;
131
+ color: #555555;
132
+ margin-bottom: 2.5rem;
133
+ font-size: 1rem;
134
+ transform: translateZ(10px);
135
+ }
136
+
137
+ .form-group {
138
+ margin-bottom: 1.5rem;
139
+ transform: translateZ(5px);
140
+ }
141
+
142
+ .form-row {
143
+ display: grid;
144
+ grid-template-columns: 1fr 1fr;
145
+ gap: 1rem;
146
+ }
147
+
148
+ label {
149
+ display: block;
150
+ font-weight: 700;
151
+ margin-bottom: 0.5rem;
152
+ font-size: 0.9rem;
153
+ text-transform: uppercase;
154
+ letter-spacing: 0.5px;
155
+ }
156
+
157
+ input {
158
+ width: 100%;
159
+ padding: 1rem;
160
+ border: 2px solid #000000;
161
+ background: #FAFAFA;
162
+ font-size: 1rem;
163
+ outline: none;
164
+ transition: all 0.2s;
165
+ border-radius: 4px;
166
+ }
167
+
168
+ input:focus {
169
+ background: #E0F0FF;
170
+ border-color: #0055FF;
171
+ box-shadow: 0 0 0 3px rgba(0, 85, 255, 0.2);
172
+ }
173
+
174
+ .btn-primary {
175
+ width: 100%;
176
+ padding: 1rem;
177
+ background: #0055FF;
178
+ color: white;
179
+ border: 2px solid #000000;
180
+ font-weight: 700;
181
+ cursor: pointer;
182
+ box-shadow: 4px 4px 0px #000000;
183
+ transition: all 0.2s;
184
+ font-size: 1rem;
185
+ text-transform: uppercase;
186
+ letter-spacing: 1px;
187
+ border-radius: 4px;
188
+ margin-top: 1rem;
189
+ }
190
+
191
+ .btn-primary:hover {
192
+ background: #0044CC;
193
+ transform: translate(-2px, -2px);
194
+ box-shadow: 6px 6px 0px #000000;
195
+ }
196
+
197
+ .btn-primary:active {
198
+ transform: translate(2px, 2px);
199
+ box-shadow: 2px 2px 0px #000000;
200
+ }
201
+
202
+ .auth-links {
203
+ text-align: center;
204
+ margin-top: 2rem;
205
+ padding-top: 1.5rem;
206
+ border-top: 2px solid #000000;
207
+ transform: translateZ(10px);
208
+ }
209
+
210
+ .auth-links a {
211
+ color: #0055FF;
212
+ text-decoration: none;
213
+ font-weight: 600;
214
+ transition: all 0.2s;
215
+ }
216
+
217
+ .auth-links a:hover {
218
+ text-decoration: underline;
219
+ color: #0044CC;
220
+ }
221
+
222
+ .password-strength {
223
+ margin-top: 0.5rem;
224
+ font-size: 0.75rem;
225
+ color: #555555;
226
+ }
227
+
228
+ .success-message {
229
+ background: #d4edda;
230
+ border: 2px solid #28a745;
231
+ padding: 1rem;
232
+ margin-bottom: 1rem;
233
+ border-radius: 4px;
234
+ text-align: center;
235
+ font-weight: 600;
236
+ color: #155724;
237
+ animation: slideIn 0.3s ease;
238
+ }
239
+
240
+ .error-message {
241
+ background: #f8d7da;
242
+ border: 2px solid #dc3545;
243
+ padding: 1rem;
244
+ margin-bottom: 1rem;
245
+ border-radius: 4px;
246
+ text-align: center;
247
+ font-weight: 600;
248
+ color: #721c24;
249
+ animation: shake 0.5s ease;
250
+ }
251
+
252
+ @keyframes shake {
253
+ 0%, 100% { transform: translateX(0); }
254
+ 25% { transform: translateX(-5px); }
255
+ 75% { transform: translateX(5px); }
256
+ }
257
+
258
+ @keyframes slideIn {
259
+ from { opacity: 0; transform: translateY(-10px); }
260
+ to { opacity: 1; transform: translateY(0); }
261
+ }
262
+
263
+ /* Logo Animation */
264
+ .logo-text {
265
+ display: inline-block;
266
+ animation: logoPulse 2s ease-in-out infinite;
267
+ }
268
+
269
+ @keyframes logoPulse {
270
+ 0%, 100% { transform: scale(1); }
271
+ 50% { transform: scale(1.02); }
272
+ }
273
+ </style>
274
+ </head>
275
+ <body>
276
+ <!-- Animated Background -->
277
+ <div class="bg-animation">
278
+ <span></span>
279
+ <span></span>
280
+ <span></span>
281
+ <span></span>
282
+ <span></span>
283
+ <span></span>
284
+ <span></span>
285
+ <span></span>
286
+ <span></span>
287
+ <span></span>
288
+ </div>
289
+
290
+ <!-- Grid Overlay -->
291
+ <div class="bg-grid"></div>
292
+
293
+ <div class="auth-container">
294
+ <div class="card auth-card">
295
+ <h1><span class="logo-text">AI</span><span style="color:#0055FF">CMS</span></h1>
296
+ <p class="subtitle">Create your account to get started</p>
297
+
298
+ <div id="message-container"></div>
299
+
300
+ <form id="register-form">
301
+ <div class="form-group">
302
+ <label>Username</label>
303
+ <input type="text" id="username" placeholder="Choose a username" required autocomplete="username">
304
+ </div>
305
+
306
+ <div class="form-row">
307
+ <div class="form-group">
308
+ <label>Password</label>
309
+ <input type="password" id="password" placeholder="Min. 6 characters" required autocomplete="new-password" minlength="6">
310
+ </div>
311
+ <div class="form-group">
312
+ <label>Confirm Password</label>
313
+ <input type="password" id="confirm-password" placeholder="Re-enter password" required autocomplete="new-password">
314
+ </div>
315
+ </div>
316
+ <p class="password-strength">πŸ”’ Must be at least 6 characters</p>
317
+
318
+ <button type="submit" class="btn-primary">Create Account</button>
319
+ </form>
320
+
321
+ <div class="auth-links">
322
+ <p>Already have an account? <a href="/login">Login here</a></p>
323
+ </div>
324
+ </div>
325
+ </div>
326
+
327
+ <script>
328
+ // 3D Tilt Effect
329
+ const card = document.querySelector('.auth-card');
330
+ const container = document.querySelector('.auth-container');
331
+
332
+ container.addEventListener('mousemove', (e) => {
333
+ const rect = container.getBoundingClientRect();
334
+ const x = e.clientX - rect.left;
335
+ const y = e.clientY - rect.top;
336
+ const centerX = rect.width / 2;
337
+ const centerY = rect.height / 2;
338
+ const rotateX = (y - centerY) / 20;
339
+ const rotateY = (centerX - x) / 20;
340
+
341
+ card.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
342
+ });
343
+
344
+ container.addEventListener('mouseleave', () => {
345
+ card.style.transform = 'rotateX(0) rotateY(0)';
346
+ });
347
+
348
+ // Register Form
349
+ document.getElementById('register-form').addEventListener('submit', async (e) => {
350
+ e.preventDefault();
351
+
352
+ const password = document.getElementById('password').value;
353
+ const confirmPassword = document.getElementById('confirm-password').value;
354
+ const messageContainer = document.getElementById('message-container');
355
+
356
+ if (password !== confirmPassword) {
357
+ messageContainer.innerHTML = '<div class="error-message">❌ Passwords do not match!</div>';
358
+ return;
359
+ }
360
+
361
+ if (password.length < 6) {
362
+ messageContainer.innerHTML = '<div class="error-message">❌ Password must be at least 6 characters!</div>';
363
+ return;
364
+ }
365
+
366
+ const res = await fetch('/register', {
367
+ method: 'POST',
368
+ headers: {'Content-Type': 'application/json'},
369
+ body: JSON.stringify({
370
+ username: document.getElementById('username').value,
371
+ password: password
372
+ })
373
+ });
374
+
375
+ const data = await res.json();
376
+
377
+ if (res.ok) {
378
+ messageContainer.innerHTML = '<div class="success-message">βœ… Account created successfully! Redirecting...</div>';
379
+ setTimeout(() => {
380
+ window.location.href = '/login';
381
+ }, 1500);
382
+ } else {
383
+ messageContainer.innerHTML = `<div class="error-message">❌ ${data.message}</div>`;
384
+ }
385
+ });
386
+
387
+ // Real-time password match validation
388
+ document.getElementById('confirm-password').addEventListener('input', function() {
389
+ const password = document.getElementById('password').value;
390
+ const confirmPassword = this.value;
391
+
392
+ if (confirmPassword && password !== confirmPassword) {
393
+ this.style.borderColor = '#dc3545';
394
+ this.style.background = '#fff5f5';
395
+ } else {
396
+ this.style.borderColor = '#000000';
397
+ this.style.background = '#FAFAFA';
398
+ }
399
+ });
400
+ </script>
401
+ </body>
402
+ </html>