@necrolab/dashboard 0.5.15 → 0.5.16
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/backend/api.js +2 -3
- package/eslint.config.js +46 -0
- package/index.html +2 -1
- package/package.json +5 -2
- package/src/App.vue +140 -170
- package/src/assets/css/base/mixins.scss +72 -0
- package/src/assets/css/base/reset.scss +0 -2
- package/src/assets/css/base/scroll.scss +43 -36
- package/src/assets/css/base/typography.scss +9 -10
- package/src/assets/css/base/variables.scss +43 -0
- package/src/assets/css/components/accessibility.scss +37 -0
- package/src/assets/css/components/buttons.scss +58 -15
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +12 -20
- package/src/assets/css/components/modals.scss +2 -2
- package/src/assets/css/components/search-groups.scss +28 -22
- package/src/assets/css/components/tables.scss +5 -7
- package/src/assets/css/components/toasts.scss +7 -7
- package/src/assets/css/components/utilities.scss +220 -0
- package/src/assets/css/main.scss +66 -77
- package/src/components/Auth/LoginForm.vue +5 -84
- package/src/components/Editors/Account/Account.vue +8 -10
- package/src/components/Editors/Account/AccountCreator.vue +28 -59
- package/src/components/Editors/Account/AccountView.vue +38 -86
- package/src/components/Editors/Account/CreateAccount.vue +8 -50
- package/src/components/Editors/Profile/CreateProfile.vue +74 -131
- package/src/components/Editors/Profile/Profile.vue +15 -17
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +46 -96
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +62 -75
- package/src/components/Filter/FilterPreview.vue +161 -135
- package/src/components/Filter/PriceSortToggle.vue +36 -43
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Table.vue +45 -51
- package/src/components/Tasks/CheckStock.vue +7 -16
- package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
- package/src/components/Tasks/Controls/MobileControls.vue +5 -20
- package/src/components/Tasks/CreateTaskAXS.vue +20 -118
- package/src/components/Tasks/CreateTaskTM.vue +33 -189
- package/src/components/Tasks/EventDetailRow.vue +21 -0
- package/src/components/Tasks/MassEdit.vue +6 -16
- package/src/components/Tasks/QuickSettings.vue +140 -216
- package/src/components/Tasks/ScrapeVenue.vue +4 -13
- package/src/components/Tasks/Stats.vue +19 -38
- package/src/components/Tasks/Task.vue +65 -268
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +43 -63
- package/src/components/Tasks/Utilities.vue +10 -44
- package/src/components/Tasks/ViewTask.vue +23 -107
- package/src/components/icons/Close.vue +2 -8
- package/src/components/icons/Gear.vue +8 -8
- package/src/components/icons/Hash.vue +5 -0
- package/src/components/icons/Key.vue +2 -8
- package/src/components/icons/Pencil.vue +2 -8
- package/src/components/icons/Profile.vue +2 -8
- package/src/components/icons/Sell.vue +2 -8
- package/src/components/icons/Spinner.vue +4 -7
- package/src/components/icons/SquareCheck.vue +2 -8
- package/src/components/icons/SquareUncheck.vue +2 -8
- package/src/components/icons/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -1
- package/src/components/ui/ActionButtonGroup.vue +113 -52
- package/src/components/ui/BalanceIndicator.vue +60 -0
- package/src/components/ui/EmptyState.vue +24 -0
- package/src/components/ui/EnableDisableToggle.vue +23 -0
- package/src/components/ui/FormField.vue +48 -48
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +89 -56
- package/src/components/ui/Navbar.vue +60 -41
- package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
- package/src/components/ui/ReconnectIndicator.vue +111 -124
- package/src/components/ui/SectionCard.vue +6 -14
- package/src/components/ui/Splash.vue +2 -10
- package/src/components/ui/StatusBadge.vue +26 -28
- package/src/components/ui/TaskToggle.vue +54 -0
- package/src/components/ui/controls/CountryChooser.vue +27 -64
- package/src/components/ui/controls/EyeToggle.vue +1 -1
- package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
- package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
- package/src/components/ui/controls/atomic/MultiDropdown.vue +71 -119
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useColorMapping.js +15 -0
- package/src/composables/useCopyToClipboard.js +1 -1
- package/src/composables/useDateFormatting.js +21 -0
- package/src/composables/useDeviceDetection.js +14 -0
- package/src/composables/useDropdownPosition.js +3 -4
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +60 -0
- package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
- package/src/libs/utils/eventUrl.js +40 -0
- package/src/libs/utils/string.js +28 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +88 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -4
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +59 -36
- package/src/views/Accounts.vue +13 -24
- package/src/views/Console.vue +70 -172
- package/src/views/Editor.vue +211 -379
- package/src/views/FilterBuilder.vue +188 -371
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +8 -15
- package/src/views/Tasks.vue +49 -36
- package/tailwind.config.js +82 -71
- package/workbox-config.cjs +47 -5
- package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
- package/exit +0 -209
- package/run +0 -177
- package/src/assets/css/base/color-fallbacks.scss +0 -10
- package/switch-branch.sh +0 -41
- /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
package/src/views/Console.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
2
|
+
<div class="mb-8 pb-16 md:mb-0 md:pb-0 mobile-portrait:mb-12 mobile-portrait:pb-24">
|
|
3
3
|
<div class="page-header" style="padding-bottom: 0.75rem;">
|
|
4
4
|
<div class="page-header-card">
|
|
5
5
|
<ConsoleIcon />
|
|
@@ -30,21 +30,23 @@
|
|
|
30
30
|
v-model="searchQuery"
|
|
31
31
|
type="text"
|
|
32
32
|
placeholder="Search logs..."
|
|
33
|
-
|
|
33
|
+
aria-label="Search logs"
|
|
34
|
+
class="transparent-input" />
|
|
34
35
|
<span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
|
|
35
36
|
</div>
|
|
36
37
|
<!-- Scroll buttons on mobile - inline with search -->
|
|
37
38
|
<button
|
|
38
|
-
class="console-scroll-btn
|
|
39
|
+
class="console-scroll-btn md:hidden"
|
|
39
40
|
@mousedown="startScrolling('up')"
|
|
40
41
|
@mouseup="stopScrolling"
|
|
41
42
|
@mouseleave="stopScrolling"
|
|
42
43
|
@touchstart="startScrolling('up')"
|
|
43
|
-
@touchend="stopScrolling"
|
|
44
|
+
@touchend="stopScrolling"
|
|
45
|
+
aria-label="Scroll up">
|
|
44
46
|
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
45
47
|
</button>
|
|
46
48
|
<button
|
|
47
|
-
class="console-scroll-btn
|
|
49
|
+
class="console-scroll-btn md:hidden"
|
|
48
50
|
@mousedown="startScrolling('down')"
|
|
49
51
|
@mouseup="stopScrolling"
|
|
50
52
|
@mouseleave="stopScrolling"
|
|
@@ -96,16 +98,15 @@
|
|
|
96
98
|
|
|
97
99
|
<Smoothie
|
|
98
100
|
:weight="0.2"
|
|
99
|
-
class="console
|
|
100
|
-
style="min-height: 12rem !important"
|
|
101
|
+
class="console-main"
|
|
101
102
|
ref="$autoscroll"
|
|
102
103
|
@wheel.stop
|
|
103
104
|
@touchmove.stop
|
|
104
105
|
@scroll="handleScroll">
|
|
105
106
|
<div
|
|
106
107
|
v-if="displayedLogs.length === 0"
|
|
107
|
-
class="
|
|
108
|
-
<ConsoleIcon class="
|
|
108
|
+
class="flex h-full min-h-56 flex-col items-center justify-center text-center font-sans">
|
|
109
|
+
<ConsoleIcon class="empty-state-icon" />
|
|
109
110
|
<p class="text-sm text-light-400">
|
|
110
111
|
{{ searchQuery ? "No logs match your search" : "No logs yet" }}
|
|
111
112
|
</p>
|
|
@@ -115,12 +116,12 @@
|
|
|
115
116
|
</div>
|
|
116
117
|
<pre
|
|
117
118
|
v-else
|
|
118
|
-
class="hidden-scrollbars log-entry"
|
|
119
|
+
class="hidden-scrollbars log-entry opacity-0 transition-all duration-150 hover:bg-white/2"
|
|
119
120
|
v-for="(line, index) in displayedLogs"
|
|
120
121
|
v-bind:key="`log-${index}`"
|
|
121
|
-
:style="{ '--index': index }"><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
|
|
122
|
+
:style="{ '--index': index }"><code class="md:text-sm lg:text-base mobile-portrait:text-xs+ mobile-portrait:leading-tight" v-html="line"></code></pre>
|
|
122
123
|
</Smoothie>
|
|
123
|
-
<div class="
|
|
124
|
+
<div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
|
|
124
125
|
<button
|
|
125
126
|
class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
126
127
|
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
@@ -140,156 +141,57 @@
|
|
|
140
141
|
</div>
|
|
141
142
|
</template>
|
|
142
143
|
<style lang="scss" scoped>
|
|
143
|
-
|
|
144
|
-
@apply pb-16 mb-8 md:pb-0 md:mb-0;
|
|
145
|
-
|
|
146
|
-
@media (max-width: 480px) and (orientation: portrait) {
|
|
147
|
-
@apply pb-24 mb-12;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.console-switches {
|
|
152
|
-
@media (max-width: 480px) and (orientation: portrait) {
|
|
153
|
-
@apply mt-6 mb-16;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
144
|
+
/* Webkit scrollbar customization (cannot be done with Tailwind utilities) */
|
|
157
145
|
.console {
|
|
158
|
-
@apply relative rounded border-2 border-dark-550 bg-dark-400 p-2 lg:p-5;
|
|
159
|
-
@apply touch-pan-x touch-pan-y;
|
|
160
|
-
height: calc(100vh - 18rem);
|
|
161
146
|
scrollbar-width: thin;
|
|
162
|
-
scrollbar-color: oklch(0.35 0 0) oklch(0.
|
|
147
|
+
scrollbar-color: oklch(0.35 0 0) oklch(0.1822 0 0);
|
|
163
148
|
-webkit-overflow-scrolling: touch;
|
|
164
149
|
|
|
165
|
-
// Use fixed height on mobile portrait to ensure switches are visible
|
|
166
|
-
@media (max-width: 768px) {
|
|
167
|
-
max-height: 60vh;
|
|
168
|
-
height: auto;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@media (max-width: 480px) and (orientation: portrait) {
|
|
172
|
-
max-height: 50vh;
|
|
173
|
-
height: auto;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
@media (min-width: 769px) and (max-width: 1023px) {
|
|
177
|
-
height: calc(100vh - 16rem);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
@media (min-width: 1024px) {
|
|
181
|
-
height: calc(100vh - 14rem);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
150
|
&::-webkit-scrollbar {
|
|
185
|
-
|
|
151
|
+
@apply w-2;
|
|
186
152
|
}
|
|
187
153
|
|
|
188
154
|
&::-webkit-scrollbar-track {
|
|
189
|
-
|
|
190
|
-
border-radius: 4px;
|
|
155
|
+
@apply rounded bg-dark-300;
|
|
191
156
|
}
|
|
192
157
|
|
|
193
158
|
&::-webkit-scrollbar-thumb {
|
|
159
|
+
@apply rounded transition-colors duration-200;
|
|
194
160
|
background: oklch(0.35 0 0);
|
|
195
|
-
border-radius: 4px;
|
|
196
|
-
transition: background-color 0.2s ease;
|
|
197
161
|
}
|
|
198
162
|
|
|
199
163
|
&::-webkit-scrollbar-thumb:hover {
|
|
200
164
|
background: oklch(0.45 0 0);
|
|
201
165
|
}
|
|
202
166
|
|
|
203
|
-
// Smooth scrolling behavior with momentum
|
|
204
167
|
&.smooth-scroll {
|
|
205
|
-
scroll-
|
|
168
|
+
@apply scroll-smooth;
|
|
206
169
|
scroll-padding: 0.5rem;
|
|
207
170
|
-webkit-overflow-scrolling: touch;
|
|
208
171
|
overscroll-behavior: contain;
|
|
209
172
|
}
|
|
210
|
-
|
|
211
|
-
// Improved log entry animations
|
|
212
|
-
.log-entry {
|
|
213
|
-
opacity: 0;
|
|
214
|
-
transform: translateY(4px);
|
|
215
|
-
animation: slideInLog 0.2s ease-out forwards;
|
|
216
|
-
transition: all 0.15s ease;
|
|
217
|
-
|
|
218
|
-
&:hover {
|
|
219
|
-
background-color: rgba(255, 255, 255, 0.02);
|
|
220
|
-
transform: translateX(2px);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Stagger animation for new logs
|
|
225
|
-
.log-entry:last-child {
|
|
226
|
-
animation-delay: 0.05s;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
@keyframes slideInLog {
|
|
230
|
-
to {
|
|
231
|
-
opacity: 1;
|
|
232
|
-
transform: translateY(0);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Empty state styling
|
|
237
|
-
.empty-state {
|
|
238
|
-
min-height: 14rem;
|
|
239
|
-
font-family:
|
|
240
|
-
"Inter",
|
|
241
|
-
-apple-system,
|
|
242
|
-
BlinkMacSystemFont,
|
|
243
|
-
"Segoe UI",
|
|
244
|
-
Helvetica,
|
|
245
|
-
Arial,
|
|
246
|
-
sans-serif;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
textarea {
|
|
250
|
-
background: transparent;
|
|
251
|
-
resize: none;
|
|
252
|
-
@apply w-full text-white focus:outline-none;
|
|
253
|
-
}
|
|
254
173
|
}
|
|
255
174
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
175
|
+
/* Animation for log entries (keyframes cannot be done with Tailwind) */
|
|
176
|
+
.log-entry {
|
|
177
|
+
transform: translateY(4px);
|
|
178
|
+
animation: slideInLog 0.2s ease-out forwards;
|
|
259
179
|
|
|
260
|
-
&:hover
|
|
261
|
-
|
|
262
|
-
border-color: oklch(0.72 0.15 145) !important;
|
|
263
|
-
outline: 1px solid oklch(0.72 0.15 145);
|
|
264
|
-
outline-offset: 0;
|
|
180
|
+
&:hover {
|
|
181
|
+
transform: translateX(2px);
|
|
265
182
|
}
|
|
266
183
|
}
|
|
267
184
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
.console {
|
|
271
|
-
height: calc(100vh - 19.5rem);
|
|
272
|
-
@apply p-1 text-xs;
|
|
273
|
-
overflow: auto;
|
|
274
|
-
|
|
275
|
-
pre {
|
|
276
|
-
line-height: 1.2;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
code {
|
|
280
|
-
font-size: 0.7rem !important;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
185
|
+
.log-entry:last-child {
|
|
186
|
+
animation-delay: 0.05s;
|
|
283
187
|
}
|
|
284
188
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
189
|
+
@keyframes slideInLog {
|
|
190
|
+
to {
|
|
191
|
+
@apply opacity-100;
|
|
192
|
+
transform: translateY(0);
|
|
289
193
|
}
|
|
290
194
|
}
|
|
291
|
-
|
|
292
|
-
/* Console-specific styles handled by utilities */
|
|
293
195
|
</style>
|
|
294
196
|
<script setup>
|
|
295
197
|
import { Smoothie } from "vue-smoothie";
|
|
@@ -301,11 +203,6 @@ import Switch from "@/components/ui/controls/atomic/Switch.vue";
|
|
|
301
203
|
import WebsocketHeartbeatJs from "websocket-heartbeat-js";
|
|
302
204
|
import { onMounted, onUnmounted, ref, nextTick, computed, watch } from "vue";
|
|
303
205
|
import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
|
|
304
|
-
import { sortAlphaNum } from "@/stores/utils";
|
|
305
|
-
|
|
306
|
-
import { useUIStore } from "@/stores/ui";
|
|
307
|
-
|
|
308
|
-
const ui = useUIStore();
|
|
309
206
|
|
|
310
207
|
const $autoscroll = ref(null);
|
|
311
208
|
const logLines = ref([]);
|
|
@@ -315,12 +212,13 @@ const taskLogMapping = ref({});
|
|
|
315
212
|
const currentTaskLog = ref("");
|
|
316
213
|
const filteredLogs = ref(true);
|
|
317
214
|
const userScrolledUp = ref(false);
|
|
318
|
-
const lastScrollTime = ref(0);
|
|
319
215
|
const scrollInterval = ref(null);
|
|
320
216
|
const isScrolling = ref(false);
|
|
321
217
|
const searchQuery = ref("");
|
|
322
218
|
|
|
323
|
-
//
|
|
219
|
+
// Optimized: Cache stripped versions to avoid regex on every filter
|
|
220
|
+
const logPlainTextCache = new Map();
|
|
221
|
+
|
|
324
222
|
const displayedLogs = computed(() => {
|
|
325
223
|
let logs =
|
|
326
224
|
currentTaskLog.value && currentTaskLog.value !== ""
|
|
@@ -330,8 +228,12 @@ const displayedLogs = computed(() => {
|
|
|
330
228
|
if (searchQuery.value.trim()) {
|
|
331
229
|
const query = searchQuery.value.toLowerCase();
|
|
332
230
|
logs = logs.filter((log) => {
|
|
333
|
-
//
|
|
334
|
-
|
|
231
|
+
// Use cached plain text or compute and cache it
|
|
232
|
+
let plainText = logPlainTextCache.get(log);
|
|
233
|
+
if (plainText === undefined) {
|
|
234
|
+
plainText = log.replace(/<[^>]*>/g, "").toLowerCase();
|
|
235
|
+
logPlainTextCache.set(log, plainText);
|
|
236
|
+
}
|
|
335
237
|
return plainText.includes(query);
|
|
336
238
|
});
|
|
337
239
|
}
|
|
@@ -346,28 +248,29 @@ const filteredCount = computed(() => {
|
|
|
346
248
|
|
|
347
249
|
const path = "/api/updates?type=console";
|
|
348
250
|
const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
|
|
349
|
-
|
|
251
|
+
|
|
252
|
+
// Scroll thresholds
|
|
253
|
+
const SCROLL_THRESHOLD = 50; // pixels from bottom to consider "near bottom"
|
|
254
|
+
const SCROLL_AMOUNT = 100; // pixels to scroll per interval
|
|
255
|
+
|
|
350
256
|
const handleScroll = (event) => {
|
|
351
257
|
if (!autoscrollToggled.value) return;
|
|
352
258
|
|
|
353
259
|
const element = event.target;
|
|
354
260
|
if (!element) return;
|
|
355
261
|
|
|
356
|
-
// Check if user is near bottom
|
|
357
|
-
const
|
|
358
|
-
const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - threshold;
|
|
262
|
+
// Check if user is near bottom
|
|
263
|
+
const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - SCROLL_THRESHOLD;
|
|
359
264
|
|
|
360
|
-
// Only update if there's an actual change
|
|
361
265
|
if (userScrolledUp.value === isNearBottom) {
|
|
362
266
|
userScrolledUp.value = !isNearBottom;
|
|
363
267
|
}
|
|
364
268
|
};
|
|
365
269
|
|
|
366
|
-
// Bulletproof scroll function with multiple fallbacks
|
|
367
270
|
const performScroll = (direction, smooth = true) => {
|
|
368
271
|
try {
|
|
369
272
|
if (!$autoscroll.value?.el) {
|
|
370
|
-
if (DEBUG)
|
|
273
|
+
if (DEBUG) return false;
|
|
371
274
|
return false;
|
|
372
275
|
}
|
|
373
276
|
|
|
@@ -391,7 +294,7 @@ const performScroll = (direction, smooth = true) => {
|
|
|
391
294
|
|
|
392
295
|
return true;
|
|
393
296
|
} catch (e) {
|
|
394
|
-
if (DEBUG)
|
|
297
|
+
if (DEBUG) return false;
|
|
395
298
|
return false;
|
|
396
299
|
}
|
|
397
300
|
};
|
|
@@ -401,64 +304,54 @@ const startScrolling = (direction) => {
|
|
|
401
304
|
if (isScrolling.value) return;
|
|
402
305
|
|
|
403
306
|
isScrolling.value = true;
|
|
404
|
-
|
|
405
|
-
// Immediate scroll
|
|
406
307
|
performScroll(direction, true);
|
|
407
308
|
|
|
408
|
-
//
|
|
409
|
-
|
|
309
|
+
// Optimized: Use requestAnimationFrame instead of setInterval for smoother scrolling
|
|
310
|
+
const continuousScroll = () => {
|
|
410
311
|
if (!isScrolling.value) return;
|
|
411
312
|
|
|
412
313
|
const element = $autoscroll.value?.el;
|
|
413
314
|
if (!element) return;
|
|
414
315
|
|
|
415
|
-
const scrollAmount = 100;
|
|
416
316
|
if (direction === "up") {
|
|
417
|
-
element.scrollTop = Math.max(0, element.scrollTop -
|
|
317
|
+
element.scrollTop = Math.max(0, element.scrollTop - SCROLL_AMOUNT);
|
|
418
318
|
} else {
|
|
419
|
-
element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop +
|
|
319
|
+
element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop + SCROLL_AMOUNT);
|
|
420
320
|
}
|
|
421
|
-
|
|
321
|
+
|
|
322
|
+
scrollInterval.value = requestAnimationFrame(continuousScroll);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
scrollInterval.value = requestAnimationFrame(continuousScroll);
|
|
422
326
|
};
|
|
423
327
|
|
|
424
328
|
const stopScrolling = () => {
|
|
425
329
|
isScrolling.value = false;
|
|
426
330
|
if (scrollInterval.value) {
|
|
427
|
-
|
|
331
|
+
cancelAnimationFrame(scrollInterval.value);
|
|
428
332
|
scrollInterval.value = null;
|
|
429
333
|
}
|
|
430
334
|
};
|
|
431
335
|
|
|
432
|
-
// Legacy function for compatibility
|
|
433
|
-
const scrollTo = (dir) => {
|
|
434
|
-
performScroll(dir, true);
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
// Simple autoscroll to bottom
|
|
438
336
|
const autoScrollToBottom = () => {
|
|
439
337
|
if (!$autoscroll.value?.el || !autoscrollToggled.value) return;
|
|
440
338
|
|
|
441
339
|
// Only scroll if user hasn't manually scrolled up
|
|
442
340
|
if (!userScrolledUp.value) {
|
|
443
341
|
const element = $autoscroll.value.el;
|
|
444
|
-
|
|
445
|
-
// Calculate the target scroll position to show the last log
|
|
446
342
|
const targetScrollTop = element.scrollHeight - element.clientHeight;
|
|
447
343
|
|
|
448
|
-
// Use smooth scrolling to ensure new logs are visible
|
|
449
344
|
if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
|
|
450
345
|
element.scrollTo({
|
|
451
346
|
top: targetScrollTop,
|
|
452
347
|
behavior: "smooth"
|
|
453
348
|
});
|
|
454
349
|
} else {
|
|
455
|
-
// For small differences or fallback, use instant scroll
|
|
456
350
|
element.scrollTop = targetScrollTop;
|
|
457
351
|
}
|
|
458
352
|
}
|
|
459
353
|
};
|
|
460
354
|
|
|
461
|
-
// Handle autoscroll toggle
|
|
462
355
|
const onAutoscrollToggle = () => {
|
|
463
356
|
if (autoscrollToggled.value) {
|
|
464
357
|
userScrolledUp.value = false;
|
|
@@ -470,6 +363,10 @@ const addAnsiToOutput = (a) => {
|
|
|
470
363
|
const html = ansii.toHtml(a?.log || a);
|
|
471
364
|
logLines.value.push(html);
|
|
472
365
|
|
|
366
|
+
// Optimized: Pre-cache the plain text version when adding logs
|
|
367
|
+
const plainText = html.replace(/<[^>]*>/g, "").toLowerCase();
|
|
368
|
+
logPlainTextCache.set(html, plainText);
|
|
369
|
+
|
|
473
370
|
// Auto scroll after adding new content with proper timing
|
|
474
371
|
nextTick().then(() => {
|
|
475
372
|
// Use a small delay to ensure DOM is fully updated
|
|
@@ -497,13 +394,17 @@ const handleWebsocketMessages = (msg) => {
|
|
|
497
394
|
const makeTaskLogMapping = (lines) => {
|
|
498
395
|
lines.forEach((l) => {
|
|
499
396
|
if (!l.metadata) {
|
|
500
|
-
if (DEBUG) console.log("Error getting metadata", l);
|
|
501
397
|
return;
|
|
502
398
|
}
|
|
503
399
|
const region = l.metadata.siteId?.split("_")?.[1];
|
|
504
400
|
const n = l.metadata.global ? "Global" : `${region}-${l.metadata.taskId}`;
|
|
505
401
|
if (!taskLogMapping.value[n]) taskLogMapping.value[n] = [];
|
|
506
|
-
|
|
402
|
+
const html = ansii.toHtml(l.log);
|
|
403
|
+
taskLogMapping.value[n].push(html);
|
|
404
|
+
|
|
405
|
+
// Optimized: Pre-cache plain text for task log mapping too
|
|
406
|
+
const plainText = html.replace(/<[^>]*>/g, "").toLowerCase();
|
|
407
|
+
logPlainTextCache.set(html, plainText);
|
|
507
408
|
});
|
|
508
409
|
};
|
|
509
410
|
|
|
@@ -525,7 +426,6 @@ window.startDebugConsoleMessages = () => {
|
|
|
525
426
|
|
|
526
427
|
if (DEBUG) window.startDebugConsoleMessages();
|
|
527
428
|
|
|
528
|
-
// Watch for log filter changes and reset scroll state
|
|
529
429
|
watch([currentTaskLog, filteredLogs], () => {
|
|
530
430
|
userScrolledUp.value = false;
|
|
531
431
|
nextTick().then(() => {
|
|
@@ -533,20 +433,18 @@ watch([currentTaskLog, filteredLogs], () => {
|
|
|
533
433
|
});
|
|
534
434
|
});
|
|
535
435
|
|
|
536
|
-
// Listen for messages
|
|
537
436
|
onMounted(() => {
|
|
538
437
|
const socket = new WebsocketHeartbeatJs({ url, pingMsg: "ping" });
|
|
539
438
|
|
|
540
439
|
socket.onmessage = (event) => {
|
|
541
440
|
const msg = JSON.parse(event.data);
|
|
542
|
-
|
|
441
|
+
;
|
|
543
442
|
msg.forEach((e) => {
|
|
544
443
|
handleWebsocketMessages(e);
|
|
545
444
|
});
|
|
546
445
|
};
|
|
547
446
|
});
|
|
548
447
|
|
|
549
|
-
// Cleanup on unmount
|
|
550
448
|
onUnmounted(() => {
|
|
551
449
|
stopScrolling();
|
|
552
450
|
});
|