zeltera commited on
Commit
dbd0087
·
verified ·
1 Parent(s): 0c03a43

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +52 -14
  2. script.js +251 -0
app.py CHANGED
@@ -16,7 +16,12 @@ 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,
@@ -38,6 +43,12 @@ class User(UserMixin, db.Model):
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
 
@@ -150,21 +161,39 @@ def login():
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
 
@@ -381,6 +410,15 @@ def dynamic_endpoint(route):
381
  cost=cost,
382
  status_code=200
383
  )
 
 
 
 
 
 
 
 
 
384
 
385
  db.session.add(log)
386
  db.session.commit()
 
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['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
20
  app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-change-in-production')
21
+ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
22
+ app.config['SESSION_COOKIE_SECURE'] = False
23
+ app.config['SESSION_COOKIE_HTTPONLY'] = True
24
+ app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
25
  app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
26
  'pool_recycle': 300,
27
  'pool_pre_ping': True,
 
43
  created_at = db.Column(db.DateTime, default=datetime.utcnow)
44
  is_admin = db.Column(db.Boolean, default=False)
45
 
46
+ # Flask-Login already provides is_authenticated via UserMixin
47
+ # But you can override if needed:
48
+ @property
49
+ def is_authenticated(self):
50
+ return True
51
+
52
  def set_password(self, password):
53
  self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
54
 
 
161
  return redirect(url_for('dashboard'))
162
 
163
  if request.method == 'POST':
164
+ try:
165
+ data = request.get_json() if request.is_json else request.form
166
+ username = data.get('username') if data else None
167
+ password = data.get('password') if data else None
168
+
169
+ if not username or not password:
170
+ if request.is_json:
171
+ return jsonify({"status": "error", "message": "Username and password required"}), 400
172
+ flash('Username and password required', 'error')
173
+ return render_template('login.html')
174
+
175
+ user = User.query.filter_by(username=username).first()
176
+
177
+ if user and user.check_password(password):
178
+ login_user(user, remember=True) # Add remember=True
179
+
180
+ # Set session as permanent
181
+ session.permanent = True
182
+
183
+ next_page = request.args.get('next')
184
+ if request.is_json:
185
+ return jsonify({"status": "success", "message": "Login successful"})
186
+ return redirect(next_page) if next_page else redirect(url_for('dashboard'))
187
+
188
  if request.is_json:
189
+ return jsonify({"status": "error", "message": "Invalid credentials"}), 401
190
+ flash('Invalid username or password', 'error')
191
+
192
+ except Exception as e:
193
+ print(f"Login error: {str(e)}")
194
+ if request.is_json:
195
+ return jsonify({"status": "error", "message": "Internal server error"}), 500
196
+ flash('An error occurred', 'error')
197
 
198
  return render_template('login.html')
199
 
 
410
  cost=cost,
411
  status_code=200
412
  )
413
+
414
+ @app.route('/debug-session')
415
+ def debug_session():
416
+ return jsonify({
417
+ 'is_authenticated': current_user.is_authenticated,
418
+ 'user_id': current_user.id if current_user.is_authenticated else None,
419
+ 'username': current_user.username if current_user.is_authenticated else None,
420
+ 'session_id': session.get('_id', 'No session')
421
+ })
422
 
423
  db.session.add(log)
424
  db.session.commit()
