@necrolab/dashboard 0.5.23 → 0.5.25

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.
Files changed (34) hide show
  1. package/index.html +14 -0
  2. package/package.json +4 -2
  3. package/src/App.vue +70 -4
  4. package/src/assets/css/components/headers.scss +8 -9
  5. package/src/assets/css/components/search-groups.scss +49 -1
  6. package/src/assets/css/components/utilities.scss +0 -21
  7. package/src/assets/css/main.scss +7 -0
  8. package/src/components/Console/ConsoleToolbar.vue +2 -2
  9. package/src/components/Editors/Account/Account.vue +1 -1
  10. package/src/components/Editors/AdminFileEditor.vue +46 -11
  11. package/src/components/Editors/Profile/CreateProfile.vue +11 -11
  12. package/src/components/Editors/TagToggle.vue +2 -1
  13. package/src/components/Filter/Filter.vue +1 -1
  14. package/src/components/Tasks/Task.vue +63 -26
  15. package/src/components/ui/Navbar.vue +104 -57
  16. package/src/components/ui/StatusBadge.vue +1 -1
  17. package/src/composables/useFilterCSS.js +6 -12
  18. package/src/composables/useNotchHandling.js +24 -58
  19. package/src/libs/Filter.js +157 -15
  20. package/src/libs/tm-renderer/axs/renderer.js +2 -2
  21. package/src/libs/tm-renderer/axs/requests.js +5 -5
  22. package/src/libs/tm-renderer/base-renderer.js +0 -78
  23. package/src/libs/tm-renderer/factory.js +5 -12
  24. package/src/libs/tm-renderer/index.js +0 -18
  25. package/src/libs/tm-renderer/request-utils.js +7 -23
  26. package/src/libs/tm-renderer/tm/renderer.js +3 -3
  27. package/src/libs/tm-renderer/tm/requests.js +6 -6
  28. package/src/views/Accounts.vue +1 -1
  29. package/src/views/Console.vue +111 -43
  30. package/src/views/Editor.vue +1 -1
  31. package/src/views/FilterBuilder.vue +21 -5
  32. package/vite.config.js +1 -3
  33. package/src/libs/tm-renderer/dependencies/node.persist.js +0 -100
  34. package/src/libs/tm-renderer/dependencies/persist.js +0 -23
package/index.html CHANGED
@@ -14,6 +14,20 @@
14
14
  <meta name="description" content="Necro Lab - dashboard" />
15
15
  <meta name="darkreader-lock" />
16
16
  <meta name="color-scheme" content="dark" />
17
+ <meta name="supported-color-schemes" content="dark" />
18
+ <!-- Prevent Noir and other dark mode extensions from modifying the site -->
19
+ <meta name="noir" content="disabled" />
20
+ <meta name="nighteye" content="disabled" />
21
+ <style>
22
+ /* Lock color scheme to prevent iOS extensions from overriding */
23
+ :root {
24
+ color-scheme: dark only;
25
+ -webkit-color-scheme: dark;
26
+ }
27
+ html, body {
28
+ color-scheme: dark only !important;
29
+ }
30
+ </style>
17
31
  <title>Necro Lab - Dashboard</title>
18
32
  <!-- DNS prefetch for external resources -->
19
33
  <link rel="dns-prefetch" href="https://fonts.googleapis.com" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@necrolab/dashboard",
3
- "version": "0.5.23",
3
+ "version": "0.5.25",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
@@ -9,7 +9,9 @@
9
9
  "expose": "node dev-server.js",
10
10
  "postinstall": "node postinstall.js",
11
11
  "preview": "vite preview",
12
- "lint": "npx eslint src/ --fix"
12
+ "lint": "npx eslint src/ --fix",
13
+ "release": "npm version patch && npm i && npm publish",
14
+ "release:minor": "npm version minor && npm i && npm publish"
13
15
  },
