code formatting and remove regex

This commit is contained in:
Maxi Quoß
2022-12-04 10:53:11 +01:00
parent f5a9a480d4
commit 375abd0c42
7 changed files with 962 additions and 406 deletions

View File

@@ -9,6 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.0.0",
"@popperjs/core": "^2.11.5",
"@rollup/plugin-replace": "^4.0.0",
"bootstrap": "^5.1.3",
"dotenv": "^16.0.0",
@@ -42,9 +43,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -65,9 +66,9 @@
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz",
"integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz",
"integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
@@ -122,13 +123,13 @@
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"node_modules/@nodelib/fs.scandir": {
@@ -172,10 +173,9 @@
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g=="
},
"node_modules/@popperjs/core": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
"peer": true,
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
@@ -280,15 +280,15 @@
}
},
"node_modules/@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
"dev": true
},
"node_modules/@types/node": {
"version": "18.0.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.6.tgz",
"integrity": "sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw=="
"version": "18.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz",
"integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ=="
},
"node_modules/@types/pug": {
"version": "2.0.6",
@@ -313,9 +313,9 @@
}
},
"node_modules/acorn": {
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -337,9 +337,9 @@
}
},
"node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -371,9 +371,9 @@
}
},
"node_modules/bootstrap": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz",
"integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz",
"integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==",
"funding": [
{
"type": "github",
@@ -385,7 +385,7 @@
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.5"
"@popperjs/core": "^2.11.6"
}
},
"node_modules/brace-expansion": {
@@ -550,9 +550,9 @@
}
},
"node_modules/dotenv": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
"engines": {
"node": ">=12"
}
@@ -578,9 +578,9 @@
"dev": true
},
"node_modules/fast-glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -594,9 +594,9 @@
}
},
"node_modules/fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
@@ -735,9 +735,9 @@
}
},
"node_modules/ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
"integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
"dev": true,
"engines": {
"node": ">= 4"
@@ -774,9 +774,9 @@
}
},
"node_modules/is-core-module": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
"integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
@@ -982,9 +982,12 @@
}
},
"node_modules/minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
@@ -1086,9 +1089,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
"funding": [
{
"type": "opencollective",
@@ -1193,9 +1196,9 @@
}
},
"node_modules/rollup": {
"version": "2.77.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.0.tgz",
"integrity": "sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g==",
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -1283,6 +1286,7 @@
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
"deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
@@ -1375,9 +1379,9 @@
}
},
"node_modules/sass": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.0.tgz",
"integrity": "sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz",
"integrity": "sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@@ -1494,7 +1498,8 @@
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead"
},
"node_modules/strip-indent": {
"version": "3.0.0",
@@ -1532,9 +1537,9 @@
}
},
"node_modules/svelte": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==",
"version": "3.53.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz",
"integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw==",
"engines": {
"node": ">= 8"
}
@@ -1605,9 +1610,9 @@
}
},
"node_modules/terser": {
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
"integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
@@ -1696,9 +1701,9 @@
}
},
"@babel/helper-validator-identifier": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/highlight": {
@@ -1713,9 +1718,9 @@
}
},
"@fortawesome/fontawesome-free": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz",
"integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg=="
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz",
"integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A=="
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
@@ -1757,13 +1762,13 @@
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"@nodelib/fs.scandir": {
@@ -1798,10 +1803,9 @@
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g=="
},
"@popperjs/core": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
"peer": true
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
},
"@rollup/plugin-commonjs": {
"version": "17.1.0",
@@ -1883,15 +1887,15 @@
}
},
"@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
"dev": true
},
"@types/node": {
"version": "18.0.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.6.tgz",
"integrity": "sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw=="
"version": "18.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz",
"integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ=="
},
"@types/pug": {
"version": "2.0.6",
@@ -1916,9 +1920,9 @@
}
},
"acorn": {
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true
},
"ansi-styles": {
@@ -1931,9 +1935,9 @@
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -1956,9 +1960,9 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
},
"bootstrap": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz",
"integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz",
"integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==",
"requires": {}
},
"brace-expansion": {
@@ -2085,9 +2089,9 @@
}
},
"dotenv": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
},
"es6-promise": {
"version": "3.3.1",
@@ -2107,9 +2111,9 @@
"dev": true
},
"fast-glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
@@ -2120,9 +2124,9 @@
}
},
"fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
@@ -2227,9 +2231,9 @@
"dev": true
},
"ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
"integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
"dev": true
},
"immutable": {
@@ -2260,9 +2264,9 @@
}
},
"is-core-module": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
"integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"requires": {
"has": "^1.0.3"
@@ -2422,9 +2426,9 @@
}
},
"minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
},
"mkdirp": {
"version": "0.5.6",
@@ -2496,9 +2500,9 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
@@ -2560,9 +2564,9 @@
}
},
"rollup": {
"version": "2.77.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.0.tgz",
"integrity": "sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g==",
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"requires": {
"fsevents": "~2.3.2"
}
@@ -2684,9 +2688,9 @@
}
},
"sass": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.0.tgz",
"integrity": "sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz",
"integrity": "sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==",
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@@ -2799,9 +2803,9 @@
"dev": true
},
"svelte": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.49.0.tgz",
"integrity": "sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA=="
"version": "3.53.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz",
"integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw=="
},
"svelte-preprocess": {
"version": "4.10.7",
@@ -2817,9 +2821,9 @@
}
},
"terser": {
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
"integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",

View File

@@ -3,7 +3,7 @@
/** This script modifies the project to support TS code in .svelte files like:
<script lang="ts">
export let name: string;
export let name: string;
</script>
As well as validating the code for CI.
@@ -82,11 +82,11 @@ const tsconfig = `{
"include": ["src/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}`
const tsconfigPath = path.join(projectRoot, "tsconfig.json")
const tsconfigPath = path.join(projectRoot, "tsconfig.json")
fs.writeFileSync(tsconfigPath, tsconfig)
// Add global.d.ts
const dtsPath = path.join(projectRoot, "src", "global.d.ts")
const dtsPath = path.join(projectRoot, "src", "global.d.ts")
fs.writeFileSync(dtsPath, `/// <reference types="svelte" />`)
// Delete this script, but not during testing

View File

@@ -1,48 +1,58 @@
<script>
import { onMount } from 'svelte';
import socketStore from './socketStore.js';
import { onMount } from "svelte";
import socketStore from "./socketStore.js";
import Navbar from "./components/Navbar.svelte";
import DeviceCard from "./components/DeviceCard.svelte";
import Toast from "./components/Toast.svelte";
let visitors = 0;
let devices = [];
let settings = {}
let settings = {};
let toast = {
title: "",
message: "",
color: "",
show: false
}
show: false,
};
onMount(() => {
socketStore.subscribeStatus(status => {
socketStore.subscribeStatus((status) => {
if (status == "open") {
showToast("Websocket", "Connected", "success")
showToast("Websocket", "Connected", "success");
} else if (status == "close") {
showToast("Websocket", "Connection closed. Trying to reconnect ...", "danger")
showToast(
"Websocket",
"Connection closed. Trying to reconnect ...",
"danger"
);
} else if (status == "error") {
showToast("Websocket", "Error when connecting to websocket", "danger")
showToast(
"Websocket",
"Error when connecting to websocket",
"danger"
);
}
})
});
socketStore.subscribeMsg(currentMessage => {
socketStore.subscribeMsg((currentMessage) => {
if (currentMessage.type == "init") {
// create devices
devices = [...currentMessage.message.devices]
devices = [...currentMessage.message.devices];
devices = devices;
devices.sort(compare);
settings = currentMessage.message.settings;
} else if (currentMessage.type == "status") {
// set device array and sort
const index = devices.findIndex(x => x.id == currentMessage.message.id);
const index = devices.findIndex(
(x) => x.id == currentMessage.message.id
);
if (devices.length === 0 || index === -1) {
devices.push(currentMessage.message);
devices = devices;
} else {
devices[index] = currentMessage.message;
}
devices.sort(compare)
devices.sort(compare);
// set device status
if (currentMessage.message.up == true) {
setUp(currentMessage.message);
@@ -51,22 +61,25 @@
}
} else if (currentMessage.type == "pending") {
// set device pending
setPending(currentMessage.message)
setPending(currentMessage.message);
} else if (currentMessage.type == "visitor") {
// update visitor count
visitors = currentMessage.message;
} else if (currentMessage.type == "delete") {
// delete device
const devCol = document.querySelector(`#device-col-${currentMessage.message}`);
const devCol = document.querySelector(
`#device-col-${currentMessage.message}`
);
devCol.remove();
} else if (currentMessage.type == "scan_network") {
// set scanned network devices
if (!currentMessage.message) {
return
return;
}
settings["scan_network"] = currentMessage.message;
const btnScan = document.querySelector("#btnScan");
const btnScanSpinner = document.querySelector("#btnScanSpinner");
const btnScanSpinner =
document.querySelector("#btnScanSpinner");
const btnScanText = document.querySelector("#btnScanText");
btnScan.disabled = false;
btnScanSpinner.classList.add("d-none");
@@ -74,28 +87,39 @@
} else if (currentMessage.type == "backup") {
// download backup file
const now = new Date();
const fileName = `upsnap_backup_${now.toISOString()}.json`
const fileName = `upsnap_backup_${now.toISOString()}.json`;
const a = document.createElement("a");
const file = new Blob([JSON.stringify(currentMessage.message)], { type: "text/plain" });
const file = new Blob(
[JSON.stringify(currentMessage.message)],
{ type: "text/plain" }
);
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
} else if (currentMessage.type == "operationStatus") {
if (currentMessage.message == "Success") {
showToast(currentMessage.message, "Changes were saved", "success")
showToast(
currentMessage.message,
"Changes were saved",
"success"
);
} else if (currentMessage.message == "Error") {
showToast(currentMessage.message, "Error while saving the device. Please check the logs.", "danger")
showToast(
currentMessage.message,
"Error while saving the device. Please check the logs.",
"danger"
);
}
}
})
})
});
});
function setUp(device) {
const dot = document.querySelector(`#dot-${device.id}`)
const dot = document.querySelector(`#dot-${device.id}`);
const spinner = document.querySelector(`#spinner-${device.id}`);
if (dot) {
if (dot.classList.contains("danger")) {
showToast(device.name, "Device is up!", "success")
showToast(device.name, "Device is up!", "success");
}
dot.style.animation = "none";
dot.offsetWidth;
@@ -110,11 +134,11 @@
}
function setDown(device) {
const dot = document.querySelector(`#dot-${device.id}`)
const dot = document.querySelector(`#dot-${device.id}`);
const spinner = document.querySelector(`#spinner-${device.id}`);
if (dot) {
if (dot.classList.contains("success")) {
showToast(device.name, "Device is down!", "danger")
showToast(device.name, "Device is down!", "danger");
}
dot.style.animation = "none";
dot.offsetWidth;
@@ -137,7 +161,7 @@
function showToast(title, message, color) {
if (settings.notifications === false) {
return
return;
}
toast.title = title;
toast.message = message;
@@ -145,31 +169,30 @@
toast.show = true;
setTimeout(() => {
toast.show = false;
}, 4000)
}, 4000);
}
function compare(a, b) {
if (a.name < b.name){
if (a.name < b.name) {
return -1;
}
if (a.name > b.name){
if (a.name > b.name) {
return 1;
}
return 0;
}
</script>
<main>
<Navbar settings={settings} visitors={visitors} />
<Navbar {settings} {visitors} />
<div class="container mb-3">
<div class="row">
{#each devices as device}
<DeviceCard device={device}/>
<DeviceCard {device} />
{/each}
</div>
</div>
<Toast toast={toast} />
<Toast {toast} />
</main>
<style lang="scss" global>
@@ -201,29 +224,30 @@
border-top: 1px solid var(--color-text);
}
.btn, button {
.btn,
button {
&.btn-light {
color: var(--color-text);
background-color: var(--bg-lighter);
border-width: 0px;
}
&.btn-outline-success {
border-color: $success;
&:hover {
background-color: $success;
}
}
&.btn-outline-success {
border-color: $success;
&:hover {
background-color: $success;
}
}
&.btn-outline-danger {
border-color: $danger;
&:hover {
background-color: $danger;
}
}
}
&.btn-outline-danger {
border-color: $danger;
&:hover {
background-color: $danger;
}
}
}
.form-control{
.form-control {
&:focus {
border-color: inherit;
box-shadow: none;
@@ -231,48 +255,48 @@
}
.callout {
padding: 1rem;
border-left-width: 0.25rem;
border-radius: 0.25rem;
padding: 1rem;
border-left-width: 0.25rem;
border-radius: 0.25rem;
&.callout-info {
&.callout-info {
background-color: var(--info-dark-transparent);
border: 1px solid var(--info-dark-transparent);
border-left: 5px solid var(--info);
}
}
&.callout-danger {
&.callout-danger {
background-color: var(--danger-dark-transparent);
border: 1px solid var(--danger-dark-transparent);
border-left: 5px solid var(--danger);
}
}
}
}
@keyframes on-pulse {
0% {
box-shadow: 0 0 0 0 $success;
}
0% {
box-shadow: 0 0 0 0 $success;
}
70% {
box-shadow: 0 0 0 15px rgba(0, 0, 0, 0);
}
70% {
box-shadow: 0 0 0 15px rgba(0, 0, 0, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
100% {
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
@keyframes off-pulse {
0% {
box-shadow: 0 0 0 0 $danger;
}
@keyframes off-pulse {
0% {
box-shadow: 0 0 0 0 $danger;
}
70% {
box-shadow: 0 0 0 15px rgba(0, 0, 0, 0);
}
70% {
box-shadow: 0 0 0 15px rgba(0, 0, 0, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
100% {
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
</style>

View File

@@ -1,10 +1,9 @@
<script>
const preferesDark = window.matchMedia("(prefers-color-scheme: dark)");
preferesDark.addEventListener("change", e => {
preferesDark.addEventListener("change", (e) => {
setTheme(e.matches ? "dark" : "light");
})
});
if (localStorage.getItem("data-theme") === null) {
setTheme(preferesDark.matches ? "dark" : "light");
@@ -12,36 +11,44 @@
setTheme(localStorage.getItem("data-theme"));
}
function setTheme(color) {
function setTheme(color) {
if (color == "system") {
document.documentElement.setAttribute("data-theme", preferesDark ? "dark" : "light");
document.documentElement.setAttribute(
"data-theme",
preferesDark ? "dark" : "light"
);
localStorage.setItem("data-theme", preferesDark ? "dark" : "light");
} else {
document.documentElement.setAttribute("data-theme", color);
localStorage.setItem("data-theme", color);
}
}
}
</script>
<div class="dropdown">
<button class="btn btn-light dropdown-toggle px-3 me-2 py-2" type="button" id="darkModeButton" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-palette me-2"></i>Theme
<button
class="btn btn-light dropdown-toggle px-3 me-2 py-2"
type="button"
id="darkModeButton"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i class="fa-solid fa-palette me-2" />Theme
</button>
<ul class="dropdown-menu" aria-labelledby="darkModeButton">
<li>
<button class="dropdown-item" on:click={() => setTheme("dark")}>
<i class="fa-solid fa-moon me-2"></i>Dark
<i class="fa-solid fa-moon me-2" />Dark
</button>
</li>
<li>
<button class="dropdown-item" on:click={() => setTheme("light")}>
<i class="fa-solid fa-sun me-2"></i>Light
<i class="fa-solid fa-sun me-2" />Light
</button>
</li>
<li>
<button class="dropdown-item" on:click={() => setTheme("system")}>
<i class="fa-solid fa-desktop me-2"></i>System
<i class="fa-solid fa-desktop me-2" />System
</button>
</li>
</ul>

View File

@@ -1,44 +1,46 @@
<script>
import socketStore from '../socketStore.js';
import socketStore from "../socketStore.js";
export let device;
let modalDevice = JSON.parse(JSON.stringify(device));
let customPort = {}
let customPort = {};
function wake(id) {
function wake(id) {
socketStore.sendMessage({
type: "wake",
id: id
})
}
id: id,
});
}
function shutdown(id) {
socketStore.sendMessage({
type: "shutdown",
id: id
})
}
id: id,
});
}
function deleteDevice() {
function deleteDevice() {
socketStore.sendMessage({
type: "delete_device",
id: modalDevice.id
})
}
id: modalDevice.id,
});
}
function updateDevice() {
device = modalDevice;
socketStore.sendMessage({
type: "update_device",
data: modalDevice
})
data: modalDevice,
});
}
function updatePort() {
if (!customPort.number) {
return
return;
}
const index = modalDevice.ports.findIndex(x => x.number == customPort.number);
const index = modalDevice.ports.findIndex(
(x) => x.number == customPort.number
);
if (customPort.name) {
// add port
if (index === -1) {
@@ -46,75 +48,127 @@
modalDevice.ports.push(JSON.parse(JSON.stringify(customPort)));
} else {
customPort.checked = modalDevice.ports[index].checked;
modalDevice.ports[index] = JSON.parse(JSON.stringify(customPort));
modalDevice.ports[index] = JSON.parse(
JSON.stringify(customPort)
);
}
} else {
// delete port
if (index >= 0) {
modalDevice.ports.splice(index, 1)
modalDevice.ports.splice(index, 1);
}
}
modalDevice = modalDevice;
// send to backend
socketStore.sendMessage({
type: "update_port",
data: customPort
})
data: customPort,
});
}
function openModal() {
modalDevice = Object.assign({}, device)
modalDevice = Object.assign({}, device);
}
function validatePort() {
if (typeof customPort.number != "number") {
customPort.number = 1
customPort.number = 1;
} else if (customPort.number > 65535) {
customPort.number = 65535
customPort.number = 65535;
}
}
</script>
<div id="device-col-{device.id}" class="col-xs-12 col-sm-6 col-md-4 col-lg-3 g-4">
<div
id="device-col-{device.id}"
class="col-xs-12 col-sm-6 col-md-4 col-lg-3 g-4"
>
<div class="card border-0 p-3 pt-2">
<div class="card-body">
<div class="row">
<div class="col-auto me-auto">
<div id="spinner-{device.id}" class="spinner-border warning d-none" role="status"></div>
<div
id="spinner-{device.id}"
class="spinner-border warning d-none"
role="status"
/>
{#if device.up === true}
{#if device.shutdown.command}
<div class="hover" on:click="{() => shutdown(device.id)}" data-bs-toggle="tooltip" title="Shutdown command: {device.shutdown.command}" role="button">
<i id="dot-{device.id}" class="fa-solid fa-power-off fa-2x success"></i>
</div>
<div
class="hover"
on:click={() => shutdown(device.id)}
on:keydown={() => shutdown(device.id)}
data-bs-toggle="tooltip"
title="Shutdown command: {device.shutdown
.command}"
role="button"
>
<i
id="dot-{device.id}"
class="fa-solid fa-power-off fa-2x success"
/>
</div>
{:else}
<i id="dot-{device.id}" class="fa-solid fa-power-off fa-2x success"></i>
<i
id="dot-{device.id}"
class="fa-solid fa-power-off fa-2x success"
/>
{/if}
{:else if device.up === false}
<div class="hover" on:click="{() => wake(device.id)}" role="button">
<i id="dot-{device.id}" class="fa-solid fa-power-off fa-2x danger"></i>
<div
class="hover"
on:click={() => wake(device.id)}
on:keydown={() => wake(device.id)}
role="button"
>
<i
id="dot-{device.id}"
class="fa-solid fa-power-off fa-2x danger"
/>
</div>
{:else}
<div class="hover" role="button">
<i id="dot-{device.id}" class="fa-solid fa-power-off fa-2x text-muted"></i>
<i
id="dot-{device.id}"
class="fa-solid fa-power-off fa-2x text-muted"
/>
</div>
{/if}
</div>
{#if device.wake.enabled}
<div class="col-auto px-2" data-bs-toggle="tooltip" title="Wake cron: {device.wake.cron}">
<i class="fa-solid fa-circle-play fa-2x text-muted"></i>
<div
class="col-auto px-2"
data-bs-toggle="tooltip"
title="Wake cron: {device.wake.cron}"
>
<i class="fa-solid fa-circle-play fa-2x text-muted" />
</div>
{/if}
{#if device.shutdown.enabled}
<div class="col-auto px-2" data-bs-toggle="tooltip" title="Shutdown cron: {device.wake.cron}">
<i class="fa-solid fa-circle-stop fa-2x text-muted"></i>
<div
class="col-auto px-2"
data-bs-toggle="tooltip"
title="Shutdown cron: {device.wake.cron}"
>
<i class="fa-solid fa-circle-stop fa-2x text-muted" />
</div>
{/if}
<div class="col-auto hover" data-bs-toggle="modal" data-bs-target="#device-modal-{device.id}" role="button" on:click="{() => openModal()}">
<i class="fa-solid fa-ellipsis-vertical fa-2x"></i>
<div
class="col-auto hover"
data-bs-toggle="modal"
data-bs-target="#device-modal-{device.id}"
role="button"
on:click={() => openModal()}
on:keydown={() => openModal()}
>
<i class="fa-solid fa-ellipsis-vertical fa-2x" />
</div>
</div>
{#if device.link}
<h5 class="card-title fw-bold my-2"><a class="inherit-color" href={device.link}>{device.name}</a></h5>
<h5 class="card-title fw-bold my-2">
<a class="inherit-color" href={device.link}>{device.name}</a
>
</h5>
{:else}
<h5 class="card-title fw-bold my-2">{device.name}</h5>
{/if}
@@ -123,8 +177,13 @@
{#each device.ports as port}
{#if port.checked === true}
<li class="list-group-item">
<i class="fa-solid fa-circle align-middle {port.open ? 'success' : 'danger'}"></i>
{port.name} <span class="text-muted">({port.number})</span>
<i
class="fa-solid fa-circle align-middle {port.open
? 'success'
: 'danger'}"
/>
{port.name}
<span class="text-muted">({port.number})</span>
</li>
{/if}
{/each}
@@ -132,119 +191,328 @@
</div>
</div>
<div class="modal fade" id="device-modal-{device.id}" tabindex="-1" aria-labelledby="device-modal-{device.id}-label" aria-hidden="true">
<div
class="modal fade"
id="device-modal-{device.id}"
tabindex="-1"
aria-labelledby="device-modal-{device.id}-label"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="device-modal-{modalDevice.id}-label">{modalDevice.name}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<h5
class="modal-title fw-bold"
id="device-modal-{modalDevice.id}-label"
>
{modalDevice.name}
</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
/>
</div>
<div class="modal-body">
<form id="form-{modalDevice.id}" on:submit|preventDefault={updateDevice}>
<form
id="form-{modalDevice.id}"
on:submit|preventDefault={updateDevice}
>
<!-- general -->
<h5 class="fw-bold">General</h5>
<div class="row mb-2">
<div class="col-sm">
<label for="inputName{modalDevice.id}" class="form-label">Device name</label>
<input type="text" class="form-control" id="inputName{modalDevice.id}" bind:value="{modalDevice.name}" required>
<label
for="inputName{modalDevice.id}"
class="form-label">Device name</label
>
<input
type="text"
class="form-control"
id="inputName{modalDevice.id}"
bind:value={modalDevice.name}
required
/>
</div>
<div class="col-sm">
<label for="inputMac{modalDevice.id}" class="form-label">Mac address</label>
<input type="text" class="form-control" id="inputMac{modalDevice.mac}" bind:value="{modalDevice.mac}" required>
<label
for="inputMac{modalDevice.id}"
class="form-label">Mac address</label
>
<input
type="text"
class="form-control"
id="inputMac{modalDevice.mac}"
bind:value={modalDevice.mac}
required
/>
</div>
</div>
<div class="row mb-2">
<div class="col-sm">
<label for="inputIp{modalDevice.id}" class="form-label">IP address</label>
<input type="text" class="form-control" id="inputIp{modalDevice.id}" bind:value="{modalDevice.ip}" pattern="^([01]?\d\d?|2[0-4]\d|25[0-5])(?:\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])){'{'}3{'}'}$" required>
<label
for="inputIp{modalDevice.id}"
class="form-label">IP address</label
>
<input
type="text"
class="form-control"
id="inputIp{modalDevice.id}"
bind:value={modalDevice.ip}
required
/>
</div>
<div class="col-sm">
<label for="inputNetmask{modalDevice.id}" class="form-label">Netmask</label>
<input type="text" class="form-control" id="inputNetmask{modalDevice.id}" bind:value="{modalDevice.netmask}" pattern="^(((255\.){'{'}3{'}'}(255|254|252|248|240|224|192|128|0+))|((255\.){'{'}2{'}'}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){'{'}2{'}'})|((255|254|252|248|240|224|192|128|0+)(\.0+){'{'}3{'}'}))$" required>
<label
for="inputNetmask{modalDevice.id}"
class="form-label">Netmask</label
>
<input
type="text"
class="form-control"
id="inputNetmask{modalDevice.id}"
bind:value={modalDevice.netmask}
required
/>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-3">
<label for="inputLinkAddDevice" class="form-label">Web link</label>
<input type="text" class="form-control" id="inputILinkAddDevice" placeholder="http://...." bind:value="{modalDevice.link}">
<label
for="inputLinkAddDevice"
class="form-label">Web link</label
>
<input
type="text"
class="form-control"
id="inputILinkAddDevice"
placeholder="http://...."
bind:value={modalDevice.link}
/>
</div>
</div>
</div>
<!-- ports -->
<h5 class="fw-bold mt-4">Ports</h5>
<p class="mb-2">Select ports to check if they are open.</p>
<p class="mb-2">
Select ports to check if they are open.
</p>
{#if modalDevice.ports.length === 0}
<p class="mb-0">No ports available. Add ports below.</p>
<p class="mb-0">
No ports available. Add ports below.
</p>
{/if}
{#each modalDevice.ports as port}
<div class="form-check">
<input type="checkbox" class="form-check-input" id="{device.id}-port-{port.number}" bind:checked="{port['checked']}">
<label class="form-check-label" for="{device.id}-port-{port.number}">{port.name} <span class="text-muted">({port.number})</span></label>
<input
type="checkbox"
class="form-check-input"
id="{device.id}-port-{port.number}"
bind:checked={port["checked"]}
/>
<label
class="form-check-label"
for="{device.id}-port-{port.number}"
>{port.name}
<span class="text-muted"
>({port.number})</span
></label
>
</div>
{/each}
<label class="form-label mt-2" for="{device.id}-custom-port">Custom port</label>
<label
class="form-label mt-2"
for="{device.id}-custom-port">Custom port</label
>
<div class="input-group mb-2">
<input type="text" id="{device.id}-custom-port" class="form-control rounded-0 rounded-start" placeholder="Name" aria-label="Name" aria-describedby="button-addon2" bind:value={customPort.name}>
<input type="number" min="1" max="65535" class="form-control rounded-0" placeholder="Port" aria-label="Port" aria-describedby="button-addon2" bind:value={customPort.number} on:input={validatePort}>
<button class="btn btn-secondary" type="button" id="button-addon2" on:click="{updatePort}">Update Port</button>
<input
type="text"
id="{device.id}-custom-port"
class="form-control rounded-0 rounded-start"
placeholder="Name"
aria-label="Name"
aria-describedby="button-addon2"
bind:value={customPort.name}
/>
<input
type="number"
min="1"
max="65535"
class="form-control rounded-0"
placeholder="Port"
aria-label="Port"
aria-describedby="button-addon2"
bind:value={customPort.number}
on:input={validatePort}
/>
<button
class="btn btn-secondary"
type="button"
id="button-addon2"
on:click={updatePort}>Update Port</button
>
</div>
<button class="btn btn-secondary mt-2" type="button" data-bs-toggle="collapse" data-bs-target="#info-ports" aria-expanded="false" aria-controls="info-ports">
<i class="fa-solid fa-angle-down me-2"></i>How to use
<button
class="btn btn-secondary mt-2"
type="button"
data-bs-toggle="collapse"
data-bs-target="#info-ports"
aria-expanded="false"
aria-controls="info-ports"
>
<i class="fa-solid fa-angle-down me-2" />How to use
</button>
<div class="collapse mt-3" id="info-ports">
<div class="callout callout-info">
<p class="mb-0">Ports must be between 1 and 65535. Enter the same port with a differen name to change it. Leave name empty to delete port.</p>
<p class="mb-0">
Ports must be between 1 and 65535. Enter the
same port with a differen name to change it.
Leave name empty to delete port.
</p>
</div>
</div>
<!-- scheduled wake -->
<h5 class="fw-bold mt-4">Scheduled wake</h5>
<p class="mb-2">Wake your device at a given time.</p>
<div class="form-check">
<input class="form-check-input" type="radio" name="wake-disable" id="wake-radio-disabled-{modalDevice.id}"
bind:group={modalDevice["wake"]["enabled"]} value={false} checked={!modalDevice["wake"]["enabled"]}>
<label class="form-check-label" for="wake-radio-disabled-{modalDevice.id}">
<input
class="form-check-input"
type="radio"
name="wake-disable"
id="wake-radio-disabled-{modalDevice.id}"
bind:group={modalDevice["wake"]["enabled"]}
value={false}
checked={!modalDevice["wake"]["enabled"]}
/>
<label
class="form-check-label"
for="wake-radio-disabled-{modalDevice.id}"
>
Disabled
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="wake-cron" id="wake-radio-cron-{modalDevice.id}"
bind:group={modalDevice["wake"]["enabled"]} value={true} checked={modalDevice["wake"]["enabled"]}>
<label class="form-check-label" for="wake-radio-cron-{modalDevice.id}">
<input
class="form-check-input"
type="radio"
name="wake-cron"
id="wake-radio-cron-{modalDevice.id}"
bind:group={modalDevice["wake"]["enabled"]}
value={true}
checked={modalDevice["wake"]["enabled"]}
/>
<label
class="form-check-label"
for="wake-radio-cron-{modalDevice.id}"
>
Enabled
</label>
</div>
<div class="input-group my-1" hidden={!modalDevice["wake"]["enabled"]}>
<span class="input-group-text rounded-0 rounded-start" id="wake-cron-{modalDevice.id}">Cron</span>
<input type="text" class="form-control rounded-0 rounded-end" placeholder="* /4 * * *" aria-label="Crontab" aria-describedby="wake-cron-{modalDevice.id}" bind:value={modalDevice["wake"]["cron"]}>
<div
class="input-group my-1"
hidden={!modalDevice["wake"]["enabled"]}
>
<span
class="input-group-text rounded-0 rounded-start"
id="wake-cron-{modalDevice.id}">Cron</span
>
<input
type="text"
class="form-control rounded-0 rounded-end"
placeholder="* /4 * * *"
aria-label="Crontab"
aria-describedby="wake-cron-{modalDevice.id}"
bind:value={modalDevice["wake"]["cron"]}
/>
</div>
<button class="btn btn-secondary mt-2" type="button" data-bs-toggle="collapse" data-bs-target="#info-wake" aria-expanded="false" aria-controls="info-wake">
<i class="fa-solid fa-angle-down me-2"></i>How to use
<button
class="btn btn-secondary mt-2"
type="button"
data-bs-toggle="collapse"
data-bs-target="#info-wake"
aria-expanded="false"
aria-controls="info-wake"
>
<i class="fa-solid fa-angle-down me-2" />How to use
</button>
<div class="collapse mt-3" id="info-wake">
<div class="callout callout-info">
<p class="mb-2">Cron is a syntax describing a time pattern when to execute jobs. The above field uses common cron syntax. Examples:</p>
<pre class="mb-2">Minute Hour DayOfMonth Month DayOfWeek
<p class="mb-2">
Cron is a syntax describing a time pattern
when to execute jobs. The above field uses
common cron syntax. Examples:
</p>
<pre
class="mb-2">Minute Hour DayOfMonth Month DayOfWeek
* /4 * * * (Wake every 4 hours)
0 9 * * 1-5 (Wake from Mo-Fr at 9 a.m.)
</pre>
<p class="mb-0">Read more about <a href="https://linux.die.net/man/5/crontab" target="_blank">valid syntax here</a> or <a href="https://crontab.guru/" target="_blank">use a generator</a>. Expressions starting with "@..." are not supported.</p>
<p class="mb-0">
Read more about <a
href="https://linux.die.net/man/5/crontab"
target="_blank"
rel="noreferrer">valid syntax here</a
>
or
<a
href="https://crontab.guru/"
target="_blank"
rel="noreferrer">use a generator</a
>. Expressions starting with "@..." are not
supported.
</p>
</div>
</div>
<!-- scheduled shutdown -->
<h5 class="fw-bold mt-4">Shutdown</h5>
<p class="mb-2">Set the shutdown command here. This shell command will be executed when clicking the power button on the device card. You can use cron below, which will then execute the command at the given time.</p>
<p class="mb-2">
Set the shutdown command here. This shell command
will be executed when clicking the power button on
the device card. You can use cron below, which will
then execute the command at the given time.
</p>
<div class="input-group">
<span class="input-group-text rounded-0 rounded-start" id="shutdown-command-{modalDevice.id}">Command</span>
<input type="text" class="form-control rounded-0 rounded-end" placeholder="sshpass -p your_password ssh -o 'StrictHostKeyChecking=no' user@hostname 'sudo shutdown'" aria-label="Ccommand" aria-describedby="shutdown-command-{modalDevice.id}" bind:value={modalDevice["shutdown"]["command"]}>
<span
class="input-group-text rounded-0 rounded-start"
id="shutdown-command-{modalDevice.id}"
>Command</span
>
<input
type="text"
class="form-control rounded-0 rounded-end"
placeholder="sshpass -p your_password ssh -o 'StrictHostKeyChecking=no' user@hostname 'sudo shutdown'"
aria-label="Ccommand"
aria-describedby="shutdown-command-{modalDevice.id}"
bind:value={modalDevice["shutdown"]["command"]}
/>
</div>
<button class="btn btn-secondary mt-2" type="button" data-bs-toggle="collapse" data-bs-target="#info-shutdown" aria-expanded="false" aria-controls="info-shutdown">
<i class="fa-solid fa-angle-down me-2"></i>How to use
<button
class="btn btn-secondary mt-2"
type="button"
data-bs-toggle="collapse"
data-bs-target="#info-shutdown"
aria-expanded="false"
aria-controls="info-shutdown"
>
<i class="fa-solid fa-angle-down me-2" />How to use
</button>
<div class="collapse mt-3" id="info-shutdown">
<div class="callout callout-info">
<p class="mb-2">This field takes a shell command to trigger the shutdown. You can use <code>sshpass</code> for Linux or <code>net rpc</code> for Windows hosts.</p>
<div class="callout callout-danger mb-2">Note: This command is safed as cleartext. Meaning, passwords are clearly visible in the database.</div>
<p class="mb-2">
This field takes a shell command to trigger
the shutdown. You can use <code
>sshpass</code
>
for Linux or <code>net rpc</code> for Windows
hosts.
</p>
<div class="callout callout-danger mb-2">
Note: This command is safed as cleartext.
Meaning, passwords are clearly visible in
the database.
</div>
<p class="mb-2">Examples:</p>
<pre class="mb-2"># wake linux hosts
sshpass -p your_password ssh -o 'StrictHostKeyChecking=no' user@hostname 'sudo shutdown'
@@ -252,35 +520,92 @@
# wake windows hosts
net rpc shutdown --ipaddress 192.168.1.1 --user user%password
</pre>
<p class="mb-0">Read more about <a href="https://linux.die.net/man/1/sshpass" target="_blank">sshpass</a> or <a href="https://linux.die.net/man/8/net" target="_blank">net rpc</a>.</p>
<p class="mb-0">
Read more about <a
href="https://linux.die.net/man/1/sshpass"
target="_blank"
rel="noreferrer">sshpass</a
>
or
<a
href="https://linux.die.net/man/8/net"
target="_blank"
rel="noreferrer">net rpc</a
>.
</p>
</div>
</div>
<div>
<label class="form-label mt-2" for="{device.id}-shutdown-cron">Use cron</label>
<label
class="form-label mt-2"
for="{device.id}-shutdown-cron">Use cron</label
>
</div>
<div class="form-check" id="{device.id}-shutdown-cron">
<input class="form-check-input" type="radio" name="shutdown-disable" id="shutdown-radio-disabled-{modalDevice.id}"
bind:group={modalDevice["shutdown"]["enabled"]} value={false} checked={!modalDevice["shutdown"]["enabled"]}>
<label class="form-check-label" for="shutdown-radio-disabled-{modalDevice.id}">
<input
class="form-check-input"
type="radio"
name="shutdown-disable"
id="shutdown-radio-disabled-{modalDevice.id}"
bind:group={modalDevice["shutdown"]["enabled"]}
value={false}
checked={!modalDevice["shutdown"]["enabled"]}
/>
<label
class="form-check-label"
for="shutdown-radio-disabled-{modalDevice.id}"
>
Disabled
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="shutdown-enable" id="shutdown-radio-enabled-{modalDevice.id}"
bind:group={modalDevice["shutdown"]["enabled"]} value={true} checked={modalDevice["shutdown"]["enabled"]}>
<label class="form-check-label" for="shutdown-radio-enabled-{modalDevice.id}">
<input
class="form-check-input"
type="radio"
name="shutdown-enable"
id="shutdown-radio-enabled-{modalDevice.id}"
bind:group={modalDevice["shutdown"]["enabled"]}
value={true}
checked={modalDevice["shutdown"]["enabled"]}
/>
<label
class="form-check-label"
for="shutdown-radio-enabled-{modalDevice.id}"
>
Enabled
</label>
</div>
<div class="input-group my-1" hidden={!modalDevice["shutdown"]["enabled"]}>
<span class="input-group-text rounded-0 rounded-start" id="shutdown-cron-{modalDevice.id}">Cron</span>
<input type="text" class="form-control rounded-0 rounded-end" placeholder="* /4 * * *" aria-label="Crontab" aria-describedby="shutdown-cron-{modalDevice.id}" bind:value={modalDevice["shutdown"]["cron"]}>
<div
class="input-group my-1"
hidden={!modalDevice["shutdown"]["enabled"]}
>
<span
class="input-group-text rounded-0 rounded-start"
id="shutdown-cron-{modalDevice.id}">Cron</span
>
<input
type="text"
class="form-control rounded-0 rounded-end"
placeholder="* /4 * * *"
aria-label="Crontab"
aria-describedby="shutdown-cron-{modalDevice.id}"
bind:value={modalDevice["shutdown"]["cron"]}
/>
</div>
</form>
</div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-outline-danger" data-bs-dismiss="modal" on:click={deleteDevice}>Delete</button>
<button type="submit" form="form-{modalDevice.id}" class="btn btn-outline-success">Save changes</button>
<button
type="button"
class="btn btn-outline-danger"
data-bs-dismiss="modal"
on:click={deleteDevice}>Delete</button
>
<button
type="submit"
form="form-{modalDevice.id}"
class="btn btn-outline-success">Save changes</button
>
</div>
</div>
</div>
@@ -309,7 +634,7 @@
.card {
border-radius: 2em;
background-color: var(--bg-lighter);
background-color: var(--bg-lighter);
}
.spinner-border {

View File

@@ -1,5 +1,5 @@
<script>
import socketStore from '../socketStore.js';
import socketStore from "../socketStore.js";
import DarkToggle from "./DarkToggle.svelte";
export let visitors;
export let settings;
@@ -7,37 +7,37 @@
let addDevice = {
wake: {
enabled: false,
cron: ""
cron: "",
},
shutdown: {
enabled: false,
cron: "",
command: ""
}
}
command: "",
},
};
function updateDevice(data) {
if (Object.keys(data).length < 4) {
return
return;
}
socketStore.sendMessage({
type: "update_device",
data: data
})
data: data,
});
}
function updateSettings() {
socketStore.sendMessage({
type: "update_settings",
data: settings
})
data: settings,
});
hideModal("settings");
}
function scanNetwork() {
socketStore.sendMessage({
type: "scan_network"
})
type: "scan_network",
});
const btnScan = document.querySelector("#btnScan");
const btnScanSpinner = document.querySelector("#btnScanSpinner");
const btnScanText = document.querySelector("#btnScanText");
@@ -52,33 +52,33 @@
updateDevice(dev);
}
const restoreFromFile = (e) => {
const restoreFromFile = (e) => {
let file = e.target.files[0];
let reader = new FileReader();
reader.readAsText(file);
reader.onload = e => {
reader.onload = (e) => {
let data = JSON.parse(e.target.result);
if (Array.isArray(data)) {
// v2 file restore
data.forEach((device) => {
updateDevice(device)
})
updateDevice(device);
});
} else {
// v1 file restore
for (const [key, value] of Object.entries(data)) {
value["mac"] = key
value["link"] = ""
updateDevice(value)
value["mac"] = key;
value["link"] = "";
updateDevice(value);
}
}
}
};
hideModal("settings");
}
};
function backupToFile() {
socketStore.sendMessage({
type: "backup"
})
type: "backup",
});
}
function hideModal(id) {
@@ -86,102 +86,219 @@
const modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
}
</script>
<nav class="navbar navbar-expand-sm">
<div class="container-fluid">
<div class="navbar-brand" href="/">
<img src="favicon.png" alt="Logo" width="24" height="24" class="me-2">
<img
src="favicon.png"
alt="Logo"
width="24"
height="24"
class="me-2"
/>
UpSnap
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon" />
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<span class="ms-auto d-flex">
<DarkToggle />
<div class="dropdown">
<button class="btn btn-light dropdown-toggle px-3 me-2 py-2" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-wrench me-2"></i>More
<button
class="btn btn-light dropdown-toggle px-3 me-2 py-2"
type="button"
id="dropdownMenuButton1"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i class="fa-solid fa-wrench me-2" />More
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<ul
class="dropdown-menu"
aria-labelledby="dropdownMenuButton1"
>
<li>
<button class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addDevice">
<i class="fa-solid fa-plus me-2"></i>Add device
<button
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#addDevice"
>
<i class="fa-solid fa-plus me-2" />Add device
</button>
</li>
<li>
<button class="dropdown-item" data-bs-toggle="modal" data-bs-target="#settings">
<i class="fa-solid fa-sliders me-2"></i>Settings
<button
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#settings"
>
<i class="fa-solid fa-sliders me-2" />Settings
</button>
</li>
</ul>
</div>
<div class="btn btn-light px-3 me-2 py-2 pe-none">
<i class="me-2 fa-solid {visitors === 1 ? "fa-user" : "fa-user-group"}"></i>{visitors} {visitors === 1 ? "Visitor" : "Visitors"}
<i
class="me-2 fa-solid {visitors === 1
? 'fa-user'
: 'fa-user-group'}"
/>{visitors}
{visitors === 1 ? "Visitor" : "Visitors"}
</div>
</span>
</div>
</div>
</nav>
<div class="modal fade" id="addDevice" tabindex="-1" aria-labelledby="addDeviceLabel" aria-hidden="true">
<div
class="modal fade"
id="addDevice"
tabindex="-1"
aria-labelledby="addDeviceLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="addDeviceLabel">Add device</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<h5 class="modal-title fw-bold" id="addDeviceLabel">
Add device
</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
/>
</div>
<div class="modal-body">
<form id="addForm" on:submit|preventDefault={() => updateDevice(addDevice)}>
<form
id="addForm"
on:submit|preventDefault={() => updateDevice(addDevice)}
>
<div class="row">
<div class="col-sm">
<div class="mb-3">
<label for="inputNameAddDevice" class="form-label">Device name</label>
<input type="text" class="form-control" id="inputNameAddDevice" placeholder="Max PC" bind:value="{addDevice.name}" required>
<label
for="inputNameAddDevice"
class="form-label">Device name</label
>
<input
type="text"
class="form-control"
id="inputNameAddDevice"
placeholder="Max PC"
bind:value={addDevice.name}
required
/>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<label for="inputMacAddDevice" class="form-label">Mac address</label>
<input type="text" class="form-control" id="inputMacAddDevice" placeholder="aa:aa:aa:aa:aa:aa" bind:value="{addDevice.mac}" pattern="^([0-9A-Fa-f]{'{'}2{'}'}[:-]){'{'}5{'}'}([0-9A-Fa-f]{'{'}2{'}'})|([0-9a-fA-F]{'{'}4{'}'}\\.[0-9a-fA-F]{'{'}4{'}'}\\.[0-9a-fA-F]{'{'}4{'}'})$" required>
<label
for="inputMacAddDevice"
class="form-label">Mac address</label
>
<input
type="text"
class="form-control"
id="inputMacAddDevice"
placeholder="aa:aa:aa:aa:aa:aa"
bind:value={addDevice.mac}
required
/>
</div>
</div>
</div>
<div class="row">
<div class="col-sm">
<div class="mb-3">
<label for="inputIpAddDevice" class="form-label">IP address</label>
<input type="text" class="form-control" id="inputIpAddDevice" placeholder="192.168.1.1" bind:value="{addDevice.ip}" pattern="^([01]?\d\d?|2[0-4]\d|25[0-5])(?:\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])){'{'}3{'}'}$" required>
<label for="inputIpAddDevice" class="form-label"
>IP address</label
>
<input
type="text"
class="form-control"
id="inputIpAddDevice"
placeholder="192.168.1.1"
bind:value={addDevice.ip}
required
/>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<label for="inputNetmaskAddDevice" class="form-label">Netmask</label>
<input type="text" class="form-control" id="inputNetmaskAddDevice" placeholder="255.255.255.0" bind:value="{addDevice.netmask}" pattern="^(((255\.){'{'}3{'}'}(255|254|252|248|240|224|192|128|0+))|((255\.){'{'}2{'}'}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){'{'}2{'}'})|((255|254|252|248|240|224|192|128|0+)(\.0+){'{'}3{'}'}))$" required>
<label
for="inputNetmaskAddDevice"
class="form-label">Netmask</label
>
<input
type="text"
class="form-control"
id="inputNetmaskAddDevice"
placeholder="255.255.255.0"
bind:value={addDevice.netmask}
required
/>
</div>
</div>
</div>
<div class="col">
<div class="mb-3">
<label for="inputLinkAddDevice" class="form-label">Web link</label>
<input type="text" class="form-control" id="inputILinkAddDevice" placeholder="http://...." bind:value="{addDevice.link}">
<label for="inputLinkAddDevice" class="form-label"
>Web link</label
>
<input
type="text"
class="form-control"
id="inputILinkAddDevice"
placeholder="http://...."
bind:value={addDevice.link}
/>
</div>
</div>
<div class="row">
<div class="col-auto ms-auto">
<button type="submit" form="addForm" class="btn btn-outline-success">Add device</button>
<button
type="submit"
form="addForm"
class="btn btn-outline-success"
>Add device</button
>
</div>
</div>
<h5 class="fw-bold">Network discovery</h5>
{#if !settings.discovery}
<div class="callout callout-danger mb-2">
<p class="m-0">To enable this option, please enter your network address in the settings.</p>
<p class="m-0">
To enable this option, please enter your network
address in the settings.
</p>
</div>
{/if}
<button id="btnScan" class="btn btn-secondary" type="button" on:click={scanNetwork} disabled={!settings.discovery}>
<span id="btnScanSpinner" class="spinner-grow spinner-grow-sm d-none" role="status" aria-hidden="true"></span>
<button
id="btnScan"
class="btn btn-secondary"
type="button"
on:click={scanNetwork}
disabled={!settings.discovery}
>
<span
id="btnScanSpinner"
class="spinner-grow spinner-grow-sm d-none"
role="status"
aria-hidden="true"
/>
<span id="btnScanText">Scan</span>
</button>
{#if settings.scan_network?.length}
@@ -203,8 +320,15 @@
<td>{device.netmask}</td>
<td>{device.mac}</td>
<td>
<button type="button" id="btnAdd{i}" class="btn btn-outline-secondary py-0" on:click={() => addScan(i)}>
<i class="fa-solid fa-plus fa-sm"></i>
<button
type="button"
id="btnAdd{i}"
class="btn btn-outline-secondary py-0"
on:click={() => addScan(i)}
>
<i
class="fa-solid fa-plus fa-sm"
/>
</button>
</td>
</tr>
@@ -218,57 +342,119 @@
</div>
</div>
<div class="modal fade" id="settings" tabindex="-1" aria-labelledby="settingsLabel" aria-hidden="true">
<div
class="modal fade"
id="settings"
tabindex="-1"
aria-labelledby="settingsLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="settingsLabel">Settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
/>
</div>
<div class="modal-body">
<form id="settingsForm" on:submit|preventDefault={updateSettings}>
<form
id="settingsForm"
on:submit|preventDefault={updateSettings}
>
<h5 class="fw-bold">General</h5>
<div class="row">
<div class="col-sm-8">
<div class="mb-3">
<label for="inputNetworkDiscovery" class="form-label">Network discovery address</label>
<input type="text" class="form-control" id="inputNetworkDiscovery" placeholder="192.168.1.0/24" bind:value="{settings.discovery}" pattern="^([01]?\d\d?|2[0-4]\d|25[0-5])(?:\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])){'{'}2{'}'}(?:\.(?:0))(?:/[0-2]\d|/3[0-2])$">
<label
for="inputNetworkDiscovery"
class="form-label"
>Network discovery address</label
>
<input
type="text"
class="form-control"
id="inputNetworkDiscovery"
placeholder="192.168.1.0/24"
bind:value={settings.discovery}
/>
</div>
</div>
<div class="col-sm-4">
<div class="mb-3">
<label for="inputIntervalSettings" class="form-label">Interval (seconds)</label>
<input type="number" class="form-control" id="inputIntervalSettings" min="5" bind:value="{settings.interval}" required>
<label
for="inputIntervalSettings"
class="form-label">Interval (seconds)</label
>
<input
type="number"
class="form-control"
id="inputIntervalSettings"
min="5"
bind:value={settings.interval}
required
/>
</div>
</div>
</div>
<div class="row">
<div class="col-sm">
<div class="form-check">
<label class="form-check-label" for="flexCheckDefault">
<label
class="form-check-label"
for="flexCheckDefault"
>
Enable notifications
</label>
<input class="form-check-input" type="checkbox" value="" id="flexCheckDefault" bind:checked={settings.notifications}>
<input
class="form-check-input"
type="checkbox"
value=""
id="flexCheckDefault"
bind:checked={settings.notifications}
/>
</div>
</div>
</div>
</form>
<div class="row mb-3">
<div class="col-auto ms-auto">
<button type="submit" form="settingsForm" class="btn btn-outline-success">Save</button>
<button
type="submit"
form="settingsForm"
class="btn btn-outline-success">Save</button
>
</div>
</div>
<h5 class="fw-bold">Backup/Restore</h5>
<div class="callout callout-info mb-2">
<p class="m-0">Backup file structure has changed in v2. You can still restore both versions with this file upload.</p>
<p class="m-0">
Backup file structure has changed in v2. You can still
restore both versions with this file upload.
</p>
</div>
<div class="mb-3">
<label for="inputRestore" class="form-label">Restore from .json</label>
<input id="inputRestore" type="file" class="form-control" accept=".json" on:change={(e) => restoreFromFile(e)}>
<label for="inputRestore" class="form-label"
>Restore from .json</label
>
<input
id="inputRestore"
type="file"
class="form-control"
accept=".json"
on:change={(e) => restoreFromFile(e)}
/>
</div>
<div class="mb-3">
<button type="button" class="btn btn-secondary" on:click={backupToFile}>
<i class="fa-solid fa-download me-2"></i>
<button
type="button"
class="btn btn-secondary"
on:click={backupToFile}
>
<i class="fa-solid fa-download me-2" />
Export .json
</button>
</div>
@@ -289,15 +475,15 @@
.dropdown-menu {
background-color: var(--bg-lighter);
li:first-child>button {
li:first-child > button {
border-radius: 0.5rem 0.5rem 0rem 0rem;
}
li:last-child>button {
li:last-child > button {
border-radius: 0rem 0rem 0.5rem 0.5rem;
}
li:only-child>button {
li:only-child > button {
border-radius: 0.5rem;
}
}

View File

@@ -3,10 +3,20 @@
</script>
<div class="position-fixed bottom-0 end-0 p-3 toast-container">
<div class="toast fade {toast.show ? 'show' : 'hide'} {toast.color}-bg" role="alert" aria-live="assertive" aria-atomic="true">
<div
class="toast fade {toast.show ? 'show' : 'hide'} {toast.color}-bg"
role="alert"
aria-live="assertive"
aria-atomic="true"
>
<div class="toast-header">
<strong id="toast-title" class="me-auto">{toast.title}</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
<button
type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label="Close"
/>
</div>
<div class="toast-body fw-bold">
{toast.message}
@@ -18,19 +28,19 @@
@import "../variables.scss";
.toast-container {
z-index: 11;
border-radius: $toast-border-radius;
z-index: 11;
border-radius: $toast-border-radius;
div>.toast-body {
div > .toast-body {
color: $dark-lighter;
}
}
}
.success-bg {
.success-bg {
background-color: var(--success);
}
.danger-bg {
.danger-bg {
background-color: var(--danger);
}
</style>