@necrolab/dashboard 0.4.39 → 0.4.40

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.
@@ -46,7 +46,11 @@
46
46
  "Bash(sed:*)",
47
47
  "Bash(true)",
48
48
  "Bash(./run build)",
49
- "Bash(mv:*)"
49
+ "Bash(mv:*)",
50
+ "Bash(chown:*)",
51
+ "Bash(chmod:*)",
52
+ "Bash(timeout 10s npm run dev)",
53
+ "Bash(gtimeout:*)"
50
54
  ],
51
55
  "deny": []
52
56
  }
package/.prettierrc CHANGED
@@ -6,5 +6,18 @@
6
6
  "trailingComma": "none",
7
7
  "bracketSpacing": true,
8
8
  "arrowParens": "always",
9
- "printWidth": 120
9
+ "printWidth": 120,
10
+ "vueIndentScriptAndStyle": false,
11
+ "htmlWhitespaceSensitivity": "ignore",
12
+ "bracketSameLine": true,
13
+ "singleAttributePerLine": false,
14
+ "overrides": [
15
+ {
16
+ "files": "*.vue",
17
+ "options": {
18
+ "vueIndentScriptAndStyle": false,
19
+ "htmlWhitespaceSensitivity": "ignore"
20
+ }
21
+ }
22
+ ]
10
23
  }
package/backend/api.js CHANGED
@@ -179,6 +179,11 @@ app.ws("/api/updates", async function (ws, req) {
179
179
  }
180
180
  });
181
181
 
182
+ // Root route - serve the main app
183
+ app.get("/", (req, res) => {
184
+ res.sendFile(path.join(__dirname, "../dist/index.html"));
185
+ });
186
+
182
187
  // catches vue reloads
