@intranefr/superbackend 1.5.0 → 1.5.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/.env.example +15 -0
- package/README.md +11 -0
- package/analysis-only.skill +0 -0
- package/index.js +23 -0
- package/package.json +8 -2
- package/src/admin/endpointRegistry.js +120 -0
- package/src/controllers/admin.controller.js +90 -6
- package/src/controllers/adminBlockDefinitions.controller.js +127 -0
- package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
- package/src/controllers/adminCache.controller.js +342 -0
- package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
- package/src/controllers/adminCrons.controller.js +388 -0
- package/src/controllers/adminDbBrowser.controller.js +124 -0
- package/src/controllers/adminEjsVirtual.controller.js +13 -3
- package/src/controllers/adminExperiments.controller.js +200 -0
- package/src/controllers/adminHeadless.controller.js +9 -2
- package/src/controllers/adminHealthChecks.controller.js +570 -0
- package/src/controllers/adminI18n.controller.js +51 -29
- package/src/controllers/adminLlm.controller.js +126 -2
- package/src/controllers/adminPages.controller.js +720 -0
- package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
- package/src/controllers/adminProxy.controller.js +113 -0
- package/src/controllers/adminRateLimits.controller.js +138 -0
- package/src/controllers/adminRbac.controller.js +803 -0
- package/src/controllers/adminScripts.controller.js +126 -4
- package/src/controllers/adminSeoConfig.controller.js +71 -48
- package/src/controllers/blogAdmin.controller.js +279 -0
- package/src/controllers/blogAiAdmin.controller.js +224 -0
- package/src/controllers/blogAutomationAdmin.controller.js +141 -0
- package/src/controllers/blogInternal.controller.js +26 -0
- package/src/controllers/blogPublic.controller.js +89 -0
- package/src/controllers/experiments.controller.js +85 -0
- package/src/controllers/fileManager.controller.js +190 -0
- package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
- package/src/controllers/healthChecksPublic.controller.js +196 -0
- package/src/controllers/internalExperiments.controller.js +17 -0
- package/src/controllers/metrics.controller.js +64 -4
- package/src/controllers/orgAdmin.controller.js +80 -0
- package/src/helpers/mongooseHelper.js +258 -0
- package/src/helpers/scriptBase.js +230 -0
- package/src/helpers/scriptRunner.js +335 -0
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +810 -48
- package/src/models/BlockDefinition.js +27 -0
- package/src/models/BlogAutomationLock.js +14 -0
- package/src/models/BlogAutomationRun.js +39 -0
- package/src/models/BlogPost.js +42 -0
- package/src/models/CacheEntry.js +26 -0
- package/src/models/ConsoleEntry.js +32 -0
- package/src/models/ConsoleLog.js +23 -0
- package/src/models/ContextBlockDefinition.js +33 -0
- package/src/models/CronExecution.js +47 -0
- package/src/models/CronJob.js +70 -0
- package/src/models/Experiment.js +75 -0
- package/src/models/ExperimentAssignment.js +23 -0
- package/src/models/ExperimentEvent.js +26 -0
- package/src/models/ExperimentMetricBucket.js +30 -0
- package/src/models/ExternalDbConnection.js +49 -0
- package/src/models/FileEntry.js +22 -0
- package/src/models/GlobalSetting.js +1 -2
- package/src/models/HealthAutoHealAttempt.js +57 -0
- package/src/models/HealthCheck.js +132 -0
- package/src/models/HealthCheckRun.js +51 -0
- package/src/models/HealthIncident.js +49 -0
- package/src/models/Page.js +95 -0
- package/src/models/PageCollection.js +42 -0
- package/src/models/ProxyEntry.js +66 -0
- package/src/models/RateLimitCounter.js +19 -0
- package/src/models/RateLimitMetricBucket.js +20 -0
- package/src/models/RbacGrant.js +25 -0
- package/src/models/RbacGroup.js +16 -0
- package/src/models/RbacGroupMember.js +13 -0
- package/src/models/RbacGroupRole.js +13 -0
- package/src/models/RbacRole.js +25 -0
- package/src/models/RbacUserRole.js +13 -0
- package/src/models/ScriptDefinition.js +1 -0
- package/src/models/Webhook.js +2 -0
- package/src/routes/admin.routes.js +2 -0
- package/src/routes/adminBlog.routes.js +21 -0
- package/src/routes/adminBlogAi.routes.js +16 -0
- package/src/routes/adminBlogAutomation.routes.js +27 -0
- package/src/routes/adminCache.routes.js +20 -0
- package/src/routes/adminConsoleManager.routes.js +302 -0
- package/src/routes/adminCrons.routes.js +25 -0
- package/src/routes/adminDbBrowser.routes.js +65 -0
- package/src/routes/adminEjsVirtual.routes.js +2 -1
- package/src/routes/adminExperiments.routes.js +29 -0
- package/src/routes/adminHeadless.routes.js +2 -1
- package/src/routes/adminHealthChecks.routes.js +28 -0
- package/src/routes/adminI18n.routes.js +4 -3
- package/src/routes/adminLlm.routes.js +4 -2
- package/src/routes/adminPages.routes.js +55 -0
- package/src/routes/adminProxy.routes.js +15 -0
- package/src/routes/adminRateLimits.routes.js +17 -0
- package/src/routes/adminRbac.routes.js +38 -0
- package/src/routes/adminSeoConfig.routes.js +5 -4
- package/src/routes/adminUiComponents.routes.js +2 -1
- package/src/routes/blogInternal.routes.js +14 -0
- package/src/routes/blogPublic.routes.js +9 -0
- package/src/routes/experiments.routes.js +30 -0
- package/src/routes/fileManager.routes.js +62 -0
- package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
- package/src/routes/healthChecksPublic.routes.js +9 -0
- package/src/routes/internalExperiments.routes.js +15 -0
- package/src/routes/log.routes.js +43 -60
- package/src/routes/metrics.routes.js +4 -2
- package/src/routes/orgAdmin.routes.js +1 -0
- package/src/routes/pages.routes.js +123 -0
- package/src/routes/proxy.routes.js +46 -0
- package/src/routes/rbac.routes.js +47 -0
- package/src/routes/webhook.routes.js +2 -1
- package/src/routes/workflows.routes.js +4 -0
- package/src/services/blockDefinitionsAi.service.js +247 -0
- package/src/services/blog.service.js +99 -0
- package/src/services/blogAutomation.service.js +978 -0
- package/src/services/blogCronsBootstrap.service.js +185 -0
- package/src/services/blogPublishing.service.js +58 -0
- package/src/services/cacheLayer.service.js +696 -0
- package/src/services/consoleManager.service.js +738 -0
- package/src/services/consoleOverride.service.js +7 -1
- package/src/services/cronScheduler.service.js +350 -0
- package/src/services/dbBrowser.service.js +536 -0
- package/src/services/ejsVirtual.service.js +102 -32
- package/src/services/experiments.service.js +273 -0
- package/src/services/experimentsAggregation.service.js +308 -0
- package/src/services/experimentsCronsBootstrap.service.js +118 -0
- package/src/services/experimentsRetention.service.js +43 -0
- package/src/services/experimentsWs.service.js +134 -0
- package/src/services/fileManager.service.js +475 -0
- package/src/services/fileManagerStoragePolicy.service.js +285 -0
- package/src/services/globalSettings.service.js +15 -0
- package/src/services/healthChecks.service.js +650 -0
- package/src/services/healthChecksBootstrap.service.js +109 -0
- package/src/services/healthChecksScheduler.service.js +106 -0
- package/src/services/jsonConfigs.service.js +2 -2
- package/src/services/llmDefaults.service.js +190 -0
- package/src/services/migrationAssets/s3.js +2 -2
- package/src/services/pages.service.js +602 -0
- package/src/services/pagesContext.service.js +331 -0
- package/src/services/pagesContextBlocksAi.service.js +349 -0
- package/src/services/proxy.service.js +535 -0
- package/src/services/rateLimiter.service.js +623 -0
- package/src/services/rbac.service.js +212 -0
- package/src/services/scriptsRunner.service.js +215 -15
- package/src/services/uiComponentsAi.service.js +6 -19
- package/src/services/workflow.service.js +23 -8
- package/src/utils/orgRoles.js +14 -0
- package/src/utils/rbac/engine.js +60 -0
- package/src/utils/rbac/rightsRegistry.js +33 -0
- package/views/admin-blog-automation.ejs +877 -0
- package/views/admin-blog-edit.ejs +542 -0
- package/views/admin-blog.ejs +399 -0
- package/views/admin-cache.ejs +681 -0
- package/views/admin-console-manager.ejs +680 -0
- package/views/admin-crons.ejs +645 -0
- package/views/admin-dashboard.ejs +28 -8
- package/views/admin-db-browser.ejs +445 -0
- package/views/admin-ejs-virtual.ejs +16 -10
- package/views/admin-experiments.ejs +91 -0
- package/views/admin-file-manager.ejs +942 -0
- package/views/admin-health-checks.ejs +725 -0
- package/views/admin-i18n.ejs +59 -5
- package/views/admin-llm.ejs +99 -1
- package/views/admin-organizations.ejs +163 -1
- package/views/admin-pages.ejs +2424 -0
- package/views/admin-proxy.ejs +491 -0
- package/views/admin-rate-limiter.ejs +625 -0
- package/views/admin-rbac.ejs +1331 -0
- package/views/admin-scripts.ejs +597 -3
- package/views/admin-seo-config.ejs +61 -7
- package/views/admin-ui-components.ejs +57 -25
- package/views/admin-workflows.ejs +7 -7
- package/views/file-manager.ejs +866 -0
- package/views/pages/blocks/contact.ejs +27 -0
- package/views/pages/blocks/cta.ejs +18 -0
- package/views/pages/blocks/faq.ejs +20 -0
- package/views/pages/blocks/features.ejs +19 -0
- package/views/pages/blocks/hero.ejs +13 -0
- package/views/pages/blocks/html.ejs +5 -0
- package/views/pages/blocks/image.ejs +14 -0
- package/views/pages/blocks/testimonials.ejs +26 -0
- package/views/pages/blocks/text.ejs +10 -0
- package/views/pages/layouts/default.ejs +51 -0
- package/views/pages/layouts/minimal.ejs +42 -0
- package/views/pages/layouts/sidebar.ejs +54 -0
- package/views/pages/partials/footer.ejs +13 -0
- package/views/pages/partials/header.ejs +12 -0
- package/views/pages/partials/sidebar.ejs +8 -0
- package/views/pages/runtime/page.ejs +10 -0
- package/views/pages/templates/article.ejs +20 -0
- package/views/pages/templates/default.ejs +12 -0
- package/views/pages/templates/landing.ejs +14 -0
- package/views/pages/templates/listing.ejs +15 -0
- package/views/partials/admin-image-upload-modal.ejs +221 -0
- package/views/partials/dashboard/nav-items.ejs +12 -0
- package/views/partials/dashboard/palette.ejs +5 -3
- package/views/partials/llm-provider-model-picker.ejs +183 -0
- package/src/routes/llmUi.routes.js +0 -26
|
@@ -0,0 +1,680 @@
|
|
|
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>Admin Console Manager</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="bg-gray-50">
|
|
12
|
+
<div id="app" class="max-w-7xl mx-auto px-6 py-6" v-cloak>
|
|
13
|
+
<div class="flex items-center justify-between mb-6">
|
|
14
|
+
<div>
|
|
15
|
+
<h1 class="text-2xl font-semibold text-gray-900">Console Manager</h1>
|
|
16
|
+
<div class="text-sm text-gray-500">Manage backend console entries, tags, and persisted logs</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="flex items-center gap-2">
|
|
19
|
+
<button @click="refreshAll" class="px-3 py-2 rounded bg-gray-600 text-white text-sm hover:bg-gray-700">
|
|
20
|
+
<i class="ti ti-refresh mr-1"></i> Refresh
|
|
21
|
+
</button>
|
|
22
|
+
<button @click="saveConfig" class="px-3 py-2 rounded bg-blue-600 text-white text-sm hover:bg-blue-700">
|
|
23
|
+
<i class="ti ti-device-floppy mr-1"></i> Save Config
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="bg-white border border-gray-200 rounded-lg">
|
|
29
|
+
<div class="p-4 border-b border-gray-200 flex items-center gap-2">
|
|
30
|
+
<button @click="activeTab='entries'" :class="tabClass('entries')" class="px-3 py-2 rounded text-sm">
|
|
31
|
+
Entries
|
|
32
|
+
</button>
|
|
33
|
+
<button @click="activeTab='logs'" :class="tabClass('logs')" class="px-3 py-2 rounded text-sm">
|
|
34
|
+
Logs
|
|
35
|
+
</button>
|
|
36
|
+
<button @click="activeTab='config'" :class="tabClass('config')" class="px-3 py-2 rounded text-sm">
|
|
37
|
+
Config
|
|
38
|
+
</button>
|
|
39
|
+
<div class="flex-1"></div>
|
|
40
|
+
<div v-if="status" class="text-sm text-gray-600">{{ status }}</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="p-4" v-if="activeTab==='entries'">
|
|
44
|
+
<div class="grid grid-cols-12 gap-4 mb-4">
|
|
45
|
+
<div class="col-span-4">
|
|
46
|
+
<label class="text-xs font-semibold text-gray-600">Search</label>
|
|
47
|
+
<input v-model="entriesQuery.q" @keyup.enter="loadEntries" class="mt-1 w-full border rounded px-3 py-2" placeholder="message, hash, topFrame" />
|
|
48
|
+
</div>
|
|
49
|
+
<div class="col-span-2">
|
|
50
|
+
<label class="text-xs font-semibold text-gray-600">Method</label>
|
|
51
|
+
<select v-model="entriesQuery.method" class="mt-1 w-full border rounded px-3 py-2">
|
|
52
|
+
<option value="">(any)</option>
|
|
53
|
+
<option value="debug">debug</option>
|
|
54
|
+
<option value="log">log</option>
|
|
55
|
+
<option value="info">info</option>
|
|
56
|
+
<option value="warn">warn</option>
|
|
57
|
+
<option value="error">error</option>
|
|
58
|
+
</select>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="col-span-2">
|
|
61
|
+
<label class="text-xs font-semibold text-gray-600">Enabled</label>
|
|
62
|
+
<select v-model="entriesQuery.enabled" class="mt-1 w-full border rounded px-3 py-2">
|
|
63
|
+
<option value="">(any)</option>
|
|
64
|
+
<option value="true">enabled</option>
|
|
65
|
+
<option value="false">disabled</option>
|
|
66
|
+
</select>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="col-span-2">
|
|
69
|
+
<label class="text-xs font-semibold text-gray-600">Page size</label>
|
|
70
|
+
<select v-model.number="entriesQuery.pageSize" class="mt-1 w-full border rounded px-3 py-2">
|
|
71
|
+
<option :value="20">20</option>
|
|
72
|
+
<option :value="50">50</option>
|
|
73
|
+
<option :value="100">100</option>
|
|
74
|
+
<option :value="200">200</option>
|
|
75
|
+
</select>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="col-span-2 flex items-end">
|
|
78
|
+
<button @click="loadEntries" class="w-full px-3 py-2 rounded bg-gray-900 text-white text-sm hover:bg-black">
|
|
79
|
+
Load
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div class="mb-4">
|
|
85
|
+
<div class="text-xs font-semibold text-gray-600 mb-2">Tags</div>
|
|
86
|
+
<div class="flex flex-wrap gap-2">
|
|
87
|
+
<button
|
|
88
|
+
v-for="t in tags"
|
|
89
|
+
:key="t.tag"
|
|
90
|
+
@click="toggleTag(t.tag)"
|
|
91
|
+
class="px-2.5 py-1 rounded-full text-xs border"
|
|
92
|
+
:class="selectedTags.includes(t.tag) ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50'"
|
|
93
|
+
>
|
|
94
|
+
{{ t.tag }}
|
|
95
|
+
<span class="ml-1 opacity-75">({{ t.count }})</span>
|
|
96
|
+
</button>
|
|
97
|
+
<div v-if="!tags.length" class="text-xs text-gray-500">No tags yet.</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="flex flex-wrap items-center gap-2 mb-4">
|
|
102
|
+
<button @click="bulkEnable(true)" class="px-3 py-2 rounded bg-green-600 text-white text-sm hover:bg-green-700" :disabled="!selectedHashes.length">
|
|
103
|
+
Enable selected
|
|
104
|
+
</button>
|
|
105
|
+
<button @click="bulkEnable(false)" class="px-3 py-2 rounded bg-orange-600 text-white text-sm hover:bg-orange-700" :disabled="!selectedHashes.length">
|
|
106
|
+
Disable selected
|
|
107
|
+
</button>
|
|
108
|
+
<div class="flex items-center gap-2">
|
|
109
|
+
<input v-model="bulkTag" class="border rounded px-3 py-2 text-sm" placeholder="Tag name" />
|
|
110
|
+
<button @click="bulkAddTag" class="px-3 py-2 rounded bg-blue-600 text-white text-sm hover:bg-blue-700" :disabled="!selectedHashes.length || !bulkTag.trim()">
|
|
111
|
+
Add tag
|
|
112
|
+
</button>
|
|
113
|
+
<button @click="bulkRemoveTag" class="px-3 py-2 rounded bg-gray-600 text-white text-sm hover:bg-gray-700" :disabled="!selectedHashes.length || !bulkTag.trim()">
|
|
114
|
+
Remove tag
|
|
115
|
+
</button>
|
|
116
|
+
<button @click="confirmBulkDelete" class="px-3 py-2 rounded bg-red-600 text-white text-sm hover:bg-red-700" :disabled="!selectedHashes.length">
|
|
117
|
+
Delete selected
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="text-sm text-gray-500">Selected: {{ selectedHashes.length }}</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Confirmation Modal -->
|
|
124
|
+
<div v-if="showDeleteModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
125
|
+
<div class="bg-white rounded-lg p-6 max-w-md w-full mx-4">
|
|
126
|
+
<div class="flex items-center mb-4">
|
|
127
|
+
<i class="ti ti-alert-triangle text-red-600 text-2xl mr-3"></i>
|
|
128
|
+
<h3 class="text-lg font-semibold text-gray-900">Delete Console Entries</h3>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="text-sm text-gray-600 mb-4">
|
|
131
|
+
<p class="mb-2">You are about to delete <span class="font-semibold">{{ selectedHashes.length }}</span> console entr{{ selectedHashes.length === 1 ? 'y' : 'ies' }}.</p>
|
|
132
|
+
<p class="mb-3">This action cannot be undone.</p>
|
|
133
|
+
<label class="flex items-center gap-2">
|
|
134
|
+
<input type="checkbox" v-model="deleteLogsAlso" class="rounded" />
|
|
135
|
+
<span class="text-sm">Also delete associated logs ({{ selectedHashes.length }} entr{{ selectedHashes.length === 1 ? 'y' : 'ies' }})</span>
|
|
136
|
+
</label>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="flex justify-end gap-3">
|
|
139
|
+
<button @click="showDeleteModal = false" class="px-4 py-2 rounded border border-gray-300 text-gray-700 hover:bg-gray-50">
|
|
140
|
+
Cancel
|
|
141
|
+
</button>
|
|
142
|
+
<button @click="bulkDelete" class="px-4 py-2 rounded bg-red-600 text-white hover:bg-red-700" :disabled="isDeleting">
|
|
143
|
+
<span v-if="isDeleting">Deleting...</span>
|
|
144
|
+
<span v-else>Delete{{ deleteLogsAlso ? ' Entries & Logs' : ' Entries' }}</span>
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div class="overflow-x-auto border border-gray-200 rounded">
|
|
151
|
+
<table class="w-full">
|
|
152
|
+
<thead class="bg-gray-50 border-b border-gray-200">
|
|
153
|
+
<tr>
|
|
154
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
155
|
+
<input type="checkbox" @change="toggleSelectAll($event)" :checked="allSelected" />
|
|
156
|
+
</th>
|
|
157
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Enabled</th>
|
|
158
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Method</th>
|
|
159
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Message</th>
|
|
160
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Top frame</th>
|
|
161
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Last seen</th>
|
|
162
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Count</th>
|
|
163
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Tags</th>
|
|
164
|
+
</tr>
|
|
165
|
+
</thead>
|
|
166
|
+
<tbody class="divide-y divide-gray-200">
|
|
167
|
+
<tr v-for="it in entries" :key="it.hash" class="hover:bg-gray-50">
|
|
168
|
+
<td class="px-3 py-2">
|
|
169
|
+
<input type="checkbox" :value="it.hash" v-model="selectedHashes" />
|
|
170
|
+
</td>
|
|
171
|
+
<td class="px-3 py-2">
|
|
172
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
|
|
173
|
+
:class="it.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'">
|
|
174
|
+
{{ it.enabled ? 'enabled' : 'disabled' }}
|
|
175
|
+
</span>
|
|
176
|
+
</td>
|
|
177
|
+
<td class="px-3 py-2 text-xs font-mono">{{ it.method }}</td>
|
|
178
|
+
<td class="px-3 py-2 text-sm text-gray-900">{{ it.messageTemplate }}</td>
|
|
179
|
+
<td class="px-3 py-2 text-xs text-gray-600 font-mono">{{ it.topFrame }}</td>
|
|
180
|
+
<td class="px-3 py-2 text-xs text-gray-700">{{ formatDate(it.lastSeenAt) }}</td>
|
|
181
|
+
<td class="px-3 py-2 text-xs text-gray-700">{{ it.countTotal }}</td>
|
|
182
|
+
<td class="px-3 py-2">
|
|
183
|
+
<span v-for="tg in (it.tags || [])" :key="tg" class="inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-blue-50 text-blue-700 border border-blue-200 mr-1">
|
|
184
|
+
{{ tg }}
|
|
185
|
+
</span>
|
|
186
|
+
</td>
|
|
187
|
+
</tr>
|
|
188
|
+
<tr v-if="!entries.length">
|
|
189
|
+
<td colspan="8" class="px-4 py-6 text-sm text-gray-500">No entries found.</td>
|
|
190
|
+
</tr>
|
|
191
|
+
</tbody>
|
|
192
|
+
</table>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div class="flex items-center justify-between mt-4 text-sm text-gray-600">
|
|
196
|
+
<div>
|
|
197
|
+
Page {{ entriesPagination.page }} / {{ entriesPagination.totalPages }} ({{ entriesPagination.total }} total)
|
|
198
|
+
</div>
|
|
199
|
+
<div class="flex items-center gap-2">
|
|
200
|
+
<button @click="prevEntries" class="px-3 py-2 rounded border hover:bg-gray-50" :disabled="entriesPagination.page<=1">Prev</button>
|
|
201
|
+
<button @click="nextEntries" class="px-3 py-2 rounded border hover:bg-gray-50" :disabled="entriesPagination.page>=entriesPagination.totalPages">Next</button>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="p-4" v-if="activeTab==='logs'">
|
|
207
|
+
<div class="grid grid-cols-12 gap-4 mb-4">
|
|
208
|
+
<div class="col-span-4">
|
|
209
|
+
<label class="text-xs font-semibold text-gray-600">Search</label>
|
|
210
|
+
<input v-model="logsQuery.q" @keyup.enter="loadLogs" class="mt-1 w-full border rounded px-3 py-2" placeholder="message, argsPreview, entryHash" />
|
|
211
|
+
</div>
|
|
212
|
+
<div class="col-span-2">
|
|
213
|
+
<label class="text-xs font-semibold text-gray-600">Method</label>
|
|
214
|
+
<select v-model="logsQuery.method" class="mt-1 w-full border rounded px-3 py-2">
|
|
215
|
+
<option value="">(any)</option>
|
|
216
|
+
<option value="debug">debug</option>
|
|
217
|
+
<option value="log">log</option>
|
|
218
|
+
<option value="info">info</option>
|
|
219
|
+
<option value="warn">warn</option>
|
|
220
|
+
<option value="error">error</option>
|
|
221
|
+
</select>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="col-span-3">
|
|
224
|
+
<label class="text-xs font-semibold text-gray-600">Entry hash (optional)</label>
|
|
225
|
+
<input v-model="logsQuery.entryHash" class="mt-1 w-full border rounded px-3 py-2 font-mono text-xs" placeholder="hash" />
|
|
226
|
+
</div>
|
|
227
|
+
<div class="col-span-1">
|
|
228
|
+
<label class="text-xs font-semibold text-gray-600">Size</label>
|
|
229
|
+
<select v-model.number="logsQuery.pageSize" class="mt-1 w-full border rounded px-3 py-2">
|
|
230
|
+
<option :value="20">20</option>
|
|
231
|
+
<option :value="50">50</option>
|
|
232
|
+
<option :value="100">100</option>
|
|
233
|
+
<option :value="200">200</option>
|
|
234
|
+
</select>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="col-span-2 flex items-end">
|
|
237
|
+
<button @click="loadLogs" class="w-full px-3 py-2 rounded bg-gray-900 text-white text-sm hover:bg-black">Load</button>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="overflow-x-auto border border-gray-200 rounded">
|
|
242
|
+
<table class="w-full">
|
|
243
|
+
<thead class="bg-gray-50 border-b border-gray-200">
|
|
244
|
+
<tr>
|
|
245
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">At</th>
|
|
246
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Method</th>
|
|
247
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Entry</th>
|
|
248
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Message</th>
|
|
249
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Args</th>
|
|
250
|
+
</tr>
|
|
251
|
+
</thead>
|
|
252
|
+
<tbody class="divide-y divide-gray-200">
|
|
253
|
+
<tr v-for="it in logs" :key="it._id" class="hover:bg-gray-50">
|
|
254
|
+
<td class="px-3 py-2 text-xs text-gray-700">{{ formatDate(it.createdAt) }}</td>
|
|
255
|
+
<td class="px-3 py-2 text-xs font-mono">{{ it.method }}</td>
|
|
256
|
+
<td class="px-3 py-2 text-xs font-mono text-gray-600">{{ it.entryHash }}</td>
|
|
257
|
+
<td class="px-3 py-2 text-sm text-gray-900">{{ it.message }}</td>
|
|
258
|
+
<td class="px-3 py-2 text-xs text-gray-600 font-mono">{{ it.argsPreview }}</td>
|
|
259
|
+
</tr>
|
|
260
|
+
<tr v-if="!logs.length">
|
|
261
|
+
<td colspan="5" class="px-4 py-6 text-sm text-gray-500">No logs found (enable DB persistence in Config).</td>
|
|
262
|
+
</tr>
|
|
263
|
+
</tbody>
|
|
264
|
+
</table>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div class="flex items-center justify-between mt-4 text-sm text-gray-600">
|
|
268
|
+
<div>
|
|
269
|
+
Page {{ logsPagination.page }} / {{ logsPagination.totalPages }} ({{ logsPagination.total }} total)
|
|
270
|
+
</div>
|
|
271
|
+
<div class="flex items-center gap-2">
|
|
272
|
+
<button @click="prevLogs" class="px-3 py-2 rounded border hover:bg-gray-50" :disabled="logsPagination.page<=1">Prev</button>
|
|
273
|
+
<button @click="nextLogs" class="px-3 py-2 rounded border hover:bg-gray-50" :disabled="logsPagination.page>=logsPagination.totalPages">Next</button>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div class="p-4" v-if="activeTab==='config'">
|
|
279
|
+
<div class="grid grid-cols-12 gap-6">
|
|
280
|
+
<div class="col-span-6 space-y-4">
|
|
281
|
+
<div class="bg-white border border-gray-200 rounded p-4">
|
|
282
|
+
<div class="text-sm font-semibold text-gray-800 mb-3">General</div>
|
|
283
|
+
<div class="space-y-3">
|
|
284
|
+
<label class="flex items-center gap-2 text-sm">
|
|
285
|
+
<input type="checkbox" v-model="config.defaultEntryEnabled" />
|
|
286
|
+
New entries enabled by default
|
|
287
|
+
</label>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div class="bg-white border border-gray-200 rounded p-4">
|
|
292
|
+
<div class="text-sm font-semibold text-gray-800 mb-3">Persistence defaults</div>
|
|
293
|
+
<div class="space-y-3">
|
|
294
|
+
<label class="flex items-center gap-2 text-sm">
|
|
295
|
+
<input type="checkbox" v-model="config.defaults.persist.cache" />
|
|
296
|
+
Default persist to Cache
|
|
297
|
+
</label>
|
|
298
|
+
<label class="flex items-center gap-2 text-sm">
|
|
299
|
+
<input type="checkbox" v-model="config.defaults.persist.db" />
|
|
300
|
+
Default persist to DB
|
|
301
|
+
</label>
|
|
302
|
+
<label class="flex items-center gap-2 text-sm">
|
|
303
|
+
<input type="checkbox" v-model="config.defaults.persist.warnErrorToCacheDb" />
|
|
304
|
+
For warn/error, enable persistence defaults for new entries
|
|
305
|
+
</label>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<div class="bg-white border border-gray-200 rounded p-4">
|
|
310
|
+
<div class="text-sm font-semibold text-gray-800 mb-3">DB logs</div>
|
|
311
|
+
<div class="grid grid-cols-2 gap-4">
|
|
312
|
+
<label class="flex items-center gap-2 text-sm">
|
|
313
|
+
<input type="checkbox" v-model="config.db.enabled" />
|
|
314
|
+
Enable DB persistence
|
|
315
|
+
</label>
|
|
316
|
+
<div>
|
|
317
|
+
<label class="text-xs font-semibold text-gray-600">Retention (days)</label>
|
|
318
|
+
<input type="number" v-model.number="config.db.ttlDays" class="mt-1 w-full border rounded px-3 py-2" />
|
|
319
|
+
</div>
|
|
320
|
+
<div class="col-span-2">
|
|
321
|
+
<label class="text-xs font-semibold text-gray-600">Sample rate (%)</label>
|
|
322
|
+
<input type="number" v-model.number="config.db.sampleRatePercent" class="mt-1 w-full border rounded px-3 py-2" />
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<div class="col-span-6 space-y-4">
|
|
329
|
+
<div class="bg-white border border-gray-200 rounded p-4">
|
|
330
|
+
<div class="text-sm font-semibold text-gray-800 mb-3">Global Settings</div>
|
|
331
|
+
<div class="space-y-3">
|
|
332
|
+
<label class="flex items-center gap-2 text-sm">
|
|
333
|
+
<input type="checkbox" v-model="globalSettings.consoleManagerEnabled" @change="updateGlobalSetting" />
|
|
334
|
+
Enable Console Manager (requires restart)
|
|
335
|
+
</label>
|
|
336
|
+
<div class="text-xs text-gray-500">
|
|
337
|
+
Controls console manager initialization. When disabled, console methods are not overridden and no entries are tracked.
|
|
338
|
+
This setting uses the Global Settings system and requires a server restart to take effect.
|
|
339
|
+
</div>
|
|
340
|
+
<div v-if="globalSettingsStatus" class="text-xs p-2 rounded" :class="globalSettingsStatus.includes('Error') ? 'bg-red-100 text-red-700' : 'bg-blue-100 text-blue-700'">
|
|
341
|
+
{{ globalSettingsStatus }}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<div class="bg-white border border-gray-200 rounded p-4">
|
|
347
|
+
<div class="text-sm font-semibold text-gray-800 mb-3">Cache</div>
|
|
348
|
+
<div class="grid grid-cols-2 gap-4">
|
|
349
|
+
<label class="flex items-center gap-2 text-sm">
|
|
350
|
+
<input type="checkbox" v-model="config.cache.enabled" />
|
|
351
|
+
Enable cache counters
|
|
352
|
+
</label>
|
|
353
|
+
<div>
|
|
354
|
+
<label class="text-xs font-semibold text-gray-600">TTL seconds</label>
|
|
355
|
+
<input type="number" v-model.number="config.cache.ttlSeconds" class="mt-1 w-full border rounded px-3 py-2" />
|
|
356
|
+
</div>
|
|
357
|
+
<div class="col-span-2">
|
|
358
|
+
<label class="text-xs font-semibold text-gray-600">Namespace</label>
|
|
359
|
+
<input v-model="config.cache.namespace" class="mt-1 w-full border rounded px-3 py-2" />
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div class="bg-white border border-gray-200 rounded p-4">
|
|
365
|
+
<div class="text-sm font-semibold text-gray-800 mb-3">Performance</div>
|
|
366
|
+
<div class="grid grid-cols-2 gap-4">
|
|
367
|
+
<div>
|
|
368
|
+
<label class="text-xs font-semibold text-gray-600">Max arg chars</label>
|
|
369
|
+
<input type="number" v-model.number="config.performance.maxArgChars" class="mt-1 w-full border rounded px-3 py-2" />
|
|
370
|
+
</div>
|
|
371
|
+
<div>
|
|
372
|
+
<label class="text-xs font-semibold text-gray-600">Max args serialized</label>
|
|
373
|
+
<input type="number" v-model.number="config.performance.maxArgsSerialized" class="mt-1 w-full border rounded px-3 py-2" />
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="text-xs text-gray-500 mt-2">Config is stored using the JSON Configs system under the hood.</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<style>
|
|
386
|
+
[v-cloak] { display: none; }
|
|
387
|
+
</style>
|
|
388
|
+
|
|
389
|
+
<script>
|
|
390
|
+
window.BASE_URL = '<%= baseUrl %>';
|
|
391
|
+
|
|
392
|
+
const { createApp } = Vue;
|
|
393
|
+
|
|
394
|
+
createApp({
|
|
395
|
+
data() {
|
|
396
|
+
return {
|
|
397
|
+
activeTab: 'entries',
|
|
398
|
+
status: '',
|
|
399
|
+
|
|
400
|
+
config: {
|
|
401
|
+
defaultEntryEnabled: true,
|
|
402
|
+
defaults: { persist: { cache: false, db: false, warnErrorToCacheDb: false } },
|
|
403
|
+
db: { enabled: false, ttlDays: 7, sampleRatePercent: 100 },
|
|
404
|
+
cache: { enabled: false, ttlSeconds: 3600, namespace: 'console-manager' },
|
|
405
|
+
performance: { maxArgChars: 2000, maxArgsSerialized: 5 },
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
tags: [],
|
|
409
|
+
selectedTags: [],
|
|
410
|
+
|
|
411
|
+
entries: [],
|
|
412
|
+
entriesPagination: { page: 1, pageSize: 50, total: 0, totalPages: 1 },
|
|
413
|
+
entriesQuery: { q: '', method: '', enabled: '', page: 1, pageSize: 50 },
|
|
414
|
+
|
|
415
|
+
logs: [],
|
|
416
|
+
logsPagination: { page: 1, pageSize: 50, total: 0, totalPages: 1 },
|
|
417
|
+
logsQuery: { q: '', method: '', entryHash: '', page: 1, pageSize: 50 },
|
|
418
|
+
|
|
419
|
+
selectedHashes: [],
|
|
420
|
+
bulkTag: '',
|
|
421
|
+
showDeleteModal: false,
|
|
422
|
+
deleteLogsAlso: false,
|
|
423
|
+
isDeleting: false,
|
|
424
|
+
|
|
425
|
+
// Global settings
|
|
426
|
+
globalSettings: {
|
|
427
|
+
consoleManagerEnabled: true,
|
|
428
|
+
},
|
|
429
|
+
globalSettingsStatus: '',
|
|
430
|
+
};
|
|
431
|
+
},
|
|
432
|
+
computed: {
|
|
433
|
+
allSelected() {
|
|
434
|
+
return this.entries.length > 0 && this.selectedHashes.length === this.entries.length;
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
methods: {
|
|
438
|
+
tabClass(tab) {
|
|
439
|
+
return this.activeTab === tab
|
|
440
|
+
? 'bg-gray-900 text-white'
|
|
441
|
+
: 'bg-gray-100 text-gray-700 hover:bg-gray-200';
|
|
442
|
+
},
|
|
443
|
+
async api(path, opts) {
|
|
444
|
+
const base = window.BASE_URL || '';
|
|
445
|
+
const res = await fetch(base + path, {
|
|
446
|
+
credentials: 'same-origin',
|
|
447
|
+
headers: { 'Content-Type': 'application/json' },
|
|
448
|
+
...opts,
|
|
449
|
+
});
|
|
450
|
+
const json = await res.json().catch(() => ({}));
|
|
451
|
+
if (!res.ok) throw new Error(json.error || 'Request failed');
|
|
452
|
+
return json;
|
|
453
|
+
},
|
|
454
|
+
formatDate(d) {
|
|
455
|
+
if (!d) return '';
|
|
456
|
+
const dt = new Date(d);
|
|
457
|
+
if (Number.isNaN(dt.getTime())) return '';
|
|
458
|
+
return dt.toISOString().replace('T', ' ').slice(0, 19);
|
|
459
|
+
},
|
|
460
|
+
async loadConfig() {
|
|
461
|
+
const data = await this.api('/api/admin/console-manager/config');
|
|
462
|
+
if (data && data.config) {
|
|
463
|
+
this.config = data.config;
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
async saveConfig() {
|
|
467
|
+
this.status = 'Saving...';
|
|
468
|
+
try {
|
|
469
|
+
await this.api('/api/admin/console-manager/config', {
|
|
470
|
+
method: 'PUT',
|
|
471
|
+
body: JSON.stringify(this.config),
|
|
472
|
+
});
|
|
473
|
+
this.status = 'Saved config (retroactive defaults applied).';
|
|
474
|
+
} catch (e) {
|
|
475
|
+
this.status = e.message;
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
async loadTags() {
|
|
479
|
+
const data = await this.api('/api/admin/console-manager/tags');
|
|
480
|
+
this.tags = Array.isArray(data.items) ? data.items : [];
|
|
481
|
+
},
|
|
482
|
+
toggleTag(tag) {
|
|
483
|
+
const idx = this.selectedTags.indexOf(tag);
|
|
484
|
+
if (idx >= 0) this.selectedTags.splice(idx, 1);
|
|
485
|
+
else this.selectedTags.push(tag);
|
|
486
|
+
this.entriesQuery.page = 1;
|
|
487
|
+
this.loadEntries();
|
|
488
|
+
},
|
|
489
|
+
buildTagsQuery() {
|
|
490
|
+
return this.selectedTags.join(',');
|
|
491
|
+
},
|
|
492
|
+
async loadEntries() {
|
|
493
|
+
this.status = 'Loading entries...';
|
|
494
|
+
try {
|
|
495
|
+
const params = new URLSearchParams();
|
|
496
|
+
if (this.entriesQuery.q) params.set('q', this.entriesQuery.q);
|
|
497
|
+
if (this.entriesQuery.method) params.set('method', this.entriesQuery.method);
|
|
498
|
+
if (this.entriesQuery.enabled) params.set('enabled', this.entriesQuery.enabled);
|
|
499
|
+
params.set('page', String(this.entriesQuery.page || 1));
|
|
500
|
+
params.set('pageSize', String(this.entriesQuery.pageSize || 50));
|
|
501
|
+
const tagQ = this.buildTagsQuery();
|
|
502
|
+
if (tagQ) params.set('tags', tagQ);
|
|
503
|
+
|
|
504
|
+
const data = await this.api('/api/admin/console-manager/entries?' + params.toString());
|
|
505
|
+
this.entries = Array.isArray(data.items) ? data.items : [];
|
|
506
|
+
this.entriesPagination = data.pagination || this.entriesPagination;
|
|
507
|
+
this.selectedHashes = [];
|
|
508
|
+
this.status = '';
|
|
509
|
+
} catch (e) {
|
|
510
|
+
this.status = e.message;
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
prevEntries() {
|
|
514
|
+
if (this.entriesPagination.page <= 1) return;
|
|
515
|
+
this.entriesQuery.page = this.entriesPagination.page - 1;
|
|
516
|
+
this.loadEntries();
|
|
517
|
+
},
|
|
518
|
+
nextEntries() {
|
|
519
|
+
if (this.entriesPagination.page >= this.entriesPagination.totalPages) return;
|
|
520
|
+
this.entriesQuery.page = this.entriesPagination.page + 1;
|
|
521
|
+
this.loadEntries();
|
|
522
|
+
},
|
|
523
|
+
toggleSelectAll(e) {
|
|
524
|
+
const checked = Boolean(e?.target?.checked);
|
|
525
|
+
if (!checked) {
|
|
526
|
+
this.selectedHashes = [];
|
|
527
|
+
} else {
|
|
528
|
+
this.selectedHashes = this.entries.map((it) => it.hash);
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
async bulkEnable(enabled) {
|
|
532
|
+
if (!this.selectedHashes.length) return;
|
|
533
|
+
this.status = enabled ? 'Enabling...' : 'Disabling...';
|
|
534
|
+
try {
|
|
535
|
+
await this.api('/api/admin/console-manager/entries/bulk-enable', {
|
|
536
|
+
method: 'PUT',
|
|
537
|
+
body: JSON.stringify({ hashes: this.selectedHashes, enabled: Boolean(enabled) }),
|
|
538
|
+
});
|
|
539
|
+
await this.loadEntries();
|
|
540
|
+
this.status = '';
|
|
541
|
+
} catch (e) {
|
|
542
|
+
this.status = e.message;
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
async bulkAddTag() {
|
|
546
|
+
const tag = String(this.bulkTag || '').trim();
|
|
547
|
+
if (!tag || !this.selectedHashes.length) return;
|
|
548
|
+
this.status = 'Updating tags...';
|
|
549
|
+
try {
|
|
550
|
+
await this.api('/api/admin/console-manager/entries/bulk-tags', {
|
|
551
|
+
method: 'PUT',
|
|
552
|
+
body: JSON.stringify({ hashes: this.selectedHashes, add: [tag] }),
|
|
553
|
+
});
|
|
554
|
+
this.bulkTag = '';
|
|
555
|
+
await this.loadTags();
|
|
556
|
+
await this.loadEntries();
|
|
557
|
+
this.status = '';
|
|
558
|
+
} catch (e) {
|
|
559
|
+
this.status = e.message;
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
async bulkRemoveTag() {
|
|
563
|
+
const tag = String(this.bulkTag || '').trim();
|
|
564
|
+
if (!tag || !this.selectedHashes.length) return;
|
|
565
|
+
this.status = 'Updating tags...';
|
|
566
|
+
try {
|
|
567
|
+
await this.api('/api/admin/console-manager/entries/bulk-tags', {
|
|
568
|
+
method: 'PUT',
|
|
569
|
+
body: JSON.stringify({ hashes: this.selectedHashes, remove: [tag] }),
|
|
570
|
+
});
|
|
571
|
+
this.bulkTag = '';
|
|
572
|
+
await this.loadTags();
|
|
573
|
+
await this.loadEntries();
|
|
574
|
+
this.status = '';
|
|
575
|
+
} catch (e) {
|
|
576
|
+
this.status = e.message;
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
confirmBulkDelete() {
|
|
580
|
+
if (!this.selectedHashes.length) return;
|
|
581
|
+
this.showDeleteModal = true;
|
|
582
|
+
this.deleteLogsAlso = false;
|
|
583
|
+
},
|
|
584
|
+
async bulkDelete() {
|
|
585
|
+
if (!this.selectedHashes.length) return;
|
|
586
|
+
this.isDeleting = true;
|
|
587
|
+
this.status = 'Deleting...';
|
|
588
|
+
try {
|
|
589
|
+
const data = await this.api('/api/admin/console-manager/entries/bulk-delete', {
|
|
590
|
+
method: 'DELETE',
|
|
591
|
+
body: JSON.stringify({
|
|
592
|
+
hashes: this.selectedHashes,
|
|
593
|
+
deleteLogs: this.deleteLogsAlso
|
|
594
|
+
}),
|
|
595
|
+
});
|
|
596
|
+
this.showDeleteModal = false;
|
|
597
|
+
this.selectedHashes = [];
|
|
598
|
+
await this.loadTags();
|
|
599
|
+
await this.loadEntries();
|
|
600
|
+
this.status = `Deleted ${data.deletedEntries} entr${data.deletedEntries === 1 ? 'y' : 'ies'}${data.deletedLogs ? ` and ${data.deletedLogs} log${data.deletedLogs === 1 ? '' : 's'}` : ''}.`;
|
|
601
|
+
} catch (e) {
|
|
602
|
+
this.status = e.message;
|
|
603
|
+
} finally {
|
|
604
|
+
this.isDeleting = false;
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
async loadLogs() {
|
|
608
|
+
this.status = 'Loading logs...';
|
|
609
|
+
try {
|
|
610
|
+
const params = new URLSearchParams();
|
|
611
|
+
if (this.logsQuery.q) params.set('q', this.logsQuery.q);
|
|
612
|
+
if (this.logsQuery.method) params.set('method', this.logsQuery.method);
|
|
613
|
+
if (this.logsQuery.entryHash) params.set('entryHash', this.logsQuery.entryHash);
|
|
614
|
+
params.set('page', String(this.logsQuery.page || 1));
|
|
615
|
+
params.set('pageSize', String(this.logsQuery.pageSize || 50));
|
|
616
|
+
|
|
617
|
+
const data = await this.api('/api/admin/console-manager/logs?' + params.toString());
|
|
618
|
+
this.logs = Array.isArray(data.items) ? data.items : [];
|
|
619
|
+
this.logsPagination = data.pagination || this.logsPagination;
|
|
620
|
+
this.status = '';
|
|
621
|
+
} catch (e) {
|
|
622
|
+
this.status = e.message;
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
prevLogs() {
|
|
626
|
+
if (this.logsPagination.page <= 1) return;
|
|
627
|
+
this.logsQuery.page = this.logsPagination.page - 1;
|
|
628
|
+
this.loadLogs();
|
|
629
|
+
},
|
|
630
|
+
nextLogs() {
|
|
631
|
+
if (this.logsPagination.page >= this.logsPagination.totalPages) return;
|
|
632
|
+
this.logsQuery.page = this.logsPagination.page + 1;
|
|
633
|
+
this.loadLogs();
|
|
634
|
+
},
|
|
635
|
+
async refreshAll() {
|
|
636
|
+
this.status = 'Refreshing...';
|
|
637
|
+
try {
|
|
638
|
+
await Promise.all([
|
|
639
|
+
this.loadConfig(),
|
|
640
|
+
this.loadTags(),
|
|
641
|
+
this.loadEntries(),
|
|
642
|
+
]);
|
|
643
|
+
this.status = '';
|
|
644
|
+
} catch (e) {
|
|
645
|
+
this.status = e.message;
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
async loadGlobalSettings() {
|
|
649
|
+
try {
|
|
650
|
+
const data = await this.api('/api/admin/console-manager/global-setting');
|
|
651
|
+
this.globalSettings.consoleManagerEnabled = data.enabled;
|
|
652
|
+
} catch (e) {
|
|
653
|
+
console.error('Failed to load global settings:', e);
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
async updateGlobalSetting() {
|
|
657
|
+
this.globalSettingsStatus = 'Updating...';
|
|
658
|
+
try {
|
|
659
|
+
const data = await this.api('/api/admin/console-manager/global-setting', {
|
|
660
|
+
method: 'PUT',
|
|
661
|
+
body: JSON.stringify({ enabled: this.globalSettings.consoleManagerEnabled }),
|
|
662
|
+
});
|
|
663
|
+
this.globalSettingsStatus = data.message || 'Updated. Restart required.';
|
|
664
|
+
setTimeout(() => { this.globalSettingsStatus = ''; }, 5000);
|
|
665
|
+
} catch (e) {
|
|
666
|
+
this.globalSettingsStatus = e.message;
|
|
667
|
+
setTimeout(() => { this.globalSettingsStatus = ''; }, 5000);
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
async mounted() {
|
|
672
|
+
await Promise.all([
|
|
673
|
+
this.refreshAll(),
|
|
674
|
+
this.loadGlobalSettings(),
|
|
675
|
+
]);
|
|
676
|
+
}
|
|
677
|
+
}).mount('#app');
|
|
678
|
+
</script>
|
|
679
|
+
</body>
|
|
680
|
+
</html>
|