@necrolab/dashboard 0.4.220 → 0.5.1
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/.prettierrc +27 -1
- package/.vscode/extensions.json +1 -1
- package/README.md +64 -2
- package/artwork/image.png +0 -0
- package/backend/api.js +26 -24
- package/backend/auth.js +2 -2
- package/backend/batching.js +1 -1
- package/backend/endpoints.js +8 -11
- package/backend/index.js +2 -2
- package/backend/mock-data.js +27 -36
- package/backend/mock-src/classes/logger.js +5 -7
- package/backend/mock-src/classes/utils.js +3 -2
- package/backend/mock-src/ticketmaster.js +4 -4
- package/backend/validator.js +2 -2
- package/config/configs.json +0 -1
- package/dev-server.js +134 -0
- package/exit +209 -0
- package/index.html +78 -8
- package/index.js +1 -1
- package/jsconfig.json +16 -0
- package/package.json +39 -25
- package/postcss.config.js +1 -1
- package/postinstall.js +124 -20
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/img/logo_trans.png +0 -0
- package/public/img/necro_logo.png +0 -0
- package/public/manifest.json +16 -10
- package/run +176 -9
- package/src/App.vue +498 -85
- package/src/assets/css/base/reset.scss +43 -0
- package/src/assets/css/base/scroll.scss +114 -0
- package/src/assets/css/base/typography.scss +37 -0
- package/src/assets/css/components/buttons.scss +216 -0
- package/src/assets/css/components/forms.scss +221 -0
- package/src/assets/css/components/modals.scss +13 -0
- package/src/assets/css/components/tables.scss +27 -0
- package/src/assets/css/components/toasts.scss +100 -0
- package/src/assets/css/main.scss +201 -122
- package/src/assets/img/background.svg +2 -2
- package/src/assets/img/background.svg.backup +11 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/components/Auth/LoginForm.vue +62 -11
- package/src/components/Editors/Account/Account.vue +116 -40
- package/src/components/Editors/Account/AccountCreator.vue +88 -39
- package/src/components/Editors/Account/AccountView.vue +102 -34
- package/src/components/Editors/Account/CreateAccount.vue +80 -32
- package/src/components/Editors/Profile/CreateProfile.vue +269 -83
- package/src/components/Editors/Profile/Profile.vue +132 -47
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
- package/src/components/Editors/Profile/ProfileView.vue +89 -32
- package/src/components/Editors/TagLabel.vue +67 -6
- package/src/components/Editors/TagToggle.vue +7 -2
- package/src/components/Filter/Filter.vue +288 -71
- package/src/components/Filter/FilterPreview.vue +202 -31
- package/src/components/Filter/PriceSortToggle.vue +76 -6
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Row.vue +1 -1
- package/src/components/Table/Table.vue +19 -2
- package/src/components/Tasks/CheckStock.vue +6 -8
- package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
- package/src/components/Tasks/Controls/MobileControls.vue +8 -45
- package/src/components/Tasks/CreateTaskAXS.vue +80 -72
- package/src/components/Tasks/CreateTaskTM.vue +95 -141
- package/src/components/Tasks/MassEdit.vue +4 -6
- package/src/components/Tasks/QuickSettings.vue +199 -30
- package/src/components/Tasks/ScrapeVenue.vue +5 -6
- package/src/components/Tasks/Stats.vue +50 -24
- package/src/components/Tasks/Task.vue +384 -179
- package/src/components/Tasks/TaskLabel.vue +2 -2
- package/src/components/Tasks/TaskView.vue +136 -48
- package/src/components/Tasks/Utilities.vue +25 -10
- package/src/components/Tasks/ViewTask.vue +321 -0
- package/src/components/icons/Bag.vue +1 -1
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Eye.vue +6 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Loyalty.vue +1 -1
- package/src/components/icons/Mail.vue +2 -2
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +2 -2
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +4 -5
- package/src/components/icons/Sandclock.vue +2 -2
- package/src/components/icons/Sell.vue +21 -0
- package/src/components/icons/Spinner.vue +42 -0
- package/src/components/icons/SquareCheck.vue +18 -0
- package/src/components/icons/SquareUncheck.vue +18 -0
- package/src/components/icons/Stadium.vue +1 -1
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +26 -1
- package/src/components/ui/Modal.vue +107 -13
- package/src/components/ui/Navbar.vue +175 -40
- package/src/components/ui/ReconnectIndicator.vue +351 -55
- package/src/components/ui/Splash.vue +5 -35
- package/src/components/ui/controls/CountryChooser.vue +200 -62
- package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
- package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
- package/src/components/ui/controls/atomic/Switch.vue +53 -25
- package/src/composables/useClickOutside.js +21 -0
- package/src/composables/useDropdownPosition.js +174 -0
- package/src/libs/Filter.js +60 -24
- package/src/registerServiceWorker.js +1 -1
- package/src/stores/connection.js +4 -4
- package/src/stores/sampleData.js +172 -199
- package/src/stores/ui.js +55 -20
- package/src/stores/utils.js +30 -4
- package/src/types/index.js +41 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +116 -50
- package/src/views/Console.vue +394 -79
- package/src/views/Editor.vue +1176 -123
- package/src/views/FilterBuilder.vue +528 -250
- package/src/views/Login.vue +76 -14
- package/src/views/Profiles.vue +119 -34
- package/src/views/Tasks.vue +266 -98
- package/static/offline.html +192 -50
- package/switch-branch.sh +41 -0
- package/tailwind.config.js +119 -27
- package/vite.config.js +73 -16
- package/workbox-config.cjs +63 -0
- package/ICONS.md +0 -21
- package/public/img/background.svg +0 -14
- package/public/img/logo.png +0 -0
- package/public/img/logo_icon.png +0 -0
- package/public/img/logo_icon_2.png +0 -0
- package/src/assets/css/_input.scss +0 -143
- package/src/assets/img/logo.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/assets/img/logo_icon_2.png +0 -0
- package/vue.config.js +0 -32
- package/workbox-config.js +0 -7
package/src/views/Console.vue
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<h4 class="
|
|
3
|
+
<h4 class="mb-2 flex items-center gap-2 pt-5 text-sm font-bold text-white lg:mt-1">
|
|
4
|
+
Console
|
|
5
|
+
<ConsoleIcon />
|
|
6
|
+
</h4>
|
|
4
7
|
|
|
5
8
|
<div>
|
|
6
|
-
<div class="flex items-center
|
|
7
|
-
<div class="flex
|
|
8
|
-
<div class="w-
|
|
9
|
+
<div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
|
10
|
+
<div class="flex flex-col gap-3 md:flex-row md:items-center md:flex-1">
|
|
11
|
+
<div class="w-full md:w-64">
|
|
9
12
|
<Dropdown
|
|
10
|
-
class="w-
|
|
13
|
+
class="console-dropdown input-default w-full border-2 border-dark-550 bg-dark-500"
|
|
11
14
|
rightAmount="right-2"
|
|
12
15
|
default="All logs"
|
|
13
16
|
:allowDefault="true"
|
|
@@ -17,62 +20,118 @@
|
|
|
17
20
|
Object.entries(taskLogMapping)
|
|
18
21
|
.map(([k, v]) => `${k} (${v.length})`)
|
|
19
22
|
.sort((a, b) => a.localeCompare(b))
|
|
20
|
-
"
|
|
21
|
-
|
|
23
|
+
" />
|
|
24
|
+
</div>
|
|
25
|
+
<div class="flex items-center gap-2 flex-1">
|
|
26
|
+
<div class="input-default flex items-center flex-1 md:max-w-64">
|
|
27
|
+
<input
|
|
28
|
+
v-model="searchQuery"
|
|
29
|
+
type="text"
|
|
30
|
+
placeholder="Search logs..."
|
|
31
|
+
class="h-full w-full bg-transparent text-sm text-white outline-none" />
|
|
32
|
+
<span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
|
|
33
|
+
</div>
|
|
34
|
+
<!-- Scroll buttons on mobile - inline with search -->
|
|
35
|
+
<button
|
|
36
|
+
class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
|
|
37
|
+
@mousedown="startScrolling('up')"
|
|
38
|
+
@mouseup="stopScrolling"
|
|
39
|
+
@mouseleave="stopScrolling"
|
|
40
|
+
@touchstart="startScrolling('up')"
|
|
41
|
+
@touchend="stopScrolling">
|
|
42
|
+
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
43
|
+
</button>
|
|
44
|
+
<button
|
|
45
|
+
class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
|
|
46
|
+
@mousedown="startScrolling('down')"
|
|
47
|
+
@mouseup="stopScrolling"
|
|
48
|
+
@mouseleave="stopScrolling"
|
|
49
|
+
@touchstart="startScrolling('down')"
|
|
50
|
+
@touchend="stopScrolling">
|
|
51
|
+
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
52
|
+
</button>
|
|
22
53
|
</div>
|
|
23
54
|
</div>
|
|
24
|
-
<div class="flex
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
55
|
+
<div class="flex items-center gap-3 hidden md:flex">
|
|
56
|
+
<!-- Hide Monitors and Auto buttons only on desktop -->
|
|
57
|
+
<div class="hidden items-center gap-3 md:flex">
|
|
58
|
+
<button
|
|
59
|
+
class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
60
|
+
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
61
|
+
<Switch class="scale-75" v-model="filteredLogs" />
|
|
62
|
+
</button>
|
|
63
|
+
<button
|
|
64
|
+
class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
65
|
+
<h3 class="text-sm text-white">Auto</h3>
|
|
66
|
+
<Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
|
|
67
|
+
<div
|
|
68
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
69
|
+
class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
|
|
70
|
+
title="Autoscroll paused - scroll to bottom to resume"></div>
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
<!-- Scroll buttons - desktop only (mobile has them inline with search) -->
|
|
37
74
|
<button
|
|
38
|
-
class="
|
|
39
|
-
|
|
40
|
-
|
|
75
|
+
class="hidden md:flex 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"
|
|
76
|
+
@mousedown="startScrolling('up')"
|
|
77
|
+
@mouseup="stopScrolling"
|
|
78
|
+
@mouseleave="stopScrolling"
|
|
79
|
+
@touchstart="startScrolling('up')"
|
|
80
|
+
@touchend="stopScrolling">
|
|
81
|
+
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
41
82
|
</button>
|
|
42
83
|
<button
|
|
43
|
-
class="
|
|
44
|
-
|
|
45
|
-
|
|
84
|
+
class="hidden md:flex 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"
|
|
85
|
+
@mousedown="startScrolling('down')"
|
|
86
|
+
@mouseup="stopScrolling"
|
|
87
|
+
@mouseleave="stopScrolling"
|
|
88
|
+
@touchstart="startScrolling('down')"
|
|
89
|
+
@touchend="stopScrolling">
|
|
90
|
+
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
46
91
|
</button>
|
|
47
92
|
</div>
|
|
48
93
|
</div>
|
|
49
94
|
|
|
50
95
|
<Smoothie
|
|
51
|
-
:weight="0.
|
|
52
|
-
class="
|
|
53
|
-
style="min-height:
|
|
96
|
+
:weight="0.2"
|
|
97
|
+
class="console scrollable smooth-scroll overflow-y-auto overflow-x-hidden font-mono text-white"
|
|
98
|
+
style="min-height: 12rem !important"
|
|
54
99
|
ref="$autoscroll"
|
|
55
|
-
|
|
100
|
+
@wheel.stop
|
|
101
|
+
@touchmove.stop
|
|
102
|
+
@scroll="handleScroll">
|
|
103
|
+
<div
|
|
104
|
+
v-if="displayedLogs.length === 0"
|
|
105
|
+
class="empty-state flex h-full flex-col items-center justify-center text-center">
|
|
106
|
+
<ConsoleIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
|
|
107
|
+
<p class="text-sm text-light-400">
|
|
108
|
+
{{ searchQuery ? "No logs match your search" : "No logs yet" }}
|
|
109
|
+
</p>
|
|
110
|
+
<p class="mt-1 text-xs text-light-500">
|
|
111
|
+
{{ searchQuery ? "Try a different search term" : "Logs will appear here in real time" }}
|
|
112
|
+
</p>
|
|
113
|
+
</div>
|
|
56
114
|
<pre
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
v-
|
|
62
|
-
><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
|
|
115
|
+
v-else
|
|
116
|
+
class="hidden-scrollbars log-entry"
|
|
117
|
+
v-for="(line, index) in displayedLogs"
|
|
118
|
+
v-bind:key="`log-${index}`"
|
|
119
|
+
:style="{ '--index': index }"><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
|
|
63
120
|
</Smoothie>
|
|
64
|
-
<div class="flex
|
|
121
|
+
<div class="mt-3 flex justify-between md:hidden">
|
|
65
122
|
<button
|
|
66
|
-
class="flex
|
|
67
|
-
>
|
|
123
|
+
class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
68
124
|
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
69
125
|
<Switch class="scale-75" v-model="filteredLogs" />
|
|
70
126
|
</button>
|
|
71
127
|
<button
|
|
72
|
-
class="flex
|
|
73
|
-
>
|
|
128
|
+
class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
74
129
|
<h3 class="text-sm text-white">Auto</h3>
|
|
75
|
-
<Switch class="scale-75" v-model="autoscrollToggled" />
|
|
130
|
+
<Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
|
|
131
|
+
<div
|
|
132
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
133
|
+
class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
|
|
134
|
+
title="Autoscroll paused - scroll to bottom to resume"></div>
|
|
76
135
|
</button>
|
|
77
136
|
</div>
|
|
78
137
|
</div>
|
|
@@ -80,23 +139,124 @@
|
|
|
80
139
|
</template>
|
|
81
140
|
<style lang="scss" scoped>
|
|
82
141
|
.console {
|
|
83
|
-
@apply
|
|
84
|
-
height: calc(100vh -
|
|
142
|
+
@apply relative rounded border-2 border-dark-550 bg-dark-400 p-2 lg:p-5;
|
|
143
|
+
height: calc(100vh - 18rem);
|
|
144
|
+
scrollbar-width: thin;
|
|
145
|
+
scrollbar-color: oklch(0.35 0 0) oklch(0.19 0 0);
|
|
146
|
+
|
|
147
|
+
@media (min-width: 768px) {
|
|
148
|
+
height: calc(100vh - 16rem);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (min-width: 1024px) {
|
|
152
|
+
height: calc(100vh - 14rem);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
&::-webkit-scrollbar {
|
|
156
|
+
width: 8px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
&::-webkit-scrollbar-track {
|
|
160
|
+
background: oklch(0.19 0 0);
|
|
161
|
+
border-radius: 4px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
&::-webkit-scrollbar-thumb {
|
|
165
|
+
background: oklch(0.35 0 0);
|
|
166
|
+
border-radius: 4px;
|
|
167
|
+
transition: background-color 0.2s ease;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
171
|
+
background: oklch(0.45 0 0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Smooth scrolling behavior with momentum
|
|
175
|
+
&.smooth-scroll {
|
|
176
|
+
scroll-behavior: smooth;
|
|
177
|
+
scroll-padding: 0.5rem;
|
|
178
|
+
-webkit-overflow-scrolling: touch;
|
|
179
|
+
overscroll-behavior: contain;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Improved log entry animations
|
|
183
|
+
.log-entry {
|
|
184
|
+
opacity: 0;
|
|
185
|
+
transform: translateY(4px);
|
|
186
|
+
animation: slideInLog 0.2s ease-out forwards;
|
|
187
|
+
transition: all 0.15s ease;
|
|
188
|
+
|
|
189
|
+
&:hover {
|
|
190
|
+
background-color: rgba(255, 255, 255, 0.02);
|
|
191
|
+
transform: translateX(2px);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Stagger animation for new logs
|
|
196
|
+
.log-entry:last-child {
|
|
197
|
+
animation-delay: 0.05s;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@keyframes slideInLog {
|
|
201
|
+
to {
|
|
202
|
+
opacity: 1;
|
|
203
|
+
transform: translateY(0);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Empty state styling
|
|
208
|
+
.empty-state {
|
|
209
|
+
min-height: 14rem;
|
|
210
|
+
font-family:
|
|
211
|
+
"Inter",
|
|
212
|
+
-apple-system,
|
|
213
|
+
BlinkMacSystemFont,
|
|
214
|
+
"Segoe UI",
|
|
215
|
+
Helvetica,
|
|
216
|
+
Arial,
|
|
217
|
+
sans-serif;
|
|
218
|
+
}
|
|
219
|
+
|
|
85
220
|
textarea {
|
|
86
221
|
background: transparent;
|
|
87
222
|
resize: none;
|
|
223
|
+
@apply w-full text-white focus:outline-none;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.console-scroll-btn {
|
|
228
|
+
border-color: oklch(0.2809 0 0);
|
|
229
|
+
transition: all 0.15s ease;
|
|
88
230
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
231
|
+
&:hover, &:active {
|
|
232
|
+
border-color: oklch(0.72 0.15 145) !important;
|
|
233
|
+
outline: 1px solid oklch(0.72 0.15 145);
|
|
234
|
+
outline-offset: 0;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Mobile portrait console optimizations */
|
|
239
|
+
@screen mobile-portrait {
|
|
240
|
+
.console {
|
|
241
|
+
height: calc(100vh - 19.5rem);
|
|
242
|
+
@apply p-1 text-xs;
|
|
243
|
+
overflow: auto;
|
|
244
|
+
|
|
245
|
+
pre {
|
|
246
|
+
line-height: 1.2;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
code {
|
|
250
|
+
font-size: 0.7rem !important;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Mobile landscape console optimizations */
|
|
256
|
+
@media (max-width: 1024px) and (orientation: landscape) {
|
|
257
|
+
.console {
|
|
258
|
+
height: calc(100vh - 10.5rem);
|
|
259
|
+
}
|
|
100
260
|
}
|
|
101
261
|
|
|
102
262
|
.text-xxs {
|
|
@@ -110,28 +270,22 @@
|
|
|
110
270
|
height: 30px;
|
|
111
271
|
}
|
|
112
272
|
}
|
|
113
|
-
|
|
114
|
-
max-height: calc(100vh - 12rem);
|
|
115
|
-
overflow: hidden;
|
|
116
|
-
}
|
|
273
|
+
/* Console-specific styles handled by utilities */
|
|
117
274
|
</style>
|
|
118
275
|
<script setup>
|
|
119
276
|
import { Smoothie } from "vue-smoothie";
|
|
120
|
-
|
|
121
|
-
const DEBUG = window.location.href.startsWith("http://localhost:5173");
|
|
277
|
+
import { DEBUG } from "@/utils/debug";
|
|
122
278
|
|
|
123
279
|
import Filter from "@/libs/ansii.js";
|
|
124
280
|
import { ConsoleIcon, DownIcon, UpIcon } from "@/components/icons";
|
|
125
281
|
import Switch from "@/components/ui/controls/atomic/Switch.vue";
|
|
126
282
|
import WebsocketHeartbeatJs from "websocket-heartbeat-js";
|
|
127
|
-
import { onMounted, ref, nextTick } from "vue";
|
|
283
|
+
import { onMounted, onUnmounted, ref, nextTick, computed, watch } from "vue";
|
|
128
284
|
import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
|
|
129
285
|
import { sortAlphaNum } from "@/stores/utils";
|
|
130
286
|
|
|
131
287
|
import { useUIStore } from "@/stores/ui";
|
|
132
288
|
|
|
133
|
-
const DEBUG = window.location.href.startsWith("http://localhost:5173");
|
|
134
|
-
|
|
135
289
|
const ui = useUIStore();
|
|
136
290
|
|
|
137
291
|
const $autoscroll = ref(null);
|
|
@@ -139,27 +293,169 @@ const logLines = ref([]);
|
|
|
139
293
|
const ansii = new Filter();
|
|
140
294
|
const autoscrollToggled = ref(true);
|
|
141
295
|
const taskLogMapping = ref({});
|
|
142
|
-
const currentTaskLog = ref(
|
|
296
|
+
const currentTaskLog = ref("");
|
|
143
297
|
const filteredLogs = ref(true);
|
|
298
|
+
const userScrolledUp = ref(false);
|
|
299
|
+
const lastScrollTime = ref(0);
|
|
300
|
+
const scrollInterval = ref(null);
|
|
301
|
+
const isScrolling = ref(false);
|
|
302
|
+
const searchQuery = ref("");
|
|
303
|
+
|
|
304
|
+
// Computed filtered logs based on search query
|
|
305
|
+
const displayedLogs = computed(() => {
|
|
306
|
+
let logs =
|
|
307
|
+
currentTaskLog.value && currentTaskLog.value !== ""
|
|
308
|
+
? taskLogMapping.value[currentTaskLog.value]
|
|
309
|
+
: logLines.value.filter((l) => (filteredLogs.value ? !["-DISCORD"].some((s) => l.includes(s)) : true));
|
|
310
|
+
|
|
311
|
+
if (searchQuery.value.trim()) {
|
|
312
|
+
const query = searchQuery.value.toLowerCase();
|
|
313
|
+
logs = logs.filter((log) => {
|
|
314
|
+
// Remove HTML tags for search
|
|
315
|
+
const plainText = log.replace(/<[^>]*>/g, "").toLowerCase();
|
|
316
|
+
return plainText.includes(query);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return logs;
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const filteredCount = computed(() => {
|
|
324
|
+
if (!searchQuery.value.trim()) return "";
|
|
325
|
+
return `${displayedLogs.value.length}`;
|
|
326
|
+
});
|
|
144
327
|
|
|
145
328
|
const path = "/api/updates?type=console";
|
|
146
329
|
const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
|
|
147
|
-
|
|
330
|
+
// Handle manual scroll detection
|
|
331
|
+
const handleScroll = (event) => {
|
|
332
|
+
if (!autoscrollToggled.value) return;
|
|
333
|
+
|
|
334
|
+
const element = event.target;
|
|
335
|
+
if (!element) return;
|
|
336
|
+
|
|
337
|
+
// Check if user is near bottom (within 50px)
|
|
338
|
+
const threshold = 50;
|
|
339
|
+
const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - threshold;
|
|
340
|
+
|
|
341
|
+
// Only update if there's an actual change
|
|
342
|
+
if (userScrolledUp.value === isNearBottom) {
|
|
343
|
+
userScrolledUp.value = !isNearBottom;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Bulletproof scroll function with multiple fallbacks
|
|
348
|
+
const performScroll = (direction, smooth = true) => {
|
|
148
349
|
try {
|
|
149
|
-
if (
|
|
150
|
-
|
|
350
|
+
if (!$autoscroll.value?.el) {
|
|
351
|
+
if (DEBUG) console.log("Autoscroll element not ready");
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const element = $autoscroll.value.el;
|
|
356
|
+
|
|
357
|
+
if (direction === "up") {
|
|
358
|
+
if (smooth && element.scrollTo) {
|
|
359
|
+
element.scrollTo({ top: 0, behavior: "smooth" });
|
|
360
|
+
} else {
|
|
361
|
+
element.scrollTop = 0;
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
const targetTop = element.scrollHeight - element.clientHeight;
|
|
365
|
+
if (smooth && element.scrollTo) {
|
|
366
|
+
element.scrollTo({ top: targetTop, behavior: "smooth" });
|
|
367
|
+
} else {
|
|
368
|
+
element.scrollTop = targetTop;
|
|
369
|
+
}
|
|
370
|
+
userScrolledUp.value = false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return true;
|
|
151
374
|
} catch (e) {
|
|
152
375
|
if (DEBUG) console.log("Error scrolling", e);
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// Continuous scrolling for held buttons
|
|
381
|
+
const startScrolling = (direction) => {
|
|
382
|
+
if (isScrolling.value) return;
|
|
383
|
+
|
|
384
|
+
isScrolling.value = true;
|
|
385
|
+
|
|
386
|
+
// Immediate scroll
|
|
387
|
+
performScroll(direction, true);
|
|
388
|
+
|
|
389
|
+
// Continue scrolling while held (for long content)
|
|
390
|
+
scrollInterval.value = setInterval(() => {
|
|
391
|
+
if (!isScrolling.value) return;
|
|
392
|
+
|
|
393
|
+
const element = $autoscroll.value?.el;
|
|
394
|
+
if (!element) return;
|
|
395
|
+
|
|
396
|
+
const scrollAmount = 100;
|
|
397
|
+
if (direction === "up") {
|
|
398
|
+
element.scrollTop = Math.max(0, element.scrollTop - scrollAmount);
|
|
399
|
+
} else {
|
|
400
|
+
element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop + scrollAmount);
|
|
401
|
+
}
|
|
402
|
+
}, 50);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const stopScrolling = () => {
|
|
406
|
+
isScrolling.value = false;
|
|
407
|
+
if (scrollInterval.value) {
|
|
408
|
+
clearInterval(scrollInterval.value);
|
|
409
|
+
scrollInterval.value = null;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// Legacy function for compatibility
|
|
414
|
+
const scrollTo = (dir) => {
|
|
415
|
+
performScroll(dir, true);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// Simple autoscroll to bottom
|
|
419
|
+
const autoScrollToBottom = () => {
|
|
420
|
+
if (!$autoscroll.value?.el || !autoscrollToggled.value) return;
|
|
421
|
+
|
|
422
|
+
// Only scroll if user hasn't manually scrolled up
|
|
423
|
+
if (!userScrolledUp.value) {
|
|
424
|
+
const element = $autoscroll.value.el;
|
|
425
|
+
|
|
426
|
+
// Calculate the target scroll position to show the last log
|
|
427
|
+
const targetScrollTop = element.scrollHeight - element.clientHeight;
|
|
428
|
+
|
|
429
|
+
// Use smooth scrolling to ensure new logs are visible
|
|
430
|
+
if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
|
|
431
|
+
element.scrollTo({
|
|
432
|
+
top: targetScrollTop,
|
|
433
|
+
behavior: "smooth"
|
|
434
|
+
});
|
|
435
|
+
} else {
|
|
436
|
+
// For small differences or fallback, use instant scroll
|
|
437
|
+
element.scrollTop = targetScrollTop;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Handle autoscroll toggle
|
|
443
|
+
const onAutoscrollToggle = () => {
|
|
444
|
+
if (autoscrollToggled.value) {
|
|
445
|
+
userScrolledUp.value = false;
|
|
446
|
+
nextTick().then(autoScrollToBottom);
|
|
153
447
|
}
|
|
154
448
|
};
|
|
155
449
|
|
|
156
450
|
const addAnsiToOutput = (a) => {
|
|
157
|
-
const html = ansii.toHtml(a
|
|
451
|
+
const html = ansii.toHtml(a?.log || a);
|
|
158
452
|
logLines.value.push(html);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
453
|
+
|
|
454
|
+
// Auto scroll after adding new content with proper timing
|
|
455
|
+
nextTick().then(() => {
|
|
456
|
+
// Use a small delay to ensure DOM is fully updated
|
|
457
|
+
setTimeout(autoScrollToBottom, 10);
|
|
458
|
+
});
|
|
163
459
|
};
|
|
164
460
|
|
|
165
461
|
const handleWebsocketMessages = (msg) => {
|
|
@@ -194,16 +490,30 @@ const makeTaskLogMapping = (lines) => {
|
|
|
194
490
|
|
|
195
491
|
window.startDebugConsoleMessages = () => {
|
|
196
492
|
setInterval(() => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
493
|
+
var taskId = Math.round(Math.random() * 10);
|
|
494
|
+
const log = {
|
|
495
|
+
log: `\u001b[96m[12:16:02.273]\u001b[00m \u001b[96m[TASK-${taskId}]\u001b[00m TEST TEST TEST TEST TEST TEST TEST`,
|
|
496
|
+
metadata: {
|
|
497
|
+
taskId: taskId,
|
|
498
|
+
siteId: "TM_US",
|
|
499
|
+
global: false
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
addAnsiToOutput(log);
|
|
503
|
+
makeTaskLogMapping([log]);
|
|
504
|
+
}, 100);
|
|
203
505
|
};
|
|
204
506
|
|
|
205
507
|
if (DEBUG) window.startDebugConsoleMessages();
|
|
206
508
|
|
|
509
|
+
// Watch for log filter changes and reset scroll state
|
|
510
|
+
watch([currentTaskLog, filteredLogs], () => {
|
|
511
|
+
userScrolledUp.value = false;
|
|
512
|
+
nextTick().then(() => {
|
|
513
|
+
setTimeout(autoScrollToBottom, 10);
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
207
517
|
// Listen for messages
|
|
208
518
|
onMounted(() => {
|
|
209
519
|
const socket = new WebsocketHeartbeatJs({ url, pingMsg: "ping" });
|
|
@@ -216,4 +526,9 @@ onMounted(() => {
|
|
|
216
526
|
});
|
|
217
527
|
};
|
|
218
528
|
});
|
|
529
|
+
|
|
530
|
+
// Cleanup on unmount
|
|
531
|
+
onUnmounted(() => {
|
|
532
|
+
stopScrolling();
|
|
533
|
+
});
|
|
219
534
|
</script>
|