183
188
  app.use(
184
189
  ["/login", "/console", "/editor", "/filter", "/profiles", "/accounts"],
@@ -2,17 +2,17 @@ const users = [
2
2
  {
3
3
  event: "auth",
4
4
  botChannels: {
5
- splash: "950761407119511582",
6
- carts: "961558093832011807",
7
- checkouts: "1025407596120776794",
8
- declines: "1027868055696572508",
9
- stockChecker: "1099079655962722375",
10
- venueMaps: "1099125114450214973"
5
+ splash: "-",
6
+ carts: "-",
7
+ checkouts: "-",
8
+ declines: "-",
9
+ stockChecker: "-",
10
+ venueMaps: "-"
11
11
  },
12
12
  _id: "641a5292b561088b64fe390b",
13
- name: "admin",
13
+ name: "Admin",
14
14
  password: "admin",
15
- profilePicture: "https://avatars.githubusercontent.com/u/202437892?s=128",
15
+ profilePicture: "https://cdn.discordapp.com/avatars/435549216304267264/6cfd74ad7c5939a0bcbf218aa08be8cb.png",
16
16
  admin: true,
17
17
  proxyList: { checkout: "admin-proxies" },
18
18
  profileTags: ["Amex", "Citi", "Mercury", "Slash"],
package/dev-server.js CHANGED
@@ -1,49 +1,136 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createServer } from 'vite'
4
- import process from 'process'
3
+ import { createServer } from "vite";
4
+ import process from "process";
5
+ import { spawn } from "child_process";
6
+
7
+ // Start backend server with mock data
8
+ const startBackend = () => {
9
+ return new Promise((resolve, reject) => {
10
+ const backend = spawn("node", ["index.js"], {
11
+ stdio: ["inherit", "pipe", "pipe"],
12
+ env: { ...process.env, NODE_ENV: "development" }
13
+ });
14
+
15
+ let backendReady = false;
16
+
17
+ backend.stdout.on("data", (data) => {
18
+ const output = data.toString();
19
+
20
+ // Look for backend ready signal
21
+ if (output.includes("Web API started on port") || output.includes("8081")) {
22
+ backendReady = true;
23
+ resolve(backend);
24
+ }
25
+ });
26
+
27
+ backend.stderr.on("data", (data) => {
28
+ // Suppress error output unless it's critical
29
+ if (data.toString().includes("Error") || data.toString().includes("EADDRINUSE")) {
30
+ console.error("⚠️ Backend error:", data.toString().trim());
31
+ }
32
+ });
33
+
34
+ backend.on("error", (error) => {
35
+ reject(error);
36
+ });
37
+
38
+ backend.on("exit", (code) => {
39
+ if (code !== 0 && !backendReady) {
40
+ reject(new Error(`Backend failed to start`));
41
+ }
42
+ });
43
+
44
+ // Timeout fallback
45
+ setTimeout(() => {
46
+ if (!backendReady) {
47
+ resolve(backend);
48
+ }
49
+ }, 5000);
50
+ });
51
+ };
5
52
 
6
53
  const startServer = async () => {
7
- try {
8
- const server = await createServer({
9
- configFile: './vite.config.js',
10
- server: {
11
- port: 5173,
12
- strictPort: true,
13
- host: true,
14
- cors: true,
15
- hmr: {
16
- overlay: false
54
+ let backend;
55
+ let server;
56
+ let isShuttingDown = false;
57
+
58
+ // Cleanup function
59
+ const cleanup = () => {
60
+ if (isShuttingDown) return;
61
+ isShuttingDown = true;
62
+
63
+ console.log("\n👋 Goodbye!");
64
+ if (backend) {
65
+ backend.kill("SIGTERM");
66
+ setTimeout(() => backend.kill("SIGKILL"), 3000);
67
+ }
68
+ if (server) {
69
+ server.close();
17
70
  }
18
- }
19
- })
20
-
21
- // Handle unhandled errors gracefully
22
- process.on('uncaughtException', (error) => {
23
- if (error.code === 'ECONNRESET' || error.errno === -54) {
24
- console.log('📱 iPhone disconnected/reconnected - continuing...')
25
- return
26
- }
27
- console.error('Server error:', error)
28
- })
29
-
30
- process.on('unhandledRejection', (reason, promise) => {
31
- if (reason?.code === 'ECONNRESET' || reason?.errno === -54) {
32
- console.log('📱 iPhone connection reset - continuing...')
33
- return
34
- }
35
- console.error('Unhandled rejection at:', promise, 'reason:', reason)
36
- })
37
-
38
- await server.listen()
39
- server.printUrls()
40
-
41
- console.log('🚀 Dev server running with iOS connection error handling')
42
-
43
- } catch (error) {
44
- console.error('Failed to start server:', error)
45
- process.exit(1)
46
- }
47
- }
48
-
49
- startServer()
71
+ process.exit(0);
72
+ };
73
+
74
+ // Handle cleanup on various exit signals
75
+ process.on("SIGINT", cleanup);
76
+ process.on("SIGTERM", cleanup);
77
+
78
+ try {
79
+ console.log("🚀 Starting Necro Dashboard...");
80
+
81
+ // Ensure proper permissions for Vite cache
82
+ try {
83
+ const fs = await import('node:fs');
84
+ const path = await import('node:path');
85
+ const cacheDir = path.resolve('./node_modules/.vite');
86
+ if (fs.existsSync(cacheDir)) {
87
+ fs.chmodSync(cacheDir, 0o755);
88
+ }
89
+ } catch (e) {
90
+ // Ignore permission errors
91
+ }
92
+
93
+ backend = await startBackend();
94
+
95
+ server = await createServer({
96
+ configFile: "./vite.config.js",
97
+ server: {
98
+ port: 5173,
99
+ strictPort: true,
100
+ host: true,
101
+ cors: true,
102
+ hmr: {
103
+ overlay: false
104
+ }
105
+ }
106
+ });
107
+
108
+ // Handle unhandled errors gracefully
109
+ process.on("uncaughtException", (error) => {
110
+ if (error.code === "ECONNRESET" || error.errno === -54) {
111
+ return; // Silently handle mobile reconnects
112
+ }
113
+ console.error("❌ Server error:", error.message);
114
+ });
115
+
116
+ process.on("unhandledRejection", (reason) => {
117
+ if (reason?.code === "ECONNRESET" || reason?.errno === -54) {
118
+ return; // Silently handle mobile reconnects
119
+ }
120
+ console.error("❌ Unhandled error:", reason?.message || reason);
121
+ });
122
+
123
+ await server.listen();
124
+
125
+ console.log("\n✨ Necro Dashboard is ready!");
126
+ console.log("🌐 Development: http://localhost:5173");
127
+ console.log("🌐 Staging: http://localhost:8081\n");
128
+ console.log("💀 Happy debugging");
129
+ console.log("\n\n👆 Press Ctrl+C to stop");
130
+ } catch (error) {
131
+ console.error("Failed to start server:", error);
132
+ process.exit(1);
133
+ }
134
+ };
135
+
136
+ startServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@necrolab/dashboard",
3
- "version": "0.4.39",
3
+ "version": "0.4.40",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
@@ -15,7 +15,7 @@
15
15
  "dependencies": {
16
16
  "@faker-js/faker": "^7.6.0",
17
17
  "@msgpack/msgpack": "^3.0.0-beta2",
18
- "@necrolab/tm-renderer": "^0.1.4",
18
+ "@necrolab/tm-renderer": "^0.1.7",
19
19
  "@vitejs/plugin-vue": "^5.2.1",
20
20
  "@vueuse/core": "^11.3.0",
21
21
  "autoprefixer": "^10.4.21",
@@ -25,6 +25,7 @@
25
25
  "dragselect": "^3.1.1",
26
26
  "express": "^4.21.2",
27
27
  "express-ws": "^5.0.2",
28
+ "ipaddr.js": "^2.2.0",
28
29
  "pinia": "^2.3.0",
29
30
  "postcss": "^8.4.49",
30
31
  "register-service-worker": "^1.7.2",
package/postinstall.js CHANGED
@@ -5,19 +5,31 @@ import { fileURLToPath } from "node:url";
5
5
 
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
 
8
- console.log(__dirname);
8
+ console.log("🔧 Running postinstall build...");
9
9
 
10
- var vitePath = path.resolve(path.join(__dirname, "/../../vite/bin/vite.js"));
11
- console.log(vitePath);
12
- execSync(`node "${vitePath}" build`, {
13
- cwd: __dirname,
14
- stdio: "inherit"
15
- });
10
+ // Use npx to run vite instead of hardcoded path
11
+ try {
12
+ execSync(`npx vite build`, {
13
+ cwd: __dirname,
14
+ stdio: "inherit"
15
+ });
16
+ console.log("✅ Build completed successfully");
17
+ } catch (error) {
18
+ console.log("⚠️ Build failed, but continuing...");
19
+ }
16
20
 
17
- var oldPath = path.resolve("./dist/");
18
- var distPath = path.resolve(path.join(__dirname, "../../../dashboard/dist/"));
19
- fs.rmSync(distPath, {
20
- recursive: true
21
- });
21
+ // Only do file operations if we're in a nested structure
22
+ const distPath = path.resolve(path.join(__dirname, "../../../dashboard/dist/"));
23
+ const localDistPath = path.resolve("./dist/");
22
24
 
23
- fs.renameSync(oldPath, distPath);
25
+ if (fs.existsSync(localDistPath) && __dirname.includes("dashboard")) {
26
+ try {
27
+ if (fs.existsSync(distPath)) {
28
+ fs.rmSync(distPath, { recursive: true });
29
+ }
30
+ fs.renameSync(localDistPath, distPath);
31
+ console.log("📁 Moved dist folder");
32
+ } catch (error) {
33
+ console.log("⚠️ Could not move dist folder, but that's okay");
34
+ }
35
+ }
@@ -43,7 +43,7 @@
43
43
  }
44
44
 
45
45
  .button-default {
46
- @apply text-white font-medium h-12 rounded-lg duration-150 border border-dark-650 hover:border-dark-700 #{!important};
46
+ @apply text-white font-medium h-12 rounded-lg duration-150 border border-dark-650 hover:border-dark-700 !important;
47
47
  }
48
48
 
49
49
  // Remove number input spinners
@@ -59,9 +59,10 @@ input[type="number"] {
59
59
  }
60
60
 
61
61
  .input-incrementer {
62
- @apply flex flex-col absolute right-2 top-1/2 transform -translate-y-1/2 #{!important};
62
+ @apply flex flex-col absolute right-2 top-1/2 transform -translate-y-1/2 !important;
63
63
  gap: 2px;
64
64
 
65
+
65
66
  button {
66
67
  @apply w-6 h-4 flex items-center justify-center transition-all duration-150 rounded-sm;
67
68
  background: rgba(46, 47, 52, 0.8);
@@ -125,16 +126,16 @@ input[type="number"] {
125
126
  .ant-select-dropdown {
126
127
  z-index: 10000 !important;
127
128
  position: relative;
128
- @apply rounded-lg #{!important};
129
+ @apply rounded-lg !important;
129
130
  }
130
131
 
131
132
  .ant-select {
132
- @apply bg-dark-500 flex items-center bg-clip-padding rounded-lg relative box-border px-1 py-1 border-dark-550 border-2 #{!important};
133
+ @apply bg-dark-500 flex items-center bg-clip-padding rounded-lg relative box-border px-1 py-1 border-dark-550 border-2 !important;
133
134
  min-height: 3.45em;
134
135
  height: fit-content;
135
136
 
136
137
  .ant-select-selector {
137
- @apply h-full w-full focus:outline-none text-xs text-white #{!important};
138
+ @apply h-full w-full focus:outline-none text-xs text-white !important;
138
139
  background: transparent;
139
140
  background-color: transparent !important;
140
141
  border: 0 !important;
@@ -144,7 +145,7 @@ input[type="number"] {
144
145
 
145
146
  // Ant Design Select item styling
146
147
  .ant-select-multiple .ant-select-selection-item {
147
- @apply bg-dark-400 items-center gap-x-4 #{!important};
148
+ @apply bg-dark-400 items-center gap-x-4 !important;
148
149
  padding-inline-end: 15px;
149
150
  padding-inline-start: 15px;
150
151
  border-radius: 10px;
@@ -155,7 +156,6 @@ input[type="number"] {
155
156
  vertical-align: 0.05rem;
156
157
  }
157
158
  }
158
-
159
159
  .ant-select-selection-item-remove > .anticon svg {
160
160
  color: #fff;
161
161
  }
@@ -166,10 +166,10 @@ input[type="number"] {
166
166
 
167
167
  .ant-select-item-option {
168
168
  &-selected:not(.ant-select-item-option-disabled) {
169
- @apply bg-dark-500 text-white #{!important};
169
+ @apply bg-dark-500 text-white !important;
170
170
 
171
171
  .ant-select-item-option-state {
172
- @apply text-white #{!important};
172
+ @apply text-white !important;
173
173
 
174
174
  svg {
175
175
  margin-top: -1.5px;
@@ -182,16 +182,16 @@ input[type="number"] {
182
182
  }
183
183
 
184
184
  &-content {
185
- @apply text-white #{!important};
185
+ @apply text-white !important;
186
186
  }
187
187
  }
188
188
 
189
189
  .ant-select-dropdown {
190
- @apply bg-dark-400 #{!important};
190
+ @apply bg-dark-400 !important;
191
191
  }
192
192
 
193
193
  .ant-select-selector:before {
194
194
  content: "";
195
195
  position: absolute;
196
- @apply bg-light-400 h-1 w-1 right-1 rounded-full #{!important};
196
+ @apply bg-light-400 h-1 w-1 right-1 rounded-full !important;
197
197
  }
@@ -142,11 +142,11 @@ img {
142
142
 
143
143
  // Button utilities
144
144
  .btn-primary {
145
- @apply bg-accent-blue hover:bg-opacity-80 text-white font-medium px-4 py-2 rounded transition-all duration-150;
146
- border: 1px solid #5d7cc0;
145
+ @apply bg-dark-550 hover:bg-dark-650 text-white font-medium px-4 py-2 rounded transition-all duration-150;
146
+ border: 1px solid #44454b;
147
147
 
148
148
  &:hover {
149
- border-color: #4a6ba0;
149
+ border-color: #4b4c53;
150
150
  }
151
151
  }
152
152
 
@@ -290,26 +290,35 @@ img {
290
290
  }
291
291
  }
292
292
 
293
- // Dynamic heights for different devices
293
+ // Dynamic heights for different devices - conservative approach to prevent partial items
294
294
  .max-h-big {
295
- max-height: 36rem;
295
+ max-height: calc(100vh - 12rem); // Account for navbar, header, stats, controls, utilities
296
+ min-height: 8rem; // 2 items minimum (2 * 64px = 128px = 8rem)
297
+ overflow: hidden; // Ensure no partial items show
298
+ }
299
+
300
+ @media only screen and (min-width: 1440px) {
301
+ .max-h-big {
302
+ max-height: calc(100vh - 11rem); // Large screens get slightly more space
303
+ }
296
304
  }
297
305
 
298
306
  @media only screen and (min-device-width: 768px) and (max-device-width: 1366px) {
299
307
  .max-h-big {
300
- max-height: 44rem;
308
+ max-height: calc(100vh - 12rem);
301
309
  }
302
310
  }
303
311
 
304
312
  @media only screen and (min-device-width: 768px) and (max-device-width: 1366px) and (orientation: portrait) {
305
313
  .max-h-big {
306
- max-height: 68rem;
314
+ max-height: calc(100vh - 12rem);
307
315
  }
308
316
  }
309
317
 
310
318
  @media screen and (max-device-height: 500px) {
311
319
  .max-h-big {
312
- max-height: 11.7rem;
320
+ max-height: calc(100vh - 12rem);
321
+ min-height: 8rem;
313
322
  }
314
323
  }
315
324
 
@@ -370,10 +379,30 @@ img {
370
379
  fill: currentColor !important;
371
380
  }
372
381
 
382
+ // Toast container
383
+ .Toastify__toast-container {
384
+ pointer-events: none;
385
+ --toastify-toast-bd-radius: 6px;
386
+ }
387
+
373
388
  .Toastify__toast {
374
389
  min-height: 50px !important;
375
- height: 50px !important;
376
- pointer-events: none !important;
390
+ height: 50px !important; // Keep fixed height for consistency
391
+ pointer-events: auto !important;
392
+ margin-bottom: 6px !important;
393
+
394
+ // Ultra-smooth animations with optimized timing
395
+ transition: transform 0.18s cubic-bezier(0.25, 0.1, 0.25, 1), opacity 0.12s cubic-bezier(0.25, 0.1, 0.25, 1) !important;
396
+ transform: translate3d(0, 0, 0);
397
+
398
+ // Override default animations with smoother curves
399
+ &.Toastify__slide-enter-active {
400
+ animation: slideInRight 0.22s cubic-bezier(0.25, 0.1, 0.25, 1);
401
+ }
402
+
403
+ &.Toastify__slide-exit-active {
404
+ animation: slideOutRight 0.12s cubic-bezier(0.4, 0, 1, 1);
405
+ }
377
406
 
378
407
  &-body > div:last-child {
379
408
  font-family: "Inter", sans-serif;
@@ -387,6 +416,29 @@ img {
387
416
  }
388
417
  }
389
418
 
419
+ // Tighter, more precise animations
420
+ @keyframes slideInRight {
421
+ 0% {
422
+ transform: translateX(110%);
423
+ opacity: 0;
424
+ }
425
+ 100% {
426
+ transform: translateX(0);
427
+ opacity: 1;
428
+ }
429
+ }
430
+
431
+ @keyframes slideOutRight {
432
+ 0% {
433
+ transform: translateX(0);
434
+ opacity: 1;
435
+ }
436
+ 100% {
437
+ transform: translateX(110%);
438
+ opacity: 0;
439
+ }
440
+ }
441
+
390
442
  .Toastify__toast--error svg {
391
443
  color: #ee8282;
392
444
  }
@@ -401,6 +453,7 @@ img {
401
453
 
402
454
  .Toastify__progress-bar {
403
455
  height: 2px;
456
+ transition: width 0.08s cubic-bezier(0.25, 0.1, 0.25, 1); // Ultra-smooth progress bar
404
457
  }
405
458
 
406
459
  // Mobile optimizations
@@ -1,26 +1,32 @@
1
1
  <template>
2
2
  <div class="form-section">
3
3
  <!-- Username -->
4
- <div class="input-wrapper">
5
- <label class="label-override mb-2">Username</label>
4
+ <div class="input-wrapper mb-4">
5
+ <label style="display: flex; align-items: center; font-size: 12px; margin-bottom: 8px; color: #a0a0a6">
6
+ Username
7
+ <ProfileIcon style="margin-left: 8px; width: 16px; height: 16px" />
8
+ </label>
6
9
  <div class="input-default">
7
10
  <input v-model="user" placeholder="Username" />
8
11
  </div>
9
12
  </div>
10
13
  <!-- Password -->
11
14
  <div class="input-wrapper">
12
- <label class="label-override mb-2">Password</label>
15
+ <label style="display: flex; align-items: center; font-size: 12px; margin-bottom: 8px; color: #a0a0a6">
16
+ Password
17
+ <KeyIcon style="margin-left: 8px; width: 16px; height: 16px" />
18
+ </label>
13
19
  <div class="input-default">
14
20
  <input v-model="password" type="password" placeholder="Password" />
15
21
  </div>
16
22
  </div>
17
23
  <button
18
- class="btn-primary w-full mt-4"
24
+ class="bg-green-400 hover:bg-green-500 smooth-hover text-white font-medium px-4 py-2 rounded transition-all duration-150 w-full mt-4 h-10 flex items-center justify-center"
19
25
  @click="login()"
20
26
  :disabled="buttonDisabled"
21
27
  >
22
28
  <span v-if="!buttonDisabled">Login</span>
23
- <div v-else class="loading-spinner mx-auto"></div>
29
+ <div v-else class="loading-spinner"></div>
24
30
  </button>
25
31
  </div>
26
32
  </template>
@@ -29,6 +35,7 @@ import { useUIStore } from "@/stores/ui";
29
35
  import { ref } from "vue";
30
36
  import router from "@/router/index";
31
37
  import { sendLogin } from "@/stores/requests";
38
+ import { ProfileIcon, KeyIcon } from "@/components/icons";
32
39
 
33
40
  const password = ref("");
34
41
  const user = ref("");