Compare commits
474 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81364cbf8c | ||
|
|
b20b7b97fe | ||
|
|
b0b95c7193 | ||
|
|
9cd94b30b9 | ||
|
|
41dd4eb4fb | ||
|
|
106337d10a | ||
|
|
39a4856fef | ||
|
|
c49ce4b8a8 | ||
|
|
e55a843d49 | ||
|
|
61cfb96a65 | ||
|
|
153ac776c7 | ||
|
|
f47f03a69d | ||
|
|
5532b6a2c4 | ||
|
|
ead886dffa | ||
|
|
66c04ec353 | ||
|
|
594ec2eec6 | ||
|
|
6772709141 | ||
|
|
23f32f9830 | ||
|
|
417f803411 | ||
|
|
f1dc6b3078 | ||
|
|
0114325a18 | ||
|
|
9e911847c5 | ||
|
|
4bc18f93e7 | ||
|
|
e514178b33 | ||
|
|
87d317db52 | ||
|
|
3b2396ec41 | ||
|
|
f7ac3d0b9d | ||
|
|
d4da4032b2 | ||
|
|
dc7122532f | ||
|
|
66b0a42576 | ||
|
|
08cc452e3d | ||
|
|
3eaa5b7801 | ||
|
|
bd53ef9ecc | ||
|
|
8bb18b43f8 | ||
|
|
23103c8c4b | ||
|
|
93b5dc4dac | ||
|
|
f7058b048a | ||
|
|
159e82dbd3 | ||
|
|
31303727a4 | ||
|
|
931086aecb | ||
|
|
a295dae059 | ||
|
|
a5a6747ced | ||
|
|
a36e3c7b57 | ||
|
|
f7e4b95ee2 | ||
|
|
a2c5eec53a | ||
|
|
66e2397ca0 | ||
|
|
6dd80575f4 | ||
|
|
73985333b0 | ||
|
|
c5f9c1593c | ||
|
|
5cd39d6f0d | ||
|
|
f0ceee0bcd | ||
|
|
9db18fdefd | ||
|
|
7868c0d6c1 | ||
|
|
e09e0590d7 | ||
|
|
771abe118e | ||
|
|
451c76afe8 | ||
|
|
d21b1345bb | ||
|
|
eca174a20b | ||
|
|
5319b9dbbd | ||
|
|
de9a3b1fc4 | ||
|
|
d9e00171b2 | ||
|
|
75577ecd1d | ||
|
|
c77fc254f6 | ||
|
|
d1c2e0d3ba | ||
|
|
7976d77ac1 | ||
|
|
c3cc4ff9db | ||
|
|
b1d776aa64 | ||
|
|
c7194e2e26 | ||
|
|
2514b0a569 | ||
|
|
b23084fe7b | ||
|
|
e39d35a395 | ||
|
|
cbc6221420 | ||
|
|
e855d12949 | ||
|
|
7f2eb6e88f | ||
|
|
63491b17d6 | ||
|
|
5fbc94c319 | ||
|
|
58e0867c1e | ||
|
|
3922832b53 | ||
|
|
dadc4e4fb4 | ||
|
|
3c1d4325f2 | ||
|
|
f30abc4bd7 | ||
|
|
a13e1b8804 | ||
|
|
b2b52c2c36 | ||
|
|
cfef857d34 | ||
|
|
1b9d522bd8 | ||
|
|
59e7a9d859 | ||
|
|
01a441c687 | ||
|
|
4f90c035ca | ||
|
|
f558d02e68 | ||
|
|
2967f5b692 | ||
|
|
a005ba30b9 | ||
|
|
bb76eb86a4 | ||
|
|
33476098c1 | ||
|
|
6e749dd2d6 | ||
|
|
522b204bb9 | ||
|
|
e0e236faa2 | ||
|
|
c19bd60241 | ||
|
|
7a7bdda708 | ||
|
|
7cc436fbaa | ||
|
|
87f55c8b1e | ||
|
|
98922434f2 | ||
|
|
83ab53dd87 | ||
|
|
1e2f77abcb | ||
|
|
8f19d759f2 | ||
|
|
ed1938d10b | ||
|
|
9df3baa5f1 | ||
|
|
cbc6ee74c3 | ||
|
|
cf32a4ba01 | ||
|
|
636e04fa78 | ||
|
|
5f4469ab32 | ||
|
|
5a57d542c5 | ||
|
|
3fe4990cfd | ||
|
|
92f9cf745e | ||
|
|
c5ff6912ea | ||
|
|
e12be86c8c | ||
|
|
5cd07a83a9 | ||
|
|
126f64a793 | ||
|
|
3b352b12d8 | ||
|
|
b9642371e0 | ||
|
|
9dc6209b47 | ||
|
|
9d03517896 | ||
|
|
fa38ac6153 | ||
|
|
dd3b300931 | ||
|
|
da6f469982 | ||
|
|
a91e441073 | ||
|
|
f1ced91811 | ||
|
|
3ad5a98798 | ||
|
|
094ebdb85f | ||
|
|
dea3e7c485 | ||
|
|
4e55f5a97f | ||
|
|
619bfdc04d | ||
|
|
2b2a76c512 | ||
|
|
b293355eac | ||
|
|
2f9a2dfa92 | ||
|
|
fcca855c44 | ||
|
|
cc4370151f | ||
|
|
2c4dbb87ba | ||
|
|
99b46ce086 | ||
|
|
ee8d32383b | ||
|
|
bad782deda | ||
|
|
e31ccd09e8 | ||
|
|
134f80ada8 | ||
|
|
5040507750 | ||
|
|
02b31a0254 | ||
|
|
807d903c8a | ||
|
|
478ba9973f | ||
|
|
83a3057e7f | ||
|
|
f29f0f2b53 | ||
|
|
1382cdda33 | ||
|
|
8f37b35088 | ||
|
|
b374befa8e | ||
|
|
378e04939e | ||
|
|
bd3ac1b611 | ||
|
|
349c5af574 | ||
|
|
918f7daa74 | ||
|
|
6f380299db | ||
|
|
c33522b015 | ||
|
|
e126f3f664 | ||
|
|
c0df273c36 | ||
|
|
7994fa3c33 | ||
|
|
7fceaf536c | ||
|
|
7a0b93a0e8 | ||
|
|
c21d5a1790 | ||
|
|
4f57e259dd | ||
|
|
5c979424f3 | ||
|
|
51d528fd42 | ||
|
|
698b8bf809 | ||
|
|
b5e79b9148 | ||
|
|
810218b67e | ||
|
|
80a5d97eee | ||
|
|
6ef5d10fe8 | ||
|
|
96aa833477 | ||
|
|
ee916dadac | ||
|
|
c4f29443c4 | ||
|
|
eee8575d3f | ||
|
|
99cab5a06e | ||
|
|
e4e8204660 | ||
|
|
7fc7b25479 | ||
|
|
697c622f76 | ||
|
|
780803b0ec | ||
|
|
ad22fb693b | ||
|
|
3152e8c288 | ||
|
|
7883514f40 | ||
|
|
345c331b10 | ||
|
|
ac9a39b5be | ||
|
|
6de96dad14 | ||
|
|
eb8d81e590 | ||
|
|
5822914890 | ||
|
|
bba2f67931 | ||
|
|
2a4f2f356c | ||
|
|
665520234a | ||
|
|
65be23a1db | ||
|
|
eb53c46c33 | ||
|
|
1eed833909 | ||
|
|
c222f9cd4f | ||
|
|
3b35f5a0c6 | ||
|
|
72d7028e25 | ||
|
|
f514f5a12e | ||
|
|
3dca072b4c | ||
|
|
f802c825f6 | ||
|
|
c0f496cf07 | ||
|
|
116704c59b | ||
|
|
e9742a5252 | ||
|
|
2aaf1eca07 | ||
|
|
575876406c | ||
|
|
9c4cee49cd | ||
|
|
be0722f20a | ||
|
|
8dc0308a1d | ||
|
|
f4adbc1725 | ||
|
|
475ac70837 | ||
|
|
9cfccc6e69 | ||
|
|
1ab9779b13 | ||
|
|
1b39a7a1bf | ||
|
|
e65a29f911 | ||
|
|
98f11477b0 | ||
|
|
290cad8107 | ||
|
|
8b3694fd5c | ||
|
|
452f4aefbd | ||
|
|
231b4c3326 | ||
|
|
e55d24db66 | ||
|
|
2a2b429e75 | ||
|
|
a1ca7e861f | ||
|
|
992f999a7b | ||
|
|
be260d4b39 | ||
|
|
ce340cac67 | ||
|
|
540affbba1 | ||
|
|
1ee80b0572 | ||
|
|
0dc9e24f65 | ||
|
|
98e753ffa1 | ||
|
|
2c695225cd | ||
|
|
2d39c96153 | ||
|
|
ca8224474b | ||
|
|
701f6d7e3e | ||
|
|
26293d1e3b | ||
|
|
02852bfe55 | ||
|
|
ef073ef6ec | ||
|
|
219c40b6fd | ||
|
|
cc1832a296 | ||
|
|
16afef3e13 | ||
|
|
3b172715bb | ||
|
|
e312e0dd99 | ||
|
|
f33c6af4ee | ||
|
|
a8bb529c3d | ||
|
|
9528927cca | ||
|
|
384e1df1b7 | ||
|
|
0ad82da51c | ||
|
|
5d8b71b768 | ||
|
|
12c28f3f60 | ||
|
|
89dfa02d96 | ||
|
|
b34f96d351 | ||
|
|
f30ff24ed4 | ||
|
|
b900c401fe | ||
|
|
de7e36cb8c | ||
|
|
cc215530d3 | ||
|
|
d7428e40e3 | ||
|
|
2d7d521501 | ||
|
|
1bf91277da | ||
|
|
08528c8843 | ||
|
|
36610ac519 | ||
|
|
eda490e452 | ||
|
|
f614fa80dc | ||
|
|
a09550bf73 | ||
|
|
6899389a81 | ||
|
|
52a4acced8 | ||
|
|
3cb2e67387 | ||
|
|
5c7ccfb64f | ||
|
|
1ab3906632 | ||
|
|
73df916184 | ||
|
|
97b846b2b6 | ||
|
|
12b343b570 | ||
|
|
03e2b853be | ||
|
|
6fe48d0e4e | ||
|
|
1d80b5090d | ||
|
|
658e931aa6 | ||
|
|
c3175459ab | ||
|
|
3bf682d752 | ||
|
|
9e0801aa0c | ||
|
|
a21b0a8f99 | ||
|
|
5f1b16bc74 | ||
|
|
deba5e1e74 | ||
|
|
0960e8bac9 | ||
|
|
4a4506a913 | ||
|
|
bfab3d7441 | ||
|
|
47d7c121de | ||
|
|
484b89718a | ||
|
|
d6c8ac32a7 | ||
|
|
3d6b4e1f15 | ||
|
|
8d482845b0 | ||
|
|
a1550d8049 | ||
|
|
5584e3b72c | ||
|
|
b0ba029c66 | ||
|
|
48e492bf10 | ||
|
|
821ac9c1f8 | ||
|
|
49cb3911b8 | ||
|
|
8ad582b3b2 | ||
|
|
bbf1caf777 | ||
|
|
b3c6178274 | ||
|
|
a5907d8f7f | ||
|
|
8569c2b4d5 | ||
|
|
2a70f6ee11 | ||
|
|
c8b0408bd5 | ||
|
|
125ae7a39a | ||
|
|
20fe5fc5a7 | ||
|
|
168ed2448f | ||
|
|
a2b8dfe551 | ||
|
|
4aee1e49d9 | ||
|
|
16d92bb486 | ||
|
|
de376d04d1 | ||
|
|
d730c174d4 | ||
|
|
c798f5fd69 | ||
|
|
3a73916206 | ||
|
|
f46be0139e | ||
|
|
a6295aef6e | ||
|
|
556c2be855 | ||
|
|
80e8178384 | ||
|
|
b1c429f404 | ||
|
|
f0b992b9be | ||
|
|
084b2e1268 | ||
|
|
0fd60e3730 | ||
|
|
cfc6644087 | ||
|
|
2cd4abc3c2 | ||
|
|
8bd5b1b988 | ||
|
|
2b8f7fd6d8 | ||
|
|
b99addef4a | ||
|
|
96ada80ce1 | ||
|
|
5d33c79369 | ||
|
|
645ab89437 | ||
|
|
62b342cdbb | ||
|
|
c235a4ff16 | ||
|
|
a24516a4d5 | ||
|
|
483b1fc27d | ||
|
|
4d0de82986 | ||
|
|
85663341eb | ||
|
|
734043dee6 | ||
|
|
af2927f05b | ||
|
|
3e54b1d7bb | ||
|
|
51a0ce220c | ||
|
|
05e20e3bab | ||
|
|
03fc2c42ad | ||
|
|
401172eb36 | ||
|
|
71f1132bc8 | ||
|
|
605486feda | ||
|
|
23597e800d | ||
|
|
3218d87b1b | ||
|
|
0c58a6c92c | ||
|
|
afa3006de2 | ||
|
|
03b9bf9e7a | ||
|
|
028c0d3e06 | ||
|
|
0005488884 | ||
|
|
f73f25708c | ||
|
|
4e411aaa6b | ||
|
|
6adeab7586 | ||
|
|
16f6b7f979 | ||
|
|
068f363f09 | ||
|
|
d4554c6429 | ||
|
|
a3caa6485c | ||
|
|
795d55a2cd | ||
|
|
c53c1a27a4 | ||
|
|
903cc6bd8e | ||
|
|
13929acbd1 | ||
|
|
f6da130fce | ||
|
|
1fc793c3fd | ||
|
|
4b6ac1a415 | ||
|
|
304010db40 | ||
|
|
66563c9d95 | ||
|
|
5eca4c045b | ||
|
|
fd953e7a71 | ||
|
|
52b20cb491 | ||
|
|
c987d2800d | ||
|
|
6d6bacd6a1 | ||
|
|
f0a0c9228f | ||
|
|
dfef9e5233 | ||
|
|
ab77af9e5d | ||
|
|
5ce06c6214 | ||
|
|
d5cc80d1f2 | ||
|
|
e3f04192b8 | ||
|
|
cfb8435373 | ||
|
|
615f2abed1 | ||
|
|
ac926e84d7 | ||
|
|
8846b96905 | ||
|
|
677e99ffd9 | ||
|
|
a82b30179b | ||
|
|
ae4e9be739 | ||
|
|
7091a4016a | ||
|
|
24b292bf33 | ||
|
|
c00f006cdd | ||
|
|
ed47a41c9c | ||
|
|
39cc92853a | ||
|
|
34f7563bed | ||
|
|
964d7b38a8 | ||
|
|
17fbbca046 | ||
|
|
64faf296a6 | ||
|
|
f1c404a443 | ||
|
|
5ed1312406 | ||
|
|
00c18451cc | ||
|
|
b737b5c748 | ||
|
|
d852990314 | ||
|
|
182073f41e | ||
|
|
64d3a11866 | ||
|
|
4865e85655 | ||
|
|
3d6095d652 | ||
|
|
bd0e379d01 | ||
|
|
93395a8aa5 | ||
|
|
339437f47f | ||
|
|
80db7edf18 | ||
|
|
41e86a9e51 | ||
|
|
ef04678947 | ||
|
|
1a964a283f | ||
|
|
6c47375d18 | ||
|
|
c2abce1e35 | ||
|
|
b4b715f6e4 | ||
|
|
8a7c954d88 | ||
|
|
639f7605d1 | ||
|
|
fbcf9809c5 | ||
|
|
2474765820 | ||
|
|
ba4507bd22 | ||
|
|
2610c44ac9 | ||
|
|
f608282aa5 | ||
|
|
781f376bea | ||
|
|
6e1c3b95c2 | ||
|
|
0331eb7b25 | ||
|
|
c3a210907a | ||
|
|
3e0f1f16c1 | ||
|
|
c2be25271b | ||
|
|
bb131a7f53 | ||
|
|
c753865305 | ||
|
|
0bac7deccc | ||
|
|
9ada9f9e68 | ||
|
|
02b9e20ce3 | ||
|
|
3c8cf996b5 | ||
|
|
313e2eb06f | ||
|
|
41fcbba5cc | ||
|
|
ce9916a792 | ||
|
|
3f830458b1 | ||
|
|
a8e24b7629 | ||
|
|
80f5cb5f46 | ||
|
|
d45aa752c6 | ||
|
|
2a1e39a880 | ||
|
|
8f402ab303 | ||
|
|
644d2fe6cb | ||
|
|
b3fe781c19 | ||
|
|
69510a4e92 | ||
|
|
163d727ee1 | ||
|
|
20fdd9024d | ||
|
|
23e0a6601a | ||
|
|
47d0305bec | ||
|
|
f28c5533bc | ||
|
|
b89fe7c6b8 | ||
|
|
71ed223cf5 | ||
|
|
335491124f | ||
|
|
e45c00b83c | ||
|
|
aa5fcef2d4 | ||
|
|
01ddf6f4ea | ||
|
|
bbac9a8802 | ||
|
|
4374e78bbb | ||
|
|
6e0cf0b085 | ||
|
|
867a46bee9 | ||
|
|
4a89cb3b07 | ||
|
|
dc86f15b59 | ||
|
|
91c535ddbb | ||
|
|
f576b02858 | ||
|
|
eb27ba2c66 | ||
|
|
362a08c00f | ||
|
|
43ae90bd34 | ||
|
|
c34cbeca8d | ||
|
|
33f66fdae5 | ||
|
|
cc9720e8ef | ||
|
|
74b1b10e31 | ||
|
|
41ce72775e | ||
|
|
9876ab1e3e | ||
|
|
bd0b226f61 | ||
|
|
58501a74a7 | ||
|
|
80c1a04797 | ||
|
|
af3abe66f4 |
20
.github/ISSUE_TEMPLATE/issue_form.yml
vendored
@@ -32,17 +32,19 @@ body:
|
||||
options:
|
||||
- label: I have read and understand the [issue reporting policy](https://docs.raspap.com/issues/).
|
||||
required: true
|
||||
- label: I have read and followed the [common sense checklist](https://docs.raspap.com/troubleshooting/#dos-and-donts).
|
||||
required: true
|
||||
- label: I observed this bug on a clean install of a [supported OS](https://docs.raspap.com/#compatible-operating-systems).
|
||||
required: true
|
||||
- label: I have followed the [project prerequisites](https://docs.raspap.com/#quick-start).
|
||||
- label: I have followed the [project prerequisites](https://docs.raspap.com/quick_start/#quick-install).
|
||||
required: true
|
||||
- label: I have searched this repository for existing issues.
|
||||
required: true
|
||||
- label: I checked the [FAQ](https://docs.raspap.com/faq/) and [official documentation](https://docs.raspap.com/).
|
||||
required: true
|
||||
- label: I am using an [external wireless adapter](https://docs.raspap.com/issues/#external-hardware).
|
||||
required: true
|
||||
- label: I have generated a [RaspAP debug log](https://docs.raspap.com/ap-basics/#debug-log) and performed a [self-diagnosis](https://docs.raspap.com/ap-basics/#diagnosing-problems).
|
||||
required: false
|
||||
- label: I have generated a [RaspAP debug log](https://docs.raspap.com/troubleshooting/#debug-log) and performed a [self-diagnosis](https://docs.raspap.com/troubleshooting/#diagnosing-problems).
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
@@ -56,15 +58,15 @@ body:
|
||||
- Raspberry Pi OS (64-bit) Lite Bullseye
|
||||
- Raspberry Pi OS (32-bit) Lite Bullseye
|
||||
- Armbian 23.05 (Suni)
|
||||
- Debian Bookworm
|
||||
- Ubuntu Server 23.04 (Lunar)
|
||||
- Debian Bookworm
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: install
|
||||
attributes:
|
||||
label: Quick install or Manual setup?
|
||||
label: Installation method
|
||||
options:
|
||||
- Pre-built image
|
||||
- Quick install
|
||||
- Manual setup
|
||||
validations:
|
||||
@@ -90,6 +92,7 @@ body:
|
||||
- Raspberry Pi 3 Model B
|
||||
- Raspberry Pi Zero 2 W
|
||||
- Raspberry Pi Zero W
|
||||
- Raspberry Pi Compute Module
|
||||
- Orange Pi family
|
||||
- Other
|
||||
validations:
|
||||
@@ -99,8 +102,8 @@ body:
|
||||
attributes:
|
||||
label: RaspAP version
|
||||
options:
|
||||
- 3.2.5 (Latest)
|
||||
- Other
|
||||
- Latest
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -113,7 +116,6 @@ body:
|
||||
- Not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
|
||||
63
.github/scripts/add-raspap-stage.sh
vendored
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
mkdir -p stage-raspap/package-raspap
|
||||
|
||||
PRERUN_COMMANDS=""
|
||||
INSTALLER_COMMANDS=""
|
||||
|
||||
if [ -n "$INSIDERS_USER" ] && [ -n "$INSIDERS_TOKEN" ]; then
|
||||
echo ">>> Configuring for private Insiders build."
|
||||
|
||||
PRERUN_COMMANDS=$(
|
||||
cat <<-EOF
|
||||
echo "${INSIDERS_TOKEN}" > "\${ROOTFS_DIR}/etc/insiders_token"
|
||||
chmod 600 "\${ROOTFS_DIR}/etc/insiders_token"
|
||||
EOF
|
||||
)
|
||||
|
||||
INSTALLER_COMMANDS=$(
|
||||
cat <<-EOF
|
||||
INSIDERS_TOKEN_VALUE=\$(cat /etc/insiders_token)
|
||||
curl -sL https://install.raspap.com | bash -s -- \\
|
||||
--yes --insiders --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0 \\
|
||||
--name "${INSIDERS_USER}" --token "\$INSIDERS_TOKEN_VALUE"
|
||||
rm -f /etc/insiders_token
|
||||
EOF
|
||||
)
|
||||
else
|
||||
echo ">>> Configuring for public build."
|
||||
|
||||
INSTALLER_COMMANDS=$(
|
||||
cat <<-EOF
|
||||
curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
cat >stage-raspap/prerun.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
if [ ! -d "\${ROOTFS_DIR}" ]; then
|
||||
copy_previous
|
||||
fi
|
||||
${PRERUN_COMMANDS}
|
||||
EOF
|
||||
|
||||
cat >stage-raspap/package-raspap/00-run-chroot.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps
|
||||
|
||||
${INSTALLER_COMMANDS}
|
||||
|
||||
# Set Wi-Fi country to prevent RF kill
|
||||
raspi-config nonint do_wifi_country "US"
|
||||
|
||||
# Fetch RaspAP version and set MOTD
|
||||
RASPAP_VERSION=\$(curl -sL https://install.raspap.com | bash -s -- --version)
|
||||
echo "\$RASPAP_VERSION" | tee /etc/motd
|
||||
EOF
|
||||
|
||||
chmod +x stage-raspap/prerun.sh
|
||||
chmod +x stage-raspap/package-raspap/00-run-chroot.sh
|
||||
|
||||
echo "Build configuration complete."
|
||||
42
.github/workflows/release.yml
vendored
@@ -23,42 +23,36 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Add RaspAP Stage
|
||||
run: |
|
||||
mkdir -p stage-raspap/package-raspap &&
|
||||
{
|
||||
cat > stage-raspap/package-raspap/00-run-chroot.sh <<-EOF
|
||||
#!/bin/bash
|
||||
apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps
|
||||
curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0
|
||||
EOF
|
||||
} &&
|
||||
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
|
||||
{
|
||||
cat > stage-raspap/prerun.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
if [ ! -d "\${ROOTFS_DIR}" ]; then
|
||||
copy_previous
|
||||
fi
|
||||
EOF
|
||||
} &&
|
||||
chmod +x stage-raspap/prerun.sh
|
||||
run: sh ./.github/scripts/add-raspap-stage.sh
|
||||
|
||||
- name: Build RaspAP Image
|
||||
id: build
|
||||
uses: usimd/pi-gen-action@v1
|
||||
uses: RaspAP/pi-gen-action@v1.11.0
|
||||
with:
|
||||
image-name: "raspap-${{ github.ref_name }}-${{ matrix.arch }}"
|
||||
image-name: "raspap-trixie-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}"
|
||||
enable-ssh: 1
|
||||
stage-list: stage0 stage1 stage2 ./stage-raspap
|
||||
verbose-output: true
|
||||
pi-gen-version: ${{ matrix.pi_gen_version }}
|
||||
pi-gen-repository: RaspAP/pi-gen
|
||||
pi-gen-repository: RaspAP/pi-gen
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
asset_name: raspap-image-${{ github.ref_name }}-${{ matrix.arch }}.zip
|
||||
asset_name: "raspap-trixie-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}.img.zip"
|
||||
file: ${{ steps.build.outputs.image-path }}
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
tag: ${{ github.event.inputs.tag || github.ref }}
|
||||
overwrite: true
|
||||
|
||||
torrent:
|
||||
needs: build-raspap-image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Generate torrents for release
|
||||
uses: devopsx/action-torrent@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
local: false
|
||||
|
||||
3
.gitignore
vendored
@@ -4,6 +4,7 @@ yarn-error.log
|
||||
*.swp
|
||||
includes/config.php
|
||||
rootCA.pem
|
||||
vendor
|
||||
.env
|
||||
locale/**/*.mo
|
||||
app/net_activity
|
||||
app/js/plugins/
|
||||
|
||||
5
.gitmodules
vendored
@@ -1,3 +1,4 @@
|
||||
[submodule "plugins"]
|
||||
path = plugins
|
||||
url = https://github.com/RaspAP/plugins
|
||||
path = plugins
|
||||
url = https://github.com/RaspAP/plugins
|
||||
branch = master
|
||||
|
||||
45
BACKERS.md
@@ -1,4 +1,4 @@
|
||||
<img width="465" alt="Insiders logo" src="https://user-images.githubusercontent.com/229399/115766971-e19e1900-a3a8-11eb-8c6f-379deb4313d2.png">
|
||||
<img width="465" alt="Insiders logo" src="https://i.imgur.com/62TMUy5.png">
|
||||
|
||||
Development of RaspAP is made possible thanks to a sponsorware release model. This means that new features are first exclusively released to sponsors as part of **Insiders**. Read on to learn how sponsorship works, and how easy it is to get access to Insiders.
|
||||
|
||||
@@ -13,38 +13,47 @@ You can become a sponsor using your individual or organization's GitHub account.
|
||||
**Important**: If you're sponsoring [RaspAP](https://github.com/RaspAP/sponsors) through a GitHub organization, please send a short email to [sponsors@raspap.com](mailto:sponsors@raspap.com) with the name of your organization and the account that should be added as a collaborator.
|
||||
|
||||
## Exclusive features
|
||||
The following features are currently available exclusively to sponsors. A tangible side benefit of sponsorship is that Insiders are able to help steer future development of RaspAP. This is done through Insiders' access to discussions, feature requests, issues and pull requests in the private GitHub repository.
|
||||
The following features are currently available exclusively to sponsors. A tangible side benefit of sponsorship is that Insiders are able to help steer future development of RaspAP. This is done through your Insiders access to discussions, feature requests, issues and pull requests in the private GitHub repository.
|
||||
|
||||
✅ [Network device management](https://docs.raspap.com/net-devices/)
|
||||
✅ [Firewall settings](https://docs.raspap.com/firewall/)
|
||||
✅ [WPA3-Personal AP security](https://docs.raspap.com/ap-basics/#wpa3-personal)
|
||||
✅ [802.11w Protected Management Frames](https://docs.raspap.com/ap-basics/#80211w)
|
||||
✅ [Printable Wi-Fi signs](https://docs.raspap.com/ap-basics/#printable-signs)
|
||||
✅ [Drag & drop dashboard widgets](https://docs.raspap.com/ap-basics/#drag-drop-widgets)
|
||||
✅ [MAC address cloning](https://docs.raspap.com/net-devices/#changing-the-mac-address)
|
||||
✅ [Network diagnostics](https://docs.raspap.com/net-devices/#diagnostics)
|
||||
✅ [WireGuard VPN kill switch](https://docs.raspap.com/wireguard/#kill-switch)
|
||||
✅ [Dynamic DNS support](https://docs.raspap.com/dynamicdns/)
|
||||
✅ [Multiple WireGuard configs](https://docs.raspap.com/wireguard/#multiple-configs)
|
||||
✅ [Wireless LAN routing](https://docs.raspap.com/wlanrouting/)
|
||||
✅ [Custom user avatars](https://docs.raspap.com/authentication/#custom-user-avatars)
|
||||
✅ [WiFi repeater mode](https://docs.raspap.com/ap-basics/#wifi-repeater-mode)
|
||||
✅ [NTP Service](https://docs.raspap.com/ntp/)
|
||||
✅ [Limited privilege user role](https://docs.raspap.com/authentication/#limited-privilege-user-role)
|
||||
✅ [Limited privilege user role](https://docs.raspap.com/authentication/#limited-privilege-user-role)
|
||||
✅ [Tailscale VPN](https://docs.raspap.com/tailscale/)
|
||||
✅ [Inspect network adapters](https://docs.raspap.com/troubleshooting/#inspect-network-adapters)
|
||||
|
||||
Look for the list above to grow as we add more exclusive features. Be sure to visit this page from time to time to learn about what's new, check the [Insiders docs page](https://docs.raspap.com/insiders/) and follow [@RaspAP on Twitter](https://twitter.com/rasp_ap) to stay updated.
|
||||
Look for the list above to grow as we add more exlcusive features. Have an idea or suggestion for a future enhancement? Start or join an [Insiders discussion](https://github.com/RaspAP/raspap-insiders/discussions) and let us know!
|
||||
|
||||
## Funding targets
|
||||
Below is a list of funding targets. When a funding target is reached, the features that are tied to it are merged back into RaspAP and released to the public for general availability.
|
||||
|
||||
### $1000
|
||||
The second **Insiders Edition** includes the features listed above.
|
||||
### $1,500 - 3rd Insiders Edition
|
||||
The **3rd Insiders Edition** includes the exclusive features listed above.
|
||||
|
||||
### $500
|
||||
The [first Insiders Edition goal](https://docs.raspap.com/insiders/#500-1st-insiders-edition) was reached in December 2021. Thank you sponsors!
|
||||
### $500 - 1st Insiders Edition (completed)
|
||||
✅ Multiple OpenVPN client configs
|
||||
✅ OpenVPN certificate authentication
|
||||
✅ OpenVPN service logging
|
||||
✅ Night mode toggle
|
||||
✅ Restrict network to static clients
|
||||
✅ WireGuard support
|
||||
✅ Set AP transmit power
|
||||
|
||||
### $1,000 - 2nd Insiders Edition (completed)
|
||||
✅ Firewall settings
|
||||
✅ WPA3-Personal AP security
|
||||
✅ 802.11w Protected Management Frames
|
||||
✅ Printable Wi-Fi signs
|
||||
✅ Network diagnostics
|
||||
✅ Dynamic DNS
|
||||
✅ WireGuard kill switch
|
||||
✅ NTP Service
|
||||
|
||||
## Quarterly giving
|
||||
Beginning in 2022, each quarter 15% of all proceeds from Insiders will be donated directly to the [Raspberry Pi Foundation](https://www.raspberrypi.org/). The Raspberry Pi Foundation is a UK-based charity that works to put the power of computing and digital making into the hands of people all over the world.
|
||||
Each quarter, 15% of all proceeds from Insiders are [donated directly to the Raspberry Pi Foundation](https://docs.raspap.com/insiders/#quarterly-giving). The Raspberry Pi Foundation is a UK-based charity that works to put the power of computing and digital making into the hands of people all over the world.
|
||||
|
||||
[](https://www.youtube.com/watch?v=dEzg92g1LHw)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ RaspAP is made possible by a strong [community of developers](https://github.com
|
||||
|
||||
* [GitHub discussions](https://github.com/RaspAP/raspap-webgui/discussions)
|
||||
* [Discord chat](https://discord.gg/KVAsaAR)
|
||||
* [Twitter](https://twitter.com/rasp_ap)
|
||||
* [X](https://x.com/rasp_ap)
|
||||
* [Reddit](https://www.reddit.com/r/RaspAP/)
|
||||
|
||||
If you enjoy using RaspAP and would like to support our work financially, consider becoming an [Insider](https://github.com/sponsors/RaspAP).
|
||||
|
||||
64
README.md
@@ -1,14 +1,13 @@
|
||||

|
||||
[](https://github.com/raspap/raspap-webgui/releases) [](https://github.com/thibmaek/awesome-raspberry-pi) [](https://github.com/sponsors/RaspAP) [](https://app.travis-ci.com/RaspAP/raspap-webgui) [](https://crowdin.com/project/raspap) [](https://twitter.com/rasp_ap) [](https://reddit.com/r/RaspAP) [](https://discord.gg/KVAsaAR)
|
||||

|
||||
[](https://github.com/raspap/raspap-webgui/releases) [](https://github.com/thibmaek/awesome-raspberry-pi) [](https://github.com/sponsors/RaspAP) [](https://app.travis-ci.com/RaspAP/raspap-webgui) [](https://crowdin.com/project/raspap) [](https://twitter.com/rasp_ap) [](https://reddit.com/r/RaspAP) [](https://discord.gg/KVAsaAR)
|
||||
|
||||
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our [custom OS images](#pre-built-image), [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, [WireGuard](https://docs.raspap.com/wireguard/), [Tailscale](https://docs.raspap.com/tailscale/) and [OpenVPN](https://docs.raspap.com/openvpn/) support, [SSL certificates](https://docs.raspap.com/ssl/), [ad blocking](#ad-blocking), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
|
||||
|
||||
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
|
||||
|
||||
RaspAP has been featured on sites such as [Instructables](http://www.instructables.com/id/Raspberry-Pi-As-Completely-Wireless-Router/), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/) and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in countless projects.
|
||||
RaspAP has been featured by [PC World](https://www.pcwelt.de/article/1789512/raspberry-pi-als-wlan-router.html), [MSN](https://www.msn.com/en-us/news/technology/4-reasons-i-installed-raspap-on-my-raspberry-pi/ar-AA1GLHdE), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/), and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in [countless projects](https://github.com/RaspAP/raspap-awesome#projects).
|
||||
|
||||
We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use this with [your own projects](https://github.com/raspap/raspap-awesome).
|
||||
|
||||

|
||||

|
||||
<img width="32.5%" alt="Wifi Client" src="https://github.com/user-attachments/assets/95696ddc-da84-4339-97cc-f2a173054664">
|
||||
<img width="32.5%" alt="Hotspot" src="https://github.com/user-attachments/assets/c1c4de15-3ff2-4d3c-a7af-339c24896749">
|
||||
<img width="32.5%" alt="Adblock" src="https://github.com/user-attachments/assets/ab925687-8407-4bec-a952-9dc6a2675f49">
|
||||
@@ -18,15 +17,13 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
|
||||
|
||||
## Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick installer](#quick-installer)
|
||||
- [Quick start](#quick-start)
|
||||
- [Join Insiders](#join-insiders)
|
||||
- [WireGuard support](#wireguard-support)
|
||||
- [OpenVPN support](#openvpn-support)
|
||||
- [VPN Provider support](#vpn-provider-support)
|
||||
- [Ad Blocking](#ad-blocking)
|
||||
- [Bridged AP](#bridged-ap)
|
||||
- [Simultaneous AP and Wifi client](#simultaneous-ap-and-wifi-client)
|
||||
- [Manual installation](#manual-installation)
|
||||
- [802.11ac 5GHz support](#80211ac-5ghz-support)
|
||||
- [Supported operating systems](#supported-operating-systems)
|
||||
@@ -38,30 +35,43 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
|
||||
- [Reporting issues](#reporting-issues)
|
||||
- [License](#license)
|
||||
|
||||
## Prerequisites
|
||||
Start with a clean install of the [latest release of Raspberry Pi OS Lite](https://www.raspberrypi.com/software/operating-systems/). Both the 32- and 64-bit Lite versions are supported. The Raspberry Pi OS desktop distro is [unsupported](https://docs.raspap.com/faq/#distros).
|
||||
## Quick start
|
||||
RaspAP gives you two different ways to get up and running quickly. The simplest and recommended approach is to use a custom Raspberry Pi OS image with RaspAP preinstalled. This option eliminates guesswork and gives you a base upon which to build. Alternatively, you may execute the Quick installer on an existing [compatible OS](https://docs.raspap.com/#compatible-operating-systems).
|
||||
|
||||
### Pre-built image
|
||||
Custom Raspberry Pi OS Lite images with the latest RaspAP are available for [direct download](https://github.com/RaspAP/raspap-webgui/releases/latest). This includes both 32- and 64-bit builds for ARM architectures.
|
||||
|
||||
| Operating system | Debian version | Kernel version | RaspAP version | Size |
|
||||
| ---------------------| ---------------|-----------------|----------------|-------|
|
||||
| Raspberry Pi OS (64-bit) Lite | 12 (bookworm) | 6.6 | Latest | 777 MB|
|
||||
| Raspberry Pi OS (32-bit) Lite | 12 (bookworm) | 6.6 | Latest | 805 MB|
|
||||
|
||||
These images are automatically generated with each release of RaspAP. You may choose between an `arm64` or `armhf` (32-bit) based build. Refer to [this resource](https://www.raspberrypi.com/software/operating-systems/) to ensure compatibility with your hardware.
|
||||
|
||||
After downloading your desired image from the [latest release page](https://github.com/RaspAP/raspap-webgui/releases/latest), use a utility such as the Raspberry Pi Imager or [balenaEtcher](https://www.balena.io/etcher) to flash the OS image onto a microSD card. Insert the card into your device and boot it up. The latest RaspAP release version with the most popular optional components will be active and ready for you to configure.
|
||||
|
||||
### Quick installer
|
||||
Alternatively, start with a clean install of a [latest release of Raspberry Pi OS](https://www.raspberrypi.org/software/operating-systems/). Both the 32- and 64-bit release versions are supported, as well as the latest 64-bit Desktop distribution.
|
||||
|
||||
Update RPi OS to its latest version, including the kernel and firmware, followed by a reboot:
|
||||
|
||||
1. Update Raspbian, including the kernel and firmware, followed by a reboot:
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get full-upgrade
|
||||
sudo reboot
|
||||
```
|
||||
2. Set the "WLAN country" option in `raspi-config`'s **Localisation Options**: `sudo raspi-config`
|
||||
Set the WiFi country in raspi-config's **Localisation Options**: `sudo raspi-config`.
|
||||
|
||||
3. If you have a device without an onboard wireless chipset, the [**Edimax Wireless 802.11b/g/n nano USB adapter**](https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_n150/ew-7811un) is an excellent option – it's small, cheap and has good driver support.
|
||||
|
||||
With the prerequisites done, you can proceed with either the Quick installer or Manual installation steps below.
|
||||
|
||||
## Quick installer
|
||||
Install RaspAP from your device's shell prompt:
|
||||
```sh
|
||||
curl -sL https://install.raspap.com | bash
|
||||
```
|
||||
The [installer](https://docs.raspap.com/quick/) will complete the steps in the manual installation (below) for you.
|
||||
|
||||
After the reboot at the end of the installation the wireless network will be
|
||||
configured as an access point as follows:
|
||||
The Quick installer will respond to several [command line arguments](https://docs.raspap.com/quick/), or switches, to customize your installation in a variety of ways, or install one of RaspAP's optional helper tools.
|
||||
|
||||
### Initial settings
|
||||
After completing either of these setup options, the wireless AP network will be configured as follows:
|
||||
|
||||
* IP address: 10.3.141.1
|
||||
* Username: admin
|
||||
* Password: secret
|
||||
@@ -69,7 +79,7 @@ configured as an access point as follows:
|
||||
* SSID: `raspi-webgui`
|
||||
* Password: ChangeMe
|
||||
|
||||
**Note:** As the name suggests, the Quick Installer is a great way to quickly setup a new AP. However, it does not automagically detect the unique configuration of your system. Best results are obtained by connecting to ethernet (`eth0`) or as a WiFi client, also known as managed mode, with `wlan0`. For the latter, refer to [this FAQ](https://docs.raspap.com/faq/#headless). Special instructions for the Pi Zero W are [available here](https://docs.raspap.com/ap-sta/).
|
||||
It's _strongly recommended_ that your first post-install action is to change the default admin [authentication](https://docs.raspap.com/authentication/) settings. Thereafter, your AP's [basic settings](https://docs.raspap.com/ap-basics/) and many [advanced options](https://docs.raspap.com/ap-basics#advanced-options) are now ready to be modified by RaspAP.
|
||||
|
||||
Please [read this](https://docs.raspap.com/issues/) before reporting an issue.
|
||||
|
||||
@@ -118,11 +128,6 @@ By default RaspAP configures a routed AP for your clients to connect to. A bridg
|
||||
|
||||
More information on Bridged AP mode is provided [in our documentation](https://docs.raspap.com/bridged/).
|
||||
|
||||
## Simultaneous AP and Wifi client
|
||||
RaspAP lets you create an AP with a Wifi client configuration, often called [AP-STA mode](https://docs.raspap.com/ap-sta/). With your system configured in managed mode, enable the AP from the **Advanced** tab of **Configure hotspot** by sliding the **Wifi client AP mode** toggle. Save settings and start the hotspot. The managed mode AP is functional without restart.
|
||||
|
||||
**Note:** This option is disabled until you configure your system as a wireless client. For a device operating in [managed mode](https://docs.raspap.com/faq/#headless) without an `eth0` connection, this configuration must be enabled [_before_ a reboot](https://docs.raspap.com/ap-sta/).
|
||||
|
||||
## Manual installation
|
||||
Detailed manual setup instructions are provided [on our documentation site](https://docs.raspap.com/manual/).
|
||||
|
||||
@@ -139,11 +144,10 @@ RaspAP was originally made for Raspbian, but now also installs on the following
|
||||
| Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official |
|
||||
| Raspberry Pi OS | (64-bit) Lite Bullseye | ARM | Official |
|
||||
| Raspberry Pi OS | (32-bit) Lite Bullseye | ARM | Official |
|
||||
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Official |
|
||||
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Beta |
|
||||
| Debian | Bookworm | ARM / x86_64 | Beta |
|
||||
| Ubuntu | Server 23.04 (Lunar) | ARM / x86_64 | Beta |
|
||||
|
||||
<img src="https://github.com/RaspAP/raspap-webgui/assets/229399/6fe62f2d-631a-46c9-8ceb-83ebf0ade6a9" style="width:640px;" />
|
||||
<img src="https://i.imgur.com/XiAJNKb.png" style="width:480px;" />
|
||||
|
||||
You are also encouraged to use RaspAP's community-led [Docker container](#docker-support). Please note that "supported" is not a guarantee. If you are able to improve support for your preferred distro, we encourage you to [actively contribute](#how-to-contribute) to the project.
|
||||
|
||||
|
||||
@@ -1,55 +1,61 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
define('BLOCKLISTS_FILE', __DIR__ . '/../../config/blocklists.json');
|
||||
|
||||
if (isset($_POST['blocklist_id'])) {
|
||||
$blocklist_id = escapeshellcmd($_POST['blocklist_id']);
|
||||
$blocklist_id = $_POST['blocklist_id'];
|
||||
$json = file_get_contents(BLOCKLISTS_FILE);
|
||||
$allLists = json_decode($json, true);
|
||||
|
||||
switch ($blocklist_id) {
|
||||
case "StevenBlack/hosts \(default\)":
|
||||
$list_url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
|
||||
$dest_file = "hostnames.txt";
|
||||
break;
|
||||
case "badmojr/1Hosts \(Mini\)":
|
||||
$list_url = "https://badmojr.github.io/1Hosts/mini/hosts.txt";
|
||||
$dest_file = "hostnames.txt";
|
||||
break;
|
||||
case "badmojr/1Hosts \(Lite\)":
|
||||
$list_url = "https://badmojr.github.io/1Hosts/Lite/hosts.txt";
|
||||
$dest_file = "hostnames.txt";
|
||||
break;
|
||||
case "badmojr/1Hosts \(Pro\)":
|
||||
$list_url = "https://badmojr.github.io/1Hosts/Pro/hosts.txt";
|
||||
$dest_file = "hostnames.txt";
|
||||
break;
|
||||
case "badmojr/1Hosts \(Xtra\)":
|
||||
$list_url = "https://badmojr.github.io/1Hosts/Xtra/hosts.txt";
|
||||
$dest_file = "hostnames.txt";
|
||||
break;
|
||||
case "oisd/big \(default\)":
|
||||
$list_url = "https://big.oisd.nl/dnsmasq";
|
||||
$dest_file = "domains.txt";
|
||||
break;
|
||||
case "oisd/small":
|
||||
$list_url = "https://small.oisd.nl/dnsmasq";
|
||||
$dest_file = "domains.txt";
|
||||
break;
|
||||
case "oisd/nsfw":
|
||||
$list_url = "https://nsfw.oisd.nl/dnsmasq";
|
||||
$dest_file = "domains.txt";
|
||||
break;
|
||||
if ($allLists === null) {
|
||||
echo json_encode([
|
||||
'return' => 3,
|
||||
'output' => ['Failed to parse blocklists.json']
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
$blocklist = $list_url . $dest_file;
|
||||
$dest = substr($dest_file, 0, strrpos($dest_file, "."));
|
||||
$flatList = flattenList($allLists);
|
||||
|
||||
if (!isset($flatList[$blocklist_id])) {
|
||||
echo json_encode(['return' => 1, 'output' => ['Invalid blocklist ID']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$list_url = escapeshellcmd($flatList[$blocklist_id]['list_url']);
|
||||
$dest_file = escapeshellcmd($flatList[$blocklist_id]['dest_file']);
|
||||
$dest = pathinfo($dest_file, PATHINFO_FILENAME);
|
||||
$scriptPath = RASPI_CONFIG . '/adblock/update_blocklist.sh';
|
||||
|
||||
if (!file_exists($scriptPath)) {
|
||||
echo json_encode([
|
||||
'return' => 5,
|
||||
'output' => ["Update script not found: $scriptPath"]
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
exec("sudo $scriptPath $list_url $dest_file " . RASPI_ADBLOCK_LISTPATH, $output, $return_var);
|
||||
echo json_encode([
|
||||
'return' => $return_var,
|
||||
'output' => $output,
|
||||
'list' => $dest
|
||||
]);
|
||||
|
||||
exec("sudo /etc/raspap/adblock/update_blocklist.sh $list_url $dest_file " .RASPI_ADBLOCK_LISTPATH, $return);
|
||||
$jsonData = ['return'=>$return,'list'=>$dest];
|
||||
echo json_encode($jsonData);
|
||||
} else {
|
||||
$jsonData = ['return'=>2,'output'=>['Error getting data']];
|
||||
echo json_encode($jsonData);
|
||||
echo json_encode(['return' => 2, 'output' => ['No blocklist ID provided']]);
|
||||
}
|
||||
|
||||
function flattenList(array $grouped): array {
|
||||
$flat = [];
|
||||
foreach ($grouped as $group) {
|
||||
foreach ($group as $name => $meta) {
|
||||
$flat[$name] = $meta;
|
||||
}
|
||||
}
|
||||
return $flat;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$interface = filter_input(INPUT_GET, 'inet', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (filter_input(INPUT_GET, 'tu') == 'h') {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
<?php
|
||||
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$return = 0;
|
||||
$path = "../../config";
|
||||
$configs = array(
|
||||
array("src" => $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
|
||||
array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
|
||||
array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
|
||||
array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
|
||||
);
|
||||
$return = 0;
|
||||
$path = "../../config";
|
||||
$configs = array(
|
||||
array("src" => $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
|
||||
array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
|
||||
array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
|
||||
array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
|
||||
);
|
||||
|
||||
foreach ($configs as $config) {
|
||||
try {
|
||||
$tmp = file_get_contents($config["src"]);
|
||||
file_put_contents($config["tmp"], $tmp);
|
||||
system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
|
||||
} catch (Exception $e) {
|
||||
$return = $e->getCode();
|
||||
}
|
||||
foreach ($configs as $config) {
|
||||
try {
|
||||
$tmp = file_get_contents($config["src"]);
|
||||
file_put_contents($config["tmp"], $tmp);
|
||||
system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
|
||||
} catch (Exception $e) {
|
||||
$return = $e->getCode();
|
||||
}
|
||||
$jsonData = ['return'=>$return];
|
||||
echo json_encode($jsonData);
|
||||
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$jsonData = ['return'=>$return];
|
||||
echo json_encode($jsonData);
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
exec("ls /sys/class/net | grep -v lo", $interfaces);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
|
||||
@@ -18,3 +17,4 @@ foreach ($hostapdconfig as $hostapdconfigline) {
|
||||
};
|
||||
$channel = intval($arrConfig['channel']);
|
||||
echo json_encode($channel);
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../src/RaspAP/Parsers/IwParser.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['interface'])) {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/functions.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['interface'])) {
|
||||
|
||||
@@ -1,68 +1,19 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
use RaspAP\Networking\Hotspot\DhcpcdManager;
|
||||
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
$dhcpcdManager = new DhcpcdManager();
|
||||
|
||||
$interface = $_POST['iface'];
|
||||
|
||||
if (isset($interface)) {
|
||||
// fetch dnsmasq.conf settings for interface
|
||||
exec('cat '. escapeshellarg(RASPI_DNSMASQ_PREFIX.$interface.'.conf'), $return);
|
||||
$conf = ParseConfig($return);
|
||||
|
||||
$dhcpdata['DHCPEnabled'] = empty($conf) ? false : true;
|
||||
if (is_string($conf['dhcp-range'])) {
|
||||
$arrRange = explode(",", $conf['dhcp-range']);
|
||||
} else {
|
||||
$arrRange = explode(",", $conf['dhcp-range'][0]);
|
||||
}
|
||||
$dhcpdata['RangeStart'] = $arrRange[0] ?? null;
|
||||
$dhcpdata['RangeEnd'] = $arrRange[1] ?? null;
|
||||
$dhcpdata['RangeMask'] = $arrRange[2] ?? null;
|
||||
$dhcpdata['leaseTime'] = $arrRange[3] ?? null;
|
||||
$dhcpHost = $conf["dhcp-host"] ?? null;
|
||||
$dhcpHost = empty($dhcpHost) ? [] : $dhcpHost;
|
||||
$dhcpdata['dhcpHost'] = is_array($dhcpHost) ? $dhcpHost : [ $dhcpHost ];
|
||||
$upstreamServers = is_array($conf['server'] ?? null) ? $conf['server'] : [ $conf['server'] ?? '' ];
|
||||
$dhcpdata['upstreamServersEnabled'] = empty($conf['server']) ? false: true;
|
||||
$dhcpdata['upstreamServers'] = array_filter($upstreamServers);
|
||||
preg_match('/([0-9]*)([a-z])/i', $dhcpdata['leaseTime'], $arrRangeLeaseTime);
|
||||
$dhcpdata['leaseTime'] = $arrRangeLeaseTime[1];
|
||||
$dhcpdata['leaseTimeInterval'] = $arrRangeLeaseTime[2];
|
||||
if (isset($conf['dhcp-option'])) {
|
||||
$arrDns = explode(",", $conf['dhcp-option']);
|
||||
if ($arrDns[0] == '6') {
|
||||
if (count($arrDns) > 1) {
|
||||
$dhcpdata['DNS1'] = $arrDns[1] ?? null;
|
||||
}
|
||||
if (count($arrDns) > 2) {
|
||||
$dhcpdata['DNS2'] = $arrDns[2] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetch dhcpcd.conf settings for interface
|
||||
$conf = file_get_contents(RASPI_DHCPCD_CONFIG);
|
||||
preg_match('/^#\sRaspAP\s'.$interface.'\s.*?(?=\s*+$)/ms', $conf, $matched);
|
||||
preg_match('/metric\s(\d*)/', $matched[0], $metric);
|
||||
preg_match('/static\sip_address=(.*)/', $matched[0], $static_ip);
|
||||
preg_match('/static\srouters=(.*)/', $matched[0], $static_routers);
|
||||
preg_match('/static\sdomain_name_server=(.*)/', $matched[0], $static_dns);
|
||||
preg_match('/fallback\sstatic_'.$interface.'/', $matched[0], $fallback);
|
||||
preg_match('/(?:no)?gateway/', $matched[0], $gateway);
|
||||
preg_match('/nohook\swpa_supplicant/', $matched[0], $nohook_wpa_supplicant);
|
||||
$dhcpdata['Metric'] = $metric[1] ?? null;
|
||||
$dhcpdata['StaticIP'] = isset($static_ip[1]) && strpos($static_ip[1], '/') !== false
|
||||
? substr($static_ip[1], 0, strpos($static_ip[1], '/'))
|
||||
: ($static_ip[1] ?? '');
|
||||
$dhcpdata['SubnetMask'] = cidr2mask($static_ip[1] ?? '');
|
||||
$dhcpdata['StaticRouters'] = $static_routers[1] ?? null;
|
||||
$dhcpdata['StaticDNS'] = $static_dns[1] ?? null;
|
||||
$dhcpdata['FallbackEnabled'] = empty($fallback) ? false: true;
|
||||
$dhcpdata['DefaultRoute'] = $gateway[0] == "gateway";
|
||||
$dhcpdata['NoHookWPASupplicant'] = ($nohook_wpa_supplicant[0] ?? '') == "nohook wpa_supplicant";
|
||||
$dhcpdata = $dhcpcdManager->getInterfaceConfig($interface);
|
||||
echo json_encode($dhcpdata);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/locale.php';
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
// fetch wg client.conf
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$entity = escapeshellcmd($_POST['entity']);
|
||||
$entity = escapeshellarg($_POST['entity']);
|
||||
|
||||
if (isset($entity)) {
|
||||
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/defaults.php';
|
||||
require_once '../../includes/functions.php';
|
||||
require_once '../../includes/wifi_functions.php';
|
||||
|
||||
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||
|
||||
$wifi = new WiFiManager();
|
||||
|
||||
$networks = [];
|
||||
$network = null;
|
||||
$ssid = null;
|
||||
|
||||
knownWifiStations($networks);
|
||||
nearbyWifiStations($networks, !isset($_REQUEST["refresh"]));
|
||||
connectedWifiStations($networks);
|
||||
sortNetworksByRSSI($networks);
|
||||
foreach ($networks as $ssid => $network) $networks[$ssid]["ssidutf8"] = ssid2utf8( $ssid );
|
||||
$wifi->knownWifiStations($networks);
|
||||
$wifi->nearbyWifiStations($networks, !isset($_REQUEST["refresh"]));
|
||||
$wifi->connectedWifiStations($networks);
|
||||
$wifi->sortNetworksByRSSI($networks);
|
||||
|
||||
foreach ($networks as $ssid => $network) $networks[$ssid]["ssidutf8"] = $wifi->ssid2utf8( $ssid );
|
||||
|
||||
$connected = array_filter($networks, function($n) { return $n['connected']; } );
|
||||
$known = array_filter($networks, function($n) { return !$n['connected'] && $n['configured']; } );
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../src/RaspAP/Plugins/PluginInstaller.php';
|
||||
|
||||
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
|
||||
$plugin_uri = $_POST['plugin_uri'] ?? null;
|
||||
$plugin_version = $_POST['plugin_version'] ?? null;
|
||||
$install_path = $_POST['install_path'] ?? null;
|
||||
|
||||
if (isset($plugin_uri) && isset($plugin_version)) {
|
||||
$archiveUrl = rtrim($plugin_uri, '/') . '/archive/refs/tags/' . $plugin_version .'.zip';
|
||||
|
||||
if (isset($plugin_uri, $plugin_version, $install_path)) {
|
||||
try {
|
||||
$return = $pluginInstaller->installPlugin($archiveUrl);
|
||||
$return = $pluginInstaller->installPlugin($plugin_uri, $plugin_version, $install_path);
|
||||
echo json_encode($return);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(422); // Unprocessable Content
|
||||
http_response_code(422); // unprocessable content
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
} else {
|
||||
http_response_code(400); // Bad Request
|
||||
echo json_encode(['error' => 'Plugin URI and version are required']);
|
||||
echo json_encode(['error' => 'Plugin URI, version, and install path are required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$lastActivity = $_SESSION['lastActivity'] ?? time();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$action = escapeshellcmd($_POST['a']);
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/defaults.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$uri = RASPI_API_ENDPOINT;
|
||||
preg_match('/(\d+(\.\d+)+)/', RASPI_VERSION, $matches);
|
||||
$thisRelease = $matches[0];
|
||||
$uri = RASPI_API_ENDPOINT;
|
||||
preg_match('/(\d+(\.\d+)+)/', RASPI_VERSION, $matches);
|
||||
$thisRelease = $matches[0];
|
||||
|
||||
$json = shell_exec("wget --timeout=5 --tries=1 $uri -qO -");
|
||||
$data = json_decode($json, true);
|
||||
$tagName = $data['tag_name'];
|
||||
$updateAvailable = checkReleaseVersion($thisRelease, $tagName);
|
||||
$json = shell_exec("wget --timeout=5 --tries=1 $uri -qO -");
|
||||
$data = json_decode($json, true);
|
||||
$tagName = $data['tag_name'];
|
||||
$updateAvailable = checkReleaseVersion($thisRelease, $tagName);
|
||||
|
||||
$response['tag'] = $tagName;
|
||||
$response['update'] = $updateAvailable;
|
||||
echo json_encode($response);
|
||||
$response['tag'] = $tagName;
|
||||
$response['update'] = $updateAvailable;
|
||||
echo json_encode($response);
|
||||
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$root = getenv("DOCUMENT_ROOT");
|
||||
exec('sudo '.RASPI_CONFIG.'/system/debuglog.sh -i '.$root, $return);
|
||||
$root = getenv("DOCUMENT_ROOT");
|
||||
exec('sudo '.RASPI_CONFIG.'/system/debuglog.sh -i '.$root, $return);
|
||||
|
||||
$logOutput = implode(PHP_EOL, $return);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$filePath = $tempDir . DIRECTORY_SEPARATOR . RASPI_DEBUG_LOG;
|
||||
$handle = fopen($filePath, "w");
|
||||
fwrite($handle, $logOutput);
|
||||
fclose($handle);
|
||||
echo json_encode($filePath);
|
||||
|
||||
$logOutput = implode(PHP_EOL, $return);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$filePath = $tempDir . DIRECTORY_SEPARATOR . RASPI_DEBUG_LOG;
|
||||
$handle = fopen($filePath, "w");
|
||||
fwrite($handle, $logOutput);
|
||||
fclose($handle);
|
||||
echo json_encode($filePath);
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$tempDir = sys_get_temp_dir();
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
// set installer path + options
|
||||
$path = getenv("DOCUMENT_ROOT");
|
||||
$opts = " --update --yes --path $path";
|
||||
$installer = "sudo /etc/raspap/system/raspbian.sh";
|
||||
$execUpdate = $installer.$opts;
|
||||
// set installer path + options
|
||||
$path = getenv("DOCUMENT_ROOT");
|
||||
$opts = " --update --yes --check 0 --path $path";
|
||||
$installer = "sudo /etc/raspap/system/raspbian.sh";
|
||||
$execUpdate = $installer.$opts;
|
||||
|
||||
$response = shell_exec($execUpdate);
|
||||
echo json_encode($response);
|
||||
$response = shell_exec($execUpdate);
|
||||
echo json_encode($response);
|
||||
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$logFile = '/tmp/raspap_install.log';
|
||||
|
||||
316
app/css/all.css
@@ -9,6 +9,7 @@ License: GNU General Public License v3.0
|
||||
:root {
|
||||
--raspap-content-main: #495057;
|
||||
--raspap-text-muted: #858796;
|
||||
--raspap-text-light: #999999;
|
||||
--raspap-brand-color: #2b8080;
|
||||
--raspap-offwhite: #faf9f6;
|
||||
}
|
||||
@@ -47,8 +48,15 @@ th {
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
margin-top: 0.5em;
|
||||
margin-left: 0.5em;
|
||||
margin-top: 0.2em;
|
||||
margin-left: 0.7em;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
margin-left: 1.2rem;
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
@@ -129,7 +137,7 @@ th {
|
||||
|
||||
.loading-spinner::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 120px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh / 4);
|
||||
@@ -137,10 +145,10 @@ th {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--raspap-text-muted);
|
||||
content: "\f1ce"; /* Unicode for the circle-notch icon */
|
||||
content: "\f1ce";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900; /* Adjust as needed */
|
||||
font-size: 54px; /* Adjust icon size as needed */
|
||||
font-weight: 900;
|
||||
font-size: 54px;
|
||||
animation: spin 1.2s linear infinite;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -168,12 +176,7 @@ th {
|
||||
}
|
||||
|
||||
canvas#divDBChartBandwidthhourly {
|
||||
height: 350px!important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 150px;
|
||||
width: 200px;
|
||||
height: 509px!important;
|
||||
}
|
||||
|
||||
.dbChart {
|
||||
@@ -190,7 +193,7 @@ canvas#divDBChartBandwidthhourly {
|
||||
}
|
||||
|
||||
.check-progress {
|
||||
color: #999;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.fa-check {
|
||||
@@ -388,3 +391,290 @@ textarea.plugin-log {
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.connections-left,
|
||||
.connections-right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.connection-item {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
z-index: 5;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.connection-right {
|
||||
align-items: center;
|
||||
margin-left: 10rem;
|
||||
}
|
||||
|
||||
.connections-left i {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.connections-left i:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.connections-left i:last-child {
|
||||
margin-bottom: 0;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.center-device {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.center-device-top {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.client-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row-reverse;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.client-count {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clients-status {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.dashed-lines,
|
||||
.solid-lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 1rem;
|
||||
left: 112px;
|
||||
}
|
||||
|
||||
.dashed-lines-right,
|
||||
.solid-lines-right {
|
||||
left: -80px;
|
||||
}
|
||||
|
||||
.solid-lines, .solid-lines-right {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dashed-lines, .dashed-lines-right {
|
||||
z-index 0;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
.wifi-bands {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.band {
|
||||
padding: 0.25rem 1rem;
|
||||
border: 2px solid var(--raspap-text-light);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
font-weight: 600;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.band.active {
|
||||
border-color: var(--raspap-theme-color);
|
||||
color: var(--raspap-theme-color);
|
||||
}
|
||||
|
||||
.device-label {
|
||||
font-size: 1.3rem;
|
||||
text-align: center;
|
||||
color: var(--raspap-theme-color);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.3rem;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-item .fa-stack {
|
||||
width: 1.5em!important;
|
||||
}
|
||||
|
||||
.connection-item>i {
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.connection-item .fa-stack {
|
||||
min-width: 2.5em;
|
||||
}
|
||||
|
||||
.connections-left>.connection-item>span {
|
||||
color: var(--raspap-text-light);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: var(--raspap-text-light)!important;
|
||||
}
|
||||
|
||||
a.inactive:hover,
|
||||
a.inactive:focus {
|
||||
color: var(--raspap-text-light) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.connection-item a > span:not(.fa-stack) {
|
||||
display: none!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.connections-right,
|
||||
.connections-left {
|
||||
display: none!important;
|
||||
}
|
||||
.dashboard-container {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
.device-status {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.clients-mobile {
|
||||
display: flex!important;
|
||||
flex-direction: row!important;
|
||||
}
|
||||
}
|
||||
.connection-item.active > span {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.connection-item.active > i {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.status-item.active > span {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.status-item.active > i {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
|
||||
.clients-mobile {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.client-type {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.client-type i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--raspap-theme-color);
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid var(--raspap-theme-color);
|
||||
}
|
||||
|
||||
.client-type i.badge-icon {
|
||||
font-size: 0.7rem;
|
||||
background: var(--raspap-theme-color);
|
||||
color: var(--raspap-offwhite);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.client-count {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: var(--raspap-theme-color);
|
||||
color: var(--raspap-offwhite);
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.device-illustration {
|
||||
min-width: 220px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.led-pulse {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
.hostapd-led {
|
||||
color: #28a745;
|
||||
opacity: 1;
|
||||
transition: opacity 0.05s;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<?php
|
||||
require_once '../../includes/functions.php';
|
||||
$color = getColorOpt();
|
||||
$allCss = 'all.css';
|
||||
?>
|
||||
/*
|
||||
Theme Name: RaspAP default
|
||||
@@ -11,12 +12,12 @@ Description: Default theme for RaspAP
|
||||
License: GNU General Public License v3.0
|
||||
*/
|
||||
|
||||
@import url('all.css');
|
||||
@import url('<?= $allCss ?>?v=<?= filemtime($allCss); ?>');
|
||||
|
||||
:root {
|
||||
--raspap-theme-color: <?php echo $color; ?>;
|
||||
--raspap-theme-lighter: <?php echo lightenColor($color, 20); ?>;
|
||||
--raspap-theme-darker: <?php echo darkenColor($color, 20); ?>;
|
||||
--raspap-theme-color: <?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;
|
||||
--raspap-theme-lighter: <?php echo htmlspecialchars(lightenColor($color, 20), ENT_QUOTES, 'UTF-8'); ?>;
|
||||
--raspap-theme-darker: <?php echo htmlspecialchars(darkenColor($color, 20), ENT_QUOTES, 'UTF-8'); ?>;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/dist/icons/mstile-150x150.png"/>
|
||||
<TileColor>#b91d47</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
BIN
app/icons/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
app/icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
3
app/icons/favicon.svg
Normal file
|
After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="240.000000pt" height="240.000000pt" viewBox="0 0 240.000000 240.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,240.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M867 2334 c-3 -3 -3 -20 -1 -37 2 -18 6 -54 8 -82 2 -27 5 -53 5 -56
|
||||
1 -4 31 -6 69 -6 63 0 162 -9 207 -19 11 -2 31 -6 45 -8 14 -2 59 -14 100 -27
|
||||
41 -13 80 -23 85 -24 6 0 12 -2 15 -5 3 -3 37 -20 75 -38 161 -78 335 -219
|
||||
436 -357 55 -74 121 -175 115 -175 -3 0 10 -30 28 -67 18 -38 37 -81 41 -98 5
|
||||
-16 9 -34 11 -40 27 -94 44 -173 48 -237 2 -15 19 -20 99 -23 95 -5 101 -1 86
|
||||
60 -4 17 -10 48 -13 70 -27 197 -131 443 -258 613 -52 69 -252 272 -269 272
|
||||
-5 0 -18 8 -28 18 -37 32 -50 42 -61 42 -5 0 -10 4 -10 8 0 4 -15 15 -32 23
|
||||
-18 9 -53 27 -78 41 -95 52 -303 123 -390 133 -19 2 -46 7 -60 10 -34 8 -266
|
||||
15 -273 9z"/>
|
||||
<path d="M895 1867 c3 -50 6 -92 8 -93 1 -1 40 -4 87 -8 114 -8 139 -12 212
|
||||
-37 272 -91 470 -300 547 -578 11 -39 17 -71 13 -71 -3 0 -2 -4 4 -10 5 -5 50
|
||||
-12 100 -15 l91 -6 -3 26 c-15 141 -91 333 -182 460 -47 65 -152 170 -222 222
|
||||
-164 122 -367 191 -582 198 l-79 2 6 -90z"/>
|
||||
<path d="M747 1604 c-1 -2 -14 -5 -27 -7 -245 -47 -453 -199 -562 -412 -47
|
||||
-91 -81 -232 -81 -340 -1 -88 19 -215 34 -215 5 0 6 -7 3 -15 -4 -8 -2 -21 3
|
||||
-28 5 -6 20 -38 33 -70 12 -31 26 -57 31 -57 5 0 9 -6 9 -13 0 -7 9 -23 20
|
||||
-35 11 -12 20 -25 20 -29 0 -12 80 -93 91 -93 6 0 8 -4 5 -8 -11 -18 215 -159
|
||||
235 -147 5 4 9 2 9 -4 0 -20 189 -58 280 -56 87 2 213 25 245 44 6 3 12 6 15
|
||||
6 21 3 50 18 50 26 0 6 5 7 10 4 13 -8 128 67 193 125 67 60 164 196 159 223
|
||||
-1 4 1 7 6 7 8 0 31 47 36 76 3 11 9 32 15 47 24 62 33 251 17 344 -21 123
|
||||
-93 271 -181 371 -49 54 -156 142 -174 142 -6 0 -11 4 -11 9 0 18 -180 85
|
||||
-260 97 -43 7 -218 13 -223 8z m243 -199 c30 -9 62 -17 70 -18 8 -1 20 -5 25
|
||||
-10 6 -4 37 -25 70 -46 71 -45 165 -135 174 -166 1 -5 15 -33 31 -61 78 -136
|
||||
85 -368 15 -504 -8 -16 -15 -33 -14 -36 0 -4 -3 -10 -8 -13 -5 -3 -23 -26 -40
|
||||
-51 -25 -37 -102 -113 -148 -148 -15 -12 -117 -59 -145 -68 -97 -31 -220 -35
|
||||
-310 -11 -8 2 -28 7 -45 12 -16 4 -51 19 -77 32 -27 13 -51 24 -55 26 -4 1
|
||||
-18 12 -31 24 -14 13 -30 20 -36 16 -6 -3 -8 -3 -4 1 9 10 -34 55 -46 48 -5
|
||||
-3 -6 0 -2 6 3 6 -2 17 -12 24 -10 7 -27 28 -37 46 -11 17 -24 32 -29 32 -5 0
|
||||
-6 3 -3 7 4 3 -2 20 -12 37 -27 43 -49 111 -56 176 -8 60 -7 158 0 170 3 4 7
|
||||
25 10 46 5 45 56 152 97 205 15 20 28 42 28 48 0 6 4 10 8 8 4 -1 26 16 48 38
|
||||
22 22 49 45 60 51 55 32 123 65 140 69 10 2 42 10 69 18 63 17 193 13 265 -8z"/>
|
||||
<path d="M717 1231 c-299 -97 -383 -476 -154 -691 23 -22 48 -40 54 -40 7 0
|
||||
13 -4 13 -9 0 -17 116 -53 185 -57 130 -8 228 29 320 121 49 49 66 75 89 135
|
||||
33 89 38 197 11 270 -9 25 -17 50 -19 57 -6 30 -85 124 -133 157 -104 73 -248
|
||||
95 -366 57z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,14 +1,21 @@
|
||||
{
|
||||
"name": "RaspAP",
|
||||
"short_name": "RaspAP",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/app/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
"name": "RaspAP Admin Panel",
|
||||
"short_name": "RaspAP",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/app/icons/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/app/icons/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
BIN
app/icons/web-app-manifest-192x192.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/icons/web-app-manifest-512x512.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
54
app/img/dashed.svg
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 227 596" style="enable-background:new 0 0 227 596;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#999999;stroke-width:3;}
|
||||
.st1{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:6.0204,3.0102;}
|
||||
.st2{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:5.7963,2.8981;}
|
||||
</style>
|
||||
<g id="dashed">
|
||||
<g id="Line_1">
|
||||
<g>
|
||||
<line class="st0" x1="112.8" y1="0" x2="112.8" y2="3"/>
|
||||
<line class="st1" x1="112.8" y1="6" x2="112.8" y2="591.5"/>
|
||||
<line class="st0" x1="112.8" y1="593" x2="112.8" y2="596"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_2">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="0.8" x2="110.2" y2="0.8"/>
|
||||
<line class="st2" x1="107.3" y1="0.8" x2="4.4" y2="0.8"/>
|
||||
<line class="st0" x1="3" y1="0.8" x2="0" y2="0.8"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_3">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="198.9" x2="110.2" y2="198.9"/>
|
||||
<line class="st2" x1="107.3" y1="198.9" x2="4.4" y2="198.9"/>
|
||||
<line class="st0" x1="3" y1="198.9" x2="0" y2="198.9"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_4">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="397.1" x2="110.2" y2="397.1"/>
|
||||
<line class="st2" x1="107.3" y1="397.1" x2="4.4" y2="397.1"/>
|
||||
<line class="st0" x1="3" y1="397.1" x2="0" y2="397.1"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_5">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="595.2" x2="110.2" y2="595.2"/>
|
||||
<line class="st2" x1="107.3" y1="595.2" x2="4.4" y2="595.2"/>
|
||||
<line class="st0" x1="3" y1="595.2" x2="0" y2="595.2"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_6">
|
||||
<g>
|
||||
<line class="st0" x1="226.2" y1="297.8" x2="223.2" y2="297.8"/>
|
||||
<line class="st2" x1="220.3" y1="297.8" x2="117.4" y2="297.8"/>
|
||||
<line class="st0" x1="116" y1="297.8" x2="113" y2="297.8"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
1083
app/img/devices/compute.php
Normal file
4450
app/img/devices/default.php
Normal file
1561
app/img/devices/zero.php
Normal file
@@ -1,7 +1,10 @@
|
||||
<?php header("Content-Type: image/svg+xml; charset=utf-8"); ?>
|
||||
<?php
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/functions.php';
|
||||
$color = getColorOpt();
|
||||
$static = (isset($_GET['static']) && $_GET['static'] == '1') ||
|
||||
(defined('RASPI_UI_STATIC_LOGO') && RASPI_UI_STATIC_LOGO === true);
|
||||
?>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
@@ -10,42 +13,41 @@ $color = getColorOpt();
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 490.66666 487.11066"
|
||||
height="487.11066"
|
||||
width="490.66666"
|
||||
viewBox="0 180 352 290"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
version="1.1"><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6"><clipPath
|
||||
id="clipPath18"
|
||||
clipPathUnits="userSpaceOnUse"><path
|
||||
id="path16"
|
||||
d="M 0,365.333 H 368 V 0 H 0 Z" /></clipPath></defs><g
|
||||
transform="matrix(1.3333333,0,0,-1.3333333,0,487.11067)"
|
||||
id="g10"><g
|
||||
id="g12"><g
|
||||
clip-path="url(#clipPath18)"
|
||||
id="g14"><g
|
||||
transform="translate(192.6768,123.4365)"
|
||||
id="g20"><path
|
||||
id="path22"
|
||||
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c 0,-37.169 -30.128,-67.3 -67.296,-67.3 -37.167,0 -67.294,30.131 -67.294,67.3 0,37.165 30.127,67.296 67.294,67.296 C -30.128,67.296 0,37.165 0,0" /></g><g
|
||||
transform="translate(125.3823,219.0791)"
|
||||
id="g24"><path
|
||||
id="path26"
|
||||
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c -52.737,0 -95.641,-42.905 -95.641,-95.643 0,-52.74 42.904,-95.647 95.641,-95.647 52.737,0 95.642,42.907 95.642,95.647 C 95.642,-42.905 52.737,0 0,0 m 0,-217.29 c -67.073,0 -121.641,54.571 -121.641,121.647 C -121.641,-28.569 -67.073,26 0,26 67.074,26 121.642,-28.569 121.642,-95.643 121.642,-162.719 67.074,-217.29 0,-217.29" /></g><g
|
||||
transform="translate(144.4277,271.9385)"
|
||||
id="g28"><path
|
||||
id="path30"
|
||||
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c 66.188,0 121.118,-49.055 130.392,-112.714 l 28.259,-1.874 C 150.044,-34.655 82.181,27.791 0,27.791 c -3.892,0 -7.75,-0.147 -11.571,-0.423 L -9.73,-0.397 C -6.513,-0.161 -3.275,0 0,0" /></g><g
|
||||
transform="translate(144.4883,334.7588)"
|
||||
id="g32"><path
|
||||
id="path34"
|
||||
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c 101.94,0 185.667,-79.438 192.56,-179.664 l 27.962,-1.857 C 214.513,-65.087 117.899,27.791 0,27.791 c -5.31,0 -10.576,-0.2 -15.792,-0.571 l 1.84,-27.728 C -9.343,-0.177 -4.691,0 0,0" /></g></g></g></g></svg>
|
||||
version="1.1">
|
||||
<style>
|
||||
<?php if (!$static): ?>
|
||||
.wave {
|
||||
opacity: 0.4;
|
||||
animation: pulse 1.8s infinite;
|
||||
}
|
||||
.wave1 { animation-delay: 0.3s; }
|
||||
.wave2 { animation-delay: 0.6s; }
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 0.4; }
|
||||
20% { opacity: 1; }
|
||||
60% { opacity: 0.4; }
|
||||
100% { opacity: 0.4; }
|
||||
}
|
||||
<?php else: ?>
|
||||
.wave {
|
||||
opacity: 1.0;
|
||||
}
|
||||
<?php endif; ?>
|
||||
</style>
|
||||
|
||||
<!-- inner solid circle -->
|
||||
<circle cx="128" cy="384" r="60" fill="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>"/>
|
||||
|
||||
<!-- outer ring -->
|
||||
<circle cx="128" cy="384" r="100" fill="none" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="25"/>
|
||||
|
||||
<!-- arcs -->
|
||||
<path class="wave wave1" d="M128 234 A 150 150 0 0 1 278 384" fill="none" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="25"/>
|
||||
<path class="wave wave2" d="M128 184 A 200 200 0 0 1 328 384" fill="none" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="25"/>
|
||||
|
||||
</svg>
|
||||
|
||||
|
||||
40
app/img/right-dashed.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 314 594" style="enable-background:new 0 0 314 594;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#999999;stroke-width:3;}
|
||||
.st1{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:6.04,3.02;}
|
||||
.st2{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:5.7963,2.8981;}
|
||||
</style>
|
||||
<g id="dashed">
|
||||
<g id="horizontal">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="144" x2="113.2" y2="147"/>
|
||||
<line class="st1" x1="113.2" y1="150" x2="113.3" y2="447.5"/>
|
||||
<line class="st0" x1="113.3" y1="449" x2="113.3" y2="452"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="top">
|
||||
<g>
|
||||
<line class="st0" x1="114" y1="144.8" x2="117" y2="144.8"/>
|
||||
<line class="st2" x1="119.9" y1="144.8" x2="222.8" y2="144.8"/>
|
||||
<line class="st0" x1="224.2" y1="144.8" x2="227.2" y2="144.8"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="out">
|
||||
<g>
|
||||
<line class="st0" x1="0" y1="297.8" x2="3" y2="297.8"/>
|
||||
<line class="st2" x1="5.9" y1="297.8" x2="108.8" y2="297.8"/>
|
||||
<line class="st0" x1="110.2" y1="297.8" x2="113.2" y2="297.8"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="bottom">
|
||||
<g>
|
||||
<line class="st0" x1="113" y1="450.8" x2="116" y2="450.8"/>
|
||||
<line class="st2" x1="118.9" y1="450.8" x2="221.8" y2="450.8"/>
|
||||
<line class="st0" x1="223.2" y1="450.8" x2="226.2" y2="450.8"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
46
app/img/right-solid.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
header("Content-Type: image/svg+xml");
|
||||
$showDevice1 = isset($_GET['device-1']);
|
||||
$showOut = isset($_GET['out']);
|
||||
$showDevice2 = isset($_GET['device-2']);
|
||||
?>
|
||||
|
||||
<svg width="313" height="594" viewBox="0 0 313 594" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Frame 1">
|
||||
<g id="right connection frame">
|
||||
<g id="solid">
|
||||
<?php if ($showDevice2): ?>
|
||||
<line id="joint-device-2" y1="-0.75" x2="154" y2="-0.75"
|
||||
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 297)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showDevice1): ?>
|
||||
<line id="joint-device-1" style="display: inline;"
|
||||
y1="-0.75" x2="154" y2="-0.75"
|
||||
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 144)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
|
||||
<line id="device-1" style="display: inline;"
|
||||
y1="-0.75" x2="113.231" y2="-0.75"
|
||||
transform="matrix(1 8.74228e-08 8.74228e-08 -1 114 144)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showOut): ?>
|
||||
<line id="out" style="display: inline;"
|
||||
y1="-0.75" x2="113.231" y2="-0.75"
|
||||
transform="matrix(1 8.74228e-08 8.74228e-08 -1 -0.000305176 297)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showDevice2): ?>
|
||||
<line id="device-2" style="display: inline;"
|
||||
y1="-0.75" x2="113.231" y2="-0.75"
|
||||
transform="matrix(1 8.74228e-08 8.74228e-08 -1 113 450)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
65
app/img/solid.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
header("Content-Type: image/svg+xml");
|
||||
|
||||
require_once '../../includes/functions.php';
|
||||
$color = getColorOpt();
|
||||
|
||||
$showJoint = isset($_GET['joint']);
|
||||
$showDevice1 = isset($_GET['device-1']);
|
||||
$showOut = isset($_GET['out']);
|
||||
$showDevice2 = isset($_GET['device-2']);
|
||||
$showDevice3 = isset($_GET['device-3']);
|
||||
$showDevice4 = isset($_GET['device-4']);
|
||||
?>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="227" height="596" viewBox="0 0 227 596" fill="none">
|
||||
<?php
|
||||
// Device positions array (y-coordinates)
|
||||
$devicePositions = [
|
||||
'device-1' => 0.75,
|
||||
'out' => 297.75,
|
||||
'device-2' => 198.75,
|
||||
'device-3' => 397.058,
|
||||
'device-4' => 595.211
|
||||
];
|
||||
|
||||
// Calculate joint line segments
|
||||
if ($showJoint) {
|
||||
$activeDevices = array_filter([$showDevice1, $showDevice2, $showDevice3, $showDevice4]);
|
||||
$activeYs = [];
|
||||
|
||||
foreach ($devicePositions as $device => $y) {
|
||||
if (isset($_GET[$device])) {
|
||||
$activeYs[] = $y;
|
||||
}
|
||||
}
|
||||
|
||||
// Add top/bottom if first/last device is connected
|
||||
if ($showDevice1) array_unshift($activeYs, 0);
|
||||
if ($showDevice4) $activeYs[] = 596;
|
||||
|
||||
// Draw segments between consecutive points
|
||||
for ($i = 1; $i < count($activeYs); $i++) {
|
||||
$y1 = $activeYs[$i-1];
|
||||
$y2 = $activeYs[$i];
|
||||
echo "<line x1='112.75' y1='$y1' x2='112.75' y2='$y2' stroke='" . htmlspecialchars($color, ENT_QUOTES, 'UTF-8') . "' stroke-width='4'/>";
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($showDevice1): ?>
|
||||
<line x1="113.231" y1="0.75" x2="7.69496e-06" y2="0.75001" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="6" id="device-1"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showOut): ?>
|
||||
<line x1="226.231" y1="297.75" x2="113" y2="297.75" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4" id="out"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDevice2): ?>
|
||||
<line x1="113.231" y1="198.75" x2="7.69496e-06" y2="198.75" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4" id="device-2"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDevice3): ?>
|
||||
<line x1="113.231" y1="397.058" x2="7.69496e-06" y2="397.058" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4" id="device-3"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDevice4): ?>
|
||||
<line x1="113.231" y1="595.211" x2="7.69496e-06" y2="595.211" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4" id="device-4"/>
|
||||
<?php endif; ?>
|
||||
</svg>
|
||||
29
app/img/uri-qr-code.php
Executable file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
if (!isset($_GET['uri']) || !filter_var($_GET['uri'], FILTER_VALIDATE_URL)) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
exit("Invalid or missing URI parameter");
|
||||
}
|
||||
|
||||
$uri = $_GET['uri'];
|
||||
$command = "qrencode -t svg -m 0 -o - " . escapeshellarg($uri);
|
||||
|
||||
$svg = shell_exec($command);
|
||||
if ($svg === null) {
|
||||
error_log("QR generation failed for URI: $uri");
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
exit("Failed to generate QR code");
|
||||
}
|
||||
|
||||
$etag = hash('sha256', $uri);
|
||||
$content_length = strlen($svg);
|
||||
$last_modified = gmdate("D, d M Y H:i:s") . " GMT";
|
||||
|
||||
header("Content-Type: image/svg+xml");
|
||||
header("Content-Length: $content_length");
|
||||
header("Last-Modified: $last_modified");
|
||||
header("ETag: \"$etag\"");
|
||||
header("X-QR-Code-Content: " . htmlspecialchars($uri, ENT_QUOTES, 'UTF-8'));
|
||||
|
||||
echo $svg;
|
||||
|
||||
@@ -13,6 +13,7 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
exec("sudo cat " .RASPI_WIREGUARD_PATH.'client.conf', $return);
|
||||
$peer_conf = implode(PHP_EOL,$return);
|
||||
$peer_conf.= PHP_EOL;
|
||||
$peer_conf_sanitized = str_replace(["\r", "\n"], '', $peer_conf);
|
||||
$command = "qrencode -t svg -m 0 -o - " . mb_escapeshellarg($peer_conf);
|
||||
$svg = shell_exec($command);
|
||||
$etag = hash('sha256', $peer_conf);
|
||||
@@ -23,6 +24,6 @@ header("Content-Type: image/svg+xml");
|
||||
header("Content-Length: $content_length");
|
||||
header("Last-Modified: $last_modified");
|
||||
header("ETag: \"$etag\"");
|
||||
header("X-QR-Code-Content: $peer_conf");
|
||||
header("X-QR-Code-Content: $peer_conf_sanitized");
|
||||
echo shell_exec($command);
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
|
||||
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
|
||||
|
||||
// assume wpa encryption and get the passphrase
|
||||
// assume WPA encryption and get the passphrase
|
||||
$type = "WPA";
|
||||
$password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase'];
|
||||
|
||||
// use wep if configured
|
||||
$wep_default_key = intval($hostapd['wep_default_key']);
|
||||
// use WEP if configured
|
||||
$wep_default_key = intval($hostapd['wep_default_key'] ?? 0);
|
||||
$wep_key = 'wep_key' . $wep_default_key;
|
||||
if (array_key_exists($wep_key, $hostapd)) {
|
||||
$type = "WEP";
|
||||
@@ -30,7 +30,7 @@ if (empty($password)) {
|
||||
}
|
||||
|
||||
$ssid = $hostapd['ssid'];
|
||||
$hidden = intval($hostapd['ignore_broadcast_ssid']) != 0 ? "H:true" : "";
|
||||
$hidden = intval($hostapd['ignore_broadcast_ssid'] ?? 0) !== 0 ? "H:true" : "";
|
||||
|
||||
$ssid = qr_encode($ssid);
|
||||
$password = qr_encode($password);
|
||||
|
||||
555
app/js/ajax/main.js
Normal file
@@ -0,0 +1,555 @@
|
||||
|
||||
function loadSummary(strInterface) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/networking/get_ip_summary.php',{'interface': strInterface, 'csrf_token': csrfToken},function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
if(jsonData['return'] == 0) {
|
||||
$('#'+strInterface+'-summary').html(jsonData['output'].join('<br />'));
|
||||
} else if(jsonData['return'] == 2) {
|
||||
$('#'+strInterface+'-summary').append('<div class="alert alert-danger alert-dismissible" role="alert"><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>'+jsonData['output'].join('<br />')+'</div>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getAllInterfaces() {
|
||||
$.get('ajax/networking/get_all_interfaces.php',function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
$.each(jsonData,function(ind,value){
|
||||
loadSummary(value)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("click", "#js-clearhostapd-log", function(e) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log', 'csrf_token': csrfToken},function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
$("#hostapd-log").val("");
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on("click", "#js-cleardnsmasq-log", function(e) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/logging/clearlog.php?',{'logfile':'/var/log/dnsmasq.log', 'csrf_token': csrfToken},function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
$("#dnsmasq-log").val("");
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on("click", "#js-clearopenvpn-log", function(e) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/openvpn.log', 'csrf_token': csrfToken},function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
$("#openvpn-log").val("");
|
||||
});
|
||||
});
|
||||
|
||||
function loadWifiStations(refresh) {
|
||||
return function() {
|
||||
var complete = function() { $(this).removeClass('loading-spinner'); }
|
||||
var qs = refresh === true ? '?refresh' : '';
|
||||
$('.js-wifi-stations')
|
||||
.addClass('loading-spinner')
|
||||
.empty()
|
||||
.load('ajax/networking/wifi_stations.php'+qs, complete);
|
||||
};
|
||||
}
|
||||
$(".js-reload-wifi-stations").on("click", loadWifiStations(true));
|
||||
|
||||
/*
|
||||
Populates the DHCP server form fields
|
||||
Option toggles are set dynamically depending on the loaded configuration
|
||||
*/
|
||||
function loadInterfaceDHCPSelect() {
|
||||
var strInterface = $('#cbxdhcpiface').val();
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/networking/get_netcfg.php', {'iface' : strInterface, 'csrf_token': csrfToken}, function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
$('#dhcp-iface')[0].checked = jsonData.DHCPEnabled;
|
||||
$('#txtipaddress').val(jsonData.StaticIP);
|
||||
$('#txtsubnetmask').val(jsonData.SubnetMask);
|
||||
$('#txtgateway').val(jsonData.StaticRouters);
|
||||
$('#chkfallback')[0].checked = jsonData.FallbackEnabled;
|
||||
$('#default-route').prop('checked', jsonData.DefaultRoute);
|
||||
if (strInterface.startsWith("wl")) {
|
||||
$('#nohook-wpa-supplicant').parent().parent().parent().show()
|
||||
$('#nohook-wpa-supplicant').prop('checked', jsonData.NoHookWPASupplicant);
|
||||
} else {
|
||||
$('#nohook-wpa-supplicant').parent().parent().parent().hide()
|
||||
}
|
||||
$('#txtrangestart').val(jsonData.RangeStart);
|
||||
$('#txtrangeend').val(jsonData.RangeEnd);
|
||||
$('#txtrangeleasetime').val(jsonData.leaseTime);
|
||||
$('#txtdns1').val(jsonData.DNS1);
|
||||
$('#txtdns2').val(jsonData.DNS2);
|
||||
$('#cbxrangeleasetimeunits').val(jsonData.leaseTimeInterval);
|
||||
$('#no-resolv')[0].checked = jsonData.upstreamServersEnabled;
|
||||
$('#cbxdhcpupstreamserver').val(jsonData.upstreamServers[0]);
|
||||
$('#txtmetric').val(jsonData.Metric);
|
||||
|
||||
if (jsonData.StaticIP !== null && jsonData.StaticIP !== '' && !jsonData.FallbackEnabled) {
|
||||
$('#chkstatic').prop('checked', true).closest('.btn').addClass('active');
|
||||
$('#chkdhcp').prop('checked', false).closest('.btn').removeClass('active');
|
||||
$('#chkfallback').prop('disabled', true);
|
||||
$('#dhcp-iface').removeAttr('disabled');
|
||||
} else {
|
||||
$('#chkdhcp').closest('.btn').addClass('active');
|
||||
$('#chkdhcp').closest('.btn').blur();
|
||||
}
|
||||
if (jsonData.FallbackEnabled || $('#chkdhcp').is(':checked')) {
|
||||
$('#dhcp-iface').prop('disabled', true);
|
||||
setDhcpFieldsDisabled();
|
||||
}
|
||||
|
||||
const leaseContainer = $('.js-dhcp-static-lease-container');
|
||||
leaseContainer.empty();
|
||||
|
||||
if (jsonData.dhcpHost && jsonData.dhcpHost.length > 0) {
|
||||
const leases = jsonData.dhcpHost || [];
|
||||
leases.forEach((entry, index) => {
|
||||
const [mainPart, commentPart] = entry.split('#');
|
||||
const comment = commentPart ? commentPart.trim() : '';
|
||||
const [mac, ip] = mainPart.split(',').map(part => part.trim());
|
||||
const row = `
|
||||
<div class="row dhcp-static-lease-row js-dhcp-static-lease-row">
|
||||
<div class="col-md-4 col-xs-3">
|
||||
<input type="text" name="static_leases[mac][]" value="${mac}" placeholder="MAC address" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-3">
|
||||
<input type="text" name="static_leases[ip][]" value="${ip}" placeholder="IP address" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-3">
|
||||
<input type="text" name="static_leases[comment][]" value="${comment || ''}" placeholder="Optional comment" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-3">
|
||||
<button type="button" class="btn btn-outline-danger js-remove-dhcp-static-lease"><i class="far fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>`;
|
||||
leaseContainer.append(row);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#debugModal').on('shown.bs.modal', function (e) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/system/sys_debug.php',{'csrf_token': csrfToken},function(data){
|
||||
window.location.replace('/ajax/system/sys_get_logfile.php');
|
||||
$('#debugModal').modal('hide');
|
||||
});
|
||||
});
|
||||
|
||||
$('#chkupdateModal').on('shown.bs.modal', function (e) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/system/sys_chk_update.php',{'csrf_token': csrfToken},function(data){
|
||||
var response = JSON.parse(data);
|
||||
var tag = response.tag;
|
||||
var update = response.update;
|
||||
var msg;
|
||||
var msgUpdate = $('#msgUpdate').data('message');
|
||||
var msgLatest = $('#msgLatest').data('message');
|
||||
var msgInstall = $('#msgInstall').data('message');
|
||||
var msgDismiss = $('#js-check-dismiss').data('message');
|
||||
var faCheck = '<i class="fas fa-check ms-2"></i><br />';
|
||||
$("#updateSync").removeClass("fa-spin");
|
||||
if (update === true) {
|
||||
msg = msgUpdate +' '+tag;
|
||||
$("#msg-check-update").html(msg);
|
||||
$("#msg-check-update").append(faCheck);
|
||||
$("#msg-check-update").append("<p>"+msgInstall+"</p>");
|
||||
$("#js-sys-check-update").removeClass("collapse");
|
||||
} else {
|
||||
msg = msgLatest;
|
||||
dismiss = $("#js-check-dismiss");
|
||||
$("#msg-check-update").html(msg);
|
||||
$("#msg-check-update").append(faCheck);
|
||||
$("#js-sys-check-update").remove();
|
||||
dismiss.text(msgDismiss);
|
||||
dismiss.removeClass("btn-outline-secondary");
|
||||
dismiss.addClass("btn-primary");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#performUpdate').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/system/sys_perform_update.php',{
|
||||
'csrf_token': csrfToken
|
||||
})
|
||||
$('#chkupdateModal').modal('hide');
|
||||
$('#performupdateModal').modal('show');
|
||||
});
|
||||
|
||||
function fetchUpdateResponse() {
|
||||
const complete = 6;
|
||||
const error = 7;
|
||||
let phpFile = 'ajax/system/sys_read_logfile.php';
|
||||
|
||||
$.ajax({
|
||||
url: phpFile,
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
let divId = '#updateStep' + i;
|
||||
if (response.includes(i.toString())) {
|
||||
$(divId).removeClass('invisible');
|
||||
}
|
||||
}
|
||||
// check if the update is complete or if there's an error
|
||||
if (response.includes(complete)) {
|
||||
var successMsg = $('#successMsg').data('message');
|
||||
$('#updateMsg').after('<span class="small">' + successMsg + '</span>');
|
||||
$('#updateMsg').addClass('fa-check');
|
||||
$('#updateMsg').removeClass('invisible');
|
||||
$('#updateStep6').removeClass('invisible');
|
||||
$('#updateSync2').removeClass("fa-spin");
|
||||
$('#updateOk').removeAttr('disabled');
|
||||
} else if (response.includes(error)) {
|
||||
var errorMsg = $('#errorMsg').data('message');
|
||||
$('#updateMsg').after('<span class="small">' + errorMsg + '</span>');
|
||||
$('#updateMsg').addClass('fa-times');
|
||||
$('#updateMsg').removeClass('invisible');
|
||||
$('#updateSync2').removeClass("fa-spin");
|
||||
$('#updateOk').removeAttr('disabled');
|
||||
} else {
|
||||
setTimeout(fetchUpdateResponse, 500);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("AJAX Error:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#ovpn-confirm-delete').on('click', '.btn-delete', function (e) {
|
||||
var cfg_id = $(this).data('recordId');
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/openvpn/del_ovpncfg.php',{'cfg_id':cfg_id, 'csrf_token': csrfToken},function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
$("#ovpn-confirm-delete").modal('hide');
|
||||
var row = $(document.getElementById("openvpn-client-row-" + cfg_id));
|
||||
row.fadeOut( "slow", function() {
|
||||
row.remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('#ovpn-confirm-delete').on('show.bs.modal', function (e) {
|
||||
var data = $(e.relatedTarget).data();
|
||||
$('.btn-delete', this).data('recordId', data.recordId);
|
||||
});
|
||||
|
||||
$('#ovpn-confirm-activate').on('click', '.btn-activate', function (e) {
|
||||
var cfg_id = $(this).data('record-id');
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/openvpn/activate_ovpncfg.php',{'cfg_id':cfg_id, 'csrf_token': csrfToken},function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
$("#ovpn-confirm-activate").modal('hide');
|
||||
setTimeout(function(){
|
||||
window.location.reload();
|
||||
},300);
|
||||
});
|
||||
});
|
||||
|
||||
$('#js-system-reset-confirm').on('click', function (e) {
|
||||
var progressText = $('#js-system-reset-confirm').attr('data-message');
|
||||
var successHtml = $('#system-reset-message').attr('data-message');
|
||||
var closeHtml = $('#js-system-reset-cancel').attr('data-message');
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
var progressHtml = $('<div>').text(progressText).html() + '<i class="fas fa-cog fa-spin ms-2"></i>';
|
||||
$('#system-reset-message').html(progressHtml);
|
||||
$.post('ajax/networking/do_sys_reset.php?',{'csrf_token':csrfToken},function(data){
|
||||
setTimeout(function(){
|
||||
jsonData = JSON.parse(data);
|
||||
if(jsonData['return'] == 0) {
|
||||
$('#system-reset-message').text(successHtml);
|
||||
} else {
|
||||
$('#system-reset-message').text('Error occured: '+ jsonData['return']);
|
||||
}
|
||||
$("#js-system-reset-confirm").hide();
|
||||
$("#js-system-reset-cancel").text(closeHtml);
|
||||
},750);
|
||||
});
|
||||
});
|
||||
|
||||
$('#js-sys-reboot, #js-sys-shutdown').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
var action = $(this).data('action');
|
||||
$.post('ajax/system/sys_actions.php?',{'a': action, 'csrf_token': csrfToken},function(data){
|
||||
var response = JSON.parse(data);
|
||||
});
|
||||
});
|
||||
|
||||
$('#js-install-plugin-confirm').on('click', function (e) {
|
||||
var button = $('#install-user-plugin').data('button');
|
||||
var manifestData = button.data('plugin-manifest');
|
||||
var installPath = manifestData.install_path;
|
||||
var pluginUri = manifestData.plugin_uri;
|
||||
var pluginVersion = manifestData.version;
|
||||
var pluginConfirm = $('#js-install-plugin-confirm').text();
|
||||
var progressText = $('#js-install-plugin-confirm').attr('data-message');
|
||||
var successHtml = $('#plugin-install-message').attr('data-message');
|
||||
var successText = $('<div>').text(successHtml).text();
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
|
||||
if (pluginConfirm === 'Install now') {
|
||||
$("#install-user-plugin").modal('hide');
|
||||
$("#install-plugin-progress").modal('show');
|
||||
$.post(
|
||||
'ajax/plugins/do_plugin_install.php',
|
||||
{
|
||||
'plugin_uri': pluginUri,
|
||||
'plugin_version': pluginVersion,
|
||||
'install_path': installPath,
|
||||
'csrf_token': csrfToken
|
||||
},
|
||||
function (data) {
|
||||
setTimeout(function () {
|
||||
response = JSON.parse(data);
|
||||
if (response === true) {
|
||||
$('#plugin-install-message').contents().first().text(successText);
|
||||
$('#plugin-install-message')
|
||||
.find('i')
|
||||
.removeClass('fas fa-cog fa-spin link-secondary')
|
||||
.addClass('fas fa-check');
|
||||
$('#js-install-plugin-ok').removeAttr("disabled");
|
||||
} else {
|
||||
const errorMessage = jsonData.error || 'An unknown error occurred.';
|
||||
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
|
||||
$('#plugin-install-message')
|
||||
.contents()
|
||||
.first()
|
||||
.replaceWith('An error occurred installing the plugin:');
|
||||
$('#plugin-install-message').append(errorLog);
|
||||
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
|
||||
$('#js-install-plugin-ok').removeAttr("disabled");
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
).fail(function (xhr) {
|
||||
const jsonData = JSON.parse(xhr.responseText);
|
||||
const errorMessage = jsonData.error || 'An unknown error occurred.';
|
||||
$('#plugin-install-message')
|
||||
.contents()
|
||||
.first()
|
||||
.replaceWith('An error occurred installing the plugin:');
|
||||
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
|
||||
$('#plugin-install-message').append(errorLog);
|
||||
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
|
||||
$('#js-install-plugin-ok').removeAttr("disabled");
|
||||
});
|
||||
} else if (pluginConfirm === 'Get Insiders') {
|
||||
window.open('https://docs.raspap.com/insiders/', '_blank');
|
||||
return;
|
||||
} else if (pluginConfirm === 'OK') {
|
||||
$("#install-user-plugin").modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieves the 'channel' value specified in hostapd.conf
|
||||
function getChannel() {
|
||||
$.get('ajax/networking/get_channel.php',function(data){
|
||||
jsonData = JSON.parse(data);
|
||||
loadChannelSelect(jsonData);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Sets the wirelss channel select options based on frequencies reported by iw.
|
||||
|
||||
See: https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git
|
||||
Also: https://en.wikipedia.org/wiki/List_of_WLAN_channels
|
||||
*/
|
||||
function loadChannelSelect(selected) {
|
||||
var iface = $('#cbxinterface').val();
|
||||
var hwmodeText = '';
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
|
||||
// update hardware mode tooltip
|
||||
setHardwareModeTooltip();
|
||||
|
||||
$.post('ajax/networking/get_frequencies.php',{'interface': iface, 'csrf_token': csrfToken, 'selected': selected},function(response){
|
||||
var hw_mode = $('#cbxhwmode').val();
|
||||
var country_code = $('#cbxcountries').val();
|
||||
var channel_select = $('#cbxchannel');
|
||||
var btn_save = $('#btnSaveHostapd');
|
||||
var data = JSON.parse(response);
|
||||
var selectableChannels = [];
|
||||
|
||||
// Map selected hw_mode to available channels
|
||||
if (hw_mode === 'a') {
|
||||
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
|
||||
} else if (hw_mode !== 'ac') {
|
||||
selectableChannels = data.filter(item => item.MHz.toString().startsWith('24'));
|
||||
} else if (hw_mode === 'b') {
|
||||
selectableChannels = data.filter(item => item.MHz.toString().startsWith('24'));
|
||||
} else if (hw_mode === 'ac') {
|
||||
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
|
||||
}
|
||||
|
||||
// If selected channel doeesn't exist in allowed channels, set default or null (unsupported)
|
||||
if (!selectableChannels.find(item => item.Channel === selected)) {
|
||||
if (selectableChannels.length === 0) {
|
||||
selectableChannels[0] = { Channel: null };
|
||||
} else {
|
||||
defaultChannel = selectableChannels[0].Channel;
|
||||
selected = defaultChannel
|
||||
}
|
||||
}
|
||||
|
||||
// Set channel select with available values
|
||||
channel_select.empty();
|
||||
if (selectableChannels[0].Channel === null) {
|
||||
channel_select.append($("<option></option>").attr("value", "").text("---"));
|
||||
channel_select.prop("disabled", true);
|
||||
btn_save.prop("disabled", true);
|
||||
} else {
|
||||
channel_select.prop("disabled", false);
|
||||
btn_save.prop("disabled", false);
|
||||
$.each(selectableChannels, function(key,value) {
|
||||
channel_select.append($("<option></option>").attr("value", value.Channel).text(value.Channel));
|
||||
});
|
||||
channel_select.val(selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Sets hardware mode tooltip text for selected interface
|
||||
* and calls loadChannelSelect()
|
||||
*/
|
||||
function setHardwareModeTooltip() {
|
||||
var iface = $('#cbxinterface').val();
|
||||
var hwmodeText = '';
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
// Explanatory text if 802.11ac is disabled
|
||||
if ($('#cbxhwmode').find('option[value="ac"]').prop('disabled') == true ) {
|
||||
var hwmodeText = $('#hwmode').attr('data-tooltip');
|
||||
}
|
||||
$.post('ajax/networking/get_nl80211_band.php?',{'interface': iface, 'csrf_token': csrfToken},function(data){
|
||||
var responseText = JSON.parse(data);
|
||||
$('#tiphwmode').attr('data-original-title', responseText + '\n' + hwmodeText );
|
||||
});
|
||||
}
|
||||
|
||||
/* Updates the selected blocklist
|
||||
* Request is passed to an ajax handler to download the associated list.
|
||||
* Interface elements are updated to indicate current progress, status.
|
||||
*/
|
||||
function updateBlocklist() {
|
||||
const opt = $('#cbxblocklist option:selected');
|
||||
const blocklist_id = opt.val();
|
||||
const csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
|
||||
if (blocklist_id === '') return;
|
||||
|
||||
const statusIcon = $('#cbxblocklist-status').find('i');
|
||||
const statusWrapper = $('#cbxblocklist-status');
|
||||
|
||||
statusIcon.removeClass('fa-check fa-exclamation-triangle').addClass('fa-cog fa-spin');
|
||||
statusWrapper.removeClass('check-hidden check-error check-updated').addClass('check-progress');
|
||||
|
||||
$.post('ajax/adblock/update_blocklist.php', {
|
||||
'blocklist_id': blocklist_id,
|
||||
'csrf_token': csrfToken
|
||||
}, function (data) {
|
||||
let jsonData;
|
||||
try {
|
||||
jsonData = JSON.parse(data);
|
||||
} catch (e) {
|
||||
showError("Unexpected server response.");
|
||||
return;
|
||||
}
|
||||
const resultCode = jsonData['return'];
|
||||
const output = jsonData['output']?.join('\n') || '';
|
||||
|
||||
switch (resultCode) {
|
||||
case 0:
|
||||
statusIcon.removeClass('fa-cog fa-spin').addClass('fa-check');
|
||||
statusWrapper.removeClass('check-progress').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
|
||||
$('#blocklist-' + jsonData['list']).text("Just now");
|
||||
break;
|
||||
case 1:
|
||||
showError("Invalid blocklist.");
|
||||
break;
|
||||
case 2:
|
||||
showError("No blocklist provided.");
|
||||
break;
|
||||
case 3:
|
||||
showError("Could not parse blocklists.json.");
|
||||
break;
|
||||
case 4:
|
||||
showError("blocklists.json file not found.");
|
||||
break;
|
||||
case 5:
|
||||
showError("Update script not found.");
|
||||
break;
|
||||
default:
|
||||
showError("Unknown error occurred.");
|
||||
}
|
||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||
showError(`AJAX request failed: ${textStatus}`);
|
||||
});
|
||||
|
||||
function showError(message) {
|
||||
statusIcon.removeClass('fa-cog fa-spin').addClass('fa-exclamation-triangle');
|
||||
statusWrapper.removeClass('check-progress').addClass('check-error');
|
||||
alert("Blocklist update failed:\n\n" + message);
|
||||
}
|
||||
}
|
||||
|
||||
function clearBlocklistStatus() {
|
||||
$('#cbxblocklist-status').removeClass('check-updated').addClass('check-hidden');
|
||||
}
|
||||
|
||||
// Handler for the WireGuard generate key button
|
||||
$('.wg-keygen').click(function(){
|
||||
var parentGroup = $(this).closest('.input-group');
|
||||
var entity_pub = parentGroup.find('input[type="text"]');
|
||||
var updated = entity_pub.attr('name')+"-pubkey-status";
|
||||
var csrfToken = $('meta[name="csrf_token"]').attr('content');
|
||||
$.post('ajax/networking/get_wgkey.php',{'entity':entity_pub.attr('name'), 'csrf_token': csrfToken},function(data){
|
||||
var jsonData = JSON.parse(data);
|
||||
entity_pub.val(jsonData.pubkey);
|
||||
$('#' + updated).removeClass('check-hidden').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
|
||||
});
|
||||
});
|
||||
|
||||
// Handler for wireguard client.conf download
|
||||
$('.wg-client-dl').click(function(){
|
||||
var req = new XMLHttpRequest();
|
||||
var url = 'ajax/networking/get_wgcfg.php';
|
||||
req.open('get', url, true);
|
||||
req.responseType = 'blob';
|
||||
req.setRequestHeader('Content-type', 'text/plain; charset=UTF-8');
|
||||
req.onreadystatechange = function (event) {
|
||||
if(req.readyState == 4 && req.status == 200) {
|
||||
var blob = req.response;
|
||||
var link=document.createElement('a');
|
||||
link.href=window.URL.createObjectURL(blob);
|
||||
link.download = 'client.conf';
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
req.send();
|
||||
})
|
||||
|
||||
let sessionCheckInterval = setInterval(checkSession, 5000);
|
||||
|
||||
function checkSession() {
|
||||
// skip session check if on login page
|
||||
if (window.location.pathname === '/login') {
|
||||
return;
|
||||
}
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/session/do_check_session.php',{'csrf_token': csrfToken},function (data) {
|
||||
if (data.status === 'session_expired') {
|
||||
clearInterval(sessionCheckInterval);
|
||||
showSessionExpiredModal();
|
||||
}
|
||||
}).fail(function (jqXHR, status, err) {
|
||||
console.error("Error checking session status:", status, err);
|
||||
});
|
||||
}
|
||||
|
||||
7
app/js/bandwidthcharts.min.js
vendored
@@ -1,7 +0,0 @@
|
||||
/*!
|
||||
* RaspAP - RaspAP WiFi Configuration Portal v1.6.1 (https://github.com/billz/raspap-webgui)
|
||||
* Copyright 2013-2019 RaspAP Developers
|
||||
* Licensed under MIT (https://github.com/raspap-webgui/raspap-webgui/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(r,i){"use strict";function t(t){r("#divChartBandwidthhourly").empty(),r("#divChartBandwidthdaily").empty(),r("#divChartBandwidthmonthly").empty(),r("#divTableBandwidthhourly").empty(),r("#divTableBandwidthdaily").empty(),r("#divTableBandwidthmonthly").empty();var e=r("ul#tabbarBandwidth li.active a").attr("href").substr(1),a="ajax/bandwidth/get_bandwidth.php?";a+="inet=",a+=encodeURIComponent(r("#cbxInterface"+e+" option:selected").text()),a+="&tu=",a+=encodeURIComponent(e.substr(0,1));var d="mb";a+="&dsu="+encodeURIComponent(d);var n=function(t,e){return new Morris.Bar({element:t,xkey:"date",ykeys:["rx","tx"],labels:[i.receive+" "+e.toUpperCase(),i.send+" "+e.toUpperCase()]})}("divChartBandwidth"+e,d);!function(t,e){r("#"+t).append('<table id="tableBandwidth'+e+'" class="table table-responsive table-striped container-fluid"><thead><tr><th>date</th><th>rx</th><th>tx</th></tr></thead><tbody></tbody></table>')}("divTableBandwidth"+e,e);r.ajax({url:a,dataType:"json",beforeSend:function(){r("#divLoaderBandwidth"+e).removeClass("hidden")}}).done(function(t){r("#divLoaderBandwidth"+e).addClass("hidden"),n.setData(t),r("#tableBandwidth"+e).DataTable({searching:!1,paging:!1,data:t,order:[[0,"ASC"]],columns:[{data:"date"},{data:"rx",title:i.receive+" "+d.toUpperCase()},{data:"tx",title:i.send+" "+d.toUpperCase()}]})}).fail(function(t,e){window.console?console.error("server error"):alert("server error")})}r(document).ready(function(){r('#tabbarBandwidth a[data-toggle="tab"]').on("shown.bs.tab",t),r("#cbxInterfacehourly").on("change",t),r("#cbxInterfacedaily").on("change",t),r("#cbxInterfacemonthly").on("change",t),t()})}(jQuery,t);
|
||||
1037
app/js/custom.js
7
app/js/custom.min.js
vendored
@@ -1,7 +0,0 @@
|
||||
/*!
|
||||
* RaspAP - RaspAP WiFi Configuration Portal v1.6.1 (https://github.com/billz/raspap-webgui)
|
||||
* Copyright 2013-2019 RaspAP Developers
|
||||
* Licensed under MIT (https://github.com/raspap-webgui/raspap-webgui/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
function msgShow(t,a){if(0==t)var e="success";else if(2==t||1==t)e="danger";return'<div class="alert alert-'+e+' alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>'+a+"</div>"}function createNetmaskAddr(t){var a=[];for(i=0;i<4;i++){var e=Math.min(t,8);a.push(256-Math.pow(2,8-e)),t-=e}return a.join(".")}function loadSummary(a){$.post("/ajax/networking/get_ip_summary.php",{interface:a},function(t){jsonData=JSON.parse(t),console.log(jsonData),0==jsonData.return?$("#"+a+"-summary").html(jsonData.output.join("<br />")):2==jsonData.return&&$("#"+a+"-summary").append('<div class="alert alert-danger alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>'+jsonData.output.join("<br />")+"</div>")})}function getAllInterfaces(){$.get("/ajax/networking/get_all_interfaces.php",function(t){jsonData=JSON.parse(t),$.each(jsonData,function(t,a){loadSummary(a)})})}function setupTabs(){$('a[data-toggle="tab"]').on("shown.bs.tab",function(t){var a=$(t.target).attr("href");a.match("summary")||loadCurrentSettings(a.replace("#",""))})}function loadCurrentSettings(t){$.post("/ajax/networking/get_int_config.php",{interface:t},function(t){jsonData=JSON.parse(t),$.each(jsonData.output,function(t,a){var n=a.interface;$.each(a,function(t,a){switch(t){case"static":"true"==a?($("#"+n+"-static").click(),$("#"+n+"-nofailover").click()):$("#"+n+"-dhcp").click();break;case"failover":"true"===a?$("#"+n+"-failover").click():$("#"+n+"-nofailover").click();break;case"ip_address":var e=a.split("/");$("#"+n+"-ipaddress").val(e[0]),$("#"+n+"-netmask").val(createNetmaskAddr(e[1]));break;case"routers":$("#"+n+"-gateway").val(a);break;case"domain_name_server":svrsDNS=a.split(" "),$("#"+n+"-dnssvr").val(svrsDNS[0]),$("#"+n+"-dnssvralt").val(svrsDNS[1])}})})})}function saveNetworkSettings(t){var a=$("#frm-"+t).find(":input"),e={};$.each(a,function(t,a){"radio"==$(a).attr("type")?e[$(a).attr("id")]=$(a).prop("checked"):e[$(a).attr("id")]=$(a).val()}),e.interface=t,$.post("/ajax/networking/save_int_config.php",e,function(t){var a=JSON.parse(t);$("#msgNetworking").html(msgShow(a.return,a.output))})}function applyNetworkSettings(){$(this).data("int");arrFormData={generate:""},$.post("/ajax/networking/gen_int_config.php",arrFormData,function(t){console.log(t);var a=JSON.parse(t);$("#msgNetworking").html(msgShow(a.return,a.output))})}function setupBtns(){$("#btnSummaryRefresh").click(function(){getAllInterfaces()}),$(".intsave").click(function(){saveNetworkSettings($(this).data("int"))}),$(".intapply").click(function(){applyNetworkSettings()})}function setCSRFTokenHeader(t,a,e){var n=$("meta[name=csrf_token]").attr("content");/^(POST|PATCH|PUT|DELETE)$/i.test(e.type)&&a.setRequestHeader("X-CSRF-Token",n)}function contentLoaded(){switch(pageCurrent=window.location.href.split("?")[1].split("=")[1],pageCurrent=pageCurrent.replace("#",""),$("#side-menu").metisMenu(),pageCurrent){case"network_conf":getAllInterfaces(),setupTabs(),setupBtns()}}function loadWifiStations(a){return function(){var t=!0===a?"?refresh":"";$(".js-wifi-stations").addClass("loading-spinner").empty().load("/ajax/networking/wifi_stations.php"+t,function(){$(this).removeClass("loading-spinner")})}}$(document).on("click",".js-add-dhcp-static-lease",function(t){t.preventDefault();var a=$(".js-new-dhcp-static-lease"),e=$("input[name=mac]",a).val().trim(),n=$("input[name=ip]",a).val().trim();if(""!=e&&""!=n){var i=$("#js-dhcp-static-lease-row").html().replace("{{ mac }}",e).replace("{{ ip }}",n);$(".js-dhcp-static-lease-container").append(i),$("input[name=mac]",a).val(""),$("input[name=ip]",a).val("")}}),$(document).on("click",".js-remove-dhcp-static-lease",function(t){t.preventDefault(),$(this).parents(".js-dhcp-static-lease-row").remove()}),$(document).on("submit",".js-dhcp-settings-form",function(t){$(".js-add-dhcp-static-lease").trigger("click")}),$(".js-reload-wifi-stations").on("click",loadWifiStations(!0)),$(document).on("click",".js-toggle-password",function(t){var a=$(t.target),e=$(a.data("target"));e.is(":input")&&(t.preventDefault(),a.data("__toggle-with-initial")||a.data("__toggle-with-initial",a.text()),"password"===e.attr("type")?(a.text(a.data("toggle-with")),e.attr("type","text")):(a.text(a.data("__toggle-with-initial")),e.attr("type","password")))}),$(document).on("keyup",".js-validate-psk",function(t){var a=$(t.target),e=a.data("colors").split(","),n=$(a.data("target"));a.val().length<8||63<a.val().length?(a.css("backgroundColor",e[0]),n.attr("disabled",!0)):(a.css("backgroundColor",e[1]),n.attr("disabled",!1))}),$(document).ajaxSend(setCSRFTokenHeader).ready(contentLoaded).ready(loadWifiStations());
|
||||
@@ -1,88 +0,0 @@
|
||||
// Link quality gauge for ChartJS
|
||||
|
||||
// Support for dark theme
|
||||
theme = getCookie('theme');
|
||||
if (theme == 'lightsout.css') {
|
||||
var borderColor = 'rgba(37, 153, 63, 1)';
|
||||
var labelColor = 'rgba(37, 153, 63, 1)';
|
||||
} else if (theme == 'material-light.php') {
|
||||
var borderColor = '#f2f2fb';
|
||||
var labelColor = '#f2f2fb';
|
||||
} else if (theme == 'material-dark.php') {
|
||||
var borderColor = '#f2f2fb';
|
||||
var labelColor = '#f2f2fb';
|
||||
} else {
|
||||
var borderColor = 'rgba(147, 210, 162, 1)';
|
||||
var labelColor = 'rgba(130, 130, 130, 1)';
|
||||
}
|
||||
|
||||
let data1 = {
|
||||
datasets: [{
|
||||
data: [linkQ, 100-linkQ],
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: borderColor,
|
||||
}],
|
||||
};
|
||||
|
||||
let config = {
|
||||
type: 'doughnut',
|
||||
data: data1,
|
||||
options: {
|
||||
aspectRatio: 2,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
tooltips: {enabled: false},
|
||||
hover: {mode: null},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
rotation: (2/3)*Math.PI,//2+(1/3),
|
||||
circumference: (1+(2/3)) * Math.PI, // * Math.PI,
|
||||
cutoutPercentage: 80,
|
||||
animation: {
|
||||
animateScale: false,
|
||||
animateRotate: true
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
centerText: {
|
||||
display: true,
|
||||
text: linkQ + "%"
|
||||
},
|
||||
plugins: [{
|
||||
beforeDraw: function(chart) {
|
||||
if (chart.config.centerText.display !== null &&
|
||||
typeof chart.config.centerText.display !== 'undefined' &&
|
||||
chart.config.centerText.display) {
|
||||
drawLinkQ(chart);
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
function drawLinkQ(chart) {
|
||||
|
||||
let width = chart.chart.width;
|
||||
let height = chart.chart.height;
|
||||
let ctx = chart.chart.ctx;
|
||||
|
||||
ctx.restore();
|
||||
let fontSize = (height / 100).toFixed(2);
|
||||
ctx.font = fontSize + "em sans-serif";
|
||||
ctx.fillStyle = labelColor;
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
let text = chart.config.centerText.text;
|
||||
let textX = Math.round((width - ctx.measureText(text).width) * 0.5);
|
||||
let textY = height / 2;
|
||||
ctx.fillText(text, textX, textY);
|
||||
ctx.save();
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
let ctx = document.getElementById("divChartLinkQ").getContext("2d");
|
||||
var chart = new Chart(ctx, config);
|
||||
};
|
||||
|
||||
594
app/js/ui/main.js
Normal file
@@ -0,0 +1,594 @@
|
||||
|
||||
function msgShow(retcode,msg) {
|
||||
if(retcode == 0) { var alertType = 'success';
|
||||
} else if(retcode == 2 || retcode == 1) {
|
||||
var alertType = 'danger';
|
||||
}
|
||||
var htmlMsg = '<div class="alert alert-'+alertType+' alert-dismissible" role="alert"><button type="button" class="btn-close" data-dismiss="alert" data-bs-dismiss="alert" aria-label="Close"></button>'+msg+'</div>';
|
||||
return htmlMsg;
|
||||
}
|
||||
|
||||
function createNetmaskAddr(bitCount) {
|
||||
var mask=[];
|
||||
for(i=0;i<4;i++) {
|
||||
var n = Math.min(bitCount, 8);
|
||||
mask.push(256 - Math.pow(2, 8-n));
|
||||
bitCount -= n;
|
||||
}
|
||||
return mask.join('.');
|
||||
}
|
||||
|
||||
function setupTabs() {
|
||||
$('a[data-bs-toggle="tab"]').on('shown.bs.tab',function(e){
|
||||
var target = $(e.target).attr('href');
|
||||
if(!target.match('summary')) {
|
||||
var int = target.replace("#","");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("click", ".js-add-dhcp-static-lease", function(e) {
|
||||
e.preventDefault();
|
||||
var container = $(".js-new-dhcp-static-lease");
|
||||
var mac = $("input[name=mac]", container).val().trim();
|
||||
var ip = $("input[name=ip]", container).val().trim();
|
||||
var comment = $("input[name=comment]", container).val().trim();
|
||||
if (mac == "" || ip == "") {
|
||||
return;
|
||||
}
|
||||
var row = $("#js-dhcp-static-lease-row").html()
|
||||
.replace("{{ mac }}", mac)
|
||||
.replace("{{ ip }}", ip)
|
||||
.replace("{{ comment }}", comment);
|
||||
$(".js-dhcp-static-lease-container").append(row);
|
||||
|
||||
$("input[name=mac]", container).val("");
|
||||
$("input[name=ip]", container).val("");
|
||||
$("input[name=comment]", container).val("");
|
||||
});
|
||||
|
||||
$(document).on("click", ".js-remove-dhcp-static-lease", function(e) {
|
||||
e.preventDefault();
|
||||
$(this).parents(".js-dhcp-static-lease-row").remove();
|
||||
});
|
||||
|
||||
$(document).on("submit", ".js-dhcp-settings-form", function(e) {
|
||||
$(".js-add-dhcp-static-lease").trigger("click");
|
||||
});
|
||||
|
||||
$(document).on("click", ".js-add-dhcp-upstream-server", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var field = $("#add-dhcp-upstream-server-field")
|
||||
var row = $("#dhcp-upstream-server").html().replace("{{ server }}", field.val())
|
||||
|
||||
if (field.val().trim() == "") { return }
|
||||
|
||||
$(".js-dhcp-upstream-servers").append(row)
|
||||
|
||||
field.val("")
|
||||
});
|
||||
|
||||
$(document).on("click", ".js-remove-dhcp-upstream-server", function(e) {
|
||||
e.preventDefault();
|
||||
$(this).parents(".js-dhcp-upstream-server").remove();
|
||||
});
|
||||
|
||||
$(document).on("submit", ".js-dhcp-settings-form", function(e) {
|
||||
$(".js-add-dhcp-upstream-server").trigger("click");
|
||||
});
|
||||
|
||||
/**
|
||||
* mark a form field, e.g. a select box, with the class `.js-field-preset`
|
||||
* and give it an attribute `data-field-preset-target` with a text field's
|
||||
* css selector.
|
||||
*
|
||||
* now, if the element marked `.js-field-preset` receives a `change` event,
|
||||
* its value will be copied to all elements matching the selector in
|
||||
* data-field-preset-target.
|
||||
*/
|
||||
$(document).on("change", ".js-field-preset", function(e) {
|
||||
var selector = this.getAttribute("data-field-preset-target")
|
||||
var value = "" + this.value
|
||||
var syncValue = function(el) { el.value = value }
|
||||
|
||||
if (value.trim() === "") { return }
|
||||
|
||||
document.querySelectorAll(selector).forEach(syncValue)
|
||||
});
|
||||
|
||||
$(document).on("click", "#gen_wpa_passphrase", function(e) {
|
||||
$('#txtwpapassphrase').val(genPassword(63));
|
||||
});
|
||||
|
||||
$(document).on("click", "#gen_apikey", function(e) {
|
||||
$('#txtapikey').val(genPassword(32).toLowerCase());
|
||||
});
|
||||
|
||||
// Enable Bootstrap tooltips
|
||||
$(function () {
|
||||
$('[data-bs-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
|
||||
function genPassword(pwdLen) {
|
||||
var pwdChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var rndPass = Array(pwdLen).fill(pwdChars).map(function(x) { return x[Math.floor(Math.random() * x.length)] }).join('');
|
||||
return rndPass;
|
||||
}
|
||||
|
||||
function setupBtns() {
|
||||
$('#btnSummaryRefresh').click(function(){getAllInterfaces();});
|
||||
$('.intsave').click(function(){
|
||||
var int = $(this).data('int');
|
||||
saveNetworkSettings(int);
|
||||
});
|
||||
$('.intapply').click(function(){
|
||||
applyNetworkSettings();
|
||||
});
|
||||
}
|
||||
|
||||
function setCSRFTokenHeader(event, xhr, settings) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
if (/^(POST|PATCH|PUT|DELETE)$/i.test(settings.type)) {
|
||||
xhr.setRequestHeader("X-CSRF-Token", csrfToken);
|
||||
}
|
||||
}
|
||||
|
||||
function contentLoaded() {
|
||||
pageCurrent = window.location.href.split("/").pop();
|
||||
switch(pageCurrent) {
|
||||
case "network_conf":
|
||||
getAllInterfaces();
|
||||
setupTabs();
|
||||
setupBtns();
|
||||
break;
|
||||
case "hostapd_conf":
|
||||
getChannel();
|
||||
setHardwareModeTooltip();
|
||||
break;
|
||||
case "dhcpd_conf":
|
||||
loadInterfaceDHCPSelect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setDHCPToggles(state) {
|
||||
if ($('#chkfallback').is(':checked') && state) {
|
||||
$('#chkfallback').prop('checked', state);
|
||||
}
|
||||
if ($('#dhcp-iface').is(':checked') && !state) {
|
||||
$('#dhcp-iface').prop('checked', state);
|
||||
setDhcpFieldsDisabled();
|
||||
}
|
||||
$('#chkfallback').prop('disabled', state);
|
||||
$('#dhcp-iface').prop('disabled', !state);
|
||||
}
|
||||
|
||||
$('#chkfallback').change(function() {
|
||||
if ($('#chkfallback').is(':checked')) {
|
||||
setStaticFieldsEnabled();
|
||||
} else {
|
||||
setStaticFieldsDisabled();
|
||||
}
|
||||
});
|
||||
|
||||
$('#performupdateModal').on('shown.bs.modal', function (e) {
|
||||
fetchUpdateResponse();
|
||||
});
|
||||
|
||||
$('#hostapdModal').on('shown.bs.modal', function (e) {
|
||||
var seconds = 3;
|
||||
var pct = 0;
|
||||
var countDown = setInterval(function(){
|
||||
if(seconds <= 0){
|
||||
clearInterval(countDown);
|
||||
}
|
||||
document.getElementsByClassName('progress-bar').item(0).setAttribute('style','width:'+Number(pct)+'%');
|
||||
seconds --;
|
||||
pct = Math.floor(100-(seconds*100/4));
|
||||
}, 500);
|
||||
});
|
||||
|
||||
$('#configureClientModal').on('shown.bs.modal', function (e) {
|
||||
});
|
||||
|
||||
$('#ovpn-confirm-activate').on('shown.bs.modal', function (e) {
|
||||
var data = $(e.relatedTarget).data();
|
||||
$('.btn-activate', this).data('recordId', data.recordId);
|
||||
});
|
||||
|
||||
$('#ovpn-userpw,#ovpn-certs').on('click', function (e) {
|
||||
if (this.id == 'ovpn-userpw') {
|
||||
$('#PanelCerts').hide();
|
||||
$('#PanelUserPW').show();
|
||||
} else if (this.id == 'ovpn-certs') {
|
||||
$('#PanelUserPW').hide();
|
||||
$('#PanelCerts').show();
|
||||
}
|
||||
});
|
||||
|
||||
$('#install-user-plugin').on('shown.bs.modal', function (e) {
|
||||
var button = $(e.relatedTarget);
|
||||
$(this).data('button', button);
|
||||
var manifestData = button.data('plugin-manifest');
|
||||
var installed = button.data('plugin-installed') || false;
|
||||
var repoPublic = button.data('repo-public') || false;
|
||||
var installPath = manifestData.install_path;
|
||||
|
||||
if (!installed && repoPublic && installPath === 'plugins-available') {
|
||||
insidersHTML = 'Available with <i class="fas fa-heart heart me-1"></i><a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener">Insiders</a>';
|
||||
$('#plugin-additional').html(insidersHTML);
|
||||
} else {
|
||||
$('#plugin-additional').empty();
|
||||
}
|
||||
if (manifestData) {
|
||||
$('#plugin-docs').html(manifestData.plugin_docs
|
||||
? `<a href="${manifestData.plugin_docs}" target="_blank">${manifestData.plugin_docs}</a>`
|
||||
: 'Unknown');
|
||||
$('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`);
|
||||
$('#plugin-name').text(manifestData.name || 'Unknown');
|
||||
$('#plugin-version').text(manifestData.version || 'Unknown');
|
||||
$('#plugin-description').text(manifestData.description || 'No description provided');
|
||||
$('#plugin-author').html(manifestData.author
|
||||
? manifestData.author + (manifestData.author_uri
|
||||
? ` (<a href="${manifestData.author_uri}" target="_blank">profile</a>)` : '') : 'Unknown');
|
||||
$('#plugin-license').text(manifestData.license || 'Unknown');
|
||||
$('#plugin-locale').text(manifestData.default_locale || 'Unknown');
|
||||
$('#plugin-configuration').html(formatProperty(manifestData.configuration || 'None'));
|
||||
$('#plugin-packages').html(formatProperty(manifestData.keys || 'None'));
|
||||
$('#plugin-dependencies').html(formatProperty(manifestData.dependencies || 'None'));
|
||||
$('#plugin-javascript').html(formatProperty(manifestData.javascript || 'None'));
|
||||
$('#plugin-sudoers').html(formatProperty(manifestData.sudoers || 'None'));
|
||||
$('#plugin-user-name').html((manifestData.user_nonprivileged && manifestData.user_nonprivileged.name) || 'None');
|
||||
}
|
||||
if (installed) {
|
||||
$('#js-install-plugin-confirm').html('OK');
|
||||
} else if (!installed && repoPublic && installPath == 'plugins-available') {
|
||||
$('#js-install-plugin-confirm').html('Get Insiders');
|
||||
} else {
|
||||
$('#js-install-plugin-confirm').html('Install now');
|
||||
}
|
||||
});
|
||||
|
||||
$('#js-install-plugin-ok').on('click', function (e) {
|
||||
$("#install-plugin-progress").modal('hide');
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
function formatProperty(prop) {
|
||||
if (Array.isArray(prop)) {
|
||||
if (typeof prop[0] === 'object') {
|
||||
return prop.map(item => {
|
||||
return Object.entries(item)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('<br/>');
|
||||
}).join('<br/>');
|
||||
}
|
||||
return prop.map(line => `${line}<br/>`).join('');
|
||||
}
|
||||
if (typeof prop === 'object') {
|
||||
return Object.entries(prop)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('<br/>');
|
||||
}
|
||||
return prop || 'None';
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$("#PanelManual").hide();
|
||||
$('.ip_address').mask('0ZZ.0ZZ.0ZZ.0ZZ', {
|
||||
translation: {
|
||||
'Z': {
|
||||
pattern: /[0-9]/, optional: true
|
||||
}
|
||||
},
|
||||
placeholder: "___.___.___.___"
|
||||
});
|
||||
$('.mac_address').mask('FF:FF:FF:FF:FF:FF', {
|
||||
translation: {
|
||||
'F': {
|
||||
pattern: /[0-9a-fA-F]/, optional: false
|
||||
}
|
||||
},
|
||||
placeholder: "__:__:__:__:__:__"
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.cidr').mask('099.099.099.099/099', {
|
||||
translation: {
|
||||
'0': { pattern: /[0-9]/ }
|
||||
},
|
||||
placeholder: "___.___.___.___/___"
|
||||
});
|
||||
});
|
||||
|
||||
$('#wg-upload,#wg-manual').on('click', function (e) {
|
||||
if (this.id == 'wg-upload') {
|
||||
$('#PanelManual').hide();
|
||||
$('#PanelUpload').show();
|
||||
} else if (this.id == 'wg-manual') {
|
||||
$('#PanelUpload').hide();
|
||||
$('#PanelManual').show();
|
||||
}
|
||||
});
|
||||
|
||||
$(".custom-file-input").on("change", function() {
|
||||
var fileName = $(this).val().split("\\").pop();
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
|
||||
});
|
||||
|
||||
// Event listener for Bootstrap's form validation
|
||||
window.addEventListener('load', function() {
|
||||
// Fetch all the forms we want to apply custom Bootstrap validation styles to
|
||||
var forms = document.getElementsByClassName('needs-validation');
|
||||
// Loop over them and prevent submission
|
||||
var validation = Array.prototype.filter.call(forms, function(form) {
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (form.checkValidity() === false) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
});
|
||||
}, false);
|
||||
|
||||
function showSessionExpiredModal() {
|
||||
$('#sessionTimeoutModal').modal('show');
|
||||
}
|
||||
|
||||
$(document).on("click", "#js-session-expired-login", function(e) {
|
||||
const loginModal = $('#modal-admin-login');
|
||||
const redirectUrl = window.location.pathname;
|
||||
window.location.href = `/login?action=${encodeURIComponent(redirectUrl)}`;
|
||||
});
|
||||
|
||||
// show modal login on page load
|
||||
$(document).ready(function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const redirectUrl = $('#redirect-url').val() || params.get('action') || '/';
|
||||
$('#modal-admin-login').modal('show');
|
||||
$('#redirect-url').val(redirectUrl);
|
||||
$('#username').focus();
|
||||
$('#username').addClass("focusedInput");
|
||||
});
|
||||
|
||||
// DHCP or Static IP option group
|
||||
$('#chkstatic').on('change', function() {
|
||||
if (this.checked) {
|
||||
setStaticFieldsEnabled();
|
||||
}
|
||||
});
|
||||
|
||||
$('#chkdhcp').on('change', function() {
|
||||
this.checked ? setStaticFieldsDisabled() : null;
|
||||
});
|
||||
|
||||
|
||||
$('input[name="dhcp-iface"]').change(function() {
|
||||
if ($('input[name="dhcp-iface"]:checked').val() == '1') {
|
||||
setDhcpFieldsEnabled();
|
||||
} else {
|
||||
setDhcpFieldsDisabled();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function setStaticFieldsEnabled() {
|
||||
$('#txtipaddress').prop('required', true);
|
||||
$('#txtsubnetmask').prop('required', true);
|
||||
$('#txtgateway').prop('required', true);
|
||||
|
||||
$('#txtipaddress').removeAttr('disabled');
|
||||
$('#txtsubnetmask').removeAttr('disabled');
|
||||
$('#txtgateway').removeAttr('disabled');
|
||||
}
|
||||
|
||||
function setStaticFieldsDisabled() {
|
||||
$('#txtipaddress').prop('disabled', true);
|
||||
$('#txtsubnetmask').prop('disabled', true);
|
||||
$('#txtgateway').prop('disabled', true);
|
||||
|
||||
$('#txtipaddress').removeAttr('required');
|
||||
$('#txtsubnetmask').removeAttr('required');
|
||||
$('#txtgateway').removeAttr('required');
|
||||
}
|
||||
|
||||
function setDhcpFieldsEnabled() {
|
||||
$('#txtrangestart').prop('required', true);
|
||||
$('#txtrangeend').prop('required', true);
|
||||
$('#txtrangeleasetime').prop('required', true);
|
||||
$('#cbxrangeleasetimeunits').prop('required', true);
|
||||
|
||||
$('#txtrangestart').removeAttr('disabled');
|
||||
$('#txtrangeend').removeAttr('disabled');
|
||||
$('#txtrangeleasetime').removeAttr('disabled');
|
||||
$('#cbxrangeleasetimeunits').removeAttr('disabled');
|
||||
$('#txtdns1').removeAttr('disabled');
|
||||
$('#txtdns2').removeAttr('disabled');
|
||||
$('#txtmetric').removeAttr('disabled');
|
||||
}
|
||||
|
||||
function setDhcpFieldsDisabled() {
|
||||
$('#txtrangestart').removeAttr('required');
|
||||
$('#txtrangeend').removeAttr('required');
|
||||
$('#txtrangeleasetime').removeAttr('required');
|
||||
$('#cbxrangeleasetimeunits').removeAttr('required');
|
||||
|
||||
$('#txtrangestart').prop('disabled', true);
|
||||
$('#txtrangeend').prop('disabled', true);
|
||||
$('#txtrangeleasetime').prop('disabled', true);
|
||||
$('#cbxrangeleasetimeunits').prop('disabled', true);
|
||||
$('#txtdns1').prop('disabled', true);
|
||||
$('#txtdns2').prop('disabled', true);
|
||||
$('#txtmetric').prop('disabled', true);
|
||||
}
|
||||
|
||||
// Static Array method
|
||||
Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);
|
||||
|
||||
$(document).on("click", ".js-toggle-password", function(e) {
|
||||
var button = $(e.currentTarget);
|
||||
var field = $(button.data("bsTarget"));
|
||||
if (field.is(":input")) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!button.data("__toggle-with-initial")) {
|
||||
$("i", button).removeClass("fas fa-eye").addClass(button.attr("data-toggle-with"));
|
||||
}
|
||||
|
||||
if (field.attr("type") === "password") {
|
||||
field.attr("type", "text");
|
||||
} else {
|
||||
$("i", button).removeClass("fas fa-eye-slash").addClass("fas fa-eye");
|
||||
field.attr("type", "password");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$('#theme-select').change(function() {
|
||||
var theme = themes[$( "#theme-select" ).val() ];
|
||||
|
||||
var hasDarkTheme = theme === 'custom.php';
|
||||
var nightModeChecked = $("#night-mode").prop("checked");
|
||||
|
||||
if (nightModeChecked && hasDarkTheme) {
|
||||
if (theme === "custom.php") {
|
||||
set_theme("dark.css");
|
||||
}
|
||||
} else {
|
||||
set_theme(theme);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function set_theme(theme) {
|
||||
$('link[title="main"]').attr('href', 'app/css/' + theme);
|
||||
// persist selected theme in cookie
|
||||
setCookie('theme',theme,90);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
var currentTheme = getCookie('theme');
|
||||
// Check if the current theme is a dark theme
|
||||
var isDarkTheme = currentTheme === 'dark.css';
|
||||
|
||||
$('#night-mode').prop('checked', isDarkTheme);
|
||||
$('#night-mode').change(function() {
|
||||
var state = $(this).is(':checked');
|
||||
var currentTheme = getCookie('theme');
|
||||
|
||||
if (state == true) {
|
||||
if (currentTheme == 'custom.php') {
|
||||
set_theme('dark.css');
|
||||
}
|
||||
} else {
|
||||
if (currentTheme == 'dark.css') {
|
||||
set_theme('custom.php');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||
var expires = "expires="+ d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
}
|
||||
|
||||
function getCookie(cname) {
|
||||
var regx = new RegExp(cname + "=([^;]+)");
|
||||
var value = regx.exec(document.cookie);
|
||||
return (value != null) ? unescape(value[1]) : null;
|
||||
}
|
||||
|
||||
// Define themes
|
||||
var themes = {
|
||||
"default": "custom.php",
|
||||
"hackernews" : "hackernews.css"
|
||||
}
|
||||
|
||||
// Adds active class to current nav-item
|
||||
$(window).bind("load", function() {
|
||||
var url = window.location;
|
||||
$('.sb-nav-link-icon a').filter(function() {
|
||||
return this.href == url;
|
||||
}).parent().addClass('active');
|
||||
});
|
||||
|
||||
// Sets focus on a specified tab
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const targetTab = params.get("tab");
|
||||
if (targetTab) {
|
||||
let tabElement = document.querySelector(`[data-bs-toggle="tab"][href="#${targetTab}"]`);
|
||||
if (tabElement) {
|
||||
let tab = new bootstrap.Tab(tabElement);
|
||||
tab.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function disableValidation(form) {
|
||||
form.removeAttribute("novalidate");
|
||||
form.classList.remove("needs-validation");
|
||||
form.querySelectorAll("[required]").forEach(function (field) {
|
||||
field.removeAttribute("required");
|
||||
});
|
||||
}
|
||||
|
||||
function updateActivityLED() {
|
||||
const threshold_bytes = 300;
|
||||
fetch('app/net_activity')
|
||||
.then(res => res.text())
|
||||
.then(data => {
|
||||
const activity = parseInt(data.trim());
|
||||
const leds = document.querySelectorAll('.hostapd-led');
|
||||
|
||||
if (!isNaN(activity)) {
|
||||
leds.forEach(led => {
|
||||
if (activity > threshold_bytes) {
|
||||
led.classList.add('led-pulse');
|
||||
setTimeout(() => {
|
||||
led.classList.remove('led-pulse');
|
||||
}, 50);
|
||||
} else {
|
||||
led.classList.remove('led-pulse');
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => { /* ignore fetch errors */ });
|
||||
}
|
||||
setInterval(updateActivityLED, 100);
|
||||
|
||||
$(document).ready(function() {
|
||||
const $htmlElement = $('html');
|
||||
const $modeswitch = $('#night-mode');
|
||||
$modeswitch.on('change', function() {
|
||||
const isChecked = $(this).is(':checked');
|
||||
const newTheme = isChecked ? 'dark' : 'light';
|
||||
$htmlElement.attr('data-bs-theme', newTheme);
|
||||
localStorage.setItem('bsTheme', newTheme);
|
||||
});
|
||||
});
|
||||
|
||||
$(document)
|
||||
.ajaxSend(setCSRFTokenHeader)
|
||||
.ready(contentLoaded)
|
||||
.ready(loadWifiStations());
|
||||
|
||||
// To auto-close Bootstrap alerts; time is in milliseconds
|
||||
const alertTimeout = parseInt(getCookie('alert_timeout'), 10);
|
||||
|
||||
if (!isNaN(alertTimeout) && alertTimeout > 0) {
|
||||
window.setTimeout(function() {
|
||||
$(".alert").fadeTo(500, 0).slideUp(500, function(){
|
||||
$(this).remove();
|
||||
});
|
||||
}, alertTimeout);
|
||||
}
|
||||
|
||||
189
app/js/vendor/speedtestUI.js
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
function I(i){return document.getElementById(i);}
|
||||
|
||||
const origin=window.location.origin;
|
||||
const host=window.location.host;
|
||||
var SPEEDTEST_SERVERS=[
|
||||
{
|
||||
"name":"RaspAP Speedtest server (US)",
|
||||
"server":"https://speedtest.raspap.com/",
|
||||
"dlURL":"backend/garbage.php",
|
||||
"ulURL":"backend/empty.php",
|
||||
"pingURL":"backend/empty.php",
|
||||
"getIpURL":"backend/getIP.php"
|
||||
},
|
||||
{
|
||||
"name":"RaspAP ("+host+")",
|
||||
"server":origin,
|
||||
"dlURL":"dist/speedtest/backend/garbage.php",
|
||||
"ulURL":"dist/speedtest/backend/empty.php",
|
||||
"pingURL":"dist/speedtest/backend/empty.php",
|
||||
"getIpURL":"dist/speedtest/backend/getIP.php"
|
||||
}
|
||||
];
|
||||
|
||||
//INITIALIZE SPEEDTEST
|
||||
var s=new Speedtest(); //create speedtest object
|
||||
s.setParameter("telemetry_level","basic"); //enable telemetry
|
||||
|
||||
//SERVER AUTO SELECTION
|
||||
function initServers(){
|
||||
var noServersAvailable=function(){
|
||||
I("message").innerHTML="No servers available";
|
||||
}
|
||||
var runServerSelect=function(){
|
||||
s.selectServer(function(server){
|
||||
if(server!=null){ //at least 1 server is available
|
||||
I("loading").className="hidden"; //hide loading message
|
||||
//populate server list for manual selection
|
||||
for(var i=0;i<SPEEDTEST_SERVERS.length;i++){
|
||||
//if(SPEEDTEST_SERVERS[i].pingT==-1) continue;
|
||||
var option=document.createElement("option");
|
||||
option.value=i;
|
||||
option.textContent=SPEEDTEST_SERVERS[i].name;
|
||||
if(SPEEDTEST_SERVERS[i]===server) option.selected=true;
|
||||
I("server").appendChild(option);
|
||||
}
|
||||
//show test UI
|
||||
I("testWrapper").className="visible";
|
||||
initUI();
|
||||
}else{ //no servers are available, the test cannot proceed
|
||||
noServersAvailable();
|
||||
}
|
||||
});
|
||||
}
|
||||
if(typeof SPEEDTEST_SERVERS === "string"){
|
||||
//need to fetch list of servers from specified URL
|
||||
s.loadServerList(SPEEDTEST_SERVERS,function(servers){
|
||||
if(servers==null){ //failed to load server list
|
||||
noServersAvailable();
|
||||
}else{ //server list loaded
|
||||
SPEEDTEST_SERVERS=servers;
|
||||
runServerSelect();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
//hardcoded server list
|
||||
s.addTestPoints(SPEEDTEST_SERVERS);
|
||||
runServerSelect();
|
||||
}
|
||||
}
|
||||
|
||||
var meterBk=/Trident.*rv:(\d+\.\d+)/i.test(navigator.userAgent)?"#EAEAEA":"#80808040";
|
||||
var dlColor="#4BC0C0",
|
||||
ulColor="#616161";
|
||||
var progColor=meterBk;
|
||||
|
||||
//CODE FOR GAUGES
|
||||
function drawMeter(c,amount,bk,fg,progress,prog){
|
||||
var ctx=c.getContext("2d");
|
||||
var dp=window.devicePixelRatio||1;
|
||||
var cw=c.clientWidth*dp, ch=c.clientHeight*dp;
|
||||
var sizScale=ch*0.0055;
|
||||
if(c.width==cw&&c.height==ch){
|
||||
ctx.clearRect(0,0,cw,ch);
|
||||
}else{
|
||||
c.width=cw;
|
||||
c.height=ch;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle=bk;
|
||||
ctx.lineWidth=12*sizScale;
|
||||
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,Math.PI*0.1);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle=fg;
|
||||
ctx.lineWidth=12*sizScale;
|
||||
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,amount*Math.PI*1.2-Math.PI*1.1);
|
||||
ctx.stroke();
|
||||
if(typeof progress !== "undefined"){
|
||||
ctx.fillStyle=prog;
|
||||
ctx.fillRect(c.width*0.3,c.height-16*sizScale,c.width*0.4*progress,4*sizScale);
|
||||
}
|
||||
}
|
||||
function mbpsToAmount(s){
|
||||
return 1-(1/(Math.pow(1.3,Math.sqrt(s))));
|
||||
}
|
||||
function format(d){
|
||||
d=Number(d);
|
||||
if(d<10) return d.toFixed(2);
|
||||
if(d<100) return d.toFixed(1);
|
||||
return d.toFixed(0);
|
||||
}
|
||||
|
||||
//UI CODE
|
||||
var uiData=null;
|
||||
function startStop(){
|
||||
if(s.getState()==3){
|
||||
//speedtest is running, abort
|
||||
s.abort();
|
||||
data=null;
|
||||
I("startStopBtn").className="btn btn-outline btn-primary";
|
||||
I("server").disabled=false;
|
||||
initUI();
|
||||
}else{
|
||||
//test is not running, begin
|
||||
I("startStopBtn").className="btn btn-outline btn-primary running";
|
||||
I("server").disabled=true;
|
||||
s.onupdate=function(data){
|
||||
uiData=data;
|
||||
};
|
||||
s.onend=function(aborted){
|
||||
I("startStopBtn").className="btn btn-outline btn-primary";
|
||||
I("server").disabled=false;
|
||||
updateUI(true);
|
||||
if(!aborted){
|
||||
//if testId is present, show sharing panel, otherwise do nothing
|
||||
try{
|
||||
var testId=uiData.testId;
|
||||
if(testId!=null){
|
||||
var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
|
||||
I("resultsImg").src=shareURL;
|
||||
I("resultsURL").value=shareURL;
|
||||
I("testId").innerHTML=testId;
|
||||
I("shareArea").style.display="";
|
||||
}
|
||||
}catch(e){}
|
||||
}
|
||||
};
|
||||
s.start();
|
||||
}
|
||||
}
|
||||
//this function reads the data sent back by the test and updates the UI
|
||||
function updateUI(forced){
|
||||
if(!forced&&s.getState()!=3) return;
|
||||
if(uiData==null) return;
|
||||
var status=uiData.testState;
|
||||
I("ip").textContent=uiData.clientIp;
|
||||
I("dlText").textContent=(status==1&&uiData.dlStatus==0)?"...":format(uiData.dlStatus);
|
||||
drawMeter(I("dlMeter"),mbpsToAmount(Number(uiData.dlStatus*(status==1?oscillate():1))),meterBk,dlColor,Number(uiData.dlProgress),progColor);
|
||||
I("ulText").textContent=(status==3&&uiData.ulStatus==0)?"...":format(uiData.ulStatus);
|
||||
drawMeter(I("ulMeter"),mbpsToAmount(Number(uiData.ulStatus*(status==3?oscillate():1))),meterBk,ulColor,Number(uiData.ulProgress),progColor);
|
||||
I("pingText").textContent=format(uiData.pingStatus);
|
||||
I("jitText").textContent=format(uiData.jitterStatus);
|
||||
}
|
||||
function oscillate(){
|
||||
return 1+0.02*Math.sin(Date.now()/100);
|
||||
}
|
||||
//update the UI every frame
|
||||
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||(function(callback,element){setTimeout(callback,1000/60);});
|
||||
function frame(){
|
||||
requestAnimationFrame(frame);
|
||||
updateUI();
|
||||
}
|
||||
frame(); //start frame loop
|
||||
//function to (re)initialize UI
|
||||
function initUI(){
|
||||
drawMeter(I("dlMeter"),0,meterBk,dlColor,0);
|
||||
drawMeter(I("ulMeter"),0,meterBk,ulColor,0);
|
||||
I("dlText").textContent="";
|
||||
I("ulText").textContent="";
|
||||
I("pingText").textContent="";
|
||||
I("jitText").textContent="";
|
||||
I("ip").textContent="";
|
||||
}
|
||||
// add EventListener to diagnostic tab
|
||||
var e = document.querySelectorAll("a[href^='#diagnostic']");
|
||||
e[0].addEventListener("click", function(){
|
||||
initServers()
|
||||
});
|
||||
|
||||
78
app/lib/signprint.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/defaults.php';
|
||||
|
||||
// prevent direct file access
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
||||
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
|
||||
$ssid = $hostapd['ssid'];
|
||||
$password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase'];
|
||||
|
||||
?>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title><?php echo _("Printable Wi-Fi sign"); ?></title>
|
||||
<!-- Bootstrap Core CSS -->
|
||||
<link href="../../dist/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
|
||||
<!-- SB-Admin-2 CSS -->
|
||||
<link href="../../dist/sb-admin-2/css/sb-admin-2.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link href="../../dist/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body id="page-top">
|
||||
<div id="wrapper">
|
||||
<div id="content-wrapper" class="d-flex flex-column">
|
||||
<div id="container">
|
||||
<div class="row text-center">
|
||||
<div class="col">
|
||||
<div class="mt-5 mb-5">
|
||||
<h2><i class="fas fa-wifi"></i> <?php echo _("Wi-Fi Connect"); ?></h2>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /row -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col"></div>
|
||||
<div class="col-5">
|
||||
<img src="../img/wifi-qr-code.php" class="figure-img img-fluid" alt="RaspAP Wifi QR code" style="width:100%;">
|
||||
</div>
|
||||
<div class="col"></div>
|
||||
</div><!-- /row -->
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="col"></div>
|
||||
<div class="col-8">
|
||||
<div class="mt-4">
|
||||
<div><?php echo _("To connect with your phone or tablet, scan the QR code above with your camera app."); ?></div>
|
||||
<div><?php echo _("For other devices, use the login credentials below."); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col"></div>
|
||||
</div><!-- /row -->
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="col"></div>
|
||||
<div class="col-8">
|
||||
<div class="mt-4">
|
||||
<?php echo _("Network"); ?>
|
||||
<h3 class="mb-3"><?php echo $ssid ?></h3>
|
||||
<?php echo _("Password"); ?>
|
||||
<h3 class="mb-5"><?php echo $password ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col"></div>
|
||||
</div><!-- /row -->
|
||||
|
||||
</div><!-- /content -->
|
||||
</div><!-- /content-wrapper -->
|
||||
</div><!-- /page wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,7 +2,7 @@ server.modules += (
|
||||
"mod_rewrite",
|
||||
)
|
||||
|
||||
$HTTP["url"] =~ "^/REPLACE_ME/(?!(dist|app|ajax|config)).*" {
|
||||
$HTTP["url"] =~ "^/REPLACE_ME/(?!(dist|app|ajax|config|rootCA\.pem)).*" {
|
||||
url.rewrite-once = ( "^/REPLACE_ME/(.*?)(\?.+)?$"=>"/REPLACE_ME/index.php/$1$2" )
|
||||
server.error-handler-404 = "/REPLACE_ME/index.php"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,45 @@
|
||||
{
|
||||
"StevenBlack/hosts": [
|
||||
"StevenBlack/hosts (default)"
|
||||
],
|
||||
"badmojr/hosts": [
|
||||
"badmojr/1Hosts (Mini)",
|
||||
"badmojr/1Hosts (Lite)",
|
||||
"badmojr/1Hosts (Pro)",
|
||||
"badmojr/1Hosts (Xtra)"
|
||||
],
|
||||
"OISD/domains": [
|
||||
"oisd/big (default)",
|
||||
"oisd/small",
|
||||
"oisd/nsfw"
|
||||
]
|
||||
"StevenBlack/hosts": {
|
||||
"StevenBlack/hosts (default)": {
|
||||
"list_url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
"dest_file": "hostnames.txt"
|
||||
}
|
||||
},
|
||||
"HaGeZi/hosts": {
|
||||
"hagezi/hosts (Light)": {
|
||||
"list_url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/hosts/light.txt",
|
||||
"dest_file": "hostnames.txt"
|
||||
},
|
||||
"hagezi/hosts (Normal)": {
|
||||
"list_url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/hosts/multi.txt",
|
||||
"dest_file": "hostnames.txt"
|
||||
},
|
||||
"hagezi/hosts (Pro)": {
|
||||
"list_url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/hosts/pro.txt",
|
||||
"dest_file": "hostnames.txt"
|
||||
},
|
||||
"hagezi/hosts (Pro++)": {
|
||||
"list_url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/hosts/pro.plus.txt",
|
||||
"dest_file": "hostnames.txt"
|
||||
},
|
||||
"hagezi/hosts (Ultimate)": {
|
||||
"list_url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/hosts/ultimate.txt",
|
||||
"dest_file": "hostnames.txt"
|
||||
}
|
||||
},
|
||||
"OISD/domains": {
|
||||
"oisd/big (default)": {
|
||||
"list_url": "https://big.oisd.nl/dnsmasq",
|
||||
"dest_file": "domains.txt"
|
||||
},
|
||||
"oisd/small": {
|
||||
"list_url": "https://small.oisd.nl/dnsmasq",
|
||||
"dest_file": "domains.txt"
|
||||
},
|
||||
"oisd/nsfw": {
|
||||
"list_url": "https://nsfw.oisd.nl/dnsmasq",
|
||||
"dest_file": "domains.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ define('RASPI_OPENVPN_CLIENT_CONFIG', '/etc/openvpn/client/client.conf');
|
||||
define('RASPI_OPENVPN_CLIENT_LOGIN', '/etc/openvpn/client/login.conf');
|
||||
define('RASPI_WIREGUARD_PATH', '/etc/wireguard/');
|
||||
define('RASPI_WIREGUARD_CONFIG', RASPI_WIREGUARD_PATH.'wg0.conf');
|
||||
define('RASPI_IPTABLES_CONF', RASPI_CONFIG.'/networking/iptables_rules.json');
|
||||
define('RASPI_TORPROXY_CONFIG', '/etc/tor/torrc');
|
||||
define('RASPI_LIGHTTPD_CONFIG', '/etc/lighttpd/lighttpd.conf');
|
||||
define('RASPI_ACCESS_CHECK_IP', '1.1.1.1');
|
||||
@@ -40,6 +41,10 @@ define('RASPI_ACCESS_CHECK_DNS', 'one.one.one.one');
|
||||
// Constant for the GitHub API latest release endpoint
|
||||
define('RASPI_API_ENDPOINT', 'https://api.github.com/repos/RaspAP/raspap-webgui/releases/latest');
|
||||
|
||||
// Captive portal detection - returns 204 or 200 is successful
|
||||
define('RASPI_ACCESS_CHECK_URL', 'http://detectportal.firefox.com');
|
||||
define('RASPI_ACCESS_CHECK_URL_CODE', 200);
|
||||
|
||||
// Constant for the 5GHz wireless regulatory domain
|
||||
define("RASPI_5GHZ_CHANNEL_MIN", 100);
|
||||
define("RASPI_5GHZ_CHANNEL_MAX", 192);
|
||||
@@ -64,6 +69,7 @@ define('RASPI_SYSTEM_ENABLED', true);
|
||||
define('RASPI_MONITOR_ENABLED', false);
|
||||
define('RASPI_RESTAPI_ENABLED', false);
|
||||
define('RASPI_PLUGINS_ENABLED', true);
|
||||
define('RASPI_UI_STATIC_LOGO', false);
|
||||
|
||||
// Locale settings
|
||||
define('LOCALE_ROOT', 'locale');
|
||||
|
||||
@@ -1,23 +1,87 @@
|
||||
{
|
||||
"hostapd": {
|
||||
"modes": {
|
||||
"n": {
|
||||
"settings": [
|
||||
"hw_mode=g",
|
||||
"ieee80211n=1",
|
||||
"wmm_enabled=1"
|
||||
]
|
||||
},
|
||||
"ac": {
|
||||
"settings": [
|
||||
"hw_mode=a",
|
||||
"# N",
|
||||
"ieee80211n=1",
|
||||
"require_ht=1",
|
||||
"ht_capab=[MAX-AMSDU-3839][HT40+][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]",
|
||||
"# AC",
|
||||
"ieee80211ac=1",
|
||||
"require_vht=1",
|
||||
"ieee80211d=0",
|
||||
"ieee80211h=0",
|
||||
"vht_capab=[MAX-AMSDU-3839][SHORT-GI-80]",
|
||||
"vht_oper_chwidth=1",
|
||||
"vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}"
|
||||
]
|
||||
},
|
||||
"g": {
|
||||
"settings": [
|
||||
"hw_mode=g",
|
||||
"ieee80211n=0"
|
||||
]
|
||||
},
|
||||
"a": {
|
||||
"settings": [
|
||||
"hw_mode=a",
|
||||
"ieee80211n=0"
|
||||
]
|
||||
},
|
||||
"b": {
|
||||
"settings": [
|
||||
"hw_mode=b",
|
||||
"ieee80211n=0"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dhcp": {
|
||||
"wlan0": {
|
||||
"static ip_address": [ "10.3.141.1/24" ],
|
||||
"wlan0": {
|
||||
"static ip_address": [ "10.3.141.1" ],
|
||||
"static routers": [ "10.3.141.1" ],
|
||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||
"subnetmask": [ "255.255.255.0" ]
|
||||
},
|
||||
"wlan1": {
|
||||
"static ip_address": [ "10.9.141.1/24" ],
|
||||
"static ip_address": [ "10.9.141.1" ],
|
||||
"static routers": [ "10.9.141.1" ],
|
||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||
"subnetmask": [ "255.255.255.0" ]
|
||||
},
|
||||
"wlan2": {
|
||||
"static ip_address": [ "10.6.141.1" ],
|
||||
"static routers": [ "10.6.141.1" ],
|
||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||
"subnetmask": [ "255.255.255.0" ]
|
||||
},
|
||||
"uap0": {
|
||||
"static ip_address": [ "192.168.50.1/24" ],
|
||||
"static ip_address": ["192.168.50.1" ],
|
||||
"static routers": [ "192.168.50.1" ],
|
||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||
"subnetmask": [ "255.255.255.0" ]
|
||||
},
|
||||
"eth0": {
|
||||
"static ip_address": [ "192.168.55.1" ],
|
||||
"static routers": [ "192.168.55.1" ],
|
||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||
"subnetmask": [ "255.255.255.0" ]
|
||||
},
|
||||
"enx": {
|
||||
"static ip_address": [ "192.168.60.1" ],
|
||||
"static routers": [ "192.168.60.1" ],
|
||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||
"subnetmask": [ "255.255.255.0" ]
|
||||
},
|
||||
"options": {
|
||||
"# RaspAP default configuration": null,
|
||||
"hostname": null,
|
||||
@@ -39,8 +103,17 @@
|
||||
"wlan1": {
|
||||
"dhcp-range": [ "10.9.141.50,10.9.141.254,255.255.255.0,12h" ]
|
||||
},
|
||||
"wlan2": {
|
||||
"dhcp-range": [ "10.6.141.50,10.6.141.254,255.255.255.0,12h" ]
|
||||
},
|
||||
"uap0": {
|
||||
"dhcp-range": [ "192.168.50.50,192.168.50.150,12h" ]
|
||||
},
|
||||
"eth0": {
|
||||
"dhcp-range": [ "192.168.55.50,192.168.55.150,12h" ]
|
||||
},
|
||||
"enx": {
|
||||
"dhcp-range": [ "192.168.60.50,192.168.60.150,12h" ]
|
||||
}
|
||||
},
|
||||
"wireguard": {
|
||||
@@ -48,14 +121,16 @@
|
||||
"Address": [ "10.8.2.1/24" ],
|
||||
"ListenPort": [ "51820" ],
|
||||
"DNS": [ "9.9.9.9" ],
|
||||
"PostUp": [ "iptables -A FORWARD -i wlan0 -o wg0 -j ACCEPT; iptables -A FORWARD -i wg0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE" ],
|
||||
"PostDown": [ "iptables -D FORWARD -i wlan0 -o wg0 -j ACCEPT; iptables -D FORWARD -i wg0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE" ]
|
||||
"PostUp": [ "iptables -A FORWARD -i wlan0 -o %i -j ACCEPT; iptables -A FORWARD -i %i -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -A POSTROUTING -o %i -j MASQUERADE" ],
|
||||
"PostDown": [ "iptables -D FORWARD -i wlan0 -o %i -j ACCEPT; iptables -D FORWARD -i %i -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -D POSTROUTING -o %i -j MASQUERADE" ],
|
||||
"PostUpEx": [ "iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL ! -d %s -j REJECT" ],
|
||||
"PreDown": [ "iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL ! -d %s -j REJECT" ]
|
||||
},
|
||||
"peer": {
|
||||
"Address": [ "10.8.1.2/24" ],
|
||||
"Endpoint": [ "10.8.2.1:51820" ],
|
||||
"ListenPort": [ "21841" ],
|
||||
"AllowedIPs": ["10.8.2.0/24"],
|
||||
"AllowedIPs": [ "10.8.2.0/24" ],
|
||||
"PersistentKeepalive": [ "15" ]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ ctrl_interface_group=0
|
||||
beacon_int=100
|
||||
auth_algs=1
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
ssid=raspi-webgui
|
||||
ssid=RaspAP
|
||||
channel=1
|
||||
hw_mode=g
|
||||
wpa_passphrase=ChangeMe
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
files:
|
||||
- source: /locale/en_US/LC_MESSAGES/messages.po
|
||||
translation: /locale/%locale_with_underscore%/LC_MESSAGES/%original_file_name%
|
||||
BIN
dist/raspap/css/fonts/RaspAP.eot
vendored
3
dist/raspap/css/fonts/RaspAP.svg
vendored
@@ -9,4 +9,7 @@
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="wireguard" d="M1023.147 463.147c0 0 23.595 496.853-522.453 496.853-482.859 0-497.963-476.587-497.963-476.587s-70.997-547.413 509.141-547.413c556.501 0 511.275 527.147 511.275 527.147zM347.947 636.757c102.4 62.72 233.344 24.363 282.368-69.888 9.301-17.877 10.496-45.355 4.608-64.128-20.352-64.683-68.309-100.949-134.187-116.395 19.413 16.64 34.859 35.499 39.808 61.525 1.195 5.504 1.88 11.827 1.88 18.31 0 20.027-6.533 38.528-17.584 53.488l0.174-0.246c-16.797 22.874-43.588 37.556-73.809 37.556-11.257 0-22.038-2.037-31.995-5.763l0.63 0.207c-40.533-15.36-62.72-52.395-58.752-97.877 3.712-42.24 35.797-69.632 95.787-80.043-8.96-4.736-15.872-8.235-22.613-11.989-27.988-15.524-51.374-35.995-69.74-60.451l-0.404-0.562c-6.101-8.192-10.24-8.875-19.541-3.2-120.619 73.771-128.384 258.859 3.371 339.456zM257.707 180.992c-19.413-4.949-38.187-12.203-57.984-18.688 9.685 65.365 86.229 125.568 150.997 118.699-18.043-24.598-29.583-54.982-31.551-87.945l-0.022-0.46c-21.504-3.968-41.813-6.613-61.44-11.605zM669.995 819.2c19.115-0.725 38.315-0.427 57.472-0.853 5.287-0.363 10.162-1.075 14.91-2.128l-0.659 0.123c-4.574-6.938-9.348-12.986-14.582-18.599l0.076 0.082c-6.827-6.4-14.549-12.629-24.448-2.944-2.347 2.347-7.979 1.792-12.075 1.877-19.072 0.213-38.144 0.853-57.173 0.128-17.856-0.589-34.82-2.396-51.386-5.353l2.149 0.318c-3.072-0.555-7.595-10.667-6.229-14.421 3.328-8.832 8.149-18.56 15.317-24.192 26.411-20.907 54.485-39.595 81.067-60.288 25.771-20.139 49.792-42.24 64.427-72.533 19.029-39.595 19.627-81.067 11.392-122.752-13.739-69.547-48.939-127.147-105.941-169.045-22.955-16.853-51.413-26.453-77.696-38.528-23.168-10.667-46.933-19.84-70.144-30.379-41.813-19.029-65.28-64.427-58.411-111.573 6.357-43.307 44.373-79.445 87.851-86.912 52.181-8.96 106.069 25.003 118.827 78.080 14.336 59.605-18.048 112.896-78.72 129.024l-10.923 2.816c16.213 7.253 30.208 12.416 43.179 19.541q33.835 18.645 66.475 39.467c6.4 4.096 9.856 4.096 15.36-0.597 41.685-36.096 66.56-80.981 73.557-135.979 11.52-91.093-31.573-174.763-112.896-217.643-125.781-66.347-279.765 9.173-307.541 148.651-23.808 119.467 60.501 227.84 162.005 248.747 43.648 9.003 83.541 27.179 114.56 60.8 20.053 21.675 29.739 40.277 33.067 48.683 5.86 14.568 9.259 31.458 9.259 49.142 0 0.094 0 0.187 0 0.281v-0.014c-0.72 15.473-4.371 29.921-10.408 43.044l0.296-0.719c-10.581 24.149-51.2 62.549-61.227 70.656l-95.573 74.837c-3.371 2.773-7.168 2.56-15.36 2.005-9.813-0.683-34.773-2.048-45.525 0.768 8.704 6.613 32.427 16.213 42.667 23.893-30.976 20.907-66.304 13.397-98.773 19.627 7.509 13.995 44.629 35.456 65.749 37.888-1.455 13.545-3.483 25.484-6.166 37.173l0.406-2.101c-1.28 4.736-6.571 9.387-11.221 12.075-11.179 6.571-23.083 11.989-35.968 18.517 10.935 7.156 24.244 11.558 38.555 11.945l0.101 0.002c1.66 0.068 3.608 0.107 5.566 0.107 11.77 0 23.21-1.408 34.163-4.064l-0.987 0.202c23.040-5.248 41.387-1.792 59.691 13.824-14.421 5.803-28.843 11.093-42.795 17.365-16.163 7.396-29.343 14.415-42.082 22.091l1.89-1.056c36.267-5.035 71.296-18.645 108.373-13.653l0.939 5.035-86.101 20.053c51.328 4.693 99.115 5.461 144.384-16.555 12.757-6.229 26.027-11.349 38.272-18.432 5.973-3.413 9.941-10.24 14.848-15.573 3.84-4.181 6.997-9.813 11.776-12.373 18.091-9.6 37.973-9.984 58.283-9.515l0.427 6.827c20.437-6.4 43.392-29.952 43.392-47.147-33.109 0-66.133 0.128-99.2-0.171-3.541 0-7.040-2.603-10.539-4.011 3.328-1.963 6.613-5.461 10.027-5.589zM627.328 868.139c-1.461-0.899-2.42-2.488-2.42-4.302 0-1.516 0.67-2.876 1.731-3.799l0.006-0.005c1.344-2.305 3.804-3.83 6.62-3.83 1.429 0 2.767 0.393 3.91 1.076l-0.035-0.019c3.2 1.621 6.315 3.328 10.155 5.333-3.072 2.645-5.547 4.864-8.107 6.955-4.523 3.712-8.235 1.365-11.861-1.408z" />
|
||||
<glyph unicode="" glyph-name="raspap" horiz-adv-x="1031" d="M540.058 281.983c0-104.182-84.446-188.637-188.625-188.637-104.176 0-188.62 84.455-188.62 188.637 0 104.171 84.444 188.625 188.62 188.625 104.179 0 188.625-84.455 188.625-188.625zM351.437 550.062c-147.818 0-268.074-120.259-268.074-268.080 0-147.826 120.257-268.091 268.074-268.091s268.077 120.265 268.077 268.091c0 147.821-120.259 268.080-268.077 268.080zM351.437-58.985c-188 0-340.95 152.958-340.95 340.967 0 188.003 152.95 340.956 340.95 340.956 188.003 0 340.953-152.953 340.953-340.956 0-188.009-152.95-340.967-340.953-340.967zM404.82 698.222c185.52 0 339.484-137.497 365.479-315.929l79.208-5.253c-24.125 224.046-214.339 399.077-444.686 399.077-10.909 0-21.723-0.412-32.433-1.186l5.16-77.823c9.017 0.661 18.093 1.113 27.272 1.113zM404.989 874.303c285.73 0 520.41-222.659 539.731-503.584l78.375-5.205c-16.843 326.355-287.644 586.685-618.106 586.685-14.884 0-29.644-0.561-44.264-1.6l5.157-77.719c12.919 0.928 25.958 1.424 39.106 1.424z" />
|
||||
<glyph unicode="" glyph-name="tailscale" d="M131.2 323.8c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8-127.6-57.4-127.6-127.8 57.2-127.8 127.6-127.8zM514.4 323.8c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8c-70.6 0-127.8-57.2-127.8-127.8s57.2-127.8 127.8-127.8zM514.4-64c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8c-70.6 0-127.8-57.2-127.8-127.8s57.2-127.8 127.8-127.8zM892.8 323.8c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8c-70.6 0-127.8-57.2-127.8-127.8s57.2-127.8 127.8-127.8z" />
|
||||
<glyph unicode="" glyph-name="torproxy" d="M750.016 439.488c-32.512 29.504-73.504 53.344-115.328 77.152-19.008 10.496-77.344 56.16-57.152 120.992l-36.32 15.328c57.152 88.672 131.68 176.32 223.008 258.336-73.344-24.672-138.176-62.848-186.848-130.496 28.672 60 75.328 119.168 126.848 179.168-70.496-50.496-131.488-107.68-169.664-184l26.656 106.848c-38.176-68.672-64.832-138.336-75.328-207.84l-56.16 22.848-9.504-7.68c49.504-88.672 23.84-135.328-0.992-151.68-49.504-33.344-120.992-76.16-157.344-113.344-68.672-70.656-88.672-137.344-82.016-226.016 6.656-113.504 89.664-207.84 199.328-244.992 48.672-16.32 93.344-18.176 143.008-18.176 80 0 162.016 20.992 222.176 71.488 63.84 52.992 100.832 131.488 100.992 214.496 0.32 82.656-34.336 161.664-95.328 217.504zM598.336 60.832c-3.84-17.152-16.16-38.176-31.328-57.152 5.664 10.496 10.496 20.992 13.344 32.512 23.84 84.832 34.336 123.84 22.848 217.344-1.824 9.504-5.664 40-20 73.344-20 50.656-50.496 98.336-54.336 108.832-6.656 16.16-16.16 84.832-17.152 131.488 0.992-40 3.84-113.344 14.336-142.016 2.848-9.664 30.496-52.512 50.496-104.832 13.344-36.32 16.16-69.664 19.008-79.168 9.664-43.008-1.824-115.488-16.992-184-4.832-24.832-18.176-53.504-35.328-75.328 9.504 13.344 17.152 30.496 22.848 50.496 11.488 40 16.16 91.488 15.168 124-0.832 19.008-9.504 60-23.84 97.152-8.512 20-20.992 40.992-29.504 55.328-9.504 14.336-9.504 45.664-13.344 82.016 0.832-39.168-2.848-59.168 6.656-86.848 5.664-16.16 26.656-39.008 32.32-60.992 8.672-29.504 17.152-62.016 16.32-82.016 0-22.848-0.992-64.832-11.488-110.656-6.656-34.176-22.016-63.84-46.656-82.848 10.496 13.344 16.16 26.656 19.008 40 3.84 20 4.832 39.168 6.656 63.008 2.016 24.512 0.32 49.344-4.672 73.344-7.68 34.336-20 68.672-25.824 92.512 0.992-26.656 11.488-60 16.32-95.328 3.68-25.824 1.824-51.488 0.832-74.336-0.832-26.656-9.504-73.504-20.992-96.32-11.488 4.832-15.168 11.488-22.848 20.992-9.664 12.32-15.328 25.664-20.992 40.992-5.344 12.672-9.504 25.664-12.512 39.008-4.512 33.504 4.16 67.168 23.84 94.496 20 28.672 24 30.496 30.496 63.84-9.504-29.504-16.16-32.32-37.152-57.152-23.84-27.68-27.488-67.68-27.488-100.16 0-13.344 5.664-28.672 10.496-43.008 5.664-15.168 11.328-30.336 19.008-41.824 5.664-9.504 13.344-16.16 20-20.992-24.832 6.656-50.496 16.16-66.656 29.504-40 34.496-75.328 92.512-80.16 144-3.84 42.016 34.336 103.008 88.672 133.504 45.824 26.656 56.32 56.32 65.824 104.992-13.344-42.016-26.656-78.336-70.656-100.16-62.848-34.336-95.328-89.664-92.32-143.008 4.672-67.68 31.328-114.496 85.824-151.68 12.32-8.672 29.504-17.152 47.68-23.84-67.84 16.16-76.32 25.664-99.168 52.32 0 2.016-5.824 5.824-5.824 6.656-30.496 34.336-68.512 93.504-82.016 147.84-4.672 19.008-9.504 39.008-3.68 58.176 24.672 89.664 79.008 124 133.344 160.992 13.504 9.664 26.848 18.176 39.168 27.68 30.496 24 38.176 85.824 44.832 121.152-12.32-43.008-25.824-96.32-49.664-113.504-12.32-9.504-27.68-17.152-40-25.664-56.16-38.176-112.512-74.496-138.176-166.848-5.824-24-2.016-41.152 3.68-64 14.336-56.16 52.512-117.152 84.992-153.504l5.664-5.664c14.336-16.32 32.512-28.672 54.336-37.152-19.168 4.512-37.664 11.168-55.328 20-88.672 42.848-147.68 135.328-151.488 210.656-7.68 153.504 65.824 198.336 134.336 254.656 38.176 31.328 91.68 46.656 122.176 102.848 5.664 12.512 9.504 39.168 1.824 67.84-2.848 9.504-17.152 43.84-22.848 51.488l84.832-37.344c-1.824-40-2.848-72.32 4.672-102.016 8.672-32.32 50.656-79.008 67.84-133.504 33.344-102.848 24.832-237.152 0.832-342.176z" />
|
||||
<glyph unicode="" glyph-name="wireshark" d="M729.968 781.328c-214.016-3.36-335.76-129.28-399.568-251.616-55.952-106.816-67.744-198.88-70.688-224.032l-259.712-2.576v-42.208l277.376 2.56c10.861 0.224 19.694 8.612 20.618 19.265l0.006 0.079s10.8 113.6 69.696 227.36c54.976 106.24 151.2 209.504 324.8 225.696-107.008-213.44 10.8-460.832 10.8-460.832 3.38-7.112 10.427-11.983 18.631-12.176h0.025l302.048-2.528v42.208l-287.168 2.56c-13.76 32.208-105.040 261.040 10.8 443.84 2.048 3.197 3.265 7.097 3.265 11.281 0 11.597-9.347 21.011-20.919 21.119h-0.010z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 9.4 KiB |
BIN
dist/raspap/css/fonts/RaspAP.ttf
vendored
BIN
dist/raspap/css/fonts/RaspAP.woff
vendored
49
dist/raspap/css/style.css
vendored
@@ -1,23 +1,19 @@
|
||||
/*!
|
||||
* RaspAP-Brands Brand Icons - https://raspap.com
|
||||
* License - https://github.com/billz/RaspAP-Brands-webgui/blob/master/LICENSE
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'RaspAP';
|
||||
src: url('fonts/RaspAP.eot?e76qs3');
|
||||
src: url('fonts/RaspAP.eot?e76qs3#iefix') format('embedded-opentype'),
|
||||
url('fonts/RaspAP.ttf?e76qs3') format('truetype'),
|
||||
url('fonts/RaspAP.woff?e76qs3') format('woff'),
|
||||
url('fonts/RaspAP.svg?e76qs3#RaspAP') format('svg');
|
||||
src: url('fonts/RaspAP.eot?8h3d6d');
|
||||
src: url('fonts/RaspAP.eot?8h3d6d#iefix') format('embedded-opentype'),
|
||||
url('fonts/RaspAP.ttf?8h3d6d') format('truetype'),
|
||||
url('fonts/RaspAP.woff?8h3d6d') format('woff'),
|
||||
url('fonts/RaspAP.svg?8h3d6d#RaspAP') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
[class^="ra-"], [class*=" ra-"] {
|
||||
/* use !important to prevent issues with browser extensions that change ..webfonts */
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'RaspAP' !important;
|
||||
speak: none;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
@@ -29,25 +25,28 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ra-wireshark:before {
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
content: "\e904";
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.ra-wireguard:before {
|
||||
font-size: 1.2rem;
|
||||
font-size: 1.1rem;
|
||||
content: "\e900";
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.card-header .ra-wireguard:before {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sidebar .nav-item.active .nav-link
|
||||
span.ra-wireguard:before {
|
||||
color: #6e707e;
|
||||
}
|
||||
|
||||
.ra-raspap:before {
|
||||
font-size: 4.35rem;
|
||||
content: "\e901";
|
||||
color: #2b8080;
|
||||
margin-left: 0.1em;
|
||||
}
|
||||
|
||||
.ra-tailscale:before {
|
||||
font-size: 1.1rem;
|
||||
content: "\e902";
|
||||
vertical-align: top;
|
||||
}
|
||||
.ra-torproxy:before {
|
||||
font-size: 1.3rem;
|
||||
content: "\e903";
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
6
dist/sb-admin/js/scripts.js
vendored
@@ -13,9 +13,9 @@ window.addEventListener('DOMContentLoaded', event => {
|
||||
const sidebarToggle = document.body.querySelector('#sidebarToggle');
|
||||
if (sidebarToggle) {
|
||||
// Uncomment below to persist sidebar toggle between refreshes
|
||||
if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
|
||||
document.body.classList.toggle('sb-sidenav-toggled');
|
||||
}
|
||||
// if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
|
||||
// document.body.classList.toggle('sb-sidenav-toggled');
|
||||
// }
|
||||
sidebarToggle.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
document.body.classList.toggle('sb-sidenav-toggled');
|
||||
|
||||
14
dist/speedtest/backend/empty.php
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
header('HTTP/1.1 200 OK');
|
||||
|
||||
if (isset($_GET['cors'])) {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST');
|
||||
header('Access-Control-Allow-Headers: Content-Encoding, Content-Type');
|
||||
}
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
header('Connection: keep-alive');
|
||||
66
dist/speedtest/backend/garbage.php
vendored
Executable file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
// Disable Compression
|
||||
@ini_set('zlib.output_compression', 'Off');
|
||||
@ini_set('output_buffering', 'Off');
|
||||
@ini_set('output_handler', '');
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
function getChunkCount()
|
||||
{
|
||||
if (!array_key_exists('ckSize', $_GET)
|
||||
|| !ctype_digit($_GET['ckSize'])
|
||||
|| (int) $_GET['ckSize'] <= 0
|
||||
) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if ((int) $_GET['ckSize'] > 1024) {
|
||||
return 1024;
|
||||
}
|
||||
|
||||
return (int) $_GET['ckSize'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
function sendHeaders()
|
||||
{
|
||||
header('HTTP/1.1 200 OK');
|
||||
|
||||
if (isset($_GET['cors'])) {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST');
|
||||
}
|
||||
|
||||
// Indicate a file download
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename=random.dat');
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
|
||||
// Cache settings: never cache this request
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
}
|
||||
|
||||
// Determine how much data we should send
|
||||
$chunks = getChunkCount();
|
||||
|
||||
// Generate data
|
||||
if (function_exists('random_bytes')) {
|
||||
$data = random_bytes(1048576);
|
||||
} else {
|
||||
$data = openssl_random_pseudo_bytes(1048576);
|
||||
}
|
||||
|
||||
// Deliver chunks of 1048576 bytes
|
||||
sendHeaders();
|
||||
for ($i = 0; $i < $chunks; $i++) {
|
||||
echo $data;
|
||||
flush();
|
||||
}
|
||||
325
dist/speedtest/backend/getIP.php
vendored
Executable file
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This script detects the client's IP address and fetches ISP info from ipinfo.io/
|
||||
* Output from this script is a JSON string composed of 2 objects: a string called processedString which contains the combined IP, ISP, Country and distance as it can be presented to the user; and an object called rawIspInfo which contains the raw data from ipinfo.io (will be empty if isp detection is disabled).
|
||||
* Client side, the output of this script can be treated as JSON or as regular text. If the output is regular text, it will be shown to the user as is.
|
||||
*/
|
||||
|
||||
error_reporting(0);
|
||||
|
||||
define('API_KEY_FILE', 'getIP_ipInfo_apikey.php');
|
||||
define('SERVER_LOCATION_CACHE_FILE', 'getIP_serverLocation.php');
|
||||
|
||||
require_once 'getIP_util.php';
|
||||
|
||||
/**
|
||||
* @param string $ip
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function getLocalOrPrivateIpInfo($ip)
|
||||
{
|
||||
// ::1/128 is the only localhost ipv6 address. there are no others, no need to strpos this
|
||||
if ('::1' === $ip) {
|
||||
return 'localhost IPv6 access';
|
||||
}
|
||||
|
||||
// simplified IPv6 link-local address (should match fe80::/10)
|
||||
if (stripos($ip, 'fe80:') === 0) {
|
||||
return 'link-local IPv6 access';
|
||||
}
|
||||
|
||||
// anything within the 127/8 range is localhost ipv4, the ip must start with 127.0
|
||||
if (strpos($ip, '127.') === 0) {
|
||||
return 'localhost IPv4 access';
|
||||
}
|
||||
|
||||
// 10/8 private IPv4
|
||||
if (strpos($ip, '10.') === 0) {
|
||||
return 'private IPv4 access';
|
||||
}
|
||||
|
||||
// 172.16/12 private IPv4
|
||||
if (preg_match('/^172\.(1[6-9]|2\d|3[01])\./', $ip) === 1) {
|
||||
return 'private IPv4 access';
|
||||
}
|
||||
|
||||
// 192.168/16 private IPv4
|
||||
if (strpos($ip, '192.168.') === 0) {
|
||||
return 'private IPv4 access';
|
||||
}
|
||||
|
||||
// IPv4 link-local
|
||||
if (strpos($ip, '169.254.') === 0) {
|
||||
return 'link-local IPv4 access';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function getIpInfoTokenString()
|
||||
{
|
||||
if (!file_exists(API_KEY_FILE)
|
||||
|| !is_readable(API_KEY_FILE)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
include API_KEY_FILE;
|
||||
|
||||
if (empty($IPINFO_APIKEY)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '?token='.$IPINFO_APIKEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ip
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
function getIspInfo($ip)
|
||||
{
|
||||
$json = file_get_contents('https://ipinfo.io/'.$ip.'/json'.getIpInfoTokenString());
|
||||
if (!is_string($json)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($json, true);
|
||||
if (!is_array($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $rawIspInfo
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getIsp($rawIspInfo)
|
||||
{
|
||||
if (!is_array($rawIspInfo)
|
||||
|| !array_key_exists('org', $rawIspInfo)
|
||||
|| !is_string($rawIspInfo['org'])
|
||||
|| empty($rawIspInfo['org'])
|
||||
) {
|
||||
return 'Unknown ISP';
|
||||
}
|
||||
|
||||
// Remove AS##### from ISP name, if present
|
||||
return preg_replace('/AS\\d+\\s/', '', $rawIspInfo['org']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
function getServerLocation()
|
||||
{
|
||||
$serverLoc = null;
|
||||
if (file_exists(SERVER_LOCATION_CACHE_FILE)
|
||||
&& is_readable(SERVER_LOCATION_CACHE_FILE)
|
||||
) {
|
||||
include SERVER_LOCATION_CACHE_FILE;
|
||||
}
|
||||
if (is_string($serverLoc) && !empty($serverLoc)) {
|
||||
return $serverLoc;
|
||||
}
|
||||
|
||||
$json = file_get_contents('https://ipinfo.io/json'.getIpInfoTokenString());
|
||||
if (!is_string($json)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$details = json_decode($json, true);
|
||||
if (!is_array($details)
|
||||
|| !array_key_exists('loc', $details)
|
||||
|| !is_string($details['loc'])
|
||||
|| empty($details['loc'])
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$serverLoc = $details['loc'];
|
||||
$cacheData = "<?php\n\n\$serverLoc = '".addslashes($serverLoc)."';\n";
|
||||
file_put_contents(SERVER_LOCATION_CACHE_FILE, $cacheData);
|
||||
|
||||
return $serverLoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized algorithm from http://www.codexworld.com
|
||||
*
|
||||
* @param float $latitudeFrom
|
||||
* @param float $longitudeFrom
|
||||
* @param float $latitudeTo
|
||||
* @param float $longitudeTo
|
||||
*
|
||||
* @return float [km]
|
||||
*/
|
||||
function distance(
|
||||
$latitudeFrom,
|
||||
$longitudeFrom,
|
||||
$latitudeTo,
|
||||
$longitudeTo
|
||||
) {
|
||||
$rad = M_PI / 180;
|
||||
$theta = $longitudeFrom - $longitudeTo;
|
||||
$dist = sin($latitudeFrom * $rad)
|
||||
* sin($latitudeTo * $rad)
|
||||
+ cos($latitudeFrom * $rad)
|
||||
* cos($latitudeTo * $rad)
|
||||
* cos($theta * $rad);
|
||||
|
||||
return acos($dist) / $rad * 60 * 1.853;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $rawIspInfo
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function getDistance($rawIspInfo)
|
||||
{
|
||||
if (!is_array($rawIspInfo)
|
||||
|| !array_key_exists('loc', $rawIspInfo)
|
||||
|| !isset($_GET['distance'])
|
||||
|| !in_array($_GET['distance'], ['mi', 'km'], true)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$unit = $_GET['distance'];
|
||||
$clientLocation = $rawIspInfo['loc'];
|
||||
$serverLocation = getServerLocation();
|
||||
|
||||
if (!is_string($serverLocation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return calculateDistance(
|
||||
$serverLocation,
|
||||
$clientLocation,
|
||||
$unit
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientLocation
|
||||
* @param string $serverLocation
|
||||
* @param string $unit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function calculateDistance($clientLocation, $serverLocation, $unit)
|
||||
{
|
||||
list($clientLatitude, $clientLongitude) = explode(',', $clientLocation);
|
||||
list($serverLatitude, $serverLongitude) = explode(',', $serverLocation);
|
||||
$dist = distance(
|
||||
$clientLatitude,
|
||||
$clientLongitude,
|
||||
$serverLatitude,
|
||||
$serverLongitude
|
||||
);
|
||||
|
||||
if ('mi' === $unit) {
|
||||
$dist /= 1.609344;
|
||||
$dist = round($dist, -1);
|
||||
if ($dist < 15) {
|
||||
$dist = '<15';
|
||||
}
|
||||
|
||||
return $dist.' mi';
|
||||
}
|
||||
|
||||
if ('km' === $unit) {
|
||||
$dist = round($dist, -1);
|
||||
if ($dist < 20) {
|
||||
$dist = '<20';
|
||||
}
|
||||
|
||||
return $dist.' km';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
function sendHeaders()
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (isset($_GET['cors'])) {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST');
|
||||
}
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ip
|
||||
* @param string|null $ipInfo
|
||||
* @param string|null $distance
|
||||
* @param array|null $rawIspInfo
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function sendResponse(
|
||||
$ip,
|
||||
$ipInfo = null,
|
||||
$distance = null,
|
||||
$rawIspInfo = null
|
||||
) {
|
||||
$processedString = $ip;
|
||||
if (is_string($ipInfo)) {
|
||||
$processedString .= ' - '.$ipInfo;
|
||||
}
|
||||
|
||||
if (is_array($rawIspInfo)
|
||||
&& array_key_exists('country', $rawIspInfo)
|
||||
) {
|
||||
$processedString .= ', '.$rawIspInfo['country'];
|
||||
}
|
||||
if (is_string($distance)) {
|
||||
$processedString .= ' ('.$distance.')';
|
||||
}
|
||||
|
||||
sendHeaders();
|
||||
echo json_encode(
|
||||
[
|
||||
'processedString' => $processedString,
|
||||
'rawIspInfo' => $rawIspInfo ?: '',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$ip = getClientIp();
|
||||
|
||||
$localIpInfo = getLocalOrPrivateIpInfo($ip);
|
||||
// local ip, no need to fetch further information
|
||||
if (is_string($localIpInfo)) {
|
||||
sendResponse($ip, $localIpInfo);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_GET['isp'])) {
|
||||
sendResponse($ip);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rawIspInfo = getIspInfo($ip);
|
||||
$isp = getIsp($rawIspInfo);
|
||||
$distance = getDistance($rawIspInfo);
|
||||
|
||||
sendResponse($ip, $isp, $distance, $rawIspInfo);
|
||||
4
dist/speedtest/backend/getIP_ipInfo_apikey.php
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
// put your token between the quotes if you have one
|
||||
$IPINFO_APIKEY = '';
|
||||
3
dist/speedtest/backend/getIP_serverLocation.php
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
$serverLoc = '45.9237,6.8693';
|
||||
21
dist/speedtest/backend/getIP_util.php
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function getClientIp()
|
||||
{
|
||||
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
|
||||
$ip = $_SERVER['HTTP_X_REAL_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
$ip = preg_replace('/,.*/', '', $ip); // hosts are comma-separated, client is first
|
||||
} else {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
|
||||
return preg_replace('/^::ffff:/', '', $ip);
|
||||
}
|
||||
|
||||
231
dist/speedtest/speedtest.css
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
#testWrapper {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#loading {
|
||||
background-color: #FFFFFF;
|
||||
color: #404040;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.loadCircle {
|
||||
display: inline-block;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
vertical-align: middle;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAP1BMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZyFzwnAAAAFHRSTlMAEvRFvX406baecwbf0casimhSHyiwmqgAAADpSURBVHja7dbJbQMxAENRahnN5lkc//5rDRAkDeRgHszXgACJoKiIiIiIiIiIiIiIiIiIiIj4HHspsrpAVhdVVguzrA4OWc10WcEqpwKbnBo0OU1Q5NSpsoJFTgOecrrdEag85DRgktNqfoEdTjnd7hrEHMEJvmRUYJbTYk5Agy6nau6Abp5Cm7mDBtRdPi9gyKdU7w4p1fsLvyqs8hl4z9/w3n/Hmr9WoQ65lAU4d7lMYOz//QboRR5jBZibLMZdAR6O/Vfa1PlxNr3XdS3HzK/HVPRu/KnLs8iAOh993VpRRERERMT/fAN60wwWaVyWwAAAAABJRU5ErkJggg==');
|
||||
background-size: 2em 2em;
|
||||
margin-right: 0.5em;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
#startStopBtn {
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
transition: all 0.3s;
|
||||
box-sizing: border-box;
|
||||
width: 8em;
|
||||
line-height: 2.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#startStopBtn.running {
|
||||
background-color: #FF3030;
|
||||
border-color: #FF3030;
|
||||
color: #FFFFFF;
|
||||
line-height: 2.5em;
|
||||
}
|
||||
|
||||
#startStopBtn:before {
|
||||
content: "Start";
|
||||
}
|
||||
|
||||
#startStopBtn.running:before {
|
||||
content: "Abort";
|
||||
}
|
||||
|
||||
#serverArea {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#server {
|
||||
font-size: 1em;
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
#test {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
div.testArea {
|
||||
display: inline-block;
|
||||
width: 16em;
|
||||
height: 12.5em;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.testArea2 {
|
||||
display: inline-block;
|
||||
width: 14em;
|
||||
height: 7em;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.testArea div.testName {
|
||||
position: absolute;
|
||||
top: -0.3em;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
font-size: 1.4em;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
div.testArea2 div.testName {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
div.testArea div.meterText {
|
||||
position: absolute;
|
||||
bottom: 1.55em;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
font-size: 2.5em;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
div.testArea2 div.meterText {
|
||||
display: inline-block;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
div.meterText:empty:before {
|
||||
content: "0.00";
|
||||
}
|
||||
|
||||
div.testArea div.unit {
|
||||
position: absolute;
|
||||
bottom: 2em;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
div.testArea2 div.unit {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div.testArea canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div.testGroup {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#shareArea {
|
||||
width: 95%;
|
||||
max-width: 40em;
|
||||
margin: 0 auto;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#shareArea>* {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
#privacyPolicy {
|
||||
position: fixed;
|
||||
top: 2em;
|
||||
bottom: 2em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
overflow-y: auto;
|
||||
margin: 0 auto;
|
||||
width: 50%;
|
||||
height: auto;
|
||||
box-shadow: 0 0 3em 1em #333;
|
||||
z-index: 999999;
|
||||
text-align: left;
|
||||
background-color: #FFFFFF;
|
||||
padding: 1em;
|
||||
border-radius: 0.3em;
|
||||
color: #858796;
|
||||
}
|
||||
|
||||
#privacyPolicy h4, h5 {
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
a.privacy {
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
color: #808080;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media all and (max-width:40em) {
|
||||
body {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
div.visible {
|
||||
animation: fadeIn 0.4s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.hidden {
|
||||
animation: fadeOut 0.4s;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.centered {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
327
dist/speedtest/speedtest.js
vendored
Executable file
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
LibreSpeed - Main
|
||||
by Federico Dossena
|
||||
https://github.com/librespeed/speedtest/
|
||||
GNU LGPLv3 License
|
||||
*/
|
||||
|
||||
function Speedtest() {
|
||||
this._serverList = []; //when using multiple points of test, this is a list of test points
|
||||
this._selectedServer = null; //when using multiple points of test, this is the selected server
|
||||
this._settings = {}; //settings for the speedtest worker
|
||||
this._state = 0; //0=adding settings, 1=adding servers, 2=server selection done, 3=test running, 4=done
|
||||
}
|
||||
|
||||
Speedtest.prototype = {
|
||||
constructor: Speedtest,
|
||||
/**
|
||||
* Returns the state of the test: 0=adding settings, 1=adding servers, 2=server selection done, 3=test running, 4=done
|
||||
*/
|
||||
getState: function() {
|
||||
return this._state;
|
||||
},
|
||||
/**
|
||||
* Change one of the test settings from their defaults.
|
||||
* - parameter: string with the name of the parameter that you want to set
|
||||
* - value: new value for the parameter
|
||||
*
|
||||
* Invalid values or nonexistant parameters will be ignored by the speedtest worker.
|
||||
*/
|
||||
setParameter: function(parameter, value) {
|
||||
if (this._state == 3)
|
||||
throw "You cannot change the test settings while running the test";
|
||||
this._settings[parameter] = value;
|
||||
if(parameter === "telemetry_extra"){
|
||||
this._originalExtra=this._settings.telemetry_extra;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Used internally to check if a server object contains all the required elements.
|
||||
* Also fixes the server URL if needed.
|
||||
*/
|
||||
_checkServerDefinition: function(server) {
|
||||
try {
|
||||
if (typeof server.name !== "string")
|
||||
throw "Name string missing from server definition (name)";
|
||||
if (typeof server.server !== "string")
|
||||
throw "Server address string missing from server definition (server)";
|
||||
if (server.server.charAt(server.server.length - 1) != "/")
|
||||
server.server += "/";
|
||||
if (server.server.indexOf("//") == 0)
|
||||
server.server = location.protocol + server.server;
|
||||
if (typeof server.dlURL !== "string")
|
||||
throw "Download URL string missing from server definition (dlURL)";
|
||||
if (typeof server.ulURL !== "string")
|
||||
throw "Upload URL string missing from server definition (ulURL)";
|
||||
if (typeof server.pingURL !== "string")
|
||||
throw "Ping URL string missing from server definition (pingURL)";
|
||||
if (typeof server.getIpURL !== "string")
|
||||
throw "GetIP URL string missing from server definition (getIpURL)";
|
||||
} catch (e) {
|
||||
throw "Invalid server definition";
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a test point (multiple points of test)
|
||||
* server: the server to be added as an object. Defined in app/js/speedtestUI.js
|
||||
*/
|
||||
addTestPoint: function(server) {
|
||||
this._checkServerDefinition(server);
|
||||
if (this._state == 0) this._state = 1;
|
||||
if (this._state != 1) throw "You can't add a server after server selection";
|
||||
this._settings.mpot = true;
|
||||
this._serverList.push(server);
|
||||
},
|
||||
/**
|
||||
* Same as addTestPoint, but you can pass an array of servers
|
||||
*/
|
||||
addTestPoints: function(list) {
|
||||
for (var i = 0; i < list.length; i++) this.addTestPoint(list[i]);
|
||||
},
|
||||
/**
|
||||
* Load a JSON server list from URL (multiple points of test)
|
||||
* url: the url where the server list can be fetched. Must be an array with objects containing the following elements:
|
||||
* result: callback to be called when the list is loaded correctly. An array with the loaded servers will be passed to this function, or null if it failed
|
||||
*/
|
||||
loadServerList: function(url,result) {
|
||||
if (this._state == 0) this._state = 1;
|
||||
if (this._state != 1) throw "You can't add a server after server selection";
|
||||
this._settings.mpot = true;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = function(){
|
||||
try{
|
||||
var servers=JSON.parse(xhr.responseText);
|
||||
for(var i=0;i<servers.length;i++){
|
||||
this._checkServerDefinition(servers[i]);
|
||||
}
|
||||
this.addTestPoints(servers);
|
||||
result(servers);
|
||||
}catch(e){
|
||||
result(null);
|
||||
}
|
||||
}.bind(this);
|
||||
xhr.onerror = function(){result(null);}
|
||||
xhr.open("GET",url);
|
||||
xhr.send();
|
||||
},
|
||||
/**
|
||||
* Returns the selected server (multiple points of test)
|
||||
*/
|
||||
getSelectedServer: function() {
|
||||
if (this._state < 2 || this._selectedServer == null)
|
||||
throw "No server is selected";
|
||||
return this._selectedServer;
|
||||
},
|
||||
/**
|
||||
* Manually selects one of the test points (multiple points of test)
|
||||
*/
|
||||
setSelectedServer: function(server) {
|
||||
this._checkServerDefinition(server);
|
||||
if (this._state == 3)
|
||||
throw "You can't select a server while the test is running";
|
||||
this._selectedServer = server;
|
||||
this._state = 2;
|
||||
},
|
||||
/**
|
||||
* Automatically selects a server from the list of added test points. The server with the lowest ping will be chosen. (multiple points of test)
|
||||
* The process is asynchronous and the passed result callback function will be called when it's done, then the test can be started.
|
||||
*/
|
||||
selectServer: function(result) {
|
||||
if (this._state != 1) {
|
||||
if (this._state == 0) throw "No test points added";
|
||||
if (this._state == 2) throw "Server already selected";
|
||||
if (this._state >= 3)
|
||||
throw "You can't select a server while the test is running";
|
||||
}
|
||||
if (this._selectServerCalled) throw "selectServer already called"; else this._selectServerCalled=true;
|
||||
/*this function goes through a list of servers. For each server, the ping is measured, then the server with the function selected is called with the best server, or null if all the servers were down.
|
||||
*/
|
||||
var select = function(serverList, selected) {
|
||||
//pings the specified URL, then calls the function result. Result will receive a parameter which is either the time it took to ping the URL, or -1 if something went wrong.
|
||||
var PING_TIMEOUT = 2000;
|
||||
var USE_PING_TIMEOUT = true; //will be disabled on unsupported browsers
|
||||
if (/MSIE.(\d+\.\d+)/i.test(navigator.userAgent)) {
|
||||
//IE11 doesn't support XHR timeout
|
||||
USE_PING_TIMEOUT = false;
|
||||
}
|
||||
var ping = function(url, rtt) {
|
||||
url += (url.match(/\?/) ? "&" : "?") + "cors=true";
|
||||
var xhr = new XMLHttpRequest();
|
||||
var t = new Date().getTime();
|
||||
xhr.onload = function() {
|
||||
if (xhr.responseText.length == 0) {
|
||||
//we expect an empty response
|
||||
var instspd = new Date().getTime() - t; //rough timing estimate
|
||||
try {
|
||||
//try to get more accurate timing using performance API
|
||||
var p = performance.getEntriesByName(url);
|
||||
p = p[p.length - 1];
|
||||
var d = p.responseStart - p.requestStart;
|
||||
if (d <= 0) d = p.duration;
|
||||
if (d > 0 && d < instspd) instspd = d;
|
||||
} catch (e) {}
|
||||
rtt(instspd);
|
||||
} else rtt(-1);
|
||||
}.bind(this);
|
||||
xhr.onerror = function() {
|
||||
rtt(-1);
|
||||
}.bind(this);
|
||||
xhr.open("GET", url);
|
||||
if (USE_PING_TIMEOUT) {
|
||||
try {
|
||||
xhr.timeout = PING_TIMEOUT;
|
||||
xhr.ontimeout = xhr.onerror;
|
||||
} catch (e) {}
|
||||
}
|
||||
xhr.send();
|
||||
}.bind(this);
|
||||
|
||||
/**
|
||||
* This function repeatedly pings a server to get a good estimate of the ping. When it's done, it calls the done function without parameters.
|
||||
* At the end of the execution, the server will have a new parameter called pingT, which is either the best ping we got from the server or -1 if something went wrong.
|
||||
*/
|
||||
var PINGS = 3, //up to 3 pings are performed, unless the server is down...
|
||||
SLOW_THRESHOLD = 500; //...or one of the pings is above this threshold
|
||||
var checkServer = function(server, done) {
|
||||
var i = 0;
|
||||
server.pingT = -1;
|
||||
if (server.server.indexOf(location.protocol) == -1) done();
|
||||
else {
|
||||
var nextPing = function() {
|
||||
if (i++ == PINGS) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
ping(
|
||||
server.server + server.pingURL,
|
||||
function(t) {
|
||||
if (t >= 0) {
|
||||
if (t < server.pingT || server.pingT == -1) server.pingT = t;
|
||||
if (t < SLOW_THRESHOLD) nextPing();
|
||||
else done();
|
||||
} else done();
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this);
|
||||
nextPing();
|
||||
}
|
||||
}.bind(this);
|
||||
//check servers in list, one by one
|
||||
var i = 0;
|
||||
var done = function() {
|
||||
var bestServer = null;
|
||||
for (var i = 0; i < serverList.length; i++) {
|
||||
if (
|
||||
serverList[i].pingT != -1 &&
|
||||
(bestServer == null || serverList[i].pingT < bestServer.pingT)
|
||||
)
|
||||
bestServer = serverList[i];
|
||||
}
|
||||
selected(bestServer);
|
||||
}.bind(this);
|
||||
var nextServer = function() {
|
||||
if (i == serverList.length) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
checkServer(serverList[i++], nextServer);
|
||||
}.bind(this);
|
||||
nextServer();
|
||||
}.bind(this);
|
||||
|
||||
//parallel server selection
|
||||
var CONCURRENCY = 6;
|
||||
var serverLists = [];
|
||||
for (var i = 0; i < CONCURRENCY; i++) {
|
||||
serverLists[i] = [];
|
||||
}
|
||||
for (var i = 0; i < this._serverList.length; i++) {
|
||||
serverLists[i % CONCURRENCY].push(this._serverList[i]);
|
||||
}
|
||||
var completed = 0;
|
||||
var bestServer = null;
|
||||
for (var i = 0; i < CONCURRENCY; i++) {
|
||||
select(
|
||||
serverLists[i],
|
||||
function(server) {
|
||||
if (server != null) {
|
||||
if (bestServer == null || server.pingT < bestServer.pingT)
|
||||
bestServer = server;
|
||||
}
|
||||
completed++;
|
||||
if (completed == CONCURRENCY) {
|
||||
this._selectedServer = bestServer;
|
||||
this._state = 2;
|
||||
if (result) result(bestServer);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Starts the test.
|
||||
* During the test, the onupdate(data) callback function will be called periodically with data from the worker.
|
||||
* At the end of the test, the onend(aborted) function will be called with a boolean telling you if the test was aborted or if it ended normally.
|
||||
*/
|
||||
start: function() {
|
||||
if (this._state == 3) throw "Test already running";
|
||||
this.worker = new Worker("dist/speedtest/speedtest_worker.js?r=" + Math.random());
|
||||
this.worker.onmessage = function(e) {
|
||||
if (e.data === this._prevData) return;
|
||||
else this._prevData = e.data;
|
||||
var data = JSON.parse(e.data);
|
||||
try {
|
||||
if (this.onupdate) this.onupdate(data);
|
||||
} catch (e) {
|
||||
console.error("Speedtest onupdate event threw exception: " + e);
|
||||
}
|
||||
if (data.testState >= 4) {
|
||||
clearInterval(this.updater);
|
||||
this._state = 4;
|
||||
try {
|
||||
if (this.onend) this.onend(data.testState == 5);
|
||||
} catch (e) {
|
||||
console.error("Speedtest onend event threw exception: " + e);
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
this.updater = setInterval(
|
||||
function() {
|
||||
this.worker.postMessage("status");
|
||||
}.bind(this),
|
||||
200
|
||||
);
|
||||
|
||||
if (this._state == 1)
|
||||
throw "When using multiple points of test, you must call selectServer before starting the test";
|
||||
if (this._state == 2) {
|
||||
this._settings.url_dl =
|
||||
this._selectedServer.server + this._selectedServer.dlURL;
|
||||
this._settings.url_ul =
|
||||
this._selectedServer.server + this._selectedServer.ulURL;
|
||||
this._settings.url_ping =
|
||||
this._selectedServer.server + this._selectedServer.pingURL;
|
||||
this._settings.url_getIp =
|
||||
this._selectedServer.server + this._selectedServer.getIpURL;
|
||||
if (typeof this._originalExtra !== "undefined") {
|
||||
this._settings.telemetry_extra = JSON.stringify({
|
||||
server: this._selectedServer.name,
|
||||
extra: this._originalExtra
|
||||
});
|
||||
} else
|
||||
this._settings.telemetry_extra = JSON.stringify({
|
||||
server: this._selectedServer.name
|
||||
});
|
||||
}
|
||||
this._state = 3;
|
||||
this.worker.postMessage("start " + JSON.stringify(this._settings));
|
||||
},
|
||||
/**
|
||||
* Aborts the test while it's running.
|
||||
*/
|
||||
abort: function() {
|
||||
if (this._state < 3) throw "You cannot abort a test that's not started yet";
|
||||
if (this._state < 4) this.worker.postMessage("abort");
|
||||
}
|
||||
};
|
||||
|
||||
725
dist/speedtest/speedtest_worker.js
vendored
Executable file
@@ -0,0 +1,725 @@
|
||||
/*
|
||||
LibreSpeed - Worker
|
||||
by Federico Dossena
|
||||
https://github.com/librespeed/speedtest/
|
||||
GNU LGPLv3 License
|
||||
*/
|
||||
|
||||
// data reported to main thread
|
||||
var testState = -1; // -1=not started, 0=starting, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort
|
||||
var dlStatus = ""; // download speed in megabit/s with 2 decimal digits
|
||||
var ulStatus = ""; // upload speed in megabit/s with 2 decimal digits
|
||||
var pingStatus = ""; // ping in milliseconds with 2 decimal digits
|
||||
var jitterStatus = ""; // jitter in milliseconds with 2 decimal digits
|
||||
var clientIp = ""; // client's IP address as reported by getIP.php
|
||||
var dlProgress = 0; //progress of download test 0-1
|
||||
var ulProgress = 0; //progress of upload test 0-1
|
||||
var pingProgress = 0; //progress of ping+jitter test 0-1
|
||||
var testId = null; //test ID (sent back by telemetry if used, null otherwise)
|
||||
|
||||
var log = ""; //telemetry log
|
||||
function tlog(s) {
|
||||
if (settings.telemetry_level >= 2) {
|
||||
log += Date.now() + ": " + s + "\n";
|
||||
}
|
||||
}
|
||||
function tverb(s) {
|
||||
if (settings.telemetry_level >= 3) {
|
||||
log += Date.now() + ": " + s + "\n";
|
||||
}
|
||||
}
|
||||
function twarn(s) {
|
||||
if (settings.telemetry_level >= 2) {
|
||||
log += Date.now() + " WARN: " + s + "\n";
|
||||
}
|
||||
console.warn(s);
|
||||
}
|
||||
|
||||
// test settings. can be overridden by sending specific values with the start command
|
||||
var settings = {
|
||||
mpot: true, //set to true when in MPOT mode
|
||||
test_order: "IP_D_U", //order in which tests will be performed as a string. D=Download, U=Upload, P=Ping+Jitter, I=IP, _=1 second delay
|
||||
time_ul_max: 15, // max duration of upload test in seconds
|
||||
time_dl_max: 15, // max duration of download test in seconds
|
||||
time_auto: true, // if set to true, tests will take less time on faster connections
|
||||
time_ulGraceTime: 3, //time to wait in seconds before actually measuring ul speed (wait for buffers to fill)
|
||||
time_dlGraceTime: 1.5, //time to wait in seconds before actually measuring dl speed (wait for TCP window to increase)
|
||||
count_ping: 10, // number of pings to perform in ping test
|
||||
url_dl: "backend/garbage.php", // path to a large file or garbage.php, used for download test. must be relative to this js file
|
||||
url_ul: "backend/empty.php", // path to an empty file, used for upload test. must be relative to this js file
|
||||
url_ping: "backend/empty.php", // path to an empty file, used for ping test. must be relative to this js file
|
||||
url_getIp: "backend/getIP.php", // path to getIP.php relative to this js file, or a similar thing that outputs the client's ip
|
||||
getIp_ispInfo: true, //if set to true, the server will include ISP info with the IP address
|
||||
getIp_ispInfo_distance: "km", //km or mi=estimate distance from server in km/mi; set to false to disable distance estimation. getIp_ispInfo must be enabled in order for this to work
|
||||
xhr_dlMultistream: 6, // number of download streams to use (can be different if enable_quirks is active)
|
||||
xhr_ulMultistream: 3, // number of upload streams to use (can be different if enable_quirks is active)
|
||||
xhr_multistreamDelay: 300, //how much concurrent requests should be delayed
|
||||
xhr_ignoreErrors: 1, // 0=fail on errors, 1=attempt to restart a stream if it fails, 2=ignore all errors
|
||||
xhr_dlUseBlob: false, // if set to true, it reduces ram usage but uses the hard drive (useful with large garbagePhp_chunkSize and/or high xhr_dlMultistream)
|
||||
xhr_ul_blob_megabytes: 20, //size in megabytes of the upload blobs sent in the upload test (forced to 4 on chrome mobile)
|
||||
garbagePhp_chunkSize: 100, // size of chunks sent by garbage.php (can be different if enable_quirks is active)
|
||||
enable_quirks: true, // enable quirks for specific browsers. currently it overrides settings to optimize for specific browsers, unless they are already being overridden with the start command
|
||||
ping_allowPerformanceApi: true, // if enabled, the ping test will attempt to calculate the ping more precisely using the Performance API. Currently works perfectly in Chrome, badly in Edge, and not at all in Firefox. If Performance API is not supported or the result is obviously wrong, a fallback is provided.
|
||||
overheadCompensationFactor: 1.06, //can be changed to compensatie for transport overhead. (see doc.md for some other values)
|
||||
useMebibits: false, //if set to true, speed will be reported in mebibits/s instead of megabits/s
|
||||
telemetry_level: 1, // 0=disabled, 1=basic (results only), 2=full (results and timing) 3=debug (results+log)
|
||||
url_telemetry: "https://speedtest.raspap.com/results/telemetry.php", // path to the script that adds telemetry data to the database
|
||||
telemetry_extra: "", //extra data that can be passed to the telemetry through the settings
|
||||
forceIE11Workaround: false //when set to true, it will foce the IE11 upload test on all browsers. Debug only
|
||||
};
|
||||
|
||||
var xhr = null; // array of currently active xhr requests
|
||||
var interval = null; // timer used in tests
|
||||
var test_pointer = 0; //pointer to the next test to run inside settings.test_order
|
||||
|
||||
/*
|
||||
this function is used on URLs passed in the settings to determine whether we need a ? or an & as a separator
|
||||
*/
|
||||
function url_sep(url) {
|
||||
return url.match(/\?/) ? "&" : "?";
|
||||
}
|
||||
|
||||
/*
|
||||
listener for commands from main thread to this worker.
|
||||
commands:
|
||||
-status: returns the current status as a JSON string containing testState, dlStatus, ulStatus, pingStatus, clientIp, jitterStatus, dlProgress, ulProgress, pingProgress
|
||||
-abort: aborts the current test
|
||||
-start: starts the test. optionally, settings can be passed as JSON.
|
||||
example: start {"time_ul_max":"10", "time_dl_max":"10", "count_ping":"50"}
|
||||
*/
|
||||
this.addEventListener("message", function(e) {
|
||||
var params = e.data.split(" ");
|
||||
if (params[0] === "status") {
|
||||
// return status
|
||||
postMessage(
|
||||
JSON.stringify({
|
||||
testState: testState,
|
||||
dlStatus: dlStatus,
|
||||
ulStatus: ulStatus,
|
||||
pingStatus: pingStatus,
|
||||
clientIp: clientIp,
|
||||
jitterStatus: jitterStatus,
|
||||
dlProgress: dlProgress,
|
||||
ulProgress: ulProgress,
|
||||
pingProgress: pingProgress,
|
||||
testId: testId
|
||||
})
|
||||
);
|
||||
}
|
||||
if (params[0] === "start" && testState === -1) {
|
||||
// start new test
|
||||
testState = 0;
|
||||
try {
|
||||
// parse settings, if present
|
||||
var s = {};
|
||||
try {
|
||||
var ss = e.data.substring(5);
|
||||
if (ss) s = JSON.parse(ss);
|
||||
} catch (e) {
|
||||
twarn("Error parsing custom settings JSON. Please check your syntax");
|
||||
}
|
||||
//copy custom settings
|
||||
for (var key in s) {
|
||||
if (typeof settings[key] !== "undefined") settings[key] = s[key];
|
||||
else twarn("Unknown setting ignored: " + key);
|
||||
}
|
||||
var ua = navigator.userAgent;
|
||||
// quirks for specific browsers. apply only if not overridden. more may be added in future releases
|
||||
if (settings.enable_quirks || (typeof s.enable_quirks !== "undefined" && s.enable_quirks)) {
|
||||
if (/Firefox.(\d+\.\d+)/i.test(ua)) {
|
||||
if (typeof s.ping_allowPerformanceApi === "undefined") {
|
||||
// ff performance API sucks
|
||||
settings.ping_allowPerformanceApi = false;
|
||||
}
|
||||
}
|
||||
if (/Edge.(\d+\.\d+)/i.test(ua)) {
|
||||
if (typeof s.xhr_dlMultistream === "undefined") {
|
||||
// edge more precise with 3 download streams
|
||||
settings.xhr_dlMultistream = 3;
|
||||
}
|
||||
}
|
||||
if (/Chrome.(\d+)/i.test(ua) && !!self.fetch) {
|
||||
if (typeof s.xhr_dlMultistream === "undefined") {
|
||||
// chrome more precise with 5 streams
|
||||
settings.xhr_dlMultistream = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (/Edge.(\d+\.\d+)/i.test(ua)) {
|
||||
//Edge 15 introduced a bug that causes onprogress events to not get fired, we have to use the "small chunks" workaround that reduces accuracy
|
||||
settings.forceIE11Workaround = true;
|
||||
}
|
||||
if (/PlayStation 4.(\d+\.\d+)/i.test(ua)) {
|
||||
//PS4 browser has the same bug as IE11/Edge
|
||||
settings.forceIE11Workaround = true;
|
||||
}
|
||||
if (/Chrome.(\d+)/i.test(ua) && /Android|iPhone|iPad|iPod|Windows Phone/i.test(ua)) {
|
||||
//cheap af
|
||||
//Chrome mobile introduced a limitation somewhere around version 65, we have to limit XHR upload size to 4 megabytes
|
||||
settings.xhr_ul_blob_megabytes = 4;
|
||||
}
|
||||
if (/^((?!chrome|android|crios|fxios).)*safari/i.test(ua)) {
|
||||
//Safari also needs the IE11 workaround but only for the MPOT version
|
||||
settings.forceIE11Workaround = true;
|
||||
}
|
||||
//telemetry_level has to be parsed and not just copied
|
||||
if (typeof s.telemetry_level !== "undefined") settings.telemetry_level = s.telemetry_level === "basic" ? 1 : s.telemetry_level === "full" ? 2 : s.telemetry_level === "debug" ? 3 : 0; // telemetry level
|
||||
//transform test_order to uppercase, just in case
|
||||
settings.test_order = settings.test_order.toUpperCase();
|
||||
} catch (e) {
|
||||
twarn("Possible error in custom test settings. Some settings might not have been applied. Exception: " + e);
|
||||
}
|
||||
// run the tests
|
||||
tverb(JSON.stringify(settings));
|
||||
test_pointer = 0;
|
||||
var iRun = false,
|
||||
dRun = false,
|
||||
uRun = false,
|
||||
pRun = false;
|
||||
var runNextTest = function() {
|
||||
if (testState == 5) return;
|
||||
if (test_pointer >= settings.test_order.length) {
|
||||
//test is finished
|
||||
if (settings.telemetry_level > 0)
|
||||
sendTelemetry(function(id) {
|
||||
testState = 4;
|
||||
if (id != null) testId = id;
|
||||
});
|
||||
else testState = 4;
|
||||
return;
|
||||
}
|
||||
switch (settings.test_order.charAt(test_pointer)) {
|
||||
case "I":
|
||||
{
|
||||
test_pointer++;
|
||||
if (iRun) {
|
||||
runNextTest();
|
||||
return;
|
||||
} else iRun = true;
|
||||
getIp(runNextTest);
|
||||
}
|
||||
break;
|
||||
case "D":
|
||||
{
|
||||
test_pointer++;
|
||||
if (dRun) {
|
||||
runNextTest();
|
||||
return;
|
||||
} else dRun = true;
|
||||
testState = 1;
|
||||
dlTest(runNextTest);
|
||||
}
|
||||
break;
|
||||
case "U":
|
||||
{
|
||||
test_pointer++;
|
||||
if (uRun) {
|
||||
runNextTest();
|
||||
return;
|
||||
} else uRun = true;
|
||||
testState = 3;
|
||||
ulTest(runNextTest);
|
||||
}
|
||||
break;
|
||||
case "P":
|
||||
{
|
||||
test_pointer++;
|
||||
if (pRun) {
|
||||
runNextTest();
|
||||
return;
|
||||
} else pRun = true;
|
||||
testState = 2;
|
||||
pingTest(runNextTest);
|
||||
}
|
||||
break;
|
||||
case "_":
|
||||
{
|
||||
test_pointer++;
|
||||
setTimeout(runNextTest, 1000);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
test_pointer++;
|
||||
}
|
||||
};
|
||||
runNextTest();
|
||||
}
|
||||
if (params[0] === "abort") {
|
||||
// abort command
|
||||
if (testState >= 4) return;
|
||||
tlog("manually aborted");
|
||||
clearRequests(); // stop all xhr activity
|
||||
runNextTest = null;
|
||||
if (interval) clearInterval(interval); // clear timer if present
|
||||
if (settings.telemetry_level > 1) sendTelemetry(function() {});
|
||||
testState = 5; //set test as aborted
|
||||
dlStatus = "";
|
||||
ulStatus = "";
|
||||
pingStatus = "";
|
||||
jitterStatus = "";
|
||||
clientIp = "";
|
||||
dlProgress = 0;
|
||||
ulProgress = 0;
|
||||
pingProgress = 0;
|
||||
}
|
||||
});
|
||||
// stops all XHR activity, aggressively
|
||||
function clearRequests() {
|
||||
tverb("stopping pending XHRs");
|
||||
if (xhr) {
|
||||
for (var i = 0; i < xhr.length; i++) {
|
||||
try {
|
||||
xhr[i].onprogress = null;
|
||||
xhr[i].onload = null;
|
||||
xhr[i].onerror = null;
|
||||
} catch (e) {}
|
||||
try {
|
||||
xhr[i].upload.onprogress = null;
|
||||
xhr[i].upload.onload = null;
|
||||
xhr[i].upload.onerror = null;
|
||||
} catch (e) {}
|
||||
try {
|
||||
xhr[i].abort();
|
||||
} catch (e) {}
|
||||
try {
|
||||
delete xhr[i];
|
||||
} catch (e) {}
|
||||
}
|
||||
xhr = null;
|
||||
}
|
||||
}
|
||||
// gets client's IP using url_getIp, then calls the done function
|
||||
var ipCalled = false; // used to prevent multiple accidental calls to getIp
|
||||
var ispInfo = ""; //used for telemetry
|
||||
function getIp(done) {
|
||||
tverb("getIp");
|
||||
if (ipCalled) return;
|
||||
else ipCalled = true; // getIp already called?
|
||||
var startT = new Date().getTime();
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.onload = function() {
|
||||
tlog("IP: " + xhr.responseText + ", took " + (new Date().getTime() - startT) + "ms");
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
clientIp = data.processedString;
|
||||
ispInfo = data.rawIspInfo;
|
||||
} catch (e) {
|
||||
clientIp = xhr.responseText;
|
||||
ispInfo = "";
|
||||
}
|
||||
done();
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
tlog("getIp failed, took " + (new Date().getTime() - startT) + "ms");
|
||||
done();
|
||||
};
|
||||
xhr.open("GET", settings.url_getIp + url_sep(settings.url_getIp) + (settings.mpot ? "cors=true&" : "") + (settings.getIp_ispInfo ? "isp=true" + (settings.getIp_ispInfo_distance ? "&distance=" + settings.getIp_ispInfo_distance + "&" : "&") : "&") + "r=" + Math.random(), true);
|
||||
xhr.send();
|
||||
}
|
||||
// download test, calls done function when it's over
|
||||
var dlCalled = false; // used to prevent multiple accidental calls to dlTest
|
||||
function dlTest(done) {
|
||||
tverb("dlTest");
|
||||
if (dlCalled) return;
|
||||
else dlCalled = true; // dlTest already called?
|
||||
var totLoaded = 0.0, // total number of loaded bytes
|
||||
startT = new Date().getTime(), // timestamp when test was started
|
||||
bonusT = 0, //how many milliseconds the test has been shortened by (higher on faster connections)
|
||||
graceTimeDone = false, //set to true after the grace time is past
|
||||
failed = false; // set to true if a stream fails
|
||||
xhr = [];
|
||||
// function to create a download stream. streams are slightly delayed so that they will not end at the same time
|
||||
var testStream = function(i, delay) {
|
||||
setTimeout(
|
||||
function() {
|
||||
if (testState !== 1) return; // delayed stream ended up starting after the end of the download test
|
||||
tverb("dl test stream started " + i + " " + delay);
|
||||
var prevLoaded = 0; // number of bytes loaded last time onprogress was called
|
||||
var x = new XMLHttpRequest();
|
||||
xhr[i] = x;
|
||||
xhr[i].onprogress = function(event) {
|
||||
tverb("dl stream progress event " + i + " " + event.loaded);
|
||||
if (testState !== 1) {
|
||||
try {
|
||||
x.abort();
|
||||
} catch (e) {}
|
||||
} // just in case this XHR is still running after the download test
|
||||
// progress event, add number of new loaded bytes to totLoaded
|
||||
var loadDiff = event.loaded <= 0 ? 0 : event.loaded - prevLoaded;
|
||||
if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return; // just in case
|
||||
totLoaded += loadDiff;
|
||||
prevLoaded = event.loaded;
|
||||
}.bind(this);
|
||||
xhr[i].onload = function() {
|
||||
// the large file has been loaded entirely, start again
|
||||
tverb("dl stream finished " + i);
|
||||
try {
|
||||
xhr[i].abort();
|
||||
} catch (e) {} // reset the stream data to empty ram
|
||||
testStream(i, 0);
|
||||
}.bind(this);
|
||||
xhr[i].onerror = function() {
|
||||
// error
|
||||
tverb("dl stream failed " + i);
|
||||
if (settings.xhr_ignoreErrors === 0) failed = true; //abort
|
||||
try {
|
||||
xhr[i].abort();
|
||||
} catch (e) {}
|
||||
delete xhr[i];
|
||||
if (settings.xhr_ignoreErrors === 1) testStream(i, 0); //restart stream
|
||||
}.bind(this);
|
||||
// send xhr
|
||||
try {
|
||||
if (settings.xhr_dlUseBlob) xhr[i].responseType = "blob";
|
||||
else xhr[i].responseType = "arraybuffer";
|
||||
} catch (e) {}
|
||||
xhr[i].open("GET", settings.url_dl + url_sep(settings.url_dl) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random() + "&ckSize=" + settings.garbagePhp_chunkSize, true); // random string to prevent caching
|
||||
xhr[i].send();
|
||||
}.bind(this),
|
||||
1 + delay
|
||||
);
|
||||
}.bind(this);
|
||||
// open streams
|
||||
for (var i = 0; i < settings.xhr_dlMultistream; i++) {
|
||||
testStream(i, settings.xhr_multistreamDelay * i);
|
||||
}
|
||||
// every 200ms, update dlStatus
|
||||
interval = setInterval(
|
||||
function() {
|
||||
tverb("DL: " + dlStatus + (graceTimeDone ? "" : " (in grace time)"));
|
||||
var t = new Date().getTime() - startT;
|
||||
if (graceTimeDone) dlProgress = (t + bonusT) / (settings.time_dl_max * 1000);
|
||||
if (t < 200) return;
|
||||
if (!graceTimeDone) {
|
||||
if (t > 1000 * settings.time_dlGraceTime) {
|
||||
if (totLoaded > 0) {
|
||||
// if the connection is so slow that we didn't get a single chunk yet, do not reset
|
||||
startT = new Date().getTime();
|
||||
bonusT = 0;
|
||||
totLoaded = 0.0;
|
||||
}
|
||||
graceTimeDone = true;
|
||||
}
|
||||
} else {
|
||||
var speed = totLoaded / (t / 1000.0);
|
||||
if (settings.time_auto) {
|
||||
//decide how much to shorten the test. Every 200ms, the test is shortened by the bonusT calculated here
|
||||
var bonus = (5.0 * speed) / 100000;
|
||||
bonusT += bonus > 400 ? 400 : bonus;
|
||||
}
|
||||
//update status
|
||||
dlStatus = ((speed * 8 * settings.overheadCompensationFactor) / (settings.useMebibits ? 1048576 : 1000000)).toFixed(2); // speed is multiplied by 8 to go from bytes to bits, overhead compensation is applied, then everything is divided by 1048576 or 1000000 to go to megabits/mebibits
|
||||
if ((t + bonusT) / 1000.0 > settings.time_dl_max || failed) {
|
||||
// test is over, stop streams and timer
|
||||
if (failed || isNaN(dlStatus)) dlStatus = "Fail";
|
||||
clearRequests();
|
||||
clearInterval(interval);
|
||||
dlProgress = 1;
|
||||
tlog("dlTest: " + dlStatus + ", took " + (new Date().getTime() - startT) + "ms");
|
||||
done();
|
||||
}
|
||||
}
|
||||
}.bind(this),
|
||||
200
|
||||
);
|
||||
}
|
||||
// upload test, calls done function whent it's over
|
||||
var ulCalled = false; // used to prevent multiple accidental calls to ulTest
|
||||
function ulTest(done) {
|
||||
tverb("ulTest");
|
||||
if (ulCalled) return;
|
||||
else ulCalled = true; // ulTest already called?
|
||||
// garbage data for upload test
|
||||
var r = new ArrayBuffer(1048576);
|
||||
var maxInt = Math.pow(2, 32) - 1;
|
||||
try {
|
||||
r = new Uint32Array(r);
|
||||
for (var i = 0; i < r.length; i++) r[i] = Math.random() * maxInt;
|
||||
} catch (e) {}
|
||||
var req = [];
|
||||
var reqsmall = [];
|
||||
for (var i = 0; i < settings.xhr_ul_blob_megabytes; i++) req.push(r);
|
||||
req = new Blob(req);
|
||||
r = new ArrayBuffer(262144);
|
||||
try {
|
||||
r = new Uint32Array(r);
|
||||
for (var i = 0; i < r.length; i++) r[i] = Math.random() * maxInt;
|
||||
} catch (e) {}
|
||||
reqsmall.push(r);
|
||||
reqsmall = new Blob(reqsmall);
|
||||
var testFunction = function() {
|
||||
var totLoaded = 0.0, // total number of transmitted bytes
|
||||
startT = new Date().getTime(), // timestamp when test was started
|
||||
bonusT = 0, //how many milliseconds the test has been shortened by (higher on faster connections)
|
||||
graceTimeDone = false, //set to true after the grace time is past
|
||||
failed = false; // set to true if a stream fails
|
||||
xhr = [];
|
||||
// function to create an upload stream. streams are slightly delayed so that they will not end at the same time
|
||||
var testStream = function(i, delay) {
|
||||
setTimeout(
|
||||
function() {
|
||||
if (testState !== 3) return; // delayed stream ended up starting after the end of the upload test
|
||||
tverb("ul test stream started " + i + " " + delay);
|
||||
var prevLoaded = 0; // number of bytes transmitted last time onprogress was called
|
||||
var x = new XMLHttpRequest();
|
||||
xhr[i] = x;
|
||||
var ie11workaround;
|
||||
if (settings.forceIE11Workaround) ie11workaround = true;
|
||||
else {
|
||||
try {
|
||||
xhr[i].upload.onprogress;
|
||||
ie11workaround = false;
|
||||
} catch (e) {
|
||||
ie11workaround = true;
|
||||
}
|
||||
}
|
||||
if (ie11workaround) {
|
||||
// IE11 workarond: xhr.upload does not work properly, therefore we send a bunch of small 256k requests and use the onload event as progress. This is not precise, especially on fast connections
|
||||
xhr[i].onload = xhr[i].onerror = function() {
|
||||
tverb("ul stream progress event (ie11wa)");
|
||||
totLoaded += reqsmall.size;
|
||||
testStream(i, 0);
|
||||
};
|
||||
xhr[i].open("POST", settings.url_ul + url_sep(settings.url_ul) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random(), true); // random string to prevent caching
|
||||
try {
|
||||
xhr[i].setRequestHeader("Content-Encoding", "identity"); // disable compression (some browsers may refuse it, but data is incompressible anyway)
|
||||
} catch (e) {}
|
||||
//No Content-Type header in MPOT branch because it triggers bugs in some browsers
|
||||
xhr[i].send(reqsmall);
|
||||
} else {
|
||||
// REGULAR version, no workaround
|
||||
xhr[i].upload.onprogress = function(event) {
|
||||
tverb("ul stream progress event " + i + " " + event.loaded);
|
||||
if (testState !== 3) {
|
||||
try {
|
||||
x.abort();
|
||||
} catch (e) {}
|
||||
} // just in case this XHR is still running after the upload test
|
||||
// progress event, add number of new loaded bytes to totLoaded
|
||||
var loadDiff = event.loaded <= 0 ? 0 : event.loaded - prevLoaded;
|
||||
if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return; // just in case
|
||||
totLoaded += loadDiff;
|
||||
prevLoaded = event.loaded;
|
||||
}.bind(this);
|
||||
xhr[i].upload.onload = function() {
|
||||
// this stream sent all the garbage data, start again
|
||||
tverb("ul stream finished " + i);
|
||||
testStream(i, 0);
|
||||
}.bind(this);
|
||||
xhr[i].upload.onerror = function() {
|
||||
tverb("ul stream failed " + i);
|
||||
if (settings.xhr_ignoreErrors === 0) failed = true; //abort
|
||||
try {
|
||||
xhr[i].abort();
|
||||
} catch (e) {}
|
||||
delete xhr[i];
|
||||
if (settings.xhr_ignoreErrors === 1) testStream(i, 0); //restart stream
|
||||
}.bind(this);
|
||||
// send xhr
|
||||
xhr[i].open("POST", settings.url_ul + url_sep(settings.url_ul) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random(), true); // random string to prevent caching
|
||||
try {
|
||||
xhr[i].setRequestHeader("Content-Encoding", "identity"); // disable compression (some browsers may refuse it, but data is incompressible anyway)
|
||||
} catch (e) {}
|
||||
//No Content-Type header in MPOT branch because it triggers bugs in some browsers
|
||||
xhr[i].send(req);
|
||||
}
|
||||
}.bind(this),
|
||||
delay
|
||||
);
|
||||
}.bind(this);
|
||||
// open streams
|
||||
for (var i = 0; i < settings.xhr_ulMultistream; i++) {
|
||||
testStream(i, settings.xhr_multistreamDelay * i);
|
||||
}
|
||||
// every 200ms, update ulStatus
|
||||
interval = setInterval(
|
||||
function() {
|
||||
tverb("UL: " + ulStatus + (graceTimeDone ? "" : " (in grace time)"));
|
||||
var t = new Date().getTime() - startT;
|
||||
if (graceTimeDone) ulProgress = (t + bonusT) / (settings.time_ul_max * 1000);
|
||||
if (t < 200) return;
|
||||
if (!graceTimeDone) {
|
||||
if (t > 1000 * settings.time_ulGraceTime) {
|
||||
if (totLoaded > 0) {
|
||||
// if the connection is so slow that we didn't get a single chunk yet, do not reset
|
||||
startT = new Date().getTime();
|
||||
bonusT = 0;
|
||||
totLoaded = 0.0;
|
||||
}
|
||||
graceTimeDone = true;
|
||||
}
|
||||
} else {
|
||||
var speed = totLoaded / (t / 1000.0);
|
||||
if (settings.time_auto) {
|
||||
//decide how much to shorten the test. Every 200ms, the test is shortened by the bonusT calculated here
|
||||
var bonus = (5.0 * speed) / 100000;
|
||||
bonusT += bonus > 400 ? 400 : bonus;
|
||||
}
|
||||
//update status
|
||||
ulStatus = ((speed * 8 * settings.overheadCompensationFactor) / (settings.useMebibits ? 1048576 : 1000000)).toFixed(2); // speed is multiplied by 8 to go from bytes to bits, overhead compensation is applied, then everything is divided by 1048576 or 1000000 to go to megabits/mebibits
|
||||
if ((t + bonusT) / 1000.0 > settings.time_ul_max || failed) {
|
||||
// test is over, stop streams and timer
|
||||
if (failed || isNaN(ulStatus)) ulStatus = "Fail";
|
||||
clearRequests();
|
||||
clearInterval(interval);
|
||||
ulProgress = 1;
|
||||
tlog("ulTest: " + ulStatus + ", took " + (new Date().getTime() - startT) + "ms");
|
||||
done();
|
||||
}
|
||||
}
|
||||
}.bind(this),
|
||||
200
|
||||
);
|
||||
}.bind(this);
|
||||
if (settings.mpot) {
|
||||
tverb("Sending POST request before performing upload test");
|
||||
xhr = [];
|
||||
xhr[0] = new XMLHttpRequest();
|
||||
xhr[0].onload = xhr[0].onerror = function() {
|
||||
tverb("POST request sent, starting upload test");
|
||||
testFunction();
|
||||
}.bind(this);
|
||||
xhr[0].open("POST", settings.url_ul) + (settings.mpot ? "cors=true&" : "");
|
||||
xhr[0].send();
|
||||
} else testFunction();
|
||||
}
|
||||
// ping+jitter test, function done is called when it's over
|
||||
var ptCalled = false; // used to prevent multiple accidental calls to pingTest
|
||||
function pingTest(done) {
|
||||
tverb("pingTest");
|
||||
if (ptCalled) return;
|
||||
else ptCalled = true; // pingTest already called?
|
||||
var startT = new Date().getTime(); //when the test was started
|
||||
var prevT = null; // last time a pong was received
|
||||
var ping = 0.0; // current ping value
|
||||
var jitter = 0.0; // current jitter value
|
||||
var i = 0; // counter of pongs received
|
||||
var prevInstspd = 0; // last ping time, used for jitter calculation
|
||||
xhr = [];
|
||||
// ping function
|
||||
var doPing = function() {
|
||||
tverb("ping");
|
||||
pingProgress = i / settings.count_ping;
|
||||
prevT = new Date().getTime();
|
||||
xhr[0] = new XMLHttpRequest();
|
||||
xhr[0].onload = function() {
|
||||
// pong
|
||||
tverb("pong");
|
||||
if (i === 0) {
|
||||
prevT = new Date().getTime(); // first pong
|
||||
} else {
|
||||
var instspd = new Date().getTime() - prevT;
|
||||
if (settings.ping_allowPerformanceApi) {
|
||||
try {
|
||||
//try to get accurate performance timing using performance api
|
||||
var p = performance.getEntries();
|
||||
p = p[p.length - 1];
|
||||
var d = p.responseStart - p.requestStart;
|
||||
if (d <= 0) d = p.duration;
|
||||
if (d > 0 && d < instspd) instspd = d;
|
||||
} catch (e) {
|
||||
//if not possible, keep the estimate
|
||||
tverb("Performance API not supported, using estimate");
|
||||
}
|
||||
}
|
||||
//noticed that some browsers randomly have 0ms ping
|
||||
if (instspd < 1) instspd = prevInstspd;
|
||||
if (instspd < 1) instspd = 1;
|
||||
var instjitter = Math.abs(instspd - prevInstspd);
|
||||
if (i === 1) ping = instspd;
|
||||
/* first ping, can't tell jitter yet*/ else {
|
||||
if (instspd < ping) ping = instspd; // update ping, if the instant ping is lower
|
||||
if (i === 2) jitter = instjitter;
|
||||
//discard the first jitter measurement because it might be much higher than it should be
|
||||
else jitter = instjitter > jitter ? jitter * 0.3 + instjitter * 0.7 : jitter * 0.8 + instjitter * 0.2; // update jitter, weighted average. spikes in ping values are given more weight.
|
||||
}
|
||||
prevInstspd = instspd;
|
||||
}
|
||||
pingStatus = ping.toFixed(2);
|
||||
jitterStatus = jitter.toFixed(2);
|
||||
i++;
|
||||
tverb("ping: " + pingStatus + " jitter: " + jitterStatus);
|
||||
if (i < settings.count_ping) doPing();
|
||||
else {
|
||||
// more pings to do?
|
||||
pingProgress = 1;
|
||||
tlog("ping: " + pingStatus + " jitter: " + jitterStatus + ", took " + (new Date().getTime() - startT) + "ms");
|
||||
done();
|
||||
}
|
||||
}.bind(this);
|
||||
xhr[0].onerror = function() {
|
||||
// a ping failed, cancel test
|
||||
tverb("ping failed");
|
||||
if (settings.xhr_ignoreErrors === 0) {
|
||||
//abort
|
||||
pingStatus = "Fail";
|
||||
jitterStatus = "Fail";
|
||||
clearRequests();
|
||||
tlog("ping test failed, took " + (new Date().getTime() - startT) + "ms");
|
||||
pingProgress = 1;
|
||||
done();
|
||||
}
|
||||
if (settings.xhr_ignoreErrors === 1) doPing(); //retry ping
|
||||
if (settings.xhr_ignoreErrors === 2) {
|
||||
//ignore failed ping
|
||||
i++;
|
||||
if (i < settings.count_ping) doPing();
|
||||
else {
|
||||
// more pings to do?
|
||||
pingProgress = 1;
|
||||
tlog("ping: " + pingStatus + " jitter: " + jitterStatus + ", took " + (new Date().getTime() - startT) + "ms");
|
||||
done();
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
// send xhr
|
||||
xhr[0].open("GET", settings.url_ping + url_sep(settings.url_ping) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random(), true); // random string to prevent caching
|
||||
xhr[0].send();
|
||||
}.bind(this);
|
||||
doPing(); // start first ping
|
||||
}
|
||||
// telemetry
|
||||
function sendTelemetry(done) {
|
||||
if (settings.telemetry_level < 1) return;
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.onload = function() {
|
||||
try {
|
||||
var parts = xhr.responseText.split(" ");
|
||||
if (parts[0] == "id") {
|
||||
try {
|
||||
var id = parts[1];
|
||||
done(id);
|
||||
} catch (e) {
|
||||
done(null);
|
||||
}
|
||||
} else done(null);
|
||||
} catch (e) {
|
||||
done(null);
|
||||
}
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
console.log("TELEMETRY ERROR " + xhr.status);
|
||||
done(null);
|
||||
};
|
||||
xhr.open("POST", settings.url_telemetry + url_sep(settings.url_telemetry) + (settings.mpot ? "cors=true&" : "") + "r=" + Math.random(), true);
|
||||
var telemetryIspInfo = {
|
||||
processedString: clientIp,
|
||||
rawIspInfo: typeof ispInfo === "object" ? ispInfo : ""
|
||||
};
|
||||
try {
|
||||
var fd = new FormData();
|
||||
fd.append("ispinfo", JSON.stringify(telemetryIspInfo));
|
||||
fd.append("dl", dlStatus);
|
||||
fd.append("ul", ulStatus);
|
||||
fd.append("ping", pingStatus);
|
||||
fd.append("jitter", jitterStatus);
|
||||
fd.append("log", settings.telemetry_level > 1 ? log : "");
|
||||
fd.append("extra", settings.telemetry_extra);
|
||||
xhr.send(fd);
|
||||
} catch (ex) {
|
||||
var postData = "extra=" + encodeURIComponent(settings.telemetry_extra) + "&ispinfo=" + encodeURIComponent(JSON.stringify(telemetryIspInfo)) + "&dl=" + encodeURIComponent(dlStatus) + "&ul=" + encodeURIComponent(ulStatus) + "&ping=" + encodeURIComponent(pingStatus) + "&jitter=" + encodeURIComponent(jitterStatus) + "&log=" + encodeURIComponent(settings.telemetry_level > 1 ? log : "");
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.send(postData);
|
||||
}
|
||||
}
|
||||
|
||||
70
includes/CSRF.php
Executable file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace RaspAP\Tokens;
|
||||
|
||||
class CSRF
|
||||
{
|
||||
protected static ?CSRFTokenizer $instance = null;
|
||||
|
||||
/*
|
||||
* Get the CSRFTokenizer instance (singleton)
|
||||
*
|
||||
* @return CSRFTokenizer
|
||||
*/
|
||||
public static function instance(): CSRFTokenizer
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new CSRFTokenizer();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function token(): string
|
||||
{
|
||||
return self::instance()->getToken();
|
||||
}
|
||||
|
||||
public static function verify(): bool
|
||||
{
|
||||
if (!isset($_POST['csrf_token'])) {
|
||||
return false;
|
||||
}
|
||||
return self::instance()->csrfValidateRequest() &&
|
||||
self::instance()->CSRFValidate($_POST['csrf_token']);
|
||||
}
|
||||
|
||||
public static function metaTag(): string
|
||||
{
|
||||
return self::instance()->CSRFMetaTag();
|
||||
}
|
||||
|
||||
public static function hiddenField(): string
|
||||
{
|
||||
return self::instance()->CSRFTokenFieldTag();
|
||||
}
|
||||
|
||||
public static function handleInvalidToken(): void
|
||||
{
|
||||
self::instance()->handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a CSRF Request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateRequest(): bool
|
||||
{
|
||||
$methods = ['POST', 'PUT', 'DELETE', 'PATCH'];
|
||||
return in_array($_SERVER['REQUEST_METHOD'], $methods) &&
|
||||
self::instance()->csrfValidateRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if (\RaspAP\Tokens\CSRF::validateRequest()) {
|
||||
if (!\RaspAP\Tokens\CSRF::verify()) {
|
||||
error_log("CSRF verification failed: Token missing or invalid");
|
||||
\RaspAP\Tokens\CSRF::handleInvalidToken();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ function DisplayAdBlockConfig()
|
||||
file_put_contents("/tmp/dnsmasq_custom", $_POST['adblock-custom-hosts'].PHP_EOL);
|
||||
system("sudo cp /tmp/dnsmasq_custom " .RASPI_ADBLOCK_LISTPATH .'custom.txt', $return);
|
||||
$config.= 'addn-hosts=' .RASPI_ADBLOCK_LISTPATH .'custom.txt'.PHP_EOL;
|
||||
$custom_enabled = true;
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
@@ -64,6 +63,13 @@ function DisplayAdBlockConfig()
|
||||
}
|
||||
}
|
||||
|
||||
$custom_list = RASPI_ADBLOCK_LISTPATH . 'custom.txt';
|
||||
$custom_enabled = false;
|
||||
|
||||
if (file_exists($custom_list) && filesize($custom_list) > 0) {
|
||||
$custom_enabled = true;
|
||||
}
|
||||
|
||||
exec('cat '. RASPI_ADBLOCK_CONFIG, $return);
|
||||
$arrConf = ParseConfig($return);
|
||||
if (sizeof($arrConf) > 0) {
|
||||
|
||||
@@ -35,6 +35,8 @@ function DisplayAuthConfig($username)
|
||||
} else {
|
||||
$status->addMessage('Old password does not match', 'danger');
|
||||
}
|
||||
} elseif (isset($_POST['logout'])) {
|
||||
$auth->logout();
|
||||
}
|
||||
|
||||
echo renderTemplate(
|
||||
|
||||
@@ -9,31 +9,13 @@
|
||||
*/
|
||||
spl_autoload_register(function ($class) {
|
||||
|
||||
// project-specific namespace prefix
|
||||
$prefix = '';
|
||||
// base directory where all class files are stored
|
||||
$base_dir = __DIR__ . '/../src/';
|
||||
|
||||
// base directory for the namespace prefix
|
||||
$base_dir = 'src/';
|
||||
// convert the fully qualified class name into a file path
|
||||
$file = $base_dir . str_replace('\\', '/', $class) . '.php';
|
||||
|
||||
// normalize the base directory with a trailing separator
|
||||
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
|
||||
|
||||
// does the class use the namespace prefix?
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
// no, move to the next registered autoloader
|
||||
return;
|
||||
}
|
||||
|
||||
// get the relative class name
|
||||
$relative_class = substr($class, $len);
|
||||
|
||||
// replace the namespace prefix with the base directory, replace namespace
|
||||
// separators with directory separators in the relative class name, append
|
||||
// with .php
|
||||
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
|
||||
|
||||
// if the file exists, require it
|
||||
// require the file if it exists
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
|
||||
8
includes/bootstrap.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
// cache-control headers
|
||||
if (!headers_sent()) {
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: 0");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
require_once 'includes/wifi_functions.php';
|
||||
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -8,12 +8,13 @@ require_once 'includes/wifi_functions.php';
|
||||
*/
|
||||
function DisplayWPAConfig()
|
||||
{
|
||||
$wifi = new WiFiManager();
|
||||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
$networks = [];
|
||||
|
||||
getWifiInterface();
|
||||
knownWifiStations($networks);
|
||||
setKnownStationsWPA($networks);
|
||||
$wifi->getWifiInterface();
|
||||
$wifi->knownWifiStations($networks);
|
||||
$wifi->setKnownStationsWPA($networks);
|
||||
|
||||
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
||||
|
||||
@@ -30,7 +31,7 @@ function DisplayWPAConfig()
|
||||
} elseif (isset($_POST['wpa_reinit'])) {
|
||||
$status->addMessage('Attempting to reinitialize wpa_supplicant', 'warning');
|
||||
$force_remove = true;
|
||||
$result = reinitializeWPA($force_remove);
|
||||
$result = $wifi->reinitializeWPA($force_remove);
|
||||
} elseif (isset($_POST['client_settings'])) {
|
||||
$tmp_networks = $networks;
|
||||
if ($wpa_file = fopen('/tmp/wifidata', 'w')) {
|
||||
@@ -90,7 +91,7 @@ function DisplayWPAConfig()
|
||||
if (strlen($network['passphrase']) >=8 && strlen($network['passphrase']) <= 63) {
|
||||
unset($wpa_passphrase);
|
||||
unset($line);
|
||||
exec('wpa_passphrase '. ssid2utf8( escapeshellarg($ssid) ) . ' ' . escapeshellarg($network['passphrase']), $wpa_passphrase);
|
||||
exec('wpa_passphrase '. $wifi->ssid2utf8( escapeshellarg($ssid) ) . ' ' . escapeshellarg($network['passphrase']), $wpa_passphrase);
|
||||
foreach ($wpa_passphrase as $line) {
|
||||
if (preg_match('/^\s*}\s*$/', $line)) {
|
||||
if (array_key_exists('priority', $network)) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once 'functions.php';
|
||||
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
@@ -1,230 +1,183 @@
|
||||
<?php
|
||||
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/wifi_functions.php';
|
||||
require_once 'includes/functions.php';
|
||||
|
||||
use RaspAP\System\Sysinfo;
|
||||
use RaspAP\UI\Dashboard;
|
||||
use RaspAP\Messages\StatusMessage;
|
||||
use RaspAP\Plugins\PluginManager;
|
||||
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||
|
||||
/**
|
||||
* Show dashboard page.
|
||||
* Displays the dashboard
|
||||
*/
|
||||
function DisplayDashboard(&$extraFooterScripts)
|
||||
function DisplayDashboard(&$extraFooterScripts): void
|
||||
{
|
||||
getWifiInterface();
|
||||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
// Need this check interface name for proper shell execution.
|
||||
if (!preg_match('/^([a-zA-Z0-9]+)$/', $_SESSION['wifi_client_interface'])) {
|
||||
$status->addMessage(_('Interface name invalid.'), 'danger');
|
||||
$status->showMessages();
|
||||
return;
|
||||
}
|
||||
// instantiate RaspAP objects
|
||||
$system = new Sysinfo();
|
||||
$dashboard = new Dashboard();
|
||||
$status = new StatusMessage();
|
||||
$pluginManager = PluginManager::getInstance();
|
||||
$wifi = new WiFiManager();
|
||||
|
||||
if (!function_exists('exec')) {
|
||||
$status->addMessage(_('Required exec function is disabled. Check if exec is not added to php disable_functions.'), 'danger');
|
||||
$status->showMessages();
|
||||
return;
|
||||
}
|
||||
exec('ip a show '.$_SESSION['ap_interface'], $stdoutIp);
|
||||
$stdoutIpAllLinesGlued = implode(" ", $stdoutIp);
|
||||
$stdoutIpWRepeatedSpaces = preg_replace('/\s\s+/', ' ', $stdoutIpAllLinesGlued);
|
||||
// set AP and client interface session vars
|
||||
$wifi->getWifiInterface();
|
||||
|
||||
preg_match('/link\/ether ([0-9a-f:]+)/i', $stdoutIpWRepeatedSpaces, $matchesMacAddr) || $matchesMacAddr[1] = _('No MAC Address Found');
|
||||
$macAddr = $matchesMacAddr[1];
|
||||
|
||||
$ipv4Addrs = '';
|
||||
$ipv4Netmasks = '';
|
||||
if (!preg_match_all('/inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([0-3][0-9])/i', $stdoutIpWRepeatedSpaces, $matchesIpv4AddrAndSubnet, PREG_SET_ORDER)) {
|
||||
$ipv4Addrs = _('No IPv4 Address Found');
|
||||
} else {
|
||||
foreach ($matchesIpv4AddrAndSubnet as $inet) {
|
||||
$address = $inet[1];
|
||||
$suffix = (int) $inet[2];
|
||||
$netmask = long2ip(-1 << (32 - $suffix));
|
||||
$ipv4Addrs .= " $address";
|
||||
$ipv4Netmasks .= " $netmask";
|
||||
}
|
||||
$ipv4Addrs = trim($ipv4Addrs);
|
||||
$ipv4Netmasks = trim($ipv4Netmasks);
|
||||
}
|
||||
$ipv4Netmasks = empty($ipv4Netmasks) ? "-" : $ipv4Netmasks;
|
||||
|
||||
$ipv6Addrs = '';
|
||||
if (!preg_match_all('/inet6 ([a-f0-9:]+)/i', $stdoutIpWRepeatedSpaces, $matchesIpv6Addr)) {
|
||||
$ipv6Addrs = _('No IPv6 Address Found');
|
||||
} else {
|
||||
if (isset($matchesIpv6Addr[1])) {
|
||||
$ipv6Addrs = implode(' ', $matchesIpv6Addr[1]);
|
||||
}
|
||||
}
|
||||
|
||||
preg_match('/state (UP|DOWN)/i', $stdoutIpWRepeatedSpaces, $matchesState) || $matchesState[1] = 'unknown';
|
||||
$interfaceState = $matchesState[1];
|
||||
|
||||
// Because of table layout used in the ip output we get the interface statistics directly from
|
||||
// the system. One advantage of this is that it could work when interface is disable.
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/rx_packets ', $stdoutCatRxPackets);
|
||||
$strRxPackets = _('No data');
|
||||
if (ctype_digit($stdoutCatRxPackets[0])) {
|
||||
$strRxPackets = $stdoutCatRxPackets[0];
|
||||
}
|
||||
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/tx_packets ', $stdoutCatTxPackets);
|
||||
$strTxPackets = _('No data');
|
||||
if (ctype_digit($stdoutCatTxPackets[0])) {
|
||||
$strTxPackets = $stdoutCatTxPackets[0];
|
||||
}
|
||||
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/rx_bytes ', $stdoutCatRxBytes);
|
||||
$strRxBytes = _('No data');
|
||||
if (ctype_digit($stdoutCatRxBytes[0])) {
|
||||
$strRxBytes = $stdoutCatRxBytes[0];
|
||||
$strRxBytes .= getHumanReadableDatasize($strRxBytes);
|
||||
}
|
||||
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/tx_bytes ', $stdoutCatTxBytes);
|
||||
$strTxBytes = _('No data');
|
||||
if (ctype_digit($stdoutCatTxBytes[0])) {
|
||||
$strTxBytes = $stdoutCatTxBytes[0];
|
||||
$strTxBytes .= getHumanReadableDatasize($strTxBytes);
|
||||
}
|
||||
|
||||
exec ('vnstat --dbiflist', $stdoutVnStatDB);
|
||||
if (!preg_match('/'.$_SESSION['ap_interface'].'/', $stdoutVnStatDB[0])) {
|
||||
exec('sudo vnstat --add --iface '.$_SESSION['ap_interface'], $return);
|
||||
}
|
||||
|
||||
define('SSIDMAXLEN', 32);
|
||||
// Warning iw comes with: "Do NOT screenscrape this tool, we don't consider its output stable."
|
||||
exec('iw dev ' .$_SESSION['wifi_client_interface']. ' link ', $stdoutIw);
|
||||
$stdoutIwAllLinesGlued = implode('+', $stdoutIw); // Break lines with character illegal in SSID and MAC addr
|
||||
$stdoutIwWRepSpaces = preg_replace('/\s\s+/', ' ', $stdoutIwAllLinesGlued);
|
||||
|
||||
preg_match('/Connected to (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2}))/', $stdoutIwWRepSpaces, $matchesBSSID) || $matchesBSSID[1] = '';
|
||||
$connectedBSSID = $matchesBSSID[1];
|
||||
$connectedBSSID = empty($connectedBSSID) ? "-" : $connectedBSSID;
|
||||
|
||||
$wlanHasLink = false;
|
||||
if ($interfaceState === 'UP') {
|
||||
$wlanHasLink = true;
|
||||
}
|
||||
|
||||
if (!preg_match('/SSID: ([^+]{1,'.SSIDMAXLEN.'})/', $stdoutIwWRepSpaces, $matchesSSID)) {
|
||||
$wlanHasLink = false;
|
||||
$matchesSSID[1] = 'None';
|
||||
}
|
||||
$connectedSSID = str_replace('\x20', '', $matchesSSID[1]);
|
||||
|
||||
preg_match('/freq: (\d+)/i', $stdoutIwWRepSpaces, $matchesFrequency) || $matchesFrequency[1] = '';
|
||||
$frequency = $matchesFrequency[1].' MHz';
|
||||
|
||||
preg_match('/signal: (-?[0-9]+ dBm)/i', $stdoutIwWRepSpaces, $matchesSignal) || $matchesSignal[1] = '';
|
||||
$signalLevel = $matchesSignal[1];
|
||||
$signalLevel = empty($signalLevel) ? "-" : $signalLevel;
|
||||
|
||||
preg_match('/tx bitrate: ([0-9\.]+ [KMGT]?Bit\/s)/', $stdoutIwWRepSpaces, $matchesBitrate) || $matchesBitrate[1] = '';
|
||||
$bitrate = $matchesBitrate[1];
|
||||
$bitrate = empty($bitrate) ? "-" : $bitrate;
|
||||
|
||||
// txpower is now displayed on iw dev(..) info command, not on link command.
|
||||
exec('iw dev '.$_SESSION['wifi_client_interface'].' info ', $stdoutIwInfo);
|
||||
$stdoutIwInfoAllLinesGlued = implode(' ', $stdoutIwInfo);
|
||||
$stdoutIpInfoWRepSpaces = preg_replace('/\s\s+/', ' ', $stdoutIwInfoAllLinesGlued);
|
||||
|
||||
preg_match('/txpower ([0-9\.]+ dBm)/i', $stdoutIpInfoWRepSpaces, $matchesTxPower) || $matchesTxPower[1] = '';
|
||||
$txPower = $matchesTxPower[1];
|
||||
|
||||
// iw does not have the "Link Quality". This is a is an aggregate value,
|
||||
// and depends on the driver and hardware.
|
||||
// Display link quality as signal quality for now.
|
||||
$strLinkQuality = 0;
|
||||
if ($signalLevel > -100 && $wlanHasLink) {
|
||||
if ($signalLevel >= 0) {
|
||||
$strLinkQuality = 100;
|
||||
} else {
|
||||
$strLinkQuality = 100 + intval($signalLevel);
|
||||
}
|
||||
}
|
||||
|
||||
$wlan0up = false;
|
||||
$classMsgDevicestatus = 'warning';
|
||||
if ($interfaceState === 'UP') {
|
||||
$wlan0up = true;
|
||||
$classMsgDevicestatus = 'success';
|
||||
}
|
||||
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
if (isset($_POST['ifdown_wlan0'])) {
|
||||
// Pressed stop button
|
||||
if ($interfaceState === 'UP') {
|
||||
$status->addMessage(sprintf(_('Interface is going %s.'), _('down')), 'warning');
|
||||
exec('sudo ip link set '.$_SESSION['ap_interface'].' down');
|
||||
$wlan0up = false;
|
||||
$status->addMessage(sprintf(_('Interface is now %s.'), _('down')), 'success');
|
||||
} elseif ($interfaceState === 'unknown') {
|
||||
$status->addMessage(_('Interface state unknown.'), 'danger');
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface already %s.'), _('down')), 'warning');
|
||||
}
|
||||
} elseif (isset($_POST['ifup_wlan0'])) {
|
||||
// Pressed start button
|
||||
if ($interfaceState === 'DOWN') {
|
||||
$status->addMessage(sprintf(_('Interface is going %s.'), _('up')), 'warning');
|
||||
exec('sudo ip link set ' .$_SESSION['ap_interface']. ' up');
|
||||
exec('sudo ip -s a f label ' .$_SESSION['ap_interface']);
|
||||
$wlan0up = true;
|
||||
$status->addMessage(sprintf(_('Interface is now %s.'), _('up')), 'success');
|
||||
} elseif ($interfaceState === 'unknown') {
|
||||
$status->addMessage(_('Interface state unknown.'), 'danger');
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface already %s.'), _('up')), 'warning');
|
||||
}
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface is %s.'), strtolower($interfaceState)), $classMsgDevicestatus);
|
||||
}
|
||||
}
|
||||
// brought in from template
|
||||
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
|
||||
$bridgedEnable = $arrHostapdConf['BridgedEnable'];
|
||||
$interface = $_SESSION['ap_interface'] ?? 'wlan0';
|
||||
$clientInterface = $_SESSION['wifi_client_interface'];
|
||||
$apInterface = $_SESSION['ap_interface'];
|
||||
$MACPattern = '"([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}"';
|
||||
$hostname = $system->hostname();
|
||||
$revision = $system->rpiRevision();
|
||||
$deviceImage = $dashboard->getDeviceImage($revision);
|
||||
$hostapd = $system->hostapdStatus();
|
||||
$adblock = $system->adBlockStatus();
|
||||
$vpn = $system->getActiveVpnInterface();
|
||||
$frequency = $dashboard->getFrequencyBand($interface);
|
||||
$details = $dashboard->getInterfaceDetails($interface);
|
||||
$wireless = $dashboard->getWirelessDetails($interface);
|
||||
$connectionType = $dashboard->getConnectionType();
|
||||
$connectionIcon = $dashboard->getConnectionIcon($connectionType);
|
||||
$state = strtolower($details['state']);
|
||||
$wirelessClients = $dashboard->getWirelessClients($interface);
|
||||
$ethernetClients = $dashboard->getEthernetClients();
|
||||
$totalClients = $wirelessClients + $ethernetClients;
|
||||
$plugins = $pluginManager->getInstalledPlugins();
|
||||
$bridgedEnable = getBridgedState();
|
||||
|
||||
if (getBridgedState()) {
|
||||
$moreLink = "hostapd_conf";
|
||||
exec('iw dev ' . $apInterface . ' station dump | grep -oE ' . $MACPattern, $clients);
|
||||
} else {
|
||||
$moreLink = "dhcpd_conf";
|
||||
exec('cat ' . RASPI_DNSMASQ_LEASES . '| grep -E $(iw dev ' . $apInterface . ' station dump | grep -oE ' . $MACPattern . ' | paste -sd "|")', $clients);
|
||||
// handle page actions
|
||||
if (!empty($_POST)) {
|
||||
$status = $dashboard->handlePageAction($state, $_POST, $status, $interface);
|
||||
// refresh interface details + state
|
||||
$details = $dashboard->getInterfaceDetails($interface);
|
||||
$state = strtolower($details['state']);
|
||||
}
|
||||
|
||||
$ipv4Address = $details['ipv4'];
|
||||
$ipv4Netmask = $details['ipv4_netmask'];
|
||||
$macAddress = $details['mac'];
|
||||
$ssid = $wireless['ssid'];
|
||||
$ethernetActive = ($connectionType === 'ethernet') ? "active" : "inactive";
|
||||
$wirelessActive = ($connectionType === 'wireless') ? "active" : "inactive";
|
||||
$tetheringActive = ($connectionType === 'tethering') ? "active" : "inactive";
|
||||
$cellularActive = ($connectionType === 'cellular') ? "active" : "inactive";
|
||||
$bridgedStatus = ($bridgedEnable == 1) ? "active" : "";
|
||||
$hostapdStatus = ($hostapd[0] == 1) ? "active" : "";
|
||||
$adblockStatus = ($adblock == true) ? "active" : "";
|
||||
$wirelessClientActive = ($wirelessClients > 0) ? "active" : "inactive";
|
||||
$wirelessClientLabel = sprintf(
|
||||
_('%d WLAN %s'),
|
||||
$wirelessClients,
|
||||
$dashboard->formatClientLabel($wirelessClients)
|
||||
);
|
||||
$ethernetClientActive = ($ethernetClients > 0) ? "active" : "inactive";
|
||||
$ethernetClientLabel = sprintf(
|
||||
_('%d LAN %s'),
|
||||
$ethernetClients,
|
||||
$dashboard->formatClientLabel($ethernetClients)
|
||||
);
|
||||
$totalClientsActive = ($totalClients > 0) ? "active": "inactive";
|
||||
$freq5active = $freq24active = "";
|
||||
$varName = "freq" . str_replace('.', '', $frequency) . "active";
|
||||
$$varName = "active";
|
||||
$vpnStatus = $vpn ? "active" : "inactive";
|
||||
$vpnManaged = $vpn ? $dashboard->getVpnManaged($vpn) : null;
|
||||
$firewallManaged = $firewallStatus = "";
|
||||
$firewallInstalled = (bool) array_filter($plugins, function($p) {
|
||||
return substr($p, -strlen('Firewall')) === 'Firewall';
|
||||
});
|
||||
if (!$firewallInstalled) {
|
||||
$firewallUnavailable = '<i class="fas fa-slash fa-stack-1x"></i>';
|
||||
} else {
|
||||
$firewallManaged = '<a href="/plugin__Firewall">';
|
||||
$firewallStatus = ($dashboard->firewallEnabled() == true) ? "active" : "";
|
||||
$firewallUnavailable = null;
|
||||
}
|
||||
$ifaceStatus = $wlan0up ? "up" : "down";
|
||||
|
||||
echo renderTemplate(
|
||||
"dashboard", compact(
|
||||
"clients",
|
||||
"moreLink",
|
||||
"apInterface",
|
||||
"revision",
|
||||
"deviceImage",
|
||||
"interface",
|
||||
"clientInterface",
|
||||
"ifaceStatus",
|
||||
"bridgedEnable",
|
||||
"status",
|
||||
"ipv4Addrs",
|
||||
"ipv4Netmasks",
|
||||
"ipv6Addrs",
|
||||
"macAddr",
|
||||
"strRxPackets",
|
||||
"strRxBytes",
|
||||
"strTxPackets",
|
||||
"strTxBytes",
|
||||
"connectedSSID",
|
||||
"connectedBSSID",
|
||||
"bitrate",
|
||||
"signalLevel",
|
||||
"txPower",
|
||||
"state",
|
||||
"bridgedStatus",
|
||||
"hostapdStatus",
|
||||
"adblockStatus",
|
||||
"vpnStatus",
|
||||
"vpnManaged",
|
||||
"firewallUnavailable",
|
||||
"firewallStatus",
|
||||
"firewallManaged",
|
||||
"ipv4Address",
|
||||
"ipv4Netmask",
|
||||
"macAddress",
|
||||
"ssid",
|
||||
"frequency",
|
||||
"strLinkQuality",
|
||||
"wlan0up"
|
||||
"freq5active",
|
||||
"freq24active",
|
||||
"wirelessClients",
|
||||
"wirelessClientLabel",
|
||||
"wirelessClientActive",
|
||||
"ethernetClients",
|
||||
"ethernetClientLabel",
|
||||
"ethernetClientActive",
|
||||
"totalClients",
|
||||
"totalClientsActive",
|
||||
"connectionType",
|
||||
"connectionIcon",
|
||||
"ethernetActive",
|
||||
"wirelessActive",
|
||||
"tetheringActive",
|
||||
"cellularActive",
|
||||
"status"
|
||||
)
|
||||
);
|
||||
$extraFooterScripts[] = array('src'=>'app/js/dashboardchart.js', 'defer'=>false);
|
||||
$extraFooterScripts[] = array('src'=>'app/js/linkquality.js', 'defer'=>false);
|
||||
$extraFooterScripts[] = array('src'=>'app/js/vendor/dashboardchart.js', 'defer'=>false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a URL for an svg solid line representing the associated
|
||||
* connection type
|
||||
*
|
||||
* @param string $connectionType
|
||||
* @return string
|
||||
*/
|
||||
function renderConnection(string $connectionType): string
|
||||
{
|
||||
$deviceMap = [
|
||||
'ethernet' => 'device-1',
|
||||
'wireless' => 'device-2',
|
||||
'tethering' => 'device-3',
|
||||
'cellular' => 'device-4'
|
||||
];
|
||||
$device = $deviceMap[$connectionType] ?? 'device-unknown';
|
||||
|
||||
// return generated URL for solid.php
|
||||
return sprintf('app/img/solid.php?joint&%s&out', $device);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a URL for an svg solid line representing associated
|
||||
* client connection(s)
|
||||
*
|
||||
* @param int $wirelessClients
|
||||
* @param int $ethernetClients
|
||||
* @return string
|
||||
*/
|
||||
function renderClientConnections(int $wirelessClients, int $ethernetClients): string
|
||||
{
|
||||
$devices = [];
|
||||
|
||||
if ($wirelessClients > 0) {
|
||||
$devices[] = 'device-1&out';
|
||||
}
|
||||
if ($ethernetClients > 0) {
|
||||
$devices[] = 'device-2&out';
|
||||
}
|
||||
return empty($devices) ? '' : sprintf(
|
||||
'<img src="app/img/right-solid.php?%s" class="solid-lines solid-lines-right" alt="Client connections">',
|
||||
implode('&', $devices)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@ function DisplayDataUsage(&$extraFooterScripts)
|
||||
echo renderTemplate("data_usage", [ "interfaces" => $interfacesWlo ]);
|
||||
|
||||
$extraFooterScripts[] = array('src'=>'dist/datatables/jquery.dataTables.min.js', 'defer'=>false);
|
||||
$extraFooterScripts[] = array('src'=>'app/js/bandwidthcharts.js', 'defer'=>false);
|
||||
$extraFooterScripts[] = array('src'=>'app/js/vendor/bandwidthcharts.js', 'defer'=>false);
|
||||
}
|
||||
|
||||