script.js ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ document.getElementById('login-form').addEventListener('submit', async (e) => {
48
+ e.preventDefault();
49
+
50
+ const messageContainer = document.getElementById('message-container');
51
+ messageContainer.innerHTML = '<div style="background:#fff3cd;border:2px solid #ffc107;padding:1rem;margin-bottom:1rem;border-radius:4px;text-align:center;">Logging in...</div>';
52
+
53
+ try {
54
+ const res = await fetch('/login', {
55
+ method: 'POST',
56
+ headers: {'Content-Type': 'application/json'},
57
+ body: JSON.stringify({
58
+ username: document.getElementById('username').value,
59
+ password: document.getElementById('password').value
60
+ }),
61
+ credentials: 'include' // Important! Include cookies
62
+ });
63
+
64
+ const contentType = res.headers.get('content-type');
65
+ if (!contentType || !contentType.includes('application/json')) {
66
+ throw new Error('Server returned HTML instead of JSON. Check backend logs.');
67
+ }
68
+
69
+ const data = await res.json();
70
+
71
+ if (res.ok) {
72
+ messageContainer.innerHTML = '<div class="success-message">✅ Login successful! Redirecting...</div>';
73
+
74
+ // Force redirect to dashboard
75
+ setTimeout(() => {
76
+ window.location.href = '/';
77
+ }, 1000);
78
+ } else {
79
+ messageContainer.innerHTML = `<div class="error-message">❌ ${data.message}</div>`;
80
+ }
81
+ } catch (error) {
82
+ console.error('Login error:', error);
83
+ messageContainer.innerHTML = `<div class="error-message">❌ ${error.message}</div>`;
84
+ }
85
+ });
86
+
87
+ async function loadLogs() {
88
+ const res = await fetch('/api/cms/logs');
89
+ const logs = await res.json();
90
+ const container = document.getElementById('logs-container');
91
+
92
+ if (logs.length === 0) {
93
+ container.innerHTML = '<p class="sub-text">No API calls yet</p>';
94
+ return;
95
+ }
96
+
97
+ let html = '<table style="width:100%; font-size:0.85rem;">';
98
+ html += '<tr><th>Time</th><th>Tokens</th><th>Latency</th><th>Cost</th></tr>';
99
+
100
+ logs.slice(0, 10).forEach(log => {
101
+ html += `
102
+ <tr>
103
+ <td>${log.timestamp.split(' ')[1]}</td>
104
+ <td>${log.tokens_used}</td>
105
+ <td>${log.latency}s</td>
106
+ <td>$${log.cost}</td>
107
+ </tr>
108
+ `;
109
+ });
110
+
111
+ html += '</table>';
112
+ container.innerHTML = html;
113
+ }
114
+
115
+ // Update showSection function to load logs
116
+ function showSection(sectionId) {
117
+ document.querySelectorAll('.section').forEach(el => el.classList.remove('active'));
118
+ document.querySelectorAll('.nav-links li').forEach(el => el.classList.remove('active'));
119
+
120
+ document.getElementById(sectionId).classList.add('active');
121
+ event.target.classList.add('active');
122
+
123
+ if(sectionId === 'dashboard') {
124
+ loadStats();
125
+ loadLogs();
126
+ }
127
+ if(sectionId === 'list') loadApis();
128
+ }
129
+
130
+ // Load API List
131
+ async function loadApis() {
132
+ const res = await fetch('/api/cms/list');
133
+ const apis = await res.json();
134
+ const tbody = document.getElementById('api-table-body');
135
+
136
+ tbody.innerHTML = '';
137
+
138
+ apis.forEach(api => {
139
+ const row = `
140
+ <tr>
141
+ <td><span class="endpoint-url">/user-api/${api.route}</span></td>
142
+ <td>${api.model}</td>
143
+ <td>${api.total_calls}</td>
144
+ <td>
145
+ <button onclick="testApi('${api.route}')" style="padding:5px; cursor:pointer;">Test</button>
146
+ <button onclick="deleteApi(${api.id})" style="padding:5px; cursor:pointer; color:red;">Delete</button>
147
+ </td>
148
+ </tr>
149
+ `;
150
+ tbody.innerHTML += row;
151
+ });
152
+ }
153
+
154
+ // Add delete function
155
+ async function deleteApi(id) {
156
+ if(confirm('Are you sure you want to delete this API?')) {
157
+ await fetch(`/api/cms/delete/${id}`, { method: 'DELETE' });
158
+ loadApis();
159
+ loadStats();
160
+ }
161
+ }
162
+
163
+ // API Keys Functions
164
+ async function loadKeys() {
165
+ const res = await fetch('/api/cms/keys');
166
+ const keys = await res.json();
167
+ const tbody = document.getElementById('keys-table-body');
168
+
169
+ tbody.innerHTML = '';
170
+
171
+ keys.forEach(key => {
172
+ const row = `
173
+ <tr>
174
+ <td>${key.name}</td>
175
+ <td><code style="background:#eee; padding:2px 6px; font-size:0.75rem;">${key.key}</code></td>
176
+ <td>${key.usage_count}</td>
177
+ <td><span style="color:${key.is_active ? 'green' : 'red'}">● ${key.is_active ? 'Active' : 'Inactive'}</span></td>
178
+ <td>
179
+ <button onclick="toggleKey(${key.id})" style="padding:5px; cursor:pointer;">${key.is_active ? 'Deactivate' : 'Activate'}</button>
180
+ <button onclick="deleteKey(${key.id})" style="padding:5px; cursor:pointer; color:red;">Delete</button>
181
+ </td>
182
+ </tr>
183
+ `;
184
+ tbody.innerHTML += row;
185
+ });
186
+ }
187
+
188
+ async function createKey() {
189
+ const name = document.getElementById('key-name').value || 'Unnamed Key';
190
+
191
+ const res = await fetch('/api/cms/keys', {
192
+ method: 'POST',
193
+ headers: {'Content-Type': 'application/json'},
194
+ body: JSON.stringify({name})
195
+ });
196
+
197
+ const data = await res.json();
198
+
199
+ if (res.ok) {
200
+ alert('New API Key Generated:\n\n' + data.key + '\n\nSave this key! It won\'t be shown again.');
201
+ document.getElementById('key-name').value = '';
202
+ loadKeys();
203
+ }
204
+ }
205
+
206
+ async function toggleKey(id) {
207
+ await fetch(`/api/cms/keys/${id}/toggle`, {method: 'POST'});
208
+ loadKeys();
209
+ }
210
+
211
+ async function deleteKey(id) {
212
+ if(confirm('Delete this API Key?')) {
213
+ await fetch(`/api/cms/keys/${id}`, {method: 'DELETE'});
214
+ loadKeys();
215
+ }
216
+ }
217
+
218
+ // Export Function
219
+ function exportLogs() {
220
+ window.open('/api/cms/export', '_blank');
221
+ }
222
+
223
+ // Update showSection to handle keys
224
+ function showSection(sectionId) {
225
+ document.querySelectorAll('.section').forEach(el => el.classList.remove('active'));
226
+ document.querySelectorAll('.nav-links li').forEach(el => el.classList.remove('active'));
227
+
228
+ document.getElementById(sectionId).classList.add('active');
229
+ event.target.classList.add('active');
230
+
231
+ if(sectionId === 'dashboard') {
232
+ loadStats();
233
+ loadLogs();
234
+ }
235
+ if(sectionId === 'list') loadApis();
236
+ if(sectionId === 'keys') loadKeys();
237
+ }
238
+
239
+ // Simple Test Function
240
+ async function testApi(route) {
241
+ const res = await fetch(`/user-api/${route}`, {
242
+ method: 'POST',
243
+ headers: {'Content-Type': 'application/json'},
244
+ body: JSON.stringify({test: "data"})
245
+ });
246
+ const data = await res.json();
247
+ alert("Response:\n" + JSON.stringify(data, null, 2));
248
+ }
249
+
250
+ // Initial Load
251
+ loadStats();