mirror of
https://github.com/billz/raspap-webgui.git
synced 2025-12-27 15:34:28 +01:00
Compare commits
195 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf1ca564b | ||
|
|
29c1e7dda3 | ||
|
|
38ac6a12c6 | ||
|
|
2c896bbc12 | ||
|
|
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 | ||
|
|
f802c825f6 | ||
|
|
c0f496cf07 | ||
|
|
116704c59b | ||
|
|
e9742a5252 | ||
|
|
2aaf1eca07 | ||
|
|
575876406c |
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@@ -15,8 +15,10 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- arch: "32-bit"
|
- arch: "32-bit"
|
||||||
pi_gen_version: "master"
|
pi_gen_version: "master"
|
||||||
|
release: "trixie"
|
||||||
- arch: "64-bit"
|
- arch: "64-bit"
|
||||||
pi_gen_version: "arm64"
|
pi_gen_version: "arm64"
|
||||||
|
release: "trixie"
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -27,9 +29,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Build RaspAP Image
|
- name: Build RaspAP Image
|
||||||
id: build
|
id: build
|
||||||
uses: usimd/pi-gen-action@v1
|
uses: RaspAP/pi-gen-action@v1.11.0
|
||||||
with:
|
with:
|
||||||
image-name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}"
|
release: ${{ matrix.release }}
|
||||||
|
image-name: "raspap-${{ matrix.release }}-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}"
|
||||||
enable-ssh: 1
|
enable-ssh: 1
|
||||||
stage-list: stage0 stage1 stage2 ./stage-raspap
|
stage-list: stage0 stage1 stage2 ./stage-raspap
|
||||||
verbose-output: true
|
verbose-output: true
|
||||||
@@ -39,8 +42,20 @@ jobs:
|
|||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
asset_name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}.img.zip"
|
asset_name: "raspap-${{ matrix.release }}-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}.img.zip"
|
||||||
file: ${{ steps.build.outputs.image-path }}
|
file: ${{ steps.build.outputs.image-path }}
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.event.inputs.tag || github.ref }}
|
tag: ${{ github.event.inputs.tag || github.ref }}
|
||||||
overwrite: true
|
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
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,7 @@ yarn-error.log
|
|||||||
*.swp
|
*.swp
|
||||||
includes/config.php
|
includes/config.php
|
||||||
rootCA.pem
|
rootCA.pem
|
||||||
vendor
|
|
||||||
.env
|
.env
|
||||||
locale/**/*.mo
|
locale/**/*.mo
|
||||||
app/net_activity
|
app/net_activity
|
||||||
|
app/js/plugins/
|
||||||
|
|||||||
39
BACKERS.md
39
BACKERS.md
@@ -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.
|
**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
|
## 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/)
|
✅ [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)
|
|
||||||
✅ [MAC address cloning](https://docs.raspap.com/net-devices/#changing-the-mac-address)
|
✅ [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)
|
✅ [Multiple WireGuard configs](https://docs.raspap.com/wireguard/#multiple-configs)
|
||||||
✅ [Wireless LAN routing](https://docs.raspap.com/wlanrouting/)
|
✅ [Wireless LAN routing](https://docs.raspap.com/wlanrouting/)
|
||||||
✅ [Custom user avatars](https://docs.raspap.com/authentication/#custom-user-avatars)
|
✅ [Custom user avatars](https://docs.raspap.com/authentication/#custom-user-avatars)
|
||||||
✅ [WiFi repeater mode](https://docs.raspap.com/ap-basics/#wifi-repeater-mode)
|
✅ [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/)
|
✅ [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
|
## 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.
|
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
|
### $1,500 - 3rd Insiders Edition
|
||||||
The second **Insiders Edition** includes the features listed above.
|
The **3rd Insiders Edition** includes the exclusive features listed above.
|
||||||
|
|
||||||
### $500
|
### $500 - 1st Insiders Edition (completed)
|
||||||
The [first Insiders Edition goal](https://docs.raspap.com/insiders/#500-1st-insiders-edition) was reached in December 2021. Thank you sponsors!
|
✅ 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
|
## 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)
|
[](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)
|
* [GitHub discussions](https://github.com/RaspAP/raspap-webgui/discussions)
|
||||||
* [Discord chat](https://discord.gg/KVAsaAR)
|
* [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/)
|
* [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).
|
If you enjoy using RaspAP and would like to support our work financially, consider becoming an [Insider](https://github.com/sponsors/RaspAP).
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||

|

|
||||||
[](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 [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 has been featured by [PC World](https://www.pcwelt.de/article/1789512/raspberry-pi-als-wlan-router.html), [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).
|
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).
|
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).
|
||||||
|
|
||||||
@@ -139,6 +139,8 @@ RaspAP was originally made for Raspbian, but now also installs on the following
|
|||||||
|
|
||||||
| Distribution | Release | Architecture | Support |
|
| Distribution | Release | Architecture | Support |
|
||||||
|---|:---:|:---:|:---:|
|
|---|:---:|:---:|:---:|
|
||||||
|
| Raspberry Pi OS | (64-bit) Lite Trixie | ARM | Official |
|
||||||
|
| Raspberry Pi OS | (32-bit) Lite Trixie | ARM | Official |
|
||||||
| Raspberry Pi OS | (64-bit) Lite Bookworm | ARM | Official |
|
| Raspberry Pi OS | (64-bit) Lite Bookworm | ARM | Official |
|
||||||
| Raspberry Pi OS | (32-bit) Lite Bookworm | ARM | Official |
|
| Raspberry Pi OS | (32-bit) Lite Bookworm | ARM | Official |
|
||||||
| Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official |
|
| Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official |
|
||||||
|
|||||||
@@ -5,50 +5,57 @@ require_once '../../includes/session.php';
|
|||||||
require_once '../../includes/config.php';
|
require_once '../../includes/config.php';
|
||||||
require_once '../../includes/authenticate.php';
|
require_once '../../includes/authenticate.php';
|
||||||
|
|
||||||
|
define('BLOCKLISTS_FILE', __DIR__ . '/../../config/blocklists.json');
|
||||||
|
|
||||||
if (isset($_POST['blocklist_id'])) {
|
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) {
|
if ($allLists === null) {
|
||||||
case "StevenBlack/hosts \(default\)":
|
echo json_encode([
|
||||||
$list_url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
|
'return' => 3,
|
||||||
$dest_file = "hostnames.txt";
|
'output' => ['Failed to parse blocklists.json']
|
||||||
break;
|
]);
|
||||||
case "badmojr/1Hosts \(Mini\)":
|
exit;
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
$blocklist = $list_url . $dest_file;
|
$flatList = flattenList($allLists);
|
||||||
$dest = substr($dest_file, 0, strrpos($dest_file, "."));
|
|
||||||
|
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 {
|
} else {
|
||||||
$jsonData = ['return'=>2,'output'=>['Error getting data']];
|
echo json_encode(['return' => 2, 'output' => ['No blocklist ID provided']]);
|
||||||
echo json_encode($jsonData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flattenList(array $grouped): array {
|
||||||
|
$flat = [];
|
||||||
|
foreach ($grouped as $group) {
|
||||||
|
foreach ($group as $name => $meta) {
|
||||||
|
$flat[$name] = $meta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use RaspAP\Networking\Hotspot\DhcpcdManager;
|
||||||
|
|
||||||
require_once '../../includes/autoload.php';
|
require_once '../../includes/autoload.php';
|
||||||
require_once '../../includes/CSRF.php';
|
require_once '../../includes/CSRF.php';
|
||||||
require_once '../../includes/session.php';
|
require_once '../../includes/session.php';
|
||||||
@@ -6,63 +9,11 @@ require_once '../../includes/config.php';
|
|||||||
require_once '../../includes/authenticate.php';
|
require_once '../../includes/authenticate.php';
|
||||||
require_once '../../includes/functions.php';
|
require_once '../../includes/functions.php';
|
||||||
|
|
||||||
|
$dhcpcdManager = new DhcpcdManager();
|
||||||
|
|
||||||
$interface = $_POST['iface'];
|
$interface = $_POST['iface'];
|
||||||
|
|
||||||
if (isset($interface)) {
|
if (isset($interface)) {
|
||||||
// fetch dnsmasq.conf settings for interface
|
$dhcpdata = $dhcpcdManager->getInterfaceConfig($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";
|
|
||||||
echo json_encode($dhcpdata);
|
echo json_encode($dhcpdata);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ require_once '../../includes/session.php';
|
|||||||
require_once '../../includes/config.php';
|
require_once '../../includes/config.php';
|
||||||
require_once '../../includes/authenticate.php';
|
require_once '../../includes/authenticate.php';
|
||||||
|
|
||||||
$entity = escapeshellcmd($_POST['entity']);
|
$entity = escapeshellarg($_POST['entity']);
|
||||||
|
|
||||||
if (isset($entity)) {
|
if (isset($entity)) {
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,21 @@ require_once '../../includes/config.php';
|
|||||||
require_once '../../includes/authenticate.php';
|
require_once '../../includes/authenticate.php';
|
||||||
require_once '../../includes/defaults.php';
|
require_once '../../includes/defaults.php';
|
||||||
require_once '../../includes/functions.php';
|
require_once '../../includes/functions.php';
|
||||||
require_once '../../includes/wifi_functions.php';
|
|
||||||
|
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||||
|
|
||||||
|
$wifi = new WiFiManager();
|
||||||
|
|
||||||
$networks = [];
|
$networks = [];
|
||||||
$network = null;
|
$network = null;
|
||||||
$ssid = null;
|
$ssid = null;
|
||||||
|
|
||||||
knownWifiStations($networks);
|
$wifi->knownWifiStations($networks);
|
||||||
nearbyWifiStations($networks, !isset($_REQUEST["refresh"]));
|
$wifi->nearbyWifiStations($networks, !isset($_REQUEST["refresh"]));
|
||||||
connectedWifiStations($networks);
|
$wifi->connectedWifiStations($networks);
|
||||||
sortNetworksByRSSI($networks);
|
$wifi->sortNetworksByRSSI($networks);
|
||||||
foreach ($networks as $ssid => $network) $networks[$ssid]["ssidutf8"] = ssid2utf8( $ssid );
|
|
||||||
|
foreach ($networks as $ssid => $network) $networks[$ssid]["ssidutf8"] = $wifi->ssid2utf8( $ssid );
|
||||||
|
|
||||||
$connected = array_filter($networks, function($n) { return $n['connected']; } );
|
$connected = array_filter($networks, function($n) { return $n['connected']; } );
|
||||||
$known = array_filter($networks, function($n) { return !$n['connected'] && $n['configured']; } );
|
$known = array_filter($networks, function($n) { return !$n['connected'] && $n['configured']; } );
|
||||||
|
|||||||
@@ -48,8 +48,15 @@ th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-logo {
|
.navbar-logo {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.2em;
|
||||||
margin-left: 0.5em;
|
margin-left: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
margin-left: 1.2rem;
|
||||||
|
margin-bottom: -0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
@@ -130,7 +137,7 @@ th {
|
|||||||
|
|
||||||
.loading-spinner::before {
|
.loading-spinner::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 120px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100vh / 4);
|
height: calc(100vh / 4);
|
||||||
@@ -138,10 +145,10 @@ th {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--raspap-text-muted);
|
color: var(--raspap-text-muted);
|
||||||
content: "\f1ce"; /* Unicode for the circle-notch icon */
|
content: "\f1ce";
|
||||||
font-family: "Font Awesome 5 Free";
|
font-family: "Font Awesome 5 Free";
|
||||||
font-weight: 900; /* Adjust as needed */
|
font-weight: 900;
|
||||||
font-size: 54px; /* Adjust icon size as needed */
|
font-size: 54px;
|
||||||
animation: spin 1.2s linear infinite;
|
animation: spin 1.2s linear infinite;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once '../../includes/functions.php';
|
require_once '../../includes/functions.php';
|
||||||
$color = getColorOpt();
|
$color = getColorOpt();
|
||||||
|
$allCss = 'all.css';
|
||||||
?>
|
?>
|
||||||
/*
|
/*
|
||||||
Theme Name: RaspAP default
|
Theme Name: RaspAP default
|
||||||
@@ -11,12 +12,12 @@ Description: Default theme for RaspAP
|
|||||||
License: GNU General Public License v3.0
|
License: GNU General Public License v3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import url('all.css');
|
@import url('<?= $allCss ?>?v=<?= filemtime($allCss); ?>');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--raspap-theme-color: <?php echo $color; ?>;
|
--raspap-theme-color: <?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;
|
||||||
--raspap-theme-lighter: <?php echo lightenColor($color, 20); ?>;
|
--raspap-theme-lighter: <?php echo htmlspecialchars(lightenColor($color, 20), ENT_QUOTES, 'UTF-8'); ?>;
|
||||||
--raspap-theme-darker: <?php echo darkenColor($color, 20); ?>;
|
--raspap-theme-darker: <?php echo htmlspecialchars(darkenColor($color, 20), ENT_QUOTES, 'UTF-8'); ?>;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -8,78 +8,78 @@ $color = getColorOpt();
|
|||||||
viewBox="0 0 291.5 203.2" style="enable-background:new 0 0 291.5 203.2;" xml:space="preserve">
|
viewBox="0 0 291.5 203.2" style="enable-background:new 0 0 291.5 203.2;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{clip-path:url(#SVGID_2_);}
|
.st0{clip-path:url(#SVGID_2_);}
|
||||||
.st1{clip-path:url(#SVGID_4_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st1{clip-path:url(#SVGID_4_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st2{clip-path:url(#SVGID_6_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st2{clip-path:url(#SVGID_6_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st3{clip-path:url(#SVGID_8_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st3{clip-path:url(#SVGID_8_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st4{clip-path:url(#SVGID_10_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st4{clip-path:url(#SVGID_10_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st5{clip-path:url(#SVGID_12_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st5{clip-path:url(#SVGID_12_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st6{clip-path:url(#SVGID_14_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st6{clip-path:url(#SVGID_14_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st7{clip-path:url(#SVGID_16_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st7{clip-path:url(#SVGID_16_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st8{clip-path:url(#SVGID_18_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st8{clip-path:url(#SVGID_18_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st9{clip-path:url(#SVGID_20_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st9{clip-path:url(#SVGID_20_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st10{clip-path:url(#SVGID_22_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st10{clip-path:url(#SVGID_22_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st11{clip-path:url(#SVGID_24_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st11{clip-path:url(#SVGID_24_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st12{clip-path:url(#SVGID_26_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st12{clip-path:url(#SVGID_26_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st13{clip-path:url(#SVGID_28_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st13{clip-path:url(#SVGID_28_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st14{clip-path:url(#SVGID_30_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st14{clip-path:url(#SVGID_30_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st15{clip-path:url(#SVGID_32_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st15{clip-path:url(#SVGID_32_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st16{clip-path:url(#SVGID_34_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st16{clip-path:url(#SVGID_34_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st17{clip-path:url(#SVGID_36_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st17{clip-path:url(#SVGID_36_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st18{clip-path:url(#SVGID_38_);}
|
.st18{clip-path:url(#SVGID_38_);}
|
||||||
.st19{clip-path:url(#SVGID_40_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st19{clip-path:url(#SVGID_40_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st20{clip-path:url(#SVGID_42_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st20{clip-path:url(#SVGID_42_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st21{clip-path:url(#SVGID_44_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st21{clip-path:url(#SVGID_44_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st22{clip-path:url(#SVGID_46_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st22{clip-path:url(#SVGID_46_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st23{clip-path:url(#SVGID_48_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st23{clip-path:url(#SVGID_48_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st24{clip-path:url(#SVGID_50_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st24{clip-path:url(#SVGID_50_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st25{clip-path:url(#SVGID_52_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st25{clip-path:url(#SVGID_52_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st26{clip-path:url(#SVGID_54_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st26{clip-path:url(#SVGID_54_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st27{clip-path:url(#SVGID_56_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st27{clip-path:url(#SVGID_56_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st28{clip-path:url(#SVGID_58_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st28{clip-path:url(#SVGID_58_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st29{clip-path:url(#SVGID_60_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st29{clip-path:url(#SVGID_60_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st30{clip-path:url(#SVGID_62_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st30{clip-path:url(#SVGID_62_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st31{clip-path:url(#SVGID_64_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st31{clip-path:url(#SVGID_64_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st32{clip-path:url(#SVGID_66_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st32{clip-path:url(#SVGID_66_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st33{clip-path:url(#SVGID_68_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st33{clip-path:url(#SVGID_68_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st34{clip-path:url(#SVGID_70_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st34{clip-path:url(#SVGID_70_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st35{clip-path:url(#SVGID_72_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st35{clip-path:url(#SVGID_72_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st36{clip-path:url(#SVGID_74_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st36{clip-path:url(#SVGID_74_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st37{clip-path:url(#SVGID_76_);}
|
.st37{clip-path:url(#SVGID_76_);}
|
||||||
.st38{clip-path:url(#SVGID_78_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st38{clip-path:url(#SVGID_78_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st39{clip-path:url(#SVGID_80_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st39{clip-path:url(#SVGID_80_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st40{clip-path:url(#SVGID_82_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st40{clip-path:url(#SVGID_82_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st41{clip-path:url(#SVGID_84_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st41{clip-path:url(#SVGID_84_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st42{clip-path:url(#SVGID_86_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st42{clip-path:url(#SVGID_86_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st43{clip-path:url(#SVGID_88_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st43{clip-path:url(#SVGID_88_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st44{clip-path:url(#SVGID_90_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st44{clip-path:url(#SVGID_90_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st45{clip-path:url(#SVGID_92_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st45{clip-path:url(#SVGID_92_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st46{clip-path:url(#SVGID_94_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st46{clip-path:url(#SVGID_94_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st47{clip-path:url(#SVGID_96_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st47{clip-path:url(#SVGID_96_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st48{clip-path:url(#SVGID_98_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st48{clip-path:url(#SVGID_98_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st49{clip-path:url(#SVGID_100_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st49{clip-path:url(#SVGID_100_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st50{clip-path:url(#SVGID_102_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st50{clip-path:url(#SVGID_102_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st51{clip-path:url(#SVGID_104_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st51{clip-path:url(#SVGID_104_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st52{clip-path:url(#SVGID_106_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st52{clip-path:url(#SVGID_106_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st53{clip-path:url(#SVGID_108_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st53{clip-path:url(#SVGID_108_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st54{clip-path:url(#SVGID_110_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st54{clip-path:url(#SVGID_110_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st55{clip-path:url(#SVGID_112_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st55{clip-path:url(#SVGID_112_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st56{clip-path:url(#SVGID_114_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st56{clip-path:url(#SVGID_114_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st57{clip-path:url(#SVGID_116_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st57{clip-path:url(#SVGID_116_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st58{clip-path:url(#SVGID_118_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st58{clip-path:url(#SVGID_118_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st59{clip-path:url(#SVGID_120_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st59{clip-path:url(#SVGID_120_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st60{clip-path:url(#SVGID_122_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st60{clip-path:url(#SVGID_122_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st61{clip-path:url(#SVGID_124_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st61{clip-path:url(#SVGID_124_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st62{clip-path:url(#SVGID_126_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st62{clip-path:url(#SVGID_126_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st63{clip-path:url(#SVGID_128_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st63{clip-path:url(#SVGID_128_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st64{clip-path:url(#SVGID_130_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st64{clip-path:url(#SVGID_130_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st65{clip-path:url(#SVGID_132_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st65{clip-path:url(#SVGID_132_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st66{clip-path:url(#SVGID_134_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st66{clip-path:url(#SVGID_134_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st67{clip-path:url(#SVGID_136_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st67{clip-path:url(#SVGID_136_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st68{clip-path:url(#SVGID_138_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st68{clip-path:url(#SVGID_138_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st69{clip-path:url(#SVGID_140_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st69{clip-path:url(#SVGID_140_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st70{clip-path:url(#SVGID_142_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st70{clip-path:url(#SVGID_142_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st71{clip-path:url(#SVGID_144_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st71{clip-path:url(#SVGID_144_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
.st72{clip-path:url(#SVGID_146_);fill:none;stroke:<?php echo $color; ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
.st72{clip-path:url(#SVGID_146_);fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
</style>
|
</style>
|
||||||
<g>
|
<g>
|
||||||
<g>
|
<g>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ $color = getColorOpt();
|
|||||||
viewBox="0 0 431 321" style="enable-background:new 0 0 431 321;" xml:space="preserve">
|
viewBox="0 0 431 321" style="enable-background:new 0 0 431 321;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:none;enable-background:new;}
|
.st0{fill:none;enable-background:new;}
|
||||||
.st1{fill:none;stroke:<?php echo $color; ?>;stroke-width:0.9453;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:13.3333;}
|
.st1{fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.9453;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:13.3333;}
|
||||||
|
|
||||||
.st2{fill:none;stroke:<?php echo $color; ?>;stroke-width:0.9453;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:13.3333;enable-background:new ;}
|
.st2{fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.9453;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:13.3333;enable-background:new ;}
|
||||||
</style>
|
</style>
|
||||||
<path class="st0" d="M0,0"/>
|
<path class="st0" d="M0,0"/>
|
||||||
<g id="g20028">
|
<g id="g20028">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ $color = getColorOpt();
|
|||||||
<svg version="1.1" id="Zero_BLANK" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Zero_BLANK" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
viewBox="0 0 218.03 144.11" style="enable-background:new 0 0 218.03 144.11;" xml:space="preserve">
|
viewBox="0 0 218.03 144.11" style="enable-background:new 0 0 218.03 144.11;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:none;stroke:<?php echo $color; ?>;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
.st0{fill:none;stroke:<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||||
</style>
|
</style>
|
||||||
<g id="LINE_1238_">
|
<g id="LINE_1238_">
|
||||||
<line class="st0" x1="96.57" y1="105.12" x2="97.06" y2="105.29"/>
|
<line class="st0" x1="96.57" y1="105.12" x2="97.06" y2="105.29"/>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<?php header("Content-Type: image/svg+xml; charset=utf-8"); ?>
|
<?php header("Content-Type: image/svg+xml; charset=utf-8"); ?>
|
||||||
<?php
|
<?php
|
||||||
|
require_once '../../includes/config.php';
|
||||||
require_once '../../includes/functions.php';
|
require_once '../../includes/functions.php';
|
||||||
$color = getColorOpt();
|
$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"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
@@ -10,42 +13,41 @@ $color = getColorOpt();
|
|||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 490.66666 487.11066"
|
viewBox="0 180 352 290"
|
||||||
height="487.11066"
|
|
||||||
width="490.66666"
|
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
id="svg2"
|
id="svg2"
|
||||||
version="1.1"><metadata
|
version="1.1">
|
||||||
id="metadata8"><rdf:RDF><cc:Work
|
<style>
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
<?php if (!$static): ?>
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
.wave {
|
||||||
id="defs6"><clipPath
|
opacity: 0.4;
|
||||||
id="clipPath18"
|
animation: pulse 1.8s infinite;
|
||||||
clipPathUnits="userSpaceOnUse"><path
|
}
|
||||||
id="path16"
|
.wave1 { animation-delay: 0.3s; }
|
||||||
d="M 0,365.333 H 368 V 0 H 0 Z" /></clipPath></defs><g
|
.wave2 { animation-delay: 0.6s; }
|
||||||
transform="matrix(1.3333333,0,0,-1.3333333,0,487.11067)"
|
|
||||||
id="g10"><g
|
@keyframes pulse {
|
||||||
id="g12"><g
|
0% { opacity: 0.4; }
|
||||||
clip-path="url(#clipPath18)"
|
20% { opacity: 1; }
|
||||||
id="g14"><g
|
60% { opacity: 0.4; }
|
||||||
transform="translate(192.6768,123.4365)"
|
100% { opacity: 0.4; }
|
||||||
id="g20"><path
|
}
|
||||||
id="path22"
|
<?php else: ?>
|
||||||
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
.wave {
|
||||||
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
|
opacity: 1.0;
|
||||||
transform="translate(125.3823,219.0791)"
|
}
|
||||||
id="g24"><path
|
<?php endif; ?>
|
||||||
id="path26"
|
</style>
|
||||||
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
|
<!-- inner solid circle -->
|
||||||
transform="translate(144.4277,271.9385)"
|
<circle cx="128" cy="384" r="60" fill="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>"/>
|
||||||
id="g28"><path
|
|
||||||
id="path30"
|
<!-- outer ring -->
|
||||||
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
<circle cx="128" cy="384" r="100" fill="none" stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="25"/>
|
||||||
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)"
|
<!-- arcs -->
|
||||||
id="g32"><path
|
<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"/>
|
||||||
id="path34"
|
<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"/>
|
||||||
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>
|
</svg>
|
||||||
|
|
||||||
|
|||||||
@@ -42,24 +42,24 @@ if ($showJoint) {
|
|||||||
for ($i = 1; $i < count($activeYs); $i++) {
|
for ($i = 1; $i < count($activeYs); $i++) {
|
||||||
$y1 = $activeYs[$i-1];
|
$y1 = $activeYs[$i-1];
|
||||||
$y2 = $activeYs[$i];
|
$y2 = $activeYs[$i];
|
||||||
echo "<line x1='112.75' y1='$y1' x2='112.75' y2='$y2' stroke='$color' stroke-width='4'/>";
|
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): ?>
|
<?php if ($showDevice1): ?>
|
||||||
<line x1="113.231" y1="0.75" x2="7.69496e-06" y2="0.75001" stroke="<?php echo $color; ?>" stroke-width="6" id="device-1"/>
|
<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 endif; ?>
|
||||||
<?php if ($showOut): ?>
|
<?php if ($showOut): ?>
|
||||||
<line x1="226.231" y1="297.75" x2="113" y2="297.75" stroke="<?php echo $color; ?>" stroke-width="4" id="out"/>
|
<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 endif; ?>
|
||||||
<?php if ($showDevice2): ?>
|
<?php if ($showDevice2): ?>
|
||||||
<line x1="113.231" y1="198.75" x2="7.69496e-06" y2="198.75" stroke="<?php echo $color; ?>" stroke-width="4" id="device-2"/>
|
<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 endif; ?>
|
||||||
<?php if ($showDevice3): ?>
|
<?php if ($showDevice3): ?>
|
||||||
<line x1="113.231" y1="397.058" x2="7.69496e-06" y2="397.058" stroke="<?php echo $color; ?>" stroke-width="4" id="device-3"/>
|
<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 endif; ?>
|
||||||
<?php if ($showDevice4): ?>
|
<?php if ($showDevice4): ?>
|
||||||
<line x1="113.231" y1="595.211" x2="7.69496e-06" y2="595.211" stroke="<?php echo $color; ?>" stroke-width="4" id="device-4"/>
|
<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; ?>
|
<?php endif; ?>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,21 +1,3 @@
|
|||||||
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-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 loadSummary(strInterface) {
|
function loadSummary(strInterface) {
|
||||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||||
@@ -38,94 +20,6 @@ function getAllInterfaces() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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("#","");
|
|
||||||
loadCurrentSettings(int);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(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());
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on("click", "#js-clearhostapd-log", function(e) {
|
$(document).on("click", "#js-clearhostapd-log", function(e) {
|
||||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||||
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log', 'csrf_token': csrfToken},function(data){
|
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log', 'csrf_token': csrfToken},function(data){
|
||||||
@@ -150,54 +44,6 @@ $(document).on("click", "#js-clearopenvpn-log", function(e) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 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 loadWifiStations(refresh) {
|
function loadWifiStations(refresh) {
|
||||||
return function() {
|
return function() {
|
||||||
var complete = function() { $(this).removeClass('loading-spinner'); }
|
var complete = function() { $(this).removeClass('loading-spinner'); }
|
||||||
@@ -248,35 +94,43 @@ function loadInterfaceDHCPSelect() {
|
|||||||
$('#dhcp-iface').removeAttr('disabled');
|
$('#dhcp-iface').removeAttr('disabled');
|
||||||
} else {
|
} else {
|
||||||
$('#chkdhcp').closest('.btn').addClass('active');
|
$('#chkdhcp').closest('.btn').addClass('active');
|
||||||
$('#chkdhcp').closest('.btn').button.blur();
|
$('#chkdhcp').closest('.btn').blur();
|
||||||
}
|
}
|
||||||
if (jsonData.FallbackEnabled || $('#chkdhcp').is(':checked')) {
|
if (jsonData.FallbackEnabled || $('#chkdhcp').is(':checked')) {
|
||||||
$('#dhcp-iface').prop('disabled', true);
|
$('#dhcp-iface').prop('disabled', true);
|
||||||
setDhcpFieldsDisabled();
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#debugModal').on('shown.bs.modal', function (e) {
|
$('#debugModal').on('shown.bs.modal', function (e) {
|
||||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||||
$.post('ajax/system/sys_debug.php',{'csrf_token': csrfToken},function(data){
|
$.post('ajax/system/sys_debug.php',{'csrf_token': csrfToken},function(data){
|
||||||
@@ -327,10 +181,6 @@ $('#performUpdate').on('submit', function(event) {
|
|||||||
$('#performupdateModal').modal('show');
|
$('#performupdateModal').modal('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#performupdateModal').on('shown.bs.modal', function (e) {
|
|
||||||
fetchUpdateResponse();
|
|
||||||
});
|
|
||||||
|
|
||||||
function fetchUpdateResponse() {
|
function fetchUpdateResponse() {
|
||||||
const complete = 6;
|
const complete = 6;
|
||||||
const error = 7;
|
const error = 7;
|
||||||
@@ -372,22 +222,6 @@ function 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-delete').on('click', '.btn-delete', function (e) {
|
$('#ovpn-confirm-delete').on('click', '.btn-delete', function (e) {
|
||||||
var cfg_id = $(this).data('recordId');
|
var cfg_id = $(this).data('recordId');
|
||||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||||
@@ -418,21 +252,6 @@ $('#ovpn-confirm-activate').on('click', '.btn-activate', 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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#js-system-reset-confirm').on('click', function (e) {
|
$('#js-system-reset-confirm').on('click', function (e) {
|
||||||
var progressText = $('#js-system-reset-confirm').attr('data-message');
|
var progressText = $('#js-system-reset-confirm').attr('data-message');
|
||||||
var successHtml = $('#system-reset-message').attr('data-message');
|
var successHtml = $('#system-reset-message').attr('data-message');
|
||||||
@@ -463,49 +282,6 @@ $('#js-sys-reboot, #js-sys-shutdown').on('click', function (e) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#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-confirm').on('click', function (e) {
|
$('#js-install-plugin-confirm').on('click', function (e) {
|
||||||
var button = $('#install-user-plugin').data('button');
|
var button = $('#install-user-plugin').data('button');
|
||||||
var manifestData = button.data('plugin-manifest');
|
var manifestData = button.data('plugin-manifest');
|
||||||
@@ -572,75 +348,7 @@ $('#js-install-plugin-confirm').on('click', function (e) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#js-install-plugin-ok').on('click', function (e) {
|
// Retrieves the 'channel' value specified in hostapd.conf
|
||||||
$("#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: "___.___.___.___"
|
|
||||||
});
|
|
||||||
$('.date').mask('FF:FF:FF:FF:FF:FF', {
|
|
||||||
translation: {
|
|
||||||
"F": {
|
|
||||||
pattern: /[0-9a-z]/, optional: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retrieves the 'channel' value specified in hostapd.conf
|
|
||||||
function getChannel() {
|
function getChannel() {
|
||||||
$.get('ajax/networking/get_channel.php',function(data){
|
$.get('ajax/networking/get_channel.php',function(data){
|
||||||
jsonData = JSON.parse(data);
|
jsonData = JSON.parse(data);
|
||||||
@@ -730,20 +438,65 @@ function setHardwareModeTooltip() {
|
|||||||
* Interface elements are updated to indicate current progress, status.
|
* Interface elements are updated to indicate current progress, status.
|
||||||
*/
|
*/
|
||||||
function updateBlocklist() {
|
function updateBlocklist() {
|
||||||
var opt = $('#cbxblocklist option:selected');
|
const opt = $('#cbxblocklist option:selected');
|
||||||
var blocklist_id = opt.val();
|
const blocklist_id = opt.val();
|
||||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
const csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||||
if (blocklist_id == '') { return; }
|
|
||||||
$('#cbxblocklist-status').find('i').removeClass('fas fa-check').addClass('fas fa-cog fa-spin');
|
if (blocklist_id === '') return;
|
||||||
$('#cbxblocklist-status').removeClass('check-hidden').addClass('check-progress');
|
|
||||||
$.post('ajax/adblock/update_blocklist.php',{ 'blocklist_id':blocklist_id, 'csrf_token': csrfToken},function(data){
|
const statusIcon = $('#cbxblocklist-status').find('i');
|
||||||
var jsonData = JSON.parse(data);
|
const statusWrapper = $('#cbxblocklist-status');
|
||||||
if (jsonData['return'] == '0') {
|
|
||||||
$('#cbxblocklist-status').find('i').removeClass('fas fa-cog fa-spin').addClass('fas fa-check');
|
statusIcon.removeClass('fa-check fa-exclamation-triangle').addClass('fa-cog fa-spin');
|
||||||
$('#cbxblocklist-status').removeClass('check-progress').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
|
statusWrapper.removeClass('check-hidden check-error check-updated').addClass('check-progress');
|
||||||
$('#blocklist-'+jsonData['list']).text("Just now");
|
|
||||||
|
$.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() {
|
function clearBlocklistStatus() {
|
||||||
@@ -782,22 +535,6 @@ $('.wg-client-dl').click(function(){
|
|||||||
req.send();
|
req.send();
|
||||||
})
|
})
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
let sessionCheckInterval = setInterval(checkSession, 5000);
|
let sessionCheckInterval = setInterval(checkSession, 5000);
|
||||||
|
|
||||||
function checkSession() {
|
function checkSession() {
|
||||||
@@ -816,249 +553,3 @@ function checkSession() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
|
||||||
7
app/js/bandwidthcharts.min.js
vendored
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);
|
|
||||||
7
app/js/custom.min.js
vendored
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
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
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
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",
|
"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" )
|
url.rewrite-once = ( "^/REPLACE_ME/(.*?)(\?.+)?$"=>"/REPLACE_ME/index.php/$1$2" )
|
||||||
server.error-handler-404 = "/REPLACE_ME/index.php"
|
server.error-handler-404 = "/REPLACE_ME/index.php"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,45 @@
|
|||||||
{
|
{
|
||||||
"StevenBlack/hosts": [
|
"StevenBlack/hosts": {
|
||||||
"StevenBlack/hosts (default)"
|
"StevenBlack/hosts (default)": {
|
||||||
],
|
"list_url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||||
"badmojr/hosts": [
|
"dest_file": "hostnames.txt"
|
||||||
"badmojr/1Hosts (Mini)",
|
}
|
||||||
"badmojr/1Hosts (Lite)",
|
},
|
||||||
"badmojr/1Hosts (Pro)",
|
"HaGeZi/hosts": {
|
||||||
"badmojr/1Hosts (Xtra)"
|
"hagezi/hosts (Light)": {
|
||||||
],
|
"list_url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/hosts/light.txt",
|
||||||
"OISD/domains": [
|
"dest_file": "hostnames.txt"
|
||||||
"oisd/big (default)",
|
},
|
||||||
"oisd/small",
|
"hagezi/hosts (Normal)": {
|
||||||
"oisd/nsfw"
|
"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_OPENVPN_CLIENT_LOGIN', '/etc/openvpn/client/login.conf');
|
||||||
define('RASPI_WIREGUARD_PATH', '/etc/wireguard/');
|
define('RASPI_WIREGUARD_PATH', '/etc/wireguard/');
|
||||||
define('RASPI_WIREGUARD_CONFIG', RASPI_WIREGUARD_PATH.'wg0.conf');
|
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_TORPROXY_CONFIG', '/etc/tor/torrc');
|
||||||
define('RASPI_LIGHTTPD_CONFIG', '/etc/lighttpd/lighttpd.conf');
|
define('RASPI_LIGHTTPD_CONFIG', '/etc/lighttpd/lighttpd.conf');
|
||||||
define('RASPI_ACCESS_CHECK_IP', '1.1.1.1');
|
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
|
// Constant for the GitHub API latest release endpoint
|
||||||
define('RASPI_API_ENDPOINT', 'https://api.github.com/repos/RaspAP/raspap-webgui/releases/latest');
|
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
|
// Constant for the 5GHz wireless regulatory domain
|
||||||
define("RASPI_5GHZ_CHANNEL_MIN", 100);
|
define("RASPI_5GHZ_CHANNEL_MIN", 100);
|
||||||
define("RASPI_5GHZ_CHANNEL_MAX", 192);
|
define("RASPI_5GHZ_CHANNEL_MAX", 192);
|
||||||
@@ -64,6 +69,7 @@ define('RASPI_SYSTEM_ENABLED', true);
|
|||||||
define('RASPI_MONITOR_ENABLED', false);
|
define('RASPI_MONITOR_ENABLED', false);
|
||||||
define('RASPI_RESTAPI_ENABLED', false);
|
define('RASPI_RESTAPI_ENABLED', false);
|
||||||
define('RASPI_PLUGINS_ENABLED', true);
|
define('RASPI_PLUGINS_ENABLED', true);
|
||||||
|
define('RASPI_UI_STATIC_LOGO', false);
|
||||||
|
|
||||||
// Locale settings
|
// Locale settings
|
||||||
define('LOCALE_ROOT', 'locale');
|
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": {
|
"dhcp": {
|
||||||
"wlan0": {
|
"wlan0": {
|
||||||
"static ip_address": [ "10.3.141.1/24" ],
|
"static ip_address": [ "10.3.141.1" ],
|
||||||
"static routers": [ "10.3.141.1" ],
|
"static routers": [ "10.3.141.1" ],
|
||||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||||
"subnetmask": [ "255.255.255.0" ]
|
"subnetmask": [ "255.255.255.0" ]
|
||||||
},
|
},
|
||||||
"wlan1": {
|
"wlan1": {
|
||||||
"static ip_address": [ "10.9.141.1/24" ],
|
"static ip_address": [ "10.9.141.1" ],
|
||||||
"static routers": [ "10.9.141.1" ],
|
"static routers": [ "10.9.141.1" ],
|
||||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||||
"subnetmask": [ "255.255.255.0" ]
|
"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": {
|
"uap0": {
|
||||||
"static ip_address": [ "192.168.50.1/24" ],
|
"static ip_address": ["192.168.50.1" ],
|
||||||
"static routers": [ "192.168.50.1" ],
|
"static routers": [ "192.168.50.1" ],
|
||||||
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
|
||||||
"subnetmask": [ "255.255.255.0" ]
|
"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": {
|
"options": {
|
||||||
"# RaspAP default configuration": null,
|
"# RaspAP default configuration": null,
|
||||||
"hostname": null,
|
"hostname": null,
|
||||||
@@ -39,8 +103,17 @@
|
|||||||
"wlan1": {
|
"wlan1": {
|
||||||
"dhcp-range": [ "10.9.141.50,10.9.141.254,255.255.255.0,12h" ]
|
"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": {
|
"uap0": {
|
||||||
"dhcp-range": [ "192.168.50.50,192.168.50.150,12h" ]
|
"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": {
|
"wireguard": {
|
||||||
@@ -48,14 +121,16 @@
|
|||||||
"Address": [ "10.8.2.1/24" ],
|
"Address": [ "10.8.2.1/24" ],
|
||||||
"ListenPort": [ "51820" ],
|
"ListenPort": [ "51820" ],
|
||||||
"DNS": [ "9.9.9.9" ],
|
"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" ],
|
"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 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" ]
|
"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": {
|
"peer": {
|
||||||
"Address": [ "10.8.1.2/24" ],
|
"Address": [ "10.8.1.2/24" ],
|
||||||
"Endpoint": [ "10.8.2.1:51820" ],
|
"Endpoint": [ "10.8.2.1:51820" ],
|
||||||
"ListenPort": [ "21841" ],
|
"ListenPort": [ "21841" ],
|
||||||
"AllowedIPs": ["10.8.2.0/24"],
|
"AllowedIPs": [ "10.8.2.0/24" ],
|
||||||
"PersistentKeepalive": [ "15" ]
|
"PersistentKeepalive": [ "15" ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
BIN
dist/raspap/css/fonts/RaspAP.eot
vendored
Binary file not shown.
1
dist/raspap/css/fonts/RaspAP.svg
vendored
1
dist/raspap/css/fonts/RaspAP.svg
vendored
@@ -11,4 +11,5 @@
|
|||||||
<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="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="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="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>
|
</font></defs></svg>
|
||||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 9.4 KiB |
BIN
dist/raspap/css/fonts/RaspAP.ttf
vendored
BIN
dist/raspap/css/fonts/RaspAP.ttf
vendored
Binary file not shown.
BIN
dist/raspap/css/fonts/RaspAP.woff
vendored
BIN
dist/raspap/css/fonts/RaspAP.woff
vendored
Binary file not shown.
34
dist/raspap/css/style.css
vendored
34
dist/raspap/css/style.css
vendored
@@ -1,16 +1,15 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'RaspAP';
|
font-family: 'RaspAP';
|
||||||
src: url('fonts/RaspAP.eot?3vjloy');
|
src: url('fonts/RaspAP.eot?8h3d6d');
|
||||||
src: url('fonts/RaspAP.eot?3vjloy#iefix') format('embedded-opentype'),
|
src: url('fonts/RaspAP.eot?8h3d6d#iefix') format('embedded-opentype'),
|
||||||
url('fonts/RaspAP.ttf?3vjloy') format('truetype'),
|
url('fonts/RaspAP.ttf?8h3d6d') format('truetype'),
|
||||||
url('fonts/RaspAP.woff?3vjloy') format('woff'),
|
url('fonts/RaspAP.woff?8h3d6d') format('woff'),
|
||||||
url('fonts/RaspAP.svg?3vjloy#RaspAP') format('svg');
|
url('fonts/RaspAP.svg?8h3d6d#RaspAP') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[class^="ra-"], [class*=" ra-"] {
|
[class^="ra-"], [class*=" ra-"] {
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
font-family: 'RaspAP' !important;
|
font-family: 'RaspAP' !important;
|
||||||
@@ -26,10 +25,15 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ra-wireshark:before {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
content: "\e904";
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
.ra-wireguard:before {
|
.ra-wireguard:before {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
content: "\e900";
|
content: "\e900";
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
.ra-raspap:before {
|
.ra-raspap:before {
|
||||||
content: "\e901";
|
content: "\e901";
|
||||||
@@ -46,19 +50,3 @@
|
|||||||
content: "\e903";
|
content: "\e903";
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
.card-header .ra-wireguard:before,
|
|
||||||
.card-header .ra-tailscale:before,
|
|
||||||
.card-header .ra-torproxy:before {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar .nav-item.active .nav-link span.ra-wireguard:before,
|
|
||||||
.sidebar .nav-item.active .nav-link span.ra-tailscale:before,
|
|
||||||
.sidebar .nav-item.active .nav-link span.ra-torproxy:before {
|
|
||||||
color: #6e707e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-nav-link-icon .ra-tailscale {
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
6
dist/sb-admin/js/scripts.js
vendored
6
dist/sb-admin/js/scripts.js
vendored
@@ -13,9 +13,9 @@ window.addEventListener('DOMContentLoaded', event => {
|
|||||||
const sidebarToggle = document.body.querySelector('#sidebarToggle');
|
const sidebarToggle = document.body.querySelector('#sidebarToggle');
|
||||||
if (sidebarToggle) {
|
if (sidebarToggle) {
|
||||||
// Uncomment below to persist sidebar toggle between refreshes
|
// Uncomment below to persist sidebar toggle between refreshes
|
||||||
if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
|
// if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
|
||||||
document.body.classList.toggle('sb-sidenav-toggled');
|
// document.body.classList.toggle('sb-sidenav-toggled');
|
||||||
}
|
// }
|
||||||
sidebarToggle.addEventListener('click', event => {
|
sidebarToggle.addEventListener('click', event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
document.body.classList.toggle('sb-sidenav-toggled');
|
document.body.classList.toggle('sb-sidenav-toggled');
|
||||||
|
|||||||
14
dist/speedtest/backend/empty.php
vendored
Executable file
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
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
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
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
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
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
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
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
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
0
includes/CSRF.php
Normal file → Executable file
0
includes/CSRF.php
Normal file → Executable file
8
includes/bootstrap.php
Normal 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
|
<?php
|
||||||
|
|
||||||
require_once 'includes/wifi_functions.php';
|
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -8,12 +8,13 @@ require_once 'includes/wifi_functions.php';
|
|||||||
*/
|
*/
|
||||||
function DisplayWPAConfig()
|
function DisplayWPAConfig()
|
||||||
{
|
{
|
||||||
|
$wifi = new WiFiManager();
|
||||||
$status = new \RaspAP\Messages\StatusMessage;
|
$status = new \RaspAP\Messages\StatusMessage;
|
||||||
$networks = [];
|
$networks = [];
|
||||||
|
|
||||||
getWifiInterface();
|
$wifi->getWifiInterface();
|
||||||
knownWifiStations($networks);
|
$wifi->knownWifiStations($networks);
|
||||||
setKnownStationsWPA($networks);
|
$wifi->setKnownStationsWPA($networks);
|
||||||
|
|
||||||
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ function DisplayWPAConfig()
|
|||||||
} elseif (isset($_POST['wpa_reinit'])) {
|
} elseif (isset($_POST['wpa_reinit'])) {
|
||||||
$status->addMessage('Attempting to reinitialize wpa_supplicant', 'warning');
|
$status->addMessage('Attempting to reinitialize wpa_supplicant', 'warning');
|
||||||
$force_remove = true;
|
$force_remove = true;
|
||||||
$result = reinitializeWPA($force_remove);
|
$result = $wifi->reinitializeWPA($force_remove);
|
||||||
} elseif (isset($_POST['client_settings'])) {
|
} elseif (isset($_POST['client_settings'])) {
|
||||||
$tmp_networks = $networks;
|
$tmp_networks = $networks;
|
||||||
if ($wpa_file = fopen('/tmp/wifidata', 'w')) {
|
if ($wpa_file = fopen('/tmp/wifidata', 'w')) {
|
||||||
@@ -90,7 +91,7 @@ function DisplayWPAConfig()
|
|||||||
if (strlen($network['passphrase']) >=8 && strlen($network['passphrase']) <= 63) {
|
if (strlen($network['passphrase']) >=8 && strlen($network['passphrase']) <= 63) {
|
||||||
unset($wpa_passphrase);
|
unset($wpa_passphrase);
|
||||||
unset($line);
|
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) {
|
foreach ($wpa_passphrase as $line) {
|
||||||
if (preg_match('/^\s*}\s*$/', $line)) {
|
if (preg_match('/^\s*}\s*$/', $line)) {
|
||||||
if (array_key_exists('priority', $network)) {
|
if (array_key_exists('priority', $network)) {
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'includes/config.php';
|
require_once 'includes/config.php';
|
||||||
require_once 'includes/wifi_functions.php';
|
|
||||||
require_once 'includes/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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the dashboard
|
* Displays the dashboard
|
||||||
*/
|
*/
|
||||||
function DisplayDashboard(&$extraFooterScripts): void
|
function DisplayDashboard(&$extraFooterScripts): void
|
||||||
{
|
{
|
||||||
// instantiate RaspAP objects
|
// instantiate RaspAP objects
|
||||||
$system = new \RaspAP\System\Sysinfo;
|
$system = new Sysinfo();
|
||||||
$dashboard = new \RaspAP\UI\Dashboard;
|
$dashboard = new Dashboard();
|
||||||
$status = new \RaspAP\Messages\StatusMessage;
|
$status = new StatusMessage();
|
||||||
$pluginManager = \RaspAP\Plugins\PluginManager::getInstance();
|
$pluginManager = PluginManager::getInstance();
|
||||||
|
$wifi = new WiFiManager();
|
||||||
|
|
||||||
// set AP and client interface session vars
|
// set AP and client interface session vars
|
||||||
getWifiInterface();
|
$wifi->getWifiInterface();
|
||||||
|
|
||||||
$interface = $_SESSION['ap_interface'] ?? 'wlan0';
|
$interface = $_SESSION['ap_interface'] ?? 'wlan0';
|
||||||
$clientInterface = $_SESSION['wifi_client_interface'];
|
$clientInterface = $_SESSION['wifi_client_interface'];
|
||||||
@@ -76,12 +82,15 @@ function DisplayDashboard(&$extraFooterScripts): void
|
|||||||
$vpnStatus = $vpn ? "active" : "inactive";
|
$vpnStatus = $vpn ? "active" : "inactive";
|
||||||
$vpnManaged = $vpn ? $dashboard->getVpnManaged($vpn) : null;
|
$vpnManaged = $vpn ? $dashboard->getVpnManaged($vpn) : null;
|
||||||
$firewallManaged = $firewallStatus = "";
|
$firewallManaged = $firewallStatus = "";
|
||||||
$firewallInstalled = array_filter($plugins, fn($p) => str_ends_with($p, 'Firewall')) ? true : false;
|
$firewallInstalled = (bool) array_filter($plugins, function($p) {
|
||||||
|
return substr($p, -strlen('Firewall')) === 'Firewall';
|
||||||
|
});
|
||||||
if (!$firewallInstalled) {
|
if (!$firewallInstalled) {
|
||||||
$firewallUnavailable = '<i class="fas fa-slash fa-stack-1x"></i>';
|
$firewallUnavailable = '<i class="fas fa-slash fa-stack-1x"></i>';
|
||||||
} else {
|
} else {
|
||||||
$firewallManaged = '<a href="/plugin__Firewall">';
|
$firewallManaged = '<a href="/plugin__Firewall">';
|
||||||
$firewallStatus = ($dashboard->firewallEnabled() == true) ? "active" : "";
|
$firewallStatus = ($dashboard->firewallEnabled() == true) ? "active" : "";
|
||||||
|
$firewallUnavailable = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
echo renderTemplate(
|
echo renderTemplate(
|
||||||
@@ -123,7 +132,7 @@ function DisplayDashboard(&$extraFooterScripts): void
|
|||||||
"status"
|
"status"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$extraFooterScripts[] = array('src'=>'app/js/dashboardchart.js', 'defer'=>false);
|
$extraFooterScripts[] = array('src'=>'app/js/vendor/dashboardchart.js', 'defer'=>false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ function DisplayDataUsage(&$extraFooterScripts)
|
|||||||
echo renderTemplate("data_usage", [ "interfaces" => $interfacesWlo ]);
|
echo renderTemplate("data_usage", [ "interfaces" => $interfacesWlo ]);
|
||||||
|
|
||||||
$extraFooterScripts[] = array('src'=>'dist/datatables/jquery.dataTables.min.js', 'defer'=>false);
|
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ if (!defined('RASPI_CONFIG')) {
|
|||||||
$defaults = [
|
$defaults = [
|
||||||
'RASPI_BRAND_TEXT' => 'RaspAP',
|
'RASPI_BRAND_TEXT' => 'RaspAP',
|
||||||
'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel',
|
'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel',
|
||||||
'RASPI_VERSION' => '3.3.5',
|
'RASPI_VERSION' => '3.4.3',
|
||||||
'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json',
|
'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json',
|
||||||
'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json',
|
'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json',
|
||||||
'RASPI_CONFIG_API' => RASPI_CONFIG.'/api',
|
'RASPI_CONFIG_API' => RASPI_CONFIG.'/api',
|
||||||
@@ -37,11 +37,17 @@ $defaults = [
|
|||||||
'RASPI_OPENVPN_CLIENT_LOGIN' => '/etc/openvpn/client/login.conf',
|
'RASPI_OPENVPN_CLIENT_LOGIN' => '/etc/openvpn/client/login.conf',
|
||||||
'RASPI_WIREGUARD_PATH' => '/etc/wireguard/',
|
'RASPI_WIREGUARD_PATH' => '/etc/wireguard/',
|
||||||
'RASPI_WIREGUARD_CONFIG' => RASPI_WIREGUARD_PATH.'wg0.conf',
|
'RASPI_WIREGUARD_CONFIG' => RASPI_WIREGUARD_PATH.'wg0.conf',
|
||||||
|
'RASPI_IPTABLES_CONF' => RASPI_CONFIG.'/networking/iptables_rules.json',
|
||||||
|
'RASPI_TORPROXY_ENABLED' => false,
|
||||||
'RASPI_TORPROXY_CONFIG' => '/etc/tor/torrc',
|
'RASPI_TORPROXY_CONFIG' => '/etc/tor/torrc',
|
||||||
'RASPI_LIGHTTPD_CONFIG' => '/etc/lighttpd/lighttpd.conf',
|
'RASPI_LIGHTTPD_CONFIG' => '/etc/lighttpd/lighttpd.conf',
|
||||||
'RASPI_ACCESS_CHECK_IP' => '1.1.1.1',
|
'RASPI_ACCESS_CHECK_IP' => '1.1.1.1',
|
||||||
'RASPI_ACCESS_CHECK_DNS' => 'one.one.one.one',
|
'RASPI_ACCESS_CHECK_DNS' => 'one.one.one.one',
|
||||||
|
|
||||||
|
// Captive portal detection - returns 204 or 200 is successful
|
||||||
|
'RASPI_ACCESS_CHECK_URL' => 'http://detectportal.firefox.com',
|
||||||
|
'RASPI_ACCESS_CHECK_URL_CODE' => 200,
|
||||||
|
|
||||||
// Constants for the 5GHz wireless regulatory domain
|
// Constants for the 5GHz wireless regulatory domain
|
||||||
'RASPI_5GHZ_CHANNEL_MIN' => 100,
|
'RASPI_5GHZ_CHANNEL_MIN' => 100,
|
||||||
'RASPI_5GHZ_CHANNEL_MAX' => 192,
|
'RASPI_5GHZ_CHANNEL_MAX' => 192,
|
||||||
@@ -58,7 +64,6 @@ $defaults = [
|
|||||||
'RASPI_OPENVPN_ENABLED' => false,
|
'RASPI_OPENVPN_ENABLED' => false,
|
||||||
'RASPI_VPN_PROVIDER_ENABLED' => false,
|
'RASPI_VPN_PROVIDER_ENABLED' => false,
|
||||||
'RASPI_WIREGUARD_ENABLED' => false,
|
'RASPI_WIREGUARD_ENABLED' => false,
|
||||||
'RASPI_TORPROXY_ENABLED' => false,
|
|
||||||
'RASPI_CONFAUTH_ENABLED' => true,
|
'RASPI_CONFAUTH_ENABLED' => true,
|
||||||
'RASPI_CHANGETHEME_ENABLED' => true,
|
'RASPI_CHANGETHEME_ENABLED' => true,
|
||||||
'RASPI_VNSTAT_ENABLED' => true,
|
'RASPI_VNSTAT_ENABLED' => true,
|
||||||
|
|||||||
@@ -2,12 +2,20 @@
|
|||||||
|
|
||||||
require_once 'config.php';
|
require_once 'config.php';
|
||||||
|
|
||||||
|
use RaspAP\Networking\Hotspot\DhcpcdManager;
|
||||||
|
use RaspAP\Networking\Hotspot\DnsmasqManager;
|
||||||
|
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||||
|
use RaspAP\Messages\StatusMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage DHCP configuration
|
* Displays DHCP configuration
|
||||||
*/
|
*/
|
||||||
function DisplayDHCPConfig()
|
function DisplayDHCPConfig()
|
||||||
{
|
{
|
||||||
$status = new \RaspAP\Messages\StatusMessage;
|
$status = new StatusMessage();
|
||||||
|
$wifi = new WiFiManager();
|
||||||
|
$wifi->getWifiInterface();
|
||||||
|
|
||||||
if (!RASPI_MONITOR_ENABLED) {
|
if (!RASPI_MONITOR_ENABLED) {
|
||||||
if (isset($_POST['savedhcpdsettings'])) {
|
if (isset($_POST['savedhcpdsettings'])) {
|
||||||
saveDHCPConfig($status);
|
saveDHCPConfig($status);
|
||||||
@@ -29,6 +37,18 @@ function DisplayDHCPConfig()
|
|||||||
$status->addMessage('Failed to start dnsmasq', 'danger');
|
$status->addMessage('Failed to start dnsmasq', 'danger');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} elseif (isset($_POST['restartdhcpd'])) {
|
||||||
|
if ($dnsmasq_state) {
|
||||||
|
exec('sudo /bin/systemctl restart dnsmasq.service', $dnsmasq, $return);
|
||||||
|
if ($return == 0) {
|
||||||
|
$status->addMessage('Successfully restarted dnsmasq', 'success');
|
||||||
|
$dnsmasq_state = false;
|
||||||
|
} else {
|
||||||
|
$status->addMessage('Failed to restart dnsmasq', 'danger');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$status->addMessage('dnsmasq already stopped', 'info');
|
||||||
|
}
|
||||||
} elseif (isset($_POST['stopdhcpd'])) {
|
} elseif (isset($_POST['stopdhcpd'])) {
|
||||||
if ($dnsmasq_state) {
|
if ($dnsmasq_state) {
|
||||||
exec('sudo /bin/systemctl stop dnsmasq.service', $dnsmasq, $return);
|
exec('sudo /bin/systemctl stop dnsmasq.service', $dnsmasq, $return);
|
||||||
@@ -43,7 +63,6 @@ function DisplayDHCPConfig()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getWifiInterface();
|
|
||||||
$ap_iface = $_SESSION['ap_interface'];
|
$ap_iface = $_SESSION['ap_interface'];
|
||||||
$serviceStatus = $dnsmasq_state ? "up" : "down";
|
$serviceStatus = $dnsmasq_state ? "up" : "down";
|
||||||
exec('cat '. RASPI_DNSMASQ_PREFIX.'raspap.conf', $return);
|
exec('cat '. RASPI_DNSMASQ_PREFIX.'raspap.conf', $return);
|
||||||
@@ -86,262 +105,41 @@ function DisplayDHCPConfig()
|
|||||||
*/
|
*/
|
||||||
function saveDHCPConfig($status)
|
function saveDHCPConfig($status)
|
||||||
{
|
{
|
||||||
|
$dhcpcd = new DhcpcdManager();
|
||||||
|
$dnsmasq = new DnsmasqManager();
|
||||||
$iface = $_POST['interface'];
|
$iface = $_POST['interface'];
|
||||||
$return = 1;
|
|
||||||
|
|
||||||
// handle disable dhcp option
|
// dhcp
|
||||||
if (!isset($_POST['dhcp-iface']) && file_exists(RASPI_DNSMASQ_PREFIX.$iface.'.conf')) {
|
if (!isset($_POST['dhcp-iface']) && file_exists(RASPI_DNSMASQ_PREFIX.$iface.'.conf')) {
|
||||||
// remove dhcp + dnsmasq configs for selected interface
|
// remove dhcp + dnsmasq configs for selected interface
|
||||||
$return = removeDHCPConfig($iface,$status);
|
$return = $dhcpcd->remove($iface, $status);
|
||||||
$return = removeDnsmasqConfig($iface,$status);
|
$return = $dnsmasq->remove($iface, $status);
|
||||||
} else {
|
} else {
|
||||||
$errors = validateDHCPInput();
|
$errors = $dhcpcd->validate($_POST);
|
||||||
if (empty($errors)) {
|
if (empty($errors)) {
|
||||||
$return = updateDHCPConfig($iface,$status);
|
$dhcp_cfg = $dhcpcd->buildConfigEx($iface, $_POST, $status);
|
||||||
|
$dhcpcd->saveConfig($dhcp_cfg, $iface, $status);
|
||||||
} else {
|
} else {
|
||||||
foreach ($errors as $error) {
|
foreach ($errors as $error) {
|
||||||
$status->addMessage($error, 'danger');
|
$status->addMessage($error, 'danger');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($return == 1) {
|
|
||||||
$status->addMessage('Dnsmasq configuration failed to be updated.', 'danger');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// dnsmasq
|
||||||
if (($_POST['dhcp-iface'] == "1") || (isset($_POST['mac']))) {
|
if (($_POST['dhcp-iface'] == "1") || (isset($_POST['mac']))) {
|
||||||
$errors = validateDnsmasqInput();
|
$errors = $dnsmasq->validate($_POST);
|
||||||
if (empty($errors)) {
|
if (empty($errors)) {
|
||||||
$return = updateDnsmasqConfig($iface,$status);
|
$config = $dnsmasq->buildConfigEx($iface, $_POST);
|
||||||
|
$return = $dnsmasq->saveConfig($config, $iface);
|
||||||
|
$config = $dnsmasq->buildDefault($_POST);
|
||||||
|
$return = $dnsmasq->saveConfigDefault($config);
|
||||||
} else {
|
} else {
|
||||||
foreach ($errors as $error) {
|
foreach ($errors as $error) {
|
||||||
$status->addMessage($error, 'danger');
|
$status->addMessage($error, 'danger');
|
||||||
}
|
}
|
||||||
$return = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($return == 0) {
|
|
||||||
$status->addMessage('Dnsmasq configuration updated successfully.', 'success');
|
|
||||||
} else {
|
|
||||||
$status->addMessage('Dnsmasq configuration failed to be updated.', 'danger');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates DHCP user input from the $_POST object
|
|
||||||
*
|
|
||||||
* @return array $errors
|
|
||||||
*/
|
|
||||||
function validateDHCPInput()
|
|
||||||
{
|
|
||||||
$errors = [];
|
|
||||||
define('IFNAMSIZ', 16);
|
|
||||||
$iface = $_POST['interface'];
|
|
||||||
if (!preg_match('/^[^\s\/\\0]+$/', $iface)
|
|
||||||
|| strlen($iface) >= IFNAMSIZ
|
|
||||||
) {
|
|
||||||
$errors[] = _('Invalid interface name.');
|
|
||||||
}
|
|
||||||
if (!filter_var($_POST['StaticIP'], FILTER_VALIDATE_IP) && !empty($_POST['StaticIP'])) {
|
|
||||||
$errors[] = _('Invalid static IP address.');
|
|
||||||
}
|
|
||||||
if (!filter_var($_POST['SubnetMask'], FILTER_VALIDATE_IP) && !empty($_POST['SubnetMask'])) {
|
|
||||||
$errors[] = _('Invalid subnet mask.');
|
|
||||||
}
|
|
||||||
if (!filter_var($_POST['DefaultGateway'], FILTER_VALIDATE_IP) && !empty($_POST['DefaultGateway'])) {
|
|
||||||
$errors[] = _('Invalid default gateway.');
|
|
||||||
}
|
|
||||||
if (($_POST['dhcp-iface'] == "1")) {
|
|
||||||
if (!filter_var($_POST['RangeStart'], FILTER_VALIDATE_IP) && !empty($_POST['RangeStart'])) {
|
|
||||||
$errors[] = _('Invalid DHCP range start.');
|
|
||||||
}
|
|
||||||
if (!filter_var($_POST['RangeEnd'], FILTER_VALIDATE_IP) && !empty($_POST['RangeEnd'])) {
|
|
||||||
$errors[] = _('Invalid DHCP range end.');
|
|
||||||
}
|
|
||||||
if (!ctype_digit($_POST['RangeLeaseTime']) && $_POST['RangeLeaseTimeUnits'] !== 'i') {
|
|
||||||
$errors[] = _('Invalid DHCP lease time, not a number.');
|
|
||||||
}
|
|
||||||
if (!in_array($_POST['RangeLeaseTimeUnits'], array('m', 'h', 'd', 'i'))) {
|
|
||||||
$errors[] = _('Unknown DHCP lease time unit.');
|
|
||||||
}
|
|
||||||
if ($_POST['Metric'] !== '' && !ctype_digit($_POST['Metric'])) {
|
|
||||||
$errors[] = _('Invalid metric value, not a number.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares to string IPs
|
|
||||||
*
|
|
||||||
* @param string $ip1
|
|
||||||
* @param string $ip2
|
|
||||||
* @return boolean $result
|
|
||||||
*/
|
|
||||||
function compareIPs($ip1, $ip2)
|
|
||||||
{
|
|
||||||
$ipu1 = sprintf('%u', ip2long($ip1["ip"])) + 0;
|
|
||||||
$ipu2 = sprintf('%u', ip2long($ip2["ip"])) + 0;
|
|
||||||
return $ipu1 > $ipu2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates Dnsmasq user input from the $_POST object
|
|
||||||
*
|
|
||||||
* @return array $errors
|
|
||||||
*/
|
|
||||||
function validateDnsmasqInput()
|
|
||||||
{
|
|
||||||
$errors = [];
|
|
||||||
$encounteredIPs = [];
|
|
||||||
|
|
||||||
if (isset($_POST["static_leases"]["mac"])) {
|
|
||||||
for ($i=0; $i < count($_POST["static_leases"]["mac"]); $i++) {
|
|
||||||
$mac = trim($_POST["static_leases"]["mac"][$i]);
|
|
||||||
$ip = trim($_POST["static_leases"]["ip"][$i]);
|
|
||||||
if (!validateMac($mac)) {
|
|
||||||
$errors[] = _('Invalid MAC address: '.$mac);
|
|
||||||
}
|
|
||||||
if (in_array($ip, $encounteredIPs)) {
|
|
||||||
$errors[] = _('Duplicate IP address entered: ' . $ip);
|
|
||||||
} else {
|
|
||||||
$encounteredIPs[] = $ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a dnsmasq configuration
|
|
||||||
*
|
|
||||||
* @param string $iface
|
|
||||||
* @param object $status
|
|
||||||
* @return boolean $result
|
|
||||||
*/
|
|
||||||
function updateDnsmasqConfig($iface,$status)
|
|
||||||
{
|
|
||||||
|
|
||||||
$config = '# RaspAP '.$iface.' configuration'.PHP_EOL;
|
|
||||||
$config .= 'interface='.$iface.PHP_EOL.'dhcp-range='.$_POST['RangeStart'].','.$_POST['RangeEnd'].','.$_POST['SubnetMask'].',';
|
|
||||||
if ($_POST['RangeLeaseTimeUnits'] !== 'i') {
|
|
||||||
$config .= $_POST['RangeLeaseTime'];
|
|
||||||
$config .= $_POST['RangeLeaseTimeUnits'].PHP_EOL;
|
|
||||||
} else {
|
|
||||||
$config .= 'infinite'.PHP_EOL;
|
|
||||||
}
|
|
||||||
// Static leases
|
|
||||||
$staticLeases = array();
|
|
||||||
if (isset($_POST["static_leases"]["mac"])) {
|
|
||||||
for ($i=0; $i < count($_POST["static_leases"]["mac"]); $i++) {
|
|
||||||
$mac = trim($_POST["static_leases"]["mac"][$i]);
|
|
||||||
$ip = trim($_POST["static_leases"]["ip"][$i]);
|
|
||||||
$comment = trim($_POST["static_leases"]["comment"][$i]);
|
|
||||||
if ($mac != "" && $ip != "") {
|
|
||||||
$staticLeases[] = array('mac' => $mac, 'ip' => $ip, 'comment' => $comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort ascending by IPs
|
|
||||||
usort($staticLeases, "compareIPs");
|
|
||||||
// Update config
|
|
||||||
for ($i = 0; $i < count($staticLeases); $i++) {
|
|
||||||
$mac = $staticLeases[$i]['mac'];
|
|
||||||
$ip = $staticLeases[$i]['ip'];
|
|
||||||
$comment = $staticLeases[$i]['comment'];
|
|
||||||
$config .= "dhcp-host=$mac,$ip # $comment".PHP_EOL;
|
|
||||||
}
|
|
||||||
if ($_POST['no-resolv'] == "1") {
|
|
||||||
$config .= "no-resolv".PHP_EOL;
|
|
||||||
}
|
|
||||||
foreach ($_POST['server'] as $server) {
|
|
||||||
$config .= "server=$server".PHP_EOL;
|
|
||||||
}
|
|
||||||
if ($_POST['DNS1']) {
|
|
||||||
$config .= "dhcp-option=6," . $_POST['DNS1'];
|
|
||||||
if ($_POST['DNS2']) {
|
|
||||||
$config .= ','.$_POST['DNS2'];
|
|
||||||
}
|
|
||||||
$config .= PHP_EOL;
|
|
||||||
}
|
|
||||||
if ($_POST['dhcp-ignore'] == "1") {
|
|
||||||
$config .= 'dhcp-ignore=tag:!known'.PHP_EOL;
|
|
||||||
}
|
|
||||||
file_put_contents("/tmp/dnsmasqdata", $config);
|
|
||||||
$msg = file_exists(RASPI_DNSMASQ_PREFIX.$iface.'.conf') ? 'updated' : 'added';
|
|
||||||
system('sudo cp /tmp/dnsmasqdata '.RASPI_DNSMASQ_PREFIX.$iface.'.conf', $result);
|
|
||||||
if ($result == 0) {
|
|
||||||
$status->addMessage('Dnsmasq configuration for '.$iface.' '.$msg.'.', 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
// write default 090_raspap.conf
|
|
||||||
$config = '# RaspAP default config'.PHP_EOL;
|
|
||||||
$config .='log-facility='.RASPI_DHCPCD_LOG.PHP_EOL;
|
|
||||||
$config .='conf-dir=/etc/dnsmasq.d'.PHP_EOL;
|
|
||||||
// handle log option
|
|
||||||
if (($_POST['log-dhcp'] ?? '') == "1") {
|
|
||||||
$config .= "log-dhcp".PHP_EOL;
|
|
||||||
}
|
|
||||||
if (($_POST['log-queries'] ?? '') == "1") {
|
|
||||||
$config .= "log-queries".PHP_EOL;
|
|
||||||
}
|
|
||||||
$config .= PHP_EOL;
|
|
||||||
file_put_contents("/tmp/dnsmasqdata", $config);
|
|
||||||
system('sudo cp /tmp/dnsmasqdata '.RASPI_DNSMASQ_PREFIX.'raspap.conf', $result);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a dhcp configuration
|
|
||||||
*
|
|
||||||
* @param string $iface
|
|
||||||
* @param object $status
|
|
||||||
* @return boolean $result
|
|
||||||
*/
|
|
||||||
function updateDHCPConfig($iface,$status)
|
|
||||||
{
|
|
||||||
$cfg[] = '# RaspAP '.$iface.' configuration';
|
|
||||||
$cfg[] = 'interface '.$iface;
|
|
||||||
if (isset($_POST['StaticIP']) && $_POST['StaticIP'] !== '') {
|
|
||||||
$mask = ($_POST['SubnetMask'] !== '' && $_POST['SubnetMask'] !== '0.0.0.0') ? '/'.mask2cidr($_POST['SubnetMask']) : null;
|
|
||||||
$cfg[] = 'static ip_address='.$_POST['StaticIP'].$mask;
|
|
||||||
}
|
|
||||||
if (isset($_POST['DefaultGateway']) && $_POST['DefaultGateway'] !== '') {
|
|
||||||
$cfg[] = 'static routers='.$_POST['DefaultGateway'];
|
|
||||||
}
|
|
||||||
if ($_POST['DNS1'] !== '' || $_POST['DNS2'] !== '') {
|
|
||||||
$cfg[] = 'static domain_name_server='.$_POST['DNS1'].' '.$_POST['DNS2'];
|
|
||||||
}
|
|
||||||
if ($_POST['Metric'] !== '') {
|
|
||||||
$cfg[] = 'metric '.$_POST['Metric'];
|
|
||||||
}
|
|
||||||
if (($_POST['Fallback'] ?? 0) == 1) {
|
|
||||||
$cfg[] = 'profile static_'.$iface;
|
|
||||||
$cfg[] = 'fallback static_'.$iface;
|
|
||||||
}
|
|
||||||
$cfg[] = ($_POST['DefaultRoute'] ?? '') == '1' ? 'gateway' : 'nogateway';
|
|
||||||
if (substr($iface, 0, 2) === "wl" && ($_POST['NoHookWPASupplicant'] ?? '') == '1') {
|
|
||||||
$cfg[] = 'nohook wpa_supplicant';
|
|
||||||
}
|
|
||||||
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
|
||||||
if (!preg_match('/^interface\s'.$iface.'$/m', $dhcp_cfg)) {
|
|
||||||
$cfg[] = PHP_EOL;
|
|
||||||
$cfg = join(PHP_EOL, $cfg);
|
|
||||||
$dhcp_cfg .= $cfg;
|
|
||||||
$status->addMessage('DHCP configuration for '.$iface.' added.', 'success');
|
|
||||||
} else {
|
|
||||||
$cfg = join(PHP_EOL, $cfg);
|
|
||||||
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)/ms', $cfg, $dhcp_cfg, 1);
|
|
||||||
$status->addMessage('DHCP configuration for '.$iface.' updated.', 'success');
|
|
||||||
}
|
|
||||||
file_put_contents("/tmp/dhcpddata", $dhcp_cfg);
|
|
||||||
system('sudo cp /tmp/dhcpddata '.RASPI_DHCPCD_CONFIG, $result);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -64,77 +64,6 @@ function cidr2mask($cidr)
|
|||||||
return $netmask;
|
return $netmask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a dhcp configuration block for the specified interface
|
|
||||||
*
|
|
||||||
* @param string $iface
|
|
||||||
* @param object $status
|
|
||||||
* @return boolean $result
|
|
||||||
*/
|
|
||||||
function removeDHCPConfig($iface,$status)
|
|
||||||
{
|
|
||||||
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
|
||||||
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)([\s]+)/ms', '', $dhcp_cfg, 1);
|
|
||||||
file_put_contents("/tmp/dhcpddata", $dhcp_cfg);
|
|
||||||
system('sudo cp /tmp/dhcpddata '.RASPI_DHCPCD_CONFIG, $result);
|
|
||||||
if ($result == 0) {
|
|
||||||
$status->addMessage('DHCP configuration for '.$iface.' removed.', 'success');
|
|
||||||
} else {
|
|
||||||
$status->addMessage('Failed to remove DHCP configuration for '.$iface.'.', 'danger');
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a dhcp configuration block for the specified interface
|
|
||||||
*
|
|
||||||
* @param string $dhcp_cfg
|
|
||||||
* @param string $iface
|
|
||||||
* @return string $dhcp_cfg
|
|
||||||
*/
|
|
||||||
function removeDHCPIface($dhcp_cfg,$iface)
|
|
||||||
{
|
|
||||||
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)([\s]+)/ms', '', $dhcp_cfg, 1);
|
|
||||||
return $dhcp_cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a dnsmasq configuration block for the specified interface
|
|
||||||
*
|
|
||||||
* @param string $iface
|
|
||||||
* @param object $status
|
|
||||||
* @return boolean $result
|
|
||||||
*/
|
|
||||||
function removeDnsmasqConfig($iface,$status)
|
|
||||||
{
|
|
||||||
system('sudo rm '.RASPI_DNSMASQ_PREFIX.$iface.'.conf', $result);
|
|
||||||
if ($result == 0) {
|
|
||||||
$status->addMessage('Dnsmasq configuration for '.$iface.' removed.', 'success');
|
|
||||||
} else {
|
|
||||||
$status->addMessage('Failed to remove dnsmasq configuration for '.$iface.'.', 'danger');
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scans dnsmasq configuration dir for the specified interface
|
|
||||||
* Non-matching configs are removed, optional adblock.conf is protected
|
|
||||||
*
|
|
||||||
* @param string $dir_conf
|
|
||||||
* @param string $interface
|
|
||||||
* @param object $status
|
|
||||||
*/
|
|
||||||
function scanConfigDir($dir_conf,$interface,$status)
|
|
||||||
{
|
|
||||||
$syscnf = preg_grep('~\.(conf)$~', scandir($dir_conf));
|
|
||||||
foreach ($syscnf as $cnf) {
|
|
||||||
if ($cnf !== '090_adblock.conf' && !preg_match('/.*_'.$interface.'.conf/', $cnf)) {
|
|
||||||
system('sudo rm /etc/dnsmasq.d/'.$cnf, $result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a default (fallback) value for the selected service, interface & setting
|
* Returns a default (fallback) value for the selected service, interface & setting
|
||||||
* from /etc/raspap/networking/defaults.json
|
* from /etc/raspap/networking/defaults.json
|
||||||
@@ -570,8 +499,13 @@ function dnsServers()
|
|||||||
|
|
||||||
function blocklistProviders()
|
function blocklistProviders()
|
||||||
{
|
{
|
||||||
$data = json_decode(file_get_contents("./config/blocklists.json"));
|
$raw = json_decode(file_get_contents("./config/blocklists.json"), true);
|
||||||
return (array) $data;
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($raw as $group => $entries) {
|
||||||
|
$result[$group] = array_keys($entries);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function optionsForSelect($options)
|
function optionsForSelect($options)
|
||||||
@@ -659,6 +593,21 @@ function getColorOpt()
|
|||||||
} else {
|
} else {
|
||||||
$color = $_COOKIE['color'];
|
$color = $_COOKIE['color'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define the regex pattern for valid CSS color formats
|
||||||
|
$colorPattern = "/^(" .
|
||||||
|
"#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})" . "|" . // Hex colors (#RGB or #RRGGBB)
|
||||||
|
"rgb\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}\s*\)" . "|" . // RGB format
|
||||||
|
"rgba\(\s*(?:\d{1,3}\s*,\s*){3}\s*(0|0\.\d+|1)\s*\)" . "|" . // RGBA format
|
||||||
|
"[a-zA-Z]+" . // Named colors
|
||||||
|
")$/i";
|
||||||
|
|
||||||
|
// Validate the color
|
||||||
|
if (!preg_match($colorPattern, $color)) {
|
||||||
|
// Return a default color if validation fails
|
||||||
|
$color = "#2b8080";
|
||||||
|
}
|
||||||
|
|
||||||
return $color;
|
return $color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -966,7 +915,7 @@ function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cp
|
|||||||
?>
|
?>
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col-4 ms-2 sidebar-brand-icon">
|
<div class="col-4 ms-2 sidebar-brand-icon">
|
||||||
<img src="app/img/raspAP-logo.php" class="navbar-logo" width="60" height="60">
|
<img src="app/img/raspAP-logo.php?static=1" class="navbar-logo" width="70" height="70">
|
||||||
</div>
|
</div>
|
||||||
<div class="col ml-2">
|
<div class="col ml-2">
|
||||||
<div class="ml-1 sb-status">Status</div>
|
<div class="ml-1 sb-status">Status</div>
|
||||||
@@ -1005,4 +954,3 @@ function callbackTimeout(callable $callback, int $interval)
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,307 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once 'includes/functions.php';
|
|
||||||
require_once 'includes/wifi_functions.php';
|
|
||||||
|
|
||||||
function getClients($simple=true)
|
|
||||||
{
|
|
||||||
exec('ifconfig -a | grep -oP "^(?!lo)(\w*)"', $rawdevs); // all devices except loopback
|
|
||||||
$path=RASPI_CLIENT_SCRIPT_PATH;
|
|
||||||
$cl=array();
|
|
||||||
if (!empty($rawdevs) && is_array($rawdevs)) {
|
|
||||||
$cl["clients"]=count($rawdevs);
|
|
||||||
// search for possibly not connected modem
|
|
||||||
exec("find /sys/bus/usb/devices/usb*/ -name dev ", $devtty); // search for ttyUSB
|
|
||||||
$devtty = preg_only_match("/(ttyUSB0)/", $devtty);
|
|
||||||
if (empty(preg_only_match("/(ppp)[0-9]/", $rawdevs))) {
|
|
||||||
if (!empty($devtty)) {
|
|
||||||
$rawdevs[]="ppp0";
|
|
||||||
exec("udevadm info --name='$devtty' 2> /dev/null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($rawdevs as $i => $dev) {
|
|
||||||
$cl["device"][$i]["name"]=$dev;
|
|
||||||
$nam = (preg_match("/^(\w+)[0-9]$/",$dev,$nam) === 1) ? $nam=$nam[1] : "";
|
|
||||||
$cl["device"][$i]["type"]=$ty=getClientType($dev);
|
|
||||||
unset($udevinfo);
|
|
||||||
exec("udevadm info /sys/class/net/$dev 2> /dev/null", $udevinfo);
|
|
||||||
if ($nam == "ppp" && isset($devtty)) {
|
|
||||||
exec("udevadm info --name='$devtty' 2> /dev/null", $udevinfo);
|
|
||||||
}
|
|
||||||
if (!empty($udevinfo) && is_array($udevinfo)) {
|
|
||||||
$model = preg_only_match("/ID_MODEL_ENC=(.*)$/", $udevinfo);
|
|
||||||
if (empty($model) || preg_match("/^[0-9a-f]{4}$/", $model) === 1) {
|
|
||||||
$model = preg_only_match("/ID_MODEL_FROM_DATABASE=(.*)$/", $udevinfo);
|
|
||||||
}
|
|
||||||
if (empty($model)) {
|
|
||||||
$model = preg_only_match("/ID_OUI_FROM_DATABASE=(.*)$/", $udevinfo);
|
|
||||||
}
|
|
||||||
$vendor = preg_only_match("/ID_VENDOR_ENC=(.*)$/", $udevinfo);
|
|
||||||
if (empty($vendor) || preg_match("/^[0-9a-f]{4}$/", $vendor) === 1) {
|
|
||||||
$vendor = preg_only_match("/ID_VENDOR_FROM_DATABASE=(.*)$/", $udevinfo);
|
|
||||||
}
|
|
||||||
$driver = preg_only_match("/ID_NET_DRIVER=(.*)$/", $udevinfo);
|
|
||||||
$vendorid = preg_only_match("/ID_VENDOR_ID=(.*)$/", $udevinfo);
|
|
||||||
$productid = preg_only_match("/ID_MODEL_ID=(.*)$/", $udevinfo);
|
|
||||||
}
|
|
||||||
$cl["device"][$i]["model"] = preg_replace("/\\\\x20/", " ", $model);
|
|
||||||
$cl["device"][$i]["vendor"] = preg_replace("/\\\\x20/", " ", $vendor);
|
|
||||||
$cl["device"][$i]["vid"] = $vendorid;
|
|
||||||
$cl["device"][$i]["pid"] = $productid;
|
|
||||||
unset($mac);
|
|
||||||
exec("cat /sys/class/net/$dev/address 2> /dev/null", $mac);
|
|
||||||
$cl["device"][$i]["mac"] = empty($mac) ? "":$mac[0];
|
|
||||||
unset($ip);
|
|
||||||
exec("ifconfig $dev 2> /dev/null", $ip);
|
|
||||||
$cl["device"][$i]["ipaddress"] = preg_only_match("/.*inet ([0-9\.]+) .*/", $ip);
|
|
||||||
|
|
||||||
switch($ty) {
|
|
||||||
case "eth":
|
|
||||||
unset($res);
|
|
||||||
exec("ip link show $dev 2> /dev/null | grep -oP ' UP '", $res);
|
|
||||||
if (empty($res) && empty($ipadd)) {
|
|
||||||
$cl["device"][$i]["connected"] = "n";
|
|
||||||
} else {
|
|
||||||
$cl["device"][$i]["connected"] = "y";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "wlan":
|
|
||||||
unset($retiw);
|
|
||||||
exec("iwconfig $dev 2> /dev/null | sed -rn 's/.*(mode:master).*/1/ip'", $retiw);
|
|
||||||
$cl["device"][$i]["isAP"] = !empty($retiw);
|
|
||||||
unset($retiw);
|
|
||||||
exec("iw dev $dev link 2> /dev/null", $retiw);
|
|
||||||
if (!$simple && !empty($ssid=preg_only_match("/.*SSID:\s*([^\"]*).*/", $retiw)) ) {
|
|
||||||
$cl["device"][$i]["connected"] = "y";
|
|
||||||
$cl["device"][$i]["ssid"] = $ssid;
|
|
||||||
$cl["device"][$i]["ssidutf8"] = ssid2utf8($ssid);
|
|
||||||
$cl["device"][$i]["ap-mac"] = preg_only_match("/^Connected to ([0-9a-f\:]*).*$/", $retiw);
|
|
||||||
$sig = preg_only_match("/.*signal: (.*)$/", $retiw);
|
|
||||||
$val = preg_only_match("/^([0-9\.-]*).*$/", $sig);
|
|
||||||
if (!is_numeric($val)) {
|
|
||||||
$val = -100;
|
|
||||||
}
|
|
||||||
if ($val >= -50 ) {
|
|
||||||
$qual=100;
|
|
||||||
} else if ($val < -100) {
|
|
||||||
$qual=0;
|
|
||||||
} else {
|
|
||||||
$qual=round($val*2+200);
|
|
||||||
}
|
|
||||||
$cl["device"][$i]["signal"] = "$sig (".$qual."%)";
|
|
||||||
$cl["device"][$i]["bitrate"] = preg_only_match("/.*bitrate: ([0-9\.]* \w*\/s).*$/", $retiw);
|
|
||||||
$cl["device"][$i]["freq"] = preg_only_match("/.*freq: (.*)$/", $retiw);
|
|
||||||
$cl["device"][$i]["ap-mac"] = preg_only_match("/^Connected to ([0-9a-f\:]*).*$/", $retiw);
|
|
||||||
} else {
|
|
||||||
$cl["device"][$i]["connected"] = "n";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "ppp":
|
|
||||||
unset($res);
|
|
||||||
exec("ip link show $dev 2> /dev/null | grep -oP '( UP | UNKNOWN)'", $res);
|
|
||||||
if ($simple) {
|
|
||||||
if (empty($res)) {
|
|
||||||
$cl["device"][$i]["connected"] = "n";
|
|
||||||
$cl["device"][$i]["signal"] = "-100 dB (0%)";
|
|
||||||
} else {
|
|
||||||
$cl["device"][$i]["connected"] = "y";
|
|
||||||
$cl["device"][$i]["signal"] = "-0 dB (0%)";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (empty($res) && empty($ipadd)) {
|
|
||||||
$cl["device"][$i]["connected"] = "n";
|
|
||||||
} else {
|
|
||||||
$cl["device"][$i]["connected"] = "y";
|
|
||||||
}
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh mode modem", $res);
|
|
||||||
$cl["device"][$i]["mode"] = $res[0];
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh device modem", $res);
|
|
||||||
if ($res[0] != "none" ) {
|
|
||||||
$cl["device"][$i]["model"] = $res[0];
|
|
||||||
}
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh signal modem", $res);
|
|
||||||
$cl["device"][$i]["signal"] = $res[0];
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh operator modem", $res);
|
|
||||||
$cl["device"][$i]["operator"] = $res[0];
|
|
||||||
break;
|
|
||||||
case "hilink":
|
|
||||||
$pin=$user=$pw="";
|
|
||||||
getMobileLogin($pin,$pw,$user);
|
|
||||||
$opts=$pin.' '.$user.' '.$pw;
|
|
||||||
unset($res);
|
|
||||||
// exec("ip link show $dev 2> /dev/null | grep -oP ' UP '",$res);
|
|
||||||
exec("ifconfig -a | grep -i $dev -A 1 | grep -oP '(?<=inet )([0-9]{1,3}\.){3}'", $apiadd);
|
|
||||||
$apiadd = !empty($apiadd) ? $apiadd[0]."1" : "";
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh mode hilink $apiadd \"$opts\" ", $res);
|
|
||||||
$cl["device"][$i]["mode"] = $res[0];
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh device hilink $apiadd \"$opts\" ", $res);
|
|
||||||
if ($res[0] != "none" ) {
|
|
||||||
$cl["device"][$i]["model"] = $res[0];
|
|
||||||
}
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh signal hilink $apiadd \"$opts\" ", $res);
|
|
||||||
$cl["device"][$i]["signal"] = $res[0];
|
|
||||||
unset($ipadd);
|
|
||||||
exec("$path/info_huawei.sh ipaddress hilink $apiadd \"$opts\" ", $ipadd);
|
|
||||||
if (!empty($ipadd) && $ipadd[0] !== "none" ) {
|
|
||||||
$cl["device"][$i]["connected"] = "y";
|
|
||||||
$cl["device"][$i]["wan_ip"] = $ipadd[0];
|
|
||||||
} else {
|
|
||||||
$cl["device"][$i]["connected"] = "n";
|
|
||||||
$cl["device"][$i]["wan_ip"] = "-";
|
|
||||||
}
|
|
||||||
unset($res);
|
|
||||||
exec("$path/info_huawei.sh operator hilink $apiadd \"$opts\" ", $res);
|
|
||||||
$cl["device"][$i]["operator"] = $res[0];
|
|
||||||
break;
|
|
||||||
case "phone":
|
|
||||||
case "usb":
|
|
||||||
$cl["device"][$i]["connected"] = "y";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if (!isset($cl["device"][$i]["signal"])) {
|
|
||||||
$cl["device"][$i]["signal"]= $cl["device"][$i]["connected"] == "n" ? "-100 dB (0%)": "0 dB (100%)";;
|
|
||||||
}
|
|
||||||
if (!isset($cl["device"][$i]["isAP"])) {
|
|
||||||
$cl["device"][$i]["isAP"]=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $cl;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getClientType($dev) {
|
|
||||||
loadClientConfig();
|
|
||||||
// check if device type stored in DEVTYPE or raspapType (from UDEV rule) protperty of the device
|
|
||||||
exec("udevadm info /sys/class/net/$dev 2> /dev/null", $udevadm);
|
|
||||||
$type="none";
|
|
||||||
if (!empty($udevadm)) {
|
|
||||||
$type=preg_only_match("/raspapType=(\w*)/i",$udevadm);
|
|
||||||
if (empty($type)) {
|
|
||||||
$type=preg_only_match("/DEVTYPE=(\w*)/i",$udevadm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (empty($type) || $type == "none" || array_search($type, $_SESSION["net-device-name-prefix"]) === false) {
|
|
||||||
// no device type yet -> get device type from device name
|
|
||||||
if (preg_match("/^(\w+)[0-9]$/",$dev,$nam) === 1) $nam=$nam[1];
|
|
||||||
else $nam="none";
|
|
||||||
if (($n = array_search($nam, $_SESSION["net-device-name-prefix"])) === false) $n = count($_SESSION["net-device-types"])-1;
|
|
||||||
$type = $_SESSION["net-device-types"][$n];
|
|
||||||
}
|
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMobileLogin(&$pin,&$pw,&$user) {
|
|
||||||
if (file_exists(($f = RASPI_MOBILEDATA_CONFIG))) {
|
|
||||||
$dat = parse_ini_file($f);
|
|
||||||
$pin = (isset($dat["pin"]) && preg_match("/^[0-9]*$/", $dat["pin"])) ? "-p ".$dat["pin"] : "";
|
|
||||||
$user = (isset($dat["router_user"]) && !empty($dat["router_user"]) ) ? "-u ".$dat["router_user"] : "";
|
|
||||||
$pw = (isset($dat["router_pw"]) && !empty($dat["router_pw"]) ) ? "-P ".$dat["router_pw"] : "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadClientConfig()
|
|
||||||
{
|
|
||||||
// load network device config file for UDEV rules into $_SESSION
|
|
||||||
if (!isset($_SESSION["udevrules"])) {
|
|
||||||
$_SESSION["net-device-types"]=array();
|
|
||||||
$_SESSION["net-device-name-prefix"]=array();
|
|
||||||
try {
|
|
||||||
$udevrules = file_get_contents(RASPI_CLIENT_CONFIG_PATH);
|
|
||||||
$_SESSION["udevrules"] = json_decode($udevrules, true);
|
|
||||||
// get device types
|
|
||||||
foreach ($_SESSION["udevrules"]["network_devices"] as $dev) {
|
|
||||||
$_SESSION["net-device-name-prefix"][]=$dev["name_prefix"];
|
|
||||||
$_SESSION["net-device-types"][]=$dev["type"];
|
|
||||||
$_SESSION["net-device-types-info"][]=$dev["type_info"];
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$_SESSION["udevrules"]= null;
|
|
||||||
}
|
|
||||||
$_SESSION["net-device-types"][]="none";
|
|
||||||
$_SESSION["net-device-types-info"][]="unknown";
|
|
||||||
$_SESSION["net-device-name-prefix"][]="none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findCurrentClientIndex($clients)
|
|
||||||
{
|
|
||||||
$devid = -1;
|
|
||||||
if (!empty($clients)) {
|
|
||||||
$ncl=$clients["clients"];
|
|
||||||
if ($ncl > 0) {
|
|
||||||
$ty=-1;
|
|
||||||
foreach ($clients["device"] as $i => $dev) {
|
|
||||||
$id=array_search($dev["type"], $_SESSION["net-device-types"]);
|
|
||||||
if ($id >=0 && $_SESSION["udevrules"]["network_devices"][$id]["clientid"] > $ty && !$dev["isAP"]) {
|
|
||||||
$ty=$id;
|
|
||||||
$devid=$i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $devid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitClientConnected($dev, $timeout=10)
|
|
||||||
{
|
|
||||||
do {
|
|
||||||
exec('ifconfig -a | grep -i '.$dev.' -A 1 | grep -oP "(?<=inet )([0-9]{1,3}\.){3}[0-9]{1,3}"', $res);
|
|
||||||
$connected= !empty($res);
|
|
||||||
if (!$connected) {
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
} while (!$connected && --$timeout > 0);
|
|
||||||
return $connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setClientState($state)
|
|
||||||
{
|
|
||||||
$clients=getClients();
|
|
||||||
if (($idx = findCurrentClientIndex($clients)) >= 0) {
|
|
||||||
$dev = $clients["device"][$idx];
|
|
||||||
exec('ifconfig -a | grep -i '.$dev["name"].' -A 1 | grep -oP "(?<=inet )([0-9]{1,3}\.){3}[0-9]{1,3}"', $res);
|
|
||||||
if (!empty($res)) {
|
|
||||||
$connected=$res[0];
|
|
||||||
}
|
|
||||||
switch($dev["type"]) {
|
|
||||||
case "wlan":
|
|
||||||
if ($state =="up") {
|
|
||||||
exec('sudo ip link set '.$dev["name"].' up');
|
|
||||||
}
|
|
||||||
if (!empty($connected) && $state =="down") {
|
|
||||||
exec('sudo ip link set '.$dev["name"].' down');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "hilink":
|
|
||||||
preg_match("/^([0-9]{1,3}\.){3}/", $connected, $ipadd);
|
|
||||||
$ipadd = $ipadd[0].'1'; // ip address of the Hilink api
|
|
||||||
$mode = ($state == "up") ? 1 : 0;
|
|
||||||
$pin=$user=$pw="";
|
|
||||||
getMobileLogin($pin,$pw,$user);
|
|
||||||
exec('sudo '.RASPI_CLIENT_SCRIPT_PATH.'/onoff_huawei_hilink.sh -c '.$mode.' -h '.$ipadd.' '.$pin.' '.$user.' '.$pw);
|
|
||||||
break;
|
|
||||||
case "ppp":
|
|
||||||
if ($state == "up") {
|
|
||||||
exec('sudo ifup '.$dev["name"]);
|
|
||||||
}
|
|
||||||
if (!empty($connected) && $state == "down") {
|
|
||||||
exec('sudo ifdown '.$dev["name"]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ($state=="up") {
|
|
||||||
waitClientConnected($dev["name"], 15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'includes/wifi_functions.php';
|
use RaspAP\Networking\Hotspot\HostapdManager;
|
||||||
require_once 'includes/config.php';
|
use RaspAP\Networking\Hotspot\HotspotService;
|
||||||
|
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||||
|
use RaspAP\Messages\StatusMessage;
|
||||||
|
use RaspAP\System\Sysinfo;
|
||||||
|
|
||||||
getWifiInterface();
|
$wifi = new WiFiManager();
|
||||||
|
$wifi->getWifiInterface();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize hostapd values, display interface
|
* Initialize hostapd values, display interface
|
||||||
@@ -11,47 +15,31 @@ getWifiInterface();
|
|||||||
*/
|
*/
|
||||||
function DisplayHostAPDConfig()
|
function DisplayHostAPDConfig()
|
||||||
{
|
{
|
||||||
$status = new \RaspAP\Messages\StatusMessage;
|
$hostapd = new HostapdManager();
|
||||||
$system = new \RaspAP\System\Sysinfo;
|
$hotspot = new HotspotService();
|
||||||
|
$status = new StatusMessage();
|
||||||
|
$system = new Sysinfo();
|
||||||
$operatingSystem = $system->operatingSystem();
|
$operatingSystem = $system->operatingSystem();
|
||||||
$arrConfig = array();
|
|
||||||
$arr80211Standard = [
|
// set hostapd defaults
|
||||||
'a' => '802.11a - 5 GHz',
|
$arr80211Standard = $hotspot->get80211Standards();
|
||||||
'b' => '802.11b - 2.4 GHz',
|
$arrSecurity = $hotspot->getSecurityModes();
|
||||||
'g' => '802.11g - 2.4 GHz',
|
$arrEncType = $hotspot->getEncTypes();
|
||||||
'n' => '802.11n - 2.4/5 GHz',
|
$arr80211w = $hotspot->get80211wOptions();
|
||||||
'ac' => '802.11ac - 5 GHz'
|
|
||||||
];
|
|
||||||
$languageCode = strtok($_SESSION['locale'], '_');
|
$languageCode = strtok($_SESSION['locale'], '_');
|
||||||
$countryCodes = getCountryCodes($languageCode);
|
$countryCodes = getCountryCodes($languageCode);
|
||||||
|
$reg_domain = $hotspot->getRegDomain();
|
||||||
$arrSecurity = array(1 => 'WPA', 2 => 'WPA2', 3 => 'WPA+WPA2', 'none' => _("None"));
|
$interfaces = $hotspot->getInterfaces();
|
||||||
$arrEncType = array('TKIP' => 'TKIP', 'CCMP' => 'CCMP', 'TKIP CCMP' => 'TKIP+CCMP');
|
|
||||||
$arrTxPower = getDefaultNetOpts('txpower','dbm');
|
$arrTxPower = getDefaultNetOpts('txpower','dbm');
|
||||||
$managedModeEnabled = false;
|
$managedModeEnabled = false;
|
||||||
exec("ip -o link show | awk -F': ' '{print $2}'", $interfaces);
|
|
||||||
sort($interfaces);
|
|
||||||
|
|
||||||
$reg_domain = shell_exec("iw reg get | grep -o 'country [A-Z]\{2\}' | awk 'NR==1{print $2}'");
|
|
||||||
|
|
||||||
$cmd = "iw dev ".escapeshellarg($_SESSION['ap_interface'])." info | awk '$1==\"txpower\" {print $2}'";
|
|
||||||
exec($cmd, $txpower);
|
|
||||||
$txpower = intval($txpower[0]);
|
|
||||||
|
|
||||||
if (isset($_POST['interface'])) {
|
if (isset($_POST['interface'])) {
|
||||||
$interface = escapeshellarg($_POST['interface']);
|
$interface = $_POST['interface'];
|
||||||
}
|
} else {
|
||||||
if (!RASPI_MONITOR_ENABLED) {
|
$interface = $_SESSION['ap_interface'];
|
||||||
if (isset($_POST['SaveHostAPDSettings'])) {
|
|
||||||
SaveHostAPDConfig($arrSecurity, $arrEncType, $arr80211Standard, $interfaces, $reg_domain, $status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$arrHostapdConf = [];
|
|
||||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
|
||||||
if (file_exists($hostapdIni)) {
|
|
||||||
$arrHostapdConf = parse_ini_file($hostapdIni);
|
|
||||||
}
|
}
|
||||||
|
$txpower = $hotspot->getTxPower($interface);
|
||||||
|
$arrHostapdConf = $hotspot->getHostapdIni();
|
||||||
|
|
||||||
if (!RASPI_MONITOR_ENABLED) {
|
if (!RASPI_MONITOR_ENABLED) {
|
||||||
if (isset($_POST['StartHotspot']) || isset($_POST['RestartHotspot'])) {
|
if (isset($_POST['StartHotspot']) || isset($_POST['RestartHotspot'])) {
|
||||||
@@ -61,7 +49,7 @@ function DisplayHostAPDConfig()
|
|||||||
} elseif ($arrHostapdConf['WifiAPEnable'] == 1) {
|
} elseif ($arrHostapdConf['WifiAPEnable'] == 1) {
|
||||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface uap0 --seconds 1', $return);
|
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface uap0 --seconds 1', $return);
|
||||||
} else {
|
} else {
|
||||||
// systemctl expects a unit name like raspap-network-activity@wlan0.service, no extra quotes
|
// systemctl expects a unit name like raspap-network-activity@wlan0.service
|
||||||
$iface_nonescaped = $_POST['interface'];
|
$iface_nonescaped = $_POST['interface'];
|
||||||
if (preg_match('/^[a-zA-Z0-9_-]+$/', $iface_nonescaped)) { // validate interface name
|
if (preg_match('/^[a-zA-Z0-9_-]+$/', $iface_nonescaped)) { // validate interface name
|
||||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface ' .$iface_nonescaped. ' --seconds 1', $return);
|
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface ' .$iface_nonescaped. ' --seconds 1', $return);
|
||||||
@@ -72,6 +60,8 @@ function DisplayHostAPDConfig()
|
|||||||
foreach ($return as $line) {
|
foreach ($return as $line) {
|
||||||
$status->addMessage($line, 'info');
|
$status->addMessage($line, 'info');
|
||||||
}
|
}
|
||||||
|
} elseif (isset($_POST['SaveHostAPDSettings'])) {
|
||||||
|
$hotspot->saveSettings($_POST, $arrSecurity, $arrEncType, $arr80211Standard, $interfaces, $reg_domain, $status);
|
||||||
} elseif (isset($_POST['StopHotspot'])) {
|
} elseif (isset($_POST['StopHotspot'])) {
|
||||||
$status->addMessage('Attempting to stop hotspot', 'info');
|
$status->addMessage('Attempting to stop hotspot', 'info');
|
||||||
exec('sudo /bin/systemctl stop hostapd.service', $return);
|
exec('sudo /bin/systemctl stop hostapd.service', $return);
|
||||||
@@ -81,76 +71,37 @@ function DisplayHostAPDConfig()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
|
|
||||||
if (isset($_SESSION['wifi_client_interface'])) {
|
if (isset($_SESSION['wifi_client_interface'])) {
|
||||||
exec('iwgetid '.escapeshellarg($_SESSION['wifi_client_interface']). ' -r', $wifiNetworkID);
|
exec('iwgetid '.escapeshellarg($_SESSION['wifi_client_interface']). ' -r', $wifiNetworkID);
|
||||||
if (!empty($wifiNetworkID[0])) {
|
if (!empty($wifiNetworkID[0])) {
|
||||||
$managedModeEnabled = true;
|
$managedModeEnabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$hostapdstatus = $system->hostapdStatus();
|
|
||||||
$serviceStatus = $hostapdstatus[0] == 0 ? "down" : "up";
|
|
||||||
|
|
||||||
foreach ($hostapdconfig as $hostapdconfigline) {
|
// process txpower user input
|
||||||
if (strlen($hostapdconfigline) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($hostapdconfigline[0] != "#") {
|
|
||||||
$arrLine = explode("=", $hostapdconfigline);
|
|
||||||
$arrConfig[$arrLine[0]]=$arrLine[1];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// assign beacon_int boolean if value is set
|
|
||||||
if (isset($arrConfig['beacon_int'])) {
|
|
||||||
$arrConfig['beacon_interval_bool'] = 1;
|
|
||||||
}
|
|
||||||
// assign disassoc_low_ack boolean if value is set
|
|
||||||
if (isset($arrConfig['disassoc_low_ack'])) {
|
|
||||||
$arrConfig['disassoc_low_ack_bool'] = 1;
|
|
||||||
} else {
|
|
||||||
$arrConfig['disassoc_low_ack_bool'] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// assign country_code from iw reg if not set in config
|
|
||||||
if (empty($arrConfig['country_code']) && isset($country_code[0])) {
|
|
||||||
$arrConfig['country_code'] = $country_code[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// set txpower with iw if value is non-default ('auto')
|
|
||||||
if (isset($_POST['txpower'])) {
|
if (isset($_POST['txpower'])) {
|
||||||
if ($_POST['txpower'] != 'auto') {
|
if ($_POST['txpower'] != 'auto') {
|
||||||
$txpower = intval($_POST['txpower']);
|
$txpower = intval($_POST['txpower']);
|
||||||
$sdBm = $txpower * 100;
|
$hotspot->maybeSetTxPower($interface, $txpower, $status);
|
||||||
exec('sudo /sbin/iw dev '.$interface.' set txpower fixed '.$sdBm, $return);
|
|
||||||
$status->addMessage('Setting transmit power to '.$_POST['txpower'].' dBm.', 'success');
|
|
||||||
$txpower = $_POST['txpower'];
|
|
||||||
} elseif ($_POST['txpower'] == 'auto') {
|
} elseif ($_POST['txpower'] == 'auto') {
|
||||||
exec('sudo /sbin/iw dev '.$interface.' set txpower auto', $return);
|
$hotspot->maybeSetTxPower($interface, 'auto', $status);
|
||||||
$status->addMessage('Setting transmit power to '.$_POST['txpower'].'.', 'success');
|
|
||||||
$txpower = $_POST['txpower'];
|
|
||||||
}
|
}
|
||||||
|
$txpower = $_POST['txpower'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$selectedHwMode = $arrConfig['hw_mode'];
|
// parse hostapd configuration
|
||||||
if (isset($arrConfig['ieee80211n'])) {
|
try {
|
||||||
if (strval($arrConfig['ieee80211n']) === '1') {
|
$arrConfig = $hostapd->getConfig();
|
||||||
$selectedHwMode = 'n';
|
} catch (\RuntimeException $e) {
|
||||||
}
|
error_log('Error: ' . $e->getMessage());
|
||||||
}
|
|
||||||
if (isset($arrConfig['ieee80211ac'])) {
|
|
||||||
if (strval($arrConfig['ieee80211ac']) === '1') {
|
|
||||||
$selectedHwMode = 'ac';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isset($arrConfig['ieee80211w'])) {
|
|
||||||
if (strval($arrConfig['ieee80211w']) === '2') {
|
|
||||||
$selectedHwMode = 'w';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$arrConfig['ignore_broadcast_ssid'] ??= 0;
|
// assign disassoc_low_ack boolean if value is set
|
||||||
$arrConfig['max_num_sta'] ??= 0;
|
$arrConfig['disassoc_low_ack_bool'] = isset($arrConfig['disassoc_low_ack']) ? 1 : 0;
|
||||||
$arrConfig['wep_default_key'] ??= 0;
|
$hostapdstatus = $system->hostapdStatus();
|
||||||
|
$serviceStatus = $hostapdstatus[0] == 0 ? "down" : "up";
|
||||||
|
|
||||||
|
// ensure log is writeable
|
||||||
exec('sudo /bin/chmod o+r '.RASPI_HOSTAPD_LOG);
|
exec('sudo /bin/chmod o+r '.RASPI_HOSTAPD_LOG);
|
||||||
$logdata = getLogLimited(RASPI_HOSTAPD_LOG);
|
$logdata = getLogLimited(RASPI_HOSTAPD_LOG);
|
||||||
|
|
||||||
@@ -163,469 +114,16 @@ function DisplayHostAPDConfig()
|
|||||||
"interfaces",
|
"interfaces",
|
||||||
"arrConfig",
|
"arrConfig",
|
||||||
"arr80211Standard",
|
"arr80211Standard",
|
||||||
"selectedHwMode",
|
|
||||||
"arrSecurity",
|
"arrSecurity",
|
||||||
"arrEncType",
|
"arrEncType",
|
||||||
|
"arr80211w",
|
||||||
"arrTxPower",
|
"arrTxPower",
|
||||||
"txpower",
|
"txpower",
|
||||||
"arrHostapdConf",
|
"arrHostapdConf",
|
||||||
"operatingSystem",
|
"operatingSystem",
|
||||||
"selectedHwMode",
|
|
||||||
"countryCodes",
|
"countryCodes",
|
||||||
"logdata"
|
"logdata"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate user input, save configs for hostapd, dnsmasq & dhcp
|
|
||||||
*
|
|
||||||
* @param array $wpa_array
|
|
||||||
* @param array $enc_types
|
|
||||||
* @param array $modes
|
|
||||||
* @param string $interface
|
|
||||||
* @param string $reg_domain
|
|
||||||
* @param object $status
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_domain, $status)
|
|
||||||
{
|
|
||||||
// It should not be possible to send bad data for these fields.
|
|
||||||
// If wpa fields are absent, return false and log securely.
|
|
||||||
if (!(array_key_exists($_POST['wpa'], $wpa_array)
|
|
||||||
&& array_key_exists($_POST['wpa_pairwise'], $enc_types)
|
|
||||||
&& array_key_exists($_POST['hw_mode'], $modes))
|
|
||||||
) {
|
|
||||||
$err = "Attempting to set hostapd config with wpa='".escapeshellarg($_POST['wpa']);
|
|
||||||
$err .= "', wpa_pairwise='".$escapeshellarg(_POST['wpa_pairwise']);
|
|
||||||
$err .= "and hw_mode='".$escapeshellarg(_POST['hw_mode'])."'";
|
|
||||||
error_log($err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Validate input
|
|
||||||
$good_input = true;
|
|
||||||
|
|
||||||
if (!filter_var($_POST['channel'], FILTER_VALIDATE_INT)) {
|
|
||||||
$status->addMessage('Attempting to set channel to invalid number.', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
}
|
|
||||||
if (intval($_POST['channel']) < 1 || intval($_POST['channel']) > RASPI_5GHZ_CHANNEL_MAX) {
|
|
||||||
$status->addMessage('Attempting to set channel outside of permitted range', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
}
|
|
||||||
$arrHostapdConf = parse_ini_file('/etc/raspap/hostapd.ini');
|
|
||||||
|
|
||||||
// Check for Bridged AP mode checkbox
|
|
||||||
$bridgedEnable = 0;
|
|
||||||
if ($arrHostapdConf['BridgedEnable'] == 0) {
|
|
||||||
if (isset($_POST['bridgedEnable'])) {
|
|
||||||
$bridgedEnable = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isset($_POST['bridgedEnable'])) {
|
|
||||||
$bridgedEnable = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for WiFi client AP mode checkbox
|
|
||||||
$wifiAPEnable = 0;
|
|
||||||
if ($bridgedEnable == 0) { // enable client mode actions when not bridged
|
|
||||||
if ($arrHostapdConf['WifiAPEnable'] == 0) {
|
|
||||||
if (isset($_POST['wifiAPEnable'])) {
|
|
||||||
$wifiAPEnable = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isset($_POST['wifiAPEnable'])) {
|
|
||||||
$wifiAPEnable = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for Logfile output checkbox
|
|
||||||
$logEnable = 0;
|
|
||||||
if ($arrHostapdConf['LogEnable'] == 0) {
|
|
||||||
if (isset($_POST['logEnable'])) {
|
|
||||||
$logEnable = 1;
|
|
||||||
exec('sudo '.RASPI_CONFIG.'/hostapd/enablelog.sh');
|
|
||||||
} else {
|
|
||||||
exec('sudo '.RASPI_CONFIG.'/hostapd/disablelog.sh');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isset($_POST['logEnable'])) {
|
|
||||||
$logEnable = 1;
|
|
||||||
exec('sudo '.RASPI_CONFIG.'/hostapd/enablelog.sh');
|
|
||||||
} else {
|
|
||||||
exec('sudo '.RASPI_CONFIG.'/hostapd/disablelog.sh');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set AP interface default, override for ap-sta & bridged options
|
|
||||||
$iface = validateInterface($_POST['interface']) ? $_POST['interface'] : RASPI_WIFI_AP_INTERFACE;
|
|
||||||
|
|
||||||
$ap_iface = $iface; // the hostap AP interface
|
|
||||||
$cli_iface = $iface; // the wifi client interface
|
|
||||||
$session_iface = $iface; // the interface that the UI needs to monitor for data usage etc.
|
|
||||||
if ($wifiAPEnable) { // for AP-STA we monitor the uap0 interface, which is always the ap interface.
|
|
||||||
$ap_iface = $session_iface = 'uap0';
|
|
||||||
}
|
|
||||||
if ($bridgedEnable) { // for bridged mode we monitor the bridge, but keep the selected interface as AP.
|
|
||||||
$cli_iface = $session_iface = 'br0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// persist user options to /etc/raspap
|
|
||||||
$cfg = [];
|
|
||||||
$cfg['WifiInterface'] = $ap_iface;
|
|
||||||
$cfg['LogEnable'] = $logEnable;
|
|
||||||
// Save previous Client mode status when Bridged
|
|
||||||
$cfg['WifiAPEnable'] = ($bridgedEnable == 1 ? $arrHostapdConf['WifiAPEnable'] : $wifiAPEnable);
|
|
||||||
$cfg['BridgedEnable'] = $bridgedEnable;
|
|
||||||
$cfg['WifiManaged'] = $cli_iface;
|
|
||||||
write_php_ini($cfg, RASPI_CONFIG.'/hostapd.ini');
|
|
||||||
$_SESSION['ap_interface'] = $session_iface;
|
|
||||||
|
|
||||||
// Verify input
|
|
||||||
if (empty($_POST['ssid']) || strlen($_POST['ssid']) > 32) {
|
|
||||||
$status->addMessage('SSID must be between 1 and 32 characters', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
# NB: A pass-phrase is a sequence of between 8 and 63 ASCII-encoded characters (IEEE Std. 802.11i-2004)
|
|
||||||
# Each character in the pass-phrase must have an encoding in the range of 32 to 126 (decimal). (IEEE Std. 802.11i-2004, Annex H.4.1)
|
|
||||||
if ($_POST['wpa'] !== 'none' && (strlen($_POST['wpa_passphrase']) < 8 || strlen($_POST['wpa_passphrase']) > 63)) {
|
|
||||||
$status->addMessage('WPA passphrase must be between 8 and 63 characters', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
} elseif (!ctype_print($_POST['wpa_passphrase'])) {
|
|
||||||
$status->addMessage('WPA passphrase must be comprised of printable ASCII characters', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ignore_broadcast_ssid = $_POST['hiddenSSID'] ?? '0';
|
|
||||||
if (!ctype_digit($ignore_broadcast_ssid)) {
|
|
||||||
$status->addMessage('Parameter hiddenSSID not a number.', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
} elseif ((int)$ignore_broadcast_ssid < 0 || (int)$ignore_broadcast_ssid >= 3) {
|
|
||||||
$status->addMessage('Parameter hiddenSSID contains an invalid configuration value.', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! in_array($_POST['interface'], $interfaces)) {
|
|
||||||
$status->addMessage('Unknown interface '.htmlspecialchars($_POST['interface'], ENT_QUOTES), 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
}
|
|
||||||
if (strlen($_POST['country_code']) !== 0 && strlen($_POST['country_code']) != 2) {
|
|
||||||
$status->addMessage('Country code must be blank or two characters', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
} else {
|
|
||||||
$country_code = $_POST['country_code'];
|
|
||||||
}
|
|
||||||
if (isset($_POST['beaconintervalEnable'])) {
|
|
||||||
if (!is_numeric($_POST['beacon_interval'])) {
|
|
||||||
$status->addMessage('Beacon interval must be a numeric value', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
} elseif ($_POST['beacon_interval'] < 15 || $_POST['beacon_interval'] > 65535) {
|
|
||||||
$status->addMessage('Beacon interval must be between 15 and 65535', 'danger');
|
|
||||||
$good_input = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$_POST['max_num_sta'] = (int) $_POST['max_num_sta'];
|
|
||||||
$_POST['max_num_sta'] = $_POST['max_num_sta'] > 2007 ? 2007 : $_POST['max_num_sta'];
|
|
||||||
$_POST['max_num_sta'] = $_POST['max_num_sta'] < 1 ? null : $_POST['max_num_sta'];
|
|
||||||
|
|
||||||
if ($good_input) {
|
|
||||||
$return = updateHostapdConfig($ignore_broadcast_ssid,$wifiAPEnable,$bridgedEnable);
|
|
||||||
|
|
||||||
if (trim($country_code) != trim($reg_domain)) {
|
|
||||||
$return = iwRegSet($country_code, $status);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch dhcp-range, lease time from system config
|
|
||||||
$syscfg = parse_ini_file(RASPI_DNSMASQ_PREFIX.$ap_iface.'.conf', false, INI_SCANNER_RAW);
|
|
||||||
|
|
||||||
if ($wifiAPEnable == 1) {
|
|
||||||
// Enable uap0 configuration for ap-sta mode
|
|
||||||
// Set dhcp-range from system config, fallback to default if undefined
|
|
||||||
$dhcp_range = ($syscfg['dhcp-range'] == '') ? getDefaultNetValue('dnsmasq','uap0','dhcp-range') : $syscfg['dhcp-range'];
|
|
||||||
$config = [ '# RaspAP uap0 configuration' ];
|
|
||||||
$config[] = 'interface=lo,uap0 # Enable uap0 interface for wireless client AP mode';
|
|
||||||
$config[] = 'bind-dynamic # Hybrid between --bind-interfaces and default';
|
|
||||||
$config[] = 'server=8.8.8.8 # Forward DNS requests to Google DNS';
|
|
||||||
$config[] = 'domain-needed # Don\'t forward short names';
|
|
||||||
$config[] = 'bogus-priv # Never forward addresses in the non-routed address spaces';
|
|
||||||
$config[] = 'dhcp-range='.$dhcp_range;
|
|
||||||
if (!empty($syscfg['dhcp-option'])) {
|
|
||||||
$config[] = 'dhcp-option='.$syscfg['dhcp-option'];
|
|
||||||
}
|
|
||||||
$config[] = PHP_EOL;
|
|
||||||
scanConfigDir('/etc/dnsmasq.d/','uap0',$status);
|
|
||||||
$config = join(PHP_EOL, $config);
|
|
||||||
file_put_contents("/tmp/dnsmasqdata", $config);
|
|
||||||
system('sudo cp /tmp/dnsmasqdata '.RASPI_DNSMASQ_PREFIX.$ap_iface.'.conf', $return);
|
|
||||||
} elseif ($bridgedEnable !==1) {
|
|
||||||
$dhcp_range = ($syscfg['dhcp-range'] =='') ? getDefaultNetValue('dnsmasq',$ap_iface,'dhcp-range') : $syscfg['dhcp-range'];
|
|
||||||
$config = [ '# RaspAP '.$_POST['interface'].' configuration' ];
|
|
||||||
$config[] = 'interface='.$_POST['interface'];
|
|
||||||
$config[] = 'domain-needed';
|
|
||||||
$config[] = 'dhcp-range='.$dhcp_range;
|
|
||||||
if (!empty($syscfg['dhcp-option'])) {
|
|
||||||
$config[] = 'dhcp-option='.$syscfg['dhcp-option'];
|
|
||||||
}
|
|
||||||
$config[] = PHP_EOL;
|
|
||||||
$config = join(PHP_EOL, $config);
|
|
||||||
file_put_contents("/tmp/dnsmasqdata", $config);
|
|
||||||
system('sudo cp /tmp/dnsmasqdata '.RASPI_DNSMASQ_PREFIX.$ap_iface.'.conf', $return);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set dhcp values from system config, fallback to default if undefined
|
|
||||||
$jsonData = json_decode(getNetConfig($ap_iface), true);
|
|
||||||
$ip_address = empty($jsonData['StaticIP'])
|
|
||||||
? getDefaultNetValue('dhcp', $ap_iface, 'static ip_address') : $jsonData['StaticIP'];
|
|
||||||
$domain_name_server = empty($jsonData['StaticDNS'])
|
|
||||||
? getDefaultNetValue('dhcp', $ap_iface, 'static domain_name_server') : $jsonData['StaticDNS'];
|
|
||||||
$routers = empty($jsonData['StaticRouters'])
|
|
||||||
? getDefaultNetValue('dhcp', $ap_iface, 'static routers') : $jsonData['StaticRouters'];
|
|
||||||
$netmask = (empty($jsonData['SubnetMask']) || $jsonData['SubnetMask'] === '0.0.0.0')
|
|
||||||
? getDefaultNetValue('dhcp', $ap_iface, 'subnetmask') : $jsonData['SubnetMask'];
|
|
||||||
if (isset($ip_address) && !preg_match('/.*\/\d+/', $ip_address)) {
|
|
||||||
$ip_address.='/'.mask2cidr($netmask);
|
|
||||||
}
|
|
||||||
if ($bridgedEnable == 1) {
|
|
||||||
$config = array_keys(getDefaultNetOpts('dhcp','options'));
|
|
||||||
$config[] = PHP_EOL.'# RaspAP br0 configuration';
|
|
||||||
$config[] = 'denyinterfaces eth0 wlan0';
|
|
||||||
$config[] = 'interface br0';
|
|
||||||
$config[] = PHP_EOL;
|
|
||||||
} elseif ($wifiAPEnable == 1) {
|
|
||||||
$config = array_keys(getDefaultNetOpts('dhcp','options'));
|
|
||||||
$config[] = PHP_EOL.'# RaspAP uap0 configuration';
|
|
||||||
$config[] = 'interface uap0';
|
|
||||||
$config[] = 'static ip_address='.$ip_address;
|
|
||||||
$config[] = 'nohook wpa_supplicant';
|
|
||||||
$config[] = PHP_EOL;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$config = updateDhcpcdConfig($ap_iface, $jsonData, $ip_address, $routers, $domain_name_server);
|
|
||||||
}
|
|
||||||
|
|
||||||
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
|
||||||
|
|
||||||
$skip_dhcp = false;
|
|
||||||
if (preg_match('/wlan[2-9]\d*|wlan[1-9]\d+/', $ap_iface)) {
|
|
||||||
$skip_dhcp = true;
|
|
||||||
} elseif ($bridgedEnable == 1 || $wifiAPEnable == 1) {
|
|
||||||
$dhcp_cfg = join(PHP_EOL, $config);
|
|
||||||
$status->addMessage(sprintf(_('DHCP configuration for %s enabled.'), $ap_iface), 'success');
|
|
||||||
} elseif (!preg_match('/^interface\s'.$ap_iface.'$/m', $dhcp_cfg)) {
|
|
||||||
$config[] = PHP_EOL;
|
|
||||||
$config= join(PHP_EOL, $config);
|
|
||||||
$dhcp_cfg = removeDHCPIface($dhcp_cfg,'br0');
|
|
||||||
$dhcp_cfg = removeDHCPIface($dhcp_cfg,'uap0');
|
|
||||||
$dhcp_cfg .= $config;
|
|
||||||
$status->addMessage(sprintf(_('DHCP configuration for %s added.'), $ap_iface), 'success');
|
|
||||||
} else {
|
|
||||||
$config = join(PHP_EOL, $config);
|
|
||||||
$dhcp_cfg = removeDHCPIface($dhcp_cfg,'br0');
|
|
||||||
$dhcp_cfg = removeDHCPIface($dhcp_cfg,'uap0');
|
|
||||||
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$ap_iface.'\s.*?(?=\s*^\s*$)/ms', $config, $dhcp_cfg, 1);
|
|
||||||
$status->addMessage(sprintf(_('DHCP configuration for %s updated.'), $ap_iface), 'success');
|
|
||||||
}
|
|
||||||
if (!$skip_dhcp) {
|
|
||||||
file_put_contents("/tmp/dhcpddata", $dhcp_cfg);
|
|
||||||
system('sudo cp /tmp/dhcpddata '.RASPI_DHCPCD_CONFIG, $return);
|
|
||||||
if ($return == 0) {
|
|
||||||
$status->addMessage('Wifi Hotspot settings saved', 'success');
|
|
||||||
} else {
|
|
||||||
$status->addMessage('Unable to save wifi hotspot settings', 'danger');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$status->addMessage(sprintf(_('Interface %s has no default settings.'), $ap_iface), 'warning');
|
|
||||||
$status->addMessage(('Configure settings in <strong>DHCP Server</strong> before starting AP.'), 'warning');
|
|
||||||
$status->addMessage('Wifi Hotspot settings saved', 'success');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$status->addMessage('Unable to save wifi hotspot settings', 'danger');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a hostapd configuration
|
|
||||||
*
|
|
||||||
* @return boolean $result
|
|
||||||
*/
|
|
||||||
function updateHostapdConfig($ignore_broadcast_ssid,$wifiAPEnable,$bridgedEnable)
|
|
||||||
{
|
|
||||||
// Fixed values
|
|
||||||
$country_code = $_POST['country_code'];
|
|
||||||
$config = 'driver=nl80211'.PHP_EOL;
|
|
||||||
$config.= 'ctrl_interface='.RASPI_HOSTAPD_CTRL_INTERFACE.PHP_EOL;
|
|
||||||
$config.= 'ctrl_interface_group=0'.PHP_EOL;
|
|
||||||
$config.= 'auth_algs=1'.PHP_EOL;
|
|
||||||
$config.= 'wpa_key_mgmt=WPA-PSK'.PHP_EOL;
|
|
||||||
if (isset($_POST['beaconintervalEnable'])) {
|
|
||||||
$config.= 'beacon_int='.$_POST['beacon_interval'].PHP_EOL;
|
|
||||||
}
|
|
||||||
if (isset($_POST['disassoc_low_ackEnable'])) {
|
|
||||||
$config.= 'disassoc_low_ack=0'.PHP_EOL;
|
|
||||||
}
|
|
||||||
$config.= 'ssid='.$_POST['ssid'].PHP_EOL;
|
|
||||||
$config.= 'channel='.$_POST['channel'].PHP_EOL;
|
|
||||||
|
|
||||||
// Set VHT center frequency segment value
|
|
||||||
if ((int)$_POST['channel'] < RASPI_5GHZ_CHANNEL_MIN) {
|
|
||||||
$vht_freq_idx = 42;
|
|
||||||
} else {
|
|
||||||
$vht_freq_idx = 155;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_POST['hw_mode'] === 'n') {
|
|
||||||
$config.= 'hw_mode=g'.PHP_EOL;
|
|
||||||
$config.= 'ieee80211n=1'.PHP_EOL;
|
|
||||||
// Enable basic Quality of service
|
|
||||||
$config.= 'wmm_enabled=1'.PHP_EOL;
|
|
||||||
} elseif ($_POST['hw_mode'] === 'ac') {
|
|
||||||
$config.= 'hw_mode=a'.PHP_EOL.PHP_EOL;
|
|
||||||
$config.= '# N'.PHP_EOL;
|
|
||||||
$config.= 'ieee80211n=1'.PHP_EOL;
|
|
||||||
$config.= 'require_ht=1'.PHP_EOL;
|
|
||||||
$config.= 'ht_capab=[MAX-AMSDU-3839][HT40+][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]'.PHP_EOL.PHP_EOL;
|
|
||||||
$config.= '# AC'.PHP_EOL;
|
|
||||||
$config.= 'ieee80211ac=1'.PHP_EOL;
|
|
||||||
$config.= 'require_vht=1'.PHP_EOL;
|
|
||||||
$config.= 'ieee80211d=0'.PHP_EOL;
|
|
||||||
$config.= 'ieee80211h=0'.PHP_EOL;
|
|
||||||
$config.= 'vht_capab=[MAX-AMSDU-3839][SHORT-GI-80]'.PHP_EOL;
|
|
||||||
$config.= 'vht_oper_chwidth=1'.PHP_EOL;
|
|
||||||
$config.= 'vht_oper_centr_freq_seg0_idx='.$vht_freq_idx.PHP_EOL.PHP_EOL;
|
|
||||||
} elseif ($_POST['hw_mode'] === 'w') {
|
|
||||||
$config.= 'ieee80211w=2'.PHP_EOL;
|
|
||||||
$config.= 'wpa_key_mgmt=WPA-EAP-SHA256'.PHP_EOL;
|
|
||||||
} else {
|
|
||||||
$config.= 'hw_mode='.$_POST['hw_mode'].PHP_EOL;
|
|
||||||
$config.= 'ieee80211n=0'.PHP_EOL;
|
|
||||||
}
|
|
||||||
if ($_POST['wpa'] !== 'none') {
|
|
||||||
$config.= 'wpa_passphrase='.$_POST['wpa_passphrase'].PHP_EOL;
|
|
||||||
}
|
|
||||||
if ($wifiAPEnable == 1) {
|
|
||||||
$config.= 'interface=uap0'.PHP_EOL;
|
|
||||||
} elseif ($bridgedEnable == 1) {
|
|
||||||
$config.='interface='.$_POST['interface'].PHP_EOL;
|
|
||||||
$config.= 'bridge=br0'.PHP_EOL;
|
|
||||||
} else {
|
|
||||||
$config.= 'interface='.$_SESSION['ap_interface'].PHP_EOL;
|
|
||||||
}
|
|
||||||
$config.= 'wpa='.$_POST['wpa'].PHP_EOL;
|
|
||||||
$config.= 'wpa_pairwise='.$_POST['wpa_pairwise'].PHP_EOL;
|
|
||||||
$config.= 'country_code='.$_POST['country_code'].PHP_EOL;
|
|
||||||
$config.= 'ignore_broadcast_ssid='.$ignore_broadcast_ssid.PHP_EOL;
|
|
||||||
if (isset($_POST['max_num_sta'])) {
|
|
||||||
$config.= 'max_num_sta='.$_POST['max_num_sta'].PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
$config.= parseUserHostapdCfg();
|
|
||||||
|
|
||||||
file_put_contents("/tmp/hostapddata", $config);
|
|
||||||
system("sudo cp /tmp/hostapddata " . RASPI_HOSTAPD_CONFIG, $result);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the dhcpcd configuration for a given interface, preserving existing settings
|
|
||||||
*
|
|
||||||
* @param string $ap_iface
|
|
||||||
* @param array $jsonData
|
|
||||||
* @param string $ip_address
|
|
||||||
* @param string $routers
|
|
||||||
* @param string $domain_name_server
|
|
||||||
* @return array updated configuration
|
|
||||||
*/
|
|
||||||
function updateDhcpcdConfig($ap_iface, $jsonData, $ip_address, $routers, $domain_name_server) {
|
|
||||||
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
|
||||||
$existing_config = [];
|
|
||||||
$section_regex = '/^#\sRaspAP\s'.preg_quote($ap_iface, '/').'\s.*?(?=\s*^\s*$)/ms';
|
|
||||||
|
|
||||||
// extract existing interface configuration
|
|
||||||
if (preg_match($section_regex, $dhcp_cfg, $matches)) {
|
|
||||||
$lines = explode(PHP_EOL, $matches[0]);
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
$line = trim($line);
|
|
||||||
if (preg_match('/^(interface|static|metric|nogateway|nohook)/', $line)) {
|
|
||||||
$existing_config[] = $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize with comment
|
|
||||||
$config = [ '# RaspAP '.$ap_iface.' configuration' ];
|
|
||||||
$config[] = 'interface '.$ap_iface;
|
|
||||||
$static_settings = [
|
|
||||||
'static ip_address' => $ip_address,
|
|
||||||
'static routers' => $routers,
|
|
||||||
'static domain_name_server' => $domain_name_server
|
|
||||||
];
|
|
||||||
|
|
||||||
// merge existing settings with updates
|
|
||||||
foreach ($existing_config as $line) {
|
|
||||||
$matched = false;
|
|
||||||
foreach ($static_settings as $key => $value) {
|
|
||||||
if (strpos($line, $key) === 0) {
|
|
||||||
$config[] = "$key=$value";
|
|
||||||
$matched = true;
|
|
||||||
unset($static_settings[$key]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$matched && !preg_match('/^interface/', $line)) {
|
|
||||||
$config[] = $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add any new static settings
|
|
||||||
foreach ($static_settings as $key => $value) {
|
|
||||||
$config[] = "$key=$value";
|
|
||||||
}
|
|
||||||
|
|
||||||
// add metric if provided
|
|
||||||
if (!empty($jsonData['Metric']) && !in_array('metric '.$jsonData['Metric'], $config)) {
|
|
||||||
$config[] = 'metric '.$jsonData['Metric'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes iw to set the specified ISO 2-letter country code
|
|
||||||
*
|
|
||||||
* @param string $country_code
|
|
||||||
* @param object $status
|
|
||||||
* @return boolean $result
|
|
||||||
*/
|
|
||||||
function iwRegSet(string $country_code, $status)
|
|
||||||
{
|
|
||||||
$country_code = escapeshellarg($country_code);
|
|
||||||
$result = shell_exec("sudo iw reg set $country_code");
|
|
||||||
$status->addMessage(sprintf(_('Setting wireless regulatory domain to %s'), $country_code, 'success'));
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses optional /etc/hostapd/hostapd.conf.users file
|
|
||||||
*
|
|
||||||
* @return string $tmp
|
|
||||||
*/
|
|
||||||
function parseUserHostapdCfg()
|
|
||||||
{
|
|
||||||
if (file_exists(RASPI_HOSTAPD_CONFIG . '.users')) {
|
|
||||||
exec('cat '. RASPI_HOSTAPD_CONFIG . '.users', $hostapdconfigusers);
|
|
||||||
foreach ($hostapdconfigusers as $hostapdconfigusersline) {
|
|
||||||
if (strlen($hostapdconfigusersline) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($hostapdconfigusersline[0] != "#") {
|
|
||||||
$arrLine = explode("=", $hostapdconfigusersline);
|
|
||||||
$tmp.= $arrLine[0]."=".$arrLine[1].PHP_EOL;;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fetches details of the kernel routing table
|
* Fetches details of the kernel routing table
|
||||||
*
|
*
|
||||||
@@ -32,14 +31,19 @@ function getRouteInfo($checkAccess)
|
|||||||
if (!empty($routes)) {
|
if (!empty($routes)) {
|
||||||
foreach ($routes as $i => $route) {
|
foreach ($routes as $i => $route) {
|
||||||
$prop = explode(' ', $route);
|
$prop = explode(' ', $route);
|
||||||
$rInfo[$i]["interface"] = $prop[0];
|
$rInfo[$i]["interface"] = $dev = $prop[0];
|
||||||
$rInfo[$i]["ip-address"] = $prop[1];
|
$rInfo[$i]["ip-address"] = $prop[1];
|
||||||
$rInfo[$i]["gateway"] = $prop[2];
|
$rInfo[$i]["gateway"] = $prop[2];
|
||||||
// resolve the name of the gateway (if possible)
|
// resolve the name of the gateway (if possible)
|
||||||
unset($host);
|
unset($host);
|
||||||
exec('host ' . $prop[2] . ' | sed -rn "s/.*domain name pointer (.*)\./\1/p" | head -n 1', $host);
|
exec('host ' . $prop[2] . ' | sed -rn "s/.*domain name pointer (.*)\./\1/p" | head -n 1', $host);
|
||||||
$rInfo[$i]["gw-name"] = empty($host) ? "*" : $host[0];
|
$rInfo[$i]["gw-name"] = empty($host) ? "*" : $host[0];
|
||||||
if (isset($checkAccess) && $checkAccess) {
|
// check if AP
|
||||||
|
unset($isAP);
|
||||||
|
exec("iwconfig $dev 2> /dev/null | sed -rn 's/.*(mode:master).*/1/ip'", $isAP);
|
||||||
|
$isAP = !empty($isAP);
|
||||||
|
$rInfo[$i]["isAP"] = $isAP;
|
||||||
|
if (isset($checkAccess) && $checkAccess && !$isAP) {
|
||||||
// check internet connectivity w/ and w/o DNS resolution
|
// check internet connectivity w/ and w/o DNS resolution
|
||||||
unset($okip);
|
unset($okip);
|
||||||
exec('ping -W1 -c 1 -I ' . $prop[0] . ' ' . RASPI_ACCESS_CHECK_IP . ' | sed -rn "s/.*icmp_seq=1.*time=.*/OK/p"', $okip);
|
exec('ping -W1 -c 1 -I ' . $prop[0] . ' ' . RASPI_ACCESS_CHECK_IP . ' | sed -rn "s/.*icmp_seq=1.*time=.*/OK/p"', $okip);
|
||||||
@@ -47,6 +51,7 @@ function getRouteInfo($checkAccess)
|
|||||||
unset($okdns);
|
unset($okdns);
|
||||||
exec('ping -W1 -c 1 -I ' . $prop[0] . ' ' . RASPI_ACCESS_CHECK_DNS . ' | sed -rn "s/.*icmp_seq=1.*time=.*/OK/p"', $okdns);
|
exec('ping -W1 -c 1 -I ' . $prop[0] . ' ' . RASPI_ACCESS_CHECK_DNS . ' | sed -rn "s/.*icmp_seq=1.*time=.*/OK/p"', $okdns);
|
||||||
$rInfo[$i]["access-dns"] = empty($okdns) ? false : true;
|
$rInfo[$i]["access-dns"] = empty($okdns) ? false : true;
|
||||||
|
$rInfo[$i]["access-url"] = preg_match('/OK.*/',checkHTTPAccess($prop[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -55,6 +60,70 @@ function getRouteInfo($checkAccess)
|
|||||||
return $rInfo;
|
return $rInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function detectCaptivePortal($iface) {
|
||||||
|
$result=checkHTTPAccess($iface, true);
|
||||||
|
$checkConnect=array( "state"=>"FAILED", "URL"=>"", "interface"=> $iface, "url" => "" );
|
||||||
|
if ( !empty($result) && !preg_match('/FAILED/i',$result) ) {
|
||||||
|
$checkConnect["state"]=preg_match('/(PORTAL|OK)/i',$result);
|
||||||
|
if ( preg_match('/PORTAL (.*)/i',$result ,$url) && !empty($url) ) {
|
||||||
|
$checkConnect["URL"]=$url[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $checkConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHTTPAccess($iface, $detectPortal=false) {
|
||||||
|
|
||||||
|
$ret="FAILED no HTTP access";
|
||||||
|
exec('timeout 5 curl -is ' . RASPI_ACCESS_CHECK_URL . ' --interface ' . $iface, $rcurl);
|
||||||
|
if ( !empty($rcurl) && preg_match("/^HTTP\/[0-9\.]+ ([0-9]+)/m",$rcurl=implode("\n",$rcurl),$code) ) {
|
||||||
|
$code = $code[1];
|
||||||
|
if ( $code == 200 ) {
|
||||||
|
if ( preg_match("/<meta\s*http-equiv=\"refresh\".*content=\".*url=([^\s]+)\".*>/", $rcurl, $url) ) {
|
||||||
|
$code = 302;
|
||||||
|
$rcurl = "Location: " . $url[1];
|
||||||
|
unset($url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch($code) {
|
||||||
|
case 302:
|
||||||
|
case 307:
|
||||||
|
if ( $detectPortal ) {
|
||||||
|
if ( preg_match("/^Location:\s*(https?:\/\/[^?[:space:]]+)/m", $rcurl, $url) ) {
|
||||||
|
$url=$url[1];
|
||||||
|
if ( preg_match('/^https?:\/\/([^:\/]*).*/i', $url, $srv) && isset($srv[1]) ) {
|
||||||
|
$srv=$srv[1];
|
||||||
|
if ( preg_match('/^(([0-9]{1,3}\.){3}[0-9]{1,3}).*/', $srv, $ip) && isset($ip[1]) ) {
|
||||||
|
$ret="PORTAL " . $url;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
exec('timeout 7 sudo nmap --script=broadcast-dhcp-discover -e ' . $iface . ' 2> /dev/null | sed -rn "s/.*Domain Name Server:\s*(([0-9]{1,3}\.){3}[0-9]{1,3}).*/\1/pi"', $nameserver);
|
||||||
|
if ( !empty($nameserver) ) {
|
||||||
|
$nameserver=$nameserver[0];
|
||||||
|
exec('host ' . $srv . ' ' . $nameserver . ' | sed -rn "s/.*has address ((([0-9]{1,3}\.){3}[0-9]{1,3})).*/\1/p"', $ip2);
|
||||||
|
if ( !empty($ip2) ) {
|
||||||
|
$ip2=$ip2[0];
|
||||||
|
$url=preg_replace("/" . $srv . "/",$ip2,$url);
|
||||||
|
$ret="PORTAL " . $url;
|
||||||
|
}
|
||||||
|
else $ret="FAILED name " . $srv . " could not be resolved";
|
||||||
|
}
|
||||||
|
else $ret="FAILED no name server";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RASPI_ACCESS_CHECK_URL_CODE:
|
||||||
|
$ret="OK internet access";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$ret="FAILED unexpected response " . $code[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Fetches raw output of ip route
|
* Fetches raw output of ip route
|
||||||
*
|
*
|
||||||
@@ -65,4 +134,3 @@ function getRouteInfoRaw()
|
|||||||
exec('ip route list', $routes);
|
exec('ip route list', $routes);
|
||||||
return $routes;
|
return $routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +1,43 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Sets locale information for i18n support
|
* Sets locale information for i18n support, with secure input validation.
|
||||||
*/
|
*
|
||||||
|
* Rudimentary language detection is performed via the browser.
|
||||||
/**
|
|
||||||
* Rudimentary language detection via the browser.
|
|
||||||
* Accept-Language returns a list of weighted values with a quality (or 'q') parameter.
|
* Accept-Language returns a list of weighted values with a quality (or 'q') parameter.
|
||||||
* A better method would parse the list of preferred languages and match this with
|
* A better method would parse the list of preferred languages and match this with
|
||||||
* the languages supported by our platform.
|
* the languages supported by our platform.
|
||||||
|
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
||||||
*
|
*
|
||||||
* Refer to: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
* Uses $validLocales to mitigate OS Command Injection (CWE-78)
|
||||||
|
* @see Vulnerability Report for JVN #27202136
|
||||||
*/
|
*/
|
||||||
if (empty($_SESSION['locale']) && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2) {
|
|
||||||
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
|
|
||||||
switch ($lang) {
|
|
||||||
case "de":
|
|
||||||
$locale = "de_DE.UTF-8";
|
|
||||||
break;
|
|
||||||
case "fr":
|
|
||||||
$locale = "fr_FR.UTF-8";
|
|
||||||
break;
|
|
||||||
case "it":
|
|
||||||
$locale = "it_IT.UTF-8";
|
|
||||||
break;
|
|
||||||
case "pt":
|
|
||||||
$locale = "pt_BR.UTF-8";
|
|
||||||
break;
|
|
||||||
case "sv":
|
|
||||||
$locale = "sv_SE.UTF-8";
|
|
||||||
break;
|
|
||||||
case "nl":
|
|
||||||
$locale = "nl_NL.UTF-8";
|
|
||||||
break;
|
|
||||||
case "zh":
|
|
||||||
if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] == 'zh_TW') {
|
|
||||||
$locale = "zh_TW.UTF-8";
|
|
||||||
} else {
|
|
||||||
$locale = "zh_CN.UTF-8";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "cs":
|
|
||||||
$locale = "cs_CZ.UTF-8";
|
|
||||||
break;
|
|
||||||
case "ru":
|
|
||||||
$locale = "ru_RU.UTF-8";
|
|
||||||
break;
|
|
||||||
case "es":
|
|
||||||
$locale = "es_MX.UTF-8";
|
|
||||||
break;
|
|
||||||
case "fi":
|
|
||||||
$locale = "fi_FI.UTF-8";
|
|
||||||
break;
|
|
||||||
case "da":
|
|
||||||
$locale = "da_DK.UTF-8";
|
|
||||||
break;
|
|
||||||
case "tr":
|
|
||||||
$locale = "tr_TR.UTF-8";
|
|
||||||
break;
|
|
||||||
case "id":
|
|
||||||
$locale = "id_ID.UTF-8";
|
|
||||||
break;
|
|
||||||
case "ko":
|
|
||||||
$locale = "ko_KR.UTF-8";
|
|
||||||
break;
|
|
||||||
case "ja":
|
|
||||||
$locale = "ja_JP.UTF-8";
|
|
||||||
break;
|
|
||||||
case "vi":
|
|
||||||
$locale = "vi_VN.UTF-8";
|
|
||||||
break;
|
|
||||||
case "el":
|
|
||||||
$locale = "el_GR.UTF-8";
|
|
||||||
break;
|
|
||||||
case "pl":
|
|
||||||
$locale = "pl_PL.UTF-8";
|
|
||||||
break;
|
|
||||||
case "sk":
|
|
||||||
$locale = "sk_SK.UTF-8";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$locale = "en_GB.UTF-8";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$_SESSION['locale'] = $locale;
|
// Set locale from POST, if provided and valid
|
||||||
|
$validLocales = array_keys(getLocales());
|
||||||
|
if (!empty($_POST['locale']) && in_array($_POST['locale'], $validLocales, true)) {
|
||||||
|
$_SESSION['locale'] = $_POST['locale'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: the associated locale must be installed on the RPi
|
// Set locale from browser detection, if not already set
|
||||||
// Use: 'sudo raspi-configure' and select 'Localisation Options'
|
if (empty($_SESSION['locale'])) {
|
||||||
|
$_SESSION['locale'] = detectBrowserLocale();
|
||||||
// activate the locale setting
|
|
||||||
if (!empty($_SESSION['locale'])) {
|
|
||||||
putenv("LANG=" . $_SESSION['locale']);
|
|
||||||
setlocale(LC_ALL, $_SESSION['locale']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce only valid locale values in session
|
||||||
|
if (!in_array($_SESSION['locale'], $validLocales, true)) {
|
||||||
|
$_SESSION['locale'] = 'en_GB.UTF-8';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply locale settings
|
||||||
|
putenv("LANG=" . escapeshellarg($_SESSION['locale']));
|
||||||
|
setlocale(LC_ALL, $_SESSION['locale']);
|
||||||
bindtextdomain(LOCALE_DOMAIN, LOCALE_ROOT);
|
bindtextdomain(LOCALE_DOMAIN, LOCALE_ROOT);
|
||||||
bind_textdomain_codeset(LOCALE_DOMAIN, 'UTF-8');
|
bind_textdomain_codeset(LOCALE_DOMAIN, 'UTF-8');
|
||||||
|
|
||||||
textdomain(LOCALE_DOMAIN);
|
textdomain(LOCALE_DOMAIN);
|
||||||
|
|
||||||
function getLocales()
|
function getLocales(): array
|
||||||
{
|
{
|
||||||
$arrLocales = array(
|
return [
|
||||||
'en_GB.UTF-8' => 'English',
|
'en_GB.UTF-8' => 'English',
|
||||||
'cs_CZ.UTF-8' => 'Čeština',
|
'cs_CZ.UTF-8' => 'Čeština',
|
||||||
'zh_TW.UTF-8' => '正體中文 (Chinese traditional)',
|
'zh_TW.UTF-8' => '正體中文 (Chinese traditional)',
|
||||||
@@ -125,6 +61,65 @@ function getLocales()
|
|||||||
'sv_SE.UTF-8' => 'Svenska',
|
'sv_SE.UTF-8' => 'Svenska',
|
||||||
'tr_TR.UTF-8' => 'Türkçe',
|
'tr_TR.UTF-8' => 'Türkçe',
|
||||||
'vi_VN.UTF-8' => 'Tiếng Việt (Vietnamese)'
|
'vi_VN.UTF-8' => 'Tiếng Việt (Vietnamese)'
|
||||||
);
|
];
|
||||||
return $arrLocales;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function detectBrowserLocale(): string
|
||||||
|
{
|
||||||
|
if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) || strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) < 2) {
|
||||||
|
return 'en_GB.UTF-8';
|
||||||
|
}
|
||||||
|
|
||||||
|
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
||||||
|
$lang = strtolower(substr($acceptLang, 0, 2));
|
||||||
|
|
||||||
|
if ($lang === 'zh' && strpos($acceptLang, 'zh-TW') === 0) {
|
||||||
|
return 'zh_TW.UTF-8';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($lang) {
|
||||||
|
case 'de':
|
||||||
|
return 'de_DE.UTF-8';
|
||||||
|
case 'fr':
|
||||||
|
return 'fr_FR.UTF-8';
|
||||||
|
case 'it':
|
||||||
|
return 'it_IT.UTF-8';
|
||||||
|
case 'pt':
|
||||||
|
return 'pt_BR.UTF-8';
|
||||||
|
case 'sv':
|
||||||
|
return 'sv_SE.UTF-8';
|
||||||
|
case 'nl':
|
||||||
|
return 'nl_NL.UTF-8';
|
||||||
|
case 'zh':
|
||||||
|
return 'zh_CN.UTF-8';
|
||||||
|
case 'cs':
|
||||||
|
return 'cs_CZ.UTF-8';
|
||||||
|
case 'ru':
|
||||||
|
return 'ru_RU.UTF-8';
|
||||||
|
case 'es':
|
||||||
|
return 'es_MX.UTF-8';
|
||||||
|
case 'fi':
|
||||||
|
return 'fi_FI.UTF-8';
|
||||||
|
case 'da':
|
||||||
|
return 'da_DK.UTF-8';
|
||||||
|
case 'tr':
|
||||||
|
return 'tr_TR.UTF-8';
|
||||||
|
case 'id':
|
||||||
|
return 'id_ID.UTF-8';
|
||||||
|
case 'ko':
|
||||||
|
return 'ko_KR.UTF-8';
|
||||||
|
case 'ja':
|
||||||
|
return 'ja_JP.UTF-8';
|
||||||
|
case 'vi':
|
||||||
|
return 'vi_VN.UTF-8';
|
||||||
|
case 'el':
|
||||||
|
return 'el_GR.UTF-8';
|
||||||
|
case 'pl':
|
||||||
|
return 'pl_PL.UTF-8';
|
||||||
|
case 'sk':
|
||||||
|
return 'sk_SK.UTF-8';
|
||||||
|
default:
|
||||||
|
return 'en_GB.UTF-8';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'includes/internetRoute.php';
|
require_once 'includes/internetRoute.php';
|
||||||
|
require_once 'includes/functions.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Displays a networking summary and network diagnostic tools
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
function DisplayNetworkingConfig()
|
function DisplayNetworkingConfig(&$extraFooterScripts)
|
||||||
{
|
{
|
||||||
$status = new \RaspAP\Messages\StatusMessage;
|
$status = new \RaspAP\Messages\StatusMessage;
|
||||||
|
|
||||||
@@ -23,4 +23,5 @@ function DisplayNetworkingConfig()
|
|||||||
"routeInfoRaw",
|
"routeInfoRaw",
|
||||||
"bridgedEnabled")
|
"bridgedEnabled")
|
||||||
);
|
);
|
||||||
|
$extraFooterScripts[] = array('src'=>'app/js/vendor/speedtestUI.js', 'defer'=>false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'includes/config.php';
|
require_once 'includes/config.php';
|
||||||
require_once 'includes/wifi_functions.php';
|
|
||||||
|
|
||||||
getWifiInterface();
|
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||||
|
$wifi = new WiFiManager();
|
||||||
|
$wifi->getWifiInterface();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage OpenVPN configuration
|
* Manage OpenVPN configuration
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ $pluginManager = \RaspAP\Plugins\PluginManager::getInstance();
|
|||||||
|
|
||||||
// Get the requested page
|
// Get the requested page
|
||||||
$extraFooterScripts = array();
|
$extraFooterScripts = array();
|
||||||
$page = $_SERVER['PATH_INFO'];
|
$page = $_SERVER['PATH_INFO'] ?? '';
|
||||||
|
|
||||||
// Check if any plugin wants to handle the request
|
// Check if any plugin wants to handle the request
|
||||||
if (!$pluginManager->handlePageAction($page)) {
|
if (!$pluginManager->handlePageAction($page)) {
|
||||||
@@ -32,7 +32,7 @@ function handleCorePageAction(string $page, array &$extraFooterScripts): void
|
|||||||
DisplayWPAConfig();
|
DisplayWPAConfig();
|
||||||
break;
|
break;
|
||||||
case "/network_conf":
|
case "/network_conf":
|
||||||
DisplayNetworkingConfig();
|
DisplayNetworkingConfig($extraFooterScripts);
|
||||||
break;
|
break;
|
||||||
case "/hostapd_conf":
|
case "/hostapd_conf":
|
||||||
DisplayHostAPDConfig();
|
DisplayHostAPDConfig();
|
||||||
|
|||||||
@@ -12,6 +12,23 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
$dashboard = new \RaspAP\UI\Dashboard;
|
$dashboard = new \RaspAP\UI\Dashboard;
|
||||||
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
|
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
|
||||||
|
|
||||||
|
// set defaults
|
||||||
|
$optAutoclose = true;
|
||||||
|
$alertTimeout = 5000;
|
||||||
|
$good_input = true;
|
||||||
|
$config_port = false;
|
||||||
|
|
||||||
|
// set alert_timeout from cookie if valid
|
||||||
|
if (isset($_COOKIE['alert_timeout']) && is_numeric($_COOKIE['alert_timeout'])) {
|
||||||
|
$cookieTimeout = (int) $_COOKIE['alert_timeout'];
|
||||||
|
|
||||||
|
if ($cookieTimeout > 0) {
|
||||||
|
$alertTimeout = $cookieTimeout;
|
||||||
|
} else {
|
||||||
|
// A value of 0 means auto-close is disabled
|
||||||
|
$optAutoclose = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isset($_POST['SaveLanguage'])) {
|
if (isset($_POST['SaveLanguage'])) {
|
||||||
if (isset($_POST['locale'])) {
|
if (isset($_POST['locale'])) {
|
||||||
$_SESSION['locale'] = $_POST['locale'];
|
$_SESSION['locale'] = $_POST['locale'];
|
||||||
@@ -21,7 +38,6 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
|
|
||||||
if (!RASPI_MONITOR_ENABLED) {
|
if (!RASPI_MONITOR_ENABLED) {
|
||||||
if (isset($_POST['SaveServerSettings'])) {
|
if (isset($_POST['SaveServerSettings'])) {
|
||||||
$good_input = true;
|
|
||||||
// Validate server port
|
// Validate server port
|
||||||
if (isset($_POST['serverPort'])) {
|
if (isset($_POST['serverPort'])) {
|
||||||
if (strlen($_POST['serverPort']) > 4 || !is_numeric($_POST['serverPort'])) {
|
if (strlen($_POST['serverPort']) > 4 || !is_numeric($_POST['serverPort'])) {
|
||||||
@@ -32,13 +48,13 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Validate server bind address
|
// Validate server bind address
|
||||||
$serverBind = escapeshellarg('');
|
if (isset($_POST['serverBind']) && $_POST['serverBind'] !== '') {
|
||||||
if ($_POST['serverBind'] && $_POST['serverBind'] !== null ) {
|
$inputBind = trim($_POST['serverBind']);
|
||||||
if (!filter_var($_POST['serverBind'], FILTER_VALIDATE_IP)) {
|
if (!filter_var($inputBind, FILTER_VALIDATE_IP)) {
|
||||||
$status->addMessage('Invalid value for bind address', 'danger');
|
$status->addMessage('Invalid value for bind address', 'danger');
|
||||||
$good_input = false;
|
$good_input = false;
|
||||||
} else {
|
} else {
|
||||||
$serverBind = escapeshellarg($_POST['serverBind']);
|
$serverBind = escapeshellarg($inputBind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Validate log limit
|
// Validate log limit
|
||||||
@@ -58,6 +74,21 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
$status->addMessage($line, 'info');
|
$status->addMessage($line, 'info');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} elseif (isset($_POST['savethemeSettings'])) {
|
||||||
|
// Validate alert timout
|
||||||
|
if (isset($_POST['autoClose'])) {
|
||||||
|
$alertTimeout = trim($_POST['alertTimeout'] ?? '');
|
||||||
|
if (strlen($alertTimeout) > 7 || !is_numeric($alertTimeout)) {
|
||||||
|
$status->addMessage('Invalid value for alert close timeout', 'danger');
|
||||||
|
$good_input = false;
|
||||||
|
} else {
|
||||||
|
setcookie('alert_timeout', (int) $alertTimeout);
|
||||||
|
$status->addMessage(sprintf(_('Changing alert close timeout to %s ms'), $alertTimeout), 'info');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setcookie('alert_timeout', '', time() - 3600, '/');
|
||||||
|
$optAutoclose = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,10 +122,16 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
|
|
||||||
// memory use
|
// memory use
|
||||||
$memused = $system->usedMemory();
|
$memused = $system->usedMemory();
|
||||||
$memStatus = getMemStatus($memused);
|
$memStatus = getResourceStatus($memused);
|
||||||
$memused_status = $memStatus['status'];
|
$memused_status = $memStatus['status'];
|
||||||
$memused_led = $memStatus['led'];
|
$memused_led = $memStatus['led'];
|
||||||
|
|
||||||
|
// disk storage use
|
||||||
|
$diskused = $system->usedDisk();
|
||||||
|
$diskStatus = getResourceStatus($diskused);
|
||||||
|
$diskused_status = $diskStatus['status'];
|
||||||
|
$diskused_led = $diskStatus['led'];
|
||||||
|
|
||||||
// cpu load
|
// cpu load
|
||||||
$cpuload = $system->systemLoadPercentage();
|
$cpuload = $system->systemLoadPercentage();
|
||||||
$cpuload_status = getCPULoadStatus($cpuload);
|
$cpuload_status = getCPULoadStatus($cpuload);
|
||||||
@@ -116,7 +153,7 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
];
|
];
|
||||||
$selectedTheme = array_search($_COOKIE['theme'], $themeFiles);
|
$selectedTheme = array_search($_COOKIE['theme'], $themeFiles);
|
||||||
$extraFooterScripts[] = array('src'=>'dist/huebee/huebee.pkgd.min.js', 'defer'=>false);
|
$extraFooterScripts[] = array('src'=>'dist/huebee/huebee.pkgd.min.js', 'defer'=>false);
|
||||||
$extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false);
|
$extraFooterScripts[] = array('src'=>'app/js/vendor/huebee.js', 'defer'=>false);
|
||||||
$logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT;
|
$logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT;
|
||||||
|
|
||||||
$plugins = $pluginInstaller->getUserPlugins();
|
$plugins = $pluginInstaller->getUserPlugins();
|
||||||
@@ -138,6 +175,9 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
"memused",
|
"memused",
|
||||||
"memused_status",
|
"memused_status",
|
||||||
"memused_led",
|
"memused_led",
|
||||||
|
"diskused",
|
||||||
|
"diskused_status",
|
||||||
|
"diskused_led",
|
||||||
"cpuload",
|
"cpuload",
|
||||||
"cpuload_status",
|
"cpuload_status",
|
||||||
"cputemp",
|
"cputemp",
|
||||||
@@ -146,29 +186,31 @@ function DisplaySystem(&$extraFooterScripts)
|
|||||||
"themes",
|
"themes",
|
||||||
"selectedTheme",
|
"selectedTheme",
|
||||||
"logLimit",
|
"logLimit",
|
||||||
"pluginsTable"
|
"pluginsTable",
|
||||||
|
"optAutoclose",
|
||||||
|
"alertTimeout"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMemStatus($memused): array
|
function getResourceStatus($used): array
|
||||||
{
|
{
|
||||||
$memused_status = "primary";
|
$used_status = "primary";
|
||||||
$memused_led = "";
|
$used_led = "";
|
||||||
|
|
||||||
if ($memused > 90) {
|
if ($used > 90) {
|
||||||
$memused_status = "danger";
|
$used_status = "danger";
|
||||||
$memused_led = "service-status-down";
|
$used_led = "service-status-down";
|
||||||
} elseif ($memused > 75) {
|
} elseif ($used > 75) {
|
||||||
$memused_status = "warning";
|
$used_status = "warning";
|
||||||
$memused_led = "service-status-warn";
|
$used_led = "service-status-warn";
|
||||||
} elseif ($memused > 0) {
|
} elseif ($used > 0) {
|
||||||
$memused_status = "success";
|
$used_status = "success";
|
||||||
$memused_led = "service-status-up";
|
$used_led = "service-status-up";
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'status' => $memused_status,
|
'status' => $used_status,
|
||||||
'led' => $memused_led
|
'led' => $used_led
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,328 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once 'functions.php';
|
|
||||||
|
|
||||||
const MIN_RSSI = -100;
|
|
||||||
const MAX_RSSI = -55;
|
|
||||||
|
|
||||||
function knownWifiStations(&$networks)
|
|
||||||
{
|
|
||||||
// Find currently configured networks
|
|
||||||
exec(' sudo cat ' . RASPI_WPA_SUPPLICANT_CONFIG, $known_return);
|
|
||||||
//$index = 0;
|
|
||||||
foreach ($known_return as $line) {
|
|
||||||
if (preg_match('/network\s*=/', $line)) {
|
|
||||||
$network = array('visible' => false, 'configured' => true, 'connected' => false, 'index' => null);
|
|
||||||
++$index;
|
|
||||||
} elseif (isset($network) && $network !== null) {
|
|
||||||
if (preg_match('/^\s*}\s*$/', $line)) {
|
|
||||||
$networks[$ssid] = $network;
|
|
||||||
$network = null;
|
|
||||||
$ssid = null;
|
|
||||||
} elseif ($lineArr = preg_split('/\s*=\s*/', trim($line), 2)) {
|
|
||||||
switch (strtolower($lineArr[0])) {
|
|
||||||
case 'ssid':
|
|
||||||
$ssid = trim($lineArr[1], '"');
|
|
||||||
$ssid = str_replace('P"','',$ssid);
|
|
||||||
$network['ssid'] = $ssid;
|
|
||||||
$index = getNetworkIdBySSID($ssid);
|
|
||||||
$network['index'] = $index;
|
|
||||||
break;
|
|
||||||
case 'psk':
|
|
||||||
$network['passkey'] = trim($lineArr[1]);
|
|
||||||
$network['protocol'] = 'WPA';
|
|
||||||
break;
|
|
||||||
case '#psk':
|
|
||||||
$network['protocol'] = 'WPA';
|
|
||||||
case 'wep_key0': // Untested
|
|
||||||
$network['passphrase'] = trim($lineArr[1], '"');
|
|
||||||
break;
|
|
||||||
case 'key_mgmt':
|
|
||||||
if (! array_key_exists('passphrase', $network) && $lineArr[1] === 'NONE') {
|
|
||||||
$network['protocol'] = 'Open';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'priority':
|
|
||||||
$network['priority'] = trim($lineArr[1], '"');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nearbyWifiStations(&$networks, $cached = true)
|
|
||||||
{
|
|
||||||
$cacheTime = filemtime(RASPI_WPA_SUPPLICANT_CONFIG);
|
|
||||||
$cacheKey = "nearby_wifi_stations_$cacheTime";
|
|
||||||
|
|
||||||
if ($cached == false) {
|
|
||||||
deleteCache($cacheKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scan_results = cache(
|
|
||||||
$cacheKey, function () {
|
|
||||||
exec('sudo wpa_cli -i ' .$_SESSION['wifi_client_interface']. ' scan');
|
|
||||||
sleep(3);
|
|
||||||
$stdout = shell_exec('sudo wpa_cli -i ' .$_SESSION['wifi_client_interface']. ' scan_results');
|
|
||||||
return preg_split("/\n/", $stdout);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// get the name of the AP. Should be excluded from nearby networks
|
|
||||||
exec('cat '.RASPI_HOSTAPD_CONFIG.' | sed -rn "s/ssid=(.*)\s*$/\1/p" ', $ap_ssid);
|
|
||||||
$ap_ssid = $ap_ssid[0];
|
|
||||||
|
|
||||||
$index = 0;
|
|
||||||
if ( !empty($networks) ) {
|
|
||||||
$lastnet = end($networks);
|
|
||||||
if ( isset($lastnet['index']) ) $index = $lastnet['index'] + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($scan_results)) {
|
|
||||||
array_shift($scan_results);
|
|
||||||
foreach ($scan_results as $network) {
|
|
||||||
$arrNetwork = preg_split("/[\t]+/", $network); // split result into array
|
|
||||||
$ssid = $arrNetwork[4];
|
|
||||||
|
|
||||||
// exclude raspap ssid
|
|
||||||
if (empty($ssid) || $ssid == $ap_ssid) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter SSID string: unprintable 7bit ASCII control codes, delete or quotes -> ignore network
|
|
||||||
if (preg_match('[\x00-\x1f\x7f\'\`\´\"]', $ssid)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If network is saved
|
|
||||||
if (array_key_exists($ssid, $networks)) {
|
|
||||||
$networks[$ssid]['visible'] = true;
|
|
||||||
$networks[$ssid]['channel'] = ConvertToChannel($arrNetwork[1]);
|
|
||||||
// TODO What if the security has changed?
|
|
||||||
} else {
|
|
||||||
$networks[$ssid] = array(
|
|
||||||
'ssid' => $ssid,
|
|
||||||
'configured' => false,
|
|
||||||
'protocol' => ConvertToSecurity($arrNetwork[3]),
|
|
||||||
'channel' => ConvertToChannel($arrNetwork[1]),
|
|
||||||
'passphrase' => '',
|
|
||||||
'visible' => true,
|
|
||||||
'connected' => false,
|
|
||||||
'index' => $index
|
|
||||||
);
|
|
||||||
++$index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save RSSI, if the current value is larger than the already stored
|
|
||||||
if (array_key_exists(4, $arrNetwork) && array_key_exists($arrNetwork[4], $networks)) {
|
|
||||||
if (! array_key_exists('RSSI', $networks[$arrNetwork[4]]) || $networks[$ssid]['RSSI'] < $arrNetwork[2]) {
|
|
||||||
$networks[$ssid]['RSSI'] = $arrNetwork[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function connectedWifiStations(&$networks)
|
|
||||||
{
|
|
||||||
exec('iwconfig ' .$_SESSION['wifi_client_interface'], $iwconfig_return);
|
|
||||||
foreach ($iwconfig_return as $line) {
|
|
||||||
if (preg_match('/ESSID:\"([^"]+)\"/i', $line, $iwconfig_ssid)) {
|
|
||||||
$networks[hexSequence2lower($iwconfig_ssid[1])]['connected'] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortNetworksByRSSI(&$networks)
|
|
||||||
{
|
|
||||||
$valRSSI = array();
|
|
||||||
foreach ($networks as $SSID => $net) {
|
|
||||||
if (!array_key_exists('RSSI', $net)) {
|
|
||||||
$net['RSSI'] = -1000;
|
|
||||||
}
|
|
||||||
$valRSSI[$SSID] = $net['RSSI'];
|
|
||||||
}
|
|
||||||
$nets = $networks;
|
|
||||||
arsort($valRSSI);
|
|
||||||
$networks = array();
|
|
||||||
foreach ($valRSSI as $SSID => $RSSI) {
|
|
||||||
$networks[$SSID] = $nets[$SSID];
|
|
||||||
$networks[$SSID]['RSSI'] = $RSSI;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Determines the configured wireless AP interface
|
|
||||||
*
|
|
||||||
* If not saved in /etc/raspap/hostapd.ini, check for a second
|
|
||||||
* wireless interface with iw dev. Fallback to the constant
|
|
||||||
* value defined in config.php
|
|
||||||
*/
|
|
||||||
function getWifiInterface()
|
|
||||||
{
|
|
||||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
|
||||||
$arrHostapdConf = file_exists($hostapdIni) ? parse_ini_file($hostapdIni) : [];
|
|
||||||
|
|
||||||
$iface = $_SESSION['ap_interface'] = $arrHostapdConf['WifiInterface'] ?? RASPI_WIFI_AP_INTERFACE;
|
|
||||||
|
|
||||||
if (!validateInterface($iface)) {
|
|
||||||
$iface = RASPI_WIFI_AP_INTERFACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for 2nd wifi interface -> wifi client on different interface
|
|
||||||
exec("iw dev | awk '$1==\"Interface\" && $2!=\"$iface\" {print $2}'", $iface2);
|
|
||||||
$client_iface = $_SESSION['wifi_client_interface'] = empty($iface2) ? $iface : trim($iface2[0]);
|
|
||||||
|
|
||||||
// handle special case for RPi Zero W in AP-STA mode
|
|
||||||
if ($client_iface === "uap0" && ($arrHostapdConf['WifiAPEnable'] ?? 0)) {
|
|
||||||
$_SESSION['wifi_client_interface'] = $iface;
|
|
||||||
$_SESSION['ap_interface'] = $client_iface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Reinitializes wpa_supplicant for the wireless client interface
|
|
||||||
* The 'force' parameter deletes the socket in /var/run/wpa_supplicant/
|
|
||||||
*
|
|
||||||
* @param boolean $force
|
|
||||||
*/
|
|
||||||
function reinitializeWPA($force)
|
|
||||||
{
|
|
||||||
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
|
||||||
if ($force == true) {
|
|
||||||
$cmd = "sudo /bin/rm /var/run/wpa_supplicant/$iface";
|
|
||||||
$result = shell_exec($cmd);
|
|
||||||
}
|
|
||||||
$cmd = "sudo wpa_supplicant -B -Dnl80211 -c/etc/wpa_supplicant/wpa_supplicant.conf -i$iface";
|
|
||||||
$result = shell_exec($cmd);
|
|
||||||
sleep(1);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Replace escaped bytes (hex) by binary - assume UTF8 encoding
|
|
||||||
*
|
|
||||||
* @param string $ssid
|
|
||||||
*/
|
|
||||||
function ssid2utf8($ssid)
|
|
||||||
{
|
|
||||||
return evalHexSequence($ssid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns a signal strength indicator based on RSSI value
|
|
||||||
*
|
|
||||||
* @param string $rssi
|
|
||||||
*/
|
|
||||||
function getSignalBars($rssi)
|
|
||||||
{
|
|
||||||
// assign css class based on RSSI value
|
|
||||||
if ($rssi >= MAX_RSSI) {
|
|
||||||
$class = 'strong';
|
|
||||||
} elseif ($rssi >= -56) {
|
|
||||||
$class = 'medium';
|
|
||||||
} elseif ($rssi >= -67) {
|
|
||||||
$class = 'weak';
|
|
||||||
} elseif ($rssi >= -89) {
|
|
||||||
$class = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate percent strength
|
|
||||||
if ($rssi >= -50) {
|
|
||||||
$pct = 100;
|
|
||||||
} elseif ($rssi <= MIN_RSSI) {
|
|
||||||
$pct = 0;
|
|
||||||
} else {
|
|
||||||
$pct = 2*($rssi + 100);
|
|
||||||
}
|
|
||||||
$elem = '<div data-toggle="tooltip" title="' . _("Signal strength"). ': ' .$pct. '%" class="signal-icon ' .$class. '">'.PHP_EOL;
|
|
||||||
for ($n = 0; $n < 3; $n++ ) {
|
|
||||||
$elem .= '<div class="signal-bar"></div>'.PHP_EOL;
|
|
||||||
}
|
|
||||||
$elem .= '</div>'.PHP_EOL;
|
|
||||||
return $elem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parses output of wpa_cli list_networks, compares with known networks
|
|
||||||
* from wpa_supplicant, and adds with wpa_cli if not found
|
|
||||||
*
|
|
||||||
* @param array $networks
|
|
||||||
*/
|
|
||||||
function setKnownStationsWPA($networks)
|
|
||||||
{
|
|
||||||
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
|
||||||
$output = shell_exec("sudo wpa_cli -i $iface list_networks");
|
|
||||||
$lines = explode("\n", $output);
|
|
||||||
array_shift($lines);
|
|
||||||
$wpaCliNetworks = [];
|
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
$data = explode("\t", trim($line));
|
|
||||||
if (!empty($data) && count($data) >= 2) {
|
|
||||||
$id = $data[0];
|
|
||||||
$ssid = $data[1];
|
|
||||||
$item = [
|
|
||||||
'id' => $id,
|
|
||||||
'ssid' => $ssid
|
|
||||||
];
|
|
||||||
$wpaCliNetworks[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($networks as $network) {
|
|
||||||
$ssid = $network['ssid'];
|
|
||||||
if (!networkExists($ssid, $wpaCliNetworks)) {
|
|
||||||
$ssid = escapeshellarg('"'.$network['ssid'].'"');
|
|
||||||
$psk = escapeshellarg('"'.$network['passphrase'].'"');
|
|
||||||
$protocol = $network['protocol'];
|
|
||||||
$netid = trim(shell_exec("sudo wpa_cli -i $iface add_network"));
|
|
||||||
if (isset($netid) && !isset($known[$netid])) {
|
|
||||||
$commands = [
|
|
||||||
"sudo wpa_cli -i $iface set_network $netid ssid $ssid",
|
|
||||||
"sudo wpa_cli -i $iface set_network $netid psk $psk",
|
|
||||||
"sudo wpa_cli -i $iface enable_network $netid"
|
|
||||||
];
|
|
||||||
if ($protocol === 'Open') {
|
|
||||||
$commands[1] = "sudo wpa_cli -i $iface set_network $netid key_mgmt NONE";
|
|
||||||
}
|
|
||||||
foreach ($commands as $cmd) {
|
|
||||||
exec($cmd);
|
|
||||||
usleep(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parses wpa_cli list_networks output and returns the id
|
|
||||||
* of a corresponding network SSID
|
|
||||||
*
|
|
||||||
* @param string $ssid
|
|
||||||
* @return integer id
|
|
||||||
*/
|
|
||||||
function getNetworkIdBySSID($ssid) {
|
|
||||||
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
|
||||||
$cmd = "sudo wpa_cli -i $iface list_networks";
|
|
||||||
$output = [];
|
|
||||||
exec($cmd, $output);
|
|
||||||
array_shift($output);
|
|
||||||
foreach ($output as $line) {
|
|
||||||
$columns = preg_split('/\t/', $line);
|
|
||||||
if (count($columns) >= 4 && trim($columns[1]) === trim($ssid)) {
|
|
||||||
return $columns[0]; // return network ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function networkExists($ssid, $collection)
|
|
||||||
{
|
|
||||||
foreach ($collection as $network) {
|
|
||||||
if ($network['ssid'] === $ssid) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,28 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'config.php';
|
require_once 'includes/config.php';
|
||||||
|
|
||||||
|
use RaspAP\Networking\Hotspot\WiFiManager;
|
||||||
|
|
||||||
|
$wifi = new WiFiManager();
|
||||||
|
$wifi->getWifiInterface();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays wireguard server & peer configuration
|
* Displays wireguard server & peer configuration
|
||||||
*/
|
*/
|
||||||
function DisplayWireGuardConfig()
|
function DisplayWireGuardConfig()
|
||||||
{
|
{
|
||||||
$status = new \RaspAP\Messages\StatusMessage;
|
|
||||||
$parseFlag = true;
|
$parseFlag = true;
|
||||||
|
$status = new \RaspAP\Messages\StatusMessage;
|
||||||
if (!RASPI_MONITOR_ENABLED) {
|
if (!RASPI_MONITOR_ENABLED) {
|
||||||
$optRules = isset($_POST['wgRules']) ? $_POST['wgRules'] : null;
|
$optRules = isset($_POST['wgRules']) ? $_POST['wgRules'] : null;
|
||||||
$optInterface = isset($_POST['wgInterface']) ? $_POST['wgInterface'] : null;
|
$optInterface = isset($_POST['wgInterface']) ? $_POST['wgInterface'] : null;
|
||||||
$optConf = isset($_POST['wgCnfOpt']) ? $_POST['wgCnfOpt'] : null;
|
$optConf = isset($_POST['wgCnfOpt']) ? $_POST['wgCnfOpt'] : null;
|
||||||
$optSrvEnable = isset($_POST['wgSrvEnable']) ? $_POST['wgSrvEnable'] : null;
|
$optSrvEnable = isset($_POST['wgSrvEnable']) ? $_POST['wgSrvEnable'] : null;
|
||||||
$optLogEnable = isset($_POST['wgLogEnable']) ? $_POST['wgLogEnable'] : null;
|
$optLogEnable = isset($_POST['wgLogEnable']) ? $_POST['wgLogEnable'] : null;
|
||||||
|
$optKSwitch = isset($_POST['wgKSwitch']) ? $_POST['wgKSwitch'] : null;
|
||||||
if (isset($_POST['savewgsettings']) && $optConf == 'manual' && $optSrvEnable == 1 ) {
|
if (isset($_POST['savewgsettings']) && $optConf == 'manual' && $optSrvEnable == 1 ) {
|
||||||
SaveWireGuardConfig($status);
|
SaveWireGuardConfig($status);
|
||||||
} elseif (isset($_POST['savewgsettings']) && $optConf == 'upload' && is_uploaded_file($_FILES["wgFile"]["tmp_name"])) {
|
} elseif (isset($_POST['savewgsettings']) && $optConf == 'upload' && is_uploaded_file($_FILES["wgFile"]["tmp_name"])) {
|
||||||
SaveWireGuardUpload($status, $_FILES['wgFile'], $optRules, $optInterface);
|
SaveWireGuardUpload($status, $_FILES['wgFile'], $optRules, $optKSwitch, $optInterface);
|
||||||
} elseif (isset($_POST['savewgsettings']) && isset($_POST['wg_penabled']) ) {
|
} elseif (isset($_POST['savewgsettings']) && isset($_POST['wg_penabled']) ) {
|
||||||
SaveWireGuardConfig($status);
|
SaveWireGuardConfig($status);
|
||||||
} elseif (isset($_POST['startwg'])) {
|
} elseif (isset($_POST['startwg'])) {
|
||||||
$status->addMessage('Attempting to start WireGuard', 'info');
|
$status->addMessage('Attempting to start WireGuard', 'info');
|
||||||
|
exec('sudo /bin/systemctl enable wg-quick@wg0', $return);
|
||||||
exec('sudo /bin/systemctl start wg-quick@wg0', $return);
|
exec('sudo /bin/systemctl start wg-quick@wg0', $return);
|
||||||
foreach ($return as $line) {
|
foreach ($return as $line) {
|
||||||
$status->addMessage($line, 'info');
|
$status->addMessage($line, 'info');
|
||||||
@@ -30,6 +37,7 @@ function DisplayWireGuardConfig()
|
|||||||
} elseif (isset($_POST['stopwg'])) {
|
} elseif (isset($_POST['stopwg'])) {
|
||||||
$status->addMessage('Attempting to stop WireGuard', 'info');
|
$status->addMessage('Attempting to stop WireGuard', 'info');
|
||||||
exec('sudo /bin/systemctl stop wg-quick@wg0', $return);
|
exec('sudo /bin/systemctl stop wg-quick@wg0', $return);
|
||||||
|
exec('sudo /bin/systemctl disable wg-quick@wg0', $return);
|
||||||
foreach ($return as $line) {
|
foreach ($return as $line) {
|
||||||
$status->addMessage($line, 'info');
|
$status->addMessage($line, 'info');
|
||||||
}
|
}
|
||||||
@@ -41,7 +49,9 @@ function DisplayWireGuardConfig()
|
|||||||
exec('sudo cat '. RASPI_WIREGUARD_CONFIG, $return);
|
exec('sudo cat '. RASPI_WIREGUARD_CONFIG, $return);
|
||||||
$conf = ParseConfig($return, $parseFlag);
|
$conf = ParseConfig($return, $parseFlag);
|
||||||
$wg_srvpubkey = exec('sudo cat '. RASPI_WIREGUARD_PATH .'wg-server-public.key', $return);
|
$wg_srvpubkey = exec('sudo cat '. RASPI_WIREGUARD_PATH .'wg-server-public.key', $return);
|
||||||
$wg_srvport = ($conf['ListenPort'] == '') ? getDefaultNetValue('wireguard','server','ListenPort') : $conf['ListenPort'];
|
$wg_srvport = ($conf['ListenPort'] ?? '') === ''
|
||||||
|
? getDefaultNetValue('wireguard','server','ListenPort')
|
||||||
|
: $conf['ListenPort'];
|
||||||
$wg_srvipaddress = ($conf['Address'] == '') ? getDefaultNetValue('wireguard','server','Address') : $conf['Address'];
|
$wg_srvipaddress = ($conf['Address'] == '') ? getDefaultNetValue('wireguard','server','Address') : $conf['Address'];
|
||||||
$wg_srvdns = ($conf['DNS'] == '') ? getDefaultNetValue('wireguard','server','DNS') : $conf['DNS'];
|
$wg_srvdns = ($conf['DNS'] == '') ? getDefaultNetValue('wireguard','server','DNS') : $conf['DNS'];
|
||||||
if (is_array($wg_srvdns)) {
|
if (is_array($wg_srvdns)) {
|
||||||
@@ -70,11 +80,18 @@ function DisplayWireGuardConfig()
|
|||||||
$wg_state = ($wgstatus[0] == 'active' ? true : false );
|
$wg_state = ($wgstatus[0] == 'active' ? true : false );
|
||||||
$public_ip = get_public_ip();
|
$public_ip = get_public_ip();
|
||||||
|
|
||||||
// retrieve wg log
|
// fetch uploaded file configs
|
||||||
$wg_log = "";
|
exec("sudo ls ".RASPI_WIREGUARD_PATH, $clist);
|
||||||
|
$configs = preg_grep('/^((?!wg0).)*\.conf/', $clist);
|
||||||
|
exec("sudo readlink ".RASPI_WIREGUARD_CONFIG." | xargs basename", $ret);
|
||||||
|
$conf_default = empty($ret) ? "none" : $ret[0];
|
||||||
|
|
||||||
|
// fetch wg log
|
||||||
|
exec('sudo chmod o+r /tmp/wireguard.log');
|
||||||
if (file_exists('/tmp/wireguard.log')) {
|
if (file_exists('/tmp/wireguard.log')) {
|
||||||
exec('sudo chmod o+r /tmp/wireguard.log');
|
$log = file_get_contents('/tmp/wireguard.log');
|
||||||
$wg_log = file_get_contents('/tmp/wireguard.log');
|
} else {
|
||||||
|
$log = '';
|
||||||
}
|
}
|
||||||
$peer_id = $peer_id ?? "1";
|
$peer_id = $peer_id ?? "1";
|
||||||
|
|
||||||
@@ -90,6 +107,7 @@ function DisplayWireGuardConfig()
|
|||||||
"public_ip",
|
"public_ip",
|
||||||
"interfaces",
|
"interfaces",
|
||||||
"optRules",
|
"optRules",
|
||||||
|
"optKSwitch",
|
||||||
"optLogEnable",
|
"optLogEnable",
|
||||||
"peer_id",
|
"peer_id",
|
||||||
"wg_srvpubkey",
|
"wg_srvpubkey",
|
||||||
@@ -104,7 +122,9 @@ function DisplayWireGuardConfig()
|
|||||||
"wg_pendpoint",
|
"wg_pendpoint",
|
||||||
"wg_pallowedips",
|
"wg_pallowedips",
|
||||||
"wg_pkeepalive",
|
"wg_pkeepalive",
|
||||||
"wg_log"
|
"configs",
|
||||||
|
"conf_default",
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -116,10 +136,11 @@ function DisplayWireGuardConfig()
|
|||||||
* @param object $status
|
* @param object $status
|
||||||
* @param object $file
|
* @param object $file
|
||||||
* @param boolean $optRules
|
* @param boolean $optRules
|
||||||
|
* @param boolean $optKSwitch
|
||||||
* @param string $optInterface
|
* @param string $optInterface
|
||||||
* @return object $status
|
* @return object $status
|
||||||
*/
|
*/
|
||||||
function SaveWireGuardUpload($status, $file, $optRules, $optInterface)
|
function SaveWireGuardUpload($status, $file, $optRules, $optKSwitch, $optInterface)
|
||||||
{
|
{
|
||||||
define('KB', 1024);
|
define('KB', 1024);
|
||||||
$tmp_destdir = '/tmp/';
|
$tmp_destdir = '/tmp/';
|
||||||
@@ -148,19 +169,56 @@ function SaveWireGuardUpload($status, $file, $optRules, $optInterface)
|
|||||||
$tmp_wgconfig = $results['full_path'];
|
$tmp_wgconfig = $results['full_path'];
|
||||||
$tmp_contents = file_get_contents($tmp_wgconfig);
|
$tmp_contents = file_get_contents($tmp_wgconfig);
|
||||||
|
|
||||||
// Set iptables rules
|
// Check for existing iptables rules
|
||||||
if (isset($optRules) && !preg_match('/PostUp|PostDown/m',$tmp_contents)) {
|
if ((isset($optRules) || isset($optKSwitch)) && preg_match('/PostUp|PostDown|PreDown/m',$tmp_contents)) {
|
||||||
$rules[] = 'PostUp = '.getDefaultNetValue('wireguard','server','PostUp');
|
$status->addMessage('Existing iptables rules found in WireGuard configuration - not added', 'info');
|
||||||
$rules[] = 'PostDown = '.getDefaultNetValue('wireguard','server','PostDown');
|
} else {
|
||||||
$rules[] = '';
|
// Set rules from default config
|
||||||
$rules = join(PHP_EOL, $rules);
|
if (isset($optRules)) {
|
||||||
$rules = preg_replace('/wlan0/m', $optInterface, $rules);
|
$rules[] = 'PostUp = '.getDefaultNetValue('wireguard','server','PostUp');
|
||||||
$tmp_contents = preg_replace('/^\s*$/ms', $rules, $tmp_contents, 1);
|
$rules[] = 'PostDown = '.getDefaultNetValue('wireguard','server','PostDown');
|
||||||
file_put_contents($tmp_wgconfig, $tmp_contents);
|
$rules = preg_replace('/wlan0/m', $optInterface, $rules);
|
||||||
|
}
|
||||||
|
if (isset($optKSwitch)) {
|
||||||
|
// Get ap static ip_addr from system config, fallback to default if undefined
|
||||||
|
$jsonData = json_decode(getNetConfig($optInterface), true);
|
||||||
|
$ip_addr = ($jsonData['StaticIP'] == '') ? getDefaultNetValue('dhcp', $optInterface, 'static ip_address') : $jsonData['StaticIP'];
|
||||||
|
$mask = ($jsonData['SubnetMask'] == '') ? getDefaultNetValue('dhcp', $optInterface, 'subnetmask') : $jsonData['SubnetMask'];
|
||||||
|
|
||||||
|
// if empty, try to detect IP/mask from system
|
||||||
|
if (empty($ip_addr) || empty($mask)) {
|
||||||
|
$ipDetails = shell_exec("ip -4 -o addr show dev " . escapeshellarg($optInterface));
|
||||||
|
if (preg_match('/inet (\d+\.\d+\.\d+\.\d+)\/(\d+)/', $ipDetails, $matches)) {
|
||||||
|
$ip_addr = $matches[1];
|
||||||
|
$cidr = $matches[2];
|
||||||
|
} else {
|
||||||
|
$ip_addr = '0.0.0.0';
|
||||||
|
$cidr = '24';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$cidr = mask2cidr($mask);
|
||||||
|
}
|
||||||
|
$cidr_ip = strpos($ip_addr, '/') === false ? "$ip_addr/$cidr" : $ip_addr;
|
||||||
|
|
||||||
|
$rules[] = 'PostUp = '.getDefaultNetValue('wireguard','server','PostUpEx');
|
||||||
|
$rules[] = 'PreDown = '.getDefaultNetValue('wireguard','server','PreDown');
|
||||||
|
$rules = preg_replace('/%s/m', $cidr_ip, $rules);
|
||||||
|
}
|
||||||
|
if ((isset($rules) && count($rules) > 0)) {
|
||||||
|
$rules[] = '';
|
||||||
|
$rules = join(PHP_EOL, $rules);
|
||||||
|
$tmp_contents = preg_replace('/^\s*$/ms', $rules, $tmp_contents, 1);
|
||||||
|
file_put_contents($tmp_wgconfig, $tmp_contents);
|
||||||
|
$status->addMessage('iptables rules added to WireGuard configuration', 'info');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move processed file from tmp to destination
|
// Move processed file from /tmp and create symlink
|
||||||
system("sudo mv $tmp_wgconfig ". RASPI_WIREGUARD_CONFIG, $return);
|
$client_wg = RASPI_WIREGUARD_PATH.pathinfo($file['name'], PATHINFO_FILENAME).'.conf';
|
||||||
|
chmod($tmp_wgconfig, 0644);
|
||||||
|
system("sudo mv $tmp_wgconfig $client_wg", $return);
|
||||||
|
system("sudo rm ".RASPI_WIREGUARD_CONFIG, $return);
|
||||||
|
system("sudo ln -s $client_wg ".RASPI_WIREGUARD_CONFIG, $return);
|
||||||
|
|
||||||
if ($return ==0) {
|
if ($return ==0) {
|
||||||
$status->addMessage('WireGuard configuration uploaded successfully', 'info');
|
$status->addMessage('WireGuard configuration uploaded successfully', 'info');
|
||||||
@@ -225,7 +283,7 @@ function SaveWireGuardConfig($status)
|
|||||||
$wg_pendpoint_seg = substr($_POST['wg_pendpoint'],0,strpos($_POST['wg_pendpoint'],':'));
|
$wg_pendpoint_seg = substr($_POST['wg_pendpoint'],0,strpos($_POST['wg_pendpoint'],':'));
|
||||||
$host_port = explode(':', $wg_pendpoint_seg);
|
$host_port = explode(':', $wg_pendpoint_seg);
|
||||||
$hostname = $host_port[0];
|
$hostname = $host_port[0];
|
||||||
if (!filter_var($hostname, FILTER_VALIDATE_IP) &&
|
if (!filter_var($hostname, FILTER_VALIDATE_IP) &&
|
||||||
!filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
!filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
||||||
$status->addMessage('Invalid value for endpoint address', 'danger');
|
$status->addMessage('Invalid value for endpoint address', 'danger');
|
||||||
$good_input = false;
|
$good_input = false;
|
||||||
@@ -295,11 +353,10 @@ function SaveWireGuardConfig($status)
|
|||||||
}
|
}
|
||||||
$config[] = '';
|
$config[] = '';
|
||||||
$config = join(PHP_EOL, $config);
|
$config = join(PHP_EOL, $config);
|
||||||
|
|
||||||
file_put_contents("/tmp/wgdata", $config);
|
file_put_contents("/tmp/wgdata", $config);
|
||||||
system('sudo cp /tmp/wgdata '.RASPI_WIREGUARD_PATH.'client.conf', $return);
|
system('sudo cp /tmp/wgdata '.RASPI_WIREGUARD_PATH.'client.conf', $return);
|
||||||
} else {
|
} else {
|
||||||
# remove selected conf + keys
|
# remove selected conf + keys
|
||||||
system('sudo rm '. RASPI_WIREGUARD_PATH .'wg-peer-private.key', $return);
|
system('sudo rm '. RASPI_WIREGUARD_PATH .'wg-peer-private.key', $return);
|
||||||
system('sudo rm '. RASPI_WIREGUARD_PATH .'wg-peer-public.key', $return);
|
system('sudo rm '. RASPI_WIREGUARD_PATH .'wg-peer-public.key', $return);
|
||||||
system('sudo rm '. RASPI_WIREGUARD_PATH.'client.conf', $return);
|
system('sudo rm '. RASPI_WIREGUARD_PATH.'client.conf', $return);
|
||||||
|
|||||||
36
index.php
36
index.php
@@ -14,7 +14,7 @@
|
|||||||
* @author Lawrence Yau <sirlagz@gmail.com>
|
* @author Lawrence Yau <sirlagz@gmail.com>
|
||||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
* @license GNU General Public License, version 3 (GPL-3.0)
|
* @license GNU General Public License, version 3 (GPL-3.0)
|
||||||
* @version 3.3.5
|
* @version 3.4.3
|
||||||
* @link https://github.com/RaspAP/raspap-webgui/
|
* @link https://github.com/RaspAP/raspap-webgui/
|
||||||
* @link https://raspap.com/
|
* @link https://raspap.com/
|
||||||
* @see http://sirlagz.net/2013/02/08/raspap-webgui/
|
* @see http://sirlagz.net/2013/02/08/raspap-webgui/
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
* as you leave these references intact in the header comments of your source files.
|
* as you leave these references intact in the header comments of your source files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
require_once 'includes/bootstrap.php';
|
||||||
require_once 'includes/config.php';
|
require_once 'includes/config.php';
|
||||||
require_once 'includes/autoload.php';
|
require_once 'includes/autoload.php';
|
||||||
$handler = new RaspAP\Exceptions\ExceptionHandler;
|
$handler = new RaspAP\Exceptions\ExceptionHandler;
|
||||||
@@ -67,19 +68,22 @@ initializeApp();
|
|||||||
<title><?php echo RASPI_BRAND_TITLE; ?></title>
|
<title><?php echo RASPI_BRAND_TITLE; ?></title>
|
||||||
|
|
||||||
<!-- Bootstrap Core CSS -->
|
<!-- Bootstrap Core CSS -->
|
||||||
<link href="dist/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
<link href="dist/bootstrap/css/bootstrap.min.css?v=<?= filemtime('dist/bootstrap/css/bootstrap.min.css'); ?>" rel="stylesheet">
|
||||||
|
|
||||||
<!-- SB-Admin CSS -->
|
<!-- SB-Admin CSS -->
|
||||||
<link href="dist/sb-admin/css/styles.css" rel="stylesheet">
|
<link href="dist/sb-admin/css/styles.css?v=<?= filemtime('dist/sb-admin/css/styles.css'); ?>" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Huebee CSS -->
|
<!-- Huebee CSS -->
|
||||||
<link href="dist/huebee/huebee.min.css" rel="stylesheet">
|
<link href="dist/huebee/huebee.min.css?v=<?= filemtime('dist/huebee/huebee.min.css'); ?>" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
<link href="dist/font-awesome/css/all.min.css" rel="stylesheet" type="text/css">
|
<link href="dist/font-awesome/css/all.min.css?v=<?= filemtime('dist/font-awesome/css/all.min.css'); ?>" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
<!-- Librespeed CSS -->
|
||||||
|
<link href="dist/speedtest/speedtest.css?v=<?= filemtime('dist/speedtest/speedtest.css'); ?>" rel="stylesheet" />
|
||||||
|
|
||||||
<!-- RaspAP Fonts -->
|
<!-- RaspAP Fonts -->
|
||||||
<link href="dist/raspap/css/style.css" rel="stylesheet" type="text/css">
|
<link href="dist/raspap/css/style.css?v=<?= filemtime('dist/raspap/css/style.css'); ?>" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
<!-- Custom CSS -->
|
<!-- Custom CSS -->
|
||||||
<link href="<?php echo $_SESSION["theme_url"]; ?>" title="main" rel="stylesheet">
|
<link href="<?php echo $_SESSION["theme_url"]; ?>" title="main" rel="stylesheet">
|
||||||
@@ -123,25 +127,29 @@ initializeApp();
|
|||||||
</div>
|
</div>
|
||||||
<?php ob_end_flush(); ?>
|
<?php ob_end_flush(); ?>
|
||||||
<!-- jQuery -->
|
<!-- jQuery -->
|
||||||
<script src="dist/jquery/jquery.min.js"></script>
|
<script src="dist/jquery/jquery.min.js?v=<?= filemtime('dist/jquery/jquery.min.js'); ?>"></script>
|
||||||
|
|
||||||
<!-- Bootstrap Core JavaScript -->
|
<!-- Bootstrap Core JavaScript -->
|
||||||
<script src="dist/bootstrap/js/bootstrap.bundle.min.js"></script>
|
<script src="dist/bootstrap/js/bootstrap.bundle.min.js?v=<?= filemtime('dist/bootstrap/js/bootstrap.bundle.min.js'); ?>"></script>
|
||||||
|
|
||||||
<!-- Core plugin JavaScript -->
|
<!-- Core plugin JavaScript -->
|
||||||
<script src="dist/jquery-easing/jquery.easing.min.js"></script>
|
<script src="dist/jquery-easing/jquery.easing.min.js?v=<?= filemtime('dist/jquery-easing/jquery.easing.min.js'); ?>"></script>
|
||||||
|
|
||||||
<!-- Chart.js JavaScript -->
|
<!-- Chart.js JavaScript -->
|
||||||
<script src="dist/chart.js/Chart.min.js"></script>
|
<script src="dist/chart.js/Chart.min.js?v=<?= filemtime('dist/chart.js/Chart.min.js'); ?>"></script>
|
||||||
|
|
||||||
<!-- SB-Admin JavaScript -->
|
<!-- SB-Admin JavaScript -->
|
||||||
<script src="dist/sb-admin/js/scripts.js"></script>
|
<script src="dist/sb-admin/js/scripts.js?v=<?= filemtime('dist/sb-admin/js/scripts.js'); ?>"></script>
|
||||||
|
|
||||||
<!-- jQuery Mask plugin -->
|
<!-- jQuery Mask plugin -->
|
||||||
<script src="dist/jquery-mask/jquery.mask.min.js"></script>
|
<script src="dist/jquery-mask/jquery.mask.min.js?v=<?= filemtime('dist/jquery-mask/jquery.mask.min.js'); ?>"></script>
|
||||||
|
|
||||||
<!-- Custom RaspAP JS -->
|
<!-- Librespeed JavaScript -->
|
||||||
<script src="app/js/custom.js"></script>
|
<script src="dist/speedtest/speedtest.js?v=<?= filemtime('dist/speedtest/speedtest.js'); ?>"></script>
|
||||||
|
|
||||||
|
<!-- RaspAP JavaScript -->
|
||||||
|
<script src="app/js/ajax/main.js?v=<?= filemtime('app/js/ajax/main.js'); ?>"></script>
|
||||||
|
<script src="app/js/ui/main.js?v=<?= filemtime('app/js/ui/main.js'); ?>"></script>
|
||||||
|
|
||||||
<?php loadFooterScripts($extraFooterScripts); ?>
|
<?php loadFooterScripts($extraFooterScripts); ?>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ function _install_raspap() {
|
|||||||
_download_latest_files
|
_download_latest_files
|
||||||
_change_file_ownership
|
_change_file_ownership
|
||||||
_create_hostapd_scripts
|
_create_hostapd_scripts
|
||||||
|
_install_raspap_hostapd
|
||||||
_create_plugin_scripts
|
_create_plugin_scripts
|
||||||
_create_lighttpd_scripts
|
_create_lighttpd_scripts
|
||||||
_install_lighttpd_configs
|
_install_lighttpd_configs
|
||||||
@@ -148,21 +149,24 @@ function _get_linux_distro() {
|
|||||||
# Sets php package option based on Linux version, abort if unsupported distro
|
# Sets php package option based on Linux version, abort if unsupported distro
|
||||||
function _set_php_package() {
|
function _set_php_package() {
|
||||||
case $RELEASE in
|
case $RELEASE in
|
||||||
|
13) # Debian 13 trixie
|
||||||
|
php_package="php8.4-fpm"
|
||||||
|
phpiniconf="/etc/php/8.4/fpm/php.ini" ;;
|
||||||
23.05|12*) # Debian 12 & Armbian 23.05
|
23.05|12*) # Debian 12 & Armbian 23.05
|
||||||
php_package="php8.2-cgi"
|
php_package="php8.2-cgi"
|
||||||
phpcgiconf="/etc/php/8.2/cgi/php.ini" ;;
|
phpiniconf="/etc/php/8.2/cgi/php.ini" ;;
|
||||||
23.04) # Ubuntu Server 23.04
|
23.04) # Ubuntu Server 23.04
|
||||||
php_package="php8.1-cgi"
|
php_package="php8.1-cgi"
|
||||||
phpcgiconf="/etc/php/8.1/cgi/php.ini" ;;
|
phpiniconf="/etc/php/8.1/cgi/php.ini" ;;
|
||||||
22.04|20.04|18.04|19.10|11*) # Previous Ubuntu Server, Debian & Armbian distros
|
22.04|20.04|18.04|19.10|11*) # Previous Ubuntu Server, Debian & Armbian distros
|
||||||
php_package="php7.4-cgi"
|
php_package="php7.4-cgi"
|
||||||
phpcgiconf="/etc/php/7.4/cgi/php.ini" ;;
|
phpiniconf="/etc/php/7.4/cgi/php.ini" ;;
|
||||||
10*|11*)
|
10*|11*)
|
||||||
php_package="php7.3-cgi"
|
php_package="php7.3-cgi"
|
||||||
phpcgiconf="/etc/php/7.3/cgi/php.ini" ;;
|
phpiniconf="/etc/php/7.3/cgi/php.ini" ;;
|
||||||
9*)
|
9*)
|
||||||
php_package="php7.0-cgi"
|
php_package="php7.0-cgi"
|
||||||
phpcgiconf="/etc/php/7.0/cgi/php.ini" ;;
|
phpiniconf="/etc/php/7.0/cgi/php.ini" ;;
|
||||||
8)
|
8)
|
||||||
_install_status 1 "${DESC} and php5 are not supported. Please upgrade."
|
_install_status 1 "${DESC} and php5 are not supported. Please upgrade."
|
||||||
exit 1 ;;
|
exit 1 ;;
|
||||||
@@ -271,6 +275,12 @@ function _install_dependencies() {
|
|||||||
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | sudo debconf-set-selections
|
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | sudo debconf-set-selections
|
||||||
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | sudo debconf-set-selections
|
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | sudo debconf-set-selections
|
||||||
sudo apt-get install -y lighttpd git hostapd dnsmasq iptables-persistent $php_package $dhcpcd_package $iw_package $rsync_package $network_tools $ifconfig_package vnstat qrencode jq isoquery || _install_status 1 "Unable to install dependencies"
|
sudo apt-get install -y lighttpd git hostapd dnsmasq iptables-persistent $php_package $dhcpcd_package $iw_package $rsync_package $network_tools $ifconfig_package vnstat qrencode jq isoquery || _install_status 1 "Unable to install dependencies"
|
||||||
|
|
||||||
|
if [[ "$php_package" == *"-fpm" ]]; then
|
||||||
|
install_log "Enabling lighttpd fastcgi-php-fpm module for $php_package"
|
||||||
|
sudo lighty-enable-mod fastcgi-php-fpm || install_status 1 "Unable to enable fastcgi-php-fpm module"
|
||||||
|
fi
|
||||||
|
|
||||||
_install_status 0
|
_install_status 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,6 +787,14 @@ function _enable_raspap_daemon() {
|
|||||||
sudo systemctl enable raspapd.service || _install_status 1 "Failed to enable raspap.service"
|
sudo systemctl enable raspapd.service || _install_status 1 "Failed to enable raspap.service"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Install hostapd@.service
|
||||||
|
function _install_raspap_hostapd() {
|
||||||
|
_install_log "Installing RaspAP hostapd@.service"
|
||||||
|
sudo cp $webroot_dir/installers/hostapd@.service /etc/systemd/system/ || _install_status 1 "Unable to copy hostapd@.service file"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
_install_status 0
|
||||||
|
}
|
||||||
|
|
||||||
# Configure IP forwarding, set IP tables rules, prompt to install RaspAP daemon
|
# Configure IP forwarding, set IP tables rules, prompt to install RaspAP daemon
|
||||||
function _configure_networking() {
|
function _configure_networking() {
|
||||||
_install_log "Configuring networking"
|
_install_log "Configuring networking"
|
||||||
@@ -947,14 +965,14 @@ function _patch_system_files() {
|
|||||||
function _optimize_php() {
|
function _optimize_php() {
|
||||||
if [ "$upgrade" == 0 ]; then
|
if [ "$upgrade" == 0 ]; then
|
||||||
_install_log "Optimize PHP configuration"
|
_install_log "Optimize PHP configuration"
|
||||||
if [ ! -f "$phpcgiconf" ]; then
|
if [ ! -f "$phpiniconf" ]; then
|
||||||
_install_status 2 "PHP configuration could not be found."
|
_install_status 2 "PHP configuration could not be found."
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Backup php.ini and create symlink for restoring.
|
# Backup php.ini and create symlink for restoring.
|
||||||
datetimephpconf=$(date +%F-%R)
|
datetimephpconf=$(date +%F-%R)
|
||||||
sudo cp "$phpcgiconf" "$raspap_dir/backups/php.ini.$datetimephpconf"
|
sudo cp "$phpiniconf" "$raspap_dir/backups/php.ini.$datetimephpconf"
|
||||||
sudo ln -sf "$raspap_dir/backups/php.ini.$datetimephpconf" "$raspap_dir/backups/php.ini"
|
sudo ln -sf "$raspap_dir/backups/php.ini.$datetimephpconf" "$raspap_dir/backups/php.ini"
|
||||||
|
|
||||||
echo -n "Enable HttpOnly for session cookies (Recommended)? [Y/n]: "
|
echo -n "Enable HttpOnly for session cookies (Recommended)? [Y/n]: "
|
||||||
@@ -969,7 +987,7 @@ function _optimize_php() {
|
|||||||
|
|
||||||
if [ "$assume_yes" == 1 ] || [ "$php_session_cookie" == 1 ]; then
|
if [ "$assume_yes" == 1 ] || [ "$php_session_cookie" == 1 ]; then
|
||||||
echo "Php-cgi enabling session.cookie_httponly."
|
echo "Php-cgi enabling session.cookie_httponly."
|
||||||
sudo sed -i -E 's/^session\.cookie_httponly\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/session.cookie_httponly = 1/' "$phpcgiconf"
|
sudo sed -i -E 's/^session\.cookie_httponly\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/session.cookie_httponly = 1/' "$phpiniconf"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$php_package" = "php7.1-cgi" ]; then
|
if [ "$php_package" = "php7.1-cgi" ]; then
|
||||||
@@ -985,7 +1003,7 @@ function _optimize_php() {
|
|||||||
|
|
||||||
if [ "$assume_yes" == 1 ] || [ "$phpopcache" == 1 ]; then
|
if [ "$assume_yes" == 1 ] || [ "$phpopcache" == 1 ]; then
|
||||||
echo -e "Php-cgi enabling opcache.enable."
|
echo -e "Php-cgi enabling opcache.enable."
|
||||||
sudo sed -i -E 's/^;?opcache\.enable\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/opcache.enable = 1/' "$phpcgiconf"
|
sudo sed -i -E 's/^;?opcache\.enable\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/opcache.enable = 1/' "$phpiniconf"
|
||||||
# Make sure opcache extension is turned on.
|
# Make sure opcache extension is turned on.
|
||||||
if [ -f "/usr/sbin/phpenmod" ]; then
|
if [ -f "/usr/sbin/phpenmod" ]; then
|
||||||
sudo phpenmod opcache
|
sudo phpenmod opcache
|
||||||
|
|||||||
12
installers/hostapd@.service
Normal file
12
installers/hostapd@.service
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Hostapd access point for %i
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/sbin/hostapd -P /run/hostapd_%i.pid /etc/hostapd/hostapd-%i.conf
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
@@ -5,6 +5,7 @@ www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wpa_supplicant/wpa_supplicant-[a-zA-Z0
|
|||||||
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant-wl*.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant-wl*.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_supplicant -B -Dnl80211 -c/etc/wpa_supplicant/wpa_supplicant.conf -i[a-zA-Z0-9]*
|
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_supplicant -B -Dnl80211 -c/etc/wpa_supplicant/wpa_supplicant.conf -i[a-zA-Z0-9]*
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_supplicant -i [a-zA-Z0-9]* -c /etc/wpa_supplicant/wpa_supplicant.conf -B
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/rm /var/run/wpa_supplicant/[a-zA-Z0-9]*
|
www-data ALL=(ALL) NOPASSWD:/bin/rm /var/run/wpa_supplicant/[a-zA-Z0-9]*
|
||||||
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* scan_results
|
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* scan_results
|
||||||
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* scan
|
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* scan
|
||||||
@@ -22,6 +23,7 @@ www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop hostapd.service
|
|||||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start dnsmasq.service
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start dnsmasq.service
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop dnsmasq.service
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop dnsmasq.service
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl restart dnsmasq.service
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl restart dnsmasq.service
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl reload dnsmasq.service
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start openvpn-client@client
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start openvpn-client@client
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl enable openvpn-client@client
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl enable openvpn-client@client
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop openvpn-client@client
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop openvpn-client@client
|
||||||
@@ -44,7 +46,7 @@ www-data ALL=(ALL) NOPASSWD:/sbin/ip link set wl* up
|
|||||||
www-data ALL=(ALL) NOPASSWD:/sbin/ip -s a f label wl*
|
www-data ALL=(ALL) NOPASSWD:/sbin/ip -s a f label wl*
|
||||||
www-data ALL=(ALL) NOPASSWD:/sbin/ifup *
|
www-data ALL=(ALL) NOPASSWD:/sbin/ifup *
|
||||||
www-data ALL=(ALL) NOPASSWD:/sbin/ifdown *
|
www-data ALL=(ALL) NOPASSWD:/sbin/ifdown *
|
||||||
www-data ALL=(ALL) NOPASSWD:/sbin/iw
|
www-data ALL=(ALL) NOPASSWD:/sbin/iw dev*
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/enablelog.sh
|
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/enablelog.sh
|
||||||
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/disablelog.sh
|
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/disablelog.sh
|
||||||
@@ -59,10 +61,13 @@ www-data ALL=(ALL) NOPASSWD:/bin/chmod o+r /var/log/dnsmasq.log
|
|||||||
www-data ALL=(ALL) NOPASSWD:/bin/chmod o+r /tmp/wireguard.log
|
www-data ALL=(ALL) NOPASSWD:/bin/chmod o+r /tmp/wireguard.log
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dnsmasqdata /etc/dnsmasq.d/090_adblock.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dnsmasqdata /etc/dnsmasq.d/090_adblock.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dnsmasq_custom /etc/raspap/adblock/custom.txt
|
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dnsmasq_custom /etc/raspap/adblock/custom.txt
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/etc/raspap/adblock/update_blocklist.sh
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wgdata /etc/wireguard/*.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wgdata /etc/wireguard/*.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/mv /tmp/wg-*.key /etc/wireguard/wg-*.key
|
www-data ALL=(ALL) NOPASSWD:/bin/mv /tmp/wg-*.key /etc/wireguard/wg-*.key
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/mv /tmp/wg/* /etc/wireguard/*.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/mv /tmp/wg/* /etc/wireguard/*.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/etc/raspap/adblock/update_blocklist.sh
|
www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/wg0.conf
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/usr/bin/ls /etc/wireguard/
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/usr/bin/ln -s /etc/wireguard/*.conf /etc/wireguard/*.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/usr/bin/socat - /dev/ttyUSB[0-9]
|
www-data ALL=(ALL) NOPASSWD:/usr/bin/socat - /dev/ttyUSB[0-9]
|
||||||
www-data ALL=(ALL) NOPASSWD:/usr/local/sbin/onoff_huawei_hilink.sh *
|
www-data ALL=(ALL) NOPASSWD:/usr/local/sbin/onoff_huawei_hilink.sh *
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/sed -i * /etc/wvdial.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/sed -i * /etc/wvdial.conf
|
||||||
@@ -75,10 +80,13 @@ www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wireguard/*.conf
|
|||||||
www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wireguard/wg-*.key
|
www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wireguard/wg-*.key
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/*.conf
|
www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/*.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/wg-*.key
|
www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/wg-*.key
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/usr/bin/readlink /etc/wireguard/*.conf
|
||||||
www-data ALL=(ALL) NOPASSWD:/usr/sbin/netplan
|
www-data ALL=(ALL) NOPASSWD:/usr/sbin/netplan
|
||||||
www-data ALL=(ALL) NOPASSWD:/bin/truncate -s 0 /tmp/*.log,/bin/truncate -s 0 /var/log/dnsmasq.log
|
www-data ALL=(ALL) NOPASSWD:/bin/truncate -s 0 /tmp/*.log,/bin/truncate -s 0 /var/log/dnsmasq.log
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/usr/bin/nmap --script=broadcast-dhcp-discover -e [a-zA-Z0-9]*
|
||||||
www-data ALL=(ALL) NOPASSWD:/usr/bin/vnstat *
|
www-data ALL=(ALL) NOPASSWD:/usr/bin/vnstat *
|
||||||
www-data ALL=(ALL) NOPASSWD:/usr/sbin/visudo -cf *
|
www-data ALL=(ALL) NOPASSWD:/usr/sbin/visudo -cf *
|
||||||
www-data ALL=(ALL) NOPASSWD:/etc/raspap/plugins/plugin_helper.sh
|
www-data ALL=(ALL) NOPASSWD:/etc/raspap/plugins/plugin_helper.sh
|
||||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl start raspap-network-activity@*.service
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start raspap-network-activity@*.service
|
||||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl stop raspap-network-activity@*.service
|
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop raspap-network-activity@*.service
|
||||||
|
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wpa_conf_* /etc/wpa_supplicant/wpa_supplicant.conf
|
||||||
|
|||||||
@@ -308,7 +308,10 @@ function _check_internet() {
|
|||||||
tput civis # hide cursor
|
tput civis # hide cursor
|
||||||
|
|
||||||
# run check in background
|
# run check in background
|
||||||
( curl -Is --connect-timeout 3 --max-time 15 https://github.com | head -n 1 | grep "HTTP/2 200" >/dev/null ) &
|
(
|
||||||
|
curl -Is --connect-timeout 3 --max-time 15 https://github.com \
|
||||||
|
| grep -q "^HTTP/2 200"
|
||||||
|
) &
|
||||||
local pid=$!
|
local pid=$!
|
||||||
|
|
||||||
# display spinner while curl runs
|
# display spinner while curl runs
|
||||||
@@ -318,8 +321,9 @@ function _check_internet() {
|
|||||||
done
|
done
|
||||||
printf "\r"
|
printf "\r"
|
||||||
|
|
||||||
# check exit status of curl
|
# capture exit status
|
||||||
wait $pid || exit_code=$?
|
wait "$pid"
|
||||||
|
exit_code=$?
|
||||||
|
|
||||||
tput cnorm # restore cursor
|
tput cnorm # restore cursor
|
||||||
|
|
||||||
|
|||||||
@@ -48,21 +48,24 @@ function _get_linux_distro() {
|
|||||||
# Sets php package option based on Linux version, abort if unsupported distro
|
# Sets php package option based on Linux version, abort if unsupported distro
|
||||||
function _set_php_package() {
|
function _set_php_package() {
|
||||||
case $RELEASE in
|
case $RELEASE in
|
||||||
|
13) # Debian 13 trixie
|
||||||
|
php_package="php8.4-fpm"
|
||||||
|
phpiniconf="/etc/php/8.4/fpm/php.ini" ;;
|
||||||
23.05|12*) # Debian 12 & Armbian 23.05
|
23.05|12*) # Debian 12 & Armbian 23.05
|
||||||
php_package="php8.2-cgi"
|
php_package="php8.2-cgi"
|
||||||
phpcgiconf="/etc/php/8.2/cgi/php.ini" ;;
|
phpiniconf="/etc/php/8.2/cgi/php.ini" ;;
|
||||||
23.04) # Ubuntu Server 23.04
|
23.04) # Ubuntu Server 23.04
|
||||||
php_package="php8.1-cgi"
|
php_package="php8.1-cgi"
|
||||||
phpcgiconf="/etc/php/8.1/cgi/php.ini" ;;
|
phpiniconf="/etc/php/8.1/cgi/php.ini" ;;
|
||||||
22.04|20.04|18.04|19.10|11*) # Previous Ubuntu Server, Debian & Armbian distros
|
22.04|20.04|18.04|19.10|11*) # Previous Ubuntu Server, Debian & Armbian distros
|
||||||
php_package="php7.4-cgi"
|
php_package="php7.4-cgi"
|
||||||
phpcgiconf="/etc/php/7.4/cgi/php.ini" ;;
|
phpiniconf="/etc/php/7.4/cgi/php.ini" ;;
|
||||||
10*|11*)
|
10*|11*)
|
||||||
php_package="php7.3-cgi"
|
php_package="php7.3-cgi"
|
||||||
phpcgiconf="/etc/php/7.3/cgi/php.ini" ;;
|
phpiniconf="/etc/php/7.3/cgi/php.ini" ;;
|
||||||
9*)
|
9*)
|
||||||
php_package="php7.0-cgi"
|
php_package="php7.0-cgi"
|
||||||
phpcgiconf="/etc/php/7.0/cgi/php.ini" ;;
|
phpiniconf="/etc/php/7.0/cgi/php.ini" ;;
|
||||||
8)
|
8)
|
||||||
_install_error "${DESC} and php5 are unsupported."
|
_install_error "${DESC} and php5 are unsupported."
|
||||||
exit 1 ;;
|
exit 1 ;;
|
||||||
@@ -127,11 +130,11 @@ function _check_for_backups() {
|
|||||||
sudo cp "$raspap_dir/backups/dhcpcd.conf" /etc/dhcpcd.conf
|
sudo cp "$raspap_dir/backups/dhcpcd.conf" /etc/dhcpcd.conf
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -f "$raspap_dir/backups/php.ini" ] && [ -f "$phpcgiconf" ]; then
|
if [ -f "$raspap_dir/backups/php.ini" ] && [ -f "$phpiniconf" ]; then
|
||||||
echo -n "Restore the last php.ini file? [y/N]: "
|
echo -n "Restore the last php.ini file? [y/N]: "
|
||||||
read answer
|
read answer
|
||||||
if [[ $answer -eq 'y' ]]; then
|
if [[ $answer -eq 'y' ]]; then
|
||||||
sudo cp "$raspap_dir/backups/php.ini" "$phpcgiconf"
|
sudo cp "$raspap_dir/backups/php.ini" "$phpiniconf"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -58,9 +58,6 @@ msgstr "TOR proxy"
|
|||||||
msgid "Authentication"
|
msgid "Authentication"
|
||||||
msgstr "Authentication"
|
msgstr "Authentication"
|
||||||
|
|
||||||
msgid "Change Theme"
|
|
||||||
msgstr "Change Theme"
|
|
||||||
|
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "System"
|
msgstr "System"
|
||||||
|
|
||||||
@@ -581,12 +578,15 @@ msgstr "Clients with a particular hardware MAC address can always be allocated t
|
|||||||
msgid "This option adds <code>dhcp-host</code> entries to the dnsmasq configuration."
|
msgid "This option adds <code>dhcp-host</code> entries to the dnsmasq configuration."
|
||||||
msgstr "This option adds <code>dhcp-host</code> entries to the dnsmasq configuration."
|
msgstr "This option adds <code>dhcp-host</code> entries to the dnsmasq configuration."
|
||||||
|
|
||||||
msgid "This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the DHCPCD configuration."
|
msgid "This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the dhcpcd.conf file."
|
||||||
msgstr "This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the DHCPCD configuration."
|
msgstr "This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the dhcpcd.conf file."
|
||||||
|
|
||||||
msgid "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the DHCPCD configuration."
|
msgid "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the DHCPCD configuration."
|
||||||
msgstr "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the DHCPCD configuration."
|
msgstr "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the DHCPCD configuration."
|
||||||
|
|
||||||
|
msgid "Enable this only if you want your device to use this interface as its primary route to the internet."
|
||||||
|
msgstr "Enable this only if you want your device to use this interface as its primary route to the internet."
|
||||||
|
|
||||||
msgid "Disable wpa_supplicant dhcp hook for this interface"
|
msgid "Disable wpa_supplicant dhcp hook for this interface"
|
||||||
msgstr "Disable wpa_supplicant dhcp hook for this interface"
|
msgstr "Disable wpa_supplicant dhcp hook for this interface"
|
||||||
|
|
||||||
@@ -1064,6 +1064,9 @@ msgstr "System Time"
|
|||||||
msgid "Memory Used"
|
msgid "Memory Used"
|
||||||
msgstr "Memory Used"
|
msgstr "Memory Used"
|
||||||
|
|
||||||
|
msgid "Storage Used"
|
||||||
|
msgstr "Storage Used"
|
||||||
|
|
||||||
msgid "CPU Load"
|
msgid "CPU Load"
|
||||||
msgstr "CPU Load"
|
msgstr "CPU Load"
|
||||||
|
|
||||||
@@ -1250,6 +1253,15 @@ msgstr "Details"
|
|||||||
msgid "Installed"
|
msgid "Installed"
|
||||||
msgstr "Installed"
|
msgstr "Installed"
|
||||||
|
|
||||||
|
msgid "Alert messages"
|
||||||
|
msgstr "Alert messages"
|
||||||
|
|
||||||
|
msgid "Automatically close alerts after a specified timeout"
|
||||||
|
msgstr "Automatically close alerts after a specified timeout"
|
||||||
|
|
||||||
|
msgid "Alert close timeout (milliseconds)"
|
||||||
|
msgstr "Alert close timeout (milliseconds)"
|
||||||
|
|
||||||
#: includes/data_usage.php
|
#: includes/data_usage.php
|
||||||
msgid "Data usage"
|
msgid "Data usage"
|
||||||
msgstr "Data usage"
|
msgstr "Data usage"
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: raspap\n"
|
"Project-Id-Version: raspap\n"
|
||||||
"Report-Msgid-Bugs-To: Bill Zimmerman <billzimmerman@gmail.com>\n"
|
"Report-Msgid-Bugs-To: Bill Zimmerman <billzimmerman@gmail.com>\n"
|
||||||
"POT-Creation-Date: 2017-10-19 08:56+0000\n"
|
"POT-Creation-Date: 2017-10-19 08:56+0000\n"
|
||||||
"PO-Revision-Date: 2024-11-14 10:23\n"
|
"PO-Revision-Date: 2025-07-03 05:44\n"
|
||||||
"Last-Translator: Bill Zimmerman <billzimmerman@gmail.com>\n"
|
"Last-Translator: Bill Zimmerman <billzimmerman@gmail.com>\n"
|
||||||
"Language-Team: Korean\n"
|
"Language-Team: Korean\n"
|
||||||
"Language: ko_KR\n"
|
"Language: ko_KR\n"
|
||||||
@@ -24,8 +24,8 @@ msgstr "RaspAP WiFi 환경설정 포탈"
|
|||||||
msgid "Toggle navigation"
|
msgid "Toggle navigation"
|
||||||
msgstr "토글 내비게이션"
|
msgstr "토글 내비게이션"
|
||||||
|
|
||||||
msgid "RaspAP Wifi Portal"
|
msgid "RaspAP Admin Panel"
|
||||||
msgstr "RaspAP WiFi 포탈"
|
msgstr "RaspAP 관리자 패널"
|
||||||
|
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "대시보드 "
|
msgstr "대시보드 "
|
||||||
@@ -294,8 +294,8 @@ msgstr "주파수 "
|
|||||||
msgid "Link Quality"
|
msgid "Link Quality"
|
||||||
msgstr "링크 품질 "
|
msgstr "링크 품질 "
|
||||||
|
|
||||||
msgid "Information provided by ip and iw and from system"
|
msgid "Information provided by raspap.system"
|
||||||
msgstr "ip 및 iw, 시스템에서 제공한 정보"
|
msgstr "raspap.system에서 제공한 정보"
|
||||||
|
|
||||||
msgid "No MAC Address Found"
|
msgid "No MAC Address Found"
|
||||||
msgstr "MAC 주소를 찾을 수 없습니다"
|
msgstr "MAC 주소를 찾을 수 없습니다"
|
||||||
@@ -333,6 +333,9 @@ msgstr "연결된 디바이스 "
|
|||||||
msgid "Client: Ethernet cable"
|
msgid "Client: Ethernet cable"
|
||||||
msgstr "클라이언트: 이더넷 케이블"
|
msgstr "클라이언트: 이더넷 케이블"
|
||||||
|
|
||||||
|
msgid "Current status"
|
||||||
|
msgstr "현재 상태"
|
||||||
|
|
||||||
msgid "Ethernet"
|
msgid "Ethernet"
|
||||||
msgstr "이더넷"
|
msgstr "이더넷"
|
||||||
|
|
||||||
@@ -345,6 +348,43 @@ msgstr "스마트폰"
|
|||||||
msgid "WiFi"
|
msgid "WiFi"
|
||||||
msgstr "WiFi"
|
msgstr "WiFi"
|
||||||
|
|
||||||
|
msgid "Repeater"
|
||||||
|
msgstr "리피터"
|
||||||
|
|
||||||
|
msgid "Tethering"
|
||||||
|
msgstr "테더링"
|
||||||
|
|
||||||
|
msgid "Cellular"
|
||||||
|
msgstr "셀룰러"
|
||||||
|
|
||||||
|
msgid "AP"
|
||||||
|
msgstr "AP(Access Point)"
|
||||||
|
|
||||||
|
msgid "Bridged"
|
||||||
|
msgstr "브릿지"
|
||||||
|
|
||||||
|
msgid "Adblock"
|
||||||
|
msgstr "광고차단(adblock)"
|
||||||
|
|
||||||
|
msgid "VPN"
|
||||||
|
msgstr "VPN"
|
||||||
|
|
||||||
|
msgid "Netmask"
|
||||||
|
msgstr "넷마스크"
|
||||||
|
|
||||||
|
msgid "5G"
|
||||||
|
msgstr "5G"
|
||||||
|
|
||||||
|
msgid "2.4G"
|
||||||
|
msgstr "2.4G"
|
||||||
|
|
||||||
|
msgid "%d WLAN %s"
|
||||||
|
msgstr "%d WLAN %s"
|
||||||
|
|
||||||
|
msgid "client"
|
||||||
|
msgid_plural "clients"
|
||||||
|
msgstr[0] "클라이언트"
|
||||||
|
|
||||||
msgid "Mobile Data Client"
|
msgid "Mobile Data Client"
|
||||||
msgstr "모바일 데이터 클라이언트"
|
msgstr "모바일 데이터 클라이언트"
|
||||||
|
|
||||||
@@ -387,6 +427,19 @@ msgstr "클라이언트 장치가 없거나 아직 구성되지 않았습니다.
|
|||||||
msgid "No Client device found"
|
msgid "No Client device found"
|
||||||
msgstr "클라이언트 장치가 발견되지 않았습니다."
|
msgstr "클라이언트 장치가 발견되지 않았습니다."
|
||||||
|
|
||||||
|
#: includes/footer.php
|
||||||
|
msgid "Created by the <a href=\"%s\" target=\"_blank\" rel=\"noopener\">%s</a>"
|
||||||
|
msgstr "<a href=\"%s\" target=\"_blank\" rel=\"noopener\">%s</a>에 의해 생성됨"
|
||||||
|
|
||||||
|
msgid "RaspAP Team"
|
||||||
|
msgstr "RaspAP 팀"
|
||||||
|
|
||||||
|
msgid "Get Insiders"
|
||||||
|
msgstr "인사이더 받기"
|
||||||
|
|
||||||
|
msgid "Thanks for being an Insider"
|
||||||
|
msgstr "인사이더가 되어 주셔서 감사합니다"
|
||||||
|
|
||||||
#: includes/dhcp.php
|
#: includes/dhcp.php
|
||||||
msgid "DHCP server settings"
|
msgid "DHCP server settings"
|
||||||
msgstr "DHCP 서버 설정 "
|
msgstr "DHCP 서버 설정 "
|
||||||
@@ -1120,6 +1173,81 @@ msgstr "로그 크기 제한을 %s KB로 변경 중입니다."
|
|||||||
msgid "Information provided by raspap.sysinfo"
|
msgid "Information provided by raspap.sysinfo"
|
||||||
msgstr "raspap.sysinfo에서 제공된 정보"
|
msgstr "raspap.sysinfo에서 제공된 정보"
|
||||||
|
|
||||||
|
msgid "The following user plugins are available to extend RaspAP's functionality."
|
||||||
|
msgstr "다음 사용자 플러그인을 통해 RaspAP의 기능을 확장할 수 있습니다."
|
||||||
|
|
||||||
|
msgid "Choose <strong>Details</strong> for more information and to install a plugin."
|
||||||
|
msgstr "자세한 정보 확인 및 플러그인 설치를 위해 <strong>자세히</strong>를 선택하세요."
|
||||||
|
|
||||||
|
msgid "Network error"
|
||||||
|
msgstr "네트워크 오류"
|
||||||
|
|
||||||
|
msgid "Unable to load plugins"
|
||||||
|
msgstr "플러그인을 불러올 수 없습니다"
|
||||||
|
|
||||||
|
msgid "Reload"
|
||||||
|
msgstr "새로고침"
|
||||||
|
|
||||||
|
msgid "and try again"
|
||||||
|
msgstr "다시 시도하세요"
|
||||||
|
|
||||||
|
msgid "Plugins"
|
||||||
|
msgstr "플러그인"
|
||||||
|
|
||||||
|
msgid "Plugin details"
|
||||||
|
msgstr "플러그인 세부정보"
|
||||||
|
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "이름"
|
||||||
|
|
||||||
|
msgid "Version"
|
||||||
|
msgstr "버전"
|
||||||
|
|
||||||
|
msgid "Description"
|
||||||
|
msgstr "설명"
|
||||||
|
|
||||||
|
msgid "Plugin source"
|
||||||
|
msgstr "플러그인 소스"
|
||||||
|
|
||||||
|
msgid "Author"
|
||||||
|
msgstr "작성자"
|
||||||
|
|
||||||
|
msgid "License"
|
||||||
|
msgstr "라이선스"
|
||||||
|
|
||||||
|
msgid "Language locale"
|
||||||
|
msgstr "언어 로케일"
|
||||||
|
|
||||||
|
msgid "Configuration files"
|
||||||
|
msgstr "구성 파일"
|
||||||
|
|
||||||
|
msgid "Dependencies"
|
||||||
|
msgstr "종속성"
|
||||||
|
|
||||||
|
msgid "Permissions"
|
||||||
|
msgstr "권한"
|
||||||
|
|
||||||
|
msgid "Non-privileged users"
|
||||||
|
msgstr "비권한 사용자"
|
||||||
|
|
||||||
|
msgid "Install now"
|
||||||
|
msgstr "지금 설치하기"
|
||||||
|
|
||||||
|
msgid "Installing plugin"
|
||||||
|
msgstr "플러그인 설치 중"
|
||||||
|
|
||||||
|
msgid "Plugin installation in progress..."
|
||||||
|
msgstr "플러그인 설치 진행 중..."
|
||||||
|
|
||||||
|
msgid "Plugin install completed."
|
||||||
|
msgstr "플러그인 설치가 완료되었습니다."
|
||||||
|
|
||||||
|
msgid "Details"
|
||||||
|
msgstr "자세히"
|
||||||
|
|
||||||
|
msgid "Installed"
|
||||||
|
msgstr "설치됨"
|
||||||
|
|
||||||
#: includes/data_usage.php
|
#: includes/data_usage.php
|
||||||
msgid "Data usage"
|
msgid "Data usage"
|
||||||
msgstr "데이터 사용량 "
|
msgstr "데이터 사용량 "
|
||||||
@@ -1422,14 +1550,14 @@ msgstr "WireGuard 구성(config) 업로드"
|
|||||||
msgid "This option uploads and installs an existing WireGuard <code>.conf</code> file on this device."
|
msgid "This option uploads and installs an existing WireGuard <code>.conf</code> file on this device."
|
||||||
msgstr "이 옵션은 이 장치에 기존 WireGuard <code>.conf</code> 파일을 업로드하고 설치합니다"
|
msgstr "이 옵션은 이 장치에 기존 WireGuard <code>.conf</code> 파일을 업로드하고 설치합니다"
|
||||||
|
|
||||||
msgid "Apply iptables rules for AP interface"
|
msgid "Apply iptables rules to the selected interface"
|
||||||
msgstr "AP 인터페이스에 iptables 규칙 적용"
|
msgstr "선택한 인터페이스에 iptables 규칙 적용"
|
||||||
|
|
||||||
msgid "Recommended if you wish to forward network traffic from the wg0 interface to clients connected on the AP interface."
|
msgid "Recommended if you wish to forward network traffic from the wg0 interface to clients connected on a desired interface. The active AP interface is the default."
|
||||||
msgstr "wg0 인터페이스에서 AP 인터페이스에 연결된 클라이언트로 네트워크 트래픽을 전달하려는 경우에 권장됩니다."
|
msgstr "wg0 인터페이스에서 원하는 인터페이스에 연결된 클라이언트로 네트워크 트래픽을 포워딩하려는 경우에 권장됩니다. 기본값은 활성화된 AP 인터페이스입니다."
|
||||||
|
|
||||||
msgid "This option adds <strong>iptables</strong> <code>Postup</code> and <code>PostDown</code> rules for the configured AP interface (%s)."
|
msgid "This option adds <strong>iptables</strong> <code>Postup</code> and <code>PostDown</code> rules for the interface selected below."
|
||||||
msgstr "이 옵션은 구성된 AP 인터페이스(%s) 에 대한 <strong>iptables</strong> <code>Postup</code> 및 <code>PostDown</code> 규칙을 추가합니다."
|
msgstr "이 옵션은 아래에서 선택한 인터페이스에 대해 <strong>iptables</strong> <code>Postup</code> 및 <code>PostDown</code> 규칙을 추가합니다."
|
||||||
|
|
||||||
msgid "Select WireGuard configuration file (.conf)"
|
msgid "Select WireGuard configuration file (.conf)"
|
||||||
msgstr "WireGuard 구성 파일(.conf) 선택"
|
msgstr "WireGuard 구성 파일(.conf) 선택"
|
||||||
@@ -1521,8 +1649,8 @@ msgstr "WireGuard 구성을 업데이트하지 못했습니다."
|
|||||||
msgid "Enable kill switch"
|
msgid "Enable kill switch"
|
||||||
msgstr "킬 스위치 활성화"
|
msgstr "킬 스위치 활성화"
|
||||||
|
|
||||||
msgid "This option adds <strong>iptables</strong> <code>PostUp</code> and <code>PreDown</code> rules for the configured AP interface (%s)."
|
msgid "This option adds <strong>iptables</strong> <code>PostUp</code> and <code>PreDown</code> rules for the configured interface."
|
||||||
msgstr "이 옵션은 구성된 AP 인터페이스(%s) 에 대해 <strong>iptables</strong> <code>PostUp</code> 및 <code>PreDown</code> 규칙을 추가합니다."
|
msgstr "이 옵션은 구성된 인터페이스에 대해 <strong>iptables</strong> <code>PostUp</code> 및 <code>PreDown</code> 규칙을 추가합니다."
|
||||||
|
|
||||||
msgid "Recommended if you wish to prevent the flow of unencrypted packets through non-WireGuard interfaces."
|
msgid "Recommended if you wish to prevent the flow of unencrypted packets through non-WireGuard interfaces."
|
||||||
msgstr "WireGuard 인터페이스가 아닌 다른 인터페이스를 통한 암호화되지 않은 패킷 흐름을 방지하려는 경우 권장됩니다."
|
msgstr "WireGuard 인터페이스가 아닌 다른 인터페이스를 통한 암호화되지 않은 패킷 흐름을 방지하려는 경우 권장됩니다."
|
||||||
@@ -1605,6 +1733,12 @@ msgstr "방화벽 비활성화"
|
|||||||
msgid "Enable Firewall"
|
msgid "Enable Firewall"
|
||||||
msgstr "방화벽 활성화"
|
msgstr "방화벽 활성화"
|
||||||
|
|
||||||
|
msgid "Changing the firewall status may disrupt or allow incoming traffic. Choose <strong>Proceed</strong> to continue."
|
||||||
|
msgstr "방화벽 상태를 변경하면 수신 트래픽이 차단되거나 허용될 수 있습니다. 계속하려면 <strong>계속</strong>을 선택하세요."
|
||||||
|
|
||||||
|
msgid "Proceed"
|
||||||
|
msgstr "진행"
|
||||||
|
|
||||||
msgid "Apply changes"
|
msgid "Apply changes"
|
||||||
msgstr "변경 사항을 적용합니다"
|
msgstr "변경 사항을 적용합니다"
|
||||||
|
|
||||||
@@ -1848,6 +1982,24 @@ msgstr "restapi.service를 재시작하는 중입니다."
|
|||||||
msgid "Information provided by restapi.service"
|
msgid "Information provided by restapi.service"
|
||||||
msgstr "restapi.service에서 제공한 정보"
|
msgstr "restapi.service에서 제공한 정보"
|
||||||
|
|
||||||
|
msgid "Session Expired"
|
||||||
|
msgstr "세션 만료됨"
|
||||||
|
|
||||||
|
msgid "Your session has expired. Please login to continue."
|
||||||
|
msgstr "세션이 만료되었습니다. 계속하려면 로그인해 주세요."
|
||||||
|
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "로그인"
|
||||||
|
|
||||||
|
msgid "Administrator login"
|
||||||
|
msgstr "관리자 로그인"
|
||||||
|
|
||||||
|
msgid "Forgot password"
|
||||||
|
msgstr "비밀번호 찾기"
|
||||||
|
|
||||||
|
msgid "Login failed"
|
||||||
|
msgstr "로그인 실패"
|
||||||
|
|
||||||
msgid "NTP Server"
|
msgid "NTP Server"
|
||||||
msgstr "NTP 서버"
|
msgstr "NTP 서버"
|
||||||
|
|
||||||
@@ -1911,3 +2063,186 @@ msgstr "NTP 구성을 %s에서 찾을 수 없습니다."
|
|||||||
msgid "NTP configuration updated"
|
msgid "NTP configuration updated"
|
||||||
msgstr "NTP 구성이 업데이트되었습니다."
|
msgstr "NTP 구성이 업데이트되었습니다."
|
||||||
|
|
||||||
|
msgid "Advertising device as a Tailscale exit node"
|
||||||
|
msgstr "Tailscale 종료 노드로 디바이스 광고 중"
|
||||||
|
|
||||||
|
msgid "Attempting to optimize UDP throughput"
|
||||||
|
msgstr "UDP 처리량 최적화를 시도하는 중"
|
||||||
|
|
||||||
|
msgid "Kernel transport layer offloads enabled for UDP"
|
||||||
|
msgstr "UDP에 대해 커널 전송 계층 오프로딩이 활성화됨"
|
||||||
|
|
||||||
|
msgid "Failed to enable kernel transport layer offloads for UDP"
|
||||||
|
msgstr "UDP에 대한 커널 전송 계층 오프로딩을 활성화하지 못했습니다"
|
||||||
|
|
||||||
|
msgid "Attempting to set tailscale up"
|
||||||
|
msgstr "Tailscale 설정을 시도하는 중"
|
||||||
|
|
||||||
|
msgid "Attempting to set tailscale down"
|
||||||
|
msgstr "Tailscale 비활성화를 시도하는 중"
|
||||||
|
|
||||||
|
msgid "Attempting to disconnect from tailscale"
|
||||||
|
msgstr "Tailscale 연결 해제를 시도하는 중"
|
||||||
|
|
||||||
|
msgid "Disconnected from tailscale and expired node key"
|
||||||
|
msgstr "Tailscale에서 연결이 해제되었으며 노드 키가 만료되었습니다"
|
||||||
|
|
||||||
|
msgid "Unable to disconnect from tailscale"
|
||||||
|
msgstr "Tailscale에서 연결을 해제할 수 없습니다"
|
||||||
|
|
||||||
|
msgid "Expected tailscale binary not found at: %"
|
||||||
|
msgstr "예상된 Tailscale 바이너리가 다음 위치에 없습니다: %"
|
||||||
|
|
||||||
|
msgid "A Tailscale VPN exit node extension for RaspAP"
|
||||||
|
msgstr "RaspAP용 Tailscale VPN 종료 노드 확장 프로그램"
|
||||||
|
|
||||||
|
msgid "Unable to retrieve Tailscale login. Choose <strong>%s</strong> to continue."
|
||||||
|
msgstr "Tailscale 로그인 정보를 가져올 수 없습니다. 계속하려면 <strong>%s</strong>을 선택하세요."
|
||||||
|
|
||||||
|
msgid "Start Tailscale"
|
||||||
|
msgstr "Tailscale 시작"
|
||||||
|
|
||||||
|
msgid "Stop Tailscale"
|
||||||
|
msgstr "Tailscale 중지"
|
||||||
|
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "다음"
|
||||||
|
|
||||||
|
msgid "Device approved and activated as a Tailscale exit node"
|
||||||
|
msgstr "디바이스가 승인되어 Tailscale 종료 노드로 활성화되었습니다"
|
||||||
|
|
||||||
|
msgid "Not connected: Login required."
|
||||||
|
msgstr "연결되지 않음: 로그인 필요."
|
||||||
|
|
||||||
|
msgid "Tailscale VPN"
|
||||||
|
msgstr "Tailscale VPN"
|
||||||
|
|
||||||
|
msgid "Exit node activated"
|
||||||
|
msgstr "출구 노드가 활성화되었습니다"
|
||||||
|
|
||||||
|
msgid "The device <code>%s</code> is connected with the address <code>%s</code> and offers an <strong>exit node</strong>."
|
||||||
|
msgstr "디바이스 <code>%s</code>는 주소 <code>%s</code>로 연결되어 있으며 <strong>출구 노드</strong>를 제공합니다."
|
||||||
|
|
||||||
|
msgid "See the %s on how to use this exit node with your devices."
|
||||||
|
msgstr "이 출구 노드를 디바이스와 함께 사용하는 방법은 %s를 참조하세요."
|
||||||
|
|
||||||
|
msgid "Allow exit node"
|
||||||
|
msgstr "출구 노드 허용"
|
||||||
|
|
||||||
|
msgid "The device <code>%s</code> is pending approval as an exit node."
|
||||||
|
msgstr "디바이스 <code>%s</code>는 출구 노드로 승인 대기 중입니다."
|
||||||
|
|
||||||
|
msgid "Locate the <code>%s</code> <strong>Exit Node</strong> badge in the machines list."
|
||||||
|
msgstr "머신 목록에서 <code>%s</code> <strong>출구 노드</strong> 배지(badge)를 찾으세요."
|
||||||
|
|
||||||
|
msgid "Open Tailscale Machines"
|
||||||
|
msgstr "Tailscale 머신 열기"
|
||||||
|
|
||||||
|
msgid "To allow this device as an exit node, choose <strong>Open Tailscale Machines</strong>."
|
||||||
|
msgstr "이 디바이스를 출구 노드로 허용하려면 <strong>Tailscale 머신 열기</strong>를 선택하세요."
|
||||||
|
|
||||||
|
msgid "From the %s icon menu of the exit node, open the %s panel."
|
||||||
|
msgstr "출구 노드의 %s 아이콘 메뉴에서 %s 패널을 여세요."
|
||||||
|
|
||||||
|
msgid "Edit route settings"
|
||||||
|
msgstr "라우팅 설정 편집"
|
||||||
|
|
||||||
|
msgid "Login to Tailscale"
|
||||||
|
msgstr "Tailscale에 로그인"
|
||||||
|
|
||||||
|
msgid "To connect device %s to your tailnet, choose %s."
|
||||||
|
msgstr "디바이스 %s를 tailnet에 연결하려면 %s를 선택하세요."
|
||||||
|
|
||||||
|
msgid "After logging in, choose <strong>Next</strong> to continue."
|
||||||
|
msgstr "로그인 후, 계속하려면 <strong>다음</strong>을 선택하세요."
|
||||||
|
|
||||||
|
msgid "Configure exit node"
|
||||||
|
msgstr "출구 노드 구성"
|
||||||
|
|
||||||
|
msgid "The device <code>%s</code> is connected to your tailnet with the address <code>%s</code>."
|
||||||
|
msgstr "디바이스 <code>%s</code>는 주소 <code>%s</code>로 tailnet에 연결되어 있습니다."
|
||||||
|
|
||||||
|
msgid "By default, Tailscale only routes traffic between the devices on which it's been installed. You can also route all your public internet traffic by configuring a device on your network as a <strong>exit node</strong>"
|
||||||
|
msgstr "기본적으로 Tailscale은 설치된 디바이스 간의 트래픽만 라우팅합니다. 네트워크의 디바이스를 <strong>출구 노드</strong>로 구성하면 모든 공용 인터넷 트래픽도 라우팅할 수 있습니다"
|
||||||
|
|
||||||
|
msgid "When you route all traffic through an exit node, you're effectively using default routes (0.0.0.0/0, ::/0), similar to how you would if you were using a typical VPN."
|
||||||
|
msgstr "모든 트래픽을 출구 노드를 통해 라우팅하면, 일반적인 VPN을 사용할 때처럼 기본 경로(0.0.0.0/0, ::/0)를 사용하는 것과 동일한 방식으로 동작하게 됩니다."
|
||||||
|
|
||||||
|
msgid "You have the option of configuring this device as an exit node, or using another exit node in your tailnet."
|
||||||
|
msgstr "이 디바이스를 출구 노드로 구성하거나, tailnet 내의 다른 출구 노드를 사용할 수 있습니다."
|
||||||
|
|
||||||
|
msgid "Select an existing exit node on your tailnet"
|
||||||
|
msgstr "tailnet에서 기존 출구 노드를 선택하세요"
|
||||||
|
|
||||||
|
msgid "This is a typical configuration if you're using this device as a VPN travel router, for example."
|
||||||
|
msgstr "예를 들어 이 디바이스를 VPN 여행 라우터로 사용할 경우, 이는 일반적인 구성입니다."
|
||||||
|
|
||||||
|
msgid "Configure this device as a new exit node"
|
||||||
|
msgstr "이 디바이스를 새로운 출구 노드로 구성하세요"
|
||||||
|
|
||||||
|
msgid "By configuring this device as an exit node, public internet traffic from devices connected in your tailnet will be routed through it."
|
||||||
|
msgstr "이 디바이스를 출구 노드로 구성하면, tailnet에 연결된 디바이스의 공용 인터넷 트래픽이 이 디바이스를 통해 라우팅됩니다."
|
||||||
|
|
||||||
|
msgid "For security reasons, you must opt in to enable exit node functionality. The first step is to advertise <code>%s</code> as an exit node in your tailnet. In the next step, you'll allow this device to be an exit node."
|
||||||
|
msgstr "보안상의 이유로 출구 노드 기능을 활성화하려면 사용자가 직접 동의해야 합니다. 첫 번째 단계는 <code>%s</code>를 tailnet에서 출구 노드로 광고하는 것입니다. 다음 단계에서는 이 디바이스를 출구 노드로 허용하게 됩니다."
|
||||||
|
|
||||||
|
msgid "Advertise <code>%s</code> as an exit node"
|
||||||
|
msgstr "<code>%s</code>를 출구 노드로 광고"
|
||||||
|
|
||||||
|
msgid "This effectively configures Tailscale as a VPN to mask your real location, access region-restricted content, or enhance privacy when connecting from untrusted networks."
|
||||||
|
msgstr "이는 Tailscale을 VPN으로 구성하여 실제 위치를 숨기거나, 지역 제한 콘텐츠에 접근하거나, 신뢰되지 않은 네트워크에서 연결할 때 프라이버시를 강화하는 데 효과적입니다."
|
||||||
|
|
||||||
|
msgid "This option lets Tailscale know your device is ready to route traffic."
|
||||||
|
msgstr "이 옵션은 Tailscale에 디바이스가 트래픽을 라우팅할 준비가 되었음을 알립니다."
|
||||||
|
|
||||||
|
msgid "Recommended for Tailscale exit nodes with Linux 6.2 or later kernels, this uses UDP generic receive offload (GRO) forwarding to reduce CPU overhead."
|
||||||
|
msgstr "Linux 6.2 이상 커널을 사용하는 Tailscale 출구 노드에 권장되며, UDP 일반 수신 오프로딩(GRO) 포워딩을 사용하여 CPU 부하를 줄입니다."
|
||||||
|
|
||||||
|
msgid "This option enables transport layer offloads for better performance."
|
||||||
|
msgstr "이 옵션은 더 나은 성능을 위해 전송 계층 오프로딩을 활성화합니다."
|
||||||
|
|
||||||
|
msgid "Select an exit node"
|
||||||
|
msgstr "출구 노드를 선택하세요"
|
||||||
|
|
||||||
|
msgid "To use <code>%s</code> as a VPN gateway, configure Tailscale to use an exit node. Tailscale's suggested node is indicated with a star."
|
||||||
|
msgstr "<code>%s</code>를 VPN 게이트웨이로 사용하려면, Tailscale에서 출구 노드를 사용하도록 구성하세요. Tailscale이 추천하는 노드는 별표로 표시됩니다."
|
||||||
|
|
||||||
|
msgid "Advertise a <strong>subnet route</strong> for the active <code>%s</code> AP interface"
|
||||||
|
msgstr "활성화된 <code>%s</code> AP 인터페이스에 대해 <strong>서브넷 라우트</strong>를 광고하세요"
|
||||||
|
|
||||||
|
msgid "Subnet routes let you extend your Tailscale network (known as a tailnet) to include devices that don't or can't run the Tailscale client."
|
||||||
|
msgstr "서브넷 라우트를 사용하면 Tailscale 네트워크(즉, tailnet)를 Tailscale 클라이언트를 실행할 수 없거나 실행하지 않는 디바이스까지 확장할 수 있습니다."
|
||||||
|
|
||||||
|
msgid "A subnet route acts as a gateway between your tailnet and a physical subnet. The subnet of the active AP interface is preconfigured below; edit if necessary."
|
||||||
|
msgstr "서브넷 라우트는 tailnet과 물리적 서브넷 간의 게이트웨이 역할을 합니다. 활성화된 AP 인터페이스의 서브넷이 아래에 미리 구성되어 있으며, 필요에 따라 수정할 수 있습니다."
|
||||||
|
|
||||||
|
msgid "Route LAN traffic through the exit node."
|
||||||
|
msgstr "LAN 트래픽을 출구 노드를 통해 라우팅하세요."
|
||||||
|
|
||||||
|
msgid "This will direct all LAN traffic to go through your exit node only."
|
||||||
|
msgstr "이 설정은 모든 LAN 트래픽을 출구 노드를 통해서만 전달하도록 합니다."
|
||||||
|
|
||||||
|
msgid "Choose <strong>Next</strong> to configure <code>%s</code> to use the selected exit node."
|
||||||
|
msgstr "선택한 출구 노드를 사용하도록 <code>%s</code>를 구성하려면 <strong>다음</strong>을 선택하세요."
|
||||||
|
|
||||||
|
msgid "No exit nodes found on your tailnet. Choose <strong>Back</strong> to continue."
|
||||||
|
msgstr "tailnet에서 출구 노드를 찾을 수 없습니다. 계속하려면 <strong>뒤로</strong>를 선택하세요."
|
||||||
|
|
||||||
|
msgid "Using exit node"
|
||||||
|
msgstr "출구 노드 사용 중"
|
||||||
|
|
||||||
|
msgid "The device <code>%s</code> is configured to use exit node <code>%s</code>. It has the Tailscale MagicDNS address <code>%s</code>."
|
||||||
|
msgstr "디바이스 <code>%s</code>는 출구 노드 <code>%s</code>를 사용하도록 구성되어 있습니다. Tailscale MagicDNS 주소는 <code>%s</code>입니다."
|
||||||
|
|
||||||
|
msgid "Choose <strong>Save settings</strong> to continue."
|
||||||
|
msgstr "계속하려면 <strong>설정 저장</strong>을 선택하세요."
|
||||||
|
|
||||||
|
msgid "Choose <strong>Next</strong> to continue."
|
||||||
|
msgstr "계속하려면 <strong>다음</strong>을 선택하세요."
|
||||||
|
|
||||||
|
msgid "Tailnet status"
|
||||||
|
msgstr "Tailnet 상태"
|
||||||
|
|
||||||
|
msgid "Current <code>tailnet</code> status is displayed below."
|
||||||
|
msgstr "현재 <code>tailnet</code> 상태가 아래에 표시됩니다."
|
||||||
|
|
||||||
|
|||||||
2
plugins
2
plugins
Submodule plugins updated: 38331709b6...b8e51de448
@@ -82,9 +82,10 @@ class HTTPAuth
|
|||||||
session_regenerate_id(true); // generate a new session id
|
session_regenerate_id(true); // generate a new session id
|
||||||
session_unset(); // unset all session variables
|
session_unset(); // unset all session variables
|
||||||
session_destroy(); // destroy the session
|
session_destroy(); // destroy the session
|
||||||
|
$basePath = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/');
|
||||||
$redirectUrl = $_SERVER['REQUEST_URI'];
|
$redirectUrl = $_SERVER['REQUEST_URI'];
|
||||||
if (strpos($redirectUrl, '/login') === false) {
|
if (strpos($redirectUrl, '/login') === false) {
|
||||||
header('Location: /login?action=' . urlencode($redirectUrl));
|
header('Location: ' . $basePath . '/login?action=' . urlencode(basename($redirectUrl)));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class HtmlErrorRenderer
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->charset = 'UTF-8';
|
$this->charset = 'UTF-8';
|
||||||
$this->projectDir = $_SERVER['DOCUMENT_ROOT'];
|
$this->projectDir = dirname(__DIR__, 3);
|
||||||
$this->template = '/templates/exception.php';
|
$this->template = '/templates/exception.php';
|
||||||
$this->debug = true;
|
$this->debug = true;
|
||||||
}
|
}
|
||||||
|
|||||||
109
src/RaspAP/Networking/DeviceScanner.php
Normal file
109
src/RaspAP/Networking/DeviceScanner.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeviceScanner class
|
||||||
|
*
|
||||||
|
* @description A class for enumerating available devices and their details
|
||||||
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
|
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace RaspAP\Networking;
|
||||||
|
|
||||||
|
class DeviceScanner
|
||||||
|
{
|
||||||
|
public function listDevices(): array
|
||||||
|
{
|
||||||
|
$devices = [];
|
||||||
|
|
||||||
|
foreach (glob('/sys/class/net/*') as $ifacePath) {
|
||||||
|
$iface = basename($ifacePath);
|
||||||
|
if ($iface === 'lo') {
|
||||||
|
continue; // skip loopback
|
||||||
|
}
|
||||||
|
|
||||||
|
$device = [
|
||||||
|
'name' => $iface,
|
||||||
|
'mac' => $this->readFile("$ifacePath/address"),
|
||||||
|
'ipaddress' => $this->getIPAddress($iface),
|
||||||
|
'vendor' => '',
|
||||||
|
'model' => '',
|
||||||
|
'vid' => '',
|
||||||
|
'pid' => '',
|
||||||
|
'driver' => '',
|
||||||
|
'type' => $this->getInterfaceType($iface),
|
||||||
|
'isAP' => false,
|
||||||
|
'connected' => 'y', // placeholder
|
||||||
|
'signal' => '0 dB (100%)' // placeholder
|
||||||
|
];
|
||||||
|
|
||||||
|
$udev = $this->getUdevAttributes($iface);
|
||||||
|
$device['vendor'] = $this->getVendorName($udev);
|
||||||
|
$device['model'] = $udev['ID_MODEL_FROM_DATABASE'] ?? $udev['ID_MODEL'] ?? '';
|
||||||
|
$device['vid'] = $udev['ID_VENDOR_ID'] ?? '';
|
||||||
|
$device['pid'] = $udev['ID_MODEL_ID'] ?? '';
|
||||||
|
$device['driver'] = $udev['ID_NET_DRIVER'] ?? '';
|
||||||
|
|
||||||
|
$devices[] = $device;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readFile(string $path): string
|
||||||
|
{
|
||||||
|
return is_readable($path) ? trim(file_get_contents($path)) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getIPAddress(string $iface): string
|
||||||
|
{
|
||||||
|
$cmd = "ip -4 -o addr show dev " . escapeshellarg($iface) . " | awk '{print $4}' | cut -d/ -f1";
|
||||||
|
$result = [];
|
||||||
|
exec($cmd, $result);
|
||||||
|
return $result[0] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getInterfaceType(string $iface): string
|
||||||
|
{
|
||||||
|
$wirelessPath = "/sys/class/net/{$iface}/wireless";
|
||||||
|
if (is_dir($wirelessPath)) {
|
||||||
|
return 'wlan';
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeFile = "/sys/class/net/{$iface}/type";
|
||||||
|
$type = $this->readFile($typeFile);
|
||||||
|
|
||||||
|
return match ($type) {
|
||||||
|
'1' => 'eth', // ARPHRD_ETHER
|
||||||
|
'772' => 'loopback',
|
||||||
|
'512' => 'ppp',
|
||||||
|
default => 'unknown'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUdevAttributes(string $iface): array
|
||||||
|
{
|
||||||
|
$attributes = [];
|
||||||
|
$output = [];
|
||||||
|
$path = escapeshellarg("/sys/class/net/{$iface}");
|
||||||
|
|
||||||
|
exec("udevadm info {$path}", $output);
|
||||||
|
|
||||||
|
foreach ($output as $line) {
|
||||||
|
if (preg_match('/E: (\w+)=([^\n]+)/', $line, $matches)) {
|
||||||
|
$attributes[$matches[1]] = $matches[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getVendorName(array $udev): ?string
|
||||||
|
{
|
||||||
|
return $udev['ID_VENDOR_FROM_DATABASE']
|
||||||
|
?? $udev['ID_VENDOR']
|
||||||
|
?? $udev['ID_OUI_FROM_DATABASE']
|
||||||
|
?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
547
src/RaspAP/Networking/Hotspot/DhcpcdManager.php
Normal file
547
src/RaspAP/Networking/Hotspot/DhcpcdManager.php
Normal file
@@ -0,0 +1,547 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dhcpcd configuration class for RaspAP
|
||||||
|
*
|
||||||
|
* @description Handles building, saving and safe updating of dhcpcd configs
|
||||||
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
|
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RaspAP\Networking\Hotspot;
|
||||||
|
|
||||||
|
use RaspAP\Messages\StatusMessage;
|
||||||
|
|
||||||
|
class DhcpcdManager
|
||||||
|
{
|
||||||
|
private const CONF_DEFAULT = RASPI_DHCPCD_CONFIG;
|
||||||
|
private const CONF_TMP = '/tmp/dhcpddata';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a dhcpcd config for an interface
|
||||||
|
*
|
||||||
|
* @param string $ap_iface
|
||||||
|
* @param bool $bridgedEnable
|
||||||
|
* @param bool $repeaterEnable
|
||||||
|
* @param bool $wifiAPEnable
|
||||||
|
* @param bool $dualAPEnable
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function buildConfig(
|
||||||
|
string $ap_iface,
|
||||||
|
bool $bridgedEnable,
|
||||||
|
bool $repeaterEnable,
|
||||||
|
bool $wifiAPEnable,
|
||||||
|
bool $dualAPEnable,
|
||||||
|
StatusMessage $status
|
||||||
|
): bool
|
||||||
|
{
|
||||||
|
// determine static IP, routers, DNS
|
||||||
|
$jsonData = $this->getInterfaceConfig($ap_iface);
|
||||||
|
|
||||||
|
$ip_address = empty($jsonData['StaticIP'])
|
||||||
|
? getDefaultNetValue('dhcp', $ap_iface, 'static ip_address')
|
||||||
|
: $jsonData['StaticIP'];
|
||||||
|
$domain_name_server = empty($jsonData['StaticDNS'])
|
||||||
|
? getDefaultNetValue('dhcp', $ap_iface, 'static domain_name_server')
|
||||||
|
: $jsonData['StaticDNS'];
|
||||||
|
$routers = empty($jsonData['StaticRouters'])
|
||||||
|
? getDefaultNetValue('dhcp', $ap_iface, 'static routers')
|
||||||
|
: $jsonData['StaticRouters'];
|
||||||
|
$netmask = (empty($jsonData['SubnetMask']) || $jsonData['SubnetMask'] === '0.0.0.0')
|
||||||
|
? getDefaultNetValue('dhcp', $ap_iface, 'subnetmask')
|
||||||
|
: $jsonData['SubnetMask'];
|
||||||
|
if (!preg_match('/.*\/\d+/', $ip_address)) {
|
||||||
|
$ip_address .= '/' . mask2cidr($netmask);
|
||||||
|
}
|
||||||
|
$config = [];
|
||||||
|
|
||||||
|
if ($bridgedEnable) {
|
||||||
|
$config = array_keys(getDefaultNetOpts('dhcp', 'options'));
|
||||||
|
$config[] = '# RaspAP br0 configuration';
|
||||||
|
$config[] = 'denyinterfaces eth0 wlan0';
|
||||||
|
$config[] = 'interface br0';
|
||||||
|
} elseif ($repeaterEnable) {
|
||||||
|
$config = [
|
||||||
|
'# RaspAP ' . $ap_iface . ' configuration',
|
||||||
|
'interface ' . $ap_iface,
|
||||||
|
'static ip_address=' . $ip_address,
|
||||||
|
'static routers=' . $routers,
|
||||||
|
'static domain_name_server=' . $domain_name_server
|
||||||
|
];
|
||||||
|
$client_metric = getIfaceMetric($_SESSION['wifi_client_interface']);
|
||||||
|
if (is_int($client_metric)) {
|
||||||
|
$config[] = 'metric ' . ((int)$client_metric + 1);
|
||||||
|
} else {
|
||||||
|
$status->addMessage(
|
||||||
|
'Unable to obtain metric value for client interface. Repeater mode inactive.',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif ($wifiAPEnable) {
|
||||||
|
$config = array_keys(getDefaultNetOpts('dhcp', 'options'));
|
||||||
|
$config[] = '# RaspAP uap0 configuration';
|
||||||
|
$config[] = 'interface uap0';
|
||||||
|
$config[] = 'static ip_address=' . $ip_address;
|
||||||
|
$config[] = 'nohook wpa_supplicant';
|
||||||
|
} elseif ($dualAPEnable) {
|
||||||
|
$config = [
|
||||||
|
'# RaspAP ' . $ap_iface . ' configuration',
|
||||||
|
'interface ' . $ap_iface,
|
||||||
|
'static ip_address=' . $ip_address,
|
||||||
|
'static routers=' . $routers,
|
||||||
|
'static domain_name_server=' . $domain_name_server,
|
||||||
|
'nogateway'
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$config = $this->updateDhcpcdConfig(
|
||||||
|
$ap_iface,
|
||||||
|
$jsonData,
|
||||||
|
$ip_address,
|
||||||
|
$routers,
|
||||||
|
$domain_name_server
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$dhcp_cfg = file_get_contents(SELF::CONF_DEFAULT);
|
||||||
|
$skip_dhcp = false;
|
||||||
|
|
||||||
|
if (preg_match('/wlan[3-9]\d*|wlan[1-9]\d+/', $ap_iface)) {
|
||||||
|
$skip_dhcp = true;
|
||||||
|
} elseif ($bridgedEnable == 1 || $wifiAPEnable == 1) {
|
||||||
|
$dhcp_cfg = join(PHP_EOL, $config);
|
||||||
|
$status->addMessage(sprintf(_('DHCP configuration for %s enabled.'), $ap_iface), 'success');
|
||||||
|
} elseif (!preg_match('/^interface\s'.$ap_iface.'$/m', $dhcp_cfg)) {
|
||||||
|
$config[] = PHP_EOL;
|
||||||
|
$config= join(PHP_EOL, $config);
|
||||||
|
$dhcp_cfg = $this->removeIface($dhcp_cfg,'br0');
|
||||||
|
$dhcp_cfg = $this->removeIface($dhcp_cfg,'uap0');
|
||||||
|
$dhcp_cfg .= $config;
|
||||||
|
} else {
|
||||||
|
$config = join(PHP_EOL, $config);
|
||||||
|
$dhcp_cfg = $this->removeIface($dhcp_cfg,'br0');
|
||||||
|
$dhcp_cfg = $this->removeIface($dhcp_cfg,'uap0');
|
||||||
|
if (!strpos($dhcp_cfg, 'metric')) {
|
||||||
|
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$ap_iface.'\s.*?(?=(?:\s*^\s*$|\s*nogateway))/ms', $config, $dhcp_cfg, 1);
|
||||||
|
} else {
|
||||||
|
$metrics = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($repeaterEnable && $metrics) {
|
||||||
|
$status->addMessage(_('WiFi repeater mode: A metric value is already defined for DHCP.'), 'warning');
|
||||||
|
} else if ($repeaterEnable && !$metrics) {
|
||||||
|
$status->addMessage(sprintf(_('Metric value configured for the %s interface.'), $ap_iface), 'success');
|
||||||
|
$status->addMessage('Restart hotspot to enable WiFi repeater mode.', 'success');
|
||||||
|
$this->saveConfig($dhcp_cfg, $ap_iface, $status);
|
||||||
|
} elseif (!$skip_dhcp) {
|
||||||
|
$this->saveConfig($dhcp_cfg, $ap_iface, $status);
|
||||||
|
} else {
|
||||||
|
$status->addMessage('WiFi hotspot settings saved.', 'success');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Re)builds an existing dhcp configuration
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @param array $post_data
|
||||||
|
* @return string $dhcp_cfg
|
||||||
|
*/
|
||||||
|
public function buildConfigEx(string $iface, array $post_data, StatusMessage $status): string
|
||||||
|
{
|
||||||
|
$cfg[] = '# RaspAP '.$iface.' configuration';
|
||||||
|
$cfg[] = 'interface '.$iface;
|
||||||
|
if (isset($post_data['StaticIP']) && $post_data['StaticIP'] !== '') {
|
||||||
|
$mask = ($post_data['SubnetMask'] !== '' && $post_data['SubnetMask'] !== '0.0.0.0') ? '/'.mask2cidr($post_data['SubnetMask']) : null;
|
||||||
|
$cfg[] = 'static ip_address='.$post_data['StaticIP'].$mask;
|
||||||
|
}
|
||||||
|
if (isset($post_data['DefaultGateway']) && $post_data['DefaultGateway'] !== '') {
|
||||||
|
$cfg[] = 'static routers='.$post_data['DefaultGateway'];
|
||||||
|
}
|
||||||
|
if ($post_data['DNS1'] !== '' || $post_data['DNS2'] !== '') {
|
||||||
|
$cfg[] = 'static domain_name_server='.$post_data['DNS1'].' '.$post_data['DNS2'];
|
||||||
|
}
|
||||||
|
if ($post_data['Metric'] !== '') {
|
||||||
|
$cfg[] = 'metric '.$post_data['Metric'];
|
||||||
|
}
|
||||||
|
if (($post_data['Fallback'] ?? 0) == 1) {
|
||||||
|
$cfg[] = 'profile static_'.$iface;
|
||||||
|
$cfg[] = 'fallback static_'.$iface;
|
||||||
|
}
|
||||||
|
$cfg[] = ($post_data['DefaultRoute'] ?? '') == '1' ? 'gateway' : 'nogateway';
|
||||||
|
if (substr($iface, 0, 2) === "wl" && ($post_data['NoHookWPASupplicant'] ?? '') == '1') {
|
||||||
|
$cfg[] = 'nohook wpa_supplicant';
|
||||||
|
}
|
||||||
|
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
||||||
|
if (!preg_match('/^interface\s'.$iface.'$/m', $dhcp_cfg)) {
|
||||||
|
$cfg[] = PHP_EOL;
|
||||||
|
$cfg = join(PHP_EOL, $cfg);
|
||||||
|
$dhcp_cfg .= $cfg;
|
||||||
|
$status->addMessage('DHCP configuration for '.$iface.' added.', 'success');
|
||||||
|
} else {
|
||||||
|
$cfg = join(PHP_EOL, $cfg);
|
||||||
|
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)/ms', $cfg, $dhcp_cfg, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dhcp_cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates DHCP user input from $_POST data
|
||||||
|
*
|
||||||
|
* @param array $post_data
|
||||||
|
* @return array $errors
|
||||||
|
*/
|
||||||
|
public function validate(array $post_data): array
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
|
define('IFNAMSIZ', 16);
|
||||||
|
$iface = $post_data['interface'];
|
||||||
|
if (!preg_match('/^[^\s\/\\0]+$/', $iface)
|
||||||
|
|| strlen($iface) >= IFNAMSIZ
|
||||||
|
) {
|
||||||
|
$errors[] = _('Invalid interface name.');
|
||||||
|
}
|
||||||
|
if (!filter_var($post_data['StaticIP'], FILTER_VALIDATE_IP) && !empty($post_data['StaticIP'])) {
|
||||||
|
$errors[] = _('Invalid static IP address.');
|
||||||
|
}
|
||||||
|
if (!filter_var($post_data['SubnetMask'], FILTER_VALIDATE_IP) && !empty($post_data['SubnetMask'])) {
|
||||||
|
$errors[] = _('Invalid subnet mask.');
|
||||||
|
}
|
||||||
|
if (!filter_var($post_data['DefaultGateway'], FILTER_VALIDATE_IP) && !empty($post_data['DefaultGateway'])) {
|
||||||
|
$errors[] = _('Invalid default gateway.');
|
||||||
|
}
|
||||||
|
if (($post_data['dhcp-iface'] == "1")) {
|
||||||
|
if (!filter_var($post_data['RangeStart'], FILTER_VALIDATE_IP) && !empty($post_data['RangeStart'])) {
|
||||||
|
$errors[] = _('Invalid DHCP range start.');
|
||||||
|
}
|
||||||
|
if (!filter_var($post_data['RangeEnd'], FILTER_VALIDATE_IP) && !empty($post_data['RangeEnd'])) {
|
||||||
|
$errors[] = _('Invalid DHCP range end.');
|
||||||
|
}
|
||||||
|
if (!ctype_digit($post_data['RangeLeaseTime']) && $post_data['RangeLeaseTimeUnits'] !== 'i') {
|
||||||
|
$errors[] = _('Invalid DHCP lease time, not a number.');
|
||||||
|
}
|
||||||
|
if (!in_array($post_data['RangeLeaseTimeUnits'], array('m', 'h', 'd', 'i'))) {
|
||||||
|
$errors[] = _('Unknown DHCP lease time unit.');
|
||||||
|
}
|
||||||
|
if ($post_data['Metric'] !== '' && !ctype_digit($post_data['Metric'])) {
|
||||||
|
$errors[] = _('Invalid metric value, not a number.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a dhcpcd configuration
|
||||||
|
*
|
||||||
|
* @param string $config
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @return bool
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
public function saveConfig(string $config, string $iface, StatusMessage $status): bool
|
||||||
|
{
|
||||||
|
if (file_put_contents(self::CONF_TMP, $config) === false) {
|
||||||
|
throw new \RuntimeException("Failed to write temporary dhcpcd config");
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(sprintf('sudo cp %s %s', escapeshellarg(self::CONF_TMP), escapeshellarg(self::CONF_DEFAULT)), $o, $rc);
|
||||||
|
if ($rc !== 0) {
|
||||||
|
$status->addMessage('Unable to save DHCP configuration.', 'danger');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$status->addMessage(sprintf(_('DHCP configuration for %s updated.'), $iface), 'success');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a dhcp configuration block for the specified interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @return bool $result
|
||||||
|
*/
|
||||||
|
public function remove(string $iface, StatusMessage $status): bool
|
||||||
|
{
|
||||||
|
$configFile = SELF::CONF_DEFAULT;
|
||||||
|
$tempFile = SELF::CONF_TMP;
|
||||||
|
|
||||||
|
$dhcp_cfg = file_get_contents($configFile);
|
||||||
|
$modified_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)([\s]+)/ms', '', $dhcp_cfg, 1);
|
||||||
|
if ($modified_cfg !== $dhcp_cfg) {
|
||||||
|
file_put_contents($tempFile, $modified_cfg);
|
||||||
|
|
||||||
|
$cmd = sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile));
|
||||||
|
exec($cmd, $output, $result);
|
||||||
|
|
||||||
|
if ($result == 0) {
|
||||||
|
$status->addMessage('DHCP configuration for '.$iface.' removed', 'success');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$status->addMessage('Failed to remove DHCP configuration for '.$iface, 'danger');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a dhcp configuration block for the specified interface
|
||||||
|
*
|
||||||
|
* @param string $dhcp_cfg
|
||||||
|
* @param string $iface
|
||||||
|
* @return string $dhcp_cfg
|
||||||
|
*/
|
||||||
|
public function removeIface(string $dhcp_cfg, string $iface): string
|
||||||
|
{
|
||||||
|
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)([\s]+)/ms', '', $dhcp_cfg, 1);
|
||||||
|
return $dhcp_cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the dhcpcd configuration for a given interface, preserving existing settings
|
||||||
|
*
|
||||||
|
* @param string $ap_iface
|
||||||
|
* @param array $jsonData
|
||||||
|
* @param string $ip_address
|
||||||
|
* @param string $routers
|
||||||
|
* @param string $domain_name_server
|
||||||
|
* @return array updated configuration
|
||||||
|
*/
|
||||||
|
private function updateDhcpcdConfig(
|
||||||
|
string $ap_iface,
|
||||||
|
array $jsonData,
|
||||||
|
string $ip_address,
|
||||||
|
string $routers,
|
||||||
|
string $domain_name_server): array
|
||||||
|
{
|
||||||
|
$dhcp_cfg = file_get_contents(self::CONF_DEFAULT);
|
||||||
|
$existing_config = [];
|
||||||
|
$section_regex = '/^#\sRaspAP\s'.preg_quote($ap_iface, '/').'\s.*?(?=\s*^\s*$)/ms';
|
||||||
|
|
||||||
|
// extract existing interface configuration
|
||||||
|
if (preg_match($section_regex, $dhcp_cfg, $matches)) {
|
||||||
|
$lines = explode(PHP_EOL, $matches[0]);
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
if (preg_match('/^(interface|static|metric|nogateway|nohook)/', $line)) {
|
||||||
|
$existing_config[] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize with comment
|
||||||
|
$config = [ '# RaspAP '.$ap_iface.' configuration' ];
|
||||||
|
$config[] = 'interface '.$ap_iface;
|
||||||
|
$static_settings = [
|
||||||
|
'static ip_address' => $ip_address,
|
||||||
|
'static routers' => $routers,
|
||||||
|
'static domain_name_server' => $domain_name_server
|
||||||
|
];
|
||||||
|
|
||||||
|
// merge existing settings with updates
|
||||||
|
foreach ($existing_config as $line) {
|
||||||
|
$matched = false;
|
||||||
|
foreach ($static_settings as $key => $value) {
|
||||||
|
if (strpos($line, $key) === 0) {
|
||||||
|
$config[] = "$key=$value";
|
||||||
|
$matched = true;
|
||||||
|
unset($static_settings[$key]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$matched && !preg_match('/^interface/', $line)) {
|
||||||
|
$config[] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add any new static settings
|
||||||
|
foreach ($static_settings as $key => $value) {
|
||||||
|
$config[] = "$key=$value";
|
||||||
|
}
|
||||||
|
|
||||||
|
// add metric if provided
|
||||||
|
if (!empty($jsonData['Metric']) && !in_array('metric '.$jsonData['Metric'], $config)) {
|
||||||
|
$config[] = 'metric '.$jsonData['Metric'];
|
||||||
|
}
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the metric value for a given interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @return int $metric| bool false on failure
|
||||||
|
*/
|
||||||
|
public function getIfaceMetric($iface)
|
||||||
|
{
|
||||||
|
$metric = shell_exec("ip -o -4 route show dev ".$iface." | awk '/metric/ {print \$NF; exit}'");
|
||||||
|
if (isset($metric)) {
|
||||||
|
$metric = (int)$metric;
|
||||||
|
return $metric;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current dhcpcd info for an interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getInterfaceConfig(string $iface): array
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'DHCPEnabled' => false,
|
||||||
|
'RangeStart' => null,
|
||||||
|
'RangeEnd' => null,
|
||||||
|
'RangeMask' => null,
|
||||||
|
'leaseTime' => null,
|
||||||
|
'leaseTimeInterval' => null,
|
||||||
|
'dhcpHost' => [],
|
||||||
|
'upstreamServersEnabled' => false,
|
||||||
|
'upstreamServers' => [],
|
||||||
|
'DNS1' => null,
|
||||||
|
'DNS2' => null,
|
||||||
|
'Metric' => null,
|
||||||
|
'StaticIP' => null,
|
||||||
|
'SubnetMask' => null,
|
||||||
|
'StaticRouters' => null,
|
||||||
|
'StaticDNS' => null,
|
||||||
|
'FallbackEnabled' => false,
|
||||||
|
'DefaultRoute' => false,
|
||||||
|
'NoHookWPASupplicant' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
// dnsmasq
|
||||||
|
$dnsmasqFile = RASPI_DNSMASQ_PREFIX . $iface . '.conf';
|
||||||
|
if (file_exists($dnsmasqFile) && is_readable($dnsmasqFile)) {
|
||||||
|
$lines = [];
|
||||||
|
exec('cat ' . escapeshellarg($dnsmasqFile), $lines);
|
||||||
|
if (!function_exists('ParseConfig')) {
|
||||||
|
require_once 'includes/functions.php';
|
||||||
|
}
|
||||||
|
$conf = ParseConfig($lines);
|
||||||
|
|
||||||
|
if (!empty($conf)) {
|
||||||
|
$result['DHCPEnabled'] = true;
|
||||||
|
|
||||||
|
// dhcp-range may be multi-value
|
||||||
|
$rangeRaw = $conf['dhcp-range'] ?? null;
|
||||||
|
if (is_array($rangeRaw)) {
|
||||||
|
$rangeRaw = $rangeRaw[0] ?? null;
|
||||||
|
}
|
||||||
|
if (is_string($rangeRaw)) {
|
||||||
|
$rangeParts = explode(',', $rangeRaw);
|
||||||
|
$result['RangeStart'] = $rangeParts[0] ?? null;
|
||||||
|
$result['RangeEnd'] = $rangeParts[1] ?? null;
|
||||||
|
$result['RangeMask'] = $rangeParts[2] ?? null;
|
||||||
|
$leaseSpec = $rangeParts[3] ?? null;
|
||||||
|
if ($leaseSpec) {
|
||||||
|
if (preg_match('/^(\d+)([smhd])?$/i', $leaseSpec, $m)) {
|
||||||
|
$result['leaseTime'] = $m[1];
|
||||||
|
$result['leaseTimeInterval'] = $m[2] ?? 'h'; // default to hours if missing
|
||||||
|
} else {
|
||||||
|
$result['leaseTime'] = $leaseSpec;
|
||||||
|
$result['leaseTimeInterval'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dhcp-host entries (array or scalar)
|
||||||
|
$hosts = $conf['dhcp-host'] ?? [];
|
||||||
|
if (!is_array($hosts) && $hosts !== null) {
|
||||||
|
$hosts = [$hosts];
|
||||||
|
}
|
||||||
|
$result['dhcpHost'] = array_values(array_filter($hosts));
|
||||||
|
|
||||||
|
// upstream DNS servers (server= lines)
|
||||||
|
$servers = $conf['server'] ?? [];
|
||||||
|
if (!is_array($servers) && !empty($servers)) {
|
||||||
|
$servers = [$servers];
|
||||||
|
}
|
||||||
|
$servers = array_filter($servers);
|
||||||
|
if (!empty($servers)) {
|
||||||
|
$result['upstreamServersEnabled'] = true;
|
||||||
|
$result['upstreamServers'] = $servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dhcp-option=6,<dns1>[,<dns2>]
|
||||||
|
if (isset($conf['dhcp-option'])) {
|
||||||
|
$optsRaw = $conf['dhcp-option'];
|
||||||
|
// may be multiple dhcp-option lines; coalesce
|
||||||
|
$optLines = is_array($optsRaw) ? $optsRaw : [$optsRaw];
|
||||||
|
foreach ($optLines as $optLine) {
|
||||||
|
$parts = explode(',', $optLine);
|
||||||
|
if ($parts[0] === '6') {
|
||||||
|
$result['DNS1'] = $parts[1] ?? null;
|
||||||
|
$result['DNS2'] = $parts[2] ?? null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fallback to defaults
|
||||||
|
$rangeRaw = getDefaultNetValue('dnsmasq', $iface, 'dhcp-range');
|
||||||
|
if ($rangeRaw) {
|
||||||
|
$result['DHCPEnabled'] = true;
|
||||||
|
$rangeParts = explode(',', $rangeRaw);
|
||||||
|
$result['RangeStart'] = $rangeParts[0] ?? null;
|
||||||
|
$result['RangeEnd'] = $rangeParts[1] ?? null;
|
||||||
|
$result['RangeMask'] = $rangeParts[2] ?? null;
|
||||||
|
$leaseSpec = $rangeParts[3] ?? null;
|
||||||
|
if ($leaseSpec && preg_match('/^(\d+)([smhd])?$/i', $leaseSpec, $m)) {
|
||||||
|
$result['leaseTime'] = $m[1];
|
||||||
|
$result['leaseTimeInterval'] = $m[2] ?? 'h';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dhcpcd
|
||||||
|
if (file_exists(self::CONF_DEFAULT) && is_readable(self::CONF_DEFAULT)) {
|
||||||
|
$dhcpcd = file_get_contents(self::CONF_DEFAULT);
|
||||||
|
|
||||||
|
// match interface block starting with '# RaspAP <iface> configuration'
|
||||||
|
$sectionPattern = '/^#\sRaspAP\s' . preg_quote($iface, '/') . '\sconfiguration.*?(?=^(?:#\sRaspAP\s|\s*$))/ms';
|
||||||
|
if (preg_match($sectionPattern, $dhcpcd, $match)) {
|
||||||
|
$block = $match[0];
|
||||||
|
|
||||||
|
$result['Metric'] = $this->matchFirst('/\bmetric\s+(\d+)/i', $block);
|
||||||
|
$staticIPLine = $this->matchFirst('/static\s+ip_address=([^\r\n]+)/i', $block);
|
||||||
|
$staticRouters = $this->matchFirst('/static\s+routers=([^\r\n]+)/i', $block);
|
||||||
|
$staticDNS = $this->matchFirst('/static\s+domain_name_server=([^\r\n]+)/i', $block);
|
||||||
|
|
||||||
|
$result['StaticIP'] = $staticIPLine ? (strpos($staticIPLine,'/') !== false
|
||||||
|
? substr($staticIPLine, 0, strpos($staticIPLine,'/'))
|
||||||
|
: $staticIPLine) : null;
|
||||||
|
$result['SubnetMask'] = $staticIPLine && function_exists('cidr2mask') && strpos($staticIPLine,'/')
|
||||||
|
? cidr2mask($staticIPLine)
|
||||||
|
: ($result['SubnetMask'] ?? null);
|
||||||
|
$result['StaticRouters'] = $staticRouters;
|
||||||
|
$result['StaticDNS'] = $staticDNS;
|
||||||
|
|
||||||
|
$result['FallbackEnabled'] = (bool) preg_match('/fallback\s+static_' . preg_quote($iface, '/') . '/i', $block);
|
||||||
|
$result['DefaultRoute'] = (bool) preg_match('/\bgateway\b/', $block);
|
||||||
|
$result['NoHookWPASupplicant'] = (bool) preg_match('/nohook\s+wpa_supplicant/i', $block);
|
||||||
|
} else {
|
||||||
|
$result['StaticIP'] = getDefaultNetValue('dhcp', $iface, 'static ip_address');
|
||||||
|
$result['SubnetMask'] = getDefaultNetValue('dhcp', $iface, 'subnetmask');
|
||||||
|
$result['StaticRouters'] = getDefaultNetValue('dhcp', $iface, 'static routers');
|
||||||
|
$result['StaticDNS'] = getDefaultNetValue('dhcp', $iface, 'static domain_name_server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchFirst(string $pattern, string $subject): ?string
|
||||||
|
{
|
||||||
|
return preg_match($pattern, $subject, $m) ? trim($m[1]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
367
src/RaspAP/Networking/Hotspot/DnsmasqManager.php
Normal file
367
src/RaspAP/Networking/Hotspot/DnsmasqManager.php
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dnsmasq configuration manager for RaspAP
|
||||||
|
*
|
||||||
|
* @description Class methods to get, build and save dnsmasq configs
|
||||||
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
|
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RaspAP\Networking\Hotspot;
|
||||||
|
|
||||||
|
use RaspAP\Messages\StatusMessage;
|
||||||
|
|
||||||
|
class DnsmasqManager
|
||||||
|
{
|
||||||
|
private const CONF_DEFAULT = '/etc/dnsmasq.d/';
|
||||||
|
private const CONF_SUFFIX = '.conf';
|
||||||
|
private const CONF_TMP = '/tmp/dnsmasqdata';
|
||||||
|
private const CONF_RASPAP = '090_raspap';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves dnsmasq configuration for an interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @return array
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
public function getConfig(string $iface): array
|
||||||
|
{
|
||||||
|
$configFile = RASPI_DNSMASQ_PREFIX . "$iface.conf";
|
||||||
|
$lines = [];
|
||||||
|
|
||||||
|
if (!file_exists($configFile)) {
|
||||||
|
throw new \RuntimeException("dnsmasq config not found: $configFile");
|
||||||
|
}
|
||||||
|
if (!is_readable($configFile)) {
|
||||||
|
throw new \RuntimeException("Unable to read dnsmasq config: $configFile");
|
||||||
|
}
|
||||||
|
if (!function_exists('ParseConfig')) {
|
||||||
|
throw new \RuntimeException("Unable to execute ParseConfig()");
|
||||||
|
}
|
||||||
|
|
||||||
|
exec('cat ' . escapeshellarg($configFile), $lines, $status);
|
||||||
|
if ($status !== 0 || empty($lines)) {
|
||||||
|
throw new \RuntimeException("Failed to read dnsmasq config for $iface");
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = ParseConfig($lines);
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a dnsmasq configuration
|
||||||
|
* @param array $syscfg
|
||||||
|
* @param string $iface
|
||||||
|
* @param bool $wifiAPEnable
|
||||||
|
* @param bool $bridgedEnable
|
||||||
|
* @return array $config
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
public function buildConfig(?array $syscfg, string $iface, bool $wifiAPEnable, bool $bridgedEnable): array
|
||||||
|
{
|
||||||
|
// fallback: if no syscfg for interface seed with defaults
|
||||||
|
if ($syscfg === null) {
|
||||||
|
$syscfg = [];
|
||||||
|
$dhcp_range = getDefaultNetValue('dnsmasq', $iface, 'dhcp-range');
|
||||||
|
if ($dhcp_range !== false) {
|
||||||
|
$syscfg['dhcp-range'] = $dhcp_range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wifiAPEnable == 1) {
|
||||||
|
// Enable uap0 configuration for ap-sta mode
|
||||||
|
// Set dhcp-range from system config, fallback to default if undefined
|
||||||
|
$dhcp_range = ($syscfg['dhcp-range'] == '') ? getDefaultNetValue('dnsmasq','uap0','dhcp-range') : $syscfg['dhcp-range'];
|
||||||
|
$config = [ '# RaspAP uap0 configuration' ];
|
||||||
|
$config[] = 'interface=lo,uap0 # Enable uap0 interface for wireless client AP mode';
|
||||||
|
$config[] = 'bind-dynamic # Hybrid between --bind-interfaces and default';
|
||||||
|
$config[] = 'server=8.8.8.8 # Forward DNS requests to Google DNS';
|
||||||
|
$config[] = 'domain-needed # Don\'t forward short names';
|
||||||
|
$config[] = 'bogus-priv # Never forward addresses in the non-routed address spaces';
|
||||||
|
$config[] = 'dhcp-range='.$dhcp_range;
|
||||||
|
if (!empty($syscfg['dhcp-option'])) {
|
||||||
|
$config[] = 'dhcp-option='.$syscfg['dhcp-option'];
|
||||||
|
}
|
||||||
|
$config[] = PHP_EOL;
|
||||||
|
$this->scanConfigDir(SELF::CONF_DEFAULT,'uap0',$status);
|
||||||
|
} elseif ($bridgedEnable !==1) {
|
||||||
|
$dhcp_range = ($syscfg['dhcp-range'] =='') ? getDefaultNetValue('dnsmasq',$iface,'dhcp-range') : $syscfg['dhcp-range'];
|
||||||
|
$config = [ '# RaspAP '.$_POST['interface'].' configuration' ];
|
||||||
|
$config[] = 'interface='.$_POST['interface'];
|
||||||
|
$config[] = 'domain-needed';
|
||||||
|
$config[] = 'dhcp-range='.$dhcp_range;
|
||||||
|
|
||||||
|
// handle multiple dhcp-host + option entries
|
||||||
|
if (!empty($syscfg['dhcp-host'])) {
|
||||||
|
if (is_array($syscfg['dhcp-host'])) {
|
||||||
|
foreach ($syscfg['dhcp-host'] as $host) {
|
||||||
|
$config[] = 'dhcp-host=' . $host;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$config[] = 'dhcp-host=' . $syscfg['dhcp-host'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($syscfg['dhcp-option'])) {
|
||||||
|
$dhcpOptions = (array) $syscfg['dhcp-option'];
|
||||||
|
$grouped = [];
|
||||||
|
|
||||||
|
foreach ($dhcpOptions as $opt) {
|
||||||
|
$parts = explode(',', $opt, 2);
|
||||||
|
if (count($parts) < 2) {
|
||||||
|
continue; // skip malformed option
|
||||||
|
}
|
||||||
|
list($code, $value) = $parts;
|
||||||
|
$grouped[$code][] = $value;
|
||||||
|
}
|
||||||
|
foreach ($grouped as $code => $values) {
|
||||||
|
$config[] = 'dhcp-option=' . $code . ',' . implode(',', $values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$config[] = PHP_EOL;
|
||||||
|
}
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an extended dnsmasq configuration
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @param array $post_data
|
||||||
|
* @return array $config
|
||||||
|
*/
|
||||||
|
public function buildConfigEx(string $iface, array $post_data): array
|
||||||
|
{
|
||||||
|
$config[] = '# RaspAP '. $iface .' configuration';
|
||||||
|
$config[] = 'interface='. $iface;
|
||||||
|
$leaseTime = ($post_data['RangeLeaseTimeUnits'] !== 'i')
|
||||||
|
? $post_data['RangeLeaseTime'] . $post_data['RangeLeaseTimeUnits']
|
||||||
|
: 'infinite';
|
||||||
|
$config[] = 'dhcp-range=' . $post_data['RangeStart'] . ',' .
|
||||||
|
$post_data['RangeEnd'] . ',' .
|
||||||
|
$post_data['SubnetMask'] . ',' .
|
||||||
|
$leaseTime;
|
||||||
|
// Static leases
|
||||||
|
$staticLeases = array();
|
||||||
|
if (isset($post_data["static_leases"]["mac"])) {
|
||||||
|
for ($i=0; $i < count($post_data["static_leases"]["mac"]); $i++) {
|
||||||
|
$mac = trim($post_data["static_leases"]["mac"][$i]);
|
||||||
|
$ip = trim($post_data["static_leases"]["ip"][$i]);
|
||||||
|
$comment = trim($post_data["static_leases"]["comment"][$i]);
|
||||||
|
if ($mac != "" && $ip != "") {
|
||||||
|
$staticLeases[] = array('mac' => $mac, 'ip' => $ip, 'comment' => $comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort ascending by IPs
|
||||||
|
usort($staticLeases, [$this, 'compareIPs']);
|
||||||
|
// Update config
|
||||||
|
for ($i = 0; $i < count($staticLeases); $i++) {
|
||||||
|
$mac = $staticLeases[$i]['mac'];
|
||||||
|
$ip = $staticLeases[$i]['ip'];
|
||||||
|
$comment = $staticLeases[$i]['comment'];
|
||||||
|
$config[] = "dhcp-host=$mac,$ip # $comment";
|
||||||
|
}
|
||||||
|
if ($post_data['no-resolv'] == "1") {
|
||||||
|
$config[] = "no-resolv";
|
||||||
|
}
|
||||||
|
foreach (($post_data['server'] ?? []) as $server) {
|
||||||
|
$config[] = "server=$server";
|
||||||
|
}
|
||||||
|
if (!empty($post_data['DNS1'])) {
|
||||||
|
$dnsOption = "dhcp-option=6," . $post_data['DNS1'];
|
||||||
|
if (!empty($post_data['DNS2'])) {
|
||||||
|
$dnsOption .= ',' . $post_data['DNS2'];
|
||||||
|
}
|
||||||
|
$config[] = $dnsOption;
|
||||||
|
}
|
||||||
|
if ($post_data['dhcp-ignore'] == "1") {
|
||||||
|
$config[] = 'dhcp-ignore=tag:!known';
|
||||||
|
}
|
||||||
|
$config[]= PHP_EOL;
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a RaspAP default dnsmasq config
|
||||||
|
* Written to 090_raspap.conf
|
||||||
|
*
|
||||||
|
* @param array $post_data
|
||||||
|
* @return array $config
|
||||||
|
*/
|
||||||
|
public function buildDefault(array $post_data): array
|
||||||
|
{
|
||||||
|
// preamble
|
||||||
|
$config[] = '# RaspAP default config';
|
||||||
|
$config[] = 'log-facility='. RASPI_DHCPCD_LOG;
|
||||||
|
$config[] = 'conf-dir=/etc/dnsmasq.d';
|
||||||
|
|
||||||
|
// handle log option
|
||||||
|
if (($post_data['log-dhcp'] ?? '') == "1") {
|
||||||
|
$config[] = "log-dhcp";
|
||||||
|
}
|
||||||
|
if (($post_data['log-queries'] ?? '') == "1") {
|
||||||
|
$config[] = "log-queries";
|
||||||
|
}
|
||||||
|
$config[] = PHP_EOL;
|
||||||
|
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves dnsmasq configuration for an interface
|
||||||
|
*
|
||||||
|
* @param array $config
|
||||||
|
* @param string $iface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function saveConfig(array $config, string $iface): bool
|
||||||
|
{
|
||||||
|
$configFile = RASPI_DNSMASQ_PREFIX . $iface . SELF::CONF_SUFFIX;
|
||||||
|
$tempFile = SELF::CONF_TMP;
|
||||||
|
|
||||||
|
$config = join(PHP_EOL, $config);
|
||||||
|
file_put_contents($tempFile, $config);
|
||||||
|
$cmd = sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile));
|
||||||
|
exec($cmd, $output, $status);
|
||||||
|
if ($status !== 0) {
|
||||||
|
throw new \RuntimeException("Failed to copy temp config to $configFile");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload dnsmasq to apply changes
|
||||||
|
exec('sudo systemctl reload dnsmasq.service', $output, $status);
|
||||||
|
if ($status !== 0) {
|
||||||
|
throw new \RuntimeException("Failed to reload dnsmasq service");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves dnsmasq default configuration
|
||||||
|
*
|
||||||
|
* @param array $config
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function saveConfigDefault(array $config): bool
|
||||||
|
{
|
||||||
|
$configFile = SELF::CONF_DEFAULT . SELF::CONF_RASPAP . SELF::CONF_SUFFIX;
|
||||||
|
$tempFile = SELF::CONF_TMP;
|
||||||
|
|
||||||
|
$config = join(PHP_EOL, $config);
|
||||||
|
file_put_contents($tempFile, $config);
|
||||||
|
$cmd = sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile));
|
||||||
|
exec($cmd, $output, $status);
|
||||||
|
if ($status !== 0) {
|
||||||
|
throw new \RuntimeException("Failed to copy temp config to $configFile");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload dnsmasq to apply changes
|
||||||
|
exec('sudo systemctl reload dnsmasq.service', $output, $status);
|
||||||
|
if ($status !== 0) {
|
||||||
|
throw new \RuntimeException("Failed to reload dnsmasq service");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates dnsmasq user input from $_POST object
|
||||||
|
*
|
||||||
|
* @param array $post_data
|
||||||
|
* @return array $errors
|
||||||
|
*/
|
||||||
|
public function validate(array $post_data): array
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
|
$encounteredIPs = [];
|
||||||
|
|
||||||
|
if (isset($post_data["static_leases"]["mac"])) {
|
||||||
|
for ($i=0; $i < count($post_data["static_leases"]["mac"]); $i++) {
|
||||||
|
$mac = trim($post_data["static_leases"]["mac"][$i]);
|
||||||
|
$ip = trim($post_data["static_leases"]["ip"][$i]);
|
||||||
|
if (!validateMac($mac)) {
|
||||||
|
$errors[] = _('Invalid MAC address: '.$mac);
|
||||||
|
}
|
||||||
|
if (in_array($ip, $encounteredIPs)) {
|
||||||
|
$errors[] = _('Duplicate IP address entered: ' . $ip);
|
||||||
|
} else {
|
||||||
|
$encounteredIPs[] = $ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a configuration block for the specified interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @return bool $result
|
||||||
|
*/
|
||||||
|
public function remove(string $iface, StatusMessage $status): bool
|
||||||
|
{
|
||||||
|
system('sudo rm '.RASPI_DNSMASQ_PREFIX.$iface.'.conf', $result);
|
||||||
|
if ($result == 0) {
|
||||||
|
$status->addMessage('Dnsmasq configuration for '.$iface.' removed.', 'success');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$status->addMessage('Failed to remove dnsmasq configuration for '.$iface.'.', 'danger');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans configuration dir for the specified interface
|
||||||
|
* Non-matching configs are removed, optional adblock.conf is protected
|
||||||
|
*
|
||||||
|
* @param string $dir_conf
|
||||||
|
* @param string $interface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function scanConfigDir(string $dir_conf, string $interface): bool
|
||||||
|
{
|
||||||
|
$syscnf = preg_grep('~\.(conf)$~', scandir($dir_conf));
|
||||||
|
foreach ($syscnf as $cnf) {
|
||||||
|
if ($cnf !== '090_adblock.conf' && !preg_match('/.*_'.$interface.'.conf/', $cnf)) {
|
||||||
|
system('sudo rm /etc/dnsmasq.d/'.$cnf, $result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two IPs
|
||||||
|
*
|
||||||
|
* @param array $ip1
|
||||||
|
* @param array $ip2
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function compareIPs(array $ip1, array $ip2): int
|
||||||
|
{
|
||||||
|
$ipu1 = sprintf('%u', ip2long($ip1["ip"])) + 0;
|
||||||
|
$ipu2 = sprintf('%u', ip2long($ip2["ip"])) + 0;
|
||||||
|
return $ipu1 <=> $ipu2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add static DHCP lease
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @param string $mac
|
||||||
|
* @param string $ip
|
||||||
|
* @param string|null $comment
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function addStaticLease(string $iface, string $mac, string $ip, ?string $comment = null): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
461
src/RaspAP/Networking/Hotspot/HostapdManager.php
Normal file
461
src/RaspAP/Networking/Hotspot/HostapdManager.php
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hostapd manager class for RaspAP
|
||||||
|
*
|
||||||
|
* @description Manages hostapd configurations and runtime settings
|
||||||
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
|
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RaspAP\Networking\Hotspot;
|
||||||
|
|
||||||
|
use RaspAP\Networking\Hotspot\Validators\HostapdValidator;
|
||||||
|
use RaspAP\Messages\StatusMessage;
|
||||||
|
|
||||||
|
class HostapdManager
|
||||||
|
{
|
||||||
|
private const CONF_DEFAULT = RASPI_HOSTAPD_CONFIG;
|
||||||
|
private const CONF_PATH_PREFIX = '/etc/hostapd/hostapd-';
|
||||||
|
private const CONF_TMP = '/tmp/hostapddata';
|
||||||
|
|
||||||
|
/** @var HostapdValidator */
|
||||||
|
private $validator;
|
||||||
|
|
||||||
|
public function __construct(?HostapdValidator $validator = null)
|
||||||
|
{
|
||||||
|
$this->validator = $validator ?: new HostapdValidator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves current hostapd config
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
$configFile = SELF::CONF_DEFAULT;
|
||||||
|
|
||||||
|
if (!file_exists($configFile)) {
|
||||||
|
throw new \RuntimeException("hostapd config not found: $configFile");
|
||||||
|
}
|
||||||
|
if (!is_readable($configFile)) {
|
||||||
|
throw new \RuntimeException("Unable to read hostapd config: $configFile");
|
||||||
|
}
|
||||||
|
exec('cat ' . escapeshellarg($configFile), $hostapdconfig, $status);
|
||||||
|
if ($status !== 0 || empty($hostapdconfig)) {
|
||||||
|
throw new \RuntimeException("Failed to read hostapd config: $configFile");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($hostapdconfig as $hostapdconfigline) {
|
||||||
|
if (strlen($hostapdconfigline) === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($hostapdconfigline[0] != "#") {
|
||||||
|
$line = explode("=", $hostapdconfigline);
|
||||||
|
$config[$line[0]]=$line[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// assign beacon_int boolean if value is set
|
||||||
|
if (isset($config['beacon_int'])) {
|
||||||
|
$config['beacon_interval_bool'] = 1;
|
||||||
|
}
|
||||||
|
// assign disassoc_low_ack boolean if value is set
|
||||||
|
if (isset($config['disassoc_low_ack'])) {
|
||||||
|
$config['disassoc_low_ack_bool'] = 1;
|
||||||
|
}
|
||||||
|
// assign country_code from iw reg if not set in config
|
||||||
|
if (empty($config['country_code']) && isset($country_code[0])) {
|
||||||
|
$config['country_code'] = $country_code[0];
|
||||||
|
}
|
||||||
|
// map wpa_key_mgmt to security types
|
||||||
|
if ($config['wpa_key_mgmt'] == 'WPA-PSK WPA-PSK-SHA256 SAE') {
|
||||||
|
$config['wpa'] = 4;
|
||||||
|
} elseif ($config['wpa_key_mgmt'] == 'SAE') {
|
||||||
|
$config['wpa'] = 5;
|
||||||
|
}
|
||||||
|
$config['selected_hw_mode'] = $this->resolveHwMode($config);
|
||||||
|
$config['ignore_broadcast_ssid'] ??= 0;
|
||||||
|
$config['max_num_sta'] ??= 0;
|
||||||
|
$config['wep_default_key'] ??= 0;
|
||||||
|
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the selected hardware mode based on config
|
||||||
|
*
|
||||||
|
* @param array $config
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function resolveHwMode(array $config): string
|
||||||
|
{
|
||||||
|
$selected = $config['hw_mode'] ?? 'g'; // default fallback
|
||||||
|
|
||||||
|
if (!empty($config['ieee80211n']) && strval($config['ieee80211n']) === '1') {
|
||||||
|
$selected = 'n';
|
||||||
|
}
|
||||||
|
if (!empty($config['ieee80211ac']) && strval($config['ieee80211ac']) === '1') {
|
||||||
|
$selected = 'ac';
|
||||||
|
}
|
||||||
|
if (!empty($config['ieee80211w']) && strval($config['ieee80211w']) === '2') {
|
||||||
|
$selected = 'w';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a hostapd configuration
|
||||||
|
*
|
||||||
|
* @param array $post raw $_POST object
|
||||||
|
* @param array $wpaArray allowed WPA values
|
||||||
|
* @param array $encTypes allowed encryption types
|
||||||
|
* @param array $modes allowed hardware modes
|
||||||
|
* @param array $interfaces valid interface list
|
||||||
|
* @param string $regDomain regulatory domain
|
||||||
|
* @param StatusMessage $status Status message collector
|
||||||
|
* @return array|false validated configuration array or false on failure
|
||||||
|
*/
|
||||||
|
public function validate(
|
||||||
|
array $post,
|
||||||
|
array $wpaArray,
|
||||||
|
array $encTypes,
|
||||||
|
array $modes,
|
||||||
|
array $interfaces,
|
||||||
|
string $regDomain,
|
||||||
|
StatusMessage $status
|
||||||
|
) {
|
||||||
|
return $this->validator->validate($post, $wpaArray, $encTypes, $modes, $interfaces, $regDomain, $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds hostapd configuration text from array
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function buildConfig(array $params, StatusMessage $status): string
|
||||||
|
{
|
||||||
|
$config = [];
|
||||||
|
|
||||||
|
// core static values
|
||||||
|
$config[] = 'driver=nl80211';
|
||||||
|
$config[] = 'ctrl_interface=' . RASPI_HOSTAPD_CTRL_INTERFACE;
|
||||||
|
$config[] = 'ctrl_interface_group=0';
|
||||||
|
$config[] = 'auth_algs=1';
|
||||||
|
|
||||||
|
$wpa = $params['wpa'];
|
||||||
|
$wpa_key_mgmt = 'WPA-PSK';
|
||||||
|
|
||||||
|
if ($wpa == 4) {
|
||||||
|
$config[] = 'ieee80211w=1';
|
||||||
|
$wpa_key_mgmt = 'WPA-PSK WPA-PSK-SHA256 SAE';
|
||||||
|
$wpa = 2;
|
||||||
|
} elseif ($wpa == 5) {
|
||||||
|
$config[] = 'ieee80211w=2';
|
||||||
|
$wpa_key_mgmt = 'SAE';
|
||||||
|
$wpa = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params['80211w'] == 1) {
|
||||||
|
$config[] = 'ieee80211w=1';
|
||||||
|
$wpa_key_mgmt = 'WPA-PSK';
|
||||||
|
} elseif ($params['80211w'] == 2) {
|
||||||
|
$config[] = 'ieee80211w=2';
|
||||||
|
$wpa_key_mgmt = 'WPA-PSK-SHA256';
|
||||||
|
}
|
||||||
|
|
||||||
|
$config[] = 'wpa_key_mgmt=' . $wpa_key_mgmt;
|
||||||
|
|
||||||
|
if (!empty($params['beacon_interval'])) {
|
||||||
|
$config[] = 'beacon_int=' . intval($params['beacon_interval']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($params['disassoc_low_ack'])) {
|
||||||
|
$config[] = 'disassoc_low_ack=0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSID and channel (required)
|
||||||
|
$config[] = 'ssid=' . $params['ssid'];
|
||||||
|
$config[] = 'channel=' . $params['channel'];
|
||||||
|
|
||||||
|
// choose VHT segment index (fallback only if required)
|
||||||
|
$vht_freq_idx = ($params['channel'] < RASPI_5GHZ_CHANNEL_MIN) ? 42 : 155;
|
||||||
|
$hwMode = isset($params['hw_mode']) ? $params['hw_mode'] : '';
|
||||||
|
|
||||||
|
// fetch settings for selected mode
|
||||||
|
$modeSettings = getDefaultNetOpts('hostapd', 'modes', $hwMode);
|
||||||
|
$settings = $modeSettings[$hwMode]['settings'] ?? [];
|
||||||
|
|
||||||
|
if (!empty($settings)) {
|
||||||
|
foreach ($settings as $line) {
|
||||||
|
if (!is_string($line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$replaced = str_replace('{VHT_FREQ_IDX}', (string) $vht_freq_idx ?? '',$line);
|
||||||
|
$config[] = $replaced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WPA passphrase
|
||||||
|
if ($wpa_numeric !== 'none' && !empty($params['wpa_passphrase'])) {
|
||||||
|
$config[] = 'wpa_passphrase=' . $params['wpa_passphrase'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// bridge handling
|
||||||
|
if (!empty($params['bridge'])) {
|
||||||
|
$config[] = 'interface=' . $params['interface'];
|
||||||
|
$config[] = 'bridge=' . $params['bridge'];
|
||||||
|
} else {
|
||||||
|
$config[] = 'interface=' . $params['interface'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$config[] = 'wpa=' . $wpa;
|
||||||
|
$config[] = 'wpa_pairwise=' . ($params['wpa_pairwise'] ?? '');
|
||||||
|
$config[] = 'country_code=' . ($params['country_code'] ?? '');
|
||||||
|
$config[] = 'ignore_broadcast_ssid=' . ($params['hiddenSSID'] ?? 0);
|
||||||
|
|
||||||
|
if (!empty($params['max_num_sta'])) {
|
||||||
|
$config[] = 'max_num_sta=' . (int)$params['max_num_sta'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional additional user config
|
||||||
|
$config[] = $this->parseUserHostapdCfg();
|
||||||
|
|
||||||
|
return implode(PHP_EOL, array_filter($config, function ($v) { return $v !== null && $v !== ''; })) . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a hostapd configuration
|
||||||
|
*
|
||||||
|
* @param string $config, rendered hostapd.conf
|
||||||
|
* @param string $interface, named interface
|
||||||
|
* @param bool $dualMode, dual-band AP mode enabled
|
||||||
|
* @param bool $restart, option to restart hostapd@<iface> after save
|
||||||
|
* @return bool
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
public function saveConfig(string $config, bool $dualMode, string $iface, bool $restart = false): bool
|
||||||
|
{
|
||||||
|
$configFile = $this->resolveConfigPath($iface, $dualMode);
|
||||||
|
$tempFile = SELF::CONF_TMP;
|
||||||
|
|
||||||
|
|
||||||
|
if (file_put_contents($tempFile, $config) === false) {
|
||||||
|
throw new \RuntimeException("Failed to write temp hostapd config");
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile)), $o, $status);
|
||||||
|
if ($status !== 0) {
|
||||||
|
throw new \RuntimeException("Failed to apply new hostapd config");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($restart) {
|
||||||
|
$this->restartService($iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives mode checkbox states from POST + existing ini
|
||||||
|
*
|
||||||
|
* @param array $post raw $_POST
|
||||||
|
* @param array $currentIni parsed hostapd.ini
|
||||||
|
* @return array normalized states
|
||||||
|
*/
|
||||||
|
public function deriveModeStates(array $post, array $currentIni): array
|
||||||
|
{
|
||||||
|
$prevWifiAPEnable = (int)($currentIni['WifiAPEnable'] ?? 0);
|
||||||
|
$bridgedEnable = isset($post['bridgedEnable']) ? 1 : 0;
|
||||||
|
$repeaterEnable = 0;
|
||||||
|
$wifiAPEnable = 0;
|
||||||
|
|
||||||
|
if ($bridgedEnable === 0) {
|
||||||
|
// only meaningful when not bridged
|
||||||
|
$repeaterEnable = isset($post['repeaterEnable']) ? 1 : 0;
|
||||||
|
$wifiAPEnable = isset($post['wifiAPEnable']) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$logEnable = isset($post['logEnable']) ? 1 : 0;
|
||||||
|
$effectiveWifiAPEnable = $bridgedEnable === 1 ? $prevWifiAPEnable : $wifiAPEnable;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'BridgedEnable' => $bridgedEnable,
|
||||||
|
'RepeaterEnable' => $repeaterEnable,
|
||||||
|
'WifiAPEnable' => $effectiveWifiAPEnable,
|
||||||
|
'LogEnable' => $logEnable
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine AP interface, client (managed) interface and session/monitor interface
|
||||||
|
* Uses these semantics:
|
||||||
|
* - Base interface = user selection (validated) or RASPI_WIFI_AP_INTERFACE
|
||||||
|
* - AP-STA mode (WifiAPEnable=1): AP is 'uap0', client is base iface
|
||||||
|
* - Bridged mode: client/session use 'br0', AP remains base iface
|
||||||
|
*
|
||||||
|
* @param string $baseIface Selected interface from form
|
||||||
|
* @param array $states Output from deriveModeStates()
|
||||||
|
* @return array [ap_iface, cli_iface, session_iface]
|
||||||
|
*/
|
||||||
|
public function deriveInterfaces(string $baseIface, array $states): array
|
||||||
|
{
|
||||||
|
$apIface = $baseIface;
|
||||||
|
$cliIface = $baseIface;
|
||||||
|
$sessionIface = $baseIface;
|
||||||
|
|
||||||
|
if ($states['WifiAPEnable'] === 1 && $states['BridgedEnable'] === 0) {
|
||||||
|
// client AP (AP-STA) – uap0 is AP, base iface remains client
|
||||||
|
$apIface = 'uap0';
|
||||||
|
$sessionIface = 'uap0';
|
||||||
|
$cliIface = $baseIface;
|
||||||
|
} elseif ($states['BridgedEnable'] === 1) {
|
||||||
|
// bridged mode – monitor br0, AP stays as base wireless iface
|
||||||
|
$cliIface = 'br0';
|
||||||
|
$sessionIface = 'br0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$apIface, $cliIface, $sessionIface];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables hostapd logging
|
||||||
|
*
|
||||||
|
* @param int $logEnable
|
||||||
|
*/
|
||||||
|
private function handleLogState(int $logEnable): void
|
||||||
|
{
|
||||||
|
$script = $logEnable === 1 ? 'enablelog.sh' : 'disablelog.sh';
|
||||||
|
exec('sudo ' . RASPI_CONFIG . '/hostapd/' . $script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses optional /etc/hostapd/hostapd.conf.users file
|
||||||
|
*
|
||||||
|
* @return string $tmp
|
||||||
|
*/
|
||||||
|
private function parseUserHostapdCfg()
|
||||||
|
{
|
||||||
|
if (file_exists(SELF::CONF_DEFAULT . '.users')) {
|
||||||
|
exec('cat '. SELF::CONF_DEFAULT . '.users', $hostapdconfigusers);
|
||||||
|
foreach ($hostapdconfigusers as $hostapdconfigusersline) {
|
||||||
|
if (strlen($hostapdconfigusersline) === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($hostapdconfigusersline[0] != "#") {
|
||||||
|
$arrLine = explode("=", $hostapdconfigusersline);
|
||||||
|
$tmp.= $arrLine[0]."=".$arrLine[1].PHP_EOL;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the hostapd config file for a given interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @param bool $dualMode
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function resolveConfigPath(string $iface, bool $dualMode): string
|
||||||
|
{
|
||||||
|
if ($dualMode) {
|
||||||
|
return SELF::CONF_PATH_PREFIX . $iface . '.conf';
|
||||||
|
}
|
||||||
|
// primary interface uses the canonical config path
|
||||||
|
return self::CONF_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restarts hostapd systemd instance
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
private function restartService(string $iface): void
|
||||||
|
{
|
||||||
|
// sanitize
|
||||||
|
if (!preg_match('/^[A-Za-z0-9_-]+$/', $iface)) {
|
||||||
|
throw new \RuntimeException("Invalid interface name: $iface");
|
||||||
|
}
|
||||||
|
|
||||||
|
// use instance unit (preferred) if available
|
||||||
|
$cmds = [
|
||||||
|
sprintf('sudo systemctl restart hostapd@%s', $iface),
|
||||||
|
// fallback to singleton service
|
||||||
|
'sudo systemctl restart hostapd.service'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($cmds as $cmd) {
|
||||||
|
exec($cmd, $out, $rc);
|
||||||
|
if ($rc === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException("Failed to restart hostapd (tried instance + fallback).");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist hostapd.ini with mode / interface user settings
|
||||||
|
*
|
||||||
|
* @param array $states states from deriveModeStates()
|
||||||
|
* @param string $apIface the AP interface
|
||||||
|
* @param string $cliIface the managed interface
|
||||||
|
* @param array $previousIni existing ini
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function persistHostapdIni(array $states, string $apIface, string $cliIface, array $previousIni = []): bool
|
||||||
|
{
|
||||||
|
$this->applyLogState($states['LogEnable']);
|
||||||
|
|
||||||
|
// compose new ini payload
|
||||||
|
$cfg = [
|
||||||
|
'WifiInterface' => $apIface,
|
||||||
|
'LogEnable' => $states['LogEnable'] ?? false,
|
||||||
|
'WifiAPEnable' => $states['WifiAPEnable'] ?? false,
|
||||||
|
'BridgedEnable' => $states['BridgedEnable'] ?? false,
|
||||||
|
'RepeaterEnable' => $states['RepeaterEnable'] ?? false,
|
||||||
|
'DualAPEnable' => $states['DualAPEnable'] ?? false,
|
||||||
|
'WifiManaged' => $cliIface
|
||||||
|
];
|
||||||
|
foreach ($previousIni as $k => $v) {
|
||||||
|
if (!array_key_exists($k, $cfg)) {
|
||||||
|
$cfg[$k] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return write_php_ini($cfg, RASPI_CONFIG . '/hostapd.ini');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables hostapd logging
|
||||||
|
*
|
||||||
|
* @param int $logEnable 1 = enable, 0 = disable
|
||||||
|
*/
|
||||||
|
private function applyLogState(int $logEnable): void
|
||||||
|
{
|
||||||
|
$script = $logEnable === 1 ? 'enablelog.sh' : 'disablelog.sh';
|
||||||
|
exec('sudo ' . RASPI_CONFIG . '/hostapd/' . $script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a count of hostapd-<interface>.conf files
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function countHostapdConfigs(): int
|
||||||
|
{
|
||||||
|
$configs = glob('/etc/hostapd/hostapd-*.conf');
|
||||||
|
return is_array($configs) ? count($configs) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
365
src/RaspAP/Networking/Hotspot/HotspotService.php
Normal file
365
src/RaspAP/Networking/Hotspot/HotspotService.php
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to manage hotspot service configurations
|
||||||
|
*
|
||||||
|
* Consolidates:
|
||||||
|
* hostapd, dnsmasq and dhcpcd config updates
|
||||||
|
* dhcpcd interface adjustments
|
||||||
|
* service control (start/stop/restart)
|
||||||
|
*
|
||||||
|
* @description Manages wireless configurations and services
|
||||||
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
|
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RaspAP\Networking\Hotspot;
|
||||||
|
|
||||||
|
use RaspAP\Networking\Hotspot\Validators\HostapdValidator;
|
||||||
|
use RaspAP\Messages\StatusMessage;
|
||||||
|
|
||||||
|
class HotspotService
|
||||||
|
{
|
||||||
|
protected HostapdManager $hostapd;
|
||||||
|
protected DnsmasqManager $dnsmasq;
|
||||||
|
protected DhcpcdManager $dhcpcd;
|
||||||
|
|
||||||
|
// IEEE 802.11 standards
|
||||||
|
private const IEEE_80211_STANDARD = [
|
||||||
|
'a' => '802.11a - 5 GHz',
|
||||||
|
'b' => '802.11b - 2.4 GHz',
|
||||||
|
'g' => '802.11g - 2.4 GHz',
|
||||||
|
'n' => '802.11n - 2.4/5 GHz',
|
||||||
|
'ac' => '802.11ac - 5 GHz'
|
||||||
|
];
|
||||||
|
|
||||||
|
// encryption types
|
||||||
|
private const ENC_TYPES = [
|
||||||
|
'TKIP' => 'TKIP',
|
||||||
|
'CCMP' => 'CCMP',
|
||||||
|
'TKIP CCMP' => 'TKIP+CCMP'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->hostapd = new HostapdManager();
|
||||||
|
$this->dnsmasq = new DnsmasqManager();
|
||||||
|
$this->dhcpcd = new DhcpcdManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns IEEE 802.11 standards
|
||||||
|
*/
|
||||||
|
public static function get80211Standards(): array
|
||||||
|
{
|
||||||
|
return self::IEEE_80211_STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns encryption types
|
||||||
|
*/
|
||||||
|
public static function getEncTypes(): array
|
||||||
|
{
|
||||||
|
return self::ENC_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns translated security modes.
|
||||||
|
*/
|
||||||
|
public static function getSecurityModes(): array
|
||||||
|
{
|
||||||
|
// Build each call to ensure translation occurs under current locale.
|
||||||
|
return [
|
||||||
|
1 => 'WPA',
|
||||||
|
2 => 'WPA2',
|
||||||
|
3 => _('WPA and WPA2'),
|
||||||
|
4 => _('WPA2 and WPA3-Personal (transitional mode)'),
|
||||||
|
5 => 'WPA3-Personal (required)',
|
||||||
|
'none' => _('None'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns translated 802.11w options
|
||||||
|
*/
|
||||||
|
public static function get80211wOptions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
3 => _('Disabled'),
|
||||||
|
1 => _('Enabled (for supported clients)'),
|
||||||
|
2 => _('Required (for supported clients)'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates user input + saves configs for hostapd, dnsmasq & dhcp
|
||||||
|
*
|
||||||
|
* @param array $wpa_array
|
||||||
|
* @param array $enc_types
|
||||||
|
* @param array $modes
|
||||||
|
* @param array $interfaces
|
||||||
|
* @param string $reg_domain
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function saveSettings(
|
||||||
|
array $post_data,
|
||||||
|
array $wpa_array,
|
||||||
|
array $enc_types,
|
||||||
|
array $modes,
|
||||||
|
array $interfaces,
|
||||||
|
string $reg_domain,
|
||||||
|
StatusMessage $status): bool
|
||||||
|
{
|
||||||
|
$arrHostapdConf = $this->getHostapdIni();
|
||||||
|
$dualAPEnable = false;
|
||||||
|
|
||||||
|
// derive mode states
|
||||||
|
$states = $this->hostapd->deriveModeStates($post_data, $arrHostapdConf);
|
||||||
|
|
||||||
|
// determine base interface (validated or fallback)
|
||||||
|
$baseIface = validateInterface($post_data['interface']) ? $post_data['interface'] : RASPI_WIFI_AP_INTERFACE;
|
||||||
|
|
||||||
|
// derive interface roles
|
||||||
|
[$apIface, $cliIface, $sessionIface] = $this->hostapd->deriveInterfaces($baseIface, $states);
|
||||||
|
|
||||||
|
// persist hostapd.ini
|
||||||
|
$this->hostapd->persistHostapdIni($states, $apIface, $cliIface, $arrHostapdConf);
|
||||||
|
|
||||||
|
// store session (compatibility)
|
||||||
|
$_SESSION['ap_interface'] = $sessionIface;
|
||||||
|
|
||||||
|
// validate config from post data
|
||||||
|
$validated = $this->hostapd->validate($post_data, $wpa_array, $enc_types, $modes, $interfaces, $reg_domain, $status);
|
||||||
|
|
||||||
|
if ($validated !== false) {
|
||||||
|
try {
|
||||||
|
// normalize state flags
|
||||||
|
$validated['interface'] = $apIface;
|
||||||
|
$validated['bridge'] = !empty($states['BridgedEnable']);
|
||||||
|
$validated['apsta'] = !empty($states['WifiAPEnable']);
|
||||||
|
$validated['repeater'] = !empty($states['RepeaterEnable']);
|
||||||
|
$validated['dualmode'] = !empty($states['DualAPEnable']);
|
||||||
|
$validated['txpower'] = $post_data['txpower'];
|
||||||
|
|
||||||
|
// hostapd
|
||||||
|
$config = $this->hostapd->buildConfig($validated, $status);
|
||||||
|
$this->hostapd->saveConfig($config, $dualAPEnable, $validated['interface']);
|
||||||
|
$this->maybeSetRegDomain($post_data['country_code'], $status);
|
||||||
|
|
||||||
|
$status->addMessage('WiFi hotspot settings saved.', 'success');
|
||||||
|
|
||||||
|
// dnsmasq
|
||||||
|
try {
|
||||||
|
$syscfg = $this->dnsmasq->getConfig($validated['interface'] ?? RASPI_WIFI_AP_INTERFACE);
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
error_log('Error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dnsmasqConfig = $this->dnsmasq->buildConfig(
|
||||||
|
$syscfg,
|
||||||
|
$validated['interface'],
|
||||||
|
$validated['apsta'],
|
||||||
|
$validated['bridge']
|
||||||
|
);
|
||||||
|
$this->dnsmasq->saveConfig($dnsmasqConfig, $validated['interface'], $status);
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
error_log('Error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// dhcpcd
|
||||||
|
try {
|
||||||
|
$return = $this->dhcpcd->buildConfig(
|
||||||
|
$validated['interface'],
|
||||||
|
$validated['bridge'],
|
||||||
|
$validated['repeater'],
|
||||||
|
$validated['apsta'],
|
||||||
|
$validated['dualmode'],
|
||||||
|
$status,
|
||||||
|
);
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
error_log('Error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
error_log(sprintf(
|
||||||
|
"Error: %s in %s on line %d\nStack trace:\n%s",
|
||||||
|
$e->getMessage(),
|
||||||
|
$e->getFile(),
|
||||||
|
$e->getLine(),
|
||||||
|
$e->getTraceAsString()
|
||||||
|
));
|
||||||
|
$status->addMessage('Unable to save WiFi hotspot settings', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets system hostapd.ini
|
||||||
|
*
|
||||||
|
* @return array $config
|
||||||
|
*/
|
||||||
|
public function getHostapdIni(): array
|
||||||
|
{
|
||||||
|
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||||
|
if (file_exists($hostapdIni)) {
|
||||||
|
return parse_ini_file($hostapdIni) ?: [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets transmit power for an interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @param int|string $dbm
|
||||||
|
* @param StatusMessage $status
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function maybeSetTxPower(string $iface, $dbm, StatusMessage $status): bool
|
||||||
|
{
|
||||||
|
$currentTxPower = $this->getTxPower($iface);
|
||||||
|
|
||||||
|
if ($currentTxPower === $dbm) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dbm === 'auto') {
|
||||||
|
exec('sudo /sbin/iw dev ' . escapeshellarg($iface) . ' set txpower auto', $return);
|
||||||
|
$status->addMessage('Setting transmit power to auto.', 'success');
|
||||||
|
} else {
|
||||||
|
$sdBm = (int)$dbm * 100;
|
||||||
|
exec('sudo /sbin/iw dev ' . escapeshellarg($iface) . ' set txpower fixed ' . $sdBm, $return);
|
||||||
|
$status->addMessage('Setting transmit power to ' . $dbm . ' dBm.', 'success');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets transmit power for an interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getTxPower(string $iface): int
|
||||||
|
{
|
||||||
|
$cmd = "iw dev ".escapeshellarg($iface)." info | awk '$1==\"txpower\" {print $2}'";
|
||||||
|
exec($cmd, $txpower);
|
||||||
|
return intval($txpower[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new regulatory domain if value has changed
|
||||||
|
*
|
||||||
|
* @param string $countryCode
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function maybeSetRegDomain($countryCode, StatusMessage $status): bool
|
||||||
|
{
|
||||||
|
$currentDomain = $this->getRegDomain();
|
||||||
|
if (trim($countryCode) !== trim($currentDomain)) {
|
||||||
|
$result = $this->setRegDomain($countryCode, $status);
|
||||||
|
if ($result !== true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current regulatory domain
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRegDomain(): string
|
||||||
|
{
|
||||||
|
$domain = shell_exec("iw reg get | grep -o 'country [A-Z]\{2\}' | awk 'NR==1{print $2}'");
|
||||||
|
return $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the specified wireless regulatory domain
|
||||||
|
*
|
||||||
|
* @param string $country_code ISO 2-letter country code
|
||||||
|
* @param object $status StatusMessage object
|
||||||
|
* @return boolean $result
|
||||||
|
*/
|
||||||
|
public function setRegDomain(string $country_code, StatusMessage $status): bool
|
||||||
|
{
|
||||||
|
$country_code = escapeshellarg($country_code);
|
||||||
|
exec("sudo iw reg set $country_code", $output, $result);
|
||||||
|
if ($result !== 0) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerates available network interfaces
|
||||||
|
*
|
||||||
|
* @return array $interfaces
|
||||||
|
*/
|
||||||
|
public function getInterfaces(): array
|
||||||
|
{
|
||||||
|
exec("ip -o link show | awk -F': ' '{print $2}'", $interfaces);
|
||||||
|
|
||||||
|
// filter out loopback, docker, bridges + other virtual interfaces
|
||||||
|
// that are incapable of hosting an AP
|
||||||
|
$interfaces = array_filter($interfaces, function ($iface) {
|
||||||
|
return !preg_match('/^(lo|docker|br-|veth|tun|tap|tailscale)/', $iface);
|
||||||
|
});
|
||||||
|
sort($interfaces);
|
||||||
|
|
||||||
|
return array_values($interfaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts services for given interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function start(string $iface): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops hotspot services
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function stop(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restart hotspot services for given interface
|
||||||
|
*
|
||||||
|
* @param string $iface
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function restart(string $iface): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current hotspot status
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getStatus(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
144
src/RaspAP/Networking/Hotspot/Validators/HostapdValidator.php
Normal file
144
src/RaspAP/Networking/Hotspot/Validators/HostapdValidator.php
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hostapd validator class for RaspAP
|
||||||
|
*
|
||||||
|
* @description Validates hostapd configuration input
|
||||||
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
|
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RaspAP\Networking\Hotspot\Validators;
|
||||||
|
|
||||||
|
use RaspAP\Messages\StatusMessage;
|
||||||
|
|
||||||
|
class HostapdValidator
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates full hostapd parameter set
|
||||||
|
*
|
||||||
|
* @param array $post raw $_POST object
|
||||||
|
* @param array $wpaArray allowed WPA values
|
||||||
|
* @param array $encTypes allowed encryption types
|
||||||
|
* @param array $modes allowed hardware modes
|
||||||
|
* @param array $interfaces valid interface list
|
||||||
|
* @param string $regDomain regulatory domain
|
||||||
|
* @param StatusMessage $status Status message collector
|
||||||
|
* @return array|false validated configuration array or false on failure
|
||||||
|
*/
|
||||||
|
public function validate(
|
||||||
|
array $post,
|
||||||
|
array $wpaArray,
|
||||||
|
array $encTypes,
|
||||||
|
array $modes,
|
||||||
|
array $interfaces,
|
||||||
|
string $regDomain,
|
||||||
|
?StatusMessage $status = null
|
||||||
|
) {
|
||||||
|
$goodInput = true;
|
||||||
|
|
||||||
|
// check WPA and encryption
|
||||||
|
if (
|
||||||
|
!array_key_exists($post['wpa'], $wpaArray) ||
|
||||||
|
!array_key_exists($post['wpa_pairwise'], $encTypes) ||
|
||||||
|
!array_key_exists($post['hw_mode'], $modes)
|
||||||
|
) {
|
||||||
|
$err = "Invalid WPA or encryption settings: "
|
||||||
|
. "wpa='{$post['wpa']}', "
|
||||||
|
. "wpa_pairwise='{$post['wpa_pairwise']}', "
|
||||||
|
. "hw_mode='{$post['hw_mode']}'";
|
||||||
|
error_log($err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate channel
|
||||||
|
if (!filter_var($post['channel'], FILTER_VALIDATE_INT)) {
|
||||||
|
$status->addMessage('Attempting to set channel to invalid number.', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
if ((int)$post['channel'] < 1 || (int)$post['channel'] > RASPI_5GHZ_CHANNEL_MAX) {
|
||||||
|
$status->addMessage('Attempting to set channel outside of permitted range', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate SSID
|
||||||
|
if (empty($post['ssid']) || strlen($post['ssid']) > 32) {
|
||||||
|
$status->addMessage('SSID must be between 1 and 32 characters', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate WPA passphrase
|
||||||
|
if ($post['wpa'] !== 'none') {
|
||||||
|
if (strlen($post['wpa_passphrase']) < 8 || strlen($post['wpa_passphrase']) > 63) {
|
||||||
|
$status->addMessage('WPA passphrase must be between 8 and 63 characters', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
} elseif (!ctype_print($post['wpa_passphrase'])) {
|
||||||
|
$status->addMessage('WPA passphrase must be comprised of printable ASCII characters', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hidden SSID
|
||||||
|
$ignoreBroadcastSSID = $post['hiddenSSID'] ?? '0';
|
||||||
|
if (!ctype_digit($ignoreBroadcastSSID) || (int)$ignoreBroadcastSSID < 0 || (int)$ignoreBroadcastSSID >= 3) {
|
||||||
|
$status->addMessage('Invalid hiddenSSID parameter.', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
if (!in_array($post['interface'], $interfaces, true)) {
|
||||||
|
$status->addMessage('Unknown interface '.htmlspecialchars($post['interface'], ENT_QUOTES), 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// country code
|
||||||
|
$countryCode = $post['country_code'];
|
||||||
|
if (strlen($countryCode) !== 0 && strlen($countryCode) !== 2) {
|
||||||
|
$status->addMessage('Country code must be blank or two characters', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// beacon Interval
|
||||||
|
if (!empty($post['beaconintervalEnable'])) {
|
||||||
|
if (!is_numeric($post['beacon_interval'])) {
|
||||||
|
$status->addMessage('Beacon interval must be numeric', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
} elseif ($post['beacon_interval'] < 15 || $post['beacon_interval'] > 65535) {
|
||||||
|
$status->addMessage('Beacon interval must be between 15 and 65535', 'danger');
|
||||||
|
$goodInput = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// max number of clients
|
||||||
|
$post['max_num_sta'] = (int) ($post['max_num_sta'] ?? 0);
|
||||||
|
$post['max_num_sta'] = $post['max_num_sta'] > 2007 ? 2007 : $post['max_num_sta'];
|
||||||
|
$post['max_num_sta'] = $post['max_num_sta'] < 1 ? null : $post['max_num_sta'];
|
||||||
|
|
||||||
|
if (!$goodInput) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return normalized config array
|
||||||
|
return [
|
||||||
|
'interface' => $post['interface'],
|
||||||
|
'ssid' => $post['ssid'],
|
||||||
|
'channel' => (int)$post['channel'],
|
||||||
|
'wpa' => $post['wpa'],
|
||||||
|
'80211w' => $post['80211w'] ?? 0,
|
||||||
|
'wpa_passphrase' => $post['wpa_passphrase'],
|
||||||
|
'wpa_pairwise' => $post['wpa_pairwise'],
|
||||||
|
'hw_mode' => $post['hw_mode'],
|
||||||
|
'country_code' => $countryCode,
|
||||||
|
'hiddenSSID' => (int)$ignoreBroadcastSSID,
|
||||||
|
'max_num_sta' => $post['max_num_sta'],
|
||||||
|
'beacon_interval' => $post['beacon_interval'] ?? null,
|
||||||
|
'disassoc_low_ack' => $post['disassoc_low_ackEnable'] ?? null,
|
||||||
|
'bridge' => ($post['bridgedEnable'] ?? false) ? 'br0' : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
517
src/RaspAP/Networking/Hotspot/WiFiManager.php
Normal file
517
src/RaspAP/Networking/Hotspot/WiFiManager.php
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wireless utility class for RaspAP
|
||||||
|
* @description A collection of wireless utility methods
|
||||||
|
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||||
|
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RaspAP\Networking\Hotspot;
|
||||||
|
|
||||||
|
class WiFiManager
|
||||||
|
{
|
||||||
|
|
||||||
|
private const MIN_RSSI = -100;
|
||||||
|
private const MAX_RSSI = -55;
|
||||||
|
|
||||||
|
public function knownWifiStations(&$networks)
|
||||||
|
{
|
||||||
|
// find currently configured networks
|
||||||
|
exec(' sudo cat ' . RASPI_WPA_SUPPLICANT_CONFIG, $known_return);
|
||||||
|
$index = 0;
|
||||||
|
foreach ($known_return as $line) {
|
||||||
|
if (preg_match('/network\s*=/', $line)) {
|
||||||
|
$network = array('visible' => false, 'configured' => true, 'connected' => false, 'index' => null);
|
||||||
|
++$index;
|
||||||
|
} elseif (isset($network) && $network !== null) {
|
||||||
|
if (preg_match('/^\s*}\s*$/', $line)) {
|
||||||
|
$networks[$ssid] = $network;
|
||||||
|
$network = null;
|
||||||
|
$ssid = null;
|
||||||
|
} elseif ($lineArr = preg_split('/\s*=\s*/', trim($line), 2)) {
|
||||||
|
switch (strtolower($lineArr[0])) {
|
||||||
|
case 'ssid':
|
||||||
|
$ssid = trim($lineArr[1], '"');
|
||||||
|
$ssid = str_replace('P"','',$ssid);
|
||||||
|
$network['ssid'] = $ssid;
|
||||||
|
$index = $this->getNetworkIdBySSID($ssid);
|
||||||
|
$network['index'] = $index;
|
||||||
|
break;
|
||||||
|
case 'psk':
|
||||||
|
$network['passkey'] = trim($lineArr[1]);
|
||||||
|
$network['protocol'] = 'WPA';
|
||||||
|
break;
|
||||||
|
case '#psk':
|
||||||
|
$network['protocol'] = 'WPA';
|
||||||
|
case 'wep_key0': // Untested
|
||||||
|
$network['passphrase'] = trim($lineArr[1], '"');
|
||||||
|
break;
|
||||||
|
case 'key_mgmt':
|
||||||
|
if (! array_key_exists('passphrase', $network) && $lineArr[1] === 'NONE') {
|
||||||
|
$network['protocol'] = 'Open';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'priority':
|
||||||
|
$network['priority'] = trim($lineArr[1], '"');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans for nearby WiFi networks using `iw` and updates the reference array
|
||||||
|
*
|
||||||
|
* @param array $networks Reference to the array of known and discovered networks
|
||||||
|
* @param bool $cached If false, bypasses the cache and performs a fresh scan
|
||||||
|
*/
|
||||||
|
public function nearbyWifiStations(&$networks, $cached = true)
|
||||||
|
{
|
||||||
|
$cacheTime = filemtime(RASPI_WPA_SUPPLICANT_CONFIG);
|
||||||
|
$cacheKey = "nearby_wifi_stations_$cacheTime";
|
||||||
|
|
||||||
|
if ($cached == false) {
|
||||||
|
deleteCache($cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
||||||
|
|
||||||
|
$scan_results = cache(
|
||||||
|
$cacheKey,
|
||||||
|
function () use ($iface) {
|
||||||
|
$stdout = shell_exec("sudo iw dev $iface scan");
|
||||||
|
return preg_split("/\n/", $stdout);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// exclude the AP from nearby networks
|
||||||
|
exec('sed -rn "s/ssid=(.*)\s*$/\1/p" ' . escapeshellarg(RASPI_HOSTAPD_CONFIG), $ap_ssid);
|
||||||
|
$ap_ssid = $ap_ssid[0] ?? '';
|
||||||
|
|
||||||
|
$index = 0;
|
||||||
|
if (!empty($networks)) {
|
||||||
|
$lastnet = end($networks);
|
||||||
|
if (isset($lastnet['index'])) {
|
||||||
|
$index = $lastnet['index'] + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = [];
|
||||||
|
$commitCurrent = function () use (&$current, &$networks, &$index, $ap_ssid) {
|
||||||
|
if (empty($current['ssid'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ssid = $current['ssid'];
|
||||||
|
|
||||||
|
// unprintable 7bit ASCII control codes, delete or quotes -> ignore network
|
||||||
|
if ($ssid === $ap_ssid || preg_match('/[\x00-\x1f\x7f\'`\´"]/', $ssid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$channel = ConvertToChannel($current['freq'] ?? 0);
|
||||||
|
$rssi = $current['signal'] ?? -100;
|
||||||
|
|
||||||
|
// if network is saved
|
||||||
|
if (array_key_exists($ssid, $networks)) {
|
||||||
|
$networks[$ssid]['visible'] = true;
|
||||||
|
$networks[$ssid]['channel'] = $channel;
|
||||||
|
if (!isset($networks[$ssid]['RSSI']) || $networks[$ssid]['RSSI'] < $rssi) {
|
||||||
|
$networks[$ssid]['RSSI'] = $rssi;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$networks[$ssid] = [
|
||||||
|
'ssid' => $ssid,
|
||||||
|
'configured' => false,
|
||||||
|
'protocol' => $current['security'] ?? 'OPEN',
|
||||||
|
'channel' => $channel,
|
||||||
|
'passphrase' => '',
|
||||||
|
'visible' => true,
|
||||||
|
'connected' => false,
|
||||||
|
'RSSI' => $rssi,
|
||||||
|
'index' => $index
|
||||||
|
];
|
||||||
|
++$index;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach ($scan_results as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
|
||||||
|
if (preg_match('/^BSS\s+([0-9a-f:]{17})/', $line, $match)) {
|
||||||
|
$commitCurrent(); // commit previous
|
||||||
|
$current = [
|
||||||
|
'bssid' => $match[1],
|
||||||
|
'ssid' => '',
|
||||||
|
'signal' => null,
|
||||||
|
'freq' => null,
|
||||||
|
'security' => 'OPEN'
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match('/^SSID:\s*(.*)$/', $line, $match)) {
|
||||||
|
$current['ssid'] = $match[1];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match('/^signal:\s*(-?\d+\.\d+)/', $line, $match)) {
|
||||||
|
$current['signal'] = (float)$match[1];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match('/^freq:\s*(\d+)/', $line, $match)) {
|
||||||
|
$current['freq'] = (int)$match[1];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match('/^RSN:/', $line) || preg_match('/^WPA:/', $line)) {
|
||||||
|
$current['security'] = 'WPA/WPA2';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$commitCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function connectedWifiStations(&$networks)
|
||||||
|
{
|
||||||
|
exec('iwconfig ' .$_SESSION['wifi_client_interface'], $iwconfig_return);
|
||||||
|
foreach ($iwconfig_return as $line) {
|
||||||
|
if (preg_match('/ESSID:\"([^"]+)\"/i', $line, $iwconfig_ssid)) {
|
||||||
|
$ssid=hexSequence2lower($iwconfig_ssid[1]);
|
||||||
|
$networks[$ssid]['connected'] = true;
|
||||||
|
//$check=detectCaptivePortal($_SESSION['wifi_client_interface']);
|
||||||
|
$networks[$ssid]["portal-url"]=$check["URL"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function sortNetworksByRSSI(&$networks)
|
||||||
|
{
|
||||||
|
$valRSSI = array();
|
||||||
|
foreach ($networks as $SSID => $net) {
|
||||||
|
if (!array_key_exists('RSSI', $net)) {
|
||||||
|
$net['RSSI'] = -1000;
|
||||||
|
}
|
||||||
|
$valRSSI[$SSID] = $net['RSSI'];
|
||||||
|
}
|
||||||
|
$nets = $networks;
|
||||||
|
arsort($valRSSI);
|
||||||
|
$networks = array();
|
||||||
|
foreach ($valRSSI as $SSID => $RSSI) {
|
||||||
|
$networks[$SSID] = $nets[$SSID];
|
||||||
|
$networks[$SSID]['RSSI'] = $RSSI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determines the configured wireless AP interface
|
||||||
|
*
|
||||||
|
* If not saved in /etc/raspap/hostapd.ini, check for a second
|
||||||
|
* wireless interface with iw dev. Fallback to the constant
|
||||||
|
* value defined in config.php
|
||||||
|
*/
|
||||||
|
public function getWifiInterface()
|
||||||
|
{
|
||||||
|
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||||
|
$arrHostapdConf = file_exists($hostapdIni) ? parse_ini_file($hostapdIni) : [];
|
||||||
|
|
||||||
|
$iface = $_SESSION['ap_interface'] = $arrHostapdConf['WifiInterface'] ?? RASPI_WIFI_AP_INTERFACE;
|
||||||
|
|
||||||
|
if (!validateInterface($iface)) {
|
||||||
|
$iface = RASPI_WIFI_AP_INTERFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for 2nd wifi interface -> wifi client on different interface
|
||||||
|
exec("iw dev | awk '$1==\"Interface\" && $2!=\"$iface\" {print $2}'", $iface2);
|
||||||
|
$client_iface = $_SESSION['wifi_client_interface'] = empty($iface2) ? $iface : trim($iface2[0]);
|
||||||
|
|
||||||
|
// handle special case for RPi Zero W in AP-STA mode
|
||||||
|
if ($client_iface === "uap0" && ($arrHostapdConf['WifiAPEnable'] ?? 0)) {
|
||||||
|
$_SESSION['wifi_client_interface'] = $iface;
|
||||||
|
$_SESSION['ap_interface'] = $client_iface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reinitializes wpa_supplicant for the wireless client interface
|
||||||
|
* The 'force' parameter deletes the socket in /var/run/wpa_supplicant/
|
||||||
|
*
|
||||||
|
* @param boolean $force
|
||||||
|
*/
|
||||||
|
public function reinitializeWPA($force)
|
||||||
|
{
|
||||||
|
$iface = $_SESSION['wifi_client_interface'];
|
||||||
|
if ($force == true) {
|
||||||
|
$cmd = "sudo /sbin/wpa_supplicant -i $unescapedIface -c /etc/wpa_supplicant/wpa_supplicant.conf -B 2>&1";
|
||||||
|
$result = shell_exec($cmd);
|
||||||
|
}
|
||||||
|
$cmd = "sudo wpa_cli -i $iface reconfigure";
|
||||||
|
$result = shell_exec($cmd);
|
||||||
|
sleep(1);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace escaped bytes (hex) by binary - assume UTF8 encoding
|
||||||
|
*
|
||||||
|
* @param string $ssid
|
||||||
|
*/
|
||||||
|
public function ssid2utf8($ssid)
|
||||||
|
{
|
||||||
|
return evalHexSequence($ssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a signal strength indicator based on RSSI value
|
||||||
|
*
|
||||||
|
* @param string $rssi
|
||||||
|
*/
|
||||||
|
public function getSignalBars($rssi)
|
||||||
|
{
|
||||||
|
// assign css class based on RSSI value
|
||||||
|
$class = '';
|
||||||
|
if ($rssi >= SELF::MAX_RSSI) {
|
||||||
|
$class = 'strong';
|
||||||
|
} elseif ($rssi >= -56) {
|
||||||
|
$class = 'medium';
|
||||||
|
} elseif ($rssi >= -67) {
|
||||||
|
$class = 'weak';
|
||||||
|
} elseif ($rssi >= -89) {
|
||||||
|
$class = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate percent strength
|
||||||
|
if ($rssi >= -50) {
|
||||||
|
$pct = 100;
|
||||||
|
} elseif ($rssi <= SELF::MIN_RSSI) {
|
||||||
|
$pct = 0;
|
||||||
|
} else {
|
||||||
|
$pct = 2*($rssi + 100);
|
||||||
|
}
|
||||||
|
$elem = '<div data-toggle="tooltip" title="' . _("Signal strength"). ': ' .$pct. '%" class="signal-icon ' .$class. '">'.PHP_EOL;
|
||||||
|
for ($n = 0; $n < 3; $n++ ) {
|
||||||
|
$elem .= '<div class="signal-bar"></div>'.PHP_EOL;
|
||||||
|
}
|
||||||
|
$elem .= '</div>'.PHP_EOL;
|
||||||
|
return $elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses output of wpa_cli list_networks, compares with known networks
|
||||||
|
* from wpa_supplicant, and adds with wpa_cli if not found
|
||||||
|
*
|
||||||
|
* @param array $networks
|
||||||
|
* @throws Exception on wpa_cli command failure
|
||||||
|
*/
|
||||||
|
public function setKnownStationsWPA($networks)
|
||||||
|
{
|
||||||
|
$this->ensureWpaSupplicant();
|
||||||
|
|
||||||
|
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
||||||
|
$output = shell_exec("sudo wpa_cli -i $iface list_networks 2>&1");
|
||||||
|
|
||||||
|
if ($output === null) {
|
||||||
|
throw new \Exception("Failed to execute wpa_cli command - command returned null");
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for common wpa_cli errors and try to fix them
|
||||||
|
if (strpos($output, 'Failed to connect') !== false || strpos($output, 'No such file or directory') !== false) {
|
||||||
|
error_log("wpa_supplicant not available for interface, attempting to start it");
|
||||||
|
|
||||||
|
// try starting wpa_supplicant for this interface
|
||||||
|
$unescapedIface = trim($iface, "'\"");
|
||||||
|
$startCmd = "sudo /sbin/wpa_supplicant -i $unescapedIface -c /etc/wpa_supplicant/wpa_supplicant.conf -B 2>&1";
|
||||||
|
$startResult = shell_exec($startCmd);
|
||||||
|
sleep(2);
|
||||||
|
|
||||||
|
// retry
|
||||||
|
$output = shell_exec("sudo wpa_cli -i $iface list_networks 2>&1");
|
||||||
|
|
||||||
|
// if it still fails, throw an exception
|
||||||
|
if ($output === null || strpos($output, 'Failed to connect') !== false) {
|
||||||
|
throw new \Exception("Failed to start wpa_supplicant for interface: " . trim($startResult ?? 'unknown error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// split output into lines
|
||||||
|
$lines = explode("\n", trim($output));
|
||||||
|
|
||||||
|
// check for header line
|
||||||
|
if (empty($lines) || count($lines) < 1) {
|
||||||
|
error_log("wpa_cli list_networks returned no output");
|
||||||
|
$wpaCliNetworks = [];
|
||||||
|
} else {
|
||||||
|
// remove header line if it exists
|
||||||
|
$headerLine = trim($lines[0]);
|
||||||
|
if (strpos($headerLine, 'network id') !== false || strpos($headerLine, 'id') !== false) {
|
||||||
|
array_shift($lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
$wpaCliNetworks = [];
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$trimmedLine = trim($line);
|
||||||
|
|
||||||
|
// skip empty lines
|
||||||
|
if (empty($trimmedLine)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = explode("\t", $trimmedLine);
|
||||||
|
if (count($data) >= 2) {
|
||||||
|
$id = trim($data[0]);
|
||||||
|
$ssid = trim($data[1]);
|
||||||
|
|
||||||
|
// add if we have valid data
|
||||||
|
if ($id !== '' && $ssid !== '') {
|
||||||
|
$wpaCliNetworks[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'ssid' => $ssid
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process networks to add
|
||||||
|
foreach ($networks as $network) {
|
||||||
|
if (!isset($network['ssid']) || empty($network['ssid'])) {
|
||||||
|
error_log("Skipping network with missing or empty SSID");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ssid = $network['ssid'];
|
||||||
|
if (!$this->networkExists($ssid, $wpaCliNetworks)) {
|
||||||
|
$this->addWpaNetwork($network, $iface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to add a single network to wpa_supplicant
|
||||||
|
*
|
||||||
|
* @param array $network Network configuration
|
||||||
|
* @param string $iface Escaped shell argument for interface
|
||||||
|
*/
|
||||||
|
private function addWpaNetwork($network, $iface)
|
||||||
|
{
|
||||||
|
$ssid = escapeshellarg('"' . $network['ssid'] . '"');
|
||||||
|
$psk = escapeshellarg('"' . $network['passphrase'] . '"');
|
||||||
|
$protocol = $network['protocol'] ?? 'WPA';
|
||||||
|
|
||||||
|
// add network and get its ID
|
||||||
|
$netid = trim(shell_exec("sudo wpa_cli -i $iface add_network 2>&1"));
|
||||||
|
|
||||||
|
// validate network ID
|
||||||
|
if (!$netid || !is_numeric($netid)) {
|
||||||
|
error_log("Failed to add network '{$network['ssid']}': Invalid network ID returned: '$netid'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare command based on protocol
|
||||||
|
$commands = [
|
||||||
|
"sudo wpa_cli -i $iface set_network $netid ssid $ssid",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (strtolower($protocol) === 'open') {
|
||||||
|
$commands[] = "sudo wpa_cli -i $iface set_network $netid key_mgmt NONE";
|
||||||
|
} else {
|
||||||
|
$commands[] = "sudo wpa_cli -i $iface set_network $netid psk $psk";
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands[] = "sudo wpa_cli -i $iface enable_network $netid";
|
||||||
|
|
||||||
|
// execute commands, checking errors
|
||||||
|
foreach ($commands as $cmd) {
|
||||||
|
$result = shell_exec("$cmd 2>&1");
|
||||||
|
if ($result === null || strpos($result, 'FAIL') !== false) {
|
||||||
|
error_log("Command failed: $cmd - Result: " . ($result ?? 'null'));
|
||||||
|
// remove the failed network
|
||||||
|
shell_exec("sudo wpa_cli -i $iface remove_network $netid 2>&1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
error_log("Successfully added network: {$network['ssid']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parses wpa_cli list_networks output and returns the id
|
||||||
|
* of a corresponding network SSID
|
||||||
|
*
|
||||||
|
* @param string $ssid
|
||||||
|
* @return integer id
|
||||||
|
*/
|
||||||
|
public function getNetworkIdBySSID($ssid) {
|
||||||
|
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
|
||||||
|
$cmd = "sudo wpa_cli -i $iface list_networks";
|
||||||
|
$output = [];
|
||||||
|
exec($cmd, $output);
|
||||||
|
array_shift($output);
|
||||||
|
foreach ($output as $line) {
|
||||||
|
$columns = preg_split('/\t/', $line);
|
||||||
|
if (count($columns) >= 4 && trim($columns[1]) === trim($ssid)) {
|
||||||
|
return $columns[0]; // return network ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function networkExists($ssid, $collection)
|
||||||
|
{
|
||||||
|
foreach ($collection as $network) {
|
||||||
|
if ($network['ssid'] === $ssid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures /etc/wpa_supplicant/wpa_supplicant.conf exists with minimal safe contents
|
||||||
|
* Does not overwrite an existing file
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException on permission or write failure
|
||||||
|
*/
|
||||||
|
public function ensureWpaSupplicant(): void
|
||||||
|
{
|
||||||
|
$confPath = '/etc/wpa_supplicant/wpa_supplicant.conf';
|
||||||
|
|
||||||
|
if (file_exists($confPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = <<<CONF
|
||||||
|
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||||
|
update_config=1
|
||||||
|
CONF;
|
||||||
|
|
||||||
|
$tmpFile = tempnam(sys_get_temp_dir(), 'wpa_conf_');
|
||||||
|
if ($tmpFile === false) {
|
||||||
|
throw new \RuntimeException("Failed to create temporary file for wpa_supplicant.conf");
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($tmpFile, $contents);
|
||||||
|
chmod($tmpFile, 0600);
|
||||||
|
|
||||||
|
$cmd = escapeshellcmd("sudo cp $tmpFile $confPath");
|
||||||
|
exec($cmd, $output, $exitCode);
|
||||||
|
unlink($tmpFile);
|
||||||
|
|
||||||
|
if ($exitCode !== 0) {
|
||||||
|
throw new \RuntimeException("Failed to initialize wpa_supplicant.conf: " . implode("\n", $output));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class PluginInstaller
|
|||||||
$this->tempSudoers = '/tmp/090_';
|
$this->tempSudoers = '/tmp/090_';
|
||||||
$this->destSudoers = '/etc/sudoers.d/';
|
$this->destSudoers = '/etc/sudoers.d/';
|
||||||
$this->refModules = '/refs/heads/master/.gitmodules';
|
$this->refModules = '/refs/heads/master/.gitmodules';
|
||||||
$this->rootPath = $_SERVER['DOCUMENT_ROOT'];
|
$this->rootPath = dirname(__DIR__, 3);
|
||||||
$this->pluginsManifest = '/plugins/manifest.json';
|
$this->pluginsManifest = '/plugins/manifest.json';
|
||||||
$this->repoPublic = $this->getRepository();
|
$this->repoPublic = $this->getRepository();
|
||||||
$this->helperScriptPath = RASPI_CONFIG.'/plugins/plugin_helper.sh';
|
$this->helperScriptPath = RASPI_CONFIG.'/plugins/plugin_helper.sh';
|
||||||
|
|||||||
@@ -76,8 +76,10 @@ class PluginManager
|
|||||||
* Iterates over registered plugins and calls its associated method
|
* Iterates over registered plugins and calls its associated method
|
||||||
* @param string $page
|
* @param string $page
|
||||||
*/
|
*/
|
||||||
public function handlePageAction(string $page): bool
|
public function handlePageAction(?string $page): bool
|
||||||
{
|
{
|
||||||
|
$page = $page ?? '';
|
||||||
|
|
||||||
foreach ($this->getInstalledPlugins() as $pluginClass) {
|
foreach ($this->getInstalledPlugins() as $pluginClass) {
|
||||||
$plugin = new $pluginClass($this->pluginPath, $pluginClass);
|
$plugin = new $pluginClass($this->pluginPath, $pluginClass);
|
||||||
|
|
||||||
|
|||||||
@@ -46,12 +46,18 @@ class Sysinfo
|
|||||||
return $systime;
|
return $systime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function usedMemory()
|
public function usedMemory(): int
|
||||||
{
|
{
|
||||||
$used = shell_exec("free -m | awk 'NR==2{ total=$2 ; used=$3 } END { print used/total*100}'");
|
$used = shell_exec("free -m | awk 'NR==2{ total=$2 ; used=$3 } END { print used/total*100}'");
|
||||||
return floor(intval($used));
|
return floor(intval($used));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function usedDisk(): int
|
||||||
|
{
|
||||||
|
$output = shell_exec("df -h / | awk 'NR==2 {print $5}'");
|
||||||
|
return intval(str_replace('%', '', trim($output)));
|
||||||
|
}
|
||||||
|
|
||||||
public function processorCount()
|
public function processorCount()
|
||||||
{
|
{
|
||||||
$procs = shell_exec("nproc --all");
|
$procs = shell_exec("nproc --all");
|
||||||
@@ -167,12 +173,14 @@ class Sysinfo
|
|||||||
{
|
{
|
||||||
exec('cat '. RASPI_ADBLOCK_CONFIG, $return);
|
exec('cat '. RASPI_ADBLOCK_CONFIG, $return);
|
||||||
$arrConf = ParseConfig($return);
|
$arrConf = ParseConfig($return);
|
||||||
|
$enabled = false;
|
||||||
if (sizeof($arrConf) > 0) {
|
if (sizeof($arrConf) > 0) {
|
||||||
$enabled = true;
|
$enabled = true;
|
||||||
}
|
}
|
||||||
exec('pidof dnsmasq | wc -l', $dnsmasq);
|
exec('pidof dnsmasq | wc -l', $dnsmasq);
|
||||||
$dnsmasq_state = ($dnsmasq[0] > 0);
|
$dnsmasq_state = ($dnsmasq[0] > 0);
|
||||||
$status = $dnsmasq_state && $enabled ? true : false;
|
|
||||||
|
$status = $dnsmasq_state && $enabled;
|
||||||
return $status;
|
return $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -301,14 +301,12 @@ class Dashboard {
|
|||||||
*/
|
*/
|
||||||
public function firewallEnabled(): bool
|
public function firewallEnabled(): bool
|
||||||
{
|
{
|
||||||
$conf = array();
|
if (!file_exists($this->firewallConfig)) {
|
||||||
if (file_exists($this->firewallConfig) ) {
|
return false;
|
||||||
$conf = parse_ini_file($this->firewallConfig);
|
|
||||||
}
|
}
|
||||||
if ($conf["firewall-enable"] == 1) {
|
|
||||||
return true;
|
$conf = parse_ini_file($this->firewallConfig) ?: [];
|
||||||
}
|
return !empty($conf['firewall-enable']) && (int)$conf['firewall-enable'] === 1;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<input type="submit" class="btn btn-outline btn-primary" value="<?php echo _("Save settings"); ?>" name="savedhcpdsettings" />
|
<input type="submit" class="btn btn-outline btn-primary" value="<?php echo _("Save settings"); ?>" name="savedhcpdsettings" />
|
||||||
<?php if ($dnsmasq_state) : ?>
|
<?php if ($dnsmasq_state) : ?>
|
||||||
<input type="submit" class="btn btn-warning" value="<?php echo _("Stop dnsmasq") ?>" name="stopdhcpd" />
|
<input type="submit" class="btn btn-warning" value="<?php echo _("Stop dnsmasq") ?>" name="stopdhcpd" />
|
||||||
|
<input type="submit" class="btn btn-warning" value="<?php echo _("Restart dnsmasq") ?>" name="restartdhcpd" />
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<input type="submit" class="btn btn-success" value="<?php echo _("Start dnsmasq") ?>" name="startdhcpd" />
|
<input type="submit" class="btn btn-success" value="<?php echo _("Start dnsmasq") ?>" name="startdhcpd" />
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|||||||
@@ -69,9 +69,10 @@
|
|||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" id="default-route" type="checkbox" name="DefaultRoute" value="1" aria-describedby="default-route-description">
|
<input class="form-check-input" id="default-route" type="checkbox" name="DefaultRoute" value="1" aria-describedby="default-route-description">
|
||||||
<label class="form-check-label" for="default-route"><?php echo _("Install a default route for this interface") ?></label>
|
<label class="form-check-label" for="default-route"><?php echo _("Install a default route for this interface") ?></label>
|
||||||
|
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="<?php echo _("Enable this only if you want your device to use this interface as its primary route to the internet."); ?>"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-0" id="default-route-description">
|
<p class="mb-0" id="default-route-description">
|
||||||
<small><?php echo _("This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the DHCPCD configuration.") ?></small>
|
<small><?php echo _("This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the dhcpcd.conf file.") ?></small>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +85,7 @@
|
|||||||
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="<?php echo _("If you manage wireless connections with wpa_supplicant itself, the hook may create unwanted connection events. This option disables the hook."); ?>"></i>
|
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="<?php echo _("If you manage wireless connections with wpa_supplicant itself, the hook may create unwanted connection events. This option disables the hook."); ?>"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-0" id="hook-wpa-supplicant-description">
|
<p class="mb-0" id="hook-wpa-supplicant-description">
|
||||||
<small><?php echo _("This toggles the <code>nohook wpa_supplicant</code> option for this interface in the DHCPCD configuration.") ?></small>
|
<small><?php echo _("This toggles the <code>nohook wpa_supplicant</code> option for this interface in the dhcpcd.conf file.") ?></small>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<div class="row dhcp-static-lease-row js-new-dhcp-static-lease">
|
<div class="row dhcp-static-lease-row js-new-dhcp-static-lease">
|
||||||
<div class="col-md-4 col-xs-3">
|
<div class="col-md-4 col-xs-3">
|
||||||
<input type="text" name="mac" value="" placeholder="<?php echo _("MAC address") ?>" class="form-control date" autofocus="autofocus">
|
<input type="text" name="mac" value="" placeholder="<?php echo _("MAC address") ?>" class="form-control mac_address" autofocus="autofocus">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-xs-3">
|
<div class="col-md-3 col-xs-3">
|
||||||
<input type="text" name="ip" value="" placeholder="<?php echo _("IP address") ?>" class="form-control ip_address" maxlength="15">
|
<input type="text" name="ip" value="" placeholder="<?php echo _("IP address") ?>" class="form-control ip_address" maxlength="15">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="mb-3 col-md-6">
|
<div class="mb-3 col-md-6">
|
||||||
<label for="cbxhwmode"><?php echo _("Wireless Mode") ;?></label>
|
<label for="cbxhwmode"><?php echo _("Wireless Mode") ;?></label>
|
||||||
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="<?php echo _("The 802.11ac 5 GHz option is disabled until a compatible wireless regulatory domain is set."); ?>"></i>
|
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="<?php echo _("The 802.11ac 5 GHz option is disabled until a compatible wireless regulatory domain is set."); ?>"></i>
|
||||||
<?php SelectorOptions('hw_mode', $arr80211Standard, $selectedHwMode, 'cbxhwmode', 'getChannel'); ?>
|
<?php SelectorOptions('hw_mode', $arr80211Standard, $arrConfig['selected_hw_mode'], 'cbxhwmode', 'getChannel'); ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -4,15 +4,21 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="cbxwpa"><?php echo _("Security type"); ?></label>
|
<label for="cbxwpa"><?php echo _("Security type"); ?></label>
|
||||||
<?php SelectorOptions('wpa', $arrSecurity, $arrConfig['wpa'], 'cbxwpa'); ?>
|
<?php SelectorOptions('wpa', $arrSecurity, $arrConfig['wpa'], 'cbxwpa', 'load80211wSelect'); ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="cbxwpapairwise"><?php echo _("Encryption Type"); ?></label>
|
<label for="cbxwpapairwise"><?php echo _("Encryption Type"); ?></label>
|
||||||
<?php SelectorOptions('wpa_pairwise', $arrEncType, $arrConfig['wpa_pairwise'], 'cbxwpapairwise'); ?>
|
<?php SelectorOptions('wpa_pairwise', $arrEncType, $arrConfig['wpa_pairwise'], 'cbxwpapairwise'); ?>
|
||||||
</div>
|
</div>
|
||||||
<label for="txtwpapassphrase"><?php echo _("PSK"); ?></label>
|
<div class="mb-3">
|
||||||
|
<label for="cbx80211w"><?php echo _("802.11w"); ?></label>
|
||||||
|
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="802.11w extends strong cryptographic protection to a select set of robust management frames, including Deauthentication, Disassociation and certain categories of Action Management frames. Collectively, this is known as Management Frame Protection (MFP)."></i>
|
||||||
|
<?php SelectorOptions('80211w', $arr80211w, $arrConfig['ieee80211w'] ?? 0, 'cbx80211w'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label for="txtwpapassphrase"><?php echo _("Pre-shared key (PSK)"); ?></label>
|
||||||
<div class="input-group has-validation">
|
<div class="input-group has-validation">
|
||||||
<input type="text" class="form-control" id="txtwpapassphrase" name="wpa_passphrase" value="<?php echo htmlspecialchars($arrConfig['wpa_passphrase'], ENT_QUOTES); ?>" aria-describedby="gen_wpa_passphrase" required />
|
<input type="text" class="form-control" id="txtwpapassphrase" name="wpa_passphrase" value="<?php echo htmlspecialchars($arrConfig['wpa_passphrase'], ENT_QUOTES); ?>" required />
|
||||||
<div class="input-group-text" id="gen_wpa_passphrase"><i class="fa-solid fa-wand-magic-sparkles"></i></div>
|
<div class="input-group-text" id="gen_wpa_passphrase"><i class="fa-solid fa-wand-magic-sparkles"></i></div>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
<?php echo _("Please provide a valid PSK."); ?>
|
<?php echo _("Please provide a valid PSK."); ?>
|
||||||
@@ -22,7 +28,12 @@
|
|||||||
<div class="col-md-6 mt-3">
|
<div class="col-md-6 mt-3">
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="app/img/wifi-qr-code.php" class="figure-img img-fluid" alt="RaspAP Wifi QR code" style="width:100%;">
|
<img src="app/img/wifi-qr-code.php" class="figure-img img-fluid" alt="RaspAP Wifi QR code" style="width:100%;">
|
||||||
<figcaption class="figure-caption"><?php echo _("Scan this QR code with your phone to connect to this RaspAP."); ?></figcaption>
|
<figcaption class="figure-caption">
|
||||||
|
<?php echo sprintf(_("Scan this QR code directly or %s %sprint a sign%s for your users."),
|
||||||
|
'<i class="fas fa-print"></i>',
|
||||||
|
'<a href="javascript:window.open(\'../app/lib/signprint.php\',\'Printable Wi-Fi sign\',\'width=550,height=670\')">',
|
||||||
|
'</a>'); ?>
|
||||||
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<!-- branding -->
|
<!-- branding -->
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-3">
|
||||||
<img src="app/img/raspAP-logo.php" class="navbar-logo" alt="RaspAP logo" class="img-fluid" style="max-width: 100px;">
|
<img src="app/img/raspAP-logo.php" class="login-logo" alt="RaspAP logo" class="img-fluid" style="max-width: 100px;">
|
||||||
<h2 class="login-brand"><?php echo htmlspecialchars(RASPI_BRAND_TEXT); ?></h2>
|
<h2 class="login-brand"><?php echo htmlspecialchars(RASPI_BRAND_TEXT); ?></h2>
|
||||||
<div class="mt-2 admin-login"><?php echo _("Administrator login") ?></div>
|
<div class="mt-2 admin-login"><?php echo _("Administrator login") ?></div>
|
||||||
<div class="text-center text-danger mt-1 mb-3"><?php echo $status ?></div>
|
<div class="text-center text-danger mt-1 mb-3"><?php echo $status ?></div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@@ -9,96 +9,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div><!-- ./row -->
|
</div><!-- ./row -->
|
||||||
</div><!-- ./card-header -->
|
</div><!-- ./card-header -->
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<?php $status->showMessages(); ?>
|
||||||
|
<form role="form" action="network_conf" method="POST" class="needs-validation" novalidate>
|
||||||
|
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
|
||||||
<div id="msgNetworking"></div>
|
<div id="msgNetworking"></div>
|
||||||
|
<!-- Nav tabs -->
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation" class="nav-item"><a class="nav-link active" href="#summary" aria-controls="summary" role="tab" data-bs-toggle="tab"><?php echo _("Summary"); ?></a></li>
|
<li class="nav-item"><a class="nav-link active" href="#summary" aria-controls="summary" role="tab" data-bs-toggle="tab"><?php echo _("Summary"); ?></a></li>
|
||||||
|
<?php if (!$bridgedEnabled) : // no interface details when bridged ?>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="#diagnostic" aria-controls="diagnostic" role="tab" data-bs-toggle="tab"><?php echo _("Diagnostics"); ?></a></li>
|
||||||
|
<?php endif ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Tab panes -->
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div role="tabpanel" class="tab-pane active" id="summary">
|
<?php echo renderTemplate("networking/general", $__template_data) ?>
|
||||||
<h4 class="mt-3"><?php echo _("Internet connection"); ?></h4>
|
<?php echo renderTemplate("networking/diagnostics", $__template_data) ?>
|
||||||
<div class="row">
|
</div><!-- /.tab-content -->
|
||||||
<div class="col-sm-12">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?php echo _("Interface"); ?></th>
|
|
||||||
<th><?php echo _("IP Address"); ?></th>
|
|
||||||
<th><?php echo _("Gateway"); ?></th>
|
|
||||||
<th colspan="2"><?php echo _("Internet Access"); ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php if (isset($routeInfo["error"]) || empty($routeInfo)): ?>
|
|
||||||
<tr><td colspan=5>No route to the internet found</td></tr>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php foreach($routeInfo as $route): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?php echo $route['interface'] ?></td>
|
|
||||||
<td><?php echo $route['ip-address'] ?></td>
|
|
||||||
<td><?php echo $route['gateway'] ?><br><?php $route['gw-name'] ?></td>
|
|
||||||
<td>
|
|
||||||
<p class="m-0">
|
|
||||||
<i class="fas <?php echo $route["access-ip"] ? "fa-check" : "fa-times"; ?>"></i> <?php echo RASPI_ACCESS_CHECK_IP ?>
|
|
||||||
</p>
|
|
||||||
<p class="m-0">
|
|
||||||
<i class="fas <?php echo $route["access-dns"] ? "fa-check" : "fa-times"; ?>"></i> <?php echo RASPI_ACCESS_CHECK_DNS ?>
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach ?>
|
|
||||||
<?php endif ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 class="mt-3"><?php echo _("Routing table"); ?></h4>
|
|
||||||
<div class="card h-100 w-100">
|
|
||||||
<div class="card-header"><?php echo _("raw output") ?></div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table">
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($routeInfoRaw as $route): ?>
|
|
||||||
<tr>
|
|
||||||
<pre class="unstyled"><?php echo $route; ?></pre>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 class="mt-3"><?php echo _("Current settings") ?></h4>
|
|
||||||
<div class="row">
|
|
||||||
<?php if (!$bridgedEnabled) : // No interface details when bridged ?>
|
|
||||||
<?php foreach ($interfaces as $if): ?>
|
|
||||||
<?php $if_quoted = htmlspecialchars($if, ENT_QUOTES) ?>
|
|
||||||
<div class="col-md mb-3">
|
|
||||||
<div class="card h-100 w-100">
|
|
||||||
<div class="card-header"><?php echo $if_quoted ?></div>
|
|
||||||
<div class="card-body">
|
|
||||||
<pre class="unstyled" id="<?php echo $if_quoted ?>-summary"></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach ?>
|
|
||||||
<?php endif ?>
|
|
||||||
</div><!-- /.row -->
|
|
||||||
|
|
||||||
<button type="button" onClick="window.location.reload();" class="btn btn-outline btn-primary"><i class="fas fa-sync-alt"></i> <?php echo _("Refresh") ?></a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div><!-- /.card-body -->
|
</div><!-- /.card-body -->
|
||||||
|
|
||||||
<div class="card-footer"><?php echo _("Information provided by /sys/class/net"); ?></div>
|
<div class="card-footer"><?php echo _("Information provided by /sys/class/net"); ?></div>
|
||||||
|
|||||||
95
templates/networking/diagnostics.php
Executable file
95
templates/networking/diagnostics.php
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
<div role="tabpanel" class="tab-pane" id="diagnostic">
|
||||||
|
|
||||||
|
<h4 class="mt-3"><?php echo _("Speedtest") ?></h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
|
||||||
|
<div id="loading" class="visible">
|
||||||
|
<p id="message"><span class="loadCircle"></span><?php echo _("Selecting a server"); ?>...</p>
|
||||||
|
</div>
|
||||||
|
<div id="testWrapper" class="hidden">
|
||||||
|
<button id="startStopBtn" type="button" class="btn btn-outline btn-primary" onclick="startStop()"></button>
|
||||||
|
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display=''"><?php echo _("Privacy"); ?></a>
|
||||||
|
|
||||||
|
<div class="col-sm-4 centered">
|
||||||
|
<div id="serverArea">
|
||||||
|
<?php echo _("Server"); ?>: <select id="server" class="form-select" onchange="s.setSelectedServer(SPEEDTEST_SERVERS[this.value]);"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="test">
|
||||||
|
<div class="testGroup">
|
||||||
|
<div class="testArea2">
|
||||||
|
<div class="testName"><?php echo _("Ping"); ?></div>
|
||||||
|
<div id="pingText" class="meterText" style="color:#AA6060"></div>
|
||||||
|
<div class="unit"><?php echo _("ms"); ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="testArea2">
|
||||||
|
<div class="testName"><?php echo _("Jitter"); ?></div>
|
||||||
|
<div id="jitText" class="meterText" style="color:#AA6060"></div>
|
||||||
|
<div class="unit"><?php echo _("ms"); ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="testGroup">
|
||||||
|
<div class="testArea">
|
||||||
|
<div class="testName"><?php echo _("Download"); ?></div>
|
||||||
|
<canvas id="dlMeter" class="meter"></canvas>
|
||||||
|
<div id="dlText" class="meterText"></div>
|
||||||
|
<div class="unit"><?php echo _("Mbps"); ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="testArea">
|
||||||
|
<div class="testName"><?php echo _("Upload"); ?></div>
|
||||||
|
<canvas id="ulMeter" class="meter"></canvas>
|
||||||
|
<div id="ulText" class="meterText"></div>
|
||||||
|
<div class="unit"><?php echo _("Mbps"); ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ipArea">
|
||||||
|
<span id="ip"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.testWrapper -->
|
||||||
|
|
||||||
|
<div id="privacyPolicy" style="display:none">
|
||||||
|
<h4>Speedtest Privacy Policy</h4>
|
||||||
|
<p>RaspAP's <a href="https://speedtest.raspap.com/">Speedtest server</a> is configured with telemetry enabled.</p>
|
||||||
|
<h5>Data we collect</h5>
|
||||||
|
<p>
|
||||||
|
At the end of the test, the following data is collected and stored:
|
||||||
|
<ul>
|
||||||
|
<li>Test ID</li>
|
||||||
|
<li>Time of testing</li>
|
||||||
|
<li>Test results (download and upload speed, ping and jitter)</li>
|
||||||
|
<li>IP address</li>
|
||||||
|
<li>ISP information</li>
|
||||||
|
<li>Approximate location (inferred from IP address, not GPS)</li>
|
||||||
|
<li>User agent and browser locale</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<h5>How we use the data</h5>
|
||||||
|
<p>
|
||||||
|
Data collected through this service is used to:
|
||||||
|
<ul>
|
||||||
|
<li>Allow sharing of test results (sharable image for forums, etc.)</li>
|
||||||
|
<li>To improve the service offered to you (for instance, to detect problems on our side)</li>
|
||||||
|
</ul>
|
||||||
|
No personal information is disclosed to third parties.
|
||||||
|
</p>
|
||||||
|
<h5>Your consent</h5>
|
||||||
|
<p>
|
||||||
|
By starting the test, you consent to the terms of this privacy policy.
|
||||||
|
</p>
|
||||||
|
<h5>Data removal</h5>
|
||||||
|
<p>
|
||||||
|
If you wish to have your information deleted, simply provide us with your IP address. Without this information we won't be able to comply with your request. Contact <a href="mailto:support@raspap.com">support@raspap.com</a> for all deletion requests.
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
<div class="closePrivacyPolicy">
|
||||||
|
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display='none'">Close</a>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
</div><!-- /.privacyPolicy -->
|
||||||
|
|
||||||
|
</div><!-- /.col -->
|
||||||
|
</div><!-- /.row -->
|
||||||
|
</div><!-- /.tabpanel -->
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user