@necrolab/dashboard 0.5.16 → 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/package.json +1 -1
- package/src/App.vue +14 -480
- package/src/assets/css/components/buttons.scss +12 -68
- package/src/assets/css/components/headers.scss +1 -1
- package/src/assets/css/components/utilities.scss +91 -16
- package/src/assets/css/main.scss +22 -95
- package/src/components/Auth/LoginForm.vue +2 -2
- package/src/components/Console/ConsoleToolbar.vue +123 -0
- package/src/components/Editors/Account/Account.vue +4 -2
- package/src/components/Editors/Account/AccountView.vue +12 -37
- package/src/components/Editors/Account/CreateAccount.vue +3 -11
- package/src/components/Editors/AdminFileEditor.vue +179 -0
- package/src/components/Editors/Profile/CreateProfile.vue +4 -20
- package/src/components/Editors/Profile/Profile.vue +5 -4
- package/src/components/Editors/Profile/ProfileView.vue +13 -38
- package/src/components/Editors/ProxyFileEditor.vue +86 -0
- package/src/components/Filter/Filter.vue +6 -6
- package/src/components/Filter/FilterPreview.vue +4 -12
- package/src/components/Filter/PriceSortToggle.vue +1 -1
- package/src/components/Tasks/QuickSettings.vue +5 -5
- package/src/components/Tasks/Stats.vue +1 -1
- package/src/components/Tasks/Task.vue +5 -8
- package/src/components/Tasks/TaskView.vue +2 -1
- package/src/components/Tasks/ViewTask.vue +2 -2
- package/src/components/icons/index.js +0 -4
- package/src/components/ui/ActionButtonGroup.vue +2 -2
- package/src/components/ui/BalanceIndicator.vue +3 -3
- package/src/components/ui/EnableDisableToggle.vue +2 -2
- package/src/components/ui/FormField.vue +2 -2
- package/src/components/ui/IconLabel.vue +2 -2
- package/src/components/ui/InfoRow.vue +4 -4
- package/src/components/ui/Modal.vue +83 -9
- package/src/components/ui/Navbar.vue +3 -3
- package/src/components/ui/ReadonlyFieldsSection.vue +1 -1
- package/src/components/ui/StatusBadge.vue +1 -1
- package/src/components/ui/controls/CountryChooser.vue +5 -5
- package/src/components/ui/controls/atomic/MultiDropdown.vue +1 -1
- package/src/composables/useCodeEditor.js +117 -0
- package/src/composables/useDropdownPosition.js +0 -2
- 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/useTableRender.js +23 -0
- package/src/composables/useZoomPrevention.js +96 -0
- package/src/constants/tableLayout.js +14 -0
- package/src/libs/utils/array.js +1 -3
- package/src/libs/utils/dataGeneration.js +1 -1
- package/src/libs/utils/string.js +1 -26
- package/src/libs/utils/validation.js +2 -26
- package/src/stores/connection.js +0 -25
- package/src/stores/ui.js +21 -35
- package/src/utils/tableHelpers.js +1 -0
- package/src/views/Accounts.vue +9 -17
- package/src/views/Console.vue +15 -92
- package/src/views/Editor.vue +39 -938
- package/src/views/FilterBuilder.vue +9 -97
- package/src/views/Profiles.vue +9 -17
- package/src/views/Tasks.vue +4 -4
- package/src/assets/img/background.svg.backup +0 -11
- package/src/components/icons/SquareCheck.vue +0 -12
- package/src/components/icons/SquareUncheck.vue +0 -12
- package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
- /package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +0 -0
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
/* Console main container with responsive heights */
|
|
79
79
|
.console-main {
|
|
80
80
|
@apply relative overflow-x-auto overflow-y-auto rounded border-2 border-dark-550 bg-dark-400 p-2 font-mono text-white;
|
|
81
|
-
height: calc(100vh -
|
|
81
|
+
height: calc(100vh - 20rem);
|
|
82
|
+
max-height: calc(100vh - 20rem);
|
|
82
83
|
scroll-padding: 0.5rem;
|
|
83
84
|
-webkit-overflow-scrolling: touch;
|
|
84
85
|
overscroll-behavior: contain;
|
|
@@ -86,17 +87,19 @@
|
|
|
86
87
|
touch-action: pan-y pan-up pan-down;
|
|
87
88
|
|
|
88
89
|
@screen md {
|
|
89
|
-
height: calc(100vh -
|
|
90
|
+
height: calc(100vh - 18rem);
|
|
91
|
+
max-height: calc(100vh - 18rem);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
@screen lg {
|
|
93
|
-
height: calc(100vh -
|
|
95
|
+
height: calc(100vh - 16rem);
|
|
96
|
+
max-height: calc(100vh - 16rem);
|
|
94
97
|
padding: 1.25rem;
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
@screen mobile-portrait {
|
|
98
|
-
height: calc(100vh -
|
|
99
|
-
max-height:
|
|
101
|
+
height: calc(100vh - 22rem);
|
|
102
|
+
max-height: calc(100vh - 22rem);
|
|
100
103
|
overflow: auto;
|
|
101
104
|
padding: 0.25rem;
|
|
102
105
|
font-size: 0.75rem;
|
|
@@ -104,12 +107,13 @@
|
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
@media (orientation: landscape) and (max-width: 1023px) {
|
|
107
|
-
height: calc(100vh -
|
|
110
|
+
height: calc(100vh - 12rem);
|
|
111
|
+
max-height: calc(100vh - 12rem);
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
@media (max-width: 767px) and (orientation: landscape) {
|
|
111
115
|
height: auto;
|
|
112
|
-
max-height:
|
|
116
|
+
max-height: 65vh;
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -202,19 +206,90 @@
|
|
|
202
206
|
@apply h-full w-full bg-transparent text-sm text-white outline-none;
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
@apply flex items-center justify-center;
|
|
209
|
+
.icon-md {
|
|
210
|
+
@apply h-4 w-4;
|
|
208
211
|
}
|
|
209
212
|
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
/* Flex with gap variants - consolidates 51+ repeated patterns */
|
|
214
|
+
.flex-gap-1 {
|
|
215
|
+
@apply flex gap-1;
|
|
212
216
|
}
|
|
213
217
|
|
|
214
|
-
.
|
|
215
|
-
@apply
|
|
218
|
+
.flex-gap-2 {
|
|
219
|
+
@apply flex gap-2;
|
|
216
220
|
}
|
|
217
221
|
|
|
218
|
-
.
|
|
219
|
-
@apply
|
|
222
|
+
.flex-gap-3 {
|
|
223
|
+
@apply flex gap-3;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Standard transition - used throughout the app */
|
|
227
|
+
.transition-standard {
|
|
228
|
+
@apply transition-all duration-150;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Badge pattern for version/stat chips - QuickSettings pattern */
|
|
232
|
+
.badge-version {
|
|
233
|
+
@apply flex items-center gap-x-1.5 px-2.5 py-1.5 rounded-lg border bg-dark-400 border-dark-550;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Icon button container - 8x8 flex centered button pattern */
|
|
237
|
+
.icon-button {
|
|
238
|
+
@apply flex h-8 w-8 flex-shrink-0 items-center justify-center rounded;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* Icon size utilities for common patterns */
|
|
242
|
+
.icon-sm {
|
|
243
|
+
@apply h-3 w-3;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.icon-lg {
|
|
247
|
+
@apply h-5 w-5;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.icon-xl {
|
|
251
|
+
@apply h-6 w-6;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Square size utilities */
|
|
255
|
+
.size-8 {
|
|
256
|
+
@apply h-8 w-8;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.size-10 {
|
|
260
|
+
@apply h-10 w-10;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Common button pattern - dark with border */
|
|
264
|
+
.btn-dark-bordered {
|
|
265
|
+
@apply rounded-md border border-dark-650 bg-dark-400;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* Flex layout utilities */
|
|
269
|
+
.flex-between {
|
|
270
|
+
@apply flex items-center justify-between;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Text utilities */
|
|
274
|
+
.text-button {
|
|
275
|
+
@apply text-white text-xs font-medium;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* Padding combinations */
|
|
279
|
+
.pad-3-2 {
|
|
280
|
+
@apply px-3 py-2;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Square size utilities */
|
|
284
|
+
.square-8 {
|
|
285
|
+
@apply h-8 w-8;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* Border + rounded combinations */
|
|
289
|
+
.dark-border-rounded {
|
|
290
|
+
@apply rounded border border-dark-650;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.dark-border-rounded-md {
|
|
294
|
+
@apply rounded-md border border-dark-650;
|
|
220
295
|
}
|
package/src/assets/css/main.scss
CHANGED
|
@@ -105,17 +105,6 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
|
|
|
105
105
|
COMPONENT UTILITIES
|
|
106
106
|
========================================================================== */
|
|
107
107
|
|
|
108
|
-
.smooth-hover {
|
|
109
|
-
@apply transition-all duration-200;
|
|
110
|
-
@include scale-hover;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.status-indicator {
|
|
114
|
-
@apply h-2 w-2 flex-shrink-0 rounded-full;
|
|
115
|
-
min-width: 4px;
|
|
116
|
-
min-height: 4px;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
108
|
.mobile-icons {
|
|
120
109
|
@apply ml-auto flex items-center gap-x-2 lg:hidden;
|
|
121
110
|
|
|
@@ -134,6 +123,17 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
|
|
|
134
123
|
}
|
|
135
124
|
}
|
|
136
125
|
|
|
126
|
+
.smooth-hover {
|
|
127
|
+
@apply transition-all duration-200;
|
|
128
|
+
@include scale-hover;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.status-indicator {
|
|
132
|
+
@apply h-2 w-2 flex-shrink-0 rounded-full;
|
|
133
|
+
min-width: 4px;
|
|
134
|
+
min-height: 4px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
137
|
.loading-spinner {
|
|
138
138
|
@apply h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent;
|
|
139
139
|
}
|
|
@@ -150,90 +150,6 @@ button.bg-red-400 {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
/* ==========================================================================
|
|
154
|
-
LABEL & ICON STYLING
|
|
155
|
-
========================================================================== */
|
|
156
|
-
|
|
157
|
-
.label-override {
|
|
158
|
-
@apply mb-2 flex items-center text-xs;
|
|
159
|
-
color: var(--color-text-muted);
|
|
160
|
-
margin-top: 1rem;
|
|
161
|
-
|
|
162
|
-
svg {
|
|
163
|
-
@apply ml-2;
|
|
164
|
-
color: var(--color-text-muted) !important;
|
|
165
|
-
width: 16px;
|
|
166
|
-
height: 16px;
|
|
167
|
-
fill: var(--color-text-muted) !important;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.task-switches {
|
|
172
|
-
h4 {
|
|
173
|
-
color: var(--color-text-primary);
|
|
174
|
-
font-size: 0.8125rem;
|
|
175
|
-
font-weight: 500;
|
|
176
|
-
@apply mx-auto mb-2 flex items-center gap-x-2 text-center;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.switch-wrapper {
|
|
180
|
-
@apply gap-y-2;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
svg {
|
|
184
|
-
width: 15px !important;
|
|
185
|
-
height: 15px !important;
|
|
186
|
-
color: var(--color-text-primary) !important;
|
|
187
|
-
margin-left: 0.25rem !important;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/* ==========================================================================
|
|
192
|
-
DYNAMIC HEIGHTS & RESPONSIVE DESIGN
|
|
193
|
-
========================================================================== */
|
|
194
|
-
|
|
195
|
-
.max-h-big {
|
|
196
|
-
max-height: calc(100vh - 12rem);
|
|
197
|
-
min-height: 8rem;
|
|
198
|
-
overflow: hidden;
|
|
199
|
-
|
|
200
|
-
@screen xl {
|
|
201
|
-
max-height: calc(100vh - 11rem);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/* ==========================================================================
|
|
206
|
-
TRANSITIONS & ANIMATIONS
|
|
207
|
-
========================================================================== */
|
|
208
|
-
|
|
209
|
-
.dropdown-fade-enter-active {
|
|
210
|
-
@apply transition-all duration-300;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.dropdown-fade-leave-active {
|
|
214
|
-
@apply transition-all duration-200;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.dropdown-fade-enter-from {
|
|
218
|
-
@apply opacity-0;
|
|
219
|
-
transform: translateY(-8px) scale(0.95);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.dropdown-fade-leave-to {
|
|
223
|
-
@apply opacity-0;
|
|
224
|
-
transform: translateY(-4px) scale(0.98);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.dropdown-fade-enter-to,
|
|
228
|
-
.dropdown-fade-leave-from {
|
|
229
|
-
@apply opacity-100;
|
|
230
|
-
transform: translateY(0) scale(1);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.will-change-auto {
|
|
234
|
-
will-change: auto;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
153
|
/* ==========================================================================
|
|
238
154
|
iOS OPTIMIZATIONS
|
|
239
155
|
========================================================================== */
|
|
@@ -271,3 +187,14 @@ button.bg-red-400 {
|
|
|
271
187
|
min-height: 5px;
|
|
272
188
|
}
|
|
273
189
|
}
|
|
190
|
+
|
|
191
|
+
/* Fade transition for modals and components */
|
|
192
|
+
.fade-enter-active,
|
|
193
|
+
.fade-leave-active {
|
|
194
|
+
transition: opacity 0.15s ease;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.fade-enter-from,
|
|
198
|
+
.fade-leave-to {
|
|
199
|
+
opacity: 0;
|
|
200
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="form-section">
|
|
3
3
|
<!-- Username -->
|
|
4
4
|
<div class="form-field-labeled mb-3">
|
|
5
|
-
<div class="flex items-center
|
|
5
|
+
<div class="flex-gap-2 items-center">
|
|
6
6
|
<ProfileIcon class="w-4 h-4" />
|
|
7
7
|
<span class="text-light-300 font-medium text-sm">Username</span>
|
|
8
8
|
</div>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</div>
|
|
13
13
|
<!-- Password -->
|
|
14
14
|
<div class="form-field-labeled mb-4">
|
|
15
|
-
<div class="flex items-center
|
|
15
|
+
<div class="flex-gap-2 items-center">
|
|
16
16
|
<KeyIcon class="w-4 h-4" />
|
|
17
17
|
<span class="text-light-300 font-medium text-sm">Password</span>
|
|
18
18
|
</div>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
|
4
|
+
<div class="flex flex-col gap-3 md:flex-1 md:flex-row md:items-center">
|
|
5
|
+
<div class="w-full md:w-64">
|
|
6
|
+
<Dropdown
|
|
7
|
+
class="console-dropdown input-default w-full border-2 border-dark-550 bg-dark-500"
|
|
8
|
+
rightAmount="right-2"
|
|
9
|
+
default="All logs"
|
|
10
|
+
:allowDefault="true"
|
|
11
|
+
:value="currentTaskLog"
|
|
12
|
+
:onClick="(f) => $emit('update:currentTaskLog', f.split(' ')[0])"
|
|
13
|
+
:options="Object.entries(taskLogMapping).map(([k, v]) => `${k} (${v.length})`).sort((a, b) => a.localeCompare(b))" />
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex flex-1 items-center gap-2">
|
|
16
|
+
<div class="input-default flex flex-1 items-center md:max-w-64">
|
|
17
|
+
<input
|
|
18
|
+
:value="searchQuery"
|
|
19
|
+
@input="$emit('update:searchQuery', $event.target.value)"
|
|
20
|
+
type="text"
|
|
21
|
+
placeholder="Search logs..."
|
|
22
|
+
aria-label="Search logs"
|
|
23
|
+
class="transparent-input" />
|
|
24
|
+
<span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
|
|
25
|
+
</div>
|
|
26
|
+
<button
|
|
27
|
+
class="console-scroll-btn md:hidden"
|
|
28
|
+
@mousedown="$emit('scroll', 'up')"
|
|
29
|
+
@mouseup="$emit('scroll-stop')"
|
|
30
|
+
@mouseleave="$emit('scroll-stop')"
|
|
31
|
+
@touchstart="$emit('scroll', 'up')"
|
|
32
|
+
@touchend="$emit('scroll-stop')"
|
|
33
|
+
aria-label="Scroll up">
|
|
34
|
+
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
35
|
+
</button>
|
|
36
|
+
<button
|
|
37
|
+
class="console-scroll-btn md:hidden"
|
|
38
|
+
@mousedown="$emit('scroll', 'down')"
|
|
39
|
+
@mouseup="$emit('scroll-stop')"
|
|
40
|
+
@mouseleave="$emit('scroll-stop')"
|
|
41
|
+
@touchstart="$emit('scroll', 'down')"
|
|
42
|
+
@touchend="$emit('scroll-stop')">
|
|
43
|
+
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="flex hidden items-center gap-3 md:flex">
|
|
48
|
+
<div class="hidden items-center gap-3 md:flex">
|
|
49
|
+
<button class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
50
|
+
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
51
|
+
<Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
|
|
52
|
+
</button>
|
|
53
|
+
<button class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
54
|
+
<h3 class="text-sm text-white">Auto</h3>
|
|
55
|
+
<Switch class="scale-75" :model-value="autoscrollToggled" @update:model-value="$emit('update:autoscrollToggled', $event); $emit('autoscroll-toggle')" />
|
|
56
|
+
<div
|
|
57
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
58
|
+
class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
|
|
59
|
+
title="Autoscroll paused - scroll to bottom to resume"></div>
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<button
|
|
63
|
+
class="hidden size-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"
|
|
64
|
+
@mousedown="$emit('scroll', 'up')"
|
|
65
|
+
@mouseup="$emit('scroll-stop')"
|
|
66
|
+
@mouseleave="$emit('scroll-stop')"
|
|
67
|
+
@touchstart="$emit('scroll', 'up')"
|
|
68
|
+
@touchend="$emit('scroll-stop')">
|
|
69
|
+
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
70
|
+
</button>
|
|
71
|
+
<button
|
|
72
|
+
class="hidden size-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"
|
|
73
|
+
@mousedown="$emit('scroll', 'down')"
|
|
74
|
+
@mouseup="$emit('scroll-stop')"
|
|
75
|
+
@mouseleave="$emit('scroll-stop')"
|
|
76
|
+
@touchstart="$emit('scroll', 'down')"
|
|
77
|
+
@touchend="$emit('scroll-stop')">
|
|
78
|
+
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
|
|
83
|
+
<button class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
84
|
+
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
85
|
+
<Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
|
|
86
|
+
</button>
|
|
87
|
+
<button class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
88
|
+
<h3 class="text-sm text-white">Auto</h3>
|
|
89
|
+
<Switch class="scale-75" :model-value="autoscrollToggled" @update:model-value="$emit('update:autoscrollToggled', $event); $emit('autoscroll-toggle')" />
|
|
90
|
+
<div
|
|
91
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
92
|
+
class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
|
|
93
|
+
title="Autoscroll paused - scroll to bottom to resume"></div>
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup>
|
|
100
|
+
import { DownIcon, UpIcon } from '@/components/icons';
|
|
101
|
+
import Switch from '@/components/ui/controls/atomic/Switch.vue';
|
|
102
|
+
import Dropdown from '@/components/ui/controls/atomic/Dropdown.vue';
|
|
103
|
+
|
|
104
|
+
defineProps({
|
|
105
|
+
currentTaskLog: String,
|
|
106
|
+
taskLogMapping: Object,
|
|
107
|
+
searchQuery: String,
|
|
108
|
+
filteredLogs: Boolean,
|
|
109
|
+
autoscrollToggled: Boolean,
|
|
110
|
+
userScrolledUp: Boolean,
|
|
111
|
+
filteredCount: String
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
defineEmits([
|
|
115
|
+
'update:currentTaskLog',
|
|
116
|
+
'update:searchQuery',
|
|
117
|
+
'update:filteredLogs',
|
|
118
|
+
'update:autoscrollToggled',
|
|
119
|
+
'scroll',
|
|
120
|
+
'scroll-stop',
|
|
121
|
+
'autoscroll-toggle'
|
|
122
|
+
]);
|
|
123
|
+
</script>
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
</li>
|
|
40
40
|
<li v-if="props.account.enabled">
|
|
41
41
|
<button @click="disable">
|
|
42
|
-
<img class="
|
|
42
|
+
<img class="icon-md" src="/img/controls/disable.svg" />
|
|
43
43
|
</button>
|
|
44
44
|
</li>
|
|
45
45
|
<li v-else>
|
|
46
46
|
<button @click="enable">
|
|
47
|
-
<img class="
|
|
47
|
+
<img class="icon-md" src="/img/controls/enable.svg" />
|
|
48
48
|
</button>
|
|
49
49
|
</li>
|
|
50
50
|
</ActionButtonGroup>
|
|
@@ -61,6 +61,8 @@ import { useUIStore } from "@/stores/ui";
|
|
|
61
61
|
import TagLabel from "@/components/Editors/TagLabel.vue";
|
|
62
62
|
import { useRowSelection } from "@/composables/useRowSelection";
|
|
63
63
|
import { useCopyToClipboard } from "@/composables/useCopyToClipboard";
|
|
64
|
+
import { computed } from "vue";
|
|
65
|
+
import { useEnableDisable } from "@/composables/useEnableDisable";
|
|
64
66
|
|
|
65
67
|
const ui = useUIStore();
|
|
66
68
|
const { copy } = useCopyToClipboard();
|
|
@@ -8,24 +8,24 @@
|
|
|
8
8
|
@valueUpdate="ui.toggleMainCheckbox('accounts')"
|
|
9
9
|
:isHeader="true" />
|
|
10
10
|
<div class="mx-auto flex cursor-pointer items-center" @click="ui.toggleSort('eventId')">
|
|
11
|
-
<MailIcon class="mr-0
|
|
11
|
+
<MailIcon class="mr-0 icon-md md:mr-3" />
|
|
12
12
|
<h4 class="hidden text-white md:flex">Email</h4>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
15
|
<div class="col-span-2 hidden items-center justify-center md:flex" v-once>
|
|
16
|
-
<KeyIcon class="mr-0
|
|
16
|
+
<KeyIcon class="mr-0 icon-md md:mr-3" />
|
|
17
17
|
<h4 class="hidden text-white md:flex">Password</h4>
|
|
18
18
|
</div>
|
|
19
19
|
<div class="grid-cell-center" v-once>
|
|
20
|
-
<CheckmarkIcon class="mr-0
|
|
20
|
+
<CheckmarkIcon class="mr-0 icon-md md:mr-3" />
|
|
21
21
|
<h4 class="hidden text-white md:flex">Enabled</h4>
|
|
22
22
|
</div>
|
|
23
23
|
<div class="col-span-1 hidden items-center justify-center lg:flex" v-once>
|
|
24
|
-
<TicketIcon class="mr-0
|
|
24
|
+
<TicketIcon class="mr-0 icon-md md:mr-3" />
|
|
25
25
|
<h4 class="hidden text-white md:flex">Tags</h4>
|
|
26
26
|
</div>
|
|
27
27
|
<div class="grid-cell-center" v-once>
|
|
28
|
-
<ClickIcon class="mr-0
|
|
28
|
+
<ClickIcon class="mr-0 icon-md md:mr-3" />
|
|
29
29
|
<h4 class="hidden text-white md:flex">Actions</h4>
|
|
30
30
|
</div>
|
|
31
31
|
</Header>
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
:key="account.id || account.index"
|
|
39
39
|
class="min-h-16 flex-shrink-0 hover:bg-dark-550">
|
|
40
40
|
<Account
|
|
41
|
-
:class="i
|
|
41
|
+
:class="getRowClass(i)"
|
|
42
42
|
:account="account" />
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
@@ -59,7 +59,9 @@ import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
|
59
59
|
import EmptyState from "@/components/ui/EmptyState.vue";
|
|
60
60
|
import { useUIStore } from "@/stores/ui";
|
|
61
61
|
import { useDynamicTableHeight } from "@/composables/useDynamicTableHeight";
|
|
62
|
-
import { computed
|
|
62
|
+
import { computed } from "vue";
|
|
63
|
+
import { useTableRender } from "@/composables/useTableRender";
|
|
64
|
+
import { getRowClass } from "@/utils/tableHelpers";
|
|
63
65
|
|
|
64
66
|
const props = defineProps({
|
|
65
67
|
accounts: {
|
|
@@ -69,36 +71,9 @@ const props = defineProps({
|
|
|
69
71
|
});
|
|
70
72
|
const ui = useUIStore();
|
|
71
73
|
|
|
72
|
-
const
|
|
73
|
-
const toRender = computed(() => {
|
|
74
|
-
let c = 0;
|
|
75
|
-
const rendered = props.accounts.map((t) => ({ ...t, index: c++ }));
|
|
74
|
+
const { toRender } = useTableRender(computed(() => props.accounts));
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
rendered.forEach((t) => {
|
|
79
|
-
if (t.id && !(t.id in i.value)) {
|
|
80
|
-
i.value[t.id] = 0;
|
|
81
|
-
}
|
|
82
|
-
if (!(t.index in i.value)) {
|
|
83
|
-
i.value[t.index] = 0;
|
|
84
|
-
}
|
|
85
|
-
});
|
|
76
|
+
import { TABLE_LAYOUT } from "@/constants/tableLayout";
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Layout constants for dynamic table height calculation
|
|
91
|
-
const LAYOUT_CONSTANTS = {
|
|
92
|
-
TOP_RESERVED_SPACE: 180, // Page header + search controls + gaps (router-wrapper handles navbar, no UTILS section)
|
|
93
|
-
BOTTOM_BUFFER: 50, // Margin at bottom to prevent overflow
|
|
94
|
-
ROW_HEIGHT: 64, // Account row height in pixels
|
|
95
|
-
MIN_ROWS_TO_SHOW: 2 // Minimum number of rows to display
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const { dynamicTableHeight } = useDynamicTableHeight({
|
|
99
|
-
topReservedSpace: LAYOUT_CONSTANTS.TOP_RESERVED_SPACE,
|
|
100
|
-
bottomBuffer: LAYOUT_CONSTANTS.BOTTOM_BUFFER,
|
|
101
|
-
rowHeight: LAYOUT_CONSTANTS.ROW_HEIGHT,
|
|
102
|
-
minRowsToShow: LAYOUT_CONSTANTS.MIN_ROWS_TO_SHOW
|
|
103
|
-
});
|
|
78
|
+
const { dynamicTableHeight } = useDynamicTableHeight(TABLE_LAYOUT.ACCOUNTS);
|
|
104
79
|
</script>
|
|
@@ -72,6 +72,7 @@ import { useUIStore } from "@/stores/ui";
|
|
|
72
72
|
import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
|
|
73
73
|
import ReadonlyFieldsSection from "@/components/ui/ReadonlyFieldsSection.vue";
|
|
74
74
|
import { ref } from "vue";
|
|
75
|
+
import { useFormValidation } from "@/composables/useFormValidation";
|
|
75
76
|
|
|
76
77
|
const ui = useUIStore();
|
|
77
78
|
const account = ref({
|
|
@@ -82,19 +83,10 @@ const account = ref({
|
|
|
82
83
|
|
|
83
84
|
if (ui.currentlyEditing?.email) account.value = ui.currentlyEditing;
|
|
84
85
|
|
|
85
|
-
const errors =
|
|
86
|
-
|
|
87
|
-
const validate = (p) => {
|
|
88
|
-
errors.value = [];
|
|
89
|
-
|
|
90
|
-
if (!p.email.includes("@")) errors.value.push("email");
|
|
91
|
-
if (p.password.length < 5) errors.value.push("password");
|
|
92
|
-
|
|
93
|
-
return errors.value.length === 0;
|
|
94
|
-
};
|
|
86
|
+
const { errors, validateAccount } = useFormValidation();
|
|
95
87
|
|
|
96
88
|
function done() {
|
|
97
|
-
if (!
|
|
89
|
+
if (!validateAccount(account.value)) return;
|
|
98
90
|
ui.toggleModal("");
|
|
99
91
|
ui.addAccount(account.value);
|
|
100
92
|
}
|