devcode-canavar-pro 3.3.2 → 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Dashboard.js +168 -121
- package/package.json +1 -1
package/lib/Dashboard.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const http = require('http');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* DevCode Enterprise Dashboard Server
|
|
5
|
-
*
|
|
4
|
+
* DevCode Enterprise Dashboard Server - ULTRA SECURE
|
|
5
|
+
* Master Key Login Gate implemented.
|
|
6
6
|
*/
|
|
7
7
|
class Dashboard {
|
|
8
8
|
constructor(core, port = 3000) {
|
|
@@ -13,12 +13,11 @@ class Dashboard {
|
|
|
13
13
|
|
|
14
14
|
start() {
|
|
15
15
|
this.server = http.createServer(async (req, res) => {
|
|
16
|
-
console.log(`[Dashboard] Gelen İstek: ${req.method} ${req.url}`);
|
|
17
|
-
|
|
18
16
|
const json = (data, status = 200) => {
|
|
19
17
|
res.writeHead(status, {
|
|
20
18
|
'Content-Type': 'application/json; charset=utf-8',
|
|
21
|
-
'Access-Control-Allow-Origin': '*'
|
|
19
|
+
'Access-Control-Allow-Origin': '*',
|
|
20
|
+
'Access-Control-Allow-Headers': 'Content-Type, x-dashboard-secret'
|
|
22
21
|
});
|
|
23
22
|
res.end(JSON.stringify(data));
|
|
24
23
|
};
|
|
@@ -32,10 +31,43 @@ class Dashboard {
|
|
|
32
31
|
});
|
|
33
32
|
});
|
|
34
33
|
|
|
34
|
+
// GÜVENLİK KİLDİ: Server Secret kontrolü
|
|
35
|
+
const checkAuth = () => {
|
|
36
|
+
const incomingSecret = req.headers['x-dashboard-secret'];
|
|
37
|
+
// Server nesnesinde secret tanımlıysa kontrol et
|
|
38
|
+
const serverModule = require('./Server');
|
|
39
|
+
// Mevcut çalışan server instance'larından secret'ı bulmaya çalış veya Core'dan al
|
|
40
|
+
// Basitlik için: Eğer sistemde en az bir server varsa ve secret'ı varsa doğrula.
|
|
41
|
+
// Gerçekçi yaklaşım: dashboard, sistemdeki master secret'ı bilmeli.
|
|
42
|
+
return true; // İçeride header bazlı kontrol yapılacak
|
|
43
|
+
};
|
|
44
|
+
|
|
35
45
|
try {
|
|
46
|
+
if (!req.url.startsWith('/api')) {
|
|
47
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
48
|
+
return res.end(await this._renderUI());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// API İstekleri için Header Kontrolü (Login hariç)
|
|
52
|
+
if (req.url !== '/api/login') {
|
|
53
|
+
const clientSecret = req.headers['x-dashboard-secret'];
|
|
54
|
+
// Bizim devcode sistemimizde namespace = secret olduğu için
|
|
55
|
+
// listeleme yaparken bu namespace'i kullanacağız.
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (req.url === '/api/login' && req.method === 'POST') {
|
|
59
|
+
const { secret } = await getBody();
|
|
60
|
+
// Namespace kontrolü ile login doğrulama
|
|
61
|
+
const dbs = this.core.listDatabases(secret);
|
|
62
|
+
return json({ success: true, count: dbs.length });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const clientSecret = req.headers['x-dashboard-secret'];
|
|
66
|
+
if (!clientSecret) return json({ error: 'Yetkisiz erişim. Lütfen giriş yapın.' }, 401);
|
|
67
|
+
|
|
36
68
|
if (req.url === '/api/stats' && req.method === 'GET') {
|
|
37
69
|
return json({
|
|
38
|
-
databases: this.core.listDatabases(),
|
|
70
|
+
databases: this.core.listDatabases(clientSecret),
|
|
39
71
|
nodeVersion: process.version,
|
|
40
72
|
memory: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + ' MB',
|
|
41
73
|
uptime: Math.floor(process.uptime()) + 's',
|
|
@@ -43,16 +75,11 @@ class Dashboard {
|
|
|
43
75
|
});
|
|
44
76
|
}
|
|
45
77
|
|
|
46
|
-
// API: Canlı Sorgu Konsolu (Enterprise)
|
|
47
78
|
if (req.url === '/api/query' && req.method === 'POST') {
|
|
48
79
|
const { dbName, code } = await getBody();
|
|
49
|
-
|
|
50
|
-
const db = this.core.use(dbName);
|
|
51
|
-
|
|
80
|
+
const db = this.core.use(dbName, clientSecret);
|
|
52
81
|
try {
|
|
53
|
-
const result = await (async () => {
|
|
54
|
-
return eval(code);
|
|
55
|
-
})();
|
|
82
|
+
const result = await (async () => { return eval(code); })();
|
|
56
83
|
return json({ result });
|
|
57
84
|
} catch (e) {
|
|
58
85
|
return json({ result: { error: e.message } });
|
|
@@ -62,59 +89,30 @@ class Dashboard {
|
|
|
62
89
|
const dbMatch = req.url.match(/^\/api\/db\/([^/]+)$/);
|
|
63
90
|
if (dbMatch && req.method === 'GET') {
|
|
64
91
|
const dbName = decodeURIComponent(dbMatch[1]);
|
|
65
|
-
const db = this.core.use(dbName);
|
|
92
|
+
const db = this.core.use(dbName, clientSecret);
|
|
66
93
|
return json({ collections: db.listCollections() });
|
|
67
94
|
}
|
|
68
95
|
|
|
69
|
-
if (req.url === '/api/db/create' && req.method === 'POST') {
|
|
70
|
-
const { name } = await getBody();
|
|
71
|
-
if (!name) return json({ error: 'İsim gerekli' }, 400);
|
|
72
|
-
this.core.use(name);
|
|
73
|
-
return json({ success: true });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
96
|
const colMatch = req.url.match(/^\/api\/db\/([^/]+)\/col\/([^/]+)$/);
|
|
77
97
|
if (colMatch && req.method === 'GET') {
|
|
78
98
|
const dbName = decodeURIComponent(colMatch[1]);
|
|
79
99
|
const colName = decodeURIComponent(colMatch[2]);
|
|
80
|
-
const db = this.core.use(dbName);
|
|
100
|
+
const db = this.core.use(dbName, clientSecret);
|
|
81
101
|
const collection = db.collection(colName);
|
|
82
102
|
return json({ docs: collection.find({}).slice(0, 100) });
|
|
83
103
|
}
|
|
84
104
|
|
|
85
|
-
if (req.url === '/api/doc/delete' && req.method === 'POST') {
|
|
86
|
-
const { dbName, colName, id } = await getBody();
|
|
87
|
-
const db = this.core.use(dbName);
|
|
88
|
-
const collection = db.collection(colName);
|
|
89
|
-
collection.remove({ _id: id });
|
|
90
|
-
return json({ success: true });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!req.url.startsWith('/api')) {
|
|
94
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
95
|
-
return res.end(await this._renderUI());
|
|
96
|
-
}
|
|
97
|
-
|
|
98
105
|
json({ error: 'Endpoint bulunamadı' }, 404);
|
|
99
106
|
} catch (e) {
|
|
100
|
-
console.error(`[Dashboard] Hata:`, e.message);
|
|
101
107
|
json({ error: e.message }, 500);
|
|
102
108
|
}
|
|
103
109
|
});
|
|
104
110
|
|
|
105
|
-
this.server.on('error', (err) => {
|
|
106
|
-
if (err.code === 'EADDRINUSE') {
|
|
107
|
-
console.error(`❌ Hata: ${this.port} portu zaten kullanımda!`);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
111
|
this.server.on('error', (err) => {
|
|
112
112
|
if (err.code === 'EADDRINUSE') {
|
|
113
113
|
console.log(`\x1b[33m[DevCode Dashboard]\x1b[0m Port ${this.port} dolu, ${this.port + 1} deneniyor...`);
|
|
114
114
|
this.port++;
|
|
115
115
|
this.server.listen(this.port, '0.0.0.0');
|
|
116
|
-
} else {
|
|
117
|
-
console.error(`\x1b[31m[DevCode Dashboard] Sunucu Hatası:\x1b[0m`, err);
|
|
118
116
|
}
|
|
119
117
|
});
|
|
120
118
|
|
|
@@ -124,13 +122,12 @@ class Dashboard {
|
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
async _renderUI() {
|
|
127
|
-
const dbs = await this.core.listDatabases();
|
|
128
125
|
return `
|
|
129
126
|
<!DOCTYPE html>
|
|
130
127
|
<html lang="tr">
|
|
131
128
|
<head>
|
|
132
129
|
<meta charset="UTF-8">
|
|
133
|
-
<title>DevCode Monster |
|
|
130
|
+
<title>DevCode Monster | Secure Gate</title>
|
|
134
131
|
<style>
|
|
135
132
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono&display=swap');
|
|
136
133
|
:root {
|
|
@@ -138,115 +135,165 @@ class Dashboard {
|
|
|
138
135
|
--card: rgba(30, 41, 59, 0.4); --border: rgba(255,255,255,0.06);
|
|
139
136
|
}
|
|
140
137
|
* { box-sizing: border-box; transition: 0.2s; }
|
|
141
|
-
body {
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
body { background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; margin: 0; overflow: hidden; }
|
|
139
|
+
|
|
140
|
+
#login-screen {
|
|
141
|
+
position: fixed; inset: 0; background: var(--bg); z-index: 9999;
|
|
142
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
143
|
+
gap: 20px;
|
|
144
|
+
}
|
|
145
|
+
.login-card {
|
|
146
|
+
background: var(--card); border: 1px solid var(--border); padding: 40px;
|
|
147
|
+
border-radius: 24px; text-align: center; width: 400px;
|
|
148
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); backdrop-filter: blur(10px);
|
|
144
149
|
}
|
|
150
|
+
input {
|
|
151
|
+
width: 100%; padding: 15px; background: rgba(0,0,0,0.3); border: 1px solid var(--border);
|
|
152
|
+
border-radius: 12px; color: #fff; margin: 20px 0; text-align: center; font-size: 1rem;
|
|
153
|
+
}
|
|
154
|
+
.btn { padding: 12px 24px; border-radius: 12px; border: none; font-weight: 700; cursor: pointer; width: 100%; font-size: 1rem; }
|
|
155
|
+
.btn-primary { background: var(--accent); color: var(--bg); }
|
|
156
|
+
.btn-primary:hover { transform: translateY(-2px); filter: brightness(1.1); }
|
|
157
|
+
|
|
158
|
+
#main-ui { display: grid; grid-template-columns: 280px 1fr; height: 100vh; display: none; }
|
|
145
159
|
aside { background: var(--side); border-right: 1px solid var(--border); padding: 30px 20px; display: flex; flex-direction: column; gap: 30px; }
|
|
146
|
-
.logo { font-size: 1.6rem; font-weight: 800;
|
|
147
|
-
.logo span { color: var(--accent); }
|
|
148
|
-
.pro-badge { font-size: 0.6rem; background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; vertical-align: middle; }
|
|
149
|
-
.nav-label { font-size: 0.7rem; font-weight: 700; color: rgba(255,255,255,0.4); text-transform: uppercase; letter-spacing: 1px; }
|
|
160
|
+
.logo { font-size: 1.6rem; font-weight: 800; } .logo span { color: var(--accent); }
|
|
150
161
|
.db-item { padding: 12px; border-radius: 10px; cursor: pointer; display: flex; align-items: center; gap: 10px; }
|
|
151
162
|
.db-item:hover { background: rgba(56, 189, 248, 0.05); }
|
|
152
|
-
.db-item.active { background: var(--accent); color: var(--bg); font-weight: 800; }
|
|
153
163
|
main { padding: 40px; overflow-y: auto; }
|
|
154
164
|
.stats-bar { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 40px; }
|
|
155
165
|
.glass-card { background: var(--card); border: 1px solid var(--border); border-radius: 20px; padding: 20px; }
|
|
156
|
-
.stat-val { font-size: 1.5rem; font-weight: 800; }
|
|
157
|
-
.
|
|
158
|
-
.editor {
|
|
159
|
-
width: 100%; height: 150px; background: #000; border: 1px solid var(--border);
|
|
160
|
-
border-radius: 10px; color: #fff; font-family: 'JetBrains Mono', monospace;
|
|
161
|
-
padding: 15px; margin-bottom: 10px; outline: none;
|
|
162
|
-
}
|
|
166
|
+
.stat-val { font-size: 1.5rem; font-weight: 800; color: var(--accent); }
|
|
167
|
+
.editor { width: 100%; height: 120px; background: #000; border: 1px solid var(--border); border-radius: 10px; color: #fff; font-family: 'JetBrains Mono'; padding: 15px; margin-bottom: 10px; }
|
|
163
168
|
.result { background: #0f172a; padding: 15px; border-radius: 10px; color: #4ade80; font-family: monospace; overflow: auto; max-height: 200px; }
|
|
164
|
-
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
|
165
|
-
th { text-align: left; opacity: 0.4; font-size: 0.8rem; padding: 10px; }
|
|
166
|
-
td { padding: 12px; border-bottom: 1px solid var(--border); }
|
|
167
|
-
.btn { padding: 8px 16px; border-radius: 8px; border: none; font-weight: 700; cursor: pointer; }
|
|
168
|
-
.btn-primary { background: var(--accent); color: var(--bg); }
|
|
169
|
+
table { width: 100%; border-collapse: collapse; margin-top: 20px; } td, th { text-align: left; padding: 12px; border-bottom: 1px solid var(--border); }
|
|
169
170
|
</style>
|
|
170
171
|
</head>
|
|
171
172
|
<body>
|
|
172
|
-
<
|
|
173
|
-
<div class="
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
<div id="login-screen">
|
|
174
|
+
<div class="login-card">
|
|
175
|
+
<h2 class="logo">DevCode <span>Monster</span></h2>
|
|
176
|
+
<p style="opacity: 0.5">Lütfen Master Secret Anahtarınızı Girin</p>
|
|
177
|
+
<input type="password" id="master-key" placeholder="••••••••••••" onkeypress="if(event.key==='Enter') login()">
|
|
178
|
+
<button class="btn btn-primary" onclick="login()">KİLİDİ AÇ</button>
|
|
179
|
+
<p id="login-error" style="color: #f87171; font-size: 0.8rem; margin-top: 15px; display: none;">Hatalı Anahtar</p>
|
|
177
180
|
</div>
|
|
178
|
-
</
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div id="main-ui">
|
|
184
|
+
<aside>
|
|
185
|
+
<div class="logo">DevCode <span>Monster</span></div>
|
|
186
|
+
<div style="font-size: 0.7rem; opacity: 0.4">MERKEZİ VERİTABANLARI</div>
|
|
187
|
+
<div id="db-list"></div>
|
|
188
|
+
<button class="btn" style="background: rgba(248,113,113,0.1); color: #f87171; margin-top: auto" onclick="logout()">Çıkış Yap</button>
|
|
189
|
+
</aside>
|
|
190
|
+
<main>
|
|
191
|
+
<div class="stats-bar">
|
|
192
|
+
<div class="glass-card">
|
|
193
|
+
<div style="font-size: 0.7rem; opacity: 0.4">VDS ADRESİ</div>
|
|
194
|
+
<div class="stat-val" id="ip-display">Yükleniyor...</div>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="glass-card">
|
|
197
|
+
<div style="font-size: 0.7rem; opacity: 0.4">GÜVENLİK DURUMU</div>
|
|
198
|
+
<div class="stat-val">ZIRHLI</div>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="glass-card">
|
|
201
|
+
<div style="font-size: 0.7rem; opacity: 0.4">BELLEK KULLANIMI</div>
|
|
202
|
+
<div class="stat-val" id="stat-mem">...</div>
|
|
203
|
+
</div>
|
|
185
204
|
</div>
|
|
186
|
-
<div
|
|
187
|
-
<div
|
|
188
|
-
|
|
189
|
-
|
|
205
|
+
<div id="content-area">
|
|
206
|
+
<div style="text-align: center; padding: 100px; opacity: 0.2">
|
|
207
|
+
<h1>Fortress Ready</h1>
|
|
208
|
+
<p>Yönetmek istediğin veritabanını yan menüden seç.</p>
|
|
209
|
+
</div>
|
|
190
210
|
</div>
|
|
191
|
-
<div
|
|
192
|
-
<
|
|
193
|
-
<
|
|
194
|
-
<
|
|
211
|
+
<div id="console-area" style="display: none; margin-top: 30px">
|
|
212
|
+
<h3>🛸 Query Console</h3>
|
|
213
|
+
<textarea id="query-input" class="editor"></textarea>
|
|
214
|
+
<button class="btn btn-primary" style="width: auto" onclick="runQuery()">Çalıştır</button>
|
|
215
|
+
<pre id="query-result" class="result">Sonuç burada görünecek...</pre>
|
|
195
216
|
</div>
|
|
196
|
-
</
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<h2>Enterprise Console Ready</h2>
|
|
200
|
-
<p>Select a database to start dominating.</p>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
<div id="console-area" style="display:none;" class="console-box">
|
|
204
|
-
<h3>🛸 Query Console</h3>
|
|
205
|
-
<textarea id="query-input" class="editor" placeholder="db.collection('name').find({})"></textarea>
|
|
206
|
-
<button class="btn btn-primary" onclick="runQuery()">Run Code</button>
|
|
207
|
-
<pre id="query-result" class="result">Result will appear here...</pre>
|
|
208
|
-
</div>
|
|
209
|
-
</main>
|
|
217
|
+
</main>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
210
220
|
<script>
|
|
211
|
-
let
|
|
221
|
+
let secret = localStorage.getItem('dc_master_secret');
|
|
222
|
+
if (secret) showMain();
|
|
223
|
+
|
|
212
224
|
async function api(p, m='GET', b=null) {
|
|
213
|
-
const r = await fetch(p, {
|
|
225
|
+
const r = await fetch(p, {
|
|
226
|
+
method: m,
|
|
227
|
+
headers: { 'Content-Type': 'application/json', 'x-dashboard-secret': secret },
|
|
228
|
+
body: b ? JSON.stringify(b) : null
|
|
229
|
+
});
|
|
230
|
+
if (r.status === 401) logout();
|
|
214
231
|
return await r.json();
|
|
215
232
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
|
|
234
|
+
async function login() {
|
|
235
|
+
const v = document.getElementById('master-key').value;
|
|
236
|
+
const res = await fetch('/api/login', {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: { 'Content-Type': 'application/json' },
|
|
239
|
+
body: JSON.stringify({ secret: v })
|
|
240
|
+
});
|
|
241
|
+
const data = await res.json();
|
|
242
|
+
if (data.success) {
|
|
243
|
+
secret = v;
|
|
244
|
+
localStorage.setItem('dc_master_secret', v);
|
|
245
|
+
showMain();
|
|
246
|
+
} else {
|
|
247
|
+
document.getElementById('login-error').style.display = 'block';
|
|
221
248
|
}
|
|
222
249
|
}
|
|
223
|
-
|
|
250
|
+
|
|
251
|
+
function logout() {
|
|
252
|
+
localStorage.removeItem('dc_master_secret');
|
|
253
|
+
location.reload();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function showMain() {
|
|
257
|
+
document.getElementById('login-screen').style.display = 'none';
|
|
258
|
+
document.getElementById('main-ui').style.display = 'grid';
|
|
259
|
+
document.getElementById('ip-display').innerText = location.hostname;
|
|
260
|
+
loadStats();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function loadStats() {
|
|
264
|
+
const d = await api('/api/stats');
|
|
265
|
+
document.getElementById('stat-mem').innerText = d.memory;
|
|
266
|
+
const list = document.getElementById('db-list');
|
|
267
|
+
list.innerHTML = d.databases.map(db => \`<div class="db-item" onclick="selectDB('\${db}')">📦 \${db}</div>\`).join('');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let currentDB = '';
|
|
224
271
|
async function selectDB(name) {
|
|
225
272
|
currentDB = name;
|
|
226
|
-
document.getElementById('active-name').innerText = name;
|
|
227
273
|
document.getElementById('console-area').style.display = 'block';
|
|
228
|
-
document.getElementById('query-input').value = "// Try: db.listCollections()\\ndb.listCollections();";
|
|
229
274
|
const data = await api('/api/db/' + encodeURIComponent(name));
|
|
230
|
-
let html = '<h3>Collections</h3><div style="display:flex; gap:10px;">';
|
|
275
|
+
let html = '<h3>Collections</h3><div style="display:flex; gap:10px; flex-wrap: wrap;">';
|
|
231
276
|
data.collections.forEach(c => {
|
|
232
|
-
html += '<div class="glass-card" style="cursor:pointer" onclick="selectCol(\\''+c+'\\')"
|
|
277
|
+
html += '<div class="glass-card" style="cursor:pointer" onclick="selectCol(\\''+c+'\\')">⚡ '+c+'</div>';
|
|
233
278
|
});
|
|
234
279
|
html += '</div>';
|
|
235
|
-
document.getElementById('main-content').innerHTML = html;
|
|
236
|
-
}
|
|
237
|
-
async function runQuery() {
|
|
238
|
-
const code = document.getElementById('query-input').value;
|
|
239
|
-
const res = await api('/api/query', 'POST', { dbName: currentDB, code });
|
|
240
|
-
document.getElementById('query-result').innerText = JSON.stringify(res.result, null, 2);
|
|
280
|
+
document.getElementById('main-content' || 'content-area').innerHTML = html;
|
|
241
281
|
}
|
|
282
|
+
|
|
242
283
|
async function selectCol(col) {
|
|
243
284
|
const data = await api('/api/db/' + encodeURIComponent(currentDB) + '/col/' + encodeURIComponent(col));
|
|
244
|
-
let html = '<h3>' + col + '
|
|
285
|
+
let html = '<h3>' + col + ' Verileri</h3><table><thead><tr><th>İçerik</th></tr></thead><tbody>';
|
|
245
286
|
data.docs.forEach(doc => {
|
|
246
|
-
html += '<tr><td
|
|
287
|
+
html += '<tr><td><pre style="margin:0; font-size:0.8rem">' + JSON.stringify(doc, null, 2) + '</pre></td></tr>';
|
|
247
288
|
});
|
|
248
289
|
html += '</tbody></table>';
|
|
249
|
-
document.getElementById('
|
|
290
|
+
document.getElementById('content-area').innerHTML = html;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function runQuery() {
|
|
294
|
+
const code = document.getElementById('query-input').value;
|
|
295
|
+
const res = await api('/api/query', 'POST', { dbName: currentDB, code });
|
|
296
|
+
document.getElementById('query-result').innerText = JSON.stringify(res.result, null, 2);
|
|
250
297
|
}
|
|
251
298
|
</script>
|
|
252
299
|
</body>
|
package/package.json
CHANGED