@necrolab/dashboard 0.5.15 → 0.5.17
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 +70 -566
- 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 +61 -74
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +13 -21
- 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 +295 -0
- package/src/assets/css/main.scss +55 -139
- package/src/components/Auth/LoginForm.vue +7 -86
- package/src/components/Console/ConsoleToolbar.vue +123 -0
- package/src/components/Editors/Account/Account.vue +12 -12
- package/src/components/Editors/Account/AccountView.vue +38 -111
- package/src/components/Editors/Account/CreateAccount.vue +11 -61
- package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
- package/src/components/Editors/AdminFileEditor.vue +179 -0
- package/src/components/Editors/Profile/CreateProfile.vue +77 -150
- package/src/components/Editors/Profile/Profile.vue +20 -21
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +41 -116
- package/src/components/Editors/ProxyFileEditor.vue +86 -0
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +66 -79
- package/src/components/Filter/FilterPreview.vue +153 -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 +20 -39
- package/src/components/Tasks/Task.vue +64 -270
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +45 -64
- 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/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -5
- 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 +49 -49
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +161 -54
- package/src/components/ui/Navbar.vue +63 -44
- 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 +29 -66
- 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 +72 -120
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useCodeEditor.js +117 -0
- 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 +1 -4
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useEnableDisable.js +6 -0
- package/src/composables/useFilterCSS.js +71 -0
- package/src/composables/useFormValidation.js +92 -0
- package/src/composables/useGetAllTags.js +9 -0
- package/src/composables/useIOSViewportHandling.js +76 -0
- package/src/composables/useNotchHandling.js +306 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTableRender.js +23 -0
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/composables/useZoomPrevention.js +96 -0
- package/src/constants/tableLayout.js +14 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +58 -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 +3 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +64 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -29
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +80 -71
- package/src/utils/tableHelpers.js +1 -0
- package/src/views/Accounts.vue +19 -38
- package/src/views/Console.vue +74 -253
- package/src/views/Editor.vue +47 -1114
- package/src/views/FilterBuilder.vue +190 -461
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +17 -32
- 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 -2438
- package/exit +0 -209
- package/run +0 -177
- package/src/assets/css/base/color-fallbacks.scss +0 -10
- package/src/assets/img/background.svg.backup +0 -11
- package/src/components/icons/SquareCheck.vue +0 -18
- package/src/components/icons/SquareUncheck.vue +0 -18
- package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
- 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 />
|
|
@@ -8,104 +8,29 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Object.entries(taskLogMapping)
|
|
23
|
-
.map(([k, v]) => `${k} (${v.length})`)
|
|
24
|
-
.sort((a, b) => a.localeCompare(b))
|
|
25
|
-
" />
|
|
26
|
-
</div>
|
|
27
|
-
<div class="flex flex-1 items-center gap-2">
|
|
28
|
-
<div class="input-default flex flex-1 items-center md:max-w-64">
|
|
29
|
-
<input
|
|
30
|
-
v-model="searchQuery"
|
|
31
|
-
type="text"
|
|
32
|
-
placeholder="Search logs..."
|
|
33
|
-
class="h-full w-full bg-transparent text-sm text-white outline-none" />
|
|
34
|
-
<span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
|
|
35
|
-
</div>
|
|
36
|
-
<!-- Scroll buttons on mobile - inline with search -->
|
|
37
|
-
<button
|
|
38
|
-
class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
|
|
39
|
-
@mousedown="startScrolling('up')"
|
|
40
|
-
@mouseup="stopScrolling"
|
|
41
|
-
@mouseleave="stopScrolling"
|
|
42
|
-
@touchstart="startScrolling('up')"
|
|
43
|
-
@touchend="stopScrolling">
|
|
44
|
-
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
45
|
-
</button>
|
|
46
|
-
<button
|
|
47
|
-
class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
|
|
48
|
-
@mousedown="startScrolling('down')"
|
|
49
|
-
@mouseup="stopScrolling"
|
|
50
|
-
@mouseleave="stopScrolling"
|
|
51
|
-
@touchstart="startScrolling('down')"
|
|
52
|
-
@touchend="stopScrolling">
|
|
53
|
-
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
54
|
-
</button>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
<div class="flex hidden items-center gap-3 md:flex">
|
|
58
|
-
<!-- Hide Monitors and Auto buttons only on desktop -->
|
|
59
|
-
<div class="hidden items-center gap-3 md:flex">
|
|
60
|
-
<button
|
|
61
|
-
class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
62
|
-
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
63
|
-
<Switch class="scale-75" v-model="filteredLogs" />
|
|
64
|
-
</button>
|
|
65
|
-
<button
|
|
66
|
-
class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
67
|
-
<h3 class="text-sm text-white">Auto</h3>
|
|
68
|
-
<Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
|
|
69
|
-
<div
|
|
70
|
-
v-if="userScrolledUp && autoscrollToggled"
|
|
71
|
-
class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
|
|
72
|
-
title="Autoscroll paused - scroll to bottom to resume"></div>
|
|
73
|
-
</button>
|
|
74
|
-
</div>
|
|
75
|
-
<!-- Scroll buttons - desktop only (mobile has them inline with search) -->
|
|
76
|
-
<button
|
|
77
|
-
class="hidden h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
|
|
78
|
-
@mousedown="startScrolling('up')"
|
|
79
|
-
@mouseup="stopScrolling"
|
|
80
|
-
@mouseleave="stopScrolling"
|
|
81
|
-
@touchstart="startScrolling('up')"
|
|
82
|
-
@touchend="stopScrolling">
|
|
83
|
-
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
84
|
-
</button>
|
|
85
|
-
<button
|
|
86
|
-
class="hidden h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
|
|
87
|
-
@mousedown="startScrolling('down')"
|
|
88
|
-
@mouseup="stopScrolling"
|
|
89
|
-
@mouseleave="stopScrolling"
|
|
90
|
-
@touchstart="startScrolling('down')"
|
|
91
|
-
@touchend="stopScrolling">
|
|
92
|
-
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
93
|
-
</button>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
11
|
+
<ConsoleToolbar
|
|
12
|
+
v-model:currentTaskLog="currentTaskLog"
|
|
13
|
+
v-model:searchQuery="searchQuery"
|
|
14
|
+
v-model:filteredLogs="filteredLogs"
|
|
15
|
+
v-model:autoscrollToggled="autoscrollToggled"
|
|
16
|
+
:taskLogMapping="taskLogMapping"
|
|
17
|
+
:userScrolledUp="userScrolledUp"
|
|
18
|
+
:filteredCount="filteredCount"
|
|
19
|
+
@scroll="handleScrollDirection"
|
|
20
|
+
@scroll-stop="stopScrolling"
|
|
21
|
+
@autoscroll-toggle="onAutoscrollToggle" />
|
|
96
22
|
|
|
97
23
|
<Smoothie
|
|
98
24
|
:weight="0.2"
|
|
99
|
-
class="console
|
|
100
|
-
style="min-height: 12rem !important"
|
|
25
|
+
class="console-main"
|
|
101
26
|
ref="$autoscroll"
|
|
102
27
|
@wheel.stop
|
|
103
28
|
@touchmove.stop
|
|
104
29
|
@scroll="handleScroll">
|
|
105
30
|
<div
|
|
106
31
|
v-if="displayedLogs.length === 0"
|
|
107
|
-
class="
|
|
108
|
-
<ConsoleIcon class="
|
|
32
|
+
class="flex h-full min-h-56 flex-col items-center justify-center text-center font-sans">
|
|
33
|
+
<ConsoleIcon class="empty-state-icon" />
|
|
109
34
|
<p class="text-sm text-light-400">
|
|
110
35
|
{{ searchQuery ? "No logs match your search" : "No logs yet" }}
|
|
111
36
|
</p>
|
|
@@ -115,12 +40,12 @@
|
|
|
115
40
|
</div>
|
|
116
41
|
<pre
|
|
117
42
|
v-else
|
|
118
|
-
class="hidden-scrollbars log-entry"
|
|
43
|
+
class="hidden-scrollbars log-entry opacity-0 transition-standard hover:bg-white/2"
|
|
119
44
|
v-for="(line, index) in displayedLogs"
|
|
120
45
|
v-bind:key="`log-${index}`"
|
|
121
|
-
:style="{ '--index': index }"><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
|
|
46
|
+
: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
47
|
</Smoothie>
|
|
123
|
-
<div class="
|
|
48
|
+
<div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
|
|
124
49
|
<button
|
|
125
50
|
class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
126
51
|
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
@@ -140,156 +65,57 @@
|
|
|
140
65
|
</div>
|
|
141
66
|
</template>
|
|
142
67
|
<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
|
-
|
|
68
|
+
/* Webkit scrollbar customization (cannot be done with Tailwind utilities) */
|
|
157
69
|
.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
70
|
scrollbar-width: thin;
|
|
162
|
-
scrollbar-color: oklch(0.35 0 0) oklch(0.
|
|
71
|
+
scrollbar-color: oklch(0.35 0 0) oklch(0.1822 0 0);
|
|
163
72
|
-webkit-overflow-scrolling: touch;
|
|
164
73
|
|
|
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
74
|
&::-webkit-scrollbar {
|
|
185
|
-
|
|
75
|
+
@apply w-2;
|
|
186
76
|
}
|
|
187
77
|
|
|
188
78
|
&::-webkit-scrollbar-track {
|
|
189
|
-
|
|
190
|
-
border-radius: 4px;
|
|
79
|
+
@apply rounded bg-dark-300;
|
|
191
80
|
}
|
|
192
81
|
|
|
193
82
|
&::-webkit-scrollbar-thumb {
|
|
83
|
+
@apply rounded transition-colors duration-200;
|
|
194
84
|
background: oklch(0.35 0 0);
|
|
195
|
-
border-radius: 4px;
|
|
196
|
-
transition: background-color 0.2s ease;
|
|
197
85
|
}
|
|
198
86
|
|
|
199
87
|
&::-webkit-scrollbar-thumb:hover {
|
|
200
88
|
background: oklch(0.45 0 0);
|
|
201
89
|
}
|
|
202
90
|
|
|
203
|
-
// Smooth scrolling behavior with momentum
|
|
204
91
|
&.smooth-scroll {
|
|
205
|
-
scroll-
|
|
92
|
+
@apply scroll-smooth;
|
|
206
93
|
scroll-padding: 0.5rem;
|
|
207
94
|
-webkit-overflow-scrolling: touch;
|
|
208
95
|
overscroll-behavior: contain;
|
|
209
96
|
}
|
|
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
97
|
}
|
|
255
98
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
99
|
+
/* Animation for log entries (keyframes cannot be done with Tailwind) */
|
|
100
|
+
.log-entry {
|
|
101
|
+
transform: translateY(4px);
|
|
102
|
+
animation: slideInLog 0.2s ease-out forwards;
|
|
259
103
|
|
|
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;
|
|
104
|
+
&:hover {
|
|
105
|
+
transform: translateX(2px);
|
|
265
106
|
}
|
|
266
107
|
}
|
|
267
108
|
|
|
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
|
-
}
|
|
109
|
+
.log-entry:last-child {
|
|
110
|
+
animation-delay: 0.05s;
|
|
283
111
|
}
|
|
284
112
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
113
|
+
@keyframes slideInLog {
|
|
114
|
+
to {
|
|
115
|
+
@apply opacity-100;
|
|
116
|
+
transform: translateY(0);
|
|
289
117
|
}
|
|
290
118
|
}
|
|
291
|
-
|
|
292
|
-
/* Console-specific styles handled by utilities */
|
|
293
119
|
</style>
|
|
294
120
|
<script setup>
|
|
295
121
|
import { Smoothie } from "vue-smoothie";
|
|
@@ -301,11 +127,7 @@ import Switch from "@/components/ui/controls/atomic/Switch.vue";
|
|
|
301
127
|
import WebsocketHeartbeatJs from "websocket-heartbeat-js";
|
|
302
128
|
import { onMounted, onUnmounted, ref, nextTick, computed, watch } from "vue";
|
|
303
129
|
import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
|
|
304
|
-
import
|
|
305
|
-
|
|
306
|
-
import { useUIStore } from "@/stores/ui";
|
|
307
|
-
|
|
308
|
-
const ui = useUIStore();
|
|
130
|
+
import ConsoleToolbar from "@/components/Console/ConsoleToolbar.vue";
|
|
309
131
|
|
|
310
132
|
const $autoscroll = ref(null);
|
|
311
133
|
const logLines = ref([]);
|
|
@@ -315,12 +137,13 @@ const taskLogMapping = ref({});
|
|
|
315
137
|
const currentTaskLog = ref("");
|
|
316
138
|
const filteredLogs = ref(true);
|
|
317
139
|
const userScrolledUp = ref(false);
|
|
318
|
-
const lastScrollTime = ref(0);
|
|
319
140
|
const scrollInterval = ref(null);
|
|
320
141
|
const isScrolling = ref(false);
|
|
321
142
|
const searchQuery = ref("");
|
|
322
143
|
|
|
323
|
-
//
|
|
144
|
+
// Optimized: Cache stripped versions to avoid regex on every filter
|
|
145
|
+
const logPlainTextCache = new Map();
|
|
146
|
+
|
|
324
147
|
const displayedLogs = computed(() => {
|
|
325
148
|
let logs =
|
|
326
149
|
currentTaskLog.value && currentTaskLog.value !== ""
|
|
@@ -330,8 +153,12 @@ const displayedLogs = computed(() => {
|
|
|
330
153
|
if (searchQuery.value.trim()) {
|
|
331
154
|
const query = searchQuery.value.toLowerCase();
|
|
332
155
|
logs = logs.filter((log) => {
|
|
333
|
-
//
|
|
334
|
-
|
|
156
|
+
// Use cached plain text or compute and cache it
|
|
157
|
+
let plainText = logPlainTextCache.get(log);
|
|
158
|
+
if (plainText === undefined) {
|
|
159
|
+
plainText = log.replace(/<[^>]*>/g, "").toLowerCase();
|
|
160
|
+
logPlainTextCache.set(log, plainText);
|
|
161
|
+
}
|
|
335
162
|
return plainText.includes(query);
|
|
336
163
|
});
|
|
337
164
|
}
|
|
@@ -346,28 +173,27 @@ const filteredCount = computed(() => {
|
|
|
346
173
|
|
|
347
174
|
const path = "/api/updates?type=console";
|
|
348
175
|
const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
|
|
349
|
-
|
|
176
|
+
|
|
177
|
+
const SCROLL_THRESHOLD = 50;
|
|
178
|
+
const SCROLL_AMOUNT = 100;
|
|
179
|
+
|
|
350
180
|
const handleScroll = (event) => {
|
|
351
181
|
if (!autoscrollToggled.value) return;
|
|
352
182
|
|
|
353
183
|
const element = event.target;
|
|
354
184
|
if (!element) return;
|
|
355
185
|
|
|
356
|
-
|
|
357
|
-
const threshold = 50;
|
|
358
|
-
const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - threshold;
|
|
186
|
+
const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - SCROLL_THRESHOLD;
|
|
359
187
|
|
|
360
|
-
// Only update if there's an actual change
|
|
361
188
|
if (userScrolledUp.value === isNearBottom) {
|
|
362
189
|
userScrolledUp.value = !isNearBottom;
|
|
363
190
|
}
|
|
364
191
|
};
|
|
365
192
|
|
|
366
|
-
// Bulletproof scroll function with multiple fallbacks
|
|
367
193
|
const performScroll = (direction, smooth = true) => {
|
|
368
194
|
try {
|
|
369
195
|
if (!$autoscroll.value?.el) {
|
|
370
|
-
if (DEBUG)
|
|
196
|
+
if (DEBUG) return false;
|
|
371
197
|
return false;
|
|
372
198
|
}
|
|
373
199
|
|
|
@@ -391,7 +217,7 @@ const performScroll = (direction, smooth = true) => {
|
|
|
391
217
|
|
|
392
218
|
return true;
|
|
393
219
|
} catch (e) {
|
|
394
|
-
if (DEBUG)
|
|
220
|
+
if (DEBUG) return false;
|
|
395
221
|
return false;
|
|
396
222
|
}
|
|
397
223
|
};
|
|
@@ -401,64 +227,54 @@ const startScrolling = (direction) => {
|
|
|
401
227
|
if (isScrolling.value) return;
|
|
402
228
|
|
|
403
229
|
isScrolling.value = true;
|
|
404
|
-
|
|
405
|
-
// Immediate scroll
|
|
406
230
|
performScroll(direction, true);
|
|
407
231
|
|
|
408
|
-
//
|
|
409
|
-
|
|
232
|
+
// Optimized: Use requestAnimationFrame instead of setInterval for smoother scrolling
|
|
233
|
+
const continuousScroll = () => {
|
|
410
234
|
if (!isScrolling.value) return;
|
|
411
235
|
|
|
412
236
|
const element = $autoscroll.value?.el;
|
|
413
237
|
if (!element) return;
|
|
414
238
|
|
|
415
|
-
const scrollAmount = 100;
|
|
416
239
|
if (direction === "up") {
|
|
417
|
-
element.scrollTop = Math.max(0, element.scrollTop -
|
|
240
|
+
element.scrollTop = Math.max(0, element.scrollTop - SCROLL_AMOUNT);
|
|
418
241
|
} else {
|
|
419
|
-
element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop +
|
|
242
|
+
element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop + SCROLL_AMOUNT);
|
|
420
243
|
}
|
|
421
|
-
|
|
244
|
+
|
|
245
|
+
scrollInterval.value = requestAnimationFrame(continuousScroll);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
scrollInterval.value = requestAnimationFrame(continuousScroll);
|
|
422
249
|
};
|
|
423
250
|
|
|
424
251
|
const stopScrolling = () => {
|
|
425
252
|
isScrolling.value = false;
|
|
426
253
|
if (scrollInterval.value) {
|
|
427
|
-
|
|
254
|
+
cancelAnimationFrame(scrollInterval.value);
|
|
428
255
|
scrollInterval.value = null;
|
|
429
256
|
}
|
|
430
257
|
};
|
|
431
258
|
|
|
432
|
-
// Legacy function for compatibility
|
|
433
|
-
const scrollTo = (dir) => {
|
|
434
|
-
performScroll(dir, true);
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
// Simple autoscroll to bottom
|
|
438
259
|
const autoScrollToBottom = () => {
|
|
439
260
|
if (!$autoscroll.value?.el || !autoscrollToggled.value) return;
|
|
440
261
|
|
|
441
262
|
// Only scroll if user hasn't manually scrolled up
|
|
442
263
|
if (!userScrolledUp.value) {
|
|
443
264
|
const element = $autoscroll.value.el;
|
|
444
|
-
|
|
445
|
-
// Calculate the target scroll position to show the last log
|
|
446
265
|
const targetScrollTop = element.scrollHeight - element.clientHeight;
|
|
447
266
|
|
|
448
|
-
// Use smooth scrolling to ensure new logs are visible
|
|
449
267
|
if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
|
|
450
268
|
element.scrollTo({
|
|
451
269
|
top: targetScrollTop,
|
|
452
270
|
behavior: "smooth"
|
|
453
271
|
});
|
|
454
272
|
} else {
|
|
455
|
-
// For small differences or fallback, use instant scroll
|
|
456
273
|
element.scrollTop = targetScrollTop;
|
|
457
274
|
}
|
|
458
275
|
}
|
|
459
276
|
};
|
|
460
277
|
|
|
461
|
-
// Handle autoscroll toggle
|
|
462
278
|
const onAutoscrollToggle = () => {
|
|
463
279
|
if (autoscrollToggled.value) {
|
|
464
280
|
userScrolledUp.value = false;
|
|
@@ -470,6 +286,10 @@ const addAnsiToOutput = (a) => {
|
|
|
470
286
|
const html = ansii.toHtml(a?.log || a);
|
|
471
287
|
logLines.value.push(html);
|
|
472
288
|
|
|
289
|
+
// Optimized: Pre-cache the plain text version when adding logs
|
|
290
|
+
const plainText = html.replace(/<[^>]*>/g, "").toLowerCase();
|
|
291
|
+
logPlainTextCache.set(html, plainText);
|
|
292
|
+
|
|
473
293
|
// Auto scroll after adding new content with proper timing
|
|
474
294
|
nextTick().then(() => {
|
|
475
295
|
// Use a small delay to ensure DOM is fully updated
|
|
@@ -497,13 +317,17 @@ const handleWebsocketMessages = (msg) => {
|
|
|
497
317
|
const makeTaskLogMapping = (lines) => {
|
|
498
318
|
lines.forEach((l) => {
|
|
499
319
|
if (!l.metadata) {
|
|
500
|
-
if (DEBUG) console.log("Error getting metadata", l);
|
|
501
320
|
return;
|
|
502
321
|
}
|
|
503
322
|
const region = l.metadata.siteId?.split("_")?.[1];
|
|
504
323
|
const n = l.metadata.global ? "Global" : `${region}-${l.metadata.taskId}`;
|
|
505
324
|
if (!taskLogMapping.value[n]) taskLogMapping.value[n] = [];
|
|
506
|
-
|
|
325
|
+
const html = ansii.toHtml(l.log);
|
|
326
|
+
taskLogMapping.value[n].push(html);
|
|
327
|
+
|
|
328
|
+
// Optimized: Pre-cache plain text for task log mapping too
|
|
329
|
+
const plainText = html.replace(/<[^>]*>/g, "").toLowerCase();
|
|
330
|
+
logPlainTextCache.set(html, plainText);
|
|
507
331
|
});
|
|
508
332
|
};
|
|
509
333
|
|
|
@@ -525,7 +349,6 @@ window.startDebugConsoleMessages = () => {
|
|
|
525
349
|
|
|
526
350
|
if (DEBUG) window.startDebugConsoleMessages();
|
|
527
351
|
|
|
528
|
-
// Watch for log filter changes and reset scroll state
|
|
529
352
|
watch([currentTaskLog, filteredLogs], () => {
|
|
530
353
|
userScrolledUp.value = false;
|
|
531
354
|
nextTick().then(() => {
|
|
@@ -533,20 +356,18 @@ watch([currentTaskLog, filteredLogs], () => {
|
|
|
533
356
|
});
|
|
534
357
|
});
|
|
535
358
|
|
|
536
|
-
// Listen for messages
|
|
537
359
|
onMounted(() => {
|
|
538
360
|
const socket = new WebsocketHeartbeatJs({ url, pingMsg: "ping" });
|
|
539
361
|
|
|
540
362
|
socket.onmessage = (event) => {
|
|
541
363
|
const msg = JSON.parse(event.data);
|
|
542
|
-
|
|
364
|
+
;
|
|
543
365
|
msg.forEach((e) => {
|
|
544
366
|
handleWebsocketMessages(e);
|
|
545
367
|
});
|
|
546
368
|
};
|
|
547
369
|
});
|
|
548
370
|
|
|
549
|
-
// Cleanup on unmount
|
|
550
371
|
onUnmounted(() => {
|
|
551
372
|
stopScrolling();
|
|
552
373
|
});
|