quar 1.3.1 → 1.4.2
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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/publish.yaml +50 -0
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -21
- package/README.md +96 -95
- package/dummy_models/posts.model.js +52 -52
- package/dummy_models/user.model.js +44 -44
- package/index.js +102 -66
- package/package.json +11 -3
- package/public/scripts/insertTab.js +155 -155
- package/public/scripts/keyboardCommands.js +15 -15
- package/public/scripts/main.js +364 -364
- package/public/scripts/popup.js +25 -25
- package/public/styles/insertTab.css +73 -73
- package/public/styles/popup.css +70 -70
- package/public/styles/sidebar.css +33 -33
- package/public/styles/style.css +306 -306
- package/public/styles/variables.css +8 -8
- package/server.js +241 -241
- package/utils/loadModels.js +26 -26
- package/views/base.zare +16 -16
- package/views/components/insertTab.zare +7 -7
- package/views/components/popup.zare +11 -11
- package/views/components/sidebar.zare +11 -11
- package/views/pages/index.zare +48 -48
package/public/scripts/main.js
CHANGED
|
@@ -1,365 +1,365 @@
|
|
|
1
|
-
const tabsContainer = document.getElementById("tabs");
|
|
2
|
-
const content = document.getElementById("content");
|
|
3
|
-
let currentModelName = "";
|
|
4
|
-
let loadedDocuments = [];
|
|
5
|
-
const openTabs = {};
|
|
6
|
-
|
|
7
|
-
function openModel(modelName) {
|
|
8
|
-
|
|
9
|
-
if (!openTabs[modelName]) {
|
|
10
|
-
// Create a new tab
|
|
11
|
-
const tab = document.createElement("div");
|
|
12
|
-
tab.className = "tab";
|
|
13
|
-
tab.id = "tab-" + modelName;
|
|
14
|
-
tab.draggable = true;
|
|
15
|
-
tab.innerHTML = modelName + '<span class="close" onclick="closeTab(event, \'' + modelName + '\')">×</span>';
|
|
16
|
-
tab.onclick = () => activateTab(modelName);
|
|
17
|
-
tabsContainer.appendChild(tab);
|
|
18
|
-
|
|
19
|
-
tab.addEventListener("dragstart", dragStart);
|
|
20
|
-
tab.addEventListener("dragover", dragOver);
|
|
21
|
-
tab.addEventListener("drop", drop);
|
|
22
|
-
tab.addEventListener("dragend", dragEnd);
|
|
23
|
-
|
|
24
|
-
openTabs[modelName] = tab;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
activateTab(modelName);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function activateTab(modelName) {
|
|
32
|
-
|
|
33
|
-
// Prevent if tab is already active
|
|
34
|
-
if (content.dataset?.modelName == modelName) return
|
|
35
|
-
|
|
36
|
-
content.dataset.modelName = modelName;
|
|
37
|
-
document.getElementById("page-value").innerText = 1;
|
|
38
|
-
document.querySelectorAll(".operations .btn")?.forEach(btn => btn.disabled = false)
|
|
39
|
-
document.querySelector(".operations select").disabled = false;
|
|
40
|
-
|
|
41
|
-
const insertTab = document.querySelector('.insert-tab');
|
|
42
|
-
insertTab.classList.remove('active');
|
|
43
|
-
insertTab.innerHTML = "";
|
|
44
|
-
// Remove active class from all tabs
|
|
45
|
-
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
|
46
|
-
|
|
47
|
-
// Set current tab active
|
|
48
|
-
const currentTab = document.getElementById("tab-" + modelName);
|
|
49
|
-
currentTab.classList.add("active");
|
|
50
|
-
|
|
51
|
-
// Update content
|
|
52
|
-
await loadDocuments();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function closeTab(event, modelName) {
|
|
56
|
-
event.stopPropagation(); // prevent tab click
|
|
57
|
-
const tab = document.getElementById("tab-" + modelName);
|
|
58
|
-
tab.remove();
|
|
59
|
-
delete openTabs[modelName];
|
|
60
|
-
|
|
61
|
-
// Clear content if that tab was active and check if other tabs are open to be active
|
|
62
|
-
if (tab.classList.contains("active")) {
|
|
63
|
-
content.innerHTML = "";
|
|
64
|
-
const keys = Object.keys(openTabs);
|
|
65
|
-
const modelName = keys[keys.length - 1] || null;
|
|
66
|
-
if (modelName) activateTab(modelName);
|
|
67
|
-
else disableAllOptions();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function disableAllOptions() {
|
|
72
|
-
content.dataset.modelName = '';
|
|
73
|
-
|
|
74
|
-
document.querySelectorAll(".operations .btn")?.forEach(btn => btn.disabled = true);
|
|
75
|
-
document.getElementById("document-count").innerText = 0;
|
|
76
|
-
document.querySelector(".operations select").disabled = true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function loadDocuments() {
|
|
80
|
-
|
|
81
|
-
content.innerHTML = "";
|
|
82
|
-
const limit = document.getElementById("limit").value;
|
|
83
|
-
const page = document.getElementById("page-value").innerText;
|
|
84
|
-
const modelName = content.dataset?.modelName;
|
|
85
|
-
|
|
86
|
-
if (!modelName) {
|
|
87
|
-
showModal('error', 'Error Occurred!', 'No model selected.');
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const response = await fetch(`/models/${modelName}?limit=${limit}&page=${page}`);
|
|
92
|
-
if (response.status !== 200) {
|
|
93
|
-
showModal('error', 'Error Occurred!', 'Something went wrong, please try again.');
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const data = await response.json();
|
|
97
|
-
|
|
98
|
-
document.getElementById("document-count").innerText = data.count;
|
|
99
|
-
document.getElementById("page-value").dataset.totalPages = data.totalPages;
|
|
100
|
-
document.getElementById(`${modelName}-doc-count`).innerText = data.totalCount;
|
|
101
|
-
|
|
102
|
-
if (Number(page) >= Number(data.totalPages)) document.getElementById("next-page").disabled = true;
|
|
103
|
-
else document.getElementById("next-page").disabled = false;
|
|
104
|
-
|
|
105
|
-
if (Number(page) <= 1) document.getElementById("previous-page").disabled = true;
|
|
106
|
-
else document.getElementById("previous-page").disabled = false;
|
|
107
|
-
|
|
108
|
-
currentModelName = modelName;
|
|
109
|
-
loadedDocuments = data.documents;
|
|
110
|
-
// Render each array element as its own tree
|
|
111
|
-
data.documents.forEach((doc, index) => {
|
|
112
|
-
const wrapper = document.createElement('div');
|
|
113
|
-
const divDocument = document.createElement('div');
|
|
114
|
-
divDocument.innerHTML += Object.entries(doc).map(([key, value]) => renderData(key, value, index)).join('');
|
|
115
|
-
|
|
116
|
-
divDocument.classList.add('document');
|
|
117
|
-
|
|
118
|
-
wrapper.appendChild(divDocument);
|
|
119
|
-
wrapper.innerHTML += `<div class="document-actions"><button class="updateDocument" onclick="updateDocument(${index})">Update</button><button class="deleteDocument" onclick="deleteDoc('${modelName}', '${doc._id}')">Delete</button></div>`;
|
|
120
|
-
|
|
121
|
-
content.appendChild(wrapper);
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function toggleEditMode(id) {
|
|
126
|
-
const htmlData = document.getElementById(`${id}-html-data`);
|
|
127
|
-
const jsonData = document.getElementById(`${id}-json-data`);
|
|
128
|
-
|
|
129
|
-
htmlData.classList.toggle('hidden');
|
|
130
|
-
jsonData.classList.toggle('hidden');
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function gotoNextPage() {
|
|
135
|
-
const page = document.getElementById("page-value");
|
|
136
|
-
page.innerText = Number(page.innerText) + 1;
|
|
137
|
-
|
|
138
|
-
if (Number(page.innerText) > Number(page.dataset.totalPages)) {
|
|
139
|
-
page.innerText = Number(page.dataset.totalPages);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
loadDocuments();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function gotoPreviousPage() {
|
|
147
|
-
const page = document.getElementById("page-value").innerText;
|
|
148
|
-
document.getElementById("page-value").innerText = Number(page) - 1;
|
|
149
|
-
|
|
150
|
-
if (Number(page) - 1 < 1) {
|
|
151
|
-
document.getElementById("page-value").innerText = 1;
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
loadDocuments();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function renderData(key, value, index, level = "root.", parentDataType = "object") {
|
|
159
|
-
const type = typeof value;
|
|
160
|
-
if (value === null) return `<div class="field"><span class="data-key">${key}</span>: <span class="null" data-level="${level}" data-index="${index}">null</span><span class="data-type">(null)</span></div>`;
|
|
161
|
-
|
|
162
|
-
if (Array.isArray(value)) {
|
|
163
|
-
return `
|
|
164
|
-
<details class="field">
|
|
165
|
-
<summary ><span class="data-key">${key}</span><span class="data-type"> (Array - ${value.length})</span></summary>
|
|
166
|
-
<div class="data-value">
|
|
167
|
-
${value.map((v, i) => renderData(`[${i}]`, v, index, level + key, "array")).join('')}
|
|
168
|
-
</div>
|
|
169
|
-
</details>
|
|
170
|
-
`;
|
|
171
|
-
} else if (type === 'object') {
|
|
172
|
-
return `
|
|
173
|
-
<details class="field">
|
|
174
|
-
<summary><span class="data-key">${key}</span><span class="data-type"> (Object)</span></summary>
|
|
175
|
-
<div class="data-value">
|
|
176
|
-
${Object.entries(value).map(([k, v]) => renderData(k, v, index, level + key, "object")).join('')}
|
|
177
|
-
</div>
|
|
178
|
-
</details>
|
|
179
|
-
`;
|
|
180
|
-
} else if (typeof value === 'string' && /^[a-f\d]{24}$/i.test(value)) {
|
|
181
|
-
|
|
182
|
-
return `<div class="field"><span class="data-key">${key}</span>: <span class="ObjectId">${value}</span></div>`;
|
|
183
|
-
} else {
|
|
184
|
-
return `<div class="field"><span class="data-key">${key}</span>: <span class="${type}" data-level="${level}" data-index="${index}" data-parent-data-type="${parentDataType}" contenteditable >${value}</span></div>`;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Handle collapsibles
|
|
189
|
-
document.addEventListener('click', function (e) {
|
|
190
|
-
if (e.target.classList.contains('collapsible')) {
|
|
191
|
-
e.target.classList.toggle("caret-down");
|
|
192
|
-
const nested = e.target.nextElementSibling;
|
|
193
|
-
if (nested) {
|
|
194
|
-
nested.classList.toggle("active");
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
function setNestedValue(obj, path, value, parentDataType) {
|
|
200
|
-
const keys = path.split(".");
|
|
201
|
-
let current = obj;
|
|
202
|
-
|
|
203
|
-
keys.forEach((key, index) => {
|
|
204
|
-
if (index === keys.length - 1) {
|
|
205
|
-
const arrayIndexMatch = key.match(/\[(\d+)\]/);
|
|
206
|
-
if (arrayIndexMatch) {
|
|
207
|
-
const idx = parseInt(arrayIndexMatch[1], 10);
|
|
208
|
-
current[idx] = value;
|
|
209
|
-
} else {
|
|
210
|
-
current[key] = value;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
} else {
|
|
214
|
-
if (parentDataType === "array" && !Array.isArray(current[key])) {
|
|
215
|
-
current[key] = [];
|
|
216
|
-
} else if (parentDataType === "object" && typeof current[key] !== "object") {
|
|
217
|
-
current[key] = {};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
current = current[key]; // Go deeper
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async function updateDocument(index) {
|
|
226
|
-
|
|
227
|
-
const confirmUpdate = confirm("Are you sure you want to update this document?");
|
|
228
|
-
if (!confirmUpdate) return;
|
|
229
|
-
|
|
230
|
-
const doc = loadedDocuments[index];
|
|
231
|
-
const id = doc._id;
|
|
232
|
-
|
|
233
|
-
let updatedDoc = {};
|
|
234
|
-
const values = document.querySelectorAll(`[data-index="${index}"]`);
|
|
235
|
-
|
|
236
|
-
values.forEach(el => {
|
|
237
|
-
const key = el.previousElementSibling?.textContent?.trim();
|
|
238
|
-
const level = el.dataset.level;
|
|
239
|
-
|
|
240
|
-
if (key) {
|
|
241
|
-
if (level && level !== "root") {
|
|
242
|
-
// Build the full path
|
|
243
|
-
const fullPath = `${level.replace("root.", "")}.${key}`;
|
|
244
|
-
setNestedValue(updatedDoc, fullPath, el.textContent, el.dataset.parentDataType);
|
|
245
|
-
} else {
|
|
246
|
-
updatedDoc[key] = el.textContent;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
updatedDoc = { ...updatedDoc, ...updatedDoc[""] }
|
|
252
|
-
|
|
253
|
-
const res = await fetch(`/update/${currentModelName}/${id}`, {
|
|
254
|
-
method: "PUT",
|
|
255
|
-
headers: { "Content-Type": "application/json" },
|
|
256
|
-
body: JSON.stringify(updatedDoc),
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
if (res.ok) {
|
|
260
|
-
showModal('info', 'Document Updated', 'Document updated successfully.');
|
|
261
|
-
loadDocuments();
|
|
262
|
-
} else {
|
|
263
|
-
const data = await res.json();
|
|
264
|
-
showModal('error', 'Update Failed', data.error || 'Update failed, please try again.');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function deleteDoc(modelName, id) {
|
|
269
|
-
const confirmDelete = confirm("Are you sure you want to delete this document?");
|
|
270
|
-
if (!confirmDelete) return;
|
|
271
|
-
|
|
272
|
-
const res = await fetch(`/delete/${modelName}/${id}`, {
|
|
273
|
-
method: "DELETE"
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
if (res.ok) {
|
|
277
|
-
showModal('info', 'Document Deleted', 'Document deleted successfully.');
|
|
278
|
-
openModel(modelName);
|
|
279
|
-
const data = await res.json();
|
|
280
|
-
document.getElementById(`${modelName}-doc-count`).innerText = data.count || 0;
|
|
281
|
-
loadDocuments()
|
|
282
|
-
} else {
|
|
283
|
-
showModal('error', 'Delete Failed', await res.json().error || 'Delete failed, please try again.');
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async function deleteAllDocs() {
|
|
288
|
-
try {
|
|
289
|
-
const confirmDelete = confirm("Are you sure you want to delete all documents?");
|
|
290
|
-
if (!confirmDelete) return;
|
|
291
|
-
|
|
292
|
-
const modelName = content.dataset?.modelName;
|
|
293
|
-
if (!modelName) {
|
|
294
|
-
showModal('error', 'Error Occurred!', 'No model selected.');
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const res = await fetch(`/delete-all/${modelName}`, { method: "DELETE" });
|
|
299
|
-
if (res.ok) {
|
|
300
|
-
showModal('info', 'All Documents Deleted', 'All documents deleted successfully.');
|
|
301
|
-
loadDocuments();
|
|
302
|
-
} else {
|
|
303
|
-
showModal('error', 'Delete Failed', await res.json().error || 'Delete failed, please try again.');
|
|
304
|
-
}
|
|
305
|
-
} catch (error) {
|
|
306
|
-
showModal('error', 'Delete Failed', error.message || 'Delete failed, please try again.');
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function dragStart(e) {
|
|
311
|
-
dragSrc = this;
|
|
312
|
-
e.dataTransfer.effectAllowed = "move";
|
|
313
|
-
this.classList.add("dragging");
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function dragOver(e) {
|
|
317
|
-
e.preventDefault();
|
|
318
|
-
e.dataTransfer.dropEffect = "move";
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function drop(e) {
|
|
322
|
-
e.preventDefault();
|
|
323
|
-
if (dragSrc !== this) {
|
|
324
|
-
// Swap positions
|
|
325
|
-
const draggedIndex = Array.from(tabsContainer.children).indexOf(dragSrc);
|
|
326
|
-
const targetIndex = Array.from(tabsContainer.children).indexOf(this);
|
|
327
|
-
|
|
328
|
-
if (draggedIndex < targetIndex) {
|
|
329
|
-
tabsContainer.insertBefore(dragSrc, this.nextSibling);
|
|
330
|
-
} else {
|
|
331
|
-
tabsContainer.insertBefore(dragSrc, this);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function dragEnd() {
|
|
337
|
-
this.classList.remove("dragging");
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async function refreshSideBar() {
|
|
341
|
-
try {
|
|
342
|
-
const modelWrapper = document.querySelector('.model-wrapper');
|
|
343
|
-
|
|
344
|
-
const res = await fetch("/models");
|
|
345
|
-
|
|
346
|
-
if (!res.ok) {
|
|
347
|
-
const error = await res.json();
|
|
348
|
-
showModal('error', 'Error Occurred!', error.error || 'Something went wrong, please try again.');
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
modelWrapper.innerHTML = "";
|
|
353
|
-
const data = await res.json();
|
|
354
|
-
|
|
355
|
-
data.models.forEach(model => {
|
|
356
|
-
const modelDiv = document.createElement('div');
|
|
357
|
-
modelDiv.classList.add('model');
|
|
358
|
-
modelDiv.onclick = () => openModel(model.name);
|
|
359
|
-
modelDiv.innerHTML = `${model.name} <span id="${model.name}-doc-count">${model.count}</span>`;
|
|
360
|
-
modelWrapper.appendChild(modelDiv);
|
|
361
|
-
});
|
|
362
|
-
} catch (error) {
|
|
363
|
-
showModal('error', 'Error Occurred!', error.message || 'Something went wrong, please try again.');
|
|
364
|
-
}
|
|
1
|
+
const tabsContainer = document.getElementById("tabs");
|
|
2
|
+
const content = document.getElementById("content");
|
|
3
|
+
let currentModelName = "";
|
|
4
|
+
let loadedDocuments = [];
|
|
5
|
+
const openTabs = {};
|
|
6
|
+
|
|
7
|
+
function openModel(modelName) {
|
|
8
|
+
|
|
9
|
+
if (!openTabs[modelName]) {
|
|
10
|
+
// Create a new tab
|
|
11
|
+
const tab = document.createElement("div");
|
|
12
|
+
tab.className = "tab";
|
|
13
|
+
tab.id = "tab-" + modelName;
|
|
14
|
+
tab.draggable = true;
|
|
15
|
+
tab.innerHTML = modelName + '<span class="close" onclick="closeTab(event, \'' + modelName + '\')">×</span>';
|
|
16
|
+
tab.onclick = () => activateTab(modelName);
|
|
17
|
+
tabsContainer.appendChild(tab);
|
|
18
|
+
|
|
19
|
+
tab.addEventListener("dragstart", dragStart);
|
|
20
|
+
tab.addEventListener("dragover", dragOver);
|
|
21
|
+
tab.addEventListener("drop", drop);
|
|
22
|
+
tab.addEventListener("dragend", dragEnd);
|
|
23
|
+
|
|
24
|
+
openTabs[modelName] = tab;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
activateTab(modelName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function activateTab(modelName) {
|
|
32
|
+
|
|
33
|
+
// Prevent if tab is already active
|
|
34
|
+
if (content.dataset?.modelName == modelName) return
|
|
35
|
+
|
|
36
|
+
content.dataset.modelName = modelName;
|
|
37
|
+
document.getElementById("page-value").innerText = 1;
|
|
38
|
+
document.querySelectorAll(".operations .btn")?.forEach(btn => btn.disabled = false)
|
|
39
|
+
document.querySelector(".operations select").disabled = false;
|
|
40
|
+
|
|
41
|
+
const insertTab = document.querySelector('.insert-tab');
|
|
42
|
+
insertTab.classList.remove('active');
|
|
43
|
+
insertTab.innerHTML = "";
|
|
44
|
+
// Remove active class from all tabs
|
|
45
|
+
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
|
46
|
+
|
|
47
|
+
// Set current tab active
|
|
48
|
+
const currentTab = document.getElementById("tab-" + modelName);
|
|
49
|
+
currentTab.classList.add("active");
|
|
50
|
+
|
|
51
|
+
// Update content
|
|
52
|
+
await loadDocuments();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function closeTab(event, modelName) {
|
|
56
|
+
event.stopPropagation(); // prevent tab click
|
|
57
|
+
const tab = document.getElementById("tab-" + modelName);
|
|
58
|
+
tab.remove();
|
|
59
|
+
delete openTabs[modelName];
|
|
60
|
+
|
|
61
|
+
// Clear content if that tab was active and check if other tabs are open to be active
|
|
62
|
+
if (tab.classList.contains("active")) {
|
|
63
|
+
content.innerHTML = "";
|
|
64
|
+
const keys = Object.keys(openTabs);
|
|
65
|
+
const modelName = keys[keys.length - 1] || null;
|
|
66
|
+
if (modelName) activateTab(modelName);
|
|
67
|
+
else disableAllOptions();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function disableAllOptions() {
|
|
72
|
+
content.dataset.modelName = '';
|
|
73
|
+
|
|
74
|
+
document.querySelectorAll(".operations .btn")?.forEach(btn => btn.disabled = true);
|
|
75
|
+
document.getElementById("document-count").innerText = 0;
|
|
76
|
+
document.querySelector(".operations select").disabled = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function loadDocuments() {
|
|
80
|
+
|
|
81
|
+
content.innerHTML = "";
|
|
82
|
+
const limit = document.getElementById("limit").value;
|
|
83
|
+
const page = document.getElementById("page-value").innerText;
|
|
84
|
+
const modelName = content.dataset?.modelName;
|
|
85
|
+
|
|
86
|
+
if (!modelName) {
|
|
87
|
+
showModal('error', 'Error Occurred!', 'No model selected.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const response = await fetch(`/models/${modelName}?limit=${limit}&page=${page}`);
|
|
92
|
+
if (response.status !== 200) {
|
|
93
|
+
showModal('error', 'Error Occurred!', 'Something went wrong, please try again.');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
|
|
98
|
+
document.getElementById("document-count").innerText = data.count;
|
|
99
|
+
document.getElementById("page-value").dataset.totalPages = data.totalPages;
|
|
100
|
+
document.getElementById(`${modelName}-doc-count`).innerText = data.totalCount;
|
|
101
|
+
|
|
102
|
+
if (Number(page) >= Number(data.totalPages)) document.getElementById("next-page").disabled = true;
|
|
103
|
+
else document.getElementById("next-page").disabled = false;
|
|
104
|
+
|
|
105
|
+
if (Number(page) <= 1) document.getElementById("previous-page").disabled = true;
|
|
106
|
+
else document.getElementById("previous-page").disabled = false;
|
|
107
|
+
|
|
108
|
+
currentModelName = modelName;
|
|
109
|
+
loadedDocuments = data.documents;
|
|
110
|
+
// Render each array element as its own tree
|
|
111
|
+
data.documents.forEach((doc, index) => {
|
|
112
|
+
const wrapper = document.createElement('div');
|
|
113
|
+
const divDocument = document.createElement('div');
|
|
114
|
+
divDocument.innerHTML += Object.entries(doc).map(([key, value]) => renderData(key, value, index)).join('');
|
|
115
|
+
|
|
116
|
+
divDocument.classList.add('document');
|
|
117
|
+
|
|
118
|
+
wrapper.appendChild(divDocument);
|
|
119
|
+
wrapper.innerHTML += `<div class="document-actions"><button class="updateDocument" onclick="updateDocument(${index})">Update</button><button class="deleteDocument" onclick="deleteDoc('${modelName}', '${doc._id}')">Delete</button></div>`;
|
|
120
|
+
|
|
121
|
+
content.appendChild(wrapper);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function toggleEditMode(id) {
|
|
126
|
+
const htmlData = document.getElementById(`${id}-html-data`);
|
|
127
|
+
const jsonData = document.getElementById(`${id}-json-data`);
|
|
128
|
+
|
|
129
|
+
htmlData.classList.toggle('hidden');
|
|
130
|
+
jsonData.classList.toggle('hidden');
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function gotoNextPage() {
|
|
135
|
+
const page = document.getElementById("page-value");
|
|
136
|
+
page.innerText = Number(page.innerText) + 1;
|
|
137
|
+
|
|
138
|
+
if (Number(page.innerText) > Number(page.dataset.totalPages)) {
|
|
139
|
+
page.innerText = Number(page.dataset.totalPages);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
loadDocuments();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function gotoPreviousPage() {
|
|
147
|
+
const page = document.getElementById("page-value").innerText;
|
|
148
|
+
document.getElementById("page-value").innerText = Number(page) - 1;
|
|
149
|
+
|
|
150
|
+
if (Number(page) - 1 < 1) {
|
|
151
|
+
document.getElementById("page-value").innerText = 1;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
loadDocuments();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function renderData(key, value, index, level = "root.", parentDataType = "object") {
|
|
159
|
+
const type = typeof value;
|
|
160
|
+
if (value === null) return `<div class="field"><span class="data-key">${key}</span>: <span class="null" data-level="${level}" data-index="${index}">null</span><span class="data-type">(null)</span></div>`;
|
|
161
|
+
|
|
162
|
+
if (Array.isArray(value)) {
|
|
163
|
+
return `
|
|
164
|
+
<details class="field">
|
|
165
|
+
<summary ><span class="data-key">${key}</span><span class="data-type"> (Array - ${value.length})</span></summary>
|
|
166
|
+
<div class="data-value">
|
|
167
|
+
${value.map((v, i) => renderData(`[${i}]`, v, index, level + key, "array")).join('')}
|
|
168
|
+
</div>
|
|
169
|
+
</details>
|
|
170
|
+
`;
|
|
171
|
+
} else if (type === 'object') {
|
|
172
|
+
return `
|
|
173
|
+
<details class="field">
|
|
174
|
+
<summary><span class="data-key">${key}</span><span class="data-type"> (Object)</span></summary>
|
|
175
|
+
<div class="data-value">
|
|
176
|
+
${Object.entries(value).map(([k, v]) => renderData(k, v, index, level + key, "object")).join('')}
|
|
177
|
+
</div>
|
|
178
|
+
</details>
|
|
179
|
+
`;
|
|
180
|
+
} else if (typeof value === 'string' && /^[a-f\d]{24}$/i.test(value)) {
|
|
181
|
+
|
|
182
|
+
return `<div class="field"><span class="data-key">${key}</span>: <span class="ObjectId">${value}</span></div>`;
|
|
183
|
+
} else {
|
|
184
|
+
return `<div class="field"><span class="data-key">${key}</span>: <span class="${type}" data-level="${level}" data-index="${index}" data-parent-data-type="${parentDataType}" contenteditable >${value}</span></div>`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Handle collapsibles
|
|
189
|
+
document.addEventListener('click', function (e) {
|
|
190
|
+
if (e.target.classList.contains('collapsible')) {
|
|
191
|
+
e.target.classList.toggle("caret-down");
|
|
192
|
+
const nested = e.target.nextElementSibling;
|
|
193
|
+
if (nested) {
|
|
194
|
+
nested.classList.toggle("active");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
function setNestedValue(obj, path, value, parentDataType) {
|
|
200
|
+
const keys = path.split(".");
|
|
201
|
+
let current = obj;
|
|
202
|
+
|
|
203
|
+
keys.forEach((key, index) => {
|
|
204
|
+
if (index === keys.length - 1) {
|
|
205
|
+
const arrayIndexMatch = key.match(/\[(\d+)\]/);
|
|
206
|
+
if (arrayIndexMatch) {
|
|
207
|
+
const idx = parseInt(arrayIndexMatch[1], 10);
|
|
208
|
+
current[idx] = value;
|
|
209
|
+
} else {
|
|
210
|
+
current[key] = value;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
} else {
|
|
214
|
+
if (parentDataType === "array" && !Array.isArray(current[key])) {
|
|
215
|
+
current[key] = [];
|
|
216
|
+
} else if (parentDataType === "object" && typeof current[key] !== "object") {
|
|
217
|
+
current[key] = {};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
current = current[key]; // Go deeper
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function updateDocument(index) {
|
|
226
|
+
|
|
227
|
+
const confirmUpdate = confirm("Are you sure you want to update this document?");
|
|
228
|
+
if (!confirmUpdate) return;
|
|
229
|
+
|
|
230
|
+
const doc = loadedDocuments[index];
|
|
231
|
+
const id = doc._id;
|
|
232
|
+
|
|
233
|
+
let updatedDoc = {};
|
|
234
|
+
const values = document.querySelectorAll(`[data-index="${index}"]`);
|
|
235
|
+
|
|
236
|
+
values.forEach(el => {
|
|
237
|
+
const key = el.previousElementSibling?.textContent?.trim();
|
|
238
|
+
const level = el.dataset.level;
|
|
239
|
+
|
|
240
|
+
if (key) {
|
|
241
|
+
if (level && level !== "root") {
|
|
242
|
+
// Build the full path
|
|
243
|
+
const fullPath = `${level.replace("root.", "")}.${key}`;
|
|
244
|
+
setNestedValue(updatedDoc, fullPath, el.textContent, el.dataset.parentDataType);
|
|
245
|
+
} else {
|
|
246
|
+
updatedDoc[key] = el.textContent;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
updatedDoc = { ...updatedDoc, ...updatedDoc[""] }
|
|
252
|
+
|
|
253
|
+
const res = await fetch(`/update/${currentModelName}/${id}`, {
|
|
254
|
+
method: "PUT",
|
|
255
|
+
headers: { "Content-Type": "application/json" },
|
|
256
|
+
body: JSON.stringify(updatedDoc),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (res.ok) {
|
|
260
|
+
showModal('info', 'Document Updated', 'Document updated successfully.');
|
|
261
|
+
loadDocuments();
|
|
262
|
+
} else {
|
|
263
|
+
const data = await res.json();
|
|
264
|
+
showModal('error', 'Update Failed', data.error || 'Update failed, please try again.');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function deleteDoc(modelName, id) {
|
|
269
|
+
const confirmDelete = confirm("Are you sure you want to delete this document?");
|
|
270
|
+
if (!confirmDelete) return;
|
|
271
|
+
|
|
272
|
+
const res = await fetch(`/delete/${modelName}/${id}`, {
|
|
273
|
+
method: "DELETE"
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (res.ok) {
|
|
277
|
+
showModal('info', 'Document Deleted', 'Document deleted successfully.');
|
|
278
|
+
openModel(modelName);
|
|
279
|
+
const data = await res.json();
|
|
280
|
+
document.getElementById(`${modelName}-doc-count`).innerText = data.count || 0;
|
|
281
|
+
loadDocuments()
|
|
282
|
+
} else {
|
|
283
|
+
showModal('error', 'Delete Failed', await res.json().error || 'Delete failed, please try again.');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function deleteAllDocs() {
|
|
288
|
+
try {
|
|
289
|
+
const confirmDelete = confirm("Are you sure you want to delete all documents?");
|
|
290
|
+
if (!confirmDelete) return;
|
|
291
|
+
|
|
292
|
+
const modelName = content.dataset?.modelName;
|
|
293
|
+
if (!modelName) {
|
|
294
|
+
showModal('error', 'Error Occurred!', 'No model selected.');
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const res = await fetch(`/delete-all/${modelName}`, { method: "DELETE" });
|
|
299
|
+
if (res.ok) {
|
|
300
|
+
showModal('info', 'All Documents Deleted', 'All documents deleted successfully.');
|
|
301
|
+
loadDocuments();
|
|
302
|
+
} else {
|
|
303
|
+
showModal('error', 'Delete Failed', await res.json().error || 'Delete failed, please try again.');
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
showModal('error', 'Delete Failed', error.message || 'Delete failed, please try again.');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function dragStart(e) {
|
|
311
|
+
dragSrc = this;
|
|
312
|
+
e.dataTransfer.effectAllowed = "move";
|
|
313
|
+
this.classList.add("dragging");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function dragOver(e) {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
e.dataTransfer.dropEffect = "move";
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function drop(e) {
|
|
322
|
+
e.preventDefault();
|
|
323
|
+
if (dragSrc !== this) {
|
|
324
|
+
// Swap positions
|
|
325
|
+
const draggedIndex = Array.from(tabsContainer.children).indexOf(dragSrc);
|
|
326
|
+
const targetIndex = Array.from(tabsContainer.children).indexOf(this);
|
|
327
|
+
|
|
328
|
+
if (draggedIndex < targetIndex) {
|
|
329
|
+
tabsContainer.insertBefore(dragSrc, this.nextSibling);
|
|
330
|
+
} else {
|
|
331
|
+
tabsContainer.insertBefore(dragSrc, this);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function dragEnd() {
|
|
337
|
+
this.classList.remove("dragging");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function refreshSideBar() {
|
|
341
|
+
try {
|
|
342
|
+
const modelWrapper = document.querySelector('.model-wrapper');
|
|
343
|
+
|
|
344
|
+
const res = await fetch("/models");
|
|
345
|
+
|
|
346
|
+
if (!res.ok) {
|
|
347
|
+
const error = await res.json();
|
|
348
|
+
showModal('error', 'Error Occurred!', error.error || 'Something went wrong, please try again.');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
modelWrapper.innerHTML = "";
|
|
353
|
+
const data = await res.json();
|
|
354
|
+
|
|
355
|
+
data.models.forEach(model => {
|
|
356
|
+
const modelDiv = document.createElement('div');
|
|
357
|
+
modelDiv.classList.add('model');
|
|
358
|
+
modelDiv.onclick = () => openModel(model.name);
|
|
359
|
+
modelDiv.innerHTML = `${model.name} <span id="${model.name}-doc-count">${model.count}</span>`;
|
|
360
|
+
modelWrapper.appendChild(modelDiv);
|
|
361
|
+
});
|
|
362
|
+
} catch (error) {
|
|
363
|
+
showModal('error', 'Error Occurred!', error.message || 'Something went wrong, please try again.');
|
|
364
|
+
}
|
|
365
365
|
}
|