14
16
  "dependencies": {
15
17
  "@msgpack/msgpack": "^3.0.0-beta2",
package/src/App.vue CHANGED
@@ -18,8 +18,8 @@
18
18
  <ReconnectIndicator :message="ui.spinnerMessage" />
19
19
  </div>
20
20
  <div v-else key="main-components" class="flex">
21
- <Navbar v-if="layout == 'dashboard'" class="fixed" />
22
- <div class="router-wrapper w-full">
21
+ <Navbar v-if="layout == 'dashboard'" ref="navbarRef" class="fixed" />
22
+ <div class="router-wrapper w-full" :style="routerWrapperStyle">
23
23
  <router-view v-slot="{ Component, route }">
24
24
  <transition name="page-transition" mode="out-in">
25
25
  <component
@@ -36,7 +36,7 @@
36
36
 
37
37
  <script setup>
38
38
  import { storeToRefs } from "pinia";
39
- import { ref, computed } from "vue";
39
+ import { ref, computed, nextTick, onMounted, onUnmounted, watch } from "vue";
40
40
  import { useRouter } from "vue-router";
41
41
  import Navbar from "@/components/ui/Navbar.vue";
42
42
  import { useUIStore } from "@/stores/ui";
@@ -50,6 +50,8 @@ const ui = useUIStore();
50
50
  const { showSpinner: spinner } = storeToRefs(ui);
51
51
  const router = useRouter();
52
52
  const isLoading = ref(false);
53
+ const navbarRef = ref(null);
54
+ let navbarResizeObserver = null;
53
55
 
54
56
  // Initialize zoom prevention (all browsers)
55
57
  const { KEY_CODES } = useZoomPrevention();
@@ -78,6 +80,71 @@ document.onkeydown = function (evt) {
78
80
  if (!window.location.href.includes(":5173")) ui.startSpinner("Loading...");
79
81
 
80
82
  const layout = computed(() => router.currentRoute.value.meta.layout);
83
+ const routerWrapperStyle = computed(() => ({
84
+ paddingTop: layout.value === "dashboard" ? "var(--dashboard-navbar-height, 80px)" : "0px",
85
+ paddingLeft: "var(--safe-area-left, 0px)",
86
+ paddingRight: "var(--safe-area-right, 0px)",
87
+ paddingBottom: "var(--safe-area-bottom, 0px)"
88
+ }));
89
+
90
+ const updateNavbarHeight = () => {
91
+ if (layout.value !== "dashboard") {
92
+ document.documentElement.style.setProperty("--dashboard-navbar-height", "0px");
93
+ return;
94
+ }
95
+
96
+ const navbarElement = navbarRef.value?.$el;
97
+ if (!navbarElement) return;
98
+
99
+ const height = Math.ceil(navbarElement.getBoundingClientRect().height);
100
+ if (height > 0) {
101
+ document.documentElement.style.setProperty("--dashboard-navbar-height", `${height}px`);
102
+ }
103
+ };
104
+
105
+ const scheduleNavbarHeightUpdate = () => {
106
+ requestAnimationFrame(updateNavbarHeight);
107
+ };
108
+
109
+ const syncNavbarObserver = () => {
110
+ if (!navbarResizeObserver) return;
111
+
112
+ navbarResizeObserver.disconnect();
113
+ const navbarElement = navbarRef.value?.$el;
114
+ if (layout.value === "dashboard" && navbarElement) {
115
+ navbarResizeObserver.observe(navbarElement);
116
+ }
117
+ };
118
+
119
+ watch(
120
+ [layout, () => navbarRef.value?.$el],
121
+ () => {
122
+ nextTick(() => {
123
+ syncNavbarObserver();
124
+ scheduleNavbarHeightUpdate();
125
+ });
126
+ },
127
+ { immediate: true }
128
+ );
129
+
130
+ onMounted(() => {
131
+ scheduleNavbarHeightUpdate();
132
+ window.addEventListener("resize", scheduleNavbarHeightUpdate, { passive: true });
133
+ window.visualViewport?.addEventListener("resize", scheduleNavbarHeightUpdate);
134
+
135
+ if (typeof ResizeObserver !== "undefined") {
136
+ navbarResizeObserver = new ResizeObserver(scheduleNavbarHeightUpdate);
137
+ syncNavbarObserver();
138
+ }
139
+ });
140
+
141
+ onUnmounted(() => {
142
+ window.removeEventListener("resize", scheduleNavbarHeightUpdate);
143
+ window.visualViewport?.removeEventListener("resize", scheduleNavbarHeightUpdate);
144
+ navbarResizeObserver?.disconnect();
145
+ navbarResizeObserver = null;
146
+ document.documentElement.style.setProperty("--dashboard-navbar-height", "0px");
147
+ });
81
148
  </script>
82
149
 
83
150
  <style lang="scss">
@@ -148,7 +215,6 @@ const layout = computed(() => router.currentRoute.value.meta.layout);
148
215
 
149
216
  /* Router wrapper */
150
217
  .router-wrapper {
151
- @apply pt-16 lg:pt-20;
152
218
  z-index: 0;
153
219
  }
154
220
 
@@ -5,14 +5,21 @@
5
5
 
6
6
  .page-header {
7
7
  @apply flex items-center justify-between;
8
- padding-top: 1.5rem;
8
+ padding-top: 0.75rem;
9
9
  padding-bottom: 0.5rem;
10
+ flex-wrap: nowrap;
11
+ gap: 0.5rem;
10
12
 
11
13
  .page-header-card {
12
14
  @apply flex items-center gap-2.5 rounded-lg;
13
15
  padding: 0.375rem 0.75rem;
14
16
  background: linear-gradient(135deg, var(--color-gradient-start) 0%, var(--color-gradient-end) 100%);
15
17
  border: 1px solid var(--color-border-light);
18
+ flex: 0 1 auto;
19
+ min-width: 0;
20
+ max-width: 60%;
21
+ white-space: nowrap;
22
+ overflow: hidden;
16
23
 
17
24
  svg, img {
18
25
  width: 17px;
@@ -74,8 +81,6 @@
74
81
  }
75
82
 
76
83
  @screen sm {
77
- padding-top: 1rem;
78
-
79
84
  .page-header-card {
80
85
  gap: 0.625rem;
81
86
  padding: 0.5rem 0.875rem;
@@ -111,9 +116,3 @@
111
116
  }
112
117
  }
113
118
  }
114
-
115
- @media (display-mode: standalone) {
116
- .page-header {
117
- padding-top: 3.5rem !important;
118
- }
119
- }
@@ -11,6 +11,7 @@
11
11
  background: var(--color-bg-input);
12
12
  transition: all 0.15s ease;
13
13
  display: flex;
14
+ position: relative;
14
15
 
15
16
  .tag-toggle,
16
17
  .dropdown,
@@ -33,18 +34,65 @@
33
34
  .tag-toggle {
34
35
  padding: 0;
35
36
  border: 0 !important;
37
+ flex-shrink: 0 !important;
38
+ min-width: max-content !important;
36
39
  }
37
40
 
38
41
  .tag-toggle button {
39
42
  font-size: 0.875rem;
40
43
  padding: 0.5rem 0.75rem;
41
- min-width: fit-content;
44
+ min-width: fit-content !important;
42
45
  border: 0 !important;
46
+ white-space: nowrap;
47
+ flex-shrink: 0 !important;
48
+ }
49
+
50
+ /* iPad - prevent TagToggle from being compressed */
51
+ @media (min-width: 768px) and (max-width: 1024px) {
52
+ .tag-toggle {
53
+ flex-shrink: 0 !important;
54
+ min-width: max-content !important;
55
+ }
56
+
57
+ .tag-toggle button {
58
+ padding: 0.375rem 0.5rem;
59
+ font-size: 0.75rem;
60
+ flex-shrink: 0 !important;
61
+ }
62
+
63
+ input {
64
+ flex: 0 1 auto;
65
+ min-width: 100px !important;
66
+ }
43
67
  }
44
68
 
45
69
  .dropdown {
46
70
  width: 120px !important;
47
71
  min-width: 120px !important;
72
+ overflow: visible !important;
73
+ position: static !important;
74
+
75
+ /* iPad responsive - ensure arrow is visible */
76
+ @media (min-width: 768px) and (max-width: 1024px) {
77
+ width: 140px !important;
78
+ min-width: 140px !important;
79
+ flex-shrink: 0;
80
+ }
81
+
82
+ /* Mobile - more flexible */
83
+ @media (max-width: 767px) {
84
+ width: auto !important;
85
+ min-width: 90px !important;
86
+ max-width: 110px !important;
87
+ flex-shrink: 1;
88
+ }
89
+ }
90
+
91
+ /* Ensure dropdown menu portal isn't clipped */
92
+ .dropdown-menu-portal,
93
+ .dropdown-content-portal {
94
+ overflow: visible !important;
95
+ z-index: 9999 !important;
48
96
  }
49
97
 
50
98
  .dropdown:not(.transparent):focus-within,
@@ -78,43 +78,22 @@
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 - 20rem);
82
- max-height: calc(100vh - 20rem);
83
81
  scroll-padding: 0.5rem;
84
82
  -webkit-overflow-scrolling: touch;
85
83
  overscroll-behavior: contain;
86
84
  min-height: 12rem !important;
87
85
  touch-action: pan-y pan-up pan-down;
88
86
 
89
- @screen md {
90
- height: calc(100vh - 18rem);
91
- max-height: calc(100vh - 18rem);
92
- }
93
-
94
87
  @screen lg {
95
- height: calc(100vh - 16rem);
96
- max-height: calc(100vh - 16rem);
97
88
  padding: 1.25rem;
98
89
  }
99
90
 
100
91
  @screen mobile-portrait {
101
- height: calc(100vh - 22rem);
102
- max-height: calc(100vh - 22rem);
103
92
  overflow: auto;
104
93
  padding: 0.25rem;
105
94
  font-size: 0.75rem;
106
95
  line-height: 1rem;
107
96
  }
108
-
109
- @media (orientation: landscape) and (max-width: 1023px) {
110
- height: calc(100vh - 12rem);
111
- max-height: calc(100vh - 12rem);
112
- }
113
-
114
- @media (max-width: 767px) and (orientation: landscape) {
115
- height: auto;
116
- max-height: 65vh;
117
- }
118
97
  }
119
98
 
120
99
  /* FilterBuilder action button */
@@ -22,6 +22,13 @@
22
22
  BODY LAYOUT & BACKGROUND
23
23
  ========================================================================== */
24
24
 
25
+ :root {
26
+ --safe-area-top: 0px;
27
+ --safe-area-right: 0px;
28
+ --safe-area-bottom: 0px;
29
+ --safe-area-left: 0px;
30
+ }
31
+
25
32
  html {
26
33
  @apply bg-dark-300;
27
34
  min-height: 100vh;
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div>
3
- <div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
3
+ <div class="mb-2 flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
4
4
  <div class="flex flex-col gap-3 md:flex-1 md:flex-row md:items-center">
5
5
  <div class="w-full md:w-64">
6
6
  <Dropdown
@@ -79,7 +79,7 @@
79
79
  </button>
80
80
  </div>
81
81
  </div>
82
- <div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
82
+ <div class="mb-2 mt-2 flex justify-between gap-2 md:hidden">
83
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
84
  <h3 class="text-sm text-white">Hide Monitors</h3>
85
85
  <Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
@@ -30,7 +30,7 @@
30
30
  </h4>
31
31
  </div>
32
32
 
33
- <div class="col-span-1 flex">
33
+ <div class="col-span-1 flex items-center justify-center">
34
34
  <ActionButtonGroup>
35
35
  <li>
36
36
  <button @click="edit">
@@ -2,18 +2,19 @@
2
2
  <div class="admin-editor-section" :class="{ 'landscape-hidden': isHidden }" v-if="!isHidden">
3
3
  <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
4
4
  <div class="flex items-center gap-2 w-full">
5
- <div class="unified-search-group flex flex-1 dropdown-container z-dropdown-high lg:flex-initial lg:min-w-64">
6
- <Dropdown
7
- class="admin-file-dropdown flex-1"
8
- default="Select a file"
9
- :onClick="loadFile"
10
- :options="availableFiles"
11
- :allowDefault="false"
12
- :includeAdjacentButtons="true"
13
- rightAmount="right-1" />
5
+ <div class="admin-file-selector flex-1 lg:flex-initial lg:w-64 dropdown-container z-dropdown-high">
6
+ <div class="relative min-w-0 flex-grow">
7
+ <Dropdown
8
+ class="admin-file-dropdown w-full"
9
+ default="Select a file"
10
+ :onClick="loadFile"
11
+ :options="availableFiles"
12
+ :allowDefault="false"
13
+ :includeAdjacentButtons="true"
14
+ rightAmount="right-1" />
15
+ </div>
14
16
  <button
15
- class="refresh-button flex items-center justify-center w-9 h-10 flex-shrink-0 text-white bg-dark-400 hover:bg-dark-450"
16
- style="transition: all 0.2s ease"
17
+ class="refresh-button"
17
18
  @click="$emit('refresh-files')"
18
19
  title="Refresh file list">
19
20
  <ReloadIcon class="refresh-icon icon-md" />
@@ -180,6 +181,40 @@ onMounted(() => {
180
181
  </script>
181
182
 
182
183
  <style scoped>
184
+ .admin-file-selector {
185
+ @apply flex items-center overflow-hidden rounded-lg border-2 border-dark-550 bg-dark-500;
186
+ min-height: 40px;
187
+ transition: border-color 0.15s ease;
188
+ }
189
+
190
+ .admin-file-selector:hover {
191
+ @apply border-dark-650;
192
+ }
193
+
194
+ .admin-file-selector:focus-within {
195
+ border-color: var(--color-primary);
196
+ outline: 1px solid var(--color-primary);
197
+ outline-offset: 0;
198
+ }
199
+
200
+ :deep(.admin-file-dropdown.dropdown) {
201
+ @apply h-10 border-0 rounded-none bg-transparent shadow-none;
202
+ padding: 0 0.75rem !important;
203
+ }
204
+
205
+ :deep(.admin-file-dropdown .dropdown-display) {
206
+ padding: 0;
207
+ }
208
+
209
+ .refresh-button {
210
+ @apply flex h-10 w-10 flex-shrink-0 items-center justify-center border-l border-dark-550 bg-dark-400 text-white;
211
+ transition: all 0.2s ease;
212
+ }
213
+
214
+ .refresh-button:hover {
215
+ @apply border-dark-625 bg-dark-450;
216
+ }
217
+
183
218
  .editor-action-btn {
184
219
  @apply flex items-center justify-center w-9 h-9 rounded border-2 border-dark-550 bg-dark-400 text-white;
185
220
  transition: all 0.2s ease;
@@ -8,8 +8,17 @@
8
8
 
9
9
  <div>
10
10
  <div class="my-3 grid grid-cols-12 gap-3">
11
+ <!-- Country chooser -->
12
+ <FormField label="Country" :icon="StadiumIcon" z-index="0" class="col-span-4 md:col-span-2" noWrapper>
13
+ <ProfileCountryChooser
14
+ class="h-10"
15
+ :value="formProfile.country"
16
+ :onClick="chooseCountry"
17
+ :disabled="true" />
18
+ </FormField>
19
+
11
20
  <!-- Profile tag -->
12
- <FormField label="Profile Tag" :icon="TagIcon" z-index="0" class="col-span-12 md:col-span-4" noWrapper>
21
+ <FormField label="Profile Tag" :icon="TagIcon" z-index="0" class="col-span-8 md:col-span-4" noWrapper>
13
22
  <Dropdown
14
23
  :class="`input-default dropdown w-full`"
15
24
  :default="ui.profile.tags[0]"
@@ -19,7 +28,7 @@
19
28
  </FormField>
20
29
 
21
30
  <!-- Card Number -->
22
- <FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-12 md:col-span-8">
31
+ <FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-12 md:col-span-6">
23
32
  <input
24
33
  ref="cardNumberInput"
25
34
  placeholder="Enter card number"
@@ -30,15 +39,6 @@
30
39
  @focus="formatCardNumberDisplay" />
31
40
  </FormField>
32
41
 
33
- <!-- Country chooser -->
34
- <FormField label="Country" :icon="StadiumIcon" z-index="0" class="col-span-12 md:col-span-2" noWrapper>
35
- <ProfileCountryChooser
36
- class="h-10"
37
- :value="formProfile.country"
38
- :onClick="chooseCountry"
39
- :disabled="true" />
40
- </FormField>
41
-
42
42
  <!-- Exp Year -->
43
43
  <FormField label="Expiry Year" :icon="TimerIcon" :error="errors.includes('expYear')" z-index="0" class="col-span-6 md:col-span-5" noWrapper>
44
44
  <Dropdown
@@ -1,8 +1,9 @@
1
1
  <template>
2
2
  <button
3
3
  @click="increase"
4
- class="h-10 px-1 text-white bg-dark-500 relative overflow-hidden"
4
+ class="h-10 px-3 text-white bg-dark-500 relative overflow-hidden"
5
5
  :class="noBorder ? 'border-0' : 'border-2 border-dark-550'"
6
+ style="min-width: max-content;"
6
7
  >
7
8
  <span v-if="sortOptions[currentOpt % sortOptions.length] === 'Enabled'"
8
9
  ><img class="mx-auto" height="16px" width="14px" src="@/assets/img/square_check.svg"
@@ -300,7 +300,7 @@ props.filterBuilder.onUpdate(() => {
300
300
 
301
301
  <style scoped>
302
302
  .filter-card {
303
- @apply bg-dark-500 border border-dark-625/30 relative mb-2 transition-colors duration-200;
303
+ @apply bg-dark-500 border border-dark-625/30 relative transition-colors duration-200;
304
304
  }
305
305
 
306
306
  .filter-card:hover:not(.expanded-filter) {