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