mirror of
https://github.com/RaspAP/raspap-webgui.git
synced 2025-04-21 19:23:25 +00:00
Compare commits
329 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3175459ab | ||
![]() |
3bf682d752 | ||
![]() |
9e0801aa0c | ||
![]() |
a21b0a8f99 | ||
![]() |
5f1b16bc74 | ||
![]() |
deba5e1e74 | ||
![]() |
0960e8bac9 | ||
![]() |
4a4506a913 | ||
![]() |
bfab3d7441 | ||
![]() |
47d7c121de | ||
![]() |
484b89718a | ||
![]() |
d6c8ac32a7 | ||
![]() |
3d6b4e1f15 | ||
![]() |
8d482845b0 | ||
![]() |
a1550d8049 | ||
![]() |
5584e3b72c | ||
![]() |
b0ba029c66 | ||
![]() |
48e492bf10 | ||
![]() |
821ac9c1f8 | ||
![]() |
49cb3911b8 | ||
![]() |
8ad582b3b2 | ||
![]() |
bbf1caf777 | ||
![]() |
b3c6178274 | ||
![]() |
a5907d8f7f | ||
![]() |
8569c2b4d5 | ||
![]() |
2a70f6ee11 | ||
![]() |
c8b0408bd5 | ||
![]() |
125ae7a39a | ||
![]() |
20fe5fc5a7 | ||
![]() |
168ed2448f | ||
![]() |
a2b8dfe551 | ||
![]() |
4aee1e49d9 | ||
![]() |
16d92bb486 | ||
![]() |
de376d04d1 | ||
![]() |
d730c174d4 | ||
![]() |
c798f5fd69 | ||
![]() |
3a73916206 | ||
![]() |
f46be0139e | ||
![]() |
a6295aef6e | ||
![]() |
556c2be855 | ||
![]() |
80e8178384 | ||
![]() |
b1c429f404 | ||
![]() |
f0b992b9be | ||
![]() |
084b2e1268 | ||
![]() |
0fd60e3730 | ||
![]() |
cfc6644087 | ||
![]() |
2cd4abc3c2 | ||
![]() |
8bd5b1b988 | ||
![]() |
2b8f7fd6d8 | ||
![]() |
b99addef4a | ||
![]() |
96ada80ce1 | ||
![]() |
5d33c79369 | ||
![]() |
645ab89437 | ||
![]() |
62b342cdbb | ||
![]() |
c235a4ff16 | ||
![]() |
a24516a4d5 | ||
![]() |
483b1fc27d | ||
![]() |
4d0de82986 | ||
![]() |
85663341eb | ||
![]() |
734043dee6 | ||
![]() |
af2927f05b | ||
![]() |
3e54b1d7bb | ||
![]() |
51a0ce220c | ||
![]() |
05e20e3bab | ||
![]() |
03fc2c42ad | ||
![]() |
401172eb36 | ||
![]() |
71f1132bc8 | ||
![]() |
605486feda | ||
![]() |
23597e800d | ||
![]() |
3218d87b1b | ||
![]() |
0c58a6c92c | ||
![]() |
afa3006de2 | ||
![]() |
03b9bf9e7a | ||
![]() |
028c0d3e06 | ||
![]() |
0005488884 | ||
![]() |
f73f25708c | ||
![]() |
4e411aaa6b | ||
![]() |
6adeab7586 | ||
![]() |
16f6b7f979 | ||
![]() |
068f363f09 | ||
![]() |
d4554c6429 | ||
![]() |
a3caa6485c | ||
![]() |
795d55a2cd | ||
![]() |
c53c1a27a4 | ||
![]() |
903cc6bd8e | ||
![]() |
13929acbd1 | ||
![]() |
f6da130fce | ||
![]() |
1fc793c3fd | ||
![]() |
4b6ac1a415 | ||
![]() |
304010db40 | ||
![]() |
66563c9d95 | ||
![]() |
5eca4c045b | ||
![]() |
fd953e7a71 | ||
![]() |
52b20cb491 | ||
![]() |
c987d2800d | ||
![]() |
6d6bacd6a1 | ||
![]() |
f0a0c9228f | ||
![]() |
dfef9e5233 | ||
![]() |
ab77af9e5d | ||
![]() |
5ce06c6214 | ||
![]() |
d5cc80d1f2 | ||
![]() |
e3f04192b8 | ||
![]() |
cfb8435373 | ||
![]() |
615f2abed1 | ||
![]() |
ac926e84d7 | ||
![]() |
8846b96905 | ||
![]() |
677e99ffd9 | ||
![]() |
a82b30179b | ||
![]() |
ae4e9be739 | ||
![]() |
7091a4016a | ||
![]() |
24b292bf33 | ||
![]() |
c00f006cdd | ||
![]() |
ed47a41c9c | ||
![]() |
39cc92853a | ||
![]() |
34f7563bed | ||
![]() |
964d7b38a8 | ||
![]() |
17fbbca046 | ||
![]() |
64faf296a6 | ||
![]() |
f1c404a443 | ||
![]() |
5ed1312406 | ||
![]() |
00c18451cc | ||
![]() |
b737b5c748 | ||
![]() |
d852990314 | ||
![]() |
182073f41e | ||
![]() |
64d3a11866 | ||
![]() |
4865e85655 | ||
![]() |
3d6095d652 | ||
![]() |
bd0e379d01 | ||
![]() |
93395a8aa5 | ||
![]() |
339437f47f | ||
![]() |
80db7edf18 | ||
![]() |
41e86a9e51 | ||
![]() |
ef04678947 | ||
![]() |
1a964a283f | ||
![]() |
6c47375d18 | ||
![]() |
c2abce1e35 | ||
![]() |
b4b715f6e4 | ||
![]() |
8a7c954d88 | ||
![]() |
639f7605d1 | ||
![]() |
fbcf9809c5 | ||
![]() |
2474765820 | ||
![]() |
ba4507bd22 | ||
![]() |
2610c44ac9 | ||
![]() |
f608282aa5 | ||
![]() |
781f376bea | ||
![]() |
6e1c3b95c2 | ||
![]() |
0331eb7b25 | ||
![]() |
c3a210907a | ||
![]() |
3e0f1f16c1 | ||
![]() |
c2be25271b | ||
![]() |
bb131a7f53 | ||
![]() |
c753865305 | ||
![]() |
0bac7deccc | ||
![]() |
9ada9f9e68 | ||
![]() |
02b9e20ce3 | ||
![]() |
3c8cf996b5 | ||
![]() |
313e2eb06f | ||
![]() |
41fcbba5cc | ||
![]() |
ce9916a792 | ||
![]() |
3f830458b1 | ||
![]() |
a8e24b7629 | ||
![]() |
80f5cb5f46 | ||
![]() |
d45aa752c6 | ||
![]() |
2a1e39a880 | ||
![]() |
8f402ab303 | ||
![]() |
644d2fe6cb | ||
![]() |
b3fe781c19 | ||
![]() |
69510a4e92 | ||
![]() |
163d727ee1 | ||
![]() |
20fdd9024d | ||
![]() |
23e0a6601a | ||
![]() |
47d0305bec | ||
![]() |
f28c5533bc | ||
![]() |
b89fe7c6b8 | ||
![]() |
71ed223cf5 | ||
![]() |
335491124f | ||
![]() |
e45c00b83c | ||
![]() |
aa5fcef2d4 | ||
![]() |
01ddf6f4ea | ||
![]() |
bbac9a8802 | ||
![]() |
4374e78bbb | ||
![]() |
6e0cf0b085 | ||
![]() |
867a46bee9 | ||
![]() |
4a89cb3b07 | ||
![]() |
dc86f15b59 | ||
![]() |
91c535ddbb | ||
![]() |
f576b02858 | ||
![]() |
eb27ba2c66 | ||
![]() |
362a08c00f | ||
![]() |
43ae90bd34 | ||
![]() |
c34cbeca8d | ||
![]() |
33f66fdae5 | ||
![]() |
cc9720e8ef | ||
![]() |
74b1b10e31 | ||
![]() |
41ce72775e | ||
![]() |
9876ab1e3e | ||
![]() |
bd0b226f61 | ||
![]() |
3417115a84 | ||
![]() |
7a880d563f | ||
![]() |
32e191e55d | ||
![]() |
58501a74a7 | ||
![]() |
80c1a04797 | ||
![]() |
af3abe66f4 | ||
![]() |
8a8be213f7 | ||
![]() |
0efbe2b326 | ||
![]() |
624e43f954 | ||
![]() |
55b6c81eca | ||
![]() |
aa49dcf32c | ||
![]() |
10d5c4a8a6 | ||
![]() |
2f205fe9e4 | ||
![]() |
6b7b8ef8d0 | ||
![]() |
a9804b2f9c | ||
![]() |
484d40f455 | ||
![]() |
18114df2bd | ||
![]() |
e61dac332c | ||
![]() |
b9842b5462 | ||
![]() |
739057c7ac | ||
![]() |
d689024c1f | ||
![]() |
c1975a78a1 | ||
![]() |
5fbafeb455 | ||
![]() |
7def2d6da1 | ||
![]() |
538e37ceb7 | ||
![]() |
5d01fa59b1 | ||
![]() |
0b0f4bc06d | ||
![]() |
a619e7a25b | ||
![]() |
a8bd85cc80 | ||
![]() |
e423b7f4d3 | ||
![]() |
47c509277c | ||
![]() |
a6fdb63dd2 | ||
![]() |
75bb3e4a34 | ||
![]() |
36c3e5036f | ||
![]() |
a9021c89f4 | ||
![]() |
77f183817a | ||
![]() |
97bc8174b4 | ||
![]() |
83ed9b5c19 | ||
![]() |
0871601b31 | ||
![]() |
eb25142e03 | ||
![]() |
42b6c8fce3 | ||
![]() |
7a9e5169f6 | ||
![]() |
2ca7448bd3 | ||
![]() |
cd3fde71e3 | ||
![]() |
ec0dd304ee | ||
![]() |
50ed5f9f4b | ||
![]() |
259fac717f | ||
![]() |
0fd65fdbc2 | ||
![]() |
2b92d028f6 | ||
![]() |
9c4f8be363 | ||
![]() |
21b9feb0ef | ||
![]() |
bc7d4ef1c1 | ||
![]() |
bc23dfc130 | ||
![]() |
9042ee8c01 | ||
![]() |
effcb5e48e | ||
![]() |
111c9581a3 | ||
![]() |
6cb0be96b4 | ||
![]() |
6dbdf89760 | ||
![]() |
792ce0c956 | ||
![]() |
3f883a70de | ||
![]() |
187041b030 | ||
![]() |
c51b520b8d | ||
![]() |
866d8eb5b0 | ||
![]() |
3cf22a9cbb | ||
![]() |
6f1c34f28d | ||
![]() |
41a138026b | ||
![]() |
0c31b5ba71 | ||
![]() |
2bab11b951 | ||
![]() |
b52bd84ea4 | ||
![]() |
ff7e674b2e | ||
![]() |
3e91f50966 | ||
![]() |
7dcc177424 | ||
![]() |
94b502a336 | ||
![]() |
bfd5859ce1 | ||
![]() |
13cdfbd8cb | ||
![]() |
531970d9c6 | ||
![]() |
5902a8d6a0 | ||
![]() |
9bb2075b77 | ||
![]() |
3c61954971 | ||
![]() |
361a2f7531 | ||
![]() |
ad36695224 | ||
![]() |
92ba7df9c6 | ||
![]() |
ae7b03857e | ||
![]() |
2c0ace4500 | ||
![]() |
759e5dcf5d | ||
![]() |
bf0d9f88e2 | ||
![]() |
bf74ff7057 | ||
![]() |
4328f54270 | ||
![]() |
efd67fb698 | ||
![]() |
44c99dacdf | ||
![]() |
048d4ab3e6 | ||
![]() |
801ca5a788 | ||
![]() |
e5987a6b59 | ||
![]() |
2cb66660c5 | ||
![]() |
c3968ba42e | ||
![]() |
edb86d7781 | ||
![]() |
6785cc1104 | ||
![]() |
ee38614334 | ||
![]() |
9b087f88a7 | ||
![]() |
247b35b254 | ||
![]() |
89c4f16e45 | ||
![]() |
117370efcf | ||
![]() |
66e35c564c | ||
![]() |
36b0285158 | ||
![]() |
07ec56227d | ||
![]() |
cf78f5dc94 | ||
![]() |
daa6bfd460 | ||
![]() |
6a85ac9b93 | ||
![]() |
cc85c556a1 | ||
![]() |
80be4d34af | ||
![]() |
3898b82287 | ||
![]() |
26c64d42e6 | ||
![]() |
168dd18679 | ||
![]() |
b7a6d334e3 | ||
![]() |
f811d21dc7 | ||
![]() |
26bd632bab | ||
![]() |
5020e8b5a0 | ||
![]() |
b5179f3991 | ||
![]() |
86e8b208e0 | ||
![]() |
a7ac30f548 | ||
![]() |
7684a4932d | ||
![]() |
8ae468376f | ||
![]() |
90794c8828 | ||
![]() |
28d1395bef | ||
![]() |
8048fcf5c4 | ||
![]() |
710a8dab4e | ||
![]() |
274b42d225 | ||
![]() |
69d58f4713 | ||
![]() |
d90ccf4277 | ||
![]() |
a1b0ade0a5 | ||
![]() |
f1a5b9163b | ||
![]() |
cb4fc2ac54 |
115 changed files with 10702 additions and 962 deletions
22
.github/ISSUE_TEMPLATE/issue_form.yml
vendored
22
.github/ISSUE_TEMPLATE/issue_form.yml
vendored
|
@ -34,15 +34,15 @@ body:
|
|||
required: true
|
||||
- label: I observed this bug on a clean install of a [supported OS](https://docs.raspap.com/#compatible-operating-systems).
|
||||
required: true
|
||||
- label: I have followed the [project prerequisites](https://docs.raspap.com/#quick-start).
|
||||
- label: I have followed the [project prerequisites](https://docs.raspap.com/quick_start/#quick-install).
|
||||
required: true
|
||||
- label: I have searched this repository for existing issues.
|
||||
required: true
|
||||
- label: I checked the [FAQ](https://docs.raspap.com/faq/) and [official documentation](https://docs.raspap.com/).
|
||||
required: true
|
||||
- label: I am using an [external wireless adapter](https://docs.raspap.com/issues/#external-hardware).
|
||||
required: true
|
||||
- label: I have generated a [RaspAP debug log](https://docs.raspap.com/ap-basics/#debug-log) and performed a [self-diagnosis](https://docs.raspap.com/ap-basics/#diagnosing-problems).
|
||||
required: false
|
||||
- label: I have generated a [RaspAP debug log](https://docs.raspap.com/troubleshooting/#debug-log) and performed a [self-diagnosis](https://docs.raspap.com/troubleshooting/#diagnosing-problems).
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
|
@ -52,18 +52,19 @@ body:
|
|||
options:
|
||||
- Raspberry Pi OS (64-bit) Lite Bookworm
|
||||
- Raspberry Pi OS (32-bit) Lite Bookworm
|
||||
- Raspberry Pi OS (64-bit) Desktop Bookwom
|
||||
- Raspberry Pi OS (64-bit) Lite Bullseye
|
||||
- Raspberry Pi OS (32-bit) Lite Bullseye
|
||||
- Armbian 23.05 (Suni)
|
||||
- Debian Bookworm
|
||||
- Ubuntu Server 23.04 (Lunar)
|
||||
- Debian Bookworm
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: install
|
||||
attributes:
|
||||
label: Quick install or Manual setup?
|
||||
label: Installation method
|
||||
options:
|
||||
- Pre-built image
|
||||
- Quick install
|
||||
- Manual setup
|
||||
validations:
|
||||
|
@ -89,6 +90,7 @@ body:
|
|||
- Raspberry Pi 3 Model B
|
||||
- Raspberry Pi Zero 2 W
|
||||
- Raspberry Pi Zero W
|
||||
- Raspberry Pi Compute Module
|
||||
- Orange Pi family
|
||||
- Other
|
||||
validations:
|
||||
|
@ -98,11 +100,8 @@ body:
|
|||
attributes:
|
||||
label: RaspAP version
|
||||
options:
|
||||
- 3.1.3 (Latest)
|
||||
- 3.1.2
|
||||
- 3.1.1
|
||||
- 3.1.0
|
||||
- Other
|
||||
- Latest
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
@ -115,7 +114,6 @@ body:
|
|||
- Not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
|
|
80
.github/workflows/release.yml
vendored
Normal file
80
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
name: Build and publish RaspAP images
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build-raspap-image:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: "32-bit"
|
||||
pi_gen_version: "master"
|
||||
- arch: "64-bit"
|
||||
pi_gen_version: "arm64"
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Add RaspAP Stage
|
||||
run: |
|
||||
mkdir -p stage-raspap/package-raspap &&
|
||||
{
|
||||
cat > stage-raspap/package-raspap/00-run-chroot.sh <<-EOF
|
||||
#!/bin/bash
|
||||
apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps
|
||||
curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0
|
||||
|
||||
# Set Wi-Fi country to prevent RF kill
|
||||
raspi-config nonint do_wifi_country "US"
|
||||
|
||||
# Fetch RaspAP version and set MOTD
|
||||
RASPAP_VERSION=\$(curl -sL https://install.raspap.com | bash -s -- --version)
|
||||
echo "\$RASPAP_VERSION" | tee /etc/motd
|
||||
EOF
|
||||
} &&
|
||||
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
|
||||
{
|
||||
cat > stage-raspap/prerun.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
if [ ! -d "\${ROOTFS_DIR}" ]; then
|
||||
copy_previous
|
||||
fi
|
||||
EOF
|
||||
} &&
|
||||
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
|
||||
{
|
||||
cat > stage-raspap/prerun.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
if [ ! -d "\${ROOTFS_DIR}" ]; then
|
||||
copy_previous
|
||||
fi
|
||||
EOF
|
||||
} &&
|
||||
chmod +x stage-raspap/prerun.sh
|
||||
|
||||
- name: Build RaspAP Image
|
||||
id: build
|
||||
uses: usimd/pi-gen-action@v1
|
||||
with:
|
||||
image-name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}"
|
||||
enable-ssh: 1
|
||||
stage-list: stage0 stage1 stage2 ./stage-raspap
|
||||
verbose-output: true
|
||||
pi-gen-version: ${{ matrix.pi_gen_version }}
|
||||
pi-gen-repository: RaspAP/pi-gen
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
asset_name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}.img.zip"
|
||||
file: ${{ steps.build.outputs.image-path }}
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.event.inputs.tag || github.ref }}
|
||||
overwrite: true
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,7 +3,7 @@ node_modules
|
|||
yarn-error.log
|
||||
*.swp
|
||||
includes/config.php
|
||||
plugins/
|
||||
rootCA.pem
|
||||
vendor
|
||||
.env
|
||||
locale/**/*.mo
|
||||
|
|
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
[submodule "plugins"]
|
||||
path = plugins
|
||||
url = https://github.com/RaspAP/plugins
|
||||
branch = master
|
|
@ -1,4 +1,4 @@
|
|||
<img width="465" alt="Insiders logo" src="https://user-images.githubusercontent.com/229399/115766971-e19e1900-a3a8-11eb-8c6f-379deb4313d2.png">
|
||||
<img width="465" alt="Insiders logo" src="https://i.imgur.com/62TMUy5.png">
|
||||
|
||||
Development of RaspAP is made possible thanks to a sponsorware release model. This means that new features are first exclusively released to sponsors as part of **Insiders**. Read on to learn how sponsorship works, and how easy it is to get access to Insiders.
|
||||
|
||||
|
@ -20,7 +20,6 @@ The following features are currently available exclusively to sponsors. A tangib
|
|||
✅ [WPA3-Personal AP security](https://docs.raspap.com/ap-basics/#wpa3-personal)
|
||||
✅ [802.11w Protected Management Frames](https://docs.raspap.com/ap-basics/#80211w)
|
||||
✅ [Printable Wi-Fi signs](https://docs.raspap.com/ap-basics/#printable-signs)
|
||||
✅ [Drag & drop dashboard widgets](https://docs.raspap.com/ap-basics/#drag-drop-widgets)
|
||||
✅ [MAC address cloning](https://docs.raspap.com/net-devices/#changing-the-mac-address)
|
||||
✅ [Network diagnostics](https://docs.raspap.com/net-devices/#diagnostics)
|
||||
✅ [WireGuard VPN kill switch](https://docs.raspap.com/wireguard/#kill-switch)
|
||||
|
|
64
README.md
64
README.md
|
@ -1,14 +1,13 @@
|
|||

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

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

|
||||

|
||||
<img width="32.5%" alt="Wifi Client" src="https://github.com/user-attachments/assets/95696ddc-da84-4339-97cc-f2a173054664">
|
||||
<img width="32.5%" alt="Hotspot" src="https://github.com/user-attachments/assets/c1c4de15-3ff2-4d3c-a7af-339c24896749">
|
||||
<img width="32.5%" alt="Adblock" src="https://github.com/user-attachments/assets/ab925687-8407-4bec-a952-9dc6a2675f49">
|
||||
|
@ -18,15 +17,13 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
|
|||
|
||||
## Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick installer](#quick-installer)
|
||||
- [Quick start](#quick-start)
|
||||
- [Join Insiders](#join-insiders)
|
||||
- [WireGuard support](#wireguard-support)
|
||||
- [OpenVPN support](#openvpn-support)
|
||||
- [VPN Provider support](#vpn-provider-support)
|
||||
- [Ad Blocking](#ad-blocking)
|
||||
- [Bridged AP](#bridged-ap)
|
||||
- [Simultaneous AP and Wifi client](#simultaneous-ap-and-wifi-client)
|
||||
- [Manual installation](#manual-installation)
|
||||
- [802.11ac 5GHz support](#80211ac-5ghz-support)
|
||||
- [Supported operating systems](#supported-operating-systems)
|
||||
|
@ -38,30 +35,43 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
|
|||
- [Reporting issues](#reporting-issues)
|
||||
- [License](#license)
|
||||
|
||||
## Prerequisites
|
||||
Start with a clean install of the [latest release of Raspberry Pi OS Lite](https://www.raspberrypi.com/software/operating-systems/). Both the 32- and 64-bit Lite versions are supported. The Raspberry Pi OS desktop distro is [unsupported](https://docs.raspap.com/faq/#distros).
|
||||
## Quick start
|
||||
RaspAP gives you two different ways to get up and running quickly. The simplest and recommended approach is to use a custom Raspberry Pi OS image with RaspAP preinstalled. This option eliminates guesswork and gives you a base upon which to build. Alternatively, you may execute the Quick installer on an existing [compatible OS](https://docs.raspap.com/#compatible-operating-systems).
|
||||
|
||||
### Pre-built image
|
||||
Custom Raspberry Pi OS Lite images with the latest RaspAP are available for [direct download](https://github.com/RaspAP/raspap-webgui/releases/latest). This includes both 32- and 64-bit builds for ARM architectures.
|
||||
|
||||
| Operating system | Debian version | Kernel version | RaspAP version | Size |
|
||||
| ---------------------| ---------------|-----------------|----------------|-------|
|
||||
| Raspberry Pi OS (64-bit) Lite | 12 (bookworm) | 6.6 | Latest | 777 MB|
|
||||
| Raspberry Pi OS (32-bit) Lite | 12 (bookworm) | 6.6 | Latest | 805 MB|
|
||||
|
||||
These images are automatically generated with each release of RaspAP. You may choose between an `arm64` or `armhf` (32-bit) based build. Refer to [this resource](https://www.raspberrypi.com/software/operating-systems/) to ensure compatibility with your hardware.
|
||||
|
||||
After downloading your desired image from the [latest release page](https://github.com/RaspAP/raspap-webgui/releases/latest), use a utility such as the Raspberry Pi Imager or [balenaEtcher](https://www.balena.io/etcher) to flash the OS image onto a microSD card. Insert the card into your device and boot it up. The latest RaspAP release version with the most popular optional components will be active and ready for you to configure.
|
||||
|
||||
### Quick installer
|
||||
Alternatively, start with a clean install of a [latest release of Raspberry Pi OS](https://www.raspberrypi.org/software/operating-systems/). Both the 32- and 64-bit release versions are supported, as well as the latest 64-bit Desktop distribution.
|
||||
|
||||
Update RPi OS to its latest version, including the kernel and firmware, followed by a reboot:
|
||||
|
||||
1. Update Raspbian, including the kernel and firmware, followed by a reboot:
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get full-upgrade
|
||||
sudo reboot
|
||||
```
|
||||
2. Set the "WLAN country" option in `raspi-config`'s **Localisation Options**: `sudo raspi-config`
|
||||
Set the WiFi country in raspi-config's **Localisation Options**: `sudo raspi-config`.
|
||||
|
||||
3. If you have a device without an onboard wireless chipset, the [**Edimax Wireless 802.11b/g/n nano USB adapter**](https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_n150/ew-7811un) is an excellent option – it's small, cheap and has good driver support.
|
||||
|
||||
With the prerequisites done, you can proceed with either the Quick installer or Manual installation steps below.
|
||||
|
||||
## Quick installer
|
||||
Install RaspAP from your device's shell prompt:
|
||||
```sh
|
||||
curl -sL https://install.raspap.com | bash
|
||||
```
|
||||
The [installer](https://docs.raspap.com/quick/) will complete the steps in the manual installation (below) for you.
|
||||
|
||||
After the reboot at the end of the installation the wireless network will be
|
||||
configured as an access point as follows:
|
||||
The Quick installer will respond to several [command line arguments](https://docs.raspap.com/quick/), or switches, to customize your installation in a variety of ways, or install one of RaspAP's optional helper tools.
|
||||
|
||||
### Initial settings
|
||||
After completing either of these setup options, the wireless AP network will be configured as follows:
|
||||
|
||||
* IP address: 10.3.141.1
|
||||
* Username: admin
|
||||
* Password: secret
|
||||
|
@ -69,7 +79,7 @@ configured as an access point as follows:
|
|||
* SSID: `raspi-webgui`
|
||||
* Password: ChangeMe
|
||||
|
||||
**Note:** As the name suggests, the Quick Installer is a great way to quickly setup a new AP. However, it does not automagically detect the unique configuration of your system. Best results are obtained by connecting to ethernet (`eth0`) or as a WiFi client, also known as managed mode, with `wlan0`. For the latter, refer to [this FAQ](https://docs.raspap.com/faq/#headless). Special instructions for the Pi Zero W are [available here](https://docs.raspap.com/ap-sta/).
|
||||
It's _strongly recommended_ that your first post-install action is to change the default admin [authentication](https://docs.raspap.com/authentication/) settings. Thereafter, your AP's [basic settings](https://docs.raspap.com/ap-basics/) and many [advanced options](https://docs.raspap.com/ap-basics#advanced-options) are now ready to be modified by RaspAP.
|
||||
|
||||
Please [read this](https://docs.raspap.com/issues/) before reporting an issue.
|
||||
|
||||
|
@ -118,11 +128,6 @@ By default RaspAP configures a routed AP for your clients to connect to. A bridg
|
|||
|
||||
More information on Bridged AP mode is provided [in our documentation](https://docs.raspap.com/bridged/).
|
||||
|
||||
## Simultaneous AP and Wifi client
|
||||
RaspAP lets you create an AP with a Wifi client configuration, often called [AP-STA mode](https://docs.raspap.com/ap-sta/). With your system configured in managed mode, enable the AP from the **Advanced** tab of **Configure hotspot** by sliding the **Wifi client AP mode** toggle. Save settings and start the hotspot. The managed mode AP is functional without restart.
|
||||
|
||||
**Note:** This option is disabled until you configure your system as a wireless client. For a device operating in [managed mode](https://docs.raspap.com/faq/#headless) without an `eth0` connection, this configuration must be enabled [_before_ a reboot](https://docs.raspap.com/ap-sta/).
|
||||
|
||||
## Manual installation
|
||||
Detailed manual setup instructions are provided [on our documentation site](https://docs.raspap.com/manual/).
|
||||
|
||||
|
@ -139,11 +144,10 @@ RaspAP was originally made for Raspbian, but now also installs on the following
|
|||
| Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official |
|
||||
| Raspberry Pi OS | (64-bit) Lite Bullseye | ARM | Official |
|
||||
| Raspberry Pi OS | (32-bit) Lite Bullseye | ARM | Official |
|
||||
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Official |
|
||||
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Beta |
|
||||
| Debian | Bookworm | ARM / x86_64 | Beta |
|
||||
| Ubuntu | Server 23.04 (Lunar) | ARM / x86_64 | Beta |
|
||||
|
||||
<img src="https://github.com/RaspAP/raspap-webgui/assets/229399/6fe62f2d-631a-46c9-8ceb-83ebf0ade6a9" style="width:640px;" />
|
||||
<img src="https://i.imgur.com/XiAJNKb.png" style="width:480px;" />
|
||||
|
||||
You are also encouraged to use RaspAP's community-led [Docker container](#docker-support). Please note that "supported" is not a guarantee. If you are able to improve support for your preferred distro, we encourage you to [actively contribute](#how-to-contribute) to the project.
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['blocklist_id'])) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$interface = filter_input(INPUT_GET, 'inet', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (filter_input(INPUT_GET, 'tu') == 'h') {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
|
|
|
@ -1,36 +1,29 @@
|
|||
<?php
|
||||
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$return = 0;
|
||||
$path = "../../config";
|
||||
$configs = array(
|
||||
array("src" => $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
|
||||
array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
|
||||
array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
|
||||
array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
|
||||
);
|
||||
$return = 0;
|
||||
$path = "../../config";
|
||||
$configs = array(
|
||||
array("src" => $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
|
||||
array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
|
||||
array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
|
||||
array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
|
||||
);
|
||||
|
||||
foreach ($configs as $config) {
|
||||
try {
|
||||
$tmp = file_get_contents($config["src"]);
|
||||
file_put_contents($config["tmp"], $tmp);
|
||||
system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
|
||||
} catch (Exception $e) {
|
||||
$return = $e->getCode();
|
||||
}
|
||||
foreach ($configs as $config) {
|
||||
try {
|
||||
$tmp = file_get_contents($config["src"]);
|
||||
file_put_contents($config["tmp"], $tmp);
|
||||
system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
|
||||
} catch (Exception $e) {
|
||||
$return = $e->getCode();
|
||||
}
|
||||
$jsonData = ['return'=>$return];
|
||||
echo json_encode($jsonData);
|
||||
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$jsonData = ['return'=>$return];
|
||||
echo json_encode($jsonData);
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
exec("ls /sys/class/net | grep -v lo", $interfaces);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
|
||||
|
@ -17,3 +17,4 @@ foreach ($hostapdconfig as $hostapdconfigline) {
|
|||
};
|
||||
$channel = intval($arrConfig['channel']);
|
||||
echo json_encode($channel);
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../src/RaspAP/Parsers/IwParser.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['interface'])) {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/functions.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['interface'])) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
$interface = $_POST['iface'];
|
||||
|
||||
|
@ -18,14 +19,14 @@ if (isset($interface)) {
|
|||
} else {
|
||||
$arrRange = explode(",", $conf['dhcp-range'][0]);
|
||||
}
|
||||
$dhcpdata['RangeStart'] = $arrRange[0];
|
||||
$dhcpdata['RangeEnd'] = $arrRange[1];
|
||||
$dhcpdata['RangeMask'] = $arrRange[2];
|
||||
$dhcpdata['leaseTime'] = $arrRange[3];
|
||||
$dhcpHost = $conf["dhcp-host"];
|
||||
$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']) ? $conf['server'] : [ $conf['server'] ];
|
||||
$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);
|
||||
|
@ -35,10 +36,10 @@ if (isset($interface)) {
|
|||
$arrDns = explode(",", $conf['dhcp-option']);
|
||||
if ($arrDns[0] == '6') {
|
||||
if (count($arrDns) > 1) {
|
||||
$dhcpdata['DNS1'] = $arrDns[1];
|
||||
$dhcpdata['DNS1'] = $arrDns[1] ?? null;
|
||||
}
|
||||
if (count($arrDns) > 2) {
|
||||
$dhcpdata['DNS2'] = $arrDns[2];
|
||||
$dhcpdata['DNS2'] = $arrDns[2] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,13 +54,15 @@ if (isset($interface)) {
|
|||
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];
|
||||
$dhcpdata['StaticIP'] = strpos($static_ip[1],'/') ? substr($static_ip[1], 0, strpos($static_ip[1],'/')) : $static_ip[1];
|
||||
$dhcpdata['SubnetMask'] = cidr2mask($static_ip[1]);
|
||||
$dhcpdata['StaticRouters'] = $static_routers[1];
|
||||
$dhcpdata['StaticDNS'] = $static_dns[1];
|
||||
$dhcpdata['Metric'] = $metric[1] ?? null;
|
||||
$dhcpdata['StaticIP'] = isset($static_ip[1]) && strpos($static_ip[1], '/') !== false
|
||||
? substr($static_ip[1], 0, strpos($static_ip[1], '/'))
|
||||
: ($static_ip[1] ?? '');
|
||||
$dhcpdata['SubnetMask'] = cidr2mask($static_ip[1] ?? '');
|
||||
$dhcpdata['StaticRouters'] = $static_routers[1] ?? null;
|
||||
$dhcpdata['StaticDNS'] = $static_dns[1] ?? null;
|
||||
$dhcpdata['FallbackEnabled'] = empty($fallback) ? false: true;
|
||||
$dhcpdata['DefaultRoute'] = $gateway[0] == "gateway";
|
||||
$dhcpdata['NoHookWPASupplicant'] = $nohook_wpa_supplicant[0] == "nohook wpa_supplicant";
|
||||
$dhcpdata['NoHookWPASupplicant'] = ($nohook_wpa_supplicant[0] ?? '') == "nohook wpa_supplicant";
|
||||
echo json_encode($dhcpdata);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/locale.php';
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
// fetch wg client.conf
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$entity = escapeshellcmd($_POST['entity']);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/defaults.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
|
|
26
ajax/plugins/do_plugin_install.php
Executable file
26
ajax/plugins/do_plugin_install.php
Executable file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
|
||||
$plugin_uri = $_POST['plugin_uri'] ?? null;
|
||||
$plugin_version = $_POST['plugin_version'] ?? null;
|
||||
$install_path = $_POST['install_path'] ?? null;
|
||||
|
||||
if (isset($plugin_uri, $plugin_version, $install_path)) {
|
||||
try {
|
||||
$return = $pluginInstaller->installPlugin($plugin_uri, $plugin_version, $install_path);
|
||||
echo json_encode($return);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(422); // unprocessable content
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
} else {
|
||||
http_response_code(400); // Bad Request
|
||||
echo json_encode(['error' => 'Plugin URI, version, and install path are required']);
|
||||
exit;
|
||||
}
|
||||
|
31
ajax/session/do_check_session.php
Executable file
31
ajax/session/do_check_session.php
Executable file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$lastActivity = $_SESSION['lastActivity'] ?? time();
|
||||
$sessionLifetime = time() - $lastActivity;
|
||||
$status = $sessionLifetime >= RASPI_SESSION_TIMEOUT ? 'session_expired' : 'active';
|
||||
|
||||
if ($status === 'session_expired') {
|
||||
session_unset(); // unset all session variables
|
||||
session_destroy(); // destroy the session
|
||||
}
|
||||
|
||||
// send response
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
$response = [
|
||||
'status' => $status,
|
||||
'last_activity' => $lastActivity,
|
||||
'session_lifetime' => $sessionLifetime
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
exit();
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$action = escapeshellcmd($_POST['a']);
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/defaults.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$uri = RASPI_API_ENDPOINT;
|
||||
preg_match('/(\d+(\.\d+)+)/', RASPI_VERSION, $matches);
|
||||
$thisRelease = $matches[0];
|
||||
$uri = RASPI_API_ENDPOINT;
|
||||
preg_match('/(\d+(\.\d+)+)/', RASPI_VERSION, $matches);
|
||||
$thisRelease = $matches[0];
|
||||
|
||||
$json = shell_exec("wget --timeout=5 --tries=1 $uri -qO -");
|
||||
$data = json_decode($json, true);
|
||||
$tagName = $data['tag_name'];
|
||||
$updateAvailable = checkReleaseVersion($thisRelease, $tagName);
|
||||
$json = shell_exec("wget --timeout=5 --tries=1 $uri -qO -");
|
||||
$data = json_decode($json, true);
|
||||
$tagName = $data['tag_name'];
|
||||
$updateAvailable = checkReleaseVersion($thisRelease, $tagName);
|
||||
|
||||
$response['tag'] = $tagName;
|
||||
$response['update'] = $updateAvailable;
|
||||
echo json_encode($response);
|
||||
$response['tag'] = $tagName;
|
||||
$response['update'] = $updateAvailable;
|
||||
echo json_encode($response);
|
||||
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$root = getenv("DOCUMENT_ROOT");
|
||||
exec('sudo '.RASPI_CONFIG.'/system/debuglog.sh -i '.$root, $return);
|
||||
$root = getenv("DOCUMENT_ROOT");
|
||||
exec('sudo '.RASPI_CONFIG.'/system/debuglog.sh -i '.$root, $return);
|
||||
|
||||
$logOutput = implode(PHP_EOL, $return);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$filePath = $tempDir . DIRECTORY_SEPARATOR . RASPI_DEBUG_LOG;
|
||||
$handle = fopen($filePath, "w");
|
||||
fwrite($handle, $logOutput);
|
||||
fclose($handle);
|
||||
echo json_encode($filePath);
|
||||
|
||||
$logOutput = implode(PHP_EOL, $return);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$filePath = $tempDir . DIRECTORY_SEPARATOR . RASPI_DEBUG_LOG;
|
||||
$handle = fopen($filePath, "w");
|
||||
fwrite($handle, $logOutput);
|
||||
fclose($handle);
|
||||
echo json_encode($filePath);
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$tempDir = sys_get_temp_dir();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
|
@ -11,7 +11,7 @@ if (isset($_POST['csrf_token'])) {
|
|||
}
|
||||
// set installer path + options
|
||||
$path = getenv("DOCUMENT_ROOT");
|
||||
$opts = " --update --yes --path $path";
|
||||
$opts = " --update --yes --check 0 --path $path";
|
||||
$installer = "sudo /etc/raspap/system/raspbian.sh";
|
||||
$execUpdate = $installer.$opts;
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$logFile = '/tmp/raspap_install.log';
|
||||
|
|
351
app/css/all.css
351
app/css/all.css
|
@ -9,7 +9,9 @@ License: GNU General Public License v3.0
|
|||
:root {
|
||||
--raspap-content-main: #495057;
|
||||
--raspap-text-muted: #858796;
|
||||
--raspap-text-light: #999999;
|
||||
--raspap-brand-color: #2b8080;
|
||||
--raspap-offwhite: #faf9f6;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -167,12 +169,7 @@ th {
|
|||
}
|
||||
|
||||
canvas#divDBChartBandwidthhourly {
|
||||
height: 350px!important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 150px;
|
||||
width: 200px;
|
||||
height: 509px!important;
|
||||
}
|
||||
|
||||
.dbChart {
|
||||
|
@ -189,7 +186,7 @@ canvas#divDBChartBandwidthhourly {
|
|||
}
|
||||
|
||||
.check-progress {
|
||||
color: #999;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.fa-check {
|
||||
|
@ -306,16 +303,16 @@ button > i.fas {
|
|||
|
||||
/* Font Awesome 5 brands */
|
||||
.fa-reddit {
|
||||
color: #ff4500;
|
||||
color: #ff4500;
|
||||
}
|
||||
.fa-twitter {
|
||||
color: #55acee
|
||||
color: #55acee
|
||||
}
|
||||
.fa-discord {
|
||||
color: #7289da
|
||||
color: #7289da
|
||||
}
|
||||
.fa-github {
|
||||
color: #151b23
|
||||
color: #151b23
|
||||
}
|
||||
|
||||
@keyframes heart {
|
||||
|
@ -332,3 +329,335 @@ button > i.fas {
|
|||
animation: heart 1000ms infinite;
|
||||
}
|
||||
|
||||
#modal-admin-login .modal-content {
|
||||
background: radial-gradient(circle at 120% -20%, #032626, #052c2c, #073232, #0a3838, #0d3f3f, #114545, #144c4c);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#modal-admin-login .modal-body {
|
||||
min-width: 330px;
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
color: var(--raspap-theme-color);
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
.admin-login {
|
||||
color: var(--raspap-offwhite);
|
||||
font-size: 1.2em
|
||||
}
|
||||
|
||||
.btn-admin-login {
|
||||
color: var(--raspap-offwhite);
|
||||
background-color: var(--raspap-theme-color);
|
||||
}
|
||||
|
||||
.btn-admin-login:hover {
|
||||
color: var(--raspap-offwhite);
|
||||
background-color: #236969;
|
||||
}
|
||||
|
||||
.no-right-radius {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.btn-passwd-append {
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
#passwd-toggle:active,
|
||||
#passwd-toggle:hover,
|
||||
#passwd-toggle:focus {
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
textarea.plugin-log {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
resize: none;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
background-color: #f8f9fa;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.connections-left,
|
||||
.connections-right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.connection-item {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
z-index: 5;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.connection-right {
|
||||
align-items: center;
|
||||
margin-left: 10rem;
|
||||
}
|
||||
|
||||
.connections-left i {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.connections-left i:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.connections-left i:last-child {
|
||||
margin-bottom: 0;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.center-device {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.center-device-top {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.client-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row-reverse;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.client-count {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clients-status {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.dashed-lines,
|
||||
.solid-lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 1rem;
|
||||
left: 112px;
|
||||
}
|
||||
|
||||
.dashed-lines-right,
|
||||
.solid-lines-right {
|
||||
left: -80px;
|
||||
}
|
||||
|
||||
.solid-lines, .solid-lines-right {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dashed-lines, .dashed-lines-right {
|
||||
z-index 0;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
.wifi-bands {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.band {
|
||||
padding: 0.25rem 1rem;
|
||||
border: 2px solid var(--raspap-text-light);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
font-weight: 600;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.band.active {
|
||||
border-color: var(--raspap-theme-color);
|
||||
color: var(--raspap-theme-color);
|
||||
}
|
||||
|
||||
.device-label {
|
||||
font-size: 1.3rem;
|
||||
text-align: center;
|
||||
color: var(--raspap-theme-color);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.3rem;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-item .fa-stack {
|
||||
width: 1.5em!important;
|
||||
}
|
||||
|
||||
.connection-item>i {
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.connection-item .fa-stack {
|
||||
min-width: 2.5em;
|
||||
}
|
||||
|
||||
.connections-left>.connection-item>span {
|
||||
color: var(--raspap-text-light);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: var(--raspap-text-light)!important;
|
||||
}
|
||||
|
||||
a.inactive:hover,
|
||||
a.inactive:focus {
|
||||
color: var(--raspap-text-light) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.connection-item a > span:not(.fa-stack) {
|
||||
display: none!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.connections-right,
|
||||
.connections-left {
|
||||
display: none!important;
|
||||
}
|
||||
.dashboard-container {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
.device-status {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.clients-mobile {
|
||||
display: flex!important;
|
||||
flex-direction: row!important;
|
||||
}
|
||||
}
|
||||
.connection-item.active > span {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.connection-item.active > i {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.status-item.active > span {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.status-item.active > i {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
|
||||
.clients-mobile {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.client-type {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.client-type i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--raspap-theme-color);
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid var(--raspap-theme-color);
|
||||
}
|
||||
|
||||
.client-type i.badge-icon {
|
||||
font-size: 0.7rem;
|
||||
background: var(--raspap-theme-color);
|
||||
color: var(--raspap-offwhite);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.client-count {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: var(--raspap-theme-color);
|
||||
color: var(--raspap-offwhite);
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.device-illustration {
|
||||
min-width: 220px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
|
|
54
app/img/dashed.svg
Normal file
54
app/img/dashed.svg
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 227 596" style="enable-background:new 0 0 227 596;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#999999;stroke-width:3;}
|
||||
.st1{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:6.0204,3.0102;}
|
||||
.st2{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:5.7963,2.8981;}
|
||||
</style>
|
||||
<g id="dashed">
|
||||
<g id="Line_1">
|
||||
<g>
|
||||
<line class="st0" x1="112.8" y1="0" x2="112.8" y2="3"/>
|
||||
<line class="st1" x1="112.8" y1="6" x2="112.8" y2="591.5"/>
|
||||
<line class="st0" x1="112.8" y1="593" x2="112.8" y2="596"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_2">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="0.8" x2="110.2" y2="0.8"/>
|
||||
<line class="st2" x1="107.3" y1="0.8" x2="4.4" y2="0.8"/>
|
||||
<line class="st0" x1="3" y1="0.8" x2="0" y2="0.8"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_3">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="198.9" x2="110.2" y2="198.9"/>
|
||||
<line class="st2" x1="107.3" y1="198.9" x2="4.4" y2="198.9"/>
|
||||
<line class="st0" x1="3" y1="198.9" x2="0" y2="198.9"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_4">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="397.1" x2="110.2" y2="397.1"/>
|
||||
<line class="st2" x1="107.3" y1="397.1" x2="4.4" y2="397.1"/>
|
||||
<line class="st0" x1="3" y1="397.1" x2="0" y2="397.1"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_5">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="595.2" x2="110.2" y2="595.2"/>
|
||||
<line class="st2" x1="107.3" y1="595.2" x2="4.4" y2="595.2"/>
|
||||
<line class="st0" x1="3" y1="595.2" x2="0" y2="595.2"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Line_6">
|
||||
<g>
|
||||
<line class="st0" x1="226.2" y1="297.8" x2="223.2" y2="297.8"/>
|
||||
<line class="st2" x1="220.3" y1="297.8" x2="117.4" y2="297.8"/>
|
||||
<line class="st0" x1="116" y1="297.8" x2="113" y2="297.8"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
1083
app/img/devices/compute.php
Normal file
1083
app/img/devices/compute.php
Normal file
File diff suppressed because it is too large
Load diff
4450
app/img/devices/default.php
Normal file
4450
app/img/devices/default.php
Normal file
File diff suppressed because it is too large
Load diff
1561
app/img/devices/zero.php
Normal file
1561
app/img/devices/zero.php
Normal file
File diff suppressed because it is too large
Load diff
0
app/img/raspAP-logo.php
Normal file → Executable file
0
app/img/raspAP-logo.php
Normal file → Executable file
40
app/img/right-dashed.svg
Normal file
40
app/img/right-dashed.svg
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 314 594" style="enable-background:new 0 0 314 594;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#999999;stroke-width:3;}
|
||||
.st1{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:6.04,3.02;}
|
||||
.st2{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:5.7963,2.8981;}
|
||||
</style>
|
||||
<g id="dashed">
|
||||
<g id="horizontal">
|
||||
<g>
|
||||
<line class="st0" x1="113.2" y1="144" x2="113.2" y2="147"/>
|
||||
<line class="st1" x1="113.2" y1="150" x2="113.3" y2="447.5"/>
|
||||
<line class="st0" x1="113.3" y1="449" x2="113.3" y2="452"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="top">
|
||||
<g>
|
||||
<line class="st0" x1="114" y1="144.8" x2="117" y2="144.8"/>
|
||||
<line class="st2" x1="119.9" y1="144.8" x2="222.8" y2="144.8"/>
|
||||
<line class="st0" x1="224.2" y1="144.8" x2="227.2" y2="144.8"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="out">
|
||||
<g>
|
||||
<line class="st0" x1="0" y1="297.8" x2="3" y2="297.8"/>
|
||||
<line class="st2" x1="5.9" y1="297.8" x2="108.8" y2="297.8"/>
|
||||
<line class="st0" x1="110.2" y1="297.8" x2="113.2" y2="297.8"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="bottom">
|
||||
<g>
|
||||
<line class="st0" x1="113" y1="450.8" x2="116" y2="450.8"/>
|
||||
<line class="st2" x1="118.9" y1="450.8" x2="221.8" y2="450.8"/>
|
||||
<line class="st0" x1="223.2" y1="450.8" x2="226.2" y2="450.8"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
46
app/img/right-solid.php
Normal file
46
app/img/right-solid.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
header("Content-Type: image/svg+xml");
|
||||
$showDevice1 = isset($_GET['device-1']);
|
||||
$showOut = isset($_GET['out']);
|
||||
$showDevice2 = isset($_GET['device-2']);
|
||||
?>
|
||||
|
||||
<svg width="313" height="594" viewBox="0 0 313 594" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Frame 1">
|
||||
<g id="right connection frame">
|
||||
<g id="solid">
|
||||
<?php if ($showDevice2): ?>
|
||||
<line id="joint-device-2" y1="-0.75" x2="154" y2="-0.75"
|
||||
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 297)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showDevice1): ?>
|
||||
<line id="joint-device-1" style="display: inline;"
|
||||
y1="-0.75" x2="154" y2="-0.75"
|
||||
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 144)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
|
||||
<line id="device-1" style="display: inline;"
|
||||
y1="-0.75" x2="113.231" y2="-0.75"
|
||||
transform="matrix(1 8.74228e-08 8.74228e-08 -1 114 144)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showOut): ?>
|
||||
<line id="out" style="display: inline;"
|
||||
y1="-0.75" x2="113.231" y2="-0.75"
|
||||
transform="matrix(1 8.74228e-08 8.74228e-08 -1 -0.000305176 297)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showDevice2): ?>
|
||||
<line id="device-2" style="display: inline;"
|
||||
y1="-0.75" x2="113.231" y2="-0.75"
|
||||
transform="matrix(1 8.74228e-08 8.74228e-08 -1 113 450)"
|
||||
stroke="#008281" stroke-width="4"/>
|
||||
<?php endif; ?>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
65
app/img/solid.php
Normal file
65
app/img/solid.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
header("Content-Type: image/svg+xml");
|
||||
|
||||
require_once '../../includes/functions.php';
|
||||
$color = getColorOpt();
|
||||
|
||||
$showJoint = isset($_GET['joint']);
|
||||
$showDevice1 = isset($_GET['device-1']);
|
||||
$showOut = isset($_GET['out']);
|
||||
$showDevice2 = isset($_GET['device-2']);
|
||||
$showDevice3 = isset($_GET['device-3']);
|
||||
$showDevice4 = isset($_GET['device-4']);
|
||||
?>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="227" height="596" viewBox="0 0 227 596" fill="none">
|
||||
<?php
|
||||
// Device positions array (y-coordinates)
|
||||
$devicePositions = [
|
||||
'device-1' => 0.75,
|
||||
'out' => 297.75,
|
||||
'device-2' => 198.75,
|
||||
'device-3' => 397.058,
|
||||
'device-4' => 595.211
|
||||
];
|
||||
|
||||
// Calculate joint line segments
|
||||
if ($showJoint) {
|
||||
$activeDevices = array_filter([$showDevice1, $showDevice2, $showDevice3, $showDevice4]);
|
||||
$activeYs = [];
|
||||
|
||||
foreach ($devicePositions as $device => $y) {
|
||||
if (isset($_GET[$device])) {
|
||||
$activeYs[] = $y;
|
||||
}
|
||||
}
|
||||
|
||||
// Add top/bottom if first/last device is connected
|
||||
if ($showDevice1) array_unshift($activeYs, 0);
|
||||
if ($showDevice4) $activeYs[] = 596;
|
||||
|
||||
// Draw segments between consecutive points
|
||||
for ($i = 1; $i < count($activeYs); $i++) {
|
||||
$y1 = $activeYs[$i-1];
|
||||
$y2 = $activeYs[$i];
|
||||
echo "<line x1='112.75' y1='$y1' x2='112.75' y2='$y2' stroke='$color' stroke-width='4'/>";
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?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"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showOut): ?>
|
||||
<line x1="226.231" y1="297.75" x2="113" y2="297.75" stroke="<?php echo $color; ?>" stroke-width="4" id="out"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDevice2): ?>
|
||||
<line x1="113.231" y1="198.75" x2="7.69496e-06" y2="198.75" stroke="<?php echo $color; ?>" stroke-width="4" id="device-2"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDevice3): ?>
|
||||
<line x1="113.231" y1="397.058" x2="7.69496e-06" y2="397.058" stroke="<?php echo $color; ?>" stroke-width="4" id="device-3"/>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDevice4): ?>
|
||||
<line x1="113.231" y1="595.211" x2="7.69496e-06" y2="595.211" stroke="<?php echo $color; ?>" stroke-width="4" id="device-4"/>
|
||||
<?php endif; ?>
|
||||
</svg>
|
29
app/img/uri-qr-code.php
Executable file
29
app/img/uri-qr-code.php
Executable file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
if (!isset($_GET['uri']) || !filter_var($_GET['uri'], FILTER_VALIDATE_URL)) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
exit("Invalid or missing URI parameter");
|
||||
}
|
||||
|
||||
$uri = $_GET['uri'];
|
||||
$command = "qrencode -t svg -m 0 -o - " . escapeshellarg($uri);
|
||||
|
||||
$svg = shell_exec($command);
|
||||
if ($svg === null) {
|
||||
error_log("QR generation failed for URI: $uri");
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
exit("Failed to generate QR code");
|
||||
}
|
||||
|
||||
$etag = hash('sha256', $uri);
|
||||
$content_length = strlen($svg);
|
||||
$last_modified = gmdate("D, d M Y H:i:s") . " GMT";
|
||||
|
||||
header("Content-Type: image/svg+xml");
|
||||
header("Content-Length: $content_length");
|
||||
header("Last-Modified: $last_modified");
|
||||
header("ETag: \"$etag\"");
|
||||
header("X-QR-Code-Content: " . htmlspecialchars($uri, ENT_QUOTES, 'UTF-8'));
|
||||
|
||||
echo $svg;
|
||||
|
3
app/img/wg-qr-code.php
Normal file → Executable file
3
app/img/wg-qr-code.php
Normal file → Executable file
|
@ -13,6 +13,7 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
|
|||
exec("sudo cat " .RASPI_WIREGUARD_PATH.'client.conf', $return);
|
||||
$peer_conf = implode(PHP_EOL,$return);
|
||||
$peer_conf.= PHP_EOL;
|
||||
$peer_conf_sanitized = str_replace(["\r", "\n"], '', $peer_conf);
|
||||
$command = "qrencode -t svg -m 0 -o - " . mb_escapeshellarg($peer_conf);
|
||||
$svg = shell_exec($command);
|
||||
$etag = hash('sha256', $peer_conf);
|
||||
|
@ -23,6 +24,6 @@ header("Content-Type: image/svg+xml");
|
|||
header("Content-Length: $content_length");
|
||||
header("Last-Modified: $last_modified");
|
||||
header("ETag: \"$etag\"");
|
||||
header("X-QR-Code-Content: $peer_conf");
|
||||
header("X-QR-Code-Content: $peer_conf_sanitized");
|
||||
echo shell_exec($command);
|
||||
|
||||
|
|
8
app/img/wifi-qr-code.php
Normal file → Executable file
8
app/img/wifi-qr-code.php
Normal file → Executable file
|
@ -12,12 +12,12 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
|
|||
|
||||
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
|
||||
|
||||
// assume wpa encryption and get the passphrase
|
||||
// assume WPA encryption and get the passphrase
|
||||
$type = "WPA";
|
||||
$password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase'];
|
||||
|
||||
// use wep if configured
|
||||
$wep_default_key = intval($hostapd['wep_default_key']);
|
||||
// use WEP if configured
|
||||
$wep_default_key = intval($hostapd['wep_default_key'] ?? 0);
|
||||
$wep_key = 'wep_key' . $wep_default_key;
|
||||
if (array_key_exists($wep_key, $hostapd)) {
|
||||
$type = "WEP";
|
||||
|
@ -30,7 +30,7 @@ if (empty($password)) {
|
|||
}
|
||||
|
||||
$ssid = $hostapd['ssid'];
|
||||
$hidden = intval($hostapd['ignore_broadcast_ssid']) != 0 ? "H:true" : "";
|
||||
$hidden = intval($hostapd['ignore_broadcast_ssid'] ?? 0) !== 0 ? "H:true" : "";
|
||||
|
||||
$ssid = qr_encode($ssid);
|
||||
$password = qr_encode($password);
|
||||
|
|
294
app/js/custom.js
294
app/js/custom.js
|
@ -332,47 +332,42 @@ $('#performupdateModal').on('shown.bs.modal', function (e) {
|
|||
});
|
||||
|
||||
function fetchUpdateResponse() {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const complete = 6;
|
||||
const error = 7;
|
||||
let phpFile = 'ajax/system/sys_read_logfile.php';
|
||||
|
||||
$.ajax({
|
||||
url: phpFile,
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
let endPolling = false;
|
||||
success: function(response) {
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
let divId = '#updateStep' + i;
|
||||
if (response.includes(i.toString())) {
|
||||
$(divId).removeClass('invisible');
|
||||
}
|
||||
if (response.includes(complete)) {
|
||||
var successMsg = $('#successMsg').data('message');
|
||||
$('#updateMsg').after('<span class="small">' + successMsg + '</span>');
|
||||
$('#updateMsg').addClass('fa-check');
|
||||
$('#updateMsg').removeClass('invisible');
|
||||
$('#updateStep6').removeClass('invisible');
|
||||
$('#updateSync2').removeClass("fa-spin");
|
||||
$('#updateOk').removeAttr('disabled');
|
||||
endPolling = true;
|
||||
break;
|
||||
} else if (response.includes(error)) {
|
||||
var errorMsg = $('#errorMsg').data('message');
|
||||
$('#updateMsg').after('<span class="small">' + errorMsg + '</span>');
|
||||
$('#updateMsg').addClass('fa-times');
|
||||
$('#updateMsg').removeClass('invisible');
|
||||
$('#updateSync2').removeClass("fa-spin");
|
||||
$('#updateOk').removeAttr('disabled');
|
||||
endPolling = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!endPolling) {
|
||||
// check if the update is complete or if there's an error
|
||||
if (response.includes(complete)) {
|
||||
var successMsg = $('#successMsg').data('message');
|
||||
$('#updateMsg').after('<span class="small">' + successMsg + '</span>');
|
||||
$('#updateMsg').addClass('fa-check');
|
||||
$('#updateMsg').removeClass('invisible');
|
||||
$('#updateStep6').removeClass('invisible');
|
||||
$('#updateSync2').removeClass("fa-spin");
|
||||
$('#updateOk').removeAttr('disabled');
|
||||
} else if (response.includes(error)) {
|
||||
var errorMsg = $('#errorMsg').data('message');
|
||||
$('#updateMsg').after('<span class="small">' + errorMsg + '</span>');
|
||||
$('#updateMsg').addClass('fa-times');
|
||||
$('#updateMsg').removeClass('invisible');
|
||||
$('#updateSync2').removeClass("fa-spin");
|
||||
$('#updateOk').removeAttr('disabled');
|
||||
} else {
|
||||
setTimeout(fetchUpdateResponse, 500);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error(error);
|
||||
console.error("AJAX Error:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -468,6 +463,139 @@ $('#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) {
|
||||
var button = $('#install-user-plugin').data('button');
|
||||
var manifestData = button.data('plugin-manifest');
|
||||
var installPath = manifestData.install_path;
|
||||
var pluginUri = manifestData.plugin_uri;
|
||||
var pluginVersion = manifestData.version;
|
||||
var pluginConfirm = $('#js-install-plugin-confirm').text();
|
||||
var progressText = $('#js-install-plugin-confirm').attr('data-message');
|
||||
var successHtml = $('#plugin-install-message').attr('data-message');
|
||||
var successText = $('<div>').text(successHtml).text();
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
|
||||
if (pluginConfirm === 'Install now') {
|
||||
$("#install-user-plugin").modal('hide');
|
||||
$("#install-plugin-progress").modal('show');
|
||||
$.post(
|
||||
'ajax/plugins/do_plugin_install.php',
|
||||
{
|
||||
'plugin_uri': pluginUri,
|
||||
'plugin_version': pluginVersion,
|
||||
'install_path': installPath,
|
||||
'csrf_token': csrfToken
|
||||
},
|
||||
function (data) {
|
||||
setTimeout(function () {
|
||||
response = JSON.parse(data);
|
||||
if (response === true) {
|
||||
$('#plugin-install-message').contents().first().text(successText);
|
||||
$('#plugin-install-message')
|
||||
.find('i')
|
||||
.removeClass('fas fa-cog fa-spin link-secondary')
|
||||
.addClass('fas fa-check');
|
||||
$('#js-install-plugin-ok').removeAttr("disabled");
|
||||
} else {
|
||||
const errorMessage = jsonData.error || 'An unknown error occurred.';
|
||||
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
|
||||
$('#plugin-install-message')
|
||||
.contents()
|
||||
.first()
|
||||
.replaceWith('An error occurred installing the plugin:');
|
||||
$('#plugin-install-message').append(errorLog);
|
||||
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
|
||||
$('#js-install-plugin-ok').removeAttr("disabled");
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
).fail(function (xhr) {
|
||||
const jsonData = JSON.parse(xhr.responseText);
|
||||
const errorMessage = jsonData.error || 'An unknown error occurred.';
|
||||
$('#plugin-install-message')
|
||||
.contents()
|
||||
.first()
|
||||
.replaceWith('An error occurred installing the plugin:');
|
||||
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
|
||||
$('#plugin-install-message').append(errorLog);
|
||||
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
|
||||
$('#js-install-plugin-ok').removeAttr("disabled");
|
||||
});
|
||||
} else if (pluginConfirm === 'Get Insiders') {
|
||||
window.open('https://docs.raspap.com/insiders/', '_blank');
|
||||
return;
|
||||
} else if (pluginConfirm === 'OK') {
|
||||
$("#install-user-plugin").modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
$('#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/><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', {
|
||||
|
@ -488,6 +616,15 @@ $(document).ready(function(){
|
|||
});
|
||||
});
|
||||
|
||||
$(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();
|
||||
|
@ -498,7 +635,6 @@ $('#wg-upload,#wg-manual').on('click', function (e) {
|
|||
}
|
||||
});
|
||||
|
||||
// Add the following code if you want the name of the file appear on select
|
||||
$(".custom-file-input").on("change", function() {
|
||||
var fileName = $(this).val().split("\\").pop();
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
|
||||
|
@ -614,18 +750,18 @@ function clearBlocklistStatus() {
|
|||
$('#cbxblocklist-status').removeClass('check-updated').addClass('check-hidden');
|
||||
}
|
||||
|
||||
// Handler for the wireguard generate key button
|
||||
// Handler for the WireGuard generate key button
|
||||
$('.wg-keygen').click(function(){
|
||||
var entity_pub = $(this).parent('div').prev('input[type="text"]');
|
||||
var entity_priv = $(this).parent('div').next('input[type="hidden"]');
|
||||
var parentGroup = $(this).closest('.input-group');
|
||||
var entity_pub = parentGroup.find('input[type="text"]');
|
||||
var updated = entity_pub.attr('name')+"-pubkey-status";
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
var csrfToken = $('meta[name="csrf_token"]').attr('content');
|
||||
$.post('ajax/networking/get_wgkey.php',{'entity':entity_pub.attr('name'), 'csrf_token': csrfToken},function(data){
|
||||
var jsonData = JSON.parse(data);
|
||||
entity_pub.val(jsonData.pubkey);
|
||||
$('#' + updated).removeClass('check-hidden').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Handler for wireguard client.conf download
|
||||
$('.wg-client-dl').click(function(){
|
||||
|
@ -662,6 +798,44 @@ window.addEventListener('load', function() {
|
|||
});
|
||||
}, false);
|
||||
|
||||
let sessionCheckInterval = setInterval(checkSession, 5000);
|
||||
|
||||
function checkSession() {
|
||||
// skip session check if on login page
|
||||
if (window.location.pathname === '/login') {
|
||||
return;
|
||||
}
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/session/do_check_session.php',{'csrf_token': csrfToken},function (data) {
|
||||
if (data.status === 'session_expired') {
|
||||
clearInterval(sessionCheckInterval);
|
||||
showSessionExpiredModal();
|
||||
}
|
||||
}).fail(function (jqXHR, status, err) {
|
||||
console.error("Error checking session status:", status, err);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -816,42 +990,9 @@ function getCookie(cname) {
|
|||
// Define themes
|
||||
var themes = {
|
||||
"default": "custom.php",
|
||||
"hackernews" : "hackernews.css",
|
||||
"lightsout" : "lightsout.php",
|
||||
"material-light" : "material-light.php",
|
||||
"material-dark" : "material-dark.php",
|
||||
"hackernews" : "hackernews.css"
|
||||
}
|
||||
|
||||
// Toggles the sidebar navigation.
|
||||
// Overrides the default SB Admin 2 behavior
|
||||
$("#sidebarToggleTopbar").on('click', function(e) {
|
||||
$("body").toggleClass("sidebar-toggled");
|
||||
$(".sidebar").toggleClass("toggled d-none");
|
||||
});
|
||||
|
||||
// Overrides SB Admin 2
|
||||
$("#sidebarToggle, #sidebarToggleTop").on('click', function(e) {
|
||||
var toggled = $(".sidebar").hasClass("toggled");
|
||||
// Persist state in cookie
|
||||
setCookie('sidebarToggled',toggled, 90);
|
||||
});
|
||||
|
||||
$(function() {
|
||||
if ($(window).width() < 768) {
|
||||
$('.sidebar').addClass('toggled');
|
||||
setCookie('sidebarToggled',false, 90);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on("load resize",function(e) {
|
||||
if ($(window).width() > 768) {
|
||||
$('.sidebar').removeClass('d-none d-md-block');
|
||||
if (getCookie('sidebarToggled') == 'false') {
|
||||
$('.sidebar').removeClass('toggled');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Adds active class to current nav-item
|
||||
$(window).bind("load", function() {
|
||||
var url = window.location;
|
||||
|
@ -860,6 +1001,27 @@ $(window).bind("load", function() {
|
|||
}).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");
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
const $htmlElement = $('html');
|
||||
const $modeswitch = $('#night-mode');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
define('RASPI_BRAND_TEXT', 'RaspAP');
|
||||
define('RASPI_BRAND_TITLE', RASPI_BRAND_TEXT.' Admin Panel');
|
||||
define('RASPI_CONFIG', '/etc/raspap');
|
||||
define('RASPI_CONFIG_NETWORK', RASPI_CONFIG.'/networking/defaults.json');
|
||||
define('RASPI_CONFIG_PROVIDERS', 'config/vpn-providers.json');
|
||||
|
@ -11,6 +12,7 @@ define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap');
|
|||
define('RASPI_ERROR_LOG', sys_get_temp_dir() . '/raspap_error.log');
|
||||
define('RASPI_DEBUG_LOG', 'raspap_debug.log');
|
||||
define('RASPI_LOG_SIZE_LIMIT', 64);
|
||||
define('RASPI_SESSION_TIMEOUT', 1440);
|
||||
|
||||
// Constants for configuration file paths.
|
||||
// These are typical for default RPi installs. Modify if needed.
|
||||
|
@ -61,6 +63,7 @@ define('RASPI_VNSTAT_ENABLED', true);
|
|||
define('RASPI_SYSTEM_ENABLED', true);
|
||||
define('RASPI_MONITOR_ENABLED', false);
|
||||
define('RASPI_RESTAPI_ENABLED', false);
|
||||
define('RASPI_PLUGINS_ENABLED', true);
|
||||
|
||||
// Locale settings
|
||||
define('LOCALE_ROOT', 'locale');
|
||||
|
|
|
@ -4,7 +4,7 @@ ctrl_interface_group=0
|
|||
beacon_int=100
|
||||
auth_algs=1
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
ssid=raspi-webgui
|
||||
ssid=RaspAP
|
||||
channel=1
|
||||
hw_mode=g
|
||||
wpa_passphrase=ChangeMe
|
||||
|
|
66
includes/CSRF.php
Normal file
66
includes/CSRF.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace RaspAP\Tokens;
|
||||
|
||||
class CSRF
|
||||
{
|
||||
protected static ?CSRFTokenizer $instance = null;
|
||||
|
||||
/*
|
||||
* Get the CSRFTokenizer instance (singleton)
|
||||
*
|
||||
* @return CSRFTokenizer
|
||||
*/
|
||||
public static function instance(): CSRFTokenizer
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new CSRFTokenizer();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function token(): string
|
||||
{
|
||||
return self::instance()->getToken();
|
||||
}
|
||||
|
||||
public static function verify(): bool
|
||||
{
|
||||
$token = $_POST['csrf_token'];
|
||||
return self::instance()->csrfValidateRequest() &&
|
||||
self::instance()->CSRFValidate($_POST['csrf_token'] ?? '');
|
||||
}
|
||||
|
||||
public static function metaTag(): string
|
||||
{
|
||||
return self::instance()->CSRFMetaTag();
|
||||
}
|
||||
|
||||
public static function hiddenField(): string
|
||||
{
|
||||
return self::instance()->CSRFTokenFieldTag();
|
||||
}
|
||||
|
||||
public static function handleInvalidToken(): void
|
||||
{
|
||||
self::instance()->handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a CSRF Request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateRequest(): bool
|
||||
{
|
||||
return self::instance()->csrfValidateRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if (\RaspAP\Tokens\CSRF::validateRequest()) {
|
||||
if (!\RaspAP\Tokens\CSRF::verify()) {
|
||||
error_log("CSRF verification failed. Token: " . ($_POST['csrf_token'] ?? 'not provided'));
|
||||
\RaspAP\Tokens\CSRF::handleInvalidToken();
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,6 @@ function DisplayAdBlockConfig()
|
|||
file_put_contents("/tmp/dnsmasq_custom", $_POST['adblock-custom-hosts'].PHP_EOL);
|
||||
system("sudo cp /tmp/dnsmasq_custom " .RASPI_ADBLOCK_LISTPATH .'custom.txt', $return);
|
||||
$config.= 'addn-hosts=' .RASPI_ADBLOCK_LISTPATH .'custom.txt'.PHP_EOL;
|
||||
$custom_enabled = true;
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
|
@ -64,6 +63,13 @@ function DisplayAdBlockConfig()
|
|||
}
|
||||
}
|
||||
|
||||
$custom_list = RASPI_ADBLOCK_LISTPATH . 'custom.txt';
|
||||
$custom_enabled = false;
|
||||
|
||||
if (file_exists($custom_list) && filesize($custom_list) > 0) {
|
||||
$custom_enabled = true;
|
||||
}
|
||||
|
||||
exec('cat '. RASPI_ADBLOCK_CONFIG, $return);
|
||||
$arrConf = ParseConfig($return);
|
||||
if (sizeof($arrConf) > 0) {
|
||||
|
|
|
@ -35,6 +35,8 @@ function DisplayAuthConfig($username)
|
|||
} else {
|
||||
$status->addMessage('Old password does not match', 'danger');
|
||||
}
|
||||
} elseif (isset($_POST['logout'])) {
|
||||
$auth->logout();
|
||||
}
|
||||
|
||||
echo renderTemplate(
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
<?php
|
||||
|
||||
if (RASPI_AUTH_ENABLED) {
|
||||
$user = $_SERVER['PHP_AUTH_USER'] ?? '';
|
||||
$pass = $_SERVER['PHP_AUTH_PW'] ?? '';
|
||||
|
||||
$auth = new \RaspAP\Auth\HTTPAuth;
|
||||
|
||||
if (!$auth->isLogged()) {
|
||||
if ($auth->login($user, $pass)) {
|
||||
$config = $auth->getAuthConfig();
|
||||
} else {
|
||||
$auth->authenticate();
|
||||
}
|
||||
$auth->authenticate();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,31 +9,13 @@
|
|||
*/
|
||||
spl_autoload_register(function ($class) {
|
||||
|
||||
// project-specific namespace prefix
|
||||
$prefix = '';
|
||||
// base directory where all class files are stored
|
||||
$base_dir = __DIR__ . '/../src/';
|
||||
|
||||
// base directory for the namespace prefix
|
||||
$base_dir = 'src/';
|
||||
// convert the fully qualified class name into a file path
|
||||
$file = $base_dir . str_replace('\\', '/', $class) . '.php';
|
||||
|
||||
// normalize the base directory with a trailing separator
|
||||
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
|
||||
|
||||
// does the class use the namespace prefix?
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
// no, move to the next registered autoloader
|
||||
return;
|
||||
}
|
||||
|
||||
// get the relative class name
|
||||
$relative_class = substr($class, $len);
|
||||
|
||||
// replace the namespace prefix with the base directory, replace namespace
|
||||
// separators with directory separators in the relative class name, append
|
||||
// with .php
|
||||
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
|
||||
|
||||
// if the file exists, require it
|
||||
// require the file if it exists
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once 'functions.php';
|
||||
require_once 'session.php';
|
||||
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
|
@ -5,226 +5,170 @@ require_once 'includes/wifi_functions.php';
|
|||
require_once 'includes/functions.php';
|
||||
|
||||
/**
|
||||
* Show dashboard page.
|
||||
* Displays the dashboard
|
||||
*/
|
||||
function DisplayDashboard(&$extraFooterScripts)
|
||||
function DisplayDashboard(&$extraFooterScripts): void
|
||||
{
|
||||
getWifiInterface();
|
||||
// instantiate RaspAP objects
|
||||
$system = new \RaspAP\System\Sysinfo;
|
||||
$dashboard = new \RaspAP\UI\Dashboard;
|
||||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
// Need this check interface name for proper shell execution.
|
||||
if (!preg_match('/^([a-zA-Z0-9]+)$/', $_SESSION['wifi_client_interface'])) {
|
||||
$status->addMessage(_('Interface name invalid.'), 'danger');
|
||||
$status->showMessages();
|
||||
return;
|
||||
}
|
||||
$pluginManager = \RaspAP\Plugins\PluginManager::getInstance();
|
||||
|
||||
if (!function_exists('exec')) {
|
||||
$status->addMessage(_('Required exec function is disabled. Check if exec is not added to php disable_functions.'), 'danger');
|
||||
$status->showMessages();
|
||||
return;
|
||||
}
|
||||
exec('ip a show '.$_SESSION['ap_interface'], $stdoutIp);
|
||||
$stdoutIpAllLinesGlued = implode(" ", $stdoutIp);
|
||||
$stdoutIpWRepeatedSpaces = preg_replace('/\s\s+/', ' ', $stdoutIpAllLinesGlued);
|
||||
// set AP and client interface session vars
|
||||
getWifiInterface();
|
||||
|
||||
preg_match('/link\/ether ([0-9a-f:]+)/i', $stdoutIpWRepeatedSpaces, $matchesMacAddr) || $matchesMacAddr[1] = _('No MAC Address Found');
|
||||
$macAddr = $matchesMacAddr[1];
|
||||
|
||||
$ipv4Addrs = '';
|
||||
$ipv4Netmasks = '';
|
||||
if (!preg_match_all('/inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([0-3][0-9])/i', $stdoutIpWRepeatedSpaces, $matchesIpv4AddrAndSubnet, PREG_SET_ORDER)) {
|
||||
$ipv4Addrs = _('No IPv4 Address Found');
|
||||
} else {
|
||||
foreach ($matchesIpv4AddrAndSubnet as $inet) {
|
||||
$address = $inet[1];
|
||||
$suffix = (int) $inet[2];
|
||||
$netmask = long2ip(-1 << (32 - $suffix));
|
||||
$ipv4Addrs .= " $address";
|
||||
$ipv4Netmasks .= " $netmask";
|
||||
}
|
||||
$ipv4Addrs = trim($ipv4Addrs);
|
||||
$ipv4Netmasks = trim($ipv4Netmasks);
|
||||
}
|
||||
$ipv4Netmasks = empty($ipv4Netmasks) ? "-" : $ipv4Netmasks;
|
||||
|
||||
$ipv6Addrs = '';
|
||||
if (!preg_match_all('/inet6 ([a-f0-9:]+)/i', $stdoutIpWRepeatedSpaces, $matchesIpv6Addr)) {
|
||||
$ipv6Addrs = _('No IPv6 Address Found');
|
||||
} else {
|
||||
if (isset($matchesIpv6Addr[1])) {
|
||||
$ipv6Addrs = implode(' ', $matchesIpv6Addr[1]);
|
||||
}
|
||||
}
|
||||
|
||||
preg_match('/state (UP|DOWN)/i', $stdoutIpWRepeatedSpaces, $matchesState) || $matchesState[1] = 'unknown';
|
||||
$interfaceState = $matchesState[1];
|
||||
|
||||
// Because of table layout used in the ip output we get the interface statistics directly from
|
||||
// the system. One advantage of this is that it could work when interface is disable.
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/rx_packets ', $stdoutCatRxPackets);
|
||||
$strRxPackets = _('No data');
|
||||
if (ctype_digit($stdoutCatRxPackets[0])) {
|
||||
$strRxPackets = $stdoutCatRxPackets[0];
|
||||
}
|
||||
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/tx_packets ', $stdoutCatTxPackets);
|
||||
$strTxPackets = _('No data');
|
||||
if (ctype_digit($stdoutCatTxPackets[0])) {
|
||||
$strTxPackets = $stdoutCatTxPackets[0];
|
||||
}
|
||||
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/rx_bytes ', $stdoutCatRxBytes);
|
||||
$strRxBytes = _('No data');
|
||||
if (ctype_digit($stdoutCatRxBytes[0])) {
|
||||
$strRxBytes = $stdoutCatRxBytes[0];
|
||||
$strRxBytes .= getHumanReadableDatasize($strRxBytes);
|
||||
}
|
||||
|
||||
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/tx_bytes ', $stdoutCatTxBytes);
|
||||
$strTxBytes = _('No data');
|
||||
if (ctype_digit($stdoutCatTxBytes[0])) {
|
||||
$strTxBytes = $stdoutCatTxBytes[0];
|
||||
$strTxBytes .= getHumanReadableDatasize($strTxBytes);
|
||||
}
|
||||
|
||||
exec ('vnstat --dbiflist', $stdoutVnStatDB);
|
||||
if (!preg_match('/'.$_SESSION['ap_interface'].'/', $stdoutVnStatDB[0])) {
|
||||
exec('sudo vnstat --add --iface '.$_SESSION['ap_interface'], $return);
|
||||
}
|
||||
|
||||
define('SSIDMAXLEN', 32);
|
||||
// Warning iw comes with: "Do NOT screenscrape this tool, we don't consider its output stable."
|
||||
exec('iw dev ' .$_SESSION['wifi_client_interface']. ' link ', $stdoutIw);
|
||||
$stdoutIwAllLinesGlued = implode('+', $stdoutIw); // Break lines with character illegal in SSID and MAC addr
|
||||
$stdoutIwWRepSpaces = preg_replace('/\s\s+/', ' ', $stdoutIwAllLinesGlued);
|
||||
|
||||
preg_match('/Connected to (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2}))/', $stdoutIwWRepSpaces, $matchesBSSID) || $matchesBSSID[1] = '';
|
||||
$connectedBSSID = $matchesBSSID[1];
|
||||
$connectedBSSID = empty($connectedBSSID) ? "-" : $connectedBSSID;
|
||||
|
||||
$wlanHasLink = false;
|
||||
if ($interfaceState === 'UP') {
|
||||
$wlanHasLink = true;
|
||||
}
|
||||
|
||||
if (!preg_match('/SSID: ([^+]{1,'.SSIDMAXLEN.'})/', $stdoutIwWRepSpaces, $matchesSSID)) {
|
||||
$wlanHasLink = false;
|
||||
$matchesSSID[1] = 'None';
|
||||
}
|
||||
$connectedSSID = str_replace('\x20', '', $matchesSSID[1]);
|
||||
|
||||
preg_match('/freq: (\d+)/i', $stdoutIwWRepSpaces, $matchesFrequency) || $matchesFrequency[1] = '';
|
||||
$frequency = $matchesFrequency[1].' MHz';
|
||||
|
||||
preg_match('/signal: (-?[0-9]+ dBm)/i', $stdoutIwWRepSpaces, $matchesSignal) || $matchesSignal[1] = '';
|
||||
$signalLevel = $matchesSignal[1];
|
||||
$signalLevel = empty($signalLevel) ? "-" : $signalLevel;
|
||||
|
||||
preg_match('/tx bitrate: ([0-9\.]+ [KMGT]?Bit\/s)/', $stdoutIwWRepSpaces, $matchesBitrate) || $matchesBitrate[1] = '';
|
||||
$bitrate = $matchesBitrate[1];
|
||||
$bitrate = empty($bitrate) ? "-" : $bitrate;
|
||||
|
||||
// txpower is now displayed on iw dev(..) info command, not on link command.
|
||||
exec('iw dev '.$_SESSION['wifi_client_interface'].' info ', $stdoutIwInfo);
|
||||
$stdoutIwInfoAllLinesGlued = implode(' ', $stdoutIwInfo);
|
||||
$stdoutIpInfoWRepSpaces = preg_replace('/\s\s+/', ' ', $stdoutIwInfoAllLinesGlued);
|
||||
|
||||
preg_match('/txpower ([0-9\.]+ dBm)/i', $stdoutIpInfoWRepSpaces, $matchesTxPower) || $matchesTxPower[1] = '';
|
||||
$txPower = $matchesTxPower[1];
|
||||
|
||||
// iw does not have the "Link Quality". This is a is an aggregate value,
|
||||
// and depends on the driver and hardware.
|
||||
// Display link quality as signal quality for now.
|
||||
$strLinkQuality = 0;
|
||||
if ($signalLevel > -100 && $wlanHasLink) {
|
||||
if ($signalLevel >= 0) {
|
||||
$strLinkQuality = 100;
|
||||
} else {
|
||||
$strLinkQuality = 100 + intval($signalLevel);
|
||||
}
|
||||
}
|
||||
|
||||
$wlan0up = false;
|
||||
$classMsgDevicestatus = 'warning';
|
||||
if ($interfaceState === 'UP') {
|
||||
$wlan0up = true;
|
||||
$classMsgDevicestatus = 'success';
|
||||
}
|
||||
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
if (isset($_POST['ifdown_wlan0'])) {
|
||||
// Pressed stop button
|
||||
if ($interfaceState === 'UP') {
|
||||
$status->addMessage(sprintf(_('Interface is going %s.'), _('down')), 'warning');
|
||||
exec('sudo ip link set '.$_SESSION['ap_interface'].' down');
|
||||
$wlan0up = false;
|
||||
$status->addMessage(sprintf(_('Interface is now %s.'), _('down')), 'success');
|
||||
} elseif ($interfaceState === 'unknown') {
|
||||
$status->addMessage(_('Interface state unknown.'), 'danger');
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface already %s.'), _('down')), 'warning');
|
||||
}
|
||||
} elseif (isset($_POST['ifup_wlan0'])) {
|
||||
// Pressed start button
|
||||
if ($interfaceState === 'DOWN') {
|
||||
$status->addMessage(sprintf(_('Interface is going %s.'), _('up')), 'warning');
|
||||
exec('sudo ip link set ' .$_SESSION['ap_interface']. ' up');
|
||||
exec('sudo ip -s a f label ' .$_SESSION['ap_interface']);
|
||||
$wlan0up = true;
|
||||
$status->addMessage(sprintf(_('Interface is now %s.'), _('up')), 'success');
|
||||
} elseif ($interfaceState === 'unknown') {
|
||||
$status->addMessage(_('Interface state unknown.'), 'danger');
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface already %s.'), _('up')), 'warning');
|
||||
}
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface is %s.'), strtolower($interfaceState)), $classMsgDevicestatus);
|
||||
}
|
||||
}
|
||||
// brought in from template
|
||||
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
|
||||
$bridgedEnable = $arrHostapdConf['BridgedEnable'];
|
||||
$interface = $_SESSION['ap_interface'] ?? 'wlan0';
|
||||
$clientInterface = $_SESSION['wifi_client_interface'];
|
||||
$apInterface = $_SESSION['ap_interface'];
|
||||
$MACPattern = '"([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}"';
|
||||
$hostname = $system->hostname();
|
||||
$revision = $system->rpiRevision();
|
||||
$deviceImage = $dashboard->getDeviceImage($revision);
|
||||
$hostapd = $system->hostapdStatus();
|
||||
$adblock = $system->adBlockStatus();
|
||||
$vpn = $system->getActiveVpnInterface();
|
||||
$frequency = $dashboard->getFrequencyBand($interface);
|
||||
$details = $dashboard->getInterfaceDetails($interface);
|
||||
$wireless = $dashboard->getWirelessDetails($interface);
|
||||
$connectionType = $dashboard->getConnectionType();
|
||||
$connectionIcon = $dashboard->getConnectionIcon($connectionType);
|
||||
$state = strtolower($details['state']);
|
||||
$wirelessClients = $dashboard->getWirelessClients();
|
||||
$ethernetClients = $dashboard->getEthernetClients();
|
||||
$totalClients = $wirelessClients + $ethernetClients;
|
||||
$plugins = $pluginManager->getInstalledPlugins();
|
||||
$bridgedEnable = getBridgedState();
|
||||
|
||||
if (getBridgedState()) {
|
||||
$moreLink = "hostapd_conf";
|
||||
exec('iw dev ' . $apInterface . ' station dump | grep -oE ' . $MACPattern, $clients);
|
||||
} else {
|
||||
$moreLink = "dhcpd_conf";
|
||||
exec('cat ' . RASPI_DNSMASQ_LEASES . '| grep -E $(iw dev ' . $apInterface . ' station dump | grep -oE ' . $MACPattern . ' | paste -sd "|")', $clients);
|
||||
// handle page actions
|
||||
if (!empty($_POST)) {
|
||||
$status = $dashboard->handlePageAction($state, $_POST, $status, $interface);
|
||||
// refresh interface details + state
|
||||
$details = $dashboard->getInterfaceDetails($interface);
|
||||
$state = strtolower($details['state']);
|
||||
}
|
||||
|
||||
$ipv4Address = $details['ipv4'];
|
||||
$ipv4Netmask = $details['ipv4_netmask'];
|
||||
$macAddress = $details['mac'];
|
||||
$ssid = $wireless['ssid'];
|
||||
$ethernetActive = ($connectionType === 'ethernet') ? "active" : "inactive";
|
||||
$wirelessActive = ($connectionType === 'wireless') ? "active" : "inactive";
|
||||
$tetheringActive = ($connectionType === 'tethering') ? "active" : "inactive";
|
||||
$cellularActive = ($connectionType === 'cellular') ? "active" : "inactive";
|
||||
$bridgedStatus = ($bridgedEnable == 1) ? "active" : "";
|
||||
$hostapdStatus = ($hostapd[0] == 1) ? "active" : "";
|
||||
$adblockStatus = ($adblock == true) ? "active" : "";
|
||||
$wirelessClientActive = ($wirelessClients > 0) ? "active" : "inactive";
|
||||
$wirelessClientLabel = sprintf(
|
||||
_('%d WLAN %s'),
|
||||
$wirelessClients,
|
||||
$dashboard->formatClientLabel($wirelessClients)
|
||||
);
|
||||
$ethernetClientActive = ($ethernetClients > 0) ? "active" : "inactive";
|
||||
$ethernetClientLabel = sprintf(
|
||||
_('%d LAN %s'),
|
||||
$ethernetClients,
|
||||
$dashboard->formatClientLabel($ethernetClients)
|
||||
);
|
||||
$totalClientsActive = ($totalClients > 0) ? "active": "inactive";
|
||||
$freq5active = $freq24active = "";
|
||||
$varName = "freq" . str_replace('.', '', $frequency) . "active";
|
||||
$$varName = "active";
|
||||
$vpnStatus = $vpn ? "active" : "inactive";
|
||||
$vpnManaged = $vpn ? $dashboard->getVpnManaged($vpn) : null;
|
||||
$firewallManaged = $firewallStatus = "";
|
||||
$firewallInstalled = array_filter($plugins, fn($p) => str_ends_with($p, 'Firewall')) ? true : false;
|
||||
if (!$firewallInstalled) {
|
||||
$firewallUnavailable = '<i class="fas fa-slash fa-stack-1x"></i>';
|
||||
} else {
|
||||
$firewallManaged = '<a href="/plugin__Firewall">';
|
||||
$firewallStatus = ($dashboard->firewallEnabled() == true) ? "active" : "";
|
||||
}
|
||||
$ifaceStatus = $wlan0up ? "up" : "down";
|
||||
|
||||
echo renderTemplate(
|
||||
"dashboard", compact(
|
||||
"clients",
|
||||
"moreLink",
|
||||
"apInterface",
|
||||
"revision",
|
||||
"deviceImage",
|
||||
"interface",
|
||||
"clientInterface",
|
||||
"ifaceStatus",
|
||||
"bridgedEnable",
|
||||
"status",
|
||||
"ipv4Addrs",
|
||||
"ipv4Netmasks",
|
||||
"ipv6Addrs",
|
||||
"macAddr",
|
||||
"strRxPackets",
|
||||
"strRxBytes",
|
||||
"strTxPackets",
|
||||
"strTxBytes",
|
||||
"connectedSSID",
|
||||
"connectedBSSID",
|
||||
"bitrate",
|
||||
"signalLevel",
|
||||
"txPower",
|
||||
"state",
|
||||
"bridgedStatus",
|
||||
"hostapdStatus",
|
||||
"adblockStatus",
|
||||
"vpnStatus",
|
||||
"vpnManaged",
|
||||
"firewallUnavailable",
|
||||
"firewallStatus",
|
||||
"firewallManaged",
|
||||
"ipv4Address",
|
||||
"ipv4Netmask",
|
||||
"macAddress",
|
||||
"ssid",
|
||||
"frequency",
|
||||
"strLinkQuality",
|
||||
"wlan0up"
|
||||
"freq5active",
|
||||
"freq24active",
|
||||
"wirelessClients",
|
||||
"wirelessClientLabel",
|
||||
"wirelessClientActive",
|
||||
"ethernetClients",
|
||||
"ethernetClientLabel",
|
||||
"ethernetClientActive",
|
||||
"totalClients",
|
||||
"totalClientsActive",
|
||||
"connectionType",
|
||||
"connectionIcon",
|
||||
"ethernetActive",
|
||||
"wirelessActive",
|
||||
"tetheringActive",
|
||||
"cellularActive",
|
||||
"status"
|
||||
)
|
||||
);
|
||||
$extraFooterScripts[] = array('src'=>'app/js/dashboardchart.js', 'defer'=>false);
|
||||
$extraFooterScripts[] = array('src'=>'app/js/linkquality.js', 'defer'=>false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a URL for an svg solid line representing the associated
|
||||
* connection type
|
||||
*
|
||||
* @param string $connectionType
|
||||
* @return string
|
||||
*/
|
||||
function renderConnection(string $connectionType): string
|
||||
{
|
||||
$deviceMap = [
|
||||
'ethernet' => 'device-1',
|
||||
'wireless' => 'device-2',
|
||||
'tethering' => 'device-3',
|
||||
'cellular' => 'device-4'
|
||||
];
|
||||
$device = $deviceMap[$connectionType] ?? 'device-unknown';
|
||||
|
||||
// return generated URL for solid.php
|
||||
return sprintf('app/img/solid.php?joint&%s&out', $device);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a URL for an svg solid line representing associated
|
||||
* client connection(s)
|
||||
*
|
||||
* @param int $wirelessClients
|
||||
* @param int $ethernetClients
|
||||
* @return string
|
||||
*/
|
||||
function renderClientConnections(int $wirelessClients, int $ethernetClients): string
|
||||
{
|
||||
$devices = [];
|
||||
|
||||
if ($wirelessClients > 0) {
|
||||
$devices[] = 'device-1&out';
|
||||
}
|
||||
if ($ethernetClients > 0) {
|
||||
$devices[] = 'device-2&out';
|
||||
}
|
||||
return empty($devices) ? '' : sprintf(
|
||||
'<img src="app/img/right-solid.php?%s" class="solid-lines solid-lines-right" alt="Client connections">',
|
||||
implode('&', $devices)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ if (!defined('RASPI_CONFIG')) {
|
|||
|
||||
$defaults = [
|
||||
'RASPI_BRAND_TEXT' => 'RaspAP',
|
||||
'RASPI_VERSION' => '3.2.2',
|
||||
'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel',
|
||||
'RASPI_VERSION' => '3.3.2',
|
||||
'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json',
|
||||
'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json',
|
||||
'RASPI_CONFIG_API' => RASPI_CONFIG.'/api',
|
||||
|
@ -16,6 +17,7 @@ $defaults = [
|
|||
'RASPI_ERROR_LOG' => sys_get_temp_dir() . '/raspap_error.log',
|
||||
'RASPI_DEBUG_LOG' => 'raspap_debug.log',
|
||||
'RASPI_LOG_SIZE_LIMIT' => 64,
|
||||
'RASPI_SESSION_TIMEOUT' => 1440,
|
||||
|
||||
// Constants for configuration file paths.
|
||||
// These are typical for default RPi installs. Modify if needed.
|
||||
|
@ -63,6 +65,7 @@ $defaults = [
|
|||
'RASPI_SYSTEM_ENABLED' => true,
|
||||
'RASPI_MONITOR_ENABLED' => false,
|
||||
'RASPI_RESTAPI_ENABLED' => false,
|
||||
'RASPI_PLUGINS_ENABLED' => true,
|
||||
|
||||
// Locale settings
|
||||
'LOCALE_ROOT' => 'locale',
|
||||
|
|
|
@ -52,8 +52,8 @@ function DisplayDHCPConfig()
|
|||
$conf = ParseConfig($return);
|
||||
exec('cat '. RASPI_DNSMASQ_PREFIX.$ap_iface.'.conf', $return);
|
||||
$conf = array_merge(ParseConfig($return));
|
||||
$hosts = (array)$conf['dhcp-host'];
|
||||
$upstreamServers = (array)$conf['server'];
|
||||
$hosts = (array)($conf['dhcp-host'] ?? []);
|
||||
$upstreamServers = (array)($conf['server'] ?? []);
|
||||
exec("ip -o link show | awk -F': ' '{print $2}'", $interfaces);
|
||||
exec('cat ' . RASPI_DNSMASQ_LEASES, $leases);
|
||||
|
||||
|
@ -268,6 +268,9 @@ function updateDnsmasqConfig($iface,$status)
|
|||
}
|
||||
$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);
|
||||
|
@ -280,10 +283,10 @@ function updateDnsmasqConfig($iface,$status)
|
|||
$config .='log-facility='.RASPI_DHCPCD_LOG.PHP_EOL;
|
||||
$config .='conf-dir=/etc/dnsmasq.d'.PHP_EOL;
|
||||
// handle log option
|
||||
if ($_POST['log-dhcp'] == "1") {
|
||||
if (($_POST['log-dhcp'] ?? '') == "1") {
|
||||
$config .= "log-dhcp".PHP_EOL;
|
||||
}
|
||||
if ($_POST['log-queries'] == "1") {
|
||||
if (($_POST['log-queries'] ?? '') == "1") {
|
||||
$config .= "log-queries".PHP_EOL;
|
||||
}
|
||||
$config .= PHP_EOL;
|
||||
|
@ -317,12 +320,12 @@ function updateDHCPConfig($iface,$status)
|
|||
if ($_POST['Metric'] !== '') {
|
||||
$cfg[] = 'metric '.$_POST['Metric'];
|
||||
}
|
||||
if ($_POST['Fallback'] == 1) {
|
||||
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[] = ($_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);
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
require_once 'src/RaspAP/Exceptions/ExceptionHandler.php';
|
||||
|
||||
$handler = new RaspAP\Exceptions\ExceptionHandler;
|
||||
?>
|
||||
|
24
includes/footer.php
Normal file → Executable file
24
includes/footer.php
Normal file → Executable file
|
@ -1,10 +1,28 @@
|
|||
<div class="d-flex align-items-center justify-content-between small">
|
||||
<?php $_SESSION['lastActivity'] = time(); ?>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between small">
|
||||
<div class="text-muted">
|
||||
<span class="pe-2"><a href="/about">v<?php echo RASPI_VERSION; ?></a></span> |
|
||||
<span class="ps-2">Created by the <a href="https://github.com/RaspAP" target="_blank" rel="noopener">RaspAP Team</a></span>
|
||||
<span class="ps-2"><?php echo sprintf(_('Created by the <a href="%s" target="_blank" rel="noopener">%s</a>'), 'https://github.com/RaspAP', _('RaspAP Team')); ?></span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<i class="fas fa-heart heart"></i> <a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener">Get Insiders</a>
|
||||
<i class="fas fa-heart heart"></i> <a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener"><?php echo _("Get Insiders"); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="sessionTimeoutModal" tabindex="-1" aria-labelledby="sessionTimeoutLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title" id="sessionTimeoutLabel"><i class="fa fa-clock me-2"></i><?php echo _("Session Expired"); ?></div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php echo _("Your session has expired. Please login to continue.") ?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="js-session-expired-login" class="btn btn-outline btn-primary"><?php echo _("Login"); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php require_once 'session.php'; ?>
|
||||
<?php
|
||||
/* Functions for Networking */
|
||||
|
||||
|
@ -54,7 +55,7 @@ function cidr2mask($cidr)
|
|||
{
|
||||
$ipParts = explode('/', $cidr);
|
||||
$ip = $ipParts[0];
|
||||
$prefixLength = $ipParts[1];
|
||||
$prefixLength = $ipParts[1] ?? null;
|
||||
|
||||
$ipLong = ip2long($ip);
|
||||
$netmaskLong = bindec(str_pad(str_repeat('1', $prefixLength), 32, '0'));
|
||||
|
@ -176,15 +177,17 @@ function getDefaultNetOpts($svc,$key)
|
|||
* @param string $key
|
||||
* @return object $json
|
||||
*/
|
||||
function getProviderValue($id,$key)
|
||||
function getProviderValue($id, $key)
|
||||
{
|
||||
$obj = json_decode(file_get_contents(RASPI_CONFIG_PROVIDERS), true);
|
||||
if ($obj === null) {
|
||||
if (!isset($obj['providers']) || !is_array($obj['providers'])) {
|
||||
return false;
|
||||
} else {
|
||||
$id--;
|
||||
return $obj['providers'][$id][$key];
|
||||
}
|
||||
$id--;
|
||||
if (!isset($obj['providers'][$id]) || !is_array($obj['providers'][$id])) {
|
||||
return false;
|
||||
}
|
||||
return $obj['providers'][$id][$key] ?? false;
|
||||
}
|
||||
|
||||
/* Functions to write ini files */
|
||||
|
@ -303,79 +306,6 @@ function filter_comments($var)
|
|||
return $var[0] != '#';
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a CSRF token in the session
|
||||
*/
|
||||
function ensureCSRFSessionToken()
|
||||
{
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add CSRF Token to form
|
||||
*/
|
||||
function CSRFTokenFieldTag()
|
||||
{
|
||||
$token = htmlspecialchars($_SESSION['csrf_token']);
|
||||
return '<input type="hidden" name="csrf_token" value="' . $token . '">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retuns a CSRF meta tag (for use with xhr, for example)
|
||||
*/
|
||||
function CSRFMetaTag()
|
||||
{
|
||||
$token = htmlspecialchars($_SESSION['csrf_token']);
|
||||
return '<meta name="csrf_token" content="' . $token . '">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate CSRF Token
|
||||
*/
|
||||
function CSRFValidate()
|
||||
{
|
||||
if(isset($_POST['csrf_token'])) {
|
||||
$post_token = $_POST['csrf_token'];
|
||||
$header_token = $_SERVER['HTTP_X_CSRF_TOKEN'];
|
||||
|
||||
if (empty($post_token) && empty($header_token)) {
|
||||
return false;
|
||||
}
|
||||
$request_token = $post_token;
|
||||
if (empty($post_token)) {
|
||||
$request_token = $header_token;
|
||||
}
|
||||
if (hash_equals($_SESSION['csrf_token'], $request_token)) {
|
||||
return true;
|
||||
} else {
|
||||
error_log('CSRF violation');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the request be CSRF-validated?
|
||||
*/
|
||||
function csrfValidateRequest()
|
||||
{
|
||||
$request_method = strtolower($_SERVER['REQUEST_METHOD']);
|
||||
return in_array($request_method, [ "post", "put", "patch", "delete" ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle invalid CSRF
|
||||
*/
|
||||
function handleInvalidCSRFToken()
|
||||
{
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
header('Content-Type: text/plain');
|
||||
echo 'Invalid CSRF token';
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether array is associative
|
||||
*/
|
||||
|
@ -458,6 +388,9 @@ function ParseConfig($arrConfig, $wg = false)
|
|||
|
||||
if (strpos($line, "=") !== false) {
|
||||
list($option, $value) = array_map("trim", explode("=", $line, 2));
|
||||
} else {
|
||||
$option = $line;
|
||||
$value = "";
|
||||
}
|
||||
if (empty($config[$option])) {
|
||||
$config[$option] = $value ?: true;
|
||||
|
@ -622,6 +555,13 @@ function mb_escapeshellarg($arg)
|
|||
}
|
||||
}
|
||||
|
||||
function safeOutputValue($def, $arr)
|
||||
{
|
||||
if (array_key_exists($def, $arr)) {
|
||||
echo htmlspecialchars($arr[$def], ENT_QUOTES);
|
||||
}
|
||||
}
|
||||
|
||||
function dnsServers()
|
||||
{
|
||||
$data = json_decode(file_get_contents("./config/dns-servers.json"));
|
||||
|
@ -697,7 +637,6 @@ function formatDateAgo($datetime, $full = false)
|
|||
function initializeApp()
|
||||
{
|
||||
$_SESSION["theme_url"] = getThemeOpt();
|
||||
$_SESSION["toggleState"] = getSidebarState();
|
||||
$_SESSION["bridgedEnabled"] = getBridgedState();
|
||||
$_SESSION["providerID"] = getProviderID();
|
||||
}
|
||||
|
@ -723,22 +662,17 @@ function getColorOpt()
|
|||
return $color;
|
||||
}
|
||||
|
||||
function getSidebarState()
|
||||
{
|
||||
if(isset($_COOKIE['sidebarToggled'])) {
|
||||
if ($_COOKIE['sidebarToggled'] == 'true' ) {
|
||||
return "toggled";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns bridged AP mode status
|
||||
function getBridgedState()
|
||||
{
|
||||
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
|
||||
// defaults to false
|
||||
|
||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||
if (!file_exists($hostapdIni)) {
|
||||
return 0;
|
||||
} else {
|
||||
$arrHostapdConf = parse_ini_file($hostapdIni);
|
||||
}
|
||||
return $arrHostapdConf['BridgedEnable'];
|
||||
}
|
||||
}
|
||||
|
||||
// Returns VPN provider ID, if defined
|
||||
function getProviderID()
|
||||
|
@ -1033,3 +967,25 @@ function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cp
|
|||
<?php
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes a callback with a timeout
|
||||
*
|
||||
* @param callable $callback function to execute
|
||||
* @param int $interval timeout in milliseconds
|
||||
* @return mixed result of the callback
|
||||
* @throws \Exception if the execution exceeds the timeout or an error occurs
|
||||
*/
|
||||
function callbackTimeout(callable $callback, int $interval)
|
||||
{
|
||||
$startTime = microtime(true); // use high-resolution timer
|
||||
$result = $callback();
|
||||
$elapsed = (microtime(true) - $startTime) * 1000;
|
||||
|
||||
if ($elapsed > $interval) {
|
||||
throw new \Exception('Operation timed out');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ function DisplayHostAPDConfig()
|
|||
'a' => '802.11a - 5 GHz',
|
||||
'b' => '802.11b - 2.4 GHz',
|
||||
'g' => '802.11g - 2.4 GHz',
|
||||
'n' => '802.11n - 2.4 GHz',
|
||||
'n' => '802.11n - 2.4/5 GHz',
|
||||
'ac' => '802.11ac - 5 GHz'
|
||||
];
|
||||
$languageCode = strtok($_SESSION['locale'], '_');
|
||||
|
@ -46,7 +46,12 @@ function DisplayHostAPDConfig()
|
|||
SaveHostAPDConfig($arrSecurity, $arrEncType, $arr80211Standard, $interfaces, $reg_domain, $status);
|
||||
}
|
||||
}
|
||||
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
|
||||
|
||||
$arrHostapdConf = [];
|
||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||
if (file_exists($hostapdIni)) {
|
||||
$arrHostapdConf = parse_ini_file($hostapdIni);
|
||||
}
|
||||
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
if (isset($_POST['StartHotspot']) || isset($_POST['RestartHotspot'])) {
|
||||
|
@ -136,6 +141,9 @@ function DisplayHostAPDConfig()
|
|||
}
|
||||
}
|
||||
|
||||
$arrConfig['ignore_broadcast_ssid'] ??= 0;
|
||||
$arrConfig['max_num_sta'] ??= 0;
|
||||
$arrConfig['wep_default_key'] ??= 0;
|
||||
exec('sudo /bin/chmod o+r '.RASPI_HOSTAPD_LOG);
|
||||
$logdata = getLogLimited(RASPI_HOSTAPD_LOG);
|
||||
|
||||
|
@ -281,18 +289,13 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
|
|||
$good_input = false;
|
||||
}
|
||||
|
||||
if (isset($_POST['hiddenSSID'])) {
|
||||
if (!is_int((int)$_POST['hiddenSSID'])) {
|
||||
$status->addMessage('Parameter hiddenSSID not a number.', 'danger');
|
||||
$good_input = false;
|
||||
} elseif ((int)$_POST['hiddenSSID'] < 0 || (int)$_POST['hiddenSSID'] >= 3) {
|
||||
$status->addMessage('Parameter hiddenSSID contains invalid configuratie value.', 'danger');
|
||||
$good_input = false;
|
||||
} else {
|
||||
$ignore_broadcast_ssid = $_POST['hiddenSSID'];
|
||||
}
|
||||
} else {
|
||||
$ignore_broadcast_ssid = '0';
|
||||
$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)) {
|
||||
|
@ -364,14 +367,17 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
|
|||
|
||||
// Set dhcp values from system config, fallback to default if undefined
|
||||
$jsonData = json_decode(getNetConfig($ap_iface), true);
|
||||
$ip_address = ($jsonData['StaticIP'] == '') ? getDefaultNetValue('dhcp',$ap_iface,'static ip_address') : $jsonData['StaticIP'];
|
||||
$domain_name_server = ($jsonData['StaticDNS'] =='') ? getDefaultNetValue('dhcp',$ap_iface,'static domain_name_server') : $jsonData['StaticDNS'];
|
||||
$routers = ($jsonData['StaticRouters'] == '') ? getDefaultNetValue('dhcp',$ap_iface,'static routers') : $jsonData['StaticRouters'];
|
||||
$netmask = ($jsonData['SubnetMask'] == '' || $jsonData['SubnetMask'] == '0.0.0.0') ? getDefaultNetValue('dhcp',$ap_iface,'subnetmask') : $jsonData['SubnetMask'];
|
||||
$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';
|
||||
|
@ -392,7 +398,9 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
|
|||
$config[] = 'static ip_address='.$ip_address;
|
||||
$config[] = 'static routers='.$routers;
|
||||
$config[] = 'static domain_name_server='.$domain_name_server;
|
||||
if (! is_null($jsonData['Metric'])) { $config[] = 'metric '.$jsonData['Metric']; }
|
||||
if (!empty($jsonData['Metric'])) {
|
||||
$config[] = 'metric ' . $jsonData['Metric'];
|
||||
}
|
||||
}
|
||||
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
*
|
||||
* Refer to: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
||||
*/
|
||||
if (empty($_SESSION['locale']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2) {
|
||||
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":
|
||||
|
@ -90,9 +90,10 @@ if (empty($_SESSION['locale']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2)
|
|||
// Use: 'sudo raspi-configure' and select 'Localisation Options'
|
||||
|
||||
// activate the locale setting
|
||||
putenv("LANG=" . $_SESSION['locale']);
|
||||
setlocale(LC_ALL, $_SESSION['locale']);
|
||||
|
||||
if (!empty($_SESSION['locale'])) {
|
||||
putenv("LANG=" . $_SESSION['locale']);
|
||||
setlocale(LC_ALL, $_SESSION['locale']);
|
||||
}
|
||||
bindtextdomain(LOCALE_DOMAIN, LOCALE_ROOT);
|
||||
bind_textdomain_codeset(LOCALE_DOMAIN, 'UTF-8');
|
||||
|
||||
|
|
40
includes/login.php
Executable file
40
includes/login.php
Executable file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/functions.php';
|
||||
|
||||
/**
|
||||
* Handler for administrative user login
|
||||
*/
|
||||
function DisplayLogin()
|
||||
{
|
||||
// initialize auth object
|
||||
$auth = new \RaspAP\Auth\HTTPAuth;
|
||||
$status = null;
|
||||
$redirectUrl = null;
|
||||
|
||||
// handle page action
|
||||
if (RASPI_AUTH_ENABLED) {
|
||||
if (isset($_POST['login-auth'])) {
|
||||
// authenticate user
|
||||
$username = $_POST['username'];
|
||||
$password = $_POST['password'];
|
||||
$redirectUrl = $_POST['redirect-url'];
|
||||
if ($auth->login($username, $password)) {
|
||||
$config = $auth->getAuthConfig();
|
||||
header('Location: ' . $redirectUrl);
|
||||
die();
|
||||
} else {
|
||||
$status = "Login failed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo renderTemplate(
|
||||
"login", compact(
|
||||
"status",
|
||||
"redirectUrl"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<!-- Auth user -->
|
||||
<li class="nav-item mt-1">
|
||||
<a class="nav-link" href="auth_conf">
|
||||
<span class="mr-2 small nav-user"><?php echo htmlspecialchars($_SESSION['user_id'], ENT_QUOTES); ?></span>
|
||||
<span class="mr-2 small nav-user"><?php echo htmlspecialchars($_SESSION['user_id'] ?? '', ENT_QUOTES); ?></span>
|
||||
<i class="fas fa-user-circle text-muted mt-2 fa-3x"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -70,6 +70,9 @@ function handleCorePageAction(string $page, array &$extraFooterScripts): void
|
|||
case "/about":
|
||||
DisplayAbout();
|
||||
break;
|
||||
case "/login":
|
||||
DisplayLogin();
|
||||
break;
|
||||
default:
|
||||
DisplayDashboard($extraFooterScripts);
|
||||
}
|
||||
|
|
|
@ -278,6 +278,10 @@ function getCountries($id, $binPath)
|
|||
$countries[$value] = str_replace("_", " ", $value);
|
||||
}
|
||||
break;
|
||||
/**
|
||||
* Thanks to GitHub user @easylo who contributed this portion
|
||||
* of the AdGuard CLI country output parsing
|
||||
*/
|
||||
case 4: // adguard
|
||||
$raw_countries = [];
|
||||
$totalLines = count($output);
|
||||
|
@ -302,7 +306,7 @@ function getCountries($id, $binPath)
|
|||
ksort($raw_countries);
|
||||
// sort cities within each country
|
||||
foreach ($raw_countries as $country => $cities) {
|
||||
sort($raw_countries[$country]); // Trier les villes par ordre alphabétique
|
||||
sort($raw_countries[$country]);
|
||||
}
|
||||
// sort results by country, then by city
|
||||
foreach ($raw_countries as $country => $cities) {
|
||||
|
|
0
includes/restapi.php
Normal file → Executable file
0
includes/restapi.php
Normal file → Executable file
|
@ -3,3 +3,8 @@
|
|||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['lastActivity'])) {
|
||||
$_SESSION['lastActivity'] = time();
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,6 @@ if ($hostapd[0] ==1) {
|
|||
$hostapd_led = "service-status-up";
|
||||
} else {
|
||||
$hostapd_status = "down";
|
||||
$hostapd_led = "service-status-down";
|
||||
$hostapd_led = "service-status-warn";
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ require_once 'config.php';
|
|||
function DisplaySystem(&$extraFooterScripts)
|
||||
{
|
||||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
$dashboard = new \RaspAP\UI\Dashboard;
|
||||
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
|
||||
|
||||
if (isset($_POST['SaveLanguage'])) {
|
||||
if (isset($_POST['locale'])) {
|
||||
|
@ -85,53 +87,23 @@ function DisplaySystem(&$extraFooterScripts)
|
|||
$kernel = $system->kernelVersion();
|
||||
$systime = $system->systime();
|
||||
$revision = $system->rpiRevision();
|
||||
|
||||
// mem used
|
||||
$deviceImage = $dashboard->getDeviceImage($revision);
|
||||
|
||||
// memory use
|
||||
$memused = $system->usedMemory();
|
||||
$memused_status = "primary";
|
||||
if ($memused > 90) {
|
||||
$memused_status = "danger";
|
||||
$memused_led = "service-status-down";
|
||||
} elseif ($memused > 75) {
|
||||
$memused_status = "warning";
|
||||
$memused_led = "service-status-warn";
|
||||
} elseif ($memused > 0) {
|
||||
$memused_status = "success";
|
||||
$memused_led = "service-status-up";
|
||||
}
|
||||
$memStatus = getMemStatus($memused);
|
||||
$memused_status = $memStatus['status'];
|
||||
$memused_led = $memStatus['led'];
|
||||
|
||||
// cpu load
|
||||
$cpuload = $system->systemLoadPercentage();
|
||||
if ($cpuload > 90) {
|
||||
$cpuload_status = "danger";
|
||||
} elseif ($cpuload > 75) {
|
||||
$cpuload_status = "warning";
|
||||
} elseif ($cpuload >= 0) {
|
||||
$cpuload_status = "success";
|
||||
}
|
||||
$cpuload_status = getCPULoadStatus($cpuload);
|
||||
|
||||
// cpu temp
|
||||
$cputemp = $system->systemTemperature();
|
||||
if ($cputemp > 70) {
|
||||
$cputemp_status = "danger";
|
||||
$cputemp_led = "service-status-down";
|
||||
} elseif ($cputemp > 50) {
|
||||
$cputemp_status = "warning";
|
||||
$cputemp_led = "service-status-warn";
|
||||
} else {
|
||||
$cputemp_status = "success";
|
||||
$cputemp_led = "service-status-up";
|
||||
}
|
||||
|
||||
// hostapd status
|
||||
$hostapd = $system->hostapdStatus();
|
||||
if ($hostapd[0] == 1) {
|
||||
$hostapd_status = "active";
|
||||
$hostapd_led = "service-status-up";
|
||||
} else {
|
||||
$hostapd_status = "inactive";
|
||||
$hostapd_led = "service-status-down";
|
||||
}
|
||||
$cpuStatus = getCPUTempStatus($cputemp);
|
||||
$cputemp_status = $cpuStatus['status'];
|
||||
$cputemp_led = $cpuStatus['led'];
|
||||
|
||||
// theme options
|
||||
$themes = [
|
||||
|
@ -147,6 +119,9 @@ function DisplaySystem(&$extraFooterScripts)
|
|||
$extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false);
|
||||
$logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT;
|
||||
|
||||
$plugins = $pluginInstaller->getUserPlugins();
|
||||
$pluginsTable = $pluginInstaller->getHTMLPluginsTable($plugins);
|
||||
|
||||
echo renderTemplate("system", compact(
|
||||
"arrLocales",
|
||||
"status",
|
||||
|
@ -156,6 +131,7 @@ function DisplaySystem(&$extraFooterScripts)
|
|||
"uptime",
|
||||
"systime",
|
||||
"revision",
|
||||
"deviceImage",
|
||||
"cores",
|
||||
"os",
|
||||
"kernel",
|
||||
|
@ -167,11 +143,62 @@ function DisplaySystem(&$extraFooterScripts)
|
|||
"cputemp",
|
||||
"cputemp_status",
|
||||
"cputemp_led",
|
||||
"hostapd",
|
||||
"hostapd_status",
|
||||
"hostapd_led",
|
||||
"themes",
|
||||
"selectedTheme",
|
||||
"logLimit"
|
||||
"logLimit",
|
||||
"pluginsTable"
|
||||
));
|
||||
}
|
||||
|
||||
function getMemStatus($memused): array
|
||||
{
|
||||
$memused_status = "primary";
|
||||
$memused_led = "";
|
||||
|
||||
if ($memused > 90) {
|
||||
$memused_status = "danger";
|
||||
$memused_led = "service-status-down";
|
||||
} elseif ($memused > 75) {
|
||||
$memused_status = "warning";
|
||||
$memused_led = "service-status-warn";
|
||||
} elseif ($memused > 0) {
|
||||
$memused_status = "success";
|
||||
$memused_led = "service-status-up";
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $memused_status,
|
||||
'led' => $memused_led
|
||||
];
|
||||
}
|
||||
|
||||
function getCPULoadStatus($cpuload): string
|
||||
{
|
||||
if ($cpuload > 90) {
|
||||
$status = "danger";
|
||||
} elseif ($cpuload > 75) {
|
||||
$status = "warning";
|
||||
} elseif ($cpuload >= 0) {
|
||||
$status = "success";
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
function getCPUTempStatus($cputemp): array
|
||||
{
|
||||
if ($cputemp > 70) {
|
||||
$cputemp_status = "danger";
|
||||
$cputemp_led = "service-status-down";
|
||||
} elseif ($cputemp > 50) {
|
||||
$cputemp_status = "warning";
|
||||
$cputemp_led = "service-status-warn";
|
||||
} else {
|
||||
$cputemp_status = "success";
|
||||
$cputemp_led = "service-status-up";
|
||||
}
|
||||
return [
|
||||
'status' => $cputemp_status,
|
||||
'led' => $cputemp_led
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -160,19 +160,20 @@ function sortNetworksByRSSI(&$networks)
|
|||
*/
|
||||
function getWifiInterface()
|
||||
{
|
||||
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
|
||||
$iface = $_SESSION['ap_interface'] = isset($arrHostapdConf['WifiInterface']) ? $arrHostapdConf['WifiInterface'] : 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]));
|
||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||
$arrHostapdConf = file_exists($hostapdIni) ? parse_ini_file($hostapdIni) : [];
|
||||
|
||||
// specifically for rpi0W in AP-STA mode, the above check ends up with the interfaces
|
||||
// crossed over (wifi_client_interface vs 'ap_interface'), because the second interface (uap0) is
|
||||
// created by raspap and used as the access point.
|
||||
if ($client_iface == "uap0" && ($arrHostapdConf['WifiAPEnable'] ?? 0)){
|
||||
$_SESSION['wifi_client_interface'] = $iface;
|
||||
$_SESSION['ap_interface'] = $client_iface;
|
||||
}
|
||||
$iface = $_SESSION['ap_interface'] = $arrHostapdConf['WifiInterface'] ?? 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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -10,10 +10,10 @@ function DisplayWireGuardConfig()
|
|||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
$parseFlag = true;
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
$optRules = $_POST['wgRules'];
|
||||
$optConf = $_POST['wgCnfOpt'];
|
||||
$optSrvEnable = $_POST['wgSrvEnable'];
|
||||
$optLogEnable = $_POST['wgLogEnable'];
|
||||
$optRules = isset($_POST['wgRules']) ? $_POST['wgRules'] : null;
|
||||
$optConf = isset($_POST['wgCnfOpt']) ? $_POST['wgCnfOpt'] : null;
|
||||
$optSrvEnable = isset($_POST['wgSrvEnable']) ? $_POST['wgSrvEnable'] : null;
|
||||
$optLogEnable = isset($_POST['wgLogEnable']) ? $_POST['wgLogEnable'] : null;
|
||||
if (isset($_POST['savewgsettings']) && $optConf == 'manual' && $optSrvEnable == 1 ) {
|
||||
SaveWireGuardConfig($status);
|
||||
} elseif (isset($_POST['savewgsettings']) && $optConf == 'upload' && is_uploaded_file($_FILES["wgFile"]["tmp_name"])) {
|
||||
|
@ -69,6 +69,14 @@ function DisplayWireGuardConfig()
|
|||
$wg_state = ($wgstatus[0] == 'active' ? true : false );
|
||||
$public_ip = get_public_ip();
|
||||
|
||||
// retrieve wg log
|
||||
$wg_log = "";
|
||||
if (file_exists('/tmp/wireguard.log')) {
|
||||
exec('sudo chmod o+r /tmp/wireguard.log');
|
||||
$wg_log = file_get_contents('/tmp/wireguard.log');
|
||||
}
|
||||
$peer_id = $peer_id ?? "1";
|
||||
|
||||
echo renderTemplate(
|
||||
"wireguard", compact(
|
||||
"status",
|
||||
|
@ -89,7 +97,8 @@ function DisplayWireGuardConfig()
|
|||
"wg_peerpubkey",
|
||||
"wg_pendpoint",
|
||||
"wg_pallowedips",
|
||||
"wg_pkeepalive"
|
||||
"wg_pkeepalive",
|
||||
"wg_log"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -207,7 +216,10 @@ function SaveWireGuardConfig($status)
|
|||
}
|
||||
if (isset($_POST['wg_pendpoint']) && strlen(trim($_POST['wg_pendpoint']) >0 )) {
|
||||
$wg_pendpoint_seg = substr($_POST['wg_pendpoint'],0,strpos($_POST['wg_pendpoint'],':'));
|
||||
if (!filter_var($wg_pendpoint_seg,FILTER_VALIDATE_IP)) {
|
||||
$host_port = explode(':', $wg_pendpoint_seg);
|
||||
$hostname = $host_port[0];
|
||||
if (!filter_var($hostname, FILTER_VALIDATE_IP) &&
|
||||
!filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
||||
$status->addMessage('Invalid value for endpoint address', 'danger');
|
||||
$good_input = false;
|
||||
}
|
||||
|
|
19
index.php
19
index.php
|
@ -8,13 +8,13 @@
|
|||
* Recommended distribution is Raspberry Pi OS (64-bit) Lite. Specific instructions to install the supported software are
|
||||
* in the README and original post by @SirLagz. For a quick run through, the packages required for the WebGUI are:
|
||||
* lighttpd (version 1.4.69 installed via apt)
|
||||
* php-cgi (version 8.2.24 installed via apt)
|
||||
* php-cgi (version 8.2.28 installed via apt)
|
||||
* along with their supporting packages, php8.2 will also need to be enabled.
|
||||
*
|
||||
* @author Lawrence Yau <sirlagz@gmail.com>
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* @license GNU General Public License, version 3 (GPL-3.0)
|
||||
* @version 3.2.2
|
||||
* @version 3.3.2
|
||||
* @link https://github.com/RaspAP/raspap-webgui/
|
||||
* @link https://raspap.com/
|
||||
* @see http://sirlagz.net/2013/02/08/raspap-webgui/
|
||||
|
@ -23,18 +23,19 @@
|
|||
* as you leave these references intact in the header comments of your source files.
|
||||
*/
|
||||
|
||||
require 'includes/csrf.php';
|
||||
ensureCSRFSessionToken();
|
||||
|
||||
require_once 'includes/exceptions.php';
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/autoload.php';
|
||||
$handler = new RaspAP\Exceptions\ExceptionHandler;
|
||||
|
||||
require_once 'includes/CSRF.php';
|
||||
require_once 'includes/session.php';
|
||||
require_once 'includes/defaults.php';
|
||||
require_once 'includes/locale.php';
|
||||
require_once 'includes/functions.php';
|
||||
|
||||
// Default page actions
|
||||
require_once 'includes/dashboard.php';
|
||||
require_once 'includes/login.php';
|
||||
require_once 'includes/authenticate.php';
|
||||
require_once 'includes/admin.php';
|
||||
require_once 'includes/dhcp.php';
|
||||
|
@ -57,13 +58,13 @@ initializeApp();
|
|||
<html lang="en" <?php setTheme();?>>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<?php echo CSRFMetaTag() ?>
|
||||
<?php echo \RaspAP\Tokens\CSRF::metaTag(); ?>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title><?php echo _("RaspAP WiFi Configuration Portal"); ?></title>
|
||||
<title><?php echo RASPI_BRAND_TITLE; ?></title>
|
||||
|
||||
<!-- Bootstrap Core CSS -->
|
||||
<link href="dist/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
@ -96,6 +97,7 @@ initializeApp();
|
|||
|
||||
<body class="sb-nav-fixed">
|
||||
<!-- Navbar -->
|
||||
<?php ob_start(); ?>
|
||||
<?php require_once 'includes/navbar.php'; ?>
|
||||
<!-- End of Navbar -->
|
||||
<div id="layoutSidenav">
|
||||
|
@ -123,6 +125,7 @@ initializeApp();
|
|||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<?php ob_end_flush(); ?>
|
||||
<!-- jQuery -->
|
||||
<script src="dist/jquery/jquery.min.js"></script>
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ function _install_raspap() {
|
|||
_download_latest_files
|
||||
_change_file_ownership
|
||||
_create_hostapd_scripts
|
||||
_create_plugin_scripts
|
||||
_create_lighttpd_scripts
|
||||
_install_lighttpd_configs
|
||||
_default_configuration
|
||||
|
@ -74,6 +75,7 @@ function _update_raspap() {
|
|||
_download_latest_files
|
||||
_change_file_ownership
|
||||
_patch_system_files
|
||||
_create_plugin_scripts
|
||||
_install_complete
|
||||
}
|
||||
|
||||
|
@ -243,7 +245,22 @@ function _install_dependencies() {
|
|||
if [ ${OS,,} = "armbian" ]; then
|
||||
ifconfig_package="net-tools"
|
||||
echo "${ifconfig_package} will be installed from the main deb sources list"
|
||||
|
||||
# Manually install isoquery
|
||||
_install_log "Installing isoquery from the Debian package repository"
|
||||
isoquery_deb="https://ftp.debian.org/debian/pool/main/i/isoquery"
|
||||
if [ "$LONG_BIT" = "64" ]; then
|
||||
isoquery_pkg="isoquery_3.3.4-1+b1_arm64.deb"
|
||||
else
|
||||
isoquery_pkg="isoquery_3.3.4-1_armhf.deb"
|
||||
fi
|
||||
echo "isoquery ARM ${LONG_BIT}-bit package selected"
|
||||
wget $isoquery_deb/$isoquery_pkg -q --show-progress --progress=bar:force -P /tmp || _install_status 1 "Failed to download isoquery"
|
||||
sudo dpkg -x /tmp/$isoquery_pkg /tmp/isoquery/ || _install_status 1 "Failed to extract isoquery"
|
||||
sudo cp /tmp/isoquery/usr/bin/isoquery /usr/local/bin/ || _install_status 1 "Failed to copy isoquery binary"
|
||||
sudo chmod +x /usr/local/bin/isoquery || _install_status 1 "Failed to set executable permissions on isoquery"
|
||||
fi
|
||||
|
||||
if [ "$insiders" == 1 ]; then
|
||||
network_tools="curl dnsutils nmap"
|
||||
echo "${network_tools} will be installed from the main deb sources list"
|
||||
|
@ -298,6 +315,19 @@ function _create_hostapd_scripts() {
|
|||
_install_status 0
|
||||
}
|
||||
|
||||
# Generate plugin helper scripts
|
||||
function _create_plugin_scripts() {
|
||||
_install_log "Creating plugin helper scripts"
|
||||
sudo mkdir -p $raspap_dir/plugins || _install_status 1 "Unable to create directory '$raspap_dir/plugins'"
|
||||
|
||||
# Copy plugin helper script
|
||||
sudo cp "$webroot_dir/installers/"plugin_helper.sh "$raspap_dir/plugins" || _install_status 1 "Unable to move plugin script"
|
||||
# Change ownership and permissions of plugin script
|
||||
sudo chown -c root:root "$raspap_dir/plugins/"*.sh || _install_status 1 "Unable change owner and/or group"
|
||||
sudo chmod 750 "$raspap_dir/plugins/"*.sh || _install_status 1 "Unable to change file permissions"
|
||||
_install_status 0
|
||||
}
|
||||
|
||||
# Generate lighttpd service control scripts
|
||||
function _create_lighttpd_scripts() {
|
||||
_install_log "Creating lighttpd control scripts"
|
||||
|
@ -569,14 +599,16 @@ function _download_latest_files() {
|
|||
if [ "$repo" == "RaspAP/raspap-insiders" ]; then
|
||||
if [ -n "$username" ] && [ -n "$acctoken" ]; then
|
||||
insiders_source_url="https://${username}:${acctoken}@github.com/$repo"
|
||||
git clone --branch $branch --depth 1 -c advice.detachedHead=false $insiders_source_url $source_dir || clone=false
|
||||
git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $insiders_source_url $source_dir || clone=false
|
||||
git -C $source_dir submodule update --remote plugins || clone=false
|
||||
else
|
||||
_install_status 3
|
||||
echo "Insiders please read this: https://docs.raspap.com/insiders/#authentication"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$insiders_source_url" ]; then
|
||||
git clone --branch $branch --depth 1 -c advice.detachedHead=false $git_source_url $source_dir || clone=false
|
||||
git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $git_source_url $source_dir || clone=false
|
||||
git -C $source_dir submodule update --remote plugins || clone=false
|
||||
fi
|
||||
if [ "$clone" = false ]; then
|
||||
_install_status 1 "Unable to download files from GitHub"
|
||||
|
|
|
@ -217,10 +217,10 @@ function _dnsmasq_info() {
|
|||
if [ -f "$file" ]; then
|
||||
contents+="\n$file contents:\n"
|
||||
contents+="$(cat $file)"
|
||||
contents="${contents}$\n"
|
||||
contents+=$'\n'
|
||||
fi
|
||||
done
|
||||
_log_write $contents
|
||||
_log_write "$contents"
|
||||
else
|
||||
_log_write "Not found: ${DNSMASQ_D_DIR}"
|
||||
fi
|
||||
|
|
171
installers/plugin_helper.sh
Executable file
171
installers/plugin_helper.sh
Executable file
|
@ -0,0 +1,171 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# PluginInstaller helper for RaspAP
|
||||
# @author billz
|
||||
# license: GNU General Public License v3.0
|
||||
|
||||
# Exit on error
|
||||
set -o errexit
|
||||
|
||||
readonly raspap_user="www-data"
|
||||
|
||||
[ $# -lt 1 ] && { echo "Usage: $0 <action> [parameters...]"; exit 1; }
|
||||
|
||||
action="$1" # action to perform
|
||||
shift 1
|
||||
|
||||
case "$action" in
|
||||
|
||||
"sudoers")
|
||||
[ $# -ne 1 ] && { echo "Usage: $0 sudoers <file>"; exit 1; }
|
||||
file="$1"
|
||||
plugin_name=$(basename "$file")
|
||||
dest="/etc/sudoers.d/${plugin_name}"
|
||||
|
||||
mv "$file" "$dest" || { echo "Error: Failed to move $file to $dest."; exit 1; }
|
||||
|
||||
chown root:root "$dest" || { echo "Error: Failed to set ownership for $dest."; exit 1; }
|
||||
chmod 0440 "$dest" || { echo "Error: Failed to set permissions for $dest."; exit 1; }
|
||||
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
"packages")
|
||||
[ $# -lt 1 ] && { echo "Usage: $0 packages <apt_packages...>"; exit 1; }
|
||||
|
||||
echo "Installing APT packages..."
|
||||
for package in "$@"; do
|
||||
echo "Installing package: $package"
|
||||
apt-get install -y "$package" || { echo "Error: Failed to install $package."; exit 1; }
|
||||
done
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
"user")
|
||||
[ $# -lt 2 ] && { echo "Usage: $0 user <username> <password>."; exit 1; }
|
||||
|
||||
username=$1
|
||||
password=$2
|
||||
|
||||
if id "$username" &>/dev/null; then # user already exists
|
||||
echo "OK"
|
||||
exit 0
|
||||
fi
|
||||
# create the user without shell access
|
||||
useradd -r -s /bin/false "$username"
|
||||
|
||||
# set password non-interactively
|
||||
echo "$username:$password" | chpasswd
|
||||
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
"config")
|
||||
[ $# -lt 2 ] && { echo "Usage: $0 config <source> <destination>"; exit 1; }
|
||||
|
||||
source=$1
|
||||
destination=$2
|
||||
|
||||
if [ ! -f "$source" ]; then
|
||||
echo "Source file $source does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$destination")"
|
||||
cp "$source" "$destination"
|
||||
chown -R $raspap_user:$raspap_user "$destination"
|
||||
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
"javascript")
|
||||
[ $# -lt 2 ] && { echo "Usage: $0 javascript <source> <destination>"; exit 1; }
|
||||
|
||||
source=$1
|
||||
destination=$2
|
||||
|
||||
if [ ! -f "$source" ]; then
|
||||
echo "Source file $source does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$destination" ]; then
|
||||
mkdir -p "$destination"
|
||||
fi
|
||||
|
||||
cp "$source" "$destination"
|
||||
chown -R $raspap_user:$raspap_user "$destination"
|
||||
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
|
||||
"plugin")
|
||||
[ $# -lt 2 ] && { echo "Usage: $0 plugin <source> <destination>"; exit 1; }
|
||||
|
||||
source=$1
|
||||
destination=$2
|
||||
|
||||
if [ ! -d "$source" ]; then
|
||||
echo "Source directory $source does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
plugin_dir=$(dirname "$destination")
|
||||
if [ ! -d "$plugin_dir" ]; then
|
||||
mkdir -p "$plugin_dir"
|
||||
fi
|
||||
|
||||
cp -R "$source" "$destination"
|
||||
chown -R $raspap_user:$raspap_user "$plugin_dir"
|
||||
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
"keys")
|
||||
[ $# -ne 4 ] && { echo "Usage: $0 keys <key_url> <keyring> <repo> <sources>"; exit 1; }
|
||||
|
||||
key_url="$1"
|
||||
keyring="$2"
|
||||
repo="$3"
|
||||
list_file="$4"
|
||||
|
||||
# add repository GPG key if it doesn't already exist
|
||||
if [ ! -f "$keyring" ]; then
|
||||
echo "Downloading GPG key from $key_url..."
|
||||
curl -fsSL "$key_url" | sudo tee "$keyring" > /dev/null || { echo "Error: Failed to download GPG key."; exit 1; }
|
||||
else
|
||||
echo "Repository GPG key already exists at $keyring"
|
||||
fi
|
||||
|
||||
# add repository list if not present
|
||||
if [ ! -f "$list_file" ]; then
|
||||
echo "Adding repository $repo to sources list"
|
||||
curl -fsSL "$repo" | sudo tee "$list_file" > /dev/null || { echo "Error: Failed to add repository to sources list."; exit 1; }
|
||||
update_required=1
|
||||
else
|
||||
echo "Repository already exists in sources list"
|
||||
fi
|
||||
|
||||
# update apt package list if required
|
||||
if [ "$update_required" == "1" ]; then
|
||||
sudo apt-get update || { echo "Error: Failed to update apt"; exit 1; }
|
||||
fi
|
||||
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Invalid action: $action"
|
||||
echo "Usage: $0 <action> [parameters...]"
|
||||
echo "Actions:"
|
||||
echo " sudoers <file> Install a sudoers file"
|
||||
echo " packages <packages> Install aptitude package(s)"
|
||||
echo " user <name> <password> Add user non-interactively"
|
||||
echo " config <source <destination> Applies a config file"
|
||||
echo " javascript <source> <destination> Applies a JavaScript file"
|
||||
echo " plugin <source> <destination> Copies a plugin directory"
|
||||
echo " keys <key_url> <keyring> <repo> <sources> Installs a GPG key for a third-party repo"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
|
@ -78,3 +78,5 @@ www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/wg-*.key
|
|||
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:/usr/bin/vnstat *
|
||||
www-data ALL=(ALL) NOPASSWD:/usr/sbin/visudo -cf *
|
||||
www-data ALL=(ALL) NOPASSWD:/etc/raspap/plugins/plugin_helper.sh
|
||||
|
|
|
@ -54,6 +54,7 @@ OPTIONS:
|
|||
-p, --path <path> Used with -d, --update, sets the existing install path
|
||||
-i, --insiders Installs from the Insiders Edition (RaspAP/raspap-insiders)
|
||||
-m, --minwrite Configures a microSD card for minimum write operation
|
||||
-k, --check <flag> Sets the connectivity check flag (default is 1=perform check)
|
||||
-v, --version Outputs release info and exits
|
||||
-n, --uninstall Loads and executes the uninstaller
|
||||
-h, --help Outputs usage notes and exits
|
||||
|
@ -84,8 +85,13 @@ function _main() {
|
|||
# set defaults
|
||||
repo="RaspAP/raspap-webgui" # override with -r, --repo option
|
||||
repo_common="$repo"
|
||||
|
||||
_parse_params "$@"
|
||||
_setup_colors
|
||||
if [ "${check}" == 1 ]; then
|
||||
_check_internet
|
||||
fi
|
||||
|
||||
_log_output
|
||||
_load_installer
|
||||
}
|
||||
|
@ -103,6 +109,7 @@ function _parse_params() {
|
|||
minwrite=0
|
||||
acctoken=""
|
||||
path=""
|
||||
check=1
|
||||
|
||||
while :; do
|
||||
case "${1-}" in
|
||||
|
@ -173,6 +180,10 @@ function _parse_params() {
|
|||
path="$2"
|
||||
shift
|
||||
;;
|
||||
-k|--check)
|
||||
check="$2"
|
||||
shift
|
||||
;;
|
||||
-v|--version)
|
||||
_version
|
||||
;;
|
||||
|
@ -233,11 +244,25 @@ function _display_welcome() {
|
|||
|
||||
# Fetch latest release from GitHub or RaspAP Installer API
|
||||
function _get_release() {
|
||||
readonly RASPAP_LATEST=$(curl -s "https://api.github.com/repos/$repo/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")' )
|
||||
local response
|
||||
local host="api.github.com"
|
||||
response=$(curl -s "https://$host/repos/$repo/releases/latest")
|
||||
|
||||
if echo "$response" | grep -q 'API rate limit exceeded'; then
|
||||
_install_status 1 "GitHub API rate limit exceeded. Try again later or use a GitHub token."
|
||||
return 1
|
||||
fi
|
||||
readonly RASPAP_LATEST=$(echo "$response" | grep -Po '"tag_name": "\K.*?(?=")')
|
||||
|
||||
if [ -z "$RASPAP_LATEST" ]; then
|
||||
_install_status 1 "Failed to fetch latest release. Check network connectivity."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$insiders" == 1 ]; then
|
||||
repo="RaspAP/raspap-insiders"
|
||||
repo_common="RaspAP/raspap-webgui"
|
||||
readonly RASPAP_INSIDERS_LATEST=$(curl -s "https://api.raspap.com/repos/RaspAP/raspap-insiders/releases/latest/" | grep -Po '"tag_name": "\K.*?(?=")' )
|
||||
readonly RASPAP_INSIDERS_LATEST=$(curl -s "https://api.raspap.com/repos/RaspAP/raspap-insiders/releases/latest/" | grep -Po '"tag_name": "\K.*?(?=")')
|
||||
readonly RASPAP_RELEASE="${RASPAP_INSIDERS_LATEST} Insiders"
|
||||
else
|
||||
readonly RASPAP_RELEASE="${RASPAP_LATEST}"
|
||||
|
@ -271,6 +296,40 @@ function _install_status() {
|
|||
esac
|
||||
}
|
||||
|
||||
# Checks connectivity to github.com
|
||||
function _check_internet() {
|
||||
component="Install"
|
||||
_install_log "Checking internet connectivity..."
|
||||
|
||||
# spinner frames
|
||||
local spinner='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
||||
local i=0
|
||||
|
||||
tput civis # hide cursor
|
||||
|
||||
# run check in background
|
||||
( curl -Is --connect-timeout 3 --max-time 15 https://github.com | head -n 1 | grep "HTTP/2 200" >/dev/null ) &
|
||||
local pid=$!
|
||||
|
||||
# display spinner while curl runs
|
||||
while kill -0 $pid 2>/dev/null; do
|
||||
printf "\r%s" "${spinner:i++%${#spinner}:1}"
|
||||
sleep 0.05
|
||||
done
|
||||
printf "\r"
|
||||
|
||||
# check exit status of curl
|
||||
wait $pid || exit_code=$?
|
||||
|
||||
tput cnorm # restore cursor
|
||||
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
_install_status 1 "No internet connection or unable to reach GitHub"
|
||||
exit 1
|
||||
fi
|
||||
_install_status 0
|
||||
}
|
||||
|
||||
function _update_system_packages() {
|
||||
_install_log "Updating sources"
|
||||
sudo apt-get update || _install_status 1 "Unable to update package list"
|
||||
|
|
Binary file not shown.
|
@ -25,8 +25,8 @@ msgstr "RaspAP Wifi Configuration Portal"
|
|||
msgid "Toggle navigation"
|
||||
msgstr "Toggle navigation"
|
||||
|
||||
msgid "RaspAP Wifi Portal"
|
||||
msgstr "RaspAP Wifi Portal"
|
||||
msgid "RaspAP Admin Panel"
|
||||
msgstr "RaspAP Admin Panel"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr "Dashboard"
|
||||
|
@ -244,8 +244,8 @@ msgstr "Frequency"
|
|||
msgid "Link Quality"
|
||||
msgstr "Link Quality"
|
||||
|
||||
msgid "Information provided by ip and iw and from system"
|
||||
msgstr "Information provided by ip and iw and from system"
|
||||
msgid "Information provided by raspap.system"
|
||||
msgstr "Information provided by raspap.system"
|
||||
|
||||
msgid "No MAC Address Found"
|
||||
msgstr "No MAC Address Found"
|
||||
|
@ -283,9 +283,53 @@ msgstr "Connected Devices"
|
|||
msgid "Client: Ethernet cable"
|
||||
msgstr "Client: Ethernet cable"
|
||||
|
||||
msgid "Current status"
|
||||
msgstr "Current status"
|
||||
|
||||
msgid "Ethernet"
|
||||
msgstr "Ethernet"
|
||||
|
||||
msgid "Repeater"
|
||||
msgstr "Repeater"
|
||||
|
||||
msgid "Tethering"
|
||||
msgstr "Tethering"
|
||||
|
||||
msgid "Cellular"
|
||||
msgstr "Cellular"
|
||||
|
||||
msgid "AP"
|
||||
msgstr "AP"
|
||||
|
||||
msgid "Bridged"
|
||||
msgstr "Bridged"
|
||||
|
||||
msgid "Adblock"
|
||||
msgstr "Adblock"
|
||||
|
||||
msgid "VPN"
|
||||
msgstr "VPN"
|
||||
|
||||
msgid "Firewall"
|
||||
msgstr "Firewall"
|
||||
|
||||
msgid "Netmask"
|
||||
msgstr "Netmask"
|
||||
|
||||
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] "client"
|
||||
msgstr[1] "clients"
|
||||
|
||||
msgid "Client: Smartphone (USB tethering)"
|
||||
msgstr "Client: Smartphone (USB tethering)"
|
||||
|
||||
|
@ -334,6 +378,16 @@ msgstr "Signal strength"
|
|||
msgid "No Client device or not yet configured"
|
||||
msgstr "No Client device or not yet configured"
|
||||
|
||||
#: includes/footer.php
|
||||
msgid "Created by the <a href=\"%s\" target=\"_blank\" rel=\"noopener\">%s</a>"
|
||||
msgstr "Created by the <a href=\"%s\" target=\"_blank\" rel=\"noopener\">%s</a>"
|
||||
|
||||
msgid "RaspAP Team"
|
||||
msgstr "RaspAP Team"
|
||||
|
||||
msgid "Get Insiders"
|
||||
msgstr "Get Insiders"
|
||||
|
||||
#: includes/dhcp.php
|
||||
msgid "DHCP server settings"
|
||||
msgstr "DHCP server settings"
|
||||
|
@ -920,6 +974,81 @@ msgstr "Changing log limit size to %s KB"
|
|||
msgid "Information provided by raspap.sysinfo"
|
||||
msgstr "Information provided by raspap.sysinfo"
|
||||
|
||||
msgid "The following user plugins are available to extend RaspAP's functionality."
|
||||
msgstr "The following user plugins are available to extend RaspAP's functionality."
|
||||
|
||||
msgid "Choose <strong>Details</strong> for more information and to install a plugin."
|
||||
msgstr "Choose <strong>Details</strong> for more information and to install a plugin."
|
||||
|
||||
msgid "Network error"
|
||||
msgstr "Network error"
|
||||
|
||||
msgid "Unable to load plugins"
|
||||
msgstr "Unable to load plugins"
|
||||
|
||||
msgid "Reload"
|
||||
msgstr "Reload"
|
||||
|
||||
msgid "and try again"
|
||||
msgstr "and try again"
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr "Plugins"
|
||||
|
||||
msgid "Plugin details"
|
||||
msgstr "Plugin details"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
msgid "Version"
|
||||
msgstr "Version"
|
||||
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
msgid "Plugin source"
|
||||
msgstr "Plugin source"
|
||||
|
||||
msgid "Author"
|
||||
msgstr "Author"
|
||||
|
||||
msgid "License"
|
||||
msgstr "License"
|
||||
|
||||
msgid "Language locale"
|
||||
msgstr "Language locale"
|
||||
|
||||
msgid "Configuration files"
|
||||
msgstr "Configuration files"
|
||||
|
||||
msgid "Dependencies"
|
||||
msgstr "Dependencies"
|
||||
|
||||
msgid "Permissions"
|
||||
msgstr "Permissions"
|
||||
|
||||
msgid "Non-privileged users"
|
||||
msgstr "Non-privileged users"
|
||||
|
||||
msgid "Install now"
|
||||
msgstr "Install now"
|
||||
|
||||
msgid "Installing plugin"
|
||||
msgstr "Installing plugin"
|
||||
|
||||
msgid "Plugin installation in progress..."
|
||||
msgstr "Plugin installation in progress..."
|
||||
|
||||
msgid "Plugin install completed."
|
||||
msgstr "Plugin install completed."
|
||||
|
||||
msgid "Details"
|
||||
msgstr "Details"
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Installed"
|
||||
|
||||
#: includes/data_usage.php
|
||||
msgid "Data usage"
|
||||
msgstr "Data usage"
|
||||
|
@ -1566,3 +1695,23 @@ msgstr "Restarting restapi.service"
|
|||
msgid "Information provided by restapi.service"
|
||||
msgstr "Information provided by restapi.service"
|
||||
|
||||
#: includes/login.php
|
||||
|
||||
msgid "Session Expired"
|
||||
msgstr "Session Expired"
|
||||
|
||||
msgid "Your session has expired. Please login to continue."
|
||||
msgstr "Your session has expired. Please login to continue."
|
||||
|
||||
msgid "Login"
|
||||
msgstr "Login"
|
||||
|
||||
msgid "Administrator login"
|
||||
msgstr "Administrator login"
|
||||
|
||||
msgid "Forgot password"
|
||||
msgstr "Forgot password"
|
||||
|
||||
msgid "Login failed"
|
||||
msgstr "Login failed"
|
||||
|
||||
|
|
1
plugins
Submodule
1
plugins
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 38331709b6c8198c0dbf1c7c85cb52b8ae3ea79a
|
|
@ -15,12 +15,6 @@ namespace RaspAP\Auth;
|
|||
|
||||
class HTTPAuth
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string $realm
|
||||
*/
|
||||
public $realm = 'Authentication Required';
|
||||
|
||||
/**
|
||||
* Stored login credentials
|
||||
* @var array $auth_config
|
||||
|
@ -57,15 +51,11 @@ class HTTPAuth
|
|||
public function authenticate()
|
||||
{
|
||||
if (!$this->isLogged()) {
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('WWW-Authenticate: Basic realm="'.$this->realm.'"');
|
||||
if (function_exists('http_response_code')) {
|
||||
// http_response_code will respond with proper HTTP version
|
||||
http_response_code(401);
|
||||
} else {
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
$redirectUrl = $_SERVER['REQUEST_URI'];
|
||||
if (strpos($redirectUrl, '/login') === false) {
|
||||
header('Location: /login?action=' . urlencode($redirectUrl));
|
||||
exit();
|
||||
}
|
||||
exit('Not authorized'.PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,6 +74,21 @@ class HTTPAuth
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs out the administrative user
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
session_regenerate_id(true); // generate a new session id
|
||||
session_unset(); // unset all session variables
|
||||
session_destroy(); // destroy the session
|
||||
$redirectUrl = $_SERVER['REQUEST_URI'];
|
||||
if (strpos($redirectUrl, '/login') === false) {
|
||||
header('Location: /login?action=' . urlencode($redirectUrl));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the current authentication config
|
||||
* return array $config
|
||||
|
|
|
@ -37,7 +37,7 @@ class DotEnv
|
|||
}
|
||||
}
|
||||
} else {
|
||||
throw new Exception(".env file '{$this->envFile}' not found.");
|
||||
throw new \Exception(".env file '{$this->envFile}' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ class DotEnv
|
|||
file_put_contents("/tmp/.env", $content);
|
||||
system('sudo mv /tmp/.env '.$this->envFile, $result);
|
||||
if ($result !== 0) {
|
||||
throw new Exception("Unable to move .env file: ". $this->envFile);
|
||||
throw new \Exception("Unable to move .env file: ". $this->envFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ class DotEnv
|
|||
{
|
||||
exec('sudo touch '. escapeshellarg($this->envFile), $output, $result);
|
||||
if ($result !== 0) {
|
||||
throw new Exception("Unable to create .env file: ". $this->envFile);
|
||||
throw new \Exception("Unable to create .env file: ". $this->envFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class IwParser
|
|||
"(no IR)"
|
||||
];
|
||||
$excluded_pattern = implode('|', array_map('preg_quote', $excluded));
|
||||
$pattern = '/\*\s+(\d+)\s+MHz \[(\d+)\] \(([\d.]+) dBm\)\s(?!' .$excluded_pattern. ')/';
|
||||
$pattern = '/\*\s+([\d.]+)\s+MHz \[(\d+)\] \(([\d.]+) dBm\)\s(?!' .$excluded_pattern. ')/';
|
||||
$supportedFrequencies = [];
|
||||
|
||||
// Match iw_output containing supported frequencies
|
||||
|
|
547
src/RaspAP/Plugins/PluginInstaller.php
Normal file
547
src/RaspAP/Plugins/PluginInstaller.php
Normal file
|
@ -0,0 +1,547 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Installer class
|
||||
*
|
||||
* @description Class to handle installation of user plugins
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RaspAP\Plugins;
|
||||
|
||||
class PluginInstaller
|
||||
{
|
||||
private static $instance = null;
|
||||
private $pluginName;
|
||||
private $manifestRaw;
|
||||
private $tempSudoers;
|
||||
private $destSudoers;
|
||||
private $refModules;
|
||||
private $rootPath;
|
||||
private $pluginsManifest;
|
||||
private $repoPublic;
|
||||
private $helperScriptPath;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pluginPath = 'plugins';
|
||||
$this->manifestRaw = '/blob/master/manifest.json?raw=true';
|
||||
$this->tempSudoers = '/tmp/090_';
|
||||
$this->destSudoers = '/etc/sudoers.d/';
|
||||
$this->refModules = '/refs/heads/master/.gitmodules';
|
||||
$this->rootPath = $_SERVER['DOCUMENT_ROOT'];
|
||||
$this->pluginsManifest = '/plugins/manifest.json';
|
||||
$this->repoPublic = $this->getRepository();
|
||||
$this->helperScriptPath = RASPI_CONFIG.'/plugins/plugin_helper.sh';
|
||||
}
|
||||
|
||||
// Returns a single instance of PluginInstaller
|
||||
public static function getInstance(): PluginInstaller
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new PluginInstaller();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns availble user plugin details from a manifest.json file
|
||||
*
|
||||
* @return array $plugins
|
||||
*/
|
||||
public function getUserPlugins()
|
||||
{
|
||||
try {
|
||||
$manifestPath = $this->rootPath . $this->pluginsManifest;
|
||||
if (!file_exists($manifestPath)) {
|
||||
throw new \Exception("Manifest file not found at " . $manifestPath);
|
||||
}
|
||||
|
||||
// decode manifest file contents
|
||||
$manifestContents = file_get_contents($manifestPath);
|
||||
$manifestData = json_decode($manifestContents, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \Exception("Error parsing manifest.json: " . json_last_error_msg());
|
||||
}
|
||||
|
||||
// fetch installed plugins
|
||||
$installedPlugins = $this->getPlugins();
|
||||
$plugins = [];
|
||||
|
||||
foreach ($manifestData as $pluginManifest) {
|
||||
$pluginEntries = [];
|
||||
|
||||
foreach ($pluginManifest as $plugin) {
|
||||
$installed = false;
|
||||
|
||||
if (!empty($plugin['namespace'])) {
|
||||
foreach ($installedPlugins as $installedPlugin) {
|
||||
if (strpos($installedPlugin['class'], $plugin['namespace']) !== false) {
|
||||
$installed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$pluginEntries[] = [
|
||||
'manifest' => $plugin,
|
||||
'installed' => $installed
|
||||
];
|
||||
}
|
||||
$plugins[] = $pluginEntries;
|
||||
}
|
||||
return array_merge(...$plugins);
|
||||
} catch (\Exception $e) {
|
||||
error_log("An error occurred: " . $e->getMessage());
|
||||
throw $e; // re-throw to global ExceptionHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of installed plugins in pluginPath
|
||||
*
|
||||
* @param string|null $path; optional path to search for plugins. Defaults to $this->pluginPath.
|
||||
* @return array $plugins
|
||||
*/
|
||||
public function getPlugins(?string $path = null): array
|
||||
{
|
||||
$plugins = [];
|
||||
$pluginPath = $path ?? $this->pluginPath;
|
||||
|
||||
if (file_exists($pluginPath)) {
|
||||
$directories = scandir($pluginPath);
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
if ($directory === '.' || $directory === '..') {
|
||||
continue;
|
||||
}
|
||||
$pluginClass = "RaspAP\\Plugins\\$directory\\$directory";
|
||||
$pluginFile = "$pluginPath/$directory/$directory.php";
|
||||
|
||||
if (file_exists($pluginFile)) {
|
||||
if ($path === 'plugins-available') {
|
||||
require_once $pluginFile;
|
||||
}
|
||||
if (class_exists($pluginClass)) {
|
||||
$plugins[] = [
|
||||
'class' => $pluginClass,
|
||||
'installPath' => $pluginPath
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a plugin by either extracting an archive or creating a symlink,
|
||||
* then performs required actions as defined in the plugin manifest
|
||||
*
|
||||
* @param string $pluginUri
|
||||
* @param string $pluginVersion
|
||||
* @param string $installPath
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function installPlugin(string $pluginUri, string $pluginVersion, string $installPath): bool
|
||||
{
|
||||
$tempFile = null;
|
||||
$extractDir = null;
|
||||
$pluginDir = null;
|
||||
|
||||
try {
|
||||
if ($installPath === 'plugins-available') {
|
||||
// extract plugin name from URI
|
||||
$pluginName = basename($pluginUri);
|
||||
$sourcePath = $this->rootPath . '/plugins-available/' . $pluginName;
|
||||
$targetPath = $this->rootPath . '/plugins/' . $pluginName;
|
||||
|
||||
if (!is_dir($sourcePath)) {
|
||||
throw new \Exception("Plugin '$pluginName' not found in plugins-available");
|
||||
}
|
||||
|
||||
// ensure target does not already exist
|
||||
if (file_exists($targetPath)) {
|
||||
throw new \Exception("Plugin '$pluginName' is already installed.");
|
||||
}
|
||||
|
||||
// create symlink
|
||||
if (!symlink($sourcePath, $targetPath)) {
|
||||
throw new \Exception("Failed to symlink '$pluginName' to plugins/");
|
||||
}
|
||||
$pluginDir = $targetPath;
|
||||
} else {
|
||||
// fetch and extract the plugin archive
|
||||
$archiveUrl = rtrim($pluginUri, '/') . '/archive/refs/tags/' .$pluginVersion.'.zip';
|
||||
list($tempFile, $extractDir, $pluginDir) = $this->getPluginArchive($archiveUrl);
|
||||
}
|
||||
|
||||
$manifest = $this->parseManifest($pluginDir);
|
||||
$this->pluginName = preg_replace('/\s+/', '', $manifest['name']);
|
||||
$rollbackStack = []; // Store actions to rollback on failure
|
||||
|
||||
try {
|
||||
if (!empty($manifest['sudoers'])) {
|
||||
$this->addSudoers($manifest['sudoers']);
|
||||
$rollbackStack[] = 'removeSudoers';
|
||||
}
|
||||
if (!empty($manifest['keys'])) {
|
||||
$this->installRepositoryKeys($manifest['keys']);
|
||||
$rollbackStack[] = 'uninstallRepositoryKeys';
|
||||
}
|
||||
if (!empty($manifest['dependencies'])) {
|
||||
$this->installDependencies($manifest['dependencies']);
|
||||
$rollbackStack[] = 'uninstallDependencies';
|
||||
}
|
||||
if (!empty($manifest['user_nonprivileged'])) {
|
||||
$this->createUser($manifest['user_nonprivileged']);
|
||||
$rollbackStack[] = 'deleteUser';
|
||||
}
|
||||
if (!empty($manifest['configuration'])) {
|
||||
$this->copyConfigFiles($manifest['configuration'], $pluginDir);
|
||||
$rollbackStack[] = 'removeConfigFiles';
|
||||
}
|
||||
if (!empty($manifest['javascript'])) {
|
||||
$this->copyJavaScriptFiles($manifest['javascript'], $pluginDir);
|
||||
$rollbackStack[] = 'removeJavaScript';
|
||||
}
|
||||
if ($installPath === 'plugins') {
|
||||
$this->copyPluginFiles($pluginDir, $this->rootPath);
|
||||
$rollbackStack[] = 'removePluginFiles';
|
||||
}
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Installation step failed: ' . $e->getMessage());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
error_log('Plugin installation failed: ' . $e->getMessage());
|
||||
throw new \Exception($e->getMessage());
|
||||
} finally {
|
||||
if (isset($tempFile) && file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
if (isset($extractDir) && is_dir($extractDir)) {
|
||||
$this->deleteDir($extractDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds sudoers entries to a temp file and copies to /etc/sudoers.d/
|
||||
*
|
||||
* @param array $sudoers
|
||||
*/
|
||||
private function addSudoers(array $sudoers): void
|
||||
{
|
||||
$tmpSudoers = $this->tempSudoers . $this->pluginName;
|
||||
$destination = $this->destSudoers;
|
||||
$content = implode("\n", $sudoers);
|
||||
|
||||
if (file_put_contents($tmpSudoers, $content) === false) {
|
||||
throw new \Exception('Failed to update sudoers file.');
|
||||
}
|
||||
|
||||
$cmd = sprintf('sudo visudo -cf %s', escapeshellarg($tmpSudoers));
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'parsed ok') !== false) {
|
||||
$cmd = sprintf('sudo %s sudoers %s', escapeshellarg($this->helperScriptPath), escapeshellarg($tmpSudoers));
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception('Plugin helper failed to install sudoers.');
|
||||
}
|
||||
} else {
|
||||
throw new \Exception('Sudoers check failed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs plugin dependencies from the aptitude package repository
|
||||
*
|
||||
* @param array $dependencies
|
||||
*/
|
||||
private function installDependencies(array $dependencies): void
|
||||
{
|
||||
$packages = array_keys($dependencies);
|
||||
$packageList = implode(' ', $packages);
|
||||
|
||||
$cmd = sprintf('sudo %s packages %s', escapeshellarg($this->helperScriptPath), escapeshellarg($packageList));
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception('Plugin helper failed to install depedencies.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-priviledged Linux user
|
||||
*
|
||||
* @param array $user
|
||||
*/
|
||||
private function createUser(array $user): void
|
||||
{
|
||||
if (empty($user['name']) || empty($user['pass'])) {
|
||||
throw new \InvalidArgumentException('User name or password is missing.');
|
||||
}
|
||||
$username = escapeshellarg($user['name']);
|
||||
$password = escapeshellarg($user['pass']);
|
||||
|
||||
$cmd = sprintf('sudo %s user %s %s', escapeshellarg($this->helperScriptPath), $username, $password);
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception('Plugin helper failed to create user: ' . $user['name']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies plugin configuration files to their destination
|
||||
*
|
||||
* @param array $configurations
|
||||
* @param string $pluginDir
|
||||
*/
|
||||
private function copyConfigFiles(array $configurations, string $pluginDir): void
|
||||
{
|
||||
foreach ($configurations as $config) {
|
||||
$source = escapeshellarg($pluginDir . DIRECTORY_SEPARATOR . $config['source']);
|
||||
$destination = $config['destination'];
|
||||
|
||||
if (!str_starts_with($destination, '/')) {
|
||||
$destination = $this->rootPath . '/' . ltrim($destination, '/');
|
||||
}
|
||||
$destination = escapeshellarg($destination);
|
||||
$cmd = sprintf('sudo %s config %s %s', escapeshellarg($this->helperScriptPath), $source, $destination);
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception("Failed to copy configuration file: $source to $destination");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies plugin JavaScript files to their destination
|
||||
*
|
||||
* @param array $javascript
|
||||
* @param string $pluginDir
|
||||
*/
|
||||
private function copyJavaScriptFiles(array $javascript, string $pluginDir): void
|
||||
{
|
||||
foreach ($javascript as $js) {
|
||||
$source = escapeshellarg($pluginDir . DIRECTORY_SEPARATOR . $js);
|
||||
$destination = escapeshellarg($this->rootPath . DIRECTORY_SEPARATOR . 'app/js/plugins/');
|
||||
$cmd = sprintf('sudo %s javascript %s %s', escapeshellarg($this->helperScriptPath), $source, $destination);
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception("Failed to copy JavaScript file: $source");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies an extracted plugin directory from /tmp to /plugins
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
*/
|
||||
private function copyPluginFiles(string $source, string $destination): void
|
||||
{
|
||||
$source = escapeshellarg($source);
|
||||
$destination = escapeshellarg($destination . DIRECTORY_SEPARATOR .$this->pluginPath . DIRECTORY_SEPARATOR . $this->pluginName);
|
||||
$cmd = sprintf('sudo %s plugin %s %s', escapeshellarg($this->helperScriptPath), $source, $destination);
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception('Failed to copy plugin files to: ' . $destination);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs repository keys for third-party apt packages
|
||||
*
|
||||
* @param array $keys Array containing key_url, keyring, repo, and sources
|
||||
* @throws Exception on key installation failure
|
||||
*/
|
||||
public function installRepositoryKeys(array $keys): void
|
||||
{
|
||||
if (!is_array($keys)) {
|
||||
throw new \Exception("Invalid repository key structure: array expected");
|
||||
}
|
||||
foreach ($keys as $keyData) {
|
||||
if (!isset($keyData['key_url'], $keyData['keyring'], $keyData['repo'], $keyData['sources'])) {
|
||||
throw new \Exception("Invalid repository key structure: " . json_encode($keyData));
|
||||
}
|
||||
$cmd = sprintf(
|
||||
'sudo %s keys %s %s %s %s',
|
||||
escapeshellarg($this->helperScriptPath),
|
||||
escapeshellarg($keyData['key_url']),
|
||||
escapeshellarg($keyData['keyring']),
|
||||
escapeshellarg($keyData['repo']),
|
||||
escapeshellarg($keyData['sources'])
|
||||
);
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception("Failed to add repository and key");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and returns a downloaded plugin manifest
|
||||
*
|
||||
* @param string $pluginDir
|
||||
* @return array json
|
||||
*/
|
||||
private function parseManifest($pluginDir): array
|
||||
{
|
||||
$manifestPath = $pluginDir . DIRECTORY_SEPARATOR . 'manifest.json';
|
||||
if (!file_exists($manifestPath)) {
|
||||
throw new \Exception('manifest.json file not found.');
|
||||
}
|
||||
$json = file_get_contents($manifestPath);
|
||||
$manifest = json_decode($json, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \Exception('Failed to parse manifest.json: ' . json_last_error_msg());
|
||||
}
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a plugin archive and extracts it to /tmp
|
||||
*
|
||||
* @param string $archiveUrl
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getPluginArchive(string $archiveUrl): array
|
||||
{
|
||||
$tempFile = '';
|
||||
$extractDir = '';
|
||||
|
||||
try {
|
||||
$tempFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('plugin_', true) . '.zip';
|
||||
$extractDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('plugin_', true);
|
||||
|
||||
$data = @file_get_contents($archiveUrl); // suppress PHP warnings for better exception handling
|
||||
|
||||
if ($data === false) {
|
||||
$error = error_get_last();
|
||||
throw new \Exception('Failed to download archive: ' . ($error['message'] ?? 'Unknown error'));
|
||||
}
|
||||
|
||||
file_put_contents($tempFile, $data);
|
||||
|
||||
if (!mkdir($extractDir) && !is_dir($extractDir)) {
|
||||
throw new \Exception('Failed to create temp directory.');
|
||||
}
|
||||
|
||||
$cmd = escapeshellcmd("unzip -o $tempFile -d $extractDir");
|
||||
$output = shell_exec($cmd);
|
||||
if ($output === null) {
|
||||
throw new \Exception('Failed to extract plugin archive.');
|
||||
}
|
||||
|
||||
$extractedDirs = glob($extractDir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR);
|
||||
if (empty($extractedDirs)) {
|
||||
throw new \Exception('No directories found in plugin archive.');
|
||||
}
|
||||
|
||||
$pluginDir = $extractedDirs[0];
|
||||
|
||||
return [$tempFile, $extractDir, $pluginDir];
|
||||
} catch (\Exception $e) {
|
||||
if (!empty($tempFile) && file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
if (!empty($extractDir) && is_dir($extractDir)) {
|
||||
rmdir($extractDir);
|
||||
}
|
||||
throw new \Exception('Error occurred during plugin archive retrieval: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteDir(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
$items = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($items as $item) {
|
||||
$itemPath = $dir . DIRECTORY_SEPARATOR . $item;
|
||||
is_dir($itemPath) ? $this->deleteDir($itemPath) : unlink($itemPath);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of available plugins formatted as an HTML table
|
||||
*
|
||||
* @param array $plugins
|
||||
* @return string $html
|
||||
*/
|
||||
public function getHTMLPluginsTable(array $plugins): string
|
||||
{
|
||||
$html = '<table class="table table-striped table-hover">';
|
||||
$html .= '<thead><tr>';
|
||||
$html .= '<th scope="col">Name</th>';
|
||||
$html .= '<th scope="col">Version</th>';
|
||||
$html .= '<th scope="col">Description</th>';
|
||||
$html .= '<th scope="col"></th>';
|
||||
$html .= '</tr></thead><tbody>';
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
$manifestData = $plugin['manifest'] ?? [];
|
||||
$installed = $plugin['installed'] ?? false;
|
||||
$manifest = htmlspecialchars(json_encode($manifestData), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
if ($installed === true) {
|
||||
$button = '<button type="button" class="btn btn-outline btn-primary btn-sm text-nowrap"
|
||||
name="plugin-details" data-bs-toggle="modal" data-bs-target="#install-user-plugin"
|
||||
data-plugin-manifest="' .$manifest. '" data-plugin-installed="' .$installed. '">' . _("Installed") .'</button>';
|
||||
} elseif (!RASPI_MONITOR_ENABLED) {
|
||||
$button = '<button type="button" class="btn btn-outline btn-primary btn-sm text-nowrap"
|
||||
name="install-plugin" data-bs-toggle="modal" data-bs-target="#install-user-plugin"
|
||||
data-plugin-manifest="' .$manifest. '" data-repo-public="' .$this->repoPublic. '">' . _("Details") .'</button>';
|
||||
}
|
||||
|
||||
$icon = htmlspecialchars($manifestData['icon'] ?? '');
|
||||
$pluginDocs = htmlspecialchars($manifestData['plugin_docs'] ?? '');
|
||||
$nameText = htmlspecialchars($manifestData['name'] ?? 'Unknown Plugin');
|
||||
$name = '<i class="' .$icon. ' link-secondary me-2"></i><a href="'
|
||||
.$pluginDocs
|
||||
.'" target="_blank">'
|
||||
.$nameText. '</a>';
|
||||
|
||||
$version = htmlspecialchars($manifestData['version'] ?? 'N/A');
|
||||
$description = htmlspecialchars($manifestData['description'] ?? 'No description available');
|
||||
|
||||
$html .= '<tr><td>' .$name. '</td>';
|
||||
$html .= '<td>' .$version. '</td>';
|
||||
$html .= '<td>' .$description. '</td>';
|
||||
$html .= '<td>' .$button. '</td></tr>';
|
||||
}
|
||||
$html .= '</tbody></table>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines remote repository of installed application
|
||||
*
|
||||
* @return boolean; true if public repo
|
||||
*/
|
||||
public function getRepository(): bool
|
||||
{
|
||||
$output = [];
|
||||
exec('git -C ' . escapeshellarg($this->rootPath) . ' remote -v', $output);
|
||||
|
||||
foreach ($output as $line) {
|
||||
if (preg_match('#github\.com/RaspAP/(raspap-\w+)#', $line, $matches)) {
|
||||
$repo = $matches[1];
|
||||
$public = ($repo === 'raspap-webgui');
|
||||
return $public;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -95,48 +95,50 @@ class Sysinfo
|
|||
|
||||
/*
|
||||
* Returns RPi Model and PCB Revision from Pi Revision Code (cpuinfo)
|
||||
* @see http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/
|
||||
* @see https://github.com/raspberrypi/documentation/blob/develop/documentation/asciidoc/computers/raspberry-pi/revision-codes.adoc
|
||||
*/
|
||||
public function rpiRevision()
|
||||
{
|
||||
$revisions = array(
|
||||
'0002' => 'Model B Revision 1.0',
|
||||
'0003' => 'Model B Revision 1.0 + ECN0001',
|
||||
'0004' => 'Model B Revision 2.0 (256 MB)',
|
||||
'0005' => 'Model B Revision 2.0 (256 MB)',
|
||||
'0006' => 'Model B Revision 2.0 (256 MB)',
|
||||
'0007' => 'Model A',
|
||||
'0008' => 'Model A',
|
||||
'0009' => 'Model A',
|
||||
'000d' => 'Model B Revision 2.0 (512 MB)',
|
||||
'000e' => 'Model B Revision 2.0 (512 MB)',
|
||||
'000f' => 'Model B Revision 2.0 (512 MB)',
|
||||
'0010' => 'Model B+',
|
||||
'0013' => 'Model B+',
|
||||
'0011' => 'Compute Module',
|
||||
'0012' => 'Model A+',
|
||||
'a01041' => 'a01041',
|
||||
'a21041' => 'a21041',
|
||||
'900092' => 'PiZero 1.2',
|
||||
'900093' => 'PiZero 1.3',
|
||||
'9000c1' => 'PiZero W',
|
||||
'a02082' => 'Pi 3 Model B',
|
||||
'a22082' => 'Pi 3 Model B',
|
||||
'a32082' => 'Pi 3 Model B',
|
||||
'a52082' => 'Pi 3 Model B',
|
||||
'a020d3' => 'Pi 3 Model B+',
|
||||
'a220a0' => 'Compute Module 3',
|
||||
'a020a0' => 'Compute Module 3',
|
||||
'0002' => 'Raspberry Pi Model B Rev 1.0',
|
||||
'0003' => 'Raspberry Pi Model B Rev 1.0',
|
||||
'0004' => 'Raspberry Pi Model B Rev 2.0',
|
||||
'0005' => 'Raspberry Pi Model B Rev 2.0',
|
||||
'0006' => 'Raspberry Pi Model B Rev 2.0',
|
||||
'0007' => 'Raspberry Pi Model A',
|
||||
'0008' => 'Raspberry Pi Model A',
|
||||
'0009' => 'Raspberry Pi Model A',
|
||||
'000d' => 'Raspberry Pi Model B Rev 2.0',
|
||||
'000e' => 'Raspberry Pi Model B Rev 2.0',
|
||||
'000f' => 'Raspberry Pi Model B Rev 2.0',
|
||||
'0010' => 'Raspberry Pi Model B+',
|
||||
'0013' => 'Raspberry Pi Model B+',
|
||||
'0011' => 'Compute Module 1',
|
||||
'0012' => 'Raspberry Pi Model A+',
|
||||
'a01041' => 'Raspberry Pi 2 Model B',
|
||||
'a21041' => 'Raspberry Pi 2 Model B',
|
||||
'900092' => 'Raspberry Pi Zero 1.2',
|
||||
'900093' => 'Raspberry Pi Zero 1.3',
|
||||
'9000c1' => 'Raspberry Pi Zero W',
|
||||
'a02082' => 'Raspberry Pi 3 Model B',
|
||||
'a22082' => 'Raspberry Pi 3 Model B',
|
||||
'a32082' => 'Raspberry Pi 3 Model B',
|
||||
'a52082' => 'Raspberry Pi 3 Model B+',
|
||||
'9020e0' => 'Raspberry Pi 3 Model A+',
|
||||
'a02100' => 'Compute Module 3+',
|
||||
'a03111' => 'Model 4B Revision 1.1 (1 GB)',
|
||||
'b03111' => 'Model 4B Revision 1.1 (2 GB)',
|
||||
'c03111' => 'Model 4B Revision 1.1 (4 GB)',
|
||||
'a03111' => 'Raspberry Pi 4 Model B (1 GB)',
|
||||
'b03111' => 'Raspberry Pi 4 Model B (2 GB)',
|
||||
'c03111' => 'Raspberry Pi 4 Model B (4 GB)',
|
||||
'b03112' => 'Raspberry Pi 4 Model B (2 GB)',
|
||||
'c03112' => 'Raspberry Pi 4 Model B (4 GB)',
|
||||
'd03114' => 'Raspberry Pi 4 Model B (8 GB)',
|
||||
'902120' => 'Raspberry Pi Zero 2 W',
|
||||
'a03140' => 'Compute Module 4 (1 GB)',
|
||||
'b03140' => 'Compute Module 4 (2 GB)',
|
||||
'c03140' => 'Compute Module 4 (4 GB)',
|
||||
'd03140' => 'Compute Module 4 (8 GB)',
|
||||
'c04170' => 'Pi 5 (4 GB)',
|
||||
'd04170' => 'Pi 5 (8 GB)'
|
||||
'c04170' => 'Raspberry Pi 5 (4 GB)',
|
||||
'd04170' => 'Raspberry Pi 5 (8 GB)'
|
||||
);
|
||||
|
||||
$cpuinfo_array = '';
|
||||
|
@ -155,5 +157,48 @@ class Sysinfo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if ad blocking is enabled and active
|
||||
*
|
||||
* @return bool $status
|
||||
*/
|
||||
public function adBlockStatus(): bool
|
||||
{
|
||||
exec('cat '. RASPI_ADBLOCK_CONFIG, $return);
|
||||
$arrConf = ParseConfig($return);
|
||||
if (sizeof($arrConf) > 0) {
|
||||
$enabled = true;
|
||||
}
|
||||
exec('pidof dnsmasq | wc -l', $dnsmasq);
|
||||
$dnsmasq_state = ($dnsmasq[0] > 0);
|
||||
$status = $dnsmasq_state && $enabled ? true : false;
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a VPN interface is active
|
||||
*
|
||||
* @return string $interface
|
||||
*/
|
||||
public function getActiveVpnInterface(): ?string
|
||||
{
|
||||
$output = shell_exec('ip a 2>/dev/null');
|
||||
if (!$output) {
|
||||
return null;
|
||||
}
|
||||
$vpnInterfaces = ['wg0', 'tun0', 'tailscale0'];
|
||||
|
||||
// interface must have an 'UP' status and an IP address
|
||||
foreach ($vpnInterfaces as $interface) {
|
||||
if (strpos($output, "$interface:") !== false) {
|
||||
if (preg_match("/\d+: $interface: .*<.*UP.*>/", $output) &&
|
||||
preg_match("/inet\b.*$interface/", $output)) {
|
||||
return $interface;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
136
src/RaspAP/Tokens/CSRFTokenizer.php
Normal file
136
src/RaspAP/Tokens/CSRFTokenizer.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CSRF tokenizer class
|
||||
*
|
||||
* @description CSRF tokenizer class for RaspAP
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* @author Martin Glaß <mail@glasz.org>
|
||||
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RaspAP\Tokens;
|
||||
|
||||
class CSRFTokenizer {
|
||||
|
||||
// Constructor
|
||||
public function __construct()
|
||||
{
|
||||
$this->ensureSession();
|
||||
|
||||
// ensure a CSRF token exists in the session
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$this->ensureCSRFSessionToken();
|
||||
header("Location: " .$_SERVER['REQUEST_URI']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($this->csrfValidateRequest()) {
|
||||
$token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
||||
if (!$this->CSRFValidate($token)) {
|
||||
$this->handleInvalidCSRFToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a CSRF token in the session
|
||||
*/
|
||||
public function ensureCSRFSessionToken(): void
|
||||
{
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a CSRF Token to form
|
||||
*/
|
||||
public function CSRFTokenFieldTag(): string
|
||||
{
|
||||
$token = htmlspecialchars($_SESSION['csrf_token']);
|
||||
return '<input type="hidden" name="csrf_token" value="' . $token . '">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSRF meta tag (for use with xhr, for example)
|
||||
*/
|
||||
public function CSRFMetaTag(): string
|
||||
{
|
||||
// if session has expired or user has logged out,
|
||||
// create a new session and token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$this->ensureSession();
|
||||
$this->ensureCSRFSessionToken();
|
||||
return $_SESSION['csrf_token'];
|
||||
} else {
|
||||
$token = htmlspecialchars($_SESSION['csrf_token']);
|
||||
return '<meta name="csrf_token" content="' . $token . '">';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a CSRF Token
|
||||
*
|
||||
* @param string $token
|
||||
*/
|
||||
public function CSRFValidate(string $token): bool
|
||||
{
|
||||
if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
|
||||
error_log('Session expired or CSRF token is missing.');
|
||||
header('Location: /login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$post_token = $token ?? null;
|
||||
$header_token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
|
||||
|
||||
if (empty($post_token) && is_null($header_token)) {
|
||||
error_log('CSRF token missing in the request');
|
||||
return false;
|
||||
}
|
||||
$request_token = $post_token ?: $header_token;
|
||||
|
||||
if (hash_equals($_SESSION['csrf_token'], $request_token)) {
|
||||
return true;
|
||||
} else {
|
||||
error_log('CSRF token mismatch');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the request be CSRF-validated?
|
||||
*/
|
||||
public function csrfValidateRequest(): bool
|
||||
{
|
||||
$request_method = strtolower($_SERVER['REQUEST_METHOD']);
|
||||
return in_array($request_method, [ "post", "put", "patch", "delete" ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle invalid CSRF
|
||||
*/
|
||||
public function handleInvalidCSRFToken(): string
|
||||
{
|
||||
if (function_exists('http_response_code')) {
|
||||
http_response_code(500);
|
||||
echo 'Invalid CSRF token';
|
||||
} else {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
header('Content-Type: text/plain');
|
||||
echo 'Invalid CSRF token';
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
protected function ensureSession()
|
||||
{
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
368
src/RaspAP/UI/Dashboard.php
Normal file
368
src/RaspAP/UI/Dashboard.php
Normal file
|
@ -0,0 +1,368 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Dashboard UI class
|
||||
*
|
||||
* @description A class for rendering the RaspAP dashboard
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace RaspAP\UI;
|
||||
|
||||
class Dashboard {
|
||||
|
||||
private string $firewallConfig;
|
||||
|
||||
public function __construct() {
|
||||
$this->firewallConfig = RASPI_CONFIG.'/networking/firewall.conf';
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the management page for an associated VPN
|
||||
*
|
||||
* @param string $interface
|
||||
* @return string
|
||||
*/
|
||||
public function getVpnManaged(?string $interface = null): ?string
|
||||
{
|
||||
switch ($interface) {
|
||||
case 'wg0':
|
||||
return '/wg_conf';
|
||||
case 'tun0':
|
||||
return '/openvpn_conf';
|
||||
case 'tailscale0':
|
||||
return '/plugin__Tailscale';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses output of iw, extracts frequency (MHz) and classifies
|
||||
* it as 2.4 or 5 GHz. Returns null if not found
|
||||
*
|
||||
* @param string $interface
|
||||
* @return string frequency
|
||||
*/
|
||||
public function getFrequencyBand(string $interface): ?string
|
||||
{
|
||||
$output = shell_exec("iw dev " . escapeshellarg($interface) . " info 2>/dev/null");
|
||||
if (!$output) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/channel\s+\d+\s+\((\d+)\s+MHz\)/', $output, $matches)) {
|
||||
$frequency = (int)$matches[1];
|
||||
|
||||
if ($frequency >= 2400 && $frequency < 2500) {
|
||||
return "2.4";
|
||||
} elseif ($frequency >= 5000 && $frequency < 6000) {
|
||||
return "5";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Aggregate function that fetches output of ip and calls
|
||||
* functions to parse output into discreet network properties
|
||||
*
|
||||
* @param string $interface
|
||||
* @return array
|
||||
*/
|
||||
public function getInterfaceDetails(string $interface): array
|
||||
{
|
||||
$output = shell_exec('ip a show ' . escapeshellarg($interface));
|
||||
if (!$output) {
|
||||
return [
|
||||
'mac' => _('No MAC Address Found'),
|
||||
'ipv4' => 'None',
|
||||
'ipv4_netmask' => '-',
|
||||
'ipv6' => _('No IPv6 Address Found'),
|
||||
'state' => 'unknown'
|
||||
];
|
||||
}
|
||||
$cleanOutput = preg_replace('/\s\s+/', ' ', implode(' ', explode("\n", $output)));
|
||||
|
||||
return [
|
||||
'mac' => $this->getMacAddress($cleanOutput),
|
||||
'ipv4' => $this->getIPv4Addresses($cleanOutput),
|
||||
'ipv4_netmask' => $this->getIPv4Netmasks($cleanOutput),
|
||||
'ipv6' => $this->getIPv6Addresses($cleanOutput),
|
||||
'state' => $this->getInterfaceState($cleanOutput),
|
||||
];
|
||||
}
|
||||
|
||||
private function getMacAddress(string $output): string
|
||||
{
|
||||
return preg_match('/link\/ether ([0-9a-f:]+)/i', $output, $matches) ? $matches[1] : _('No MAC Address Found');
|
||||
}
|
||||
|
||||
private function getIPv4Addresses(string $output): string
|
||||
{
|
||||
if (!preg_match_all('/inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([0-3][0-9])/i', $output, $matches, PREG_SET_ORDER)) {
|
||||
return 'None';
|
||||
}
|
||||
|
||||
$addresses = array_column($matches, 1);
|
||||
return implode(' ', $addresses);
|
||||
}
|
||||
|
||||
private function getIPv4Netmasks(string $output): string
|
||||
{
|
||||
if (!preg_match_all('/inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([0-3][0-9])/i', $output, $matches, PREG_SET_ORDER)) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$netmasks = array_map(fn($match) => long2ip(-1 << (32 - (int)$match[2])), $matches);
|
||||
return implode(' ', $netmasks);
|
||||
}
|
||||
|
||||
private function getIPv6Addresses(string $output): string
|
||||
{
|
||||
return preg_match_all('/inet6 ([a-f0-9:]+)/i', $output, $matches) && isset($matches[1])
|
||||
? implode(' ', $matches[1])
|
||||
: _('No IPv6 Address Found');
|
||||
}
|
||||
|
||||
private function getInterfaceState(string $output): string
|
||||
{
|
||||
return preg_match('/state (UP|DOWN)/i', $output, $matches) ? $matches[1] : 'unknown';
|
||||
}
|
||||
|
||||
public function getWirelessDetails(string $interface): array
|
||||
{
|
||||
$output = shell_exec('iw dev ' . escapeshellarg($interface) . ' info');
|
||||
if (!$output) {
|
||||
return ['bssid' => '-', 'ssid' => '-'];
|
||||
}
|
||||
$cleanOutput = preg_replace('/\s\s+/', ' ', trim($output)); // Fix here
|
||||
|
||||
return [
|
||||
'bssid' => $this->getConnectedBSSID($cleanOutput),
|
||||
'ssid' => $this->getSSID($cleanOutput),
|
||||
];
|
||||
}
|
||||
|
||||
private function getConnectedBSSID(string $output): string
|
||||
{
|
||||
return preg_match('/Connected to (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2}))/i', $output, $matches)
|
||||
? $matches[1]
|
||||
: '-';
|
||||
}
|
||||
|
||||
private function getSSID(string $output): string
|
||||
{
|
||||
return preg_match('/ssid ([^\n\s]+)/i', $output, $matches)
|
||||
? $matches[1]
|
||||
: '-';
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses the output of iw to obtain a list of wireless clients
|
||||
*
|
||||
* @return integer $clientCount
|
||||
*/
|
||||
public function getWirelessClients()
|
||||
{
|
||||
exec('iw dev wlan0 station dump', $output, $status);
|
||||
|
||||
if ($status !== 0) {
|
||||
return 0;
|
||||
}
|
||||
// enumerate 'station' entries (each represents a wireless client)
|
||||
$clientCount = 0;
|
||||
foreach ($output as $line) {
|
||||
if (strpos($line, 'Station') === 0) {
|
||||
$clientCount++;
|
||||
}
|
||||
}
|
||||
return $clientCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieves ethernet neighbors from ARP cache, parses DHCP leases
|
||||
* to find matching MAC addresses and returns only clients that
|
||||
* exist in both sources
|
||||
*
|
||||
* @return int $ethernetClients
|
||||
*/
|
||||
public function getEthernetClients(): int
|
||||
{
|
||||
$ethernetClients = [];
|
||||
|
||||
// Get ARP table entries and filter ethernet clients
|
||||
$arpOutput = shell_exec("ip neigh show");
|
||||
if ($arpOutput) {
|
||||
foreach (explode("\n", trim($arpOutput)) as $line) {
|
||||
/* match both traditional interface names (eth0...n) and predictable names like
|
||||
* enp3s0 (PCI ethernet)
|
||||
* eno1 (onboard ethernet)
|
||||
* ens160, etc.
|
||||
* ...ignoring STALE entries
|
||||
*/
|
||||
if (preg_match('/^(\S+) dev (eth[0-9]+|en\w+) lladdr (\S+) (REACHABLE|DELAY|PROBE)/', $line, $matches)) {
|
||||
$ethernetClients[$matches[3]] = $matches[1]; // MAC => IP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compare against active DHCP leases
|
||||
$leaseFile = RASPI_DNSMASQ_LEASES;
|
||||
if (file_exists($leaseFile)) {
|
||||
$leases = file($leaseFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$activeLeases = [];
|
||||
foreach ($leases as $lease) {
|
||||
$fields = preg_split('/\s+/', $lease);
|
||||
if (count($fields) >= 3) {
|
||||
$activeLeases[$fields[1]] = true; // MAC as key
|
||||
}
|
||||
}
|
||||
// keep only clients that exist in the DHCP lease file
|
||||
$ethernetClients = array_intersect_key($ethernetClients, $activeLeases);
|
||||
}
|
||||
return count($ethernetClients);
|
||||
}
|
||||
|
||||
public function formatClientLabel($clientCount)
|
||||
{
|
||||
return ngettext('client', 'clients', $clientCount);
|
||||
}
|
||||
|
||||
/*
|
||||
* Determines the device's primary connection type by
|
||||
* parsing the output of ip route; the interface listed
|
||||
* as the default gateway is used for internet connectivity.
|
||||
*
|
||||
* The following interface classifications are matched:
|
||||
* - ethernet (eth0, enp*, ens*, enx*)
|
||||
* - wireless (wlan*, wlp*, wlx*)
|
||||
* - tethered USB (usb*, eth1-9)
|
||||
* - cellular (ppp*, wwan*, wwp*)
|
||||
* - fallback
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectionType(): string
|
||||
{
|
||||
// get the interface associated with the default route
|
||||
$interface = trim(shell_exec("ip route show default | awk '{print $5}'"));
|
||||
|
||||
if (empty($interface)) {
|
||||
return 'unknown';
|
||||
}
|
||||
// classify interface type
|
||||
if (preg_match('/^eth0|enp\d+s\d+|ens\d+s\d+|enx[0-9a-f]*/', $interface)) {
|
||||
return 'ethernet';
|
||||
}
|
||||
if (preg_match('/^wlan\d+|wlp\d+s\d+|wlx[0-9a-f]*/', $interface)) {
|
||||
return 'wireless';
|
||||
}
|
||||
if (preg_match('/^usb\d+|^eth[1-9]\d*/', $interface)) {
|
||||
return 'tethering';
|
||||
}
|
||||
if (preg_match('/^ppp\d+|wwan\d+|wwp\d+s\d+/', $interface)) {
|
||||
return 'cellular';
|
||||
}
|
||||
|
||||
// if none match, return the interface name as a fallback
|
||||
return "other ($interface)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fontawesome icon associated with a connection
|
||||
* type/class
|
||||
*
|
||||
* @param $type
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectionIcon($type): string
|
||||
{
|
||||
switch (strtolower($type)) {
|
||||
case 'ethernet':
|
||||
return 'fa-ethernet';
|
||||
case 'wireless':
|
||||
return 'fa-wifi';
|
||||
case 'tethering':
|
||||
return 'fa-mobile-alt';
|
||||
case 'cellular':
|
||||
return 'fa-broadcast-tower';
|
||||
default:
|
||||
return 'fa-question-circle'; // unknown
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the firewall's current status
|
||||
*
|
||||
* @return bool status
|
||||
*/
|
||||
public function firewallEnabled(): bool
|
||||
{
|
||||
$conf = array();
|
||||
if (file_exists($this->firewallConfig) ) {
|
||||
$conf = parse_ini_file($this->firewallConfig);
|
||||
}
|
||||
if ($conf["firewall-enable"] == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns an SVG resource associated with a Pi revision
|
||||
*
|
||||
* @param string $deviceName
|
||||
* @return string
|
||||
*/
|
||||
public function getDeviceImage($deviceName): string
|
||||
{
|
||||
if (stripos($deviceName, 'zero') !== false) {
|
||||
return 'zero.php';
|
||||
}
|
||||
if (stripos($deviceName, 'compute') !== false) {
|
||||
return 'compute.php';
|
||||
}
|
||||
return 'default.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles dashboard page actions
|
||||
*
|
||||
* @param string $state
|
||||
* @param array $post
|
||||
* @param object $status
|
||||
* @param string $interface
|
||||
*/
|
||||
public function handlePageAction(string $state, array $post, $status, string $interface): object
|
||||
{
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
if (isset($post['ifdown_wlan0'])) {
|
||||
if ($state === 'up') {
|
||||
$status->addMessage(sprintf(_('Interface %s is going %s'), $interface, _('down')), 'warning');
|
||||
exec('sudo ip link set ' .escapeshellarg($interface). ' down');
|
||||
$status->addMessage(sprintf(_('Interface %s is %s'), $interface, _('down')), 'success');
|
||||
} elseif ($details['state'] === 'unknown') {
|
||||
$status->addMessage(_('Interface state unknown'), 'danger');
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface %s is already %s'), $interface, _('down')), 'warning');
|
||||
}
|
||||
} elseif (isset($post['ifup_wlan0'])) {
|
||||
if ($state === 'down') {
|
||||
$status->addMessage(sprintf(_('Interface %s is going %s'), $interface, _('up')), 'warning');
|
||||
exec('sudo ip link set ' .escapeshellarg($interface). ' up');
|
||||
exec('sudo ip -s a f label ' .escapeshellarg($interface));
|
||||
usleep(250000);
|
||||
$status->addMessage(sprintf(_('Interface %s is %s'), $interface, _('up')), 'success');
|
||||
} elseif ($state === 'unknown') {
|
||||
$status->addMessage(_('Interface state unknown'), 'danger');
|
||||
} else {
|
||||
$status->addMessage(sprintf(_('Interface %s is already %s'), $interface, _('up')), 'warning');
|
||||
}
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ class Sidebar {
|
|||
public function __construct() {
|
||||
// Load default sidebar items
|
||||
$this->addItem(_('Dashboard'), 'fa-solid fa-gauge-high', 'wlan0_info', 10);
|
||||
$this->addItem(_('Hotspot'), 'far fa-dot-circle', 'hostapd_conf', 20,
|
||||
$this->addItem(_('Hotspot'), 'fas fa-bullseye', 'hostapd_conf', 20,
|
||||
fn() => RASPI_HOTSPOT_ENABLED
|
||||
);
|
||||
$this->addItem(_('DHCP Server'), 'fas fa-exchange-alt', 'dhcpd_conf', 30,
|
||||
|
@ -39,9 +39,6 @@ class Sidebar {
|
|||
);
|
||||
$this->addItem(_(getProviderValue($_SESSION["providerID"], "name")), 'fas fa-shield-alt', 'provider_conf', 90,
|
||||
fn() => RASPI_VPN_PROVIDER_ENABLED
|
||||
);
|
||||
$this->addItem(_('Authentication'), 'fas fa-user-lock', 'auth_conf', 100,
|
||||
fn() => RASPI_CONFAUTH_ENABLED
|
||||
);
|
||||
$this->addItem(_('Data usage'), 'fas fa-chart-area', 'data_use', 110,
|
||||
fn() => RASPI_VNSTAT_ENABLED
|
||||
|
|
|
@ -69,7 +69,7 @@ require_once 'app/lib/Parsedown.php';
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="col-md-12 mb-3 mt-1" id="msg-check-update"><?php echo _("Application is being updated..."); ?></div>
|
||||
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep1"></i><?php echo _("Configuring update"); ?></div>
|
||||
<div class="ms-5"><i class="fas fa-check me-2" id="updateStep1"></i><?php echo _("Configuring update"); ?></div>
|
||||
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep2"></i><?php echo _("Updating sources"); ?></div>
|
||||
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep3"></i><?php echo _("Installing package updates"); ?></div>
|
||||
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep4"></i><?php echo _("Downloading latest files"); ?></div>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="card-body">
|
||||
<?php $status->showMessages(); ?>
|
||||
<form role="form" action="adblock_conf" enctype="multipart/form-data" method="POST">
|
||||
<?php echo CSRFTokenFieldTag() ?>
|
||||
<?php echo \RaspAP\Tokens\CSRF::hiddenField();?>
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item"><a class="nav-link active" id="blocklisttab" href="#adblocklistsettings" data-bs-toggle="tab"><?php echo _("Blocklist settings"); ?></a></li>
|
||||
|
|
|
@ -30,10 +30,8 @@
|
|||
<option disabled="disabled"></option>
|
||||
<?php echo optionsForSelect(blocklistProviders()) ?>
|
||||
</select>
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-end" type="button" onclick="updateBlocklist()"><?php echo _("Update now"); ?></button>
|
||||
<span id="cbxblocklist-status" class="input-group-addon check-hidden ms-2 mt-1"><i class="fas fa-check"></i></span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-end" type="button" onclick="updateBlocklist()"><?php echo _("Update now"); ?></button>
|
||||
<span id="cbxblocklist-status" class="input-group-addon check-hidden ms-2 mt-1"><i class="fas fa-check"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
<?php ob_start() ?>
|
||||
<?php if (!RASPI_MONITOR_ENABLED) : ?>
|
||||
<input type="submit" class="btn btn-outline btn-primary" name="UpdateAdminPassword" value="<?php echo _("Save settings"); ?>" />
|
||||
<input type="submit" class="btn btn-warning" name="logout" value="<?php echo _("Logout") ?>" onclick="disableValidation(this.form)"/>
|
||||
<?php endif ?>
|
||||
<?php $buttons = ob_get_clean(); ob_end_clean() ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<i class="fas fa-user-lock me-2"></i><?php echo _("Authentication"); ?>
|
||||
<div class="col">
|
||||
<i class="fas fa-user-lock me-2"></i><?php echo _("Authentication"); ?>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
</div><!-- /.card-header -->
|
||||
|
@ -12,7 +19,7 @@
|
|||
<?php $status->showMessages(); ?>
|
||||
<h4><?php echo _("Authentication settings") ;?></h4>
|
||||
<form role="form" action="auth_conf" method="POST" class="needs-validation" novalidate>
|
||||
<?php echo CSRFTokenFieldTag() ?>
|
||||
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
|
||||
<div class="row">
|
||||
<div class="mb-3 col-md-6">
|
||||
<label for="username"><?php echo _("Username"); ?></label>
|
||||
|
@ -58,7 +65,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-outline btn-primary" name="UpdateAdminPassword" value="<?php echo _("Save settings"); ?>" />
|
||||
<?php echo $buttons ?>
|
||||
</form>
|
||||
</div><!-- /.card-body -->
|
||||
<div class="card-footer"></div>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<div class="row" id="wpaConf">
|
||||
<div class="col">
|
||||
<form method="POST" action="wpa_conf" name="wpa_conf_form">
|
||||
<?php echo CSRFTokenFieldTag() ?>
|
||||
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
|
||||
<input type="hidden" name="client_settings" ?>
|
||||
<div class="js-wifi-stations loading-spinner"></div>
|
||||
</form>
|
||||
|
|
|
@ -1,149 +1,56 @@
|
|||
<?php ob_start() ?>
|
||||
<?php if (!RASPI_MONITOR_ENABLED) : ?>
|
||||
<?php if ($state === "down") : ?>
|
||||
<input type="submit" class="btn btn-success mt-2" value="<?php echo _("Start").' '.$interface ?>" name="ifup_wlan0" />
|
||||
<?php else : ?>
|
||||
<input type="submit" class="btn btn-warning mt-2" value="<?php echo _("Stop").' '.$interface ?>" name="ifdown_wlan0" />
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
<button type="button" onClick="window.location.reload();" class="btn btn-outline btn-primary mt-2"><i class="fas fa-sync-alt"></i> <?php echo _("Refresh") ?></a>
|
||||
<?php $buttons = ob_get_clean(); ob_end_clean() ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<i class="fas fa-tachometer-alt fa-fw me-2"></i><?php echo _("Dashboard"); ?>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-light btn-icon-split btn-sm service-status float-end">
|
||||
<span class="icon"><i class="fas fa-circle service-status-<?php echo $ifaceStatus ?>"></i></span>
|
||||
<span class="text service-status"><?php echo strtolower($apInterface) .' '. _($ifaceStatus) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<i class="fas fa-tachometer-alt fa-fw me-2"></i>
|
||||
<?php echo _("Dashboard"); ?>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-light btn-icon-split btn-sm service-status float-end">
|
||||
<span class="icon"><i class="fas fa-circle service-status-<?php echo $state ?>"></i></span>
|
||||
<span class="text service-status"><?php echo strtolower($interface) .' '. _($state) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
</div><!-- /.card-header -->
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<?php $status->showMessages(); ?>
|
||||
<form action="wlan0_info" method="POST">
|
||||
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php echo _("Hourly traffic amount"); ?></h4>
|
||||
<div id="divInterface" class="d-none"><?php echo $apInterface; ?></div>
|
||||
<div class="col-md-12">
|
||||
<div class="col dbChart">
|
||||
<canvas id="divDBChartBandwidthhourly"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card-body -->
|
||||
</div><!-- /.card-->
|
||||
</div>
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item"><a class="nav-link active" id="statustab" href="#status" aria-controls="status" data-bs-toggle="tab"><?php echo _("Status"); ?></a></li>
|
||||
<li class="nav-item"><a class="nav-link" id="datatab" href="#data" data-bs-toggle="tab"><?php echo _("Data usage"); ?></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="col-sm-6 align-items-stretch">
|
||||
<div class="card h-100">
|
||||
<div class="card-body wireless">
|
||||
<h4 class="card-title"><?php echo _("Wireless Client"); ?></h4>
|
||||
<div class="row ms-1">
|
||||
<div class="col-sm">
|
||||
<div class="row mb-1">
|
||||
<div class="info-item col"><?php echo _("Connected To"); ?></div><div class="info-value col"><?php echo htmlspecialchars($connectedSSID, ENT_QUOTES); ?></div>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="info-item col"><?php echo _("Interface"); ?></div><div class="info-value col"><?php echo htmlspecialchars($clientInterface); ?></div>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="info-item col"><?php echo _("AP Mac Address"); ?></div><div class="info-value col"><?php echo htmlspecialchars($connectedBSSID, ENT_QUOTES); ?></div>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="info-item col"><?php echo _("Bitrate"); ?></div><div class="info-value col"><?php echo htmlspecialchars($bitrate, ENT_QUOTES); ?></div>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="info-item col"><?php echo _("Signal Level"); ?></div><div class="info-value col"><?php echo htmlspecialchars($signalLevel, ENT_QUOTES); ?></div>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="info-item col"><?php echo _("Transmit Power"); ?></div><div class="info-value col"><?php echo htmlspecialchars($txPower, ENT_QUOTES); ?></div>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="info-item col"><?php echo _("Frequency"); ?></div><div class="info-value col"><?php echo htmlspecialchars($frequency, ENT_QUOTES); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md d-flex">
|
||||
<script>var linkQ = <?php echo json_encode($strLinkQuality); ?>;</script>
|
||||
<div class="chart-container">
|
||||
<canvas id="divChartLinkQ"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div><!--row-->
|
||||
</div><!-- /.card-body -->
|
||||
</div><!-- /.card -->
|
||||
</div><!-- /.col-md-6 -->
|
||||
<div class="col-sm-6">
|
||||
<div class="card h-100 mb-3">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php echo _("Connected Devices"); ?></h4>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if ($bridgedEnable == 1) : ?>
|
||||
<th><?php echo _("MAC Address"); ?></th>
|
||||
<?php else : ?>
|
||||
<th><?php echo _("Host name"); ?></th>
|
||||
<th><?php echo _("IP Address"); ?></th>
|
||||
<th><?php echo _("MAC Address"); ?></th>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($bridgedEnable == 1) : ?>
|
||||
<tr>
|
||||
<td><small class="text-muted"><?php echo _("Bridged AP mode is enabled. For Hostname and IP, see your router's admin page.");?></small></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach (array_slice($clients,0, 2) as $client) : ?>
|
||||
<tr>
|
||||
<?php if ($arrHostapdConf['BridgedEnable'] == 1): ?>
|
||||
<td><?php echo htmlspecialchars($client, ENT_QUOTES) ?></td>
|
||||
<?php else : ?>
|
||||
<?php $props = explode(' ', $client) ?>
|
||||
<td><?php echo htmlspecialchars($props[3], ENT_QUOTES) ?></td>
|
||||
<td><?php echo htmlspecialchars($props[2], ENT_QUOTES) ?></td>
|
||||
<td><?php echo htmlspecialchars($props[1], ENT_QUOTES) ?></td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php if (sizeof($clients) >2) : ?>
|
||||
<div class="col-lg-12 float-end">
|
||||
<a class="btn btn-outline-info" role="button" href="<?php echo $moreLink ?>"><?php echo _("More");?> <i class="fas fa-chevron-right"></i></a>
|
||||
</div>
|
||||
<?php elseif (sizeof($clients) ==0) : ?>
|
||||
<div class="col-lg-12 mt-3"><?php echo _("No connected devices");?></div>
|
||||
<?php endif; ?>
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.card-body -->
|
||||
</div><!-- /.card -->
|
||||
</div><!-- /.col-md-6 -->
|
||||
</div><!-- /.row -->
|
||||
|
||||
<div class="col-lg-12 mt-3">
|
||||
<div class="row">
|
||||
<form action="wlan0_info" method="POST">
|
||||
<?php echo CSRFTokenFieldTag() ?>
|
||||
<?php if (!RASPI_MONITOR_ENABLED) : ?>
|
||||
<?php if (!$wlan0up) : ?>
|
||||
<input type="submit" class="btn btn-success" value="<?php echo _("Start").' '.$apInterface ?>" name="ifup_wlan0" />
|
||||
<?php else : ?>
|
||||
<input type="submit" class="btn btn-warning" value="<?php echo _("Stop").' '.$apInterface ?>" name="ifdown_wlan0" />
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
<button type="button" onClick="window.location.reload();" class="btn btn-outline btn-primary"><i class="fas fa-sync-alt"></i> <?php echo _("Refresh") ?></a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<?php echo renderTemplate("dashboard/status", $__template_data) ?>
|
||||
<?php echo renderTemplate("dashboard/data", $__template_data) ?>
|
||||
</div><!-- /.tab-content -->
|
||||
|
||||
<?php echo $buttons ?>
|
||||
</form>
|
||||
</div><!-- /.card-body -->
|
||||
<div class="card-footer"><?php echo _("Information provided by ip and iw and from system"); ?></div>
|
||||
|
||||
<div class="card-footer"><?php echo _("Information provided by raspap.sysinfo"); ?></div>
|
||||
|
||||
</div><!-- /.card -->
|
||||
</div><!-- /.col-lg-12 -->
|
||||
</div><!-- /.row -->
|
||||
<script type="text/javascript"<?php //echo ' nonce="'.$csp_page_nonce.'"'; ?>>
|
||||
// js translations:
|
||||
var t = new Array();
|
||||
t['send'] = '<?php echo addslashes(_('Send')); ?>';
|
||||
t['receive'] = '<?php echo addslashes(_('Receive')); ?>';
|
||||
</script>
|
||||
|
||||
|
|
19
templates/dashboard/data.php
Normal file
19
templates/dashboard/data.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<div class="tab-pane" id="data">
|
||||
<h4 class="card-title mt-3">
|
||||
<?php echo _("Hourly traffic"); ?>
|
||||
</h4>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="col dbChart">
|
||||
<canvas id="divDBChartBandwidthhourly"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.tab-pane | data tab -->
|
||||
|
||||
<script type="text/javascript"<?php //echo ' nonce="'.$csp_page_nonce.'"'; ?>>
|
||||
// js translations:
|
||||
var t = new Array();
|
||||
t['send'] = '<?php echo addslashes(_('Send')); ?>';
|
||||
t['receive'] = '<?php echo addslashes(_('Receive')); ?>';
|
||||
</script>
|
123
templates/dashboard/status.php
Normal file
123
templates/dashboard/status.php
Normal file
|
@ -0,0 +1,123 @@
|
|||
<div class="tab-pane active" id="status">
|
||||
<h4 class="card-title mt-3">
|
||||
<?php echo _("Current status"); ?>
|
||||
</h4>
|
||||
<div class="dashboard-container row">
|
||||
<div class="connections-left col-lg-4">
|
||||
<div class="connection-item">
|
||||
<a href="/network_conf" class="<?php echo $ethernetActive; ?>"><span><?php echo _("Ethernet"); ?></span></a>
|
||||
<a href="/network_conf" class="<?php echo $ethernetActive; ?>"><i class="fas fa-ethernet fa-2xl"></i></a>
|
||||
</div>
|
||||
<div class="connection-item">
|
||||
<a href="/network_conf" class="<?php echo $wirelessActive; ?>"><span><?php echo _("Repeater"); ?></span>
|
||||
<a href="/network_conf" class="<?php echo $wirelessActive; ?>"><i class="fas fa-wifi fa-2xl"></i></a>
|
||||
</div>
|
||||
<div class="connection-item">
|
||||
<a href="/network_conf" class="<?php echo $tetheringActive; ?>"><span><?php echo _("Tethering"); ?></span></a>
|
||||
<a href="/network_conf" class="<?php echo $tetheringActive; ?>"><i class="fas fa-mobile-alt fa-2xl"></i></a>
|
||||
</div>
|
||||
<div class="connection-item">
|
||||
<a href="/network_conf" class="<?php echo $cellularActive; ?>"><span><?php echo _("Cellular"); ?></span></a>
|
||||
<a href="/network_conf" class="<?php echo $cellularActive; ?>"><i class="fas fa-broadcast-tower fa-2xl"></i></a>
|
||||
</div>
|
||||
<img src="app/img/dashed.svg" class="dashed-lines" alt="">
|
||||
<img src="<?php echo htmlspecialchars(renderConnection($connectionType)); ?>" class="solid-lines" alt="Network connection">
|
||||
</div>
|
||||
<div class="center-device col-12 col-lg-4">
|
||||
<div class="center-device-top">
|
||||
<a href="/system_info"><img class="device-illustration" src="app/img/devices/<?php echo $deviceImage; ?>" alt="<?php echo htmlspecialchars($revision, ENT_QUOTES); ?>"></a>
|
||||
<div class="device-label"><a href="/system_info"><?php echo htmlspecialchars($revision, ENT_QUOTES); ?></a></div>
|
||||
<div class="mt-1 small"><?php echo _("IP Address"); ?>: <a href="/dhcpd_conf"><?php echo htmlspecialchars($ipv4Address, ENT_QUOTES); ?></a></div>
|
||||
<div class="small"><?php echo _("Netmask"); ?>: <a href="/dhcpd_conf"><?php echo htmlspecialchars($ipv4Netmask, ENT_QUOTES); ?></a></div>
|
||||
<div class="small"><?php echo _("MAC Address"); ?>: <a href="/dhcpd_conf"><?php echo htmlspecialchars($macAddress, ENT_QUOTES); ?></a></div>
|
||||
<div class="small"><?php echo _("SSID"); ?>: <a href="/hostapd_conf"><?php echo htmlspecialchars($ssid, ENT_QUOTES); ?></a></div>
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<div class="device-status">
|
||||
<a href="/hostapd_conf">
|
||||
<div class="status-item <?php echo $hostapdStatus; ?>">
|
||||
<i class="fas fa-bullseye fa-2xl"></i>
|
||||
<span><?php echo _('AP'); ?></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/hostapd_conf?tab=advanced">
|
||||
<div class="status-item <?php echo $bridgedStatus; ?>">
|
||||
<i class="fas fa-bridge fa-2xl"></i>
|
||||
<span><?php echo _('Bridged'); ?></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/adblock_conf">
|
||||
<div class="status-item <?php echo $adblockStatus; ?>">
|
||||
<i class="far fa-hand-paper fa-2xl"></i>
|
||||
<span><?php echo _('Adblock'); ?></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<?php echo $vpnManaged; ?>">
|
||||
<div class="status-item <?php echo $vpnStatus; ?>">
|
||||
<i class="fas fa-shield-alt fa-2xl"></i>
|
||||
<span><?php echo _('VPN'); ?></span>
|
||||
</div>
|
||||
</a>
|
||||
<?php echo $firewallManaged; ?>
|
||||
<div class="status-item <?php echo $firewallStatus; ?>">
|
||||
<span class="fa-stack fa-2xl" style="line-height: 0!important;height: 100%!important;">
|
||||
<i class="fas fa-fire-flame-curved fa-stack-1x"></i>
|
||||
<?php echo $firewallUnavailable; ?>
|
||||
</span>
|
||||
<span><?php echo _('Firewall'); ?></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="wifi-bands">
|
||||
<a href="/hostapd_conf"><span class="band <?php echo $freq5active; ?>"><?php echo _("5G"); ?></span></a>
|
||||
<a href="/hostapd_conf"><span class="band <?php echo $freq24active; ?>"><?php echo _("2.4G"); ?></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clients-mobile">
|
||||
<div class="client-type">
|
||||
<a href="/network_conf">
|
||||
<i class="fas fa-globe"></i>
|
||||
<div class="client-count">
|
||||
<i class="fas <?php echo $connectionIcon; ?> badge-icon"></i>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="client-type">
|
||||
<a href="/dhcpd_conf">
|
||||
<i class="fas fa-laptop <?php echo $totalClientsActive; ?>"></i>
|
||||
<span class="client-count"><?php echo $totalClients; ?></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connections-right col-lg-4">
|
||||
<div class="d-flex flex-column justify-content-around h-100">
|
||||
<div class="connection-item connection-right">
|
||||
<a href="/dhcpd_conf" class="<?php echo $wirelessClientActive; ?>">
|
||||
<span class="fa-stack">
|
||||
<i class="fas fa-laptop fa-stack-1x fa-2xl"></i>
|
||||
<i class="fas fa-wifi fa-stack-1x fa-xs"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a href="/dhcpd_conf"><span class="text-nowrap <?php echo $wirelessClientActive; ?>"><?php echo $wirelessClientLabel; ?></span></a>
|
||||
</div>
|
||||
<div class="connection-item connection-right">
|
||||
<a href="/dhcpd_conf" class="<?php echo $ethernetClientActive; ?>">
|
||||
<span class="fa-stack">
|
||||
<i class="fas fa-laptop fa-stack-1x fa-2xl"></i>
|
||||
<i class="fas fa-ethernet fa-stack-1x fa-xs"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a href="/dhcpd_conf"><span class="text-nowrap <?php echo $ethernetClientActive; ?>"><?php echo $ethernetClientLabel; ?></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php echo renderClientConnections($wirelessClients, $ethernetClients); ?>
|
||||
<img src="app/img/right-dashed.svg" class="dashed-lines dashed-lines-right" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.tab-pane | status tab -->
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
<div class="card-body">
|
||||
<?php $status->showMessages(); ?>
|
||||
<form method="POST" action="dhcpd_conf" class="js-dhcp-settings-form needs-validation" novalidate>
|
||||
<?php echo CSRFTokenFieldTag() ?>
|
||||
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
|
||||
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs mb-3">
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="input-group">
|
||||
<input type="hidden" name="no-resolv" value="0">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" id="no-resolv" type="checkbox" name="no-resolv" value="1" <?php echo $conf['no-resolv'] ? ' checked="checked"' : "" ?> aria-describedby="no-resolv-description">
|
||||
<input class="form-check-input" id="no-resolv" type="checkbox" name="no-resolv" value="1" <?php echo ($conf['no-resolv'] ?? false) ? ' checked="checked"' : '' ?> aria-describedby="no-resolv-description">
|
||||
<label class="form-check-label" for="no-resolv"><?php echo _("Only ever query DNS servers configured below") ?></label>
|
||||
</div>
|
||||
<p id="no-resolv-description">
|
||||
|
@ -19,9 +19,8 @@
|
|||
<div class="js-dhcp-upstream-servers">
|
||||
<?php foreach ($upstreamServers as $server): ?>
|
||||
<div class="mb-3 input-group input-group-sm js-dhcp-upstream-server">
|
||||
<input type="text" class="form-control" name="server[]" value="<?php echo $server ?>">
|
||||
<input type="text" class="form-control" name="server[]" value="<?php echo $server; ?>">
|
||||
<div class="input-group-text js-remove-dhcp-upstream-server"><i class="fas fa-minus"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
|
|
|
@ -20,11 +20,17 @@
|
|||
<tbody>
|
||||
<?php foreach ($leases as $lease) : ?>
|
||||
<tr>
|
||||
<?php foreach (explode(' ', $lease) as $prop) : ?>
|
||||
<td><?php echo htmlspecialchars($prop, ENT_QUOTES) ?></td>
|
||||
<?php endforeach ?>
|
||||
<?php
|
||||
$props = explode(' ', $lease);
|
||||
if (!empty($props)) {
|
||||
$props[0] = date('Y-m-d H:i:s', (int)$props[0]);
|
||||
}
|
||||
?>
|
||||
<?php foreach ($props as $prop) : ?>
|
||||
<td><?php echo htmlspecialchars($prop, ENT_QUOTES) ?></td>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div><!-- /.table-responsive -->
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="row">
|
||||
<div class="mb-3 col-md-6">
|
||||
<label for="code">Interface</label>
|
||||
<?php SelectorOptions('interface', $interfaces, $ap_iface, 'cbxdhcpiface', 'loadInterfaceDHCPSelect', $DHCPDisabled); ?>
|
||||
<?php SelectorOptions('interface', $interfaces, $ap_iface, 'cbxdhcpiface', 'loadInterfaceDHCPSelect'); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<p><?php echo _("Enable these options to log <code>dhcpcd</code> and <code>dnsmasq</code> activity.") ?></p>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" id="log-dhcp" type="checkbox" name="log-dhcp" value="1" <?php echo $conf['log-dhcp'] ? ' checked="checked"' : "" ?> aria-describedby="log-dhcp-requests">
|
||||
<input class="form-check-input" id="log-dhcp" type="checkbox" name="log-dhcp" value="1" <?php echo !empty($conf['log-dhcp']) ? ' checked="checked"' : "" ?> aria-describedby="log-dhcp-requests">
|
||||
<label class="form-check-label" for="log-dhcp"><?php echo _("Log DHCP requests") ?></label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" id="log-queries" type="checkbox" name="log-queries" value="1" <?php echo $conf['log-queries'] ? ' checked="checked"' : "" ?> aria-describedby="log-dhcp-queries">
|
||||
<input class="form-check-input" id="log-queries" type="checkbox" name="log-queries" value="1" <?php echo !empty($conf['log-queries']) ? ' checked="checked"' : "" ?> aria-describedby="log-dhcp-queries">
|
||||
<label class="form-check-label align-middle" for="log-queries"><?php echo _("Log DNS queries") ?></label>
|
||||
<input type="button" class="btn btn-outline btn-warning btn-sm align-top ms-4" id="js-cleardnsmasq-log" value="<?php echo _("Clear log"); ?>" />
|
||||
</div>
|
||||
|
@ -16,7 +16,7 @@
|
|||
<div class="row">
|
||||
<div class="mb-3 col-md-8 mt-2">
|
||||
<?php
|
||||
if ($conf['log-dhcp'] == 1 || $conf['log-queries'] == 1) {
|
||||
if (($conf['log-dhcp'] ?? 0) == 1 || ($conf['log-queries'] ?? 0) == 1) {
|
||||
echo '<textarea class="logoutput text-secondary" id="dnsmasq-log">'.htmlspecialchars($logdata, ENT_QUOTES).'</textarea>';
|
||||
} else {
|
||||
echo '<textarea class="logoutput my-3"></textarea>';
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<div class="input-group">
|
||||
<input type="hidden" name="dhcp-ignore" value="0">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" id="dhcp-ignore" type="checkbox" name="dhcp-ignore" value="1" <?php echo $conf['dhcp-ignore'] ? ' checked="checked"' : "" ?> aria-describedby="dhcp-ignore-description">
|
||||
<input class="form-check-input" id="dhcp-ignore" type="checkbox" name="dhcp-ignore" value="1" <?php echo !empty($conf['dhcp-ignore']) ? ' checked="checked"' : "" ?> aria-describedby="dhcp-ignore-description">
|
||||
<label class="form-check-label" for="dhcp-ignore"><?php echo _("Limit network access to static clients") ?></label>
|
||||
</div>
|
||||
<p id="dhcp-ignore-description">
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue