Compare commits

..

14 commits

18 changed files with 3064 additions and 3673 deletions

View file

@ -65,6 +65,11 @@ git clone https://github.com/liamcottle/meshtastic-map
cd meshtastic-map
```
Install Meshtastic protobufs definitions
```
git clone https://github.com/meshtastic/protobufs src/protobufs
```
Install NodeJS dependencies
```

645
package-lock.json generated
View file

@ -21,7 +21,7 @@
},
"devDependencies": {
"jest": "^30.1.3",
"prisma": "^7.4.1"
"prisma": "^6.16.2"
}
},
"node_modules/@ampproject/remapping": {
@ -69,6 +69,7 @@
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@ -603,73 +604,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@chevrotain/cst-dts-gen": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz",
"integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "10.5.0",
"@chevrotain/types": "10.5.0",
"lodash": "4.17.21"
}
},
"node_modules/@chevrotain/gast": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz",
"integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "10.5.0",
"lodash": "4.17.21"
}
},
"node_modules/@chevrotain/types": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz",
"integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@chevrotain/utils": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz",
"integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@electric-sql/pglite": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz",
"integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@electric-sql/pglite-socket": {
"version": "0.0.20",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.20.tgz",
"integrity": "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"pglite-server": "dist/scripts/server.js"
},
"peerDependencies": {
"@electric-sql/pglite": "0.3.15"
}
},
"node_modules/@electric-sql/pglite-tools": {
"version": "0.2.20",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz",
"integrity": "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==",
"devOptional": true,
"license": "Apache-2.0",
"peerDependencies": {
"@electric-sql/pglite": "0.3.15"
}
},
"node_modules/@emnapi/core": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz",
@ -704,19 +638,6 @@
"tslib": "^2.4.0"
}
},
"node_modules/@hono/node-server": {
"version": "1.19.9",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=18.14.1"
},
"peerDependencies": {
"hono": "^4"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -1143,20 +1064,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@mrleebo/prisma-ast": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.13.1.tgz",
"integrity": "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"chevrotain": "^10.5.0",
"lilconfig": "^2.1.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@ -1217,138 +1124,66 @@
}
},
"node_modules/@prisma/config": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.4.1.tgz",
"integrity": "sha512-vteSXm8N46bo3FW9MhPGVHAj+KRgrR6TWtlSk6GqToCKjTnOexXdPZyiDyEsfVW38YhqEmVl6w/6iHN8uYVJcw==",
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.2.tgz",
"integrity": "sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"c12": "3.1.0",
"deepmerge-ts": "7.1.5",
"effect": "3.18.4",
"effect": "3.16.12",
"empathic": "2.0.0"
}
},
"node_modules/@prisma/debug": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.4.1.tgz",
"integrity": "sha512-qEtzO8oLouRv18JDQUC3G3Gnv+fGVscHZm/x1DBB/WT+kOvPDQLM2woX6IGgWnSMYYlrxjuALshT7G/blvY0bQ==",
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.2.tgz",
"integrity": "sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/dev": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.20.0.tgz",
"integrity": "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==",
"devOptional": true,
"license": "ISC",
"dependencies": {
"@electric-sql/pglite": "0.3.15",
"@electric-sql/pglite-socket": "0.0.20",
"@electric-sql/pglite-tools": "0.2.20",
"@hono/node-server": "1.19.9",
"@mrleebo/prisma-ast": "0.13.1",
"@prisma/get-platform": "7.2.0",
"@prisma/query-plan-executor": "7.2.0",
"foreground-child": "3.3.1",
"get-port-please": "3.2.0",
"hono": "4.11.4",
"http-status-codes": "2.3.0",
"pathe": "2.0.3",
"proper-lockfile": "4.1.2",
"remeda": "2.33.4",
"std-env": "3.10.0",
"valibot": "1.2.0",
"zeptomatch": "2.1.0"
}
},
"node_modules/@prisma/engines": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.4.1.tgz",
"integrity": "sha512-BZEBdHvNJx5PzIG37EI/Zi5UUI5hGWjkYsQmKa7OIK6evAvebOTwutjS/VRI6cA6grmA52eLZR+oekGRMqkKxQ==",
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.2.tgz",
"integrity": "sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "7.4.1",
"@prisma/engines-version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3",
"@prisma/fetch-engine": "7.4.1",
"@prisma/get-platform": "7.4.1"
"@prisma/debug": "6.16.2",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/fetch-engine": "6.16.2",
"@prisma/get-platform": "6.16.2"
}
},
"node_modules/@prisma/engines-version": {
"version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3.tgz",
"integrity": "sha512-fUxVd1TjOW8K4XsZ8dAm88sDW5Ry7AxWDfsYEWwScS6Fjo3caKC6hgNumUfsmsy0Il9LjDn5X0PpVXNt3iwayw==",
"version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43.tgz",
"integrity": "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.1.tgz",
"integrity": "sha512-kN4tmkQzlgm/KtE+jTNSYjsDxxe/5i6GApPI32BN9T0tlgsgSBtDJbjGBICttkAIjsh73dXf8raPKxO/2n2UUg==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "7.4.1"
}
},
"node_modules/@prisma/fetch-engine": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.4.1.tgz",
"integrity": "sha512-Z9kbuxX2bvEsyeS3LZEiEnxG0lVtZbpYgaAnPj69N+A9f2De8Lta0EoFtld9zhfERVPIQWhSWUc8himky3qYdA==",
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.2.tgz",
"integrity": "sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "7.4.1",
"@prisma/engines-version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3",
"@prisma/get-platform": "7.4.1"
}
},
"node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.1.tgz",
"integrity": "sha512-kN4tmkQzlgm/KtE+jTNSYjsDxxe/5i6GApPI32BN9T0tlgsgSBtDJbjGBICttkAIjsh73dXf8raPKxO/2n2UUg==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "7.4.1"
"@prisma/debug": "6.16.2",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/get-platform": "6.16.2"
}
},
"node_modules/@prisma/get-platform": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
"integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.2.tgz",
"integrity": "sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "7.2.0"
}
},
"node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
"integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/query-plan-executor": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
"integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/studio-core": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz",
"integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==",
"devOptional": true,
"license": "Apache-2.0",
"peerDependencies": {
"@types/react": "^18.0.0 || ^19.0.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
"@prisma/debug": "6.16.2"
}
},
"node_modules/@protobufjs/aspromise": {
@ -1433,9 +1268,9 @@
}
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
"devOptional": true,
"license": "MIT"
},
@ -1530,17 +1365,6 @@
"undici-types": "~5.26.4"
}
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/readable-stream": {
"version": "4.0.21",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.21.tgz",
@ -1947,16 +1771,6 @@
"node": ">=12.17"
}
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/babel-jest": {
"version": "30.1.2",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.2.tgz",
@ -2196,6 +2010,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001737",
"electron-to-chromium": "^1.5.211",
@ -2395,21 +2210,6 @@
"node": ">=10"
}
},
"node_modules/chevrotain": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz",
"integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "10.5.0",
"@chevrotain/gast": "10.5.0",
"@chevrotain/types": "10.5.0",
"@chevrotain/utils": "10.5.0",
"lodash": "4.17.21",
"regexp-to-ast": "0.5.0"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@ -2681,9 +2481,9 @@
}
},
"node_modules/confbox": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"devOptional": true,
"license": "MIT"
},
@ -2756,7 +2556,7 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@ -2767,14 +2567,6 @@
"node": ">= 8"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -2825,16 +2617,6 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -2900,9 +2682,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/effect": {
"version": "3.18.4",
"resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
"integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
"version": "3.16.12",
"resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz",
"integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@ -3204,9 +2986,9 @@
}
},
"node_modules/exsolve": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
"devOptional": true,
"license": "MIT"
},
@ -3365,7 +3147,7 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"devOptional": true,
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
@ -3425,16 +3207,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -3489,13 +3261,6 @@
"node": ">=8.0.0"
}
},
"node_modules/get-port-please": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
"integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
"devOptional": true,
"license": "MIT"
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@ -3577,23 +3342,9 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"devOptional": true,
"dev": true,
"license": "ISC"
},
"node_modules/grammex": {
"version": "3.1.12",
"resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
"integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/graphmatch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
"integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
"devOptional": true,
"license": "MIT"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -3632,16 +3383,6 @@
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
"license": "MIT"
},
"node_modules/hono": {
"version": "4.11.4",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz",
"integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@ -3669,13 +3410,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/http-status-codes": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
"integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
"devOptional": true,
"license": "MIT"
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -3838,13 +3572,6 @@
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
},
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"devOptional": true,
"license": "MIT"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@ -3862,7 +3589,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"devOptional": true,
"dev": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
@ -4597,9 +4324,9 @@
}
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"devOptional": true,
"license": "MIT",
"bin": {
@ -4686,16 +4413,6 @@
"node": ">=6"
}
},
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@ -4716,13 +4433,6 @@
"node": ">=8"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"devOptional": true,
"license": "MIT"
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@ -4739,22 +4449,6 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/lru.min": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
"devOptional": true,
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@ -5008,40 +4702,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/mysql2": {
"version": "3.15.3",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
"integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.7.0",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/named-placeholders": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"lru.min": "^1.1.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/napi-postinstall": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz",
@ -5152,30 +4812,25 @@
"license": "MIT"
},
"node_modules/nypm": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
"integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"citty": "^0.2.0",
"citty": "^0.1.6",
"consola": "^3.4.2",
"pathe": "^2.0.3",
"tinyexec": "^1.0.2"
"pkg-types": "^2.3.0",
"tinyexec": "^1.0.1"
},
"bin": {
"nypm": "dist/cli.mjs"
},
"engines": {
"node": ">=18"
"node": "^14.16.0 || >=16.10.0"
}
},
"node_modules/nypm/node_modules/citty": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz",
"integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==",
"devOptional": true,
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -5360,7 +5015,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -5460,20 +5115,6 @@
"pathe": "^2.0.3"
}
},
"node_modules/postgres": {
"version": "3.4.7",
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
"integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
"devOptional": true,
"license": "Unlicense",
"engines": {
"node": ">=12"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/porsager"
}
},
"node_modules/pretty-format": {
"version": "30.0.5",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz",
@ -5503,34 +5144,27 @@
}
},
"node_modules/prisma": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-7.4.1.tgz",
"integrity": "sha512-gDKOXwnPiMdB+uYMhMeN8jj4K7Cu3Q2wB/wUsITOoOk446HtVb8T9BZxFJ1Zop6alc89k6PMNdR2FZCpbXp/jw==",
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.2.tgz",
"integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@prisma/config": "7.4.1",
"@prisma/dev": "0.20.0",
"@prisma/engines": "7.4.1",
"@prisma/studio-core": "0.13.1",
"mysql2": "3.15.3",
"postgres": "3.4.7"
"@prisma/config": "6.16.2",
"@prisma/engines": "6.16.2"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": "^20.19 || ^22.12 || >=24.0"
"node": ">=18.18"
},
"peerDependencies": {
"better-sqlite3": ">=9.0.0",
"typescript": ">=5.4.0"
"typescript": ">=5.1.0"
},
"peerDependenciesMeta": {
"better-sqlite3": {
"optional": true
},
"typescript": {
"optional": true
}
@ -5551,25 +5185,6 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/proper-lockfile": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"retry": "^0.12.0",
"signal-exit": "^3.0.2"
}
},
"node_modules/proper-lockfile/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"devOptional": true,
"license": "ISC"
},
"node_modules/protobufjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
@ -5672,31 +5287,6 @@
"destr": "^2.0.3"
}
},
"node_modules/react": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.4"
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@ -5734,23 +5324,6 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/regexp-to-ast": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
"integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==",
"devOptional": true,
"license": "MIT"
},
"node_modules/remeda": {
"version": "2.33.4",
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
"integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
"devOptional": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/remeda"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -5784,16 +5357,6 @@
"node": ">=8"
}
},
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@ -5862,14 +5425,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"devOptional": true,
"license": "MIT",
"peer": true
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@ -5922,12 +5477,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
"devOptional": true
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
@ -5951,7 +5500,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@ -5964,7 +5513,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -6046,7 +5595,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"devOptional": true,
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
@ -6126,16 +5675,6 @@
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
@ -6157,13 +5696,6 @@
"node": ">= 0.8"
}
},
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"devOptional": true,
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@ -6449,14 +5981,11 @@
}
},
"node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
"license": "MIT"
},
"node_modules/tmpl": {
"version": "1.0.5",
@ -6645,21 +6174,6 @@
"node": ">=10.12.0"
}
},
"node_modules/valibot": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
"integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"typescript": ">=5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -6682,7 +6196,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"devOptional": true,
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@ -6988,17 +6502,6 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zeptomatch": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
"integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"grammex": "^3.1.11",
"graphmatch": "^1.1.0"
}
}
}
}

View file

@ -21,6 +21,6 @@
},
"devDependencies": {
"jest": "^30.1.3",
"prisma": "^7.4.1"
"prisma": "^6.16.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 5.4 MiB

Before After
Before After

View file

@ -0,0 +1,115 @@
/* used to prevent ui flicker before vuejs loads */
[v-cloak] {
display: none;
}
.icon-longfast {
background-color: #009016;
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-mediumfast {
background-color: #326be7;
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-shortslow {
background-color: #0077e6;
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-mqtt-connected {
background-color: #2563eb; /* Change to use same color as disconnected // #16a34a; */
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-mqtt-disconnected {
background-color: #2563eb;
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-offline {
background-color: #e2286c;
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-position-history {
background-color: #a855f7;
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-traceroute-start {
background-color: #16a34a; /* green */
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.icon-traceroute-end {
background-color: #dc2626; /* red */
border-radius: 25px;
border: 1px solid #2C2D3C;
}
.waypoint-label {
font-size: 26px;
background-color: transparent;
}
.link {
color: #2563eb;
}
.link:hover {
text-decoration: underline;
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltip-text {
visibility: hidden;
width: 80px;
background-color: black;
color: #fff;
text-align: center;
padding: 4px 0;
border-radius: 6px;
position: absolute;
z-index: 10000;
top: 100%;
left: 50%;
margin-top: 8px;
margin-left: -40px; /* Use half of the width (120/2 = 60), to center the tooltip */
}
.tooltip .tooltip-text::after {
content: " ";
position: absolute;
bottom: 100%; /* At the top of the tooltip */
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent black transparent;
}
.tooltip:hover .tooltip-text {
visibility: visible;
}
.z-search {
z-index: 1001;
}
.z-sidebar {
z-index: 1002;
}

956
src/public/assets/js/app.js Normal file
View file

@ -0,0 +1,956 @@
Vue.createApp({
data() {
return {
isShowingAnnouncement: this.shouldShowAnnouncement(),
configNodesMaxAgeInSeconds: window.getConfigNodesMaxAgeInSeconds(),
configNodesOfflineAgeInSeconds: window.getConfigNodesOfflineAgeInSeconds(),
configWaypointsMaxAgeInSeconds: window.getConfigWaypointsMaxAgeInSeconds(),
configConnectionsMaxDistanceInMeters: window.getConfigConnectionsMaxDistanceInMeters(),
configZoomLevelGoToNode: window.getConfigZoomLevelGoToNode(),
configAutoUpdatePositionInUrl: window.getConfigAutoUpdatePositionInUrl(),
configEnableMapAnimations: window.getConfigEnableMapAnimations(),
configTemperatureFormat: window.getConfigTemperatureFormat(),
configConnectionsTimePeriodInSeconds: window.getConfigConnectionsTimePeriodInSeconds(),
configConnectionsColoredLines: window.getConfigConnectionsColoredLines(),
configConnectionsBidirectionalOnly: window.getConfigConnectionsBidirectionalOnly(),
configConnectionsMinSnrDb: window.getConfigConnectionsMinSnrDb(),
configConnectionsBidirectionalMinSnr: window.getConfigConnectionsBidirectionalMinSnr(),
isShowingHardwareModels: false,
hardwareModelStats: null,
isShowingInfoModal: this.shouldShowInfoModal(),
isShowingMobileSearch: false,
isShowingSettings: false,
nodes: [],
searchText: "",
selectedNode: null,
selectedNodeDeviceMetrics: [],
selectedNodeEnvironmentMetrics: [],
selectedNodePowerMetrics: [],
selectedNodeMqttMetrics: [],
selectedNodeTraceroutes: [],
deviceMetricsTimeRange: "7d",
environmentMetricsTimeRange: "7d",
powerMetricsTimeRange: "7d",
isPositionHistoryModalExpanded: true,
positionHistoryDateTimeFrom: null,
positionHistoryDateTimeTo: null,
selectedNodePositionHistory: [],
selectedNodeToShowPositionHistory: null,
selectedNodePositionHistoryMarkers: [],
selectedNodePositionHistoryPolyLines: [],
selectedTraceRoute: null,
tracerouteEdges: [],
selectedNodeToShowConnections: null,
moment: window.moment,
};
},
mounted: function() {
// load data
this.loadHardwareModelStats();
// handle map click callback from outside of vue
window._onMapClick = () => {
this.searchText = "";
this.isShowingMobileSearch = false;
};
// handle node callback from outside of vue
window._onNodeClick = (node) => {
this.selectedNode = node;
this.loadNodeDeviceMetrics(node.node_id);
this.loadNodeEnvironmentMetrics(node.node_id);
this.loadNodePowerMetrics(node.node_id);
this.loadNodeMqttMetrics(node.node_id);
this.loadNodeTraceroutes(node.node_id);
//this.loadNodePositionHistory(node.node_id);
};
// handle node callback from outside of vue
window._onShowNodeConnectionsClick = (node) => {
this.selectedNodeToShowConnections = node;
};
// handle nodes updated callback from outside of vue
window._onNodesUpdated = (nodes) => {
this.nodes = nodes;
};
},
methods: {
getAnnouncementId: function() {
// change this when making a new announcement
return "1";
},
shouldShowAnnouncement: function() {
const lastSeenAnnouncementId = window.localStorage.getItem("last-seen-announcement-id");
return lastSeenAnnouncementId?.toString() !== this.getAnnouncementId();
},
dismissAnnouncement: function() {
window.localStorage.setItem("last-seen-announcement-id", this.getAnnouncementId());
this.isShowingAnnouncement = false;
},
shouldShowInfoModal: function() {
return !window.getConfigHasSeenInfoModal()
&& !window.isMobile();
},
loadHardwareModelStats: function() {
window.axios.get('/api/v1/stats/hardware-models').then((response) => {
this.hardwareModelStats = response.data.hardware_model_stats;
}).catch((error) => {
// do nothing
});
},
loadNodeDeviceMetrics: function(nodeId) {
// calculate unix timestamps in milliseconds for supported time ranges
const oneDayAgoInMilliseconds = new Date().getTime() - (86400 * 1000);
const threeDaysAgoInMilliseconds = new Date().getTime() - (259200 * 1000);
const sevenDaysAgoInMilliseconds = new Date().getTime() - (604800 * 1000);
const thirtyDaysAgoInMilliseconds = new Date().getTime() - (259200 * 1000 * 10);
// determine how long back to load device metrics from
var timeFrom = threeDaysAgoInMilliseconds;
switch(this.deviceMetricsTimeRange){
case "1d": {
timeFrom = oneDayAgoInMilliseconds;
break;
}
case "3d": {
timeFrom = threeDaysAgoInMilliseconds;
break;
}
case "7d": {
timeFrom = sevenDaysAgoInMilliseconds;
break;
}
case "30d": {
timeFrom = thirtyDaysAgoInMilliseconds;
break;
}
}
window.axios.get(`/api/v1/nodes/${nodeId}/device-metrics`, {
params: {
time_from: timeFrom,
},
}).then((response) => {
// reverse response, as it's newest to oldest, but we want oldest to newest
this.selectedNodeDeviceMetrics = response.data.device_metrics.reverse();
this.renderDeviceMetricCharts();
}).catch(() => {
this.selectedNodeDeviceMetrics = [];
this.renderDeviceMetricCharts();
});
},
loadNodeEnvironmentMetrics: function(nodeId) {
// calculate unix timestamps in milliseconds for supported time ranges
const oneDayAgoInMilliseconds = new Date().getTime() - (86400 * 1000);
const threeDaysAgoInMilliseconds = new Date().getTime() - (259200 * 1000);
const sevenDaysAgoInMilliseconds = new Date().getTime() - (604800 * 1000);
const thirtyDaysAgoInMilliseconds = new Date().getTime() - (259200 * 1000 * 10);
// determine how long back to load environment metrics from
var timeFrom = threeDaysAgoInMilliseconds;
switch(this.environmentMetricsTimeRange){
case "1d": {
timeFrom = oneDayAgoInMilliseconds;
break;
}
case "3d": {
timeFrom = threeDaysAgoInMilliseconds;
break;
}
case "7d": {
timeFrom = sevenDaysAgoInMilliseconds;
break;
}
case "30d": {
timeFrom = thirtyDaysAgoInMilliseconds;
break;
}
}
window.axios.get(`/api/v1/nodes/${nodeId}/environment-metrics`, {
params: {
time_from: timeFrom,
},
}).then((response) => {
// reverse response, as it's newest to oldest, but we want oldest to newest
this.selectedNodeEnvironmentMetrics = response.data.environment_metrics.reverse();
this.renderEnvironmentMetricCharts();
}).catch(() => {
this.selectedNodeEnvironmentMetrics = [];
this.renderEnvironmentMetricCharts();
});
},
loadNodePowerMetrics: function(nodeId) {
// calculate unix timestamps in milliseconds for supported time ranges
const oneDayAgoInMilliseconds = new Date().getTime() - (86400 * 1000);
const threeDaysAgoInMilliseconds = new Date().getTime() - (259200 * 1000);
const sevenDaysAgoInMilliseconds = new Date().getTime() - (604800 * 1000);
const thirtyDaysAgoInMilliseconds = new Date().getTime() - (259200 * 1000 * 10);
// determine how long back to load power metrics from
var timeFrom = threeDaysAgoInMilliseconds;
switch(this.powerMetricsTimeRange){
case "1d": {
timeFrom = oneDayAgoInMilliseconds;
break;
}
case "3d": {
timeFrom = threeDaysAgoInMilliseconds;
break;
}
case "7d": {
timeFrom = sevenDaysAgoInMilliseconds;
break;
}
case "30d": {
timeFrom = thirtyDaysAgoInMilliseconds;
break;
}
}
window.axios.get(`/api/v1/nodes/${nodeId}/power-metrics`, {
params: {
time_from: timeFrom,
},
}).then((response) => {
// reverse response, as it's newest to oldest, but we want oldest to newest
this.selectedNodePowerMetrics = response.data.power_metrics.reverse();
this.renderPowerMetricCharts();
}).catch(() => {
this.selectedNodePowerMetrics = [];
this.renderPowerMetricCharts();
});
},
loadNodeMqttMetrics: function(nodeId) {
this.selectedNodeMqttMetrics = [];
window.axios.get(`/api/v1/nodes/${nodeId}/mqtt-metrics`).then((response) => {
this.selectedNodeMqttMetrics = response.data.mqtt_metrics;
}).catch(() => {
// do nothing
});
},
loadNodeTraceroutes: function(nodeId) {
this.selectedNodeTraceroutes = [];
window.axios.get(`/api/v1/nodes/${nodeId}/traceroutes`, {
params: {
count: 5,
},
}).then((response) => {
this.selectedNodeTraceroutes = response.data.traceroutes;
}).catch(() => {
// do nothing
});
},
loadNodePositionHistory: function(nodeId) {
this.selectedNodePositionHistory = [];
window.axios.get(`/api/v1/nodes/${nodeId}/position-history`, {
params: {
// parse from datetime-local format, and send as unix timestamp in milliseconds
time_from: moment(this.positionHistoryDateTimeFrom, "YYYY-MM-DDTHH:mm").format("x"),
time_to: moment(this.positionHistoryDateTimeTo, "YYYY-MM-DDTHH:mm").format("x"),
},
}).then((response) => {
this.selectedNodePositionHistory = response.data.position_history;
if(this.selectedNodeToShowPositionHistory != null){
clearAllPositionHistory();
onPositionHistoryUpdated(response.data.position_history);
}
}).catch(() => {
// do nothing
});
},
renderDeviceMetricCharts: function() {
try {
this.updateDeviceMetricsChart();
} catch(e) {
console.log(e);
}
},
updateDeviceMetricsChart: function() {
// destroy existing chart
const chartElementId = "deviceMetricsChart";
const existingChart = window.Chart.getChart(chartElementId);
if(existingChart != null){
existingChart.destroy();
}
// get chart element
const chartElement = window.document.getElementById(chartElementId);
if(!chartElement){
return;
}
// create chart data
const labels = [];
const batteryMetrics = [];
const channelUtilizationMetrics = [];
const airUtilTxMetrics = [];
for(const deviceMetric of this.selectedNodeDeviceMetrics){
labels.push(moment(deviceMetric.created_at));
batteryMetrics.push(deviceMetric.battery_level);
channelUtilizationMetrics.push(deviceMetric.channel_utilization);
airUtilTxMetrics.push(deviceMetric.air_util_tx);
}
// create chart
new window.Chart(chartElement, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Battery Level',
borderColor: '#3b82f6',
backgroundColor: '#3b82f6',
pointStyle: false, // no points
fill: false,
data: batteryMetrics,
},
{
label: 'Channel Util',
borderColor: '#22c55e',
backgroundColor: '#22c55e',
showLine: false, // no lines between points
fill: false,
data: channelUtilizationMetrics,
},
{
label: 'Air Util TX',
borderColor: '#f97316',
backgroundColor: '#f97316',
showLine: false, // no lines between points
fill: false,
data: airUtilTxMetrics,
},
],
},
options: {
responsive: true,
borderWidth: 2,
elements: {
point: {
radius: 2,
},
},
scales: {
x: {
position: 'top',
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MMM DD', // Jan 01
},
},
},
y: {
min: 0,
max: 101, // 101 is "Plugged In", need to include for tooltip to work
ticks: {
callback: (label) => `${label}%`,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
mode: "index",
intersect: false,
callbacks: {
label: (item) => {
return `${item.dataset.label}: ${item.formattedValue}%`;
},
},
},
},
}
});
},
renderEnvironmentMetricCharts: function() {
try {
this.updateEnvironmentMetricsChart();
} catch(e) {
console.log(e);
}
},
updateEnvironmentMetricsChart: function() {
// destroy existing chart
const chartElementId = "environmentMetricsChart";
const existingChart = window.Chart.getChart(chartElementId);
if(existingChart != null){
existingChart.destroy();
}
// get chart element
const chartElement = window.document.getElementById(chartElementId);
if(!chartElement){
return;
}
// create chart data
const labels = [];
const temperatureMetrics = [];
const relativeHumidityMetrics = [];
const barometricPressureMetrics = [];
const iaqMetrics = [];
for(const deviceMetric of this.selectedNodeEnvironmentMetrics){
labels.push(moment(deviceMetric.created_at));
temperatureMetrics.push(deviceMetric.temperature);
relativeHumidityMetrics.push(deviceMetric.relative_humidity);
barometricPressureMetrics.push(deviceMetric.barometric_pressure);
iaqMetrics.push(deviceMetric.iaq);
}
// create chart
new window.Chart(chartElement, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Temperature',
suffix: '°C',
borderColor: '#3b82f6',
backgroundColor: '#3b82f6',
pointStyle: false, // no points
fill: false,
data: temperatureMetrics,
yAxisID: 'y',
},
{
label: 'Humidity',
suffix: '%',
borderColor: '#22c55e',
backgroundColor: '#22c55e',
pointStyle: false, // no points
fill: false,
data: relativeHumidityMetrics,
yAxisID: 'y',
},
{
label: 'Pressure',
suffix: 'hPa',
borderColor: '#f97316',
backgroundColor: '#f97316',
pointStyle: false, // no points
fill: false,
data: barometricPressureMetrics,
yAxisID: 'y1',
},
{
label: 'IAQ',
suffix: 'IAQ',
borderColor: '#f472b6',
backgroundColor: '#f472b6',
pointStyle: false, // no points
fill: false,
data: iaqMetrics,
yAxisID: 'yIAQ',
},
],
},
options: {
responsive: true,
borderWidth: 2,
spanGaps: 1000 * 60 * 60 * 24, // only show lines between metrics with a 24 hour or less gap
elements: {
point: {
radius: 2,
},
},
scales: {
x: {
position: 'top',
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MMM DD', // Jan 01
},
},
},
y: {
min: -20,
max: 100,
},
y1: {
min: 800,
max: 1100,
ticks: {
stepSize: 10,
callback: (label) => `${label} hPa`,
},
position: 'right',
grid: {
drawOnChartArea: false, // only want the grid lines for one axis to show up
},
},
yIAQ: {
type: 'linear',
display: false,
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
mode: "index",
intersect: false,
callbacks: {
label: (item) => {
return `${item.dataset.label}: ${item.formattedValue}${item.dataset.suffix}`;
},
},
},
},
}
});
},
renderPowerMetricCharts: function() {
try {
this.updatePowerMetricsChart();
} catch(e) {
console.log(e);
}
},
updatePowerMetricsChart: function() {
// destroy existing chart
const chartElementId = "powerMetricsChart";
const existingChart = window.Chart.getChart(chartElementId);
if(existingChart != null){
existingChart.destroy();
}
// get chart element
const chartElement = window.document.getElementById(chartElementId);
if(!chartElement){
return;
}
// create chart data
const labels = [];
const channel1VoltageReadings = [];
const channel2VoltageReadings = [];
const channel3VoltageReadings = [];
const channel1CurrentReadings = [];
const channel2CurrentReadings = [];
const channel3CurrentReadings = [];
for(const powerMetric of this.selectedNodePowerMetrics){
labels.push(moment(powerMetric.created_at));
channel1VoltageReadings.push(powerMetric.ch1_voltage);
channel2VoltageReadings.push(powerMetric.ch2_voltage);
channel3VoltageReadings.push(powerMetric.ch3_voltage);
channel1CurrentReadings.push(powerMetric.ch1_current);
channel2CurrentReadings.push(powerMetric.ch2_current);
channel3CurrentReadings.push(powerMetric.ch3_current);
}
// create chart
new window.Chart(chartElement, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Ch1 Voltage',
suffix: "V",
borderColor: '#3b82f6',
backgroundColor: '#3b82f6',
pointStyle: false, // no points
fill: false,
data: channel1VoltageReadings,
yAxisID: 'y',
},
{
label: 'Ch2 Voltage',
suffix: "V",
borderColor: '#22c55e',
backgroundColor: '#22c55e',
pointStyle: false, // no points
fill: false,
data: channel2VoltageReadings,
yAxisID: 'y',
},
{
label: 'Ch3 Voltage',
suffix: "V",
borderColor: '#f97316',
backgroundColor: '#f97316',
pointStyle: false, // no points
fill: false,
data: channel3VoltageReadings,
yAxisID: 'y',
},
{
label: 'Ch1 Current',
suffix: "mA",
borderColor: '#93c5fd',
backgroundColor: '#93c5fd',
pointStyle: false, // no points
fill: false,
data: channel1CurrentReadings,
yAxisID: 'y1',
},
{
label: 'Ch2 Current',
suffix: "mA",
borderColor: '#86efac',
backgroundColor: '#86efac',
pointStyle: false, // no points
fill: false,
data: channel2CurrentReadings,
yAxisID: 'y1',
},
{
label: 'Ch3 Current',
suffix: "mA",
borderColor: '#fdba74',
backgroundColor: '#fdba74',
pointStyle: false, // no points
fill: false,
data: channel3CurrentReadings,
yAxisID: 'y1',
},
],
},
options: {
responsive: true,
borderWidth: 2,
spanGaps: 1000 * 60 * 60 * 3, // only show lines between metrics with a 3 hour or less gap
elements: {
point: {
radius: 2,
},
},
scales: {
x: {
position: 'top',
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MMM DD', // Jan 01
},
},
},
y: {
min: 0,
suggestedMax: 6,
ticks: {
callback: (label) => `${label}V`,
},
},
y1: {
suggestedMin: -50,
suggestedMax: 50,
ticks: {
stepSize: 50,
callback: (label) => `${label}mA`,
},
position: 'right',
grid: {
drawOnChartArea: false, // only want the grid lines for one axis to show up
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
mode: "index",
intersect: false,
callbacks: {
label: (item) => {
return `${item.dataset.label}: ${item.formattedValue}${item.dataset.suffix}`;
},
},
},
},
}
});
},
showTraceRoute: function(traceroute) {
this.selectedTraceRoute = traceroute;
},
findNodeById: function(id) {
return window.findNodeById(id);
},
findNodeMarkerById: function(id) {
return window.findNodeMarkerById(id);
},
onSearchResultNodeClick: function(node) {
// clear search
this.searchText = "";
// hide search
this.isShowingMobileSearch = false;
// go to node
if(window.goToNode(node.node_id)){
return;
}
// fallback to showing node details since we can't go to the node
window.showNodeDetails(node.node_id);
},
dismissInfoModal: function() {
this.isShowingInfoModal = false;
window.setConfigHasSeenInfoModal(true);
},
getRegionFrequencyRange: function(regionName) {
return window.getRegionFrequencyRange(regionName);
},
showNodePositionHistory: function(nodeId) {
// find node
const node = findNodeById(nodeId);
if(!node){
return;
}
// update ui
this.selectedNode = null;
this.selectedNodeToShowPositionHistory = node;
this.isPositionHistoryModalExpanded = true;
// close node info tooltip as position history shows under it
window.closeAllTooltips();
// reset default time range when opening position history ui
// YYYY-MM-DDTHH:mm is the format expected by the datetime-local input type
this.positionHistoryDateTimeFrom = moment().subtract(1, "hours").format('YYYY-MM-DDTHH:mm');
this.positionHistoryDateTimeTo = moment().format('YYYY-MM-DDTHH:mm');
// load position history
this.loadNodePositionHistory(nodeId);
},
onPositionHistoryQuickRangeClick: function(range) {
// update position history time range
switch(range){
case "1h": {
this.positionHistoryDateTimeFrom = moment().subtract(1, "hours").format('YYYY-MM-DDTHH:mm');
this.positionHistoryDateTimeTo = moment().format('YYYY-MM-DDTHH:mm');
break;
}
case "24h": {
this.positionHistoryDateTimeFrom = moment().subtract(24, "hours").format('YYYY-MM-DDTHH:mm');
this.positionHistoryDateTimeTo = moment().format('YYYY-MM-DDTHH:mm');
break;
}
case "7d": {
this.positionHistoryDateTimeFrom = moment().subtract(7, "days").format('YYYY-MM-DDTHH:mm');
this.positionHistoryDateTimeTo = moment().format('YYYY-MM-DDTHH:mm');
break;
}
}
// reload position history
const node = this.selectedNodeToShowPositionHistory;
if(node){
this.loadNodePositionHistory(node.node_id);
}
},
getShareLinkForNode: function(nodeId) {
return window.location.origin + `/?node_id=${nodeId}`;
},
copyShareLinkForNode: function(nodeId) {
// make sure copy to clipboard is supported
if(!navigator.clipboard || !navigator.clipboard.writeText){
alert("Clipboard not supported. Site must be served via https on iOS.");
return;
}
// copy share link to clipboard
const url = this.getShareLinkForNode(nodeId);
navigator.clipboard.writeText(url);
// tell user we copied it
alert("Link copied to clipboard!");
},
dismissShowingNodeConnections: function() {
window._onHideNodeConnectionsClick();
this.selectedNodeToShowConnections = null;
},
dismissShowingNodePositionHistory: function() {
this.selectedNodePositionHistory = [];
this.selectedNodeToShowPositionHistory = null;
this.selectedNodePositionHistoryMarkers = [];
this.selectedNodePositionHistoryPolyLines = [];
cleanUpPositionHistory();
},
formatUptimeSeconds: function(secondsToFormat) {
secondsToFormat = Number(secondsToFormat);
var days = Math.floor(secondsToFormat / (3600 * 24));
var hours = Math.floor((secondsToFormat % (3600 * 24)) / 3600);
var minutes = Math.floor((secondsToFormat % 3600) / 60);
var seconds = Math.floor(secondsToFormat % 60);
var daysPlural = days === 1 ? 'day' : 'days';
return `${days} ${daysPlural} ${hours}h ${minutes}m ${seconds}s`;
},
formatTemperature: function(celsius) {
switch(this.configTemperatureFormat){
case "celsius": {
return `${Number(celsius).toFixed(0)}°C`;
}
case "fahrenheit": {
const fahrenheit = this.celsiusToFahrenheit(celsius);
return `${fahrenheit.toFixed(0)}°F`;
}
}
},
convertTemperature: function(celsius) {
switch(this.configTemperatureFormat){
case "celsius": {
return celsius;
}
case "fahrenheit": {
return this.celsiusToFahrenheit(celsius);
}
}
},
getTemperatureUnit: function() {
switch(this.configTemperatureFormat){
case "celsius": return "°C";
case "fahrenheit": return "°F";
}
},
celsiusToFahrenheit: function(celsius) {
return (celsius * 9/5) + 32;
},
getNodeColour(nodeId) {
// convert node id to a hex colour
return "#" + (nodeId & 0x00FFFFFF).toString(16).padStart(6, '0');
},
getNodeTextColour(nodeId) {
// extract rgb components
const r = (nodeId & 0xFF0000) >> 16;
const g = (nodeId & 0x00FF00) >> 8;
const b = nodeId & 0x0000FF;
// calculate brightness
const brightness = ((r * 0.299) + (g * 0.587) + (b * 0.114)) / 255;
// determine text color based on brightness
return brightness > 0.5 ? "#000000" : "#FFFFFF";
},
},
computed: {
searchedNodes() {
// search nodes
const nodes = this.nodes.filter((node) => {
const matchesId = node.node_id?.toLowerCase()?.includes(this.searchText.toLowerCase());
const matchesHexId = node.node_id_hex?.toLowerCase()?.includes(this.searchText.toLowerCase());
const matchesLongName = node.long_name?.toLowerCase()?.includes(this.searchText.toLowerCase());
const matchesShortName = node.short_name?.toLowerCase()?.includes(this.searchText.toLowerCase());
return matchesId || matchesHexId || matchesLongName || matchesShortName;
});
// order alphabetically by long name
nodes.sort((nodeA, nodeB) => {
const nodeALongName = nodeA.long_name || "";
const nodeBLongName = nodeB.long_name || "";
return nodeALongName.localeCompare(nodeBLongName);
});
// only return the first 500 results to avoid ui lag...
return nodes.slice(0, 500);
},
selectedNodeLatestPowerMetric() {
const [ latestPowerMetric ] = this.selectedNodePowerMetrics.slice(-1);
return latestPowerMetric;
},
},
watch: {
configNodesMaxAgeInSeconds() {
window.setConfigNodesMaxAgeInSeconds(this.configNodesMaxAgeInSeconds);
},
configNodesOfflineAgeInSeconds() {
window.setConfigNodesOfflineAgeInSeconds(this.configNodesOfflineAgeInSeconds);
},
configWaypointsMaxAgeInSeconds() {
window.setConfigWaypointsMaxAgeInSeconds(this.configWaypointsMaxAgeInSeconds);
},
configConnectionsMaxDistanceInMeters() {
window.setConfigConnectionsMaxDistanceInMeters(this.configConnectionsMaxDistanceInMeters);
},
configZoomLevelGoToNode() {
window.setConfigZoomLevelGoToNode(this.configZoomLevelGoToNode);
},
configAutoUpdatePositionInUrl() {
window.setConfigAutoUpdatePositionInUrl(this.configAutoUpdatePositionInUrl);
},
configEnableMapAnimations() {
window.setConfigEnableMapAnimations(this.configEnableMapAnimations);
},
configTemperatureFormat() {
window.setConfigTemperatureFormat(this.configTemperatureFormat);
},
configConnectionsTimePeriodInSeconds() {
window.setConfigConnectionsTimePeriodInSeconds(this.configConnectionsTimePeriodInSeconds);
},
configConnectionsColoredLines() {
window.setConfigConnectionsColoredLines(this.configConnectionsColoredLines);
},
configConnectionsBidirectionalOnly() {
window.setConfigConnectionsBidirectionalOnly(this.configConnectionsBidirectionalOnly);
},
configConnectionsMinSnrDb() {
window.setConfigConnectionsMinSnrDb(this.configConnectionsMinSnrDb);
},
configConnectionsBidirectionalMinSnr() {
window.setConfigConnectionsBidirectionalMinSnr(this.configConnectionsBidirectionalMinSnr);
},
deviceMetricsTimeRange() {
this.loadNodeDeviceMetrics(this.selectedNode.node_id);
},
environmentMetricsTimeRange() {
this.loadNodeEnvironmentMetrics(this.selectedNode.node_id);
},
powerMetricsTimeRange() {
this.loadNodePowerMetrics(this.selectedNode.node_id);
},
},
}).mount('#app');

View file

@ -0,0 +1,199 @@
function getConfigHasSeenInfoModal() {
return localStorage.getItem("config_has_seen_info_modal") === "true";
}
function setConfigHasSeenInfoModal(value) {
return localStorage.setItem("config_has_seen_info_modal", value);
}
function getConfigAutoUpdatePositionInUrl() {
// use user preference, or enable by default
const value = localStorage.getItem("config_auto_update_position_in_url");
return value === "true" || value == null;
}
function setConfigAutoUpdatePositionInUrl(value) {
return localStorage.setItem("config_auto_update_position_in_url", value);
}
function getConfigEnableMapAnimations() {
const value = localStorage.getItem("config_enable_map_animations");
// enable animations by default
if(value === null){
return true;
}
return value === "true";
}
function setConfigEnableMapAnimations(value) {
return localStorage.setItem("config_enable_map_animations", value);
}
function getConfigTemperatureFormat() {
return localStorage.getItem("config_temperature_format") || "celsius";
}
function setConfigTemperatureFormat(format) {
return localStorage.setItem("config_temperature_format", format);
}
function getConfigMapSelectedTileLayer() {
return localStorage.getItem("config_map_selected_tile_layer") || "Thunderforest Neighbourhood";
}
function setConfigMapSelectedTileLayer(layer) {
return localStorage.setItem("config_map_selected_tile_layer", layer);
}
function getConfigMapEnabledOverlayLayers() {
try {
const value = localStorage.getItem("config_map_enabled_overlay_layers");
if(value){
return JSON.parse(value);
}
} catch(e) {}
// overlays enabled by default
return ["Legend", "Position History", "Traceroutes"];
}
function setConfigMapEnabledOverlayLayers(layers) {
return localStorage.setItem("config_map_enabled_overlay_layers", JSON.stringify(layers));
}
function getConfigNodesMaxAgeInSeconds() {
const value = localStorage.getItem("config_nodes_max_age_in_seconds");
return value != null ? parseInt(value) : null;
}
function setConfigNodesMaxAgeInSeconds(value) {
if(value != null){
return localStorage.setItem("config_nodes_max_age_in_seconds", value);
} else {
return localStorage.removeItem("config_nodes_max_age_in_seconds");
}
}
function getConfigNodesOfflineAgeInSeconds() {
const value = localStorage.getItem("config_nodes_offline_age_in_seconds");
return value != null ? parseInt(value) : 10800;
}
function setConfigNodesOfflineAgeInSeconds(value) {
if(value != null){
return localStorage.setItem("config_nodes_offline_age_in_seconds", value);
} else {
return localStorage.removeItem("config_nodes_offline_age_in_seconds");
}
}
function getConfigWaypointsMaxAgeInSeconds() {
const value = localStorage.getItem("config_waypoints_max_age_in_seconds");
return value != null ? parseInt(value) : null;
}
function setConfigWaypointsMaxAgeInSeconds(value) {
if(value != null){
return localStorage.setItem("config_waypoints_max_age_in_seconds", value);
} else {
return localStorage.removeItem("config_waypoints_max_age_in_seconds");
}
}
function getConfigConnectionsMaxDistanceInMeters() {
const value = localStorage.getItem("config_connections_max_distance_in_meters");
// default to 70km (70,000 meters)
return value != null ? parseInt(value) : 70000;
}
function setConfigConnectionsMaxDistanceInMeters(value) {
return localStorage.setItem("config_connections_max_distance_in_meters", value);
}
function getConfigZoomLevelGoToNode() {
const value = localStorage.getItem("config_zoom_level_go_to_node");
const parsedValue = value != null ? parseInt(value) : null;
return parsedValue || 15;
}
function setConfigZoomLevelGoToNode(value) {
return localStorage.setItem("config_zoom_level_go_to_node", value);
}
function getConfigConnectionsTimePeriodInSeconds() {
const value = localStorage.getItem("config_connections_time_period_in_seconds");
// default to 7 days if unset
return value != null ? parseInt(value) : 604800;
}
function setConfigConnectionsTimePeriodInSeconds(value) {
return localStorage.setItem("config_connections_time_period_in_seconds", value);
}
function getConfigConnectionsColoredLines() {
const value = localStorage.getItem("config_connections_colored_lines");
// disable colored lines by default
if(value === null){
return false;
}
return value === "true";
}
function setConfigConnectionsColoredLines(value) {
return localStorage.setItem("config_connections_colored_lines", value);
}
function getConfigConnectionsBidirectionalOnly() {
const value = localStorage.getItem("config_connections_bidirectional_only");
// disable bidirectional filter by default
if(value === null){
return false;
}
return value === "true";
}
function setConfigConnectionsBidirectionalOnly(value) {
return localStorage.setItem("config_connections_bidirectional_only", value);
}
function getConfigConnectionsMinSnrDb() {
const value = localStorage.getItem("config_connections_min_snr_db");
// default to null (unset)
if(value === null || value === ""){
return null;
}
const parsed = parseFloat(value);
return isNaN(parsed) ? null : parsed;
}
function setConfigConnectionsMinSnrDb(value) {
if(value === null || value === "" || value === undefined){
return localStorage.removeItem("config_connections_min_snr_db");
}
// Convert to string for localStorage (handles both number and string inputs)
const stringValue = typeof value === "number" ? value.toString() : String(value);
return localStorage.setItem("config_connections_min_snr_db", stringValue);
}
function getConfigConnectionsBidirectionalMinSnr() {
const value = localStorage.getItem("config_connections_bidirectional_min_snr");
// disable bidirectional minimum SNR by default
if(value === null){
return false;
}
return value === "true";
}
function setConfigConnectionsBidirectionalMinSnr(value) {
return localStorage.setItem("config_connections_bidirectional_min_snr", value);
}
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}

1692
src/public/assets/js/map.js Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

File diff suppressed because it is too large Load diff