Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b8d822011 |
20
.github/ISSUE_TEMPLATE/issue_form.yml
vendored
@@ -32,19 +32,17 @@ body:
|
||||
options:
|
||||
- label: I have read and understand the [issue reporting policy](https://docs.raspap.com/issues/).
|
||||
required: true
|
||||
- label: I have read and followed the [common sense checklist](https://docs.raspap.com/troubleshooting/#dos-and-donts).
|
||||
required: true
|
||||
- label: I observed this bug on a clean install of a [supported OS](https://docs.raspap.com/#compatible-operating-systems).
|
||||
required: true
|
||||
- label: I have followed the [project prerequisites](https://docs.raspap.com/quick_start/#quick-install).
|
||||
- label: I have followed the [project prerequisites](https://docs.raspap.com/#quick-start).
|
||||
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: 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
|
||||
- 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: true
|
||||
|
||||
- type: dropdown
|
||||
@@ -58,15 +56,15 @@ body:
|
||||
- Raspberry Pi OS (64-bit) Lite Bullseye
|
||||
- Raspberry Pi OS (32-bit) Lite Bullseye
|
||||
- Armbian 23.05 (Suni)
|
||||
- Debian Bookworm
|
||||
- Debian Bookworm
|
||||
- Ubuntu Server 23.04 (Lunar)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: install
|
||||
attributes:
|
||||
label: Installation method
|
||||
label: Quick install or Manual setup?
|
||||
options:
|
||||
- Pre-built image
|
||||
- Quick install
|
||||
- Manual setup
|
||||
validations:
|
||||
@@ -92,7 +90,6 @@ body:
|
||||
- Raspberry Pi 3 Model B
|
||||
- Raspberry Pi Zero 2 W
|
||||
- Raspberry Pi Zero W
|
||||
- Raspberry Pi Compute Module
|
||||
- Orange Pi family
|
||||
- Other
|
||||
validations:
|
||||
@@ -102,8 +99,8 @@ body:
|
||||
attributes:
|
||||
label: RaspAP version
|
||||
options:
|
||||
- Latest
|
||||
- Other (specify below)
|
||||
- 3.2.5 (Latest)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -116,6 +113,7 @@ body:
|
||||
- Not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
|
||||
63
.github/scripts/add-raspap-stage.sh
vendored
@@ -1,63 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
mkdir -p stage-raspap/package-raspap
|
||||
|
||||
PRERUN_COMMANDS=""
|
||||
INSTALLER_COMMANDS=""
|
||||
|
||||
if [ -n "$INSIDERS_USER" ] && [ -n "$INSIDERS_TOKEN" ]; then
|
||||
echo ">>> Configuring for private Insiders build."
|
||||
|
||||
PRERUN_COMMANDS=$(
|
||||
cat <<-EOF
|
||||
echo "${INSIDERS_TOKEN}" > "\${ROOTFS_DIR}/etc/insiders_token"
|
||||
chmod 600 "\${ROOTFS_DIR}/etc/insiders_token"
|
||||
EOF
|
||||
)
|
||||
|
||||
INSTALLER_COMMANDS=$(
|
||||
cat <<-EOF
|
||||
INSIDERS_TOKEN_VALUE=\$(cat /etc/insiders_token)
|
||||
curl -sL https://install.raspap.com | bash -s -- \\
|
||||
--yes --insiders --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0 \\
|
||||
--name "${INSIDERS_USER}" --token "\$INSIDERS_TOKEN_VALUE"
|
||||
rm -f /etc/insiders_token
|
||||
EOF
|
||||
)
|
||||
else
|
||||
echo ">>> Configuring for public build."
|
||||
|
||||
INSTALLER_COMMANDS=$(
|
||||
cat <<-EOF
|
||||
curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
cat >stage-raspap/prerun.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
if [ ! -d "\${ROOTFS_DIR}" ]; then
|
||||
copy_previous
|
||||
fi
|
||||
${PRERUN_COMMANDS}
|
||||
EOF
|
||||
|
||||
cat >stage-raspap/package-raspap/00-run-chroot.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps
|
||||
|
||||
${INSTALLER_COMMANDS}
|
||||
|
||||
# Set Wi-Fi country to prevent RF kill
|
||||
raspi-config nonint do_wifi_country "US"
|
||||
|
||||
# Fetch RaspAP version and set MOTD
|
||||
RASPAP_VERSION=\$(curl -sL https://install.raspap.com | bash -s -- --version)
|
||||
echo "\$RASPAP_VERSION" | tee /etc/motd
|
||||
EOF
|
||||
|
||||
chmod +x stage-raspap/prerun.sh
|
||||
chmod +x stage-raspap/package-raspap/00-run-chroot.sh
|
||||
|
||||
echo "Build configuration complete."
|
||||
26
.github/workflows/release.yml
vendored
@@ -23,13 +23,31 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Add RaspAP Stage
|
||||
run: sh ./.github/scripts/add-raspap-stage.sh
|
||||
run: |
|
||||
mkdir -p stage-raspap/package-raspap &&
|
||||
{
|
||||
cat > stage-raspap/package-raspap/00-run-chroot.sh <<-EOF
|
||||
#!/bin/bash
|
||||
apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps
|
||||
curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0
|
||||
EOF
|
||||
} &&
|
||||
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
|
||||
{
|
||||
cat > stage-raspap/prerun.sh <<-EOF
|
||||
#!/bin/bash -e
|
||||
if [ ! -d "\${ROOTFS_DIR}" ]; then
|
||||
copy_previous
|
||||
fi
|
||||
EOF
|
||||
} &&
|
||||
chmod +x stage-raspap/prerun.sh
|
||||
|
||||
- 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 }}"
|
||||
image-name: "raspap-${{ github.ref_name }}-${{ matrix.arch }}"
|
||||
enable-ssh: 1
|
||||
stage-list: stage0 stage1 stage2 ./stage-raspap
|
||||
verbose-output: true
|
||||
@@ -39,8 +57,8 @@ jobs:
|
||||
- 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"
|
||||
asset_name: raspap-image-${{ github.ref_name }}-${{ matrix.arch }}.zip
|
||||
file: ${{ steps.build.outputs.image-path }}
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.event.inputs.tag || github.ref }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
|
||||
1
.gitignore
vendored
@@ -7,4 +7,3 @@ rootCA.pem
|
||||
vendor
|
||||
.env
|
||||
locale/**/*.mo
|
||||
app/net_activity
|
||||
|
||||
5
.gitmodules
vendored
@@ -1,4 +1,3 @@
|
||||
[submodule "plugins"]
|
||||
path = plugins
|
||||
url = https://github.com/RaspAP/plugins
|
||||
branch = master
|
||||
path = plugins
|
||||
url = https://github.com/RaspAP/plugins
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<img width="465" alt="Insiders logo" src="https://i.imgur.com/62TMUy5.png">
|
||||
<img width="465" alt="Insiders logo" src="https://user-images.githubusercontent.com/229399/115766971-e19e1900-a3a8-11eb-8c6f-379deb4313d2.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,6 +20,7 @@ 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)
|
||||
@@ -29,8 +30,7 @@ The following features are currently available exclusively to sponsors. A tangib
|
||||
✅ [Custom user avatars](https://docs.raspap.com/authentication/#custom-user-avatars)
|
||||
✅ [WiFi repeater mode](https://docs.raspap.com/ap-basics/#wifi-repeater-mode)
|
||||
✅ [NTP Service](https://docs.raspap.com/ntp/)
|
||||
✅ [Limited privilege user role](https://docs.raspap.com/authentication/#limited-privilege-user-role)
|
||||
✅ [Tailscale VPN](https://docs.raspap.com/tailscale/)
|
||||
✅ [Limited privilege user role](https://docs.raspap.com/authentication/#limited-privilege-user-role)
|
||||
|
||||
Look for the list above to grow as we add more exclusive features. Be sure to visit this page from time to time to learn about what's new, check the [Insiders docs page](https://docs.raspap.com/insiders/) and follow [@RaspAP on Twitter](https://twitter.com/rasp_ap) to stay updated.
|
||||
|
||||
|
||||
64
README.md
@@ -1,13 +1,14 @@
|
||||

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

|
||||
[](https://github.com/raspap/raspap-webgui/releases) [](https://github.com/thibmaek/awesome-raspberry-pi) [](https://github.com/sponsors/RaspAP) [](https://app.travis-ci.com/RaspAP/raspap-webgui) [](https://crowdin.com/project/raspap) [](https://twitter.com/rasp_ap) [](https://reddit.com/r/RaspAP) [](https://discord.gg/KVAsaAR)
|
||||
|
||||
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our [custom OS images](#pre-built-image), [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, [WireGuard](https://docs.raspap.com/wireguard/), [Tailscale](https://docs.raspap.com/tailscale/) and [OpenVPN](https://docs.raspap.com/openvpn/) support, [SSL certificates](https://docs.raspap.com/ssl/), [ad blocking](#ad-blocking), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
|
||||
|
||||
RaspAP has been featured by [PC World](https://www.pcwelt.de/article/1789512/raspberry-pi-als-wlan-router.html), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/), and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in [countless projects](https://github.com/RaspAP/raspap-awesome#projects).
|
||||
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
|
||||
|
||||
RaspAP has been featured on sites such as [Instructables](http://www.instructables.com/id/Raspberry-Pi-As-Completely-Wireless-Router/), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/) and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in countless projects.
|
||||
|
||||
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">
|
||||
@@ -17,13 +18,15 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
|
||||
|
||||
## Contents
|
||||
|
||||
- [Quick start](#quick-start)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick installer](#quick-installer)
|
||||
- [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)
|
||||
@@ -35,43 +38,30 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
|
||||
- [Reporting issues](#reporting-issues)
|
||||
- [License](#license)
|
||||
|
||||
## 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:
|
||||
## 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).
|
||||
|
||||
1. Update Raspbian, including the kernel and firmware, followed by a reboot:
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get full-upgrade
|
||||
sudo reboot
|
||||
```
|
||||
Set the WiFi country in raspi-config's **Localisation Options**: `sudo raspi-config`.
|
||||
2. Set the "WLAN country" option 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.
|
||||
|
||||
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:
|
||||
|
||||
After the reboot at the end of the installation the wireless network will be
|
||||
configured as an access point as follows:
|
||||
* IP address: 10.3.141.1
|
||||
* Username: admin
|
||||
* Password: secret
|
||||
@@ -79,7 +69,7 @@ After completing either of these setup options, the wireless AP network will be
|
||||
* SSID: `raspi-webgui`
|
||||
* Password: ChangeMe
|
||||
|
||||
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.
|
||||
**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/).
|
||||
|
||||
Please [read this](https://docs.raspap.com/issues/) before reporting an issue.
|
||||
|
||||
@@ -128,6 +118,11 @@ 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/).
|
||||
|
||||
@@ -144,10 +139,11 @@ 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) | Beta |
|
||||
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Official |
|
||||
| Debian | Bookworm | ARM / x86_64 | Beta |
|
||||
| Ubuntu | Server 23.04 (Lunar) | ARM / x86_64 | Beta |
|
||||
|
||||
<img src="https://i.imgur.com/XiAJNKb.png" style="width:480px;" />
|
||||
<img src="https://github.com/RaspAP/raspap-webgui/assets/229399/6fe62f2d-631a-46c9-8ceb-83ebf0ade6a9" style="width:640px;" />
|
||||
|
||||
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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/functions.php';
|
||||
|
||||
$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();
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
}
|
||||
$jsonData = ['return'=>$return];
|
||||
echo json_encode($jsonData);
|
||||
$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();
|
||||
}
|
||||
}
|
||||
$jsonData = ['return'=>$return];
|
||||
echo json_encode($jsonData);
|
||||
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,4 +18,3 @@ foreach ($hostapdconfig as $hostapdconfigline) {
|
||||
};
|
||||
$channel = intval($arrConfig['channel']);
|
||||
echo json_encode($channel);
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 +1,10 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,10 +1,10 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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'];
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,26 +1,29 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
require_once '../../src/RaspAP/Plugins/PluginInstaller.php';
|
||||
|
||||
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
|
||||
$plugin_uri = $_POST['plugin_uri'] ?? null;
|
||||
$plugin_version = $_POST['plugin_version'] ?? null;
|
||||
$install_path = $_POST['install_path'] ?? null;
|
||||
|
||||
if (isset($plugin_uri, $plugin_version, $install_path)) {
|
||||
if (isset($plugin_uri) && isset($plugin_version)) {
|
||||
$archiveUrl = rtrim($plugin_uri, '/') . '/archive/refs/tags/' . $plugin_version .'.zip';
|
||||
|
||||
try {
|
||||
$return = $pluginInstaller->installPlugin($plugin_uri, $plugin_version, $install_path);
|
||||
$return = $pluginInstaller->installPlugin($archiveUrl);
|
||||
echo json_encode($return);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(422); // unprocessable content
|
||||
http_response_code(422); // Unprocessable Content
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
} else {
|
||||
http_response_code(400); // Bad Request
|
||||
echo json_encode(['error' => 'Plugin URI, version, and install path are required']);
|
||||
echo json_encode(['error' => 'Plugin URI and version are required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../includes/csrf.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$lastActivity = $_SESSION['lastActivity'] ?? time();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,22 +1,27 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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';
|
||||
|
||||
$uri = RASPI_API_ENDPOINT;
|
||||
preg_match('/(\d+(\.\d+)+)/', RASPI_VERSION, $matches);
|
||||
$thisRelease = $matches[0];
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$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,18 +1,25 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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';
|
||||
|
||||
$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);
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
$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);
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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,16 +1,24 @@
|
||||
<?php
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/CSRF.php';
|
||||
|
||||
require '../../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';
|
||||
|
||||
// set installer path + options
|
||||
$path = getenv("DOCUMENT_ROOT");
|
||||
$opts = " --update --yes --check 0 --path $path";
|
||||
$installer = "sudo /etc/raspap/system/raspbian.sh";
|
||||
$execUpdate = $installer.$opts;
|
||||
if (isset($_POST['csrf_token'])) {
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
// set installer path + options
|
||||
$path = getenv("DOCUMENT_ROOT");
|
||||
$opts = " --update --yes --path $path";
|
||||
$installer = "sudo /etc/raspap/system/raspbian.sh";
|
||||
$execUpdate = $installer.$opts;
|
||||
|
||||
$response = shell_exec($execUpdate);
|
||||
echo json_encode($response);
|
||||
$response = shell_exec($execUpdate);
|
||||
echo json_encode($response);
|
||||
|
||||
} else {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
require_once '../../includes/autoload.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/session.php';
|
||||
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
|
||||
require_once '../../includes/authenticate.php';
|
||||
|
||||
$logFile = '/tmp/raspap_install.log';
|
||||
|
||||
297
app/css/all.css
@@ -9,7 +9,6 @@ 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;
|
||||
}
|
||||
@@ -169,7 +168,12 @@ th {
|
||||
}
|
||||
|
||||
canvas#divDBChartBandwidthhourly {
|
||||
height: 509px!important;
|
||||
height: 350px!important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 150px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.dbChart {
|
||||
@@ -186,7 +190,7 @@ canvas#divDBChartBandwidthhourly {
|
||||
}
|
||||
|
||||
.check-progress {
|
||||
color: var(--raspap-text-light);
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.fa-check {
|
||||
@@ -384,290 +388,3 @@ textarea.plugin-log {
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.connections-left,
|
||||
.connections-right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.connection-item {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
z-index: 5;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.connection-right {
|
||||
align-items: center;
|
||||
margin-left: 10rem;
|
||||
}
|
||||
|
||||
.connections-left i {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.connections-left i:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.connections-left i:last-child {
|
||||
margin-bottom: 0;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.center-device {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.center-device-top {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.client-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row-reverse;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.client-count {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clients-status {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.dashed-lines,
|
||||
.solid-lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 1rem;
|
||||
left: 112px;
|
||||
}
|
||||
|
||||
.dashed-lines-right,
|
||||
.solid-lines-right {
|
||||
left: -80px;
|
||||
}
|
||||
|
||||
.solid-lines, .solid-lines-right {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dashed-lines, .dashed-lines-right {
|
||||
z-index 0;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
.wifi-bands {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.band {
|
||||
padding: 0.25rem 1rem;
|
||||
border: 2px solid var(--raspap-text-light);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
font-weight: 600;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.band.active {
|
||||
border-color: var(--raspap-theme-color);
|
||||
color: var(--raspap-theme-color);
|
||||
}
|
||||
|
||||
.device-label {
|
||||
font-size: 1.3rem;
|
||||
text-align: center;
|
||||
color: var(--raspap-theme-color);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.3rem;
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-item .fa-stack {
|
||||
width: 1.5em!important;
|
||||
}
|
||||
|
||||
.connection-item>i {
|
||||
color: var(--raspap-text-light);
|
||||
}
|
||||
|
||||
.connection-item .fa-stack {
|
||||
min-width: 2.5em;
|
||||
}
|
||||
|
||||
.connections-left>.connection-item>span {
|
||||
color: var(--raspap-text-light);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: var(--raspap-text-light)!important;
|
||||
}
|
||||
|
||||
a.inactive:hover,
|
||||
a.inactive:focus {
|
||||
color: var(--raspap-text-light) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.connection-item a > span:not(.fa-stack) {
|
||||
display: none!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.connections-right,
|
||||
.connections-left {
|
||||
display: none!important;
|
||||
}
|
||||
.dashboard-container {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
.device-status {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.clients-mobile {
|
||||
display: flex!important;
|
||||
flex-direction: row!important;
|
||||
}
|
||||
}
|
||||
.connection-item.active > span {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.connection-item.active > i {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.status-item.active > span {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
.status-item.active > i {
|
||||
color: var(--raspap-theme-color)!important;
|
||||
}
|
||||
|
||||
.clients-mobile {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.client-type {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.client-type i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--raspap-theme-color);
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid var(--raspap-theme-color);
|
||||
}
|
||||
|
||||
.client-type i.badge-icon {
|
||||
font-size: 0.7rem;
|
||||
background: var(--raspap-theme-color);
|
||||
color: var(--raspap-offwhite);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.client-count {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: var(--raspap-theme-color);
|
||||
color: var(--raspap-offwhite);
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.device-illustration {
|
||||
min-width: 220px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.led-pulse {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
.hostapd-led {
|
||||
color: #28a745;
|
||||
opacity: 1;
|
||||
transition: opacity 0.05s;
|
||||
}
|
||||
|
||||
|
||||
BIN
app/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
9
app/icons/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/dist/icons/mstile-150x150.png"/>
|
||||
<TileColor>#b91d47</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
app/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 15 KiB |
BIN
app/icons/favicon.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 69 KiB |
BIN
app/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
48
app/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="240.000000pt" height="240.000000pt" viewBox="0 0 240.000000 240.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,240.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M867 2334 c-3 -3 -3 -20 -1 -37 2 -18 6 -54 8 -82 2 -27 5 -53 5 -56
|
||||
1 -4 31 -6 69 -6 63 0 162 -9 207 -19 11 -2 31 -6 45 -8 14 -2 59 -14 100 -27
|
||||
41 -13 80 -23 85 -24 6 0 12 -2 15 -5 3 -3 37 -20 75 -38 161 -78 335 -219
|
||||
436 -357 55 -74 121 -175 115 -175 -3 0 10 -30 28 -67 18 -38 37 -81 41 -98 5
|
||||
-16 9 -34 11 -40 27 -94 44 -173 48 -237 2 -15 19 -20 99 -23 95 -5 101 -1 86
|
||||
60 -4 17 -10 48 -13 70 -27 197 -131 443 -258 613 -52 69 -252 272 -269 272
|
||||
-5 0 -18 8 -28 18 -37 32 -50 42 -61 42 -5 0 -10 4 -10 8 0 4 -15 15 -32 23
|
||||
-18 9 -53 27 -78 41 -95 52 -303 123 -390 133 -19 2 -46 7 -60 10 -34 8 -266
|
||||
15 -273 9z"/>
|
||||
<path d="M895 1867 c3 -50 6 -92 8 -93 1 -1 40 -4 87 -8 114 -8 139 -12 212
|
||||
-37 272 -91 470 -300 547 -578 11 -39 17 -71 13 -71 -3 0 -2 -4 4 -10 5 -5 50
|
||||
-12 100 -15 l91 -6 -3 26 c-15 141 -91 333 -182 460 -47 65 -152 170 -222 222
|
||||
-164 122 -367 191 -582 198 l-79 2 6 -90z"/>
|
||||
<path d="M747 1604 c-1 -2 -14 -5 -27 -7 -245 -47 -453 -199 -562 -412 -47
|
||||
-91 -81 -232 -81 -340 -1 -88 19 -215 34 -215 5 0 6 -7 3 -15 -4 -8 -2 -21 3
|
||||
-28 5 -6 20 -38 33 -70 12 -31 26 -57 31 -57 5 0 9 -6 9 -13 0 -7 9 -23 20
|
||||
-35 11 -12 20 -25 20 -29 0 -12 80 -93 91 -93 6 0 8 -4 5 -8 -11 -18 215 -159
|
||||
235 -147 5 4 9 2 9 -4 0 -20 189 -58 280 -56 87 2 213 25 245 44 6 3 12 6 15
|
||||
6 21 3 50 18 50 26 0 6 5 7 10 4 13 -8 128 67 193 125 67 60 164 196 159 223
|
||||
-1 4 1 7 6 7 8 0 31 47 36 76 3 11 9 32 15 47 24 62 33 251 17 344 -21 123
|
||||
-93 271 -181 371 -49 54 -156 142 -174 142 -6 0 -11 4 -11 9 0 18 -180 85
|
||||
-260 97 -43 7 -218 13 -223 8z m243 -199 c30 -9 62 -17 70 -18 8 -1 20 -5 25
|
||||
-10 6 -4 37 -25 70 -46 71 -45 165 -135 174 -166 1 -5 15 -33 31 -61 78 -136
|
||||
85 -368 15 -504 -8 -16 -15 -33 -14 -36 0 -4 -3 -10 -8 -13 -5 -3 -23 -26 -40
|
||||
-51 -25 -37 -102 -113 -148 -148 -15 -12 -117 -59 -145 -68 -97 -31 -220 -35
|
||||
-310 -11 -8 2 -28 7 -45 12 -16 4 -51 19 -77 32 -27 13 -51 24 -55 26 -4 1
|
||||
-18 12 -31 24 -14 13 -30 20 -36 16 -6 -3 -8 -3 -4 1 9 10 -34 55 -46 48 -5
|
||||
-3 -6 0 -2 6 3 6 -2 17 -12 24 -10 7 -27 28 -37 46 -11 17 -24 32 -29 32 -5 0
|
||||
-6 3 -3 7 4 3 -2 20 -12 37 -27 43 -49 111 -56 176 -8 60 -7 158 0 170 3 4 7
|
||||
25 10 46 5 45 56 152 97 205 15 20 28 42 28 48 0 6 4 10 8 8 4 -1 26 16 48 38
|
||||
22 22 49 45 60 51 55 32 123 65 140 69 10 2 42 10 69 18 63 17 193 13 265 -8z"/>
|
||||
<path d="M717 1231 c-299 -97 -383 -476 -154 -691 23 -22 48 -40 54 -40 7 0
|
||||
13 -4 13 -9 0 -17 116 -53 185 -57 130 -8 228 29 320 121 49 49 66 75 89 135
|
||||
33 89 38 197 11 270 -9 25 -17 50 -19 57 -6 30 -85 124 -133 157 -104 73 -248
|
||||
95 -366 57z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -1,21 +1,14 @@
|
||||
{
|
||||
"name": "RaspAP Admin Panel",
|
||||
"short_name": "RaspAP",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/app/icons/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/app/icons/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
"name": "RaspAP",
|
||||
"short_name": "RaspAP",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/app/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,54 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,40 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,46 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,65 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (!isset($_GET['uri']) || !filter_var($_GET['uri'], FILTER_VALIDATE_URL)) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
exit("Invalid or missing URI parameter");
|
||||
}
|
||||
|
||||
$uri = $_GET['uri'];
|
||||
$command = "qrencode -t svg -m 0 -o - " . escapeshellarg($uri);
|
||||
|
||||
$svg = shell_exec($command);
|
||||
if ($svg === null) {
|
||||
error_log("QR generation failed for URI: $uri");
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
exit("Failed to generate QR code");
|
||||
}
|
||||
|
||||
$etag = hash('sha256', $uri);
|
||||
$content_length = strlen($svg);
|
||||
$last_modified = gmdate("D, d M Y H:i:s") . " GMT";
|
||||
|
||||
header("Content-Type: image/svg+xml");
|
||||
header("Content-Length: $content_length");
|
||||
header("Last-Modified: $last_modified");
|
||||
header("ETag: \"$etag\"");
|
||||
header("X-QR-Code-Content: " . htmlspecialchars($uri, ENT_QUOTES, 'UTF-8'));
|
||||
|
||||
echo $svg;
|
||||
|
||||
@@ -13,7 +13,6 @@ 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);
|
||||
@@ -24,6 +23,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_sanitized");
|
||||
header("X-QR-Code-Content: $peer_conf");
|
||||
echo shell_exec($command);
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
|
||||
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
|
||||
|
||||
// assume WPA encryption and get the passphrase
|
||||
// assume wpa encryption and get the passphrase
|
||||
$type = "WPA";
|
||||
$password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase'];
|
||||
|
||||
// use WEP if configured
|
||||
$wep_default_key = intval($hostapd['wep_default_key'] ?? 0);
|
||||
// use wep if configured
|
||||
$wep_default_key = intval($hostapd['wep_default_key']);
|
||||
$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) !== 0 ? "H:true" : "";
|
||||
$hidden = intval($hostapd['ignore_broadcast_ssid']) != 0 ? "H:true" : "";
|
||||
|
||||
$ssid = qr_encode($ssid);
|
||||
$password = qr_encode($password);
|
||||
|
||||
183
app/js/custom.js
@@ -332,42 +332,47 @@ $('#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) {
|
||||
success: function(response) {
|
||||
let endPolling = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
if (!endPolling) {
|
||||
setTimeout(fetchUpdateResponse, 500);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("AJAX Error:", error);
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -465,68 +470,54 @@ $('#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;
|
||||
var installed = button.data('plugin-installed');
|
||||
|
||||
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-uri').html(manifestData.plugin_uri
|
||||
? `<a href="${manifestData.plugin_uri}" target="_blank">${manifestData.plugin_uri}</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');
|
||||
? 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');
|
||||
$('#plugin-configuration').html(formatProperty(manifestData.configuration || {}));
|
||||
$('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {}));
|
||||
$('#plugin-sudoers').html(formatProperty(manifestData.sudoers || []));
|
||||
$('#plugin-user-name').html(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 pluginUri = $('#plugin-uri a').attr('href');
|
||||
var pluginVersion = $('#plugin-version').text();
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
|
||||
if (pluginConfirm === 'Install now') {
|
||||
$("#install-user-plugin").modal('hide');
|
||||
$("#install-user-plugin").modal('hide');
|
||||
|
||||
if ($('#js-install-plugin-confirm').text() === 'Install now') {
|
||||
$("#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) {
|
||||
@@ -564,11 +555,6 @@ $('#js-install-plugin-confirm').on('click', function (e) {
|
||||
$('#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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -584,7 +570,7 @@ function formatProperty(prop) {
|
||||
return Object.entries(item)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('<br/>');
|
||||
}).join('<br/>');
|
||||
}).join('<br/><br/>');
|
||||
}
|
||||
return prop.map(line => `${line}<br/>`).join('');
|
||||
}
|
||||
@@ -990,9 +976,42 @@ function getCookie(cname) {
|
||||
// Define themes
|
||||
var themes = {
|
||||
"default": "custom.php",
|
||||
"hackernews" : "hackernews.css"
|
||||
"hackernews" : "hackernews.css",
|
||||
"lightsout" : "lightsout.php",
|
||||
"material-light" : "material-light.php",
|
||||
"material-dark" : "material-dark.php",
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -1001,52 +1020,6 @@ $(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");
|
||||
});
|
||||
}
|
||||
|
||||
function updateActivityLED() {
|
||||
const threshold_bytes = 300;
|
||||
fetch('/app/net_activity')
|
||||
.then(res => res.text())
|
||||
.then(data => {
|
||||
const activity = parseInt(data.trim());
|
||||
const leds = document.querySelectorAll('.hostapd-led');
|
||||
|
||||
if (!isNaN(activity)) {
|
||||
leds.forEach(led => {
|
||||
if (activity > threshold_bytes) {
|
||||
led.classList.add('led-pulse');
|
||||
setTimeout(() => {
|
||||
led.classList.remove('led-pulse');
|
||||
}, 50);
|
||||
} else {
|
||||
led.classList.remove('led-pulse');
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => { /* ignore fetch errors */ });
|
||||
}
|
||||
setInterval(updateActivityLED, 100);
|
||||
|
||||
$(document).ready(function() {
|
||||
const $htmlElement = $('html');
|
||||
const $modeswitch = $('#night-mode');
|
||||
|
||||
@@ -4,7 +4,7 @@ ctrl_interface_group=0
|
||||
beacon_int=100
|
||||
auth_algs=1
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
ssid=RaspAP
|
||||
ssid=raspi-webgui
|
||||
channel=1
|
||||
hw_mode=g
|
||||
wpa_passphrase=ChangeMe
|
||||
|
||||
BIN
dist/raspap/css/fonts/RaspAP.eot
vendored
2
dist/raspap/css/fonts/RaspAP.svg
vendored
@@ -9,6 +9,4 @@
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="wireguard" d="M1023.147 463.147c0 0 23.595 496.853-522.453 496.853-482.859 0-497.963-476.587-497.963-476.587s-70.997-547.413 509.141-547.413c556.501 0 511.275 527.147 511.275 527.147zM347.947 636.757c102.4 62.72 233.344 24.363 282.368-69.888 9.301-17.877 10.496-45.355 4.608-64.128-20.352-64.683-68.309-100.949-134.187-116.395 19.413 16.64 34.859 35.499 39.808 61.525 1.195 5.504 1.88 11.827 1.88 18.31 0 20.027-6.533 38.528-17.584 53.488l0.174-0.246c-16.797 22.874-43.588 37.556-73.809 37.556-11.257 0-22.038-2.037-31.995-5.763l0.63 0.207c-40.533-15.36-62.72-52.395-58.752-97.877 3.712-42.24 35.797-69.632 95.787-80.043-8.96-4.736-15.872-8.235-22.613-11.989-27.988-15.524-51.374-35.995-69.74-60.451l-0.404-0.562c-6.101-8.192-10.24-8.875-19.541-3.2-120.619 73.771-128.384 258.859 3.371 339.456zM257.707 180.992c-19.413-4.949-38.187-12.203-57.984-18.688 9.685 65.365 86.229 125.568 150.997 118.699-18.043-24.598-29.583-54.982-31.551-87.945l-0.022-0.46c-21.504-3.968-41.813-6.613-61.44-11.605zM669.995 819.2c19.115-0.725 38.315-0.427 57.472-0.853 5.287-0.363 10.162-1.075 14.91-2.128l-0.659 0.123c-4.574-6.938-9.348-12.986-14.582-18.599l0.076 0.082c-6.827-6.4-14.549-12.629-24.448-2.944-2.347 2.347-7.979 1.792-12.075 1.877-19.072 0.213-38.144 0.853-57.173 0.128-17.856-0.589-34.82-2.396-51.386-5.353l2.149 0.318c-3.072-0.555-7.595-10.667-6.229-14.421 3.328-8.832 8.149-18.56 15.317-24.192 26.411-20.907 54.485-39.595 81.067-60.288 25.771-20.139 49.792-42.24 64.427-72.533 19.029-39.595 19.627-81.067 11.392-122.752-13.739-69.547-48.939-127.147-105.941-169.045-22.955-16.853-51.413-26.453-77.696-38.528-23.168-10.667-46.933-19.84-70.144-30.379-41.813-19.029-65.28-64.427-58.411-111.573 6.357-43.307 44.373-79.445 87.851-86.912 52.181-8.96 106.069 25.003 118.827 78.080 14.336 59.605-18.048 112.896-78.72 129.024l-10.923 2.816c16.213 7.253 30.208 12.416 43.179 19.541q33.835 18.645 66.475 39.467c6.4 4.096 9.856 4.096 15.36-0.597 41.685-36.096 66.56-80.981 73.557-135.979 11.52-91.093-31.573-174.763-112.896-217.643-125.781-66.347-279.765 9.173-307.541 148.651-23.808 119.467 60.501 227.84 162.005 248.747 43.648 9.003 83.541 27.179 114.56 60.8 20.053 21.675 29.739 40.277 33.067 48.683 5.86 14.568 9.259 31.458 9.259 49.142 0 0.094 0 0.187 0 0.281v-0.014c-0.72 15.473-4.371 29.921-10.408 43.044l0.296-0.719c-10.581 24.149-51.2 62.549-61.227 70.656l-95.573 74.837c-3.371 2.773-7.168 2.56-15.36 2.005-9.813-0.683-34.773-2.048-45.525 0.768 8.704 6.613 32.427 16.213 42.667 23.893-30.976 20.907-66.304 13.397-98.773 19.627 7.509 13.995 44.629 35.456 65.749 37.888-1.455 13.545-3.483 25.484-6.166 37.173l0.406-2.101c-1.28 4.736-6.571 9.387-11.221 12.075-11.179 6.571-23.083 11.989-35.968 18.517 10.935 7.156 24.244 11.558 38.555 11.945l0.101 0.002c1.66 0.068 3.608 0.107 5.566 0.107 11.77 0 23.21-1.408 34.163-4.064l-0.987 0.202c23.040-5.248 41.387-1.792 59.691 13.824-14.421 5.803-28.843 11.093-42.795 17.365-16.163 7.396-29.343 14.415-42.082 22.091l1.89-1.056c36.267-5.035 71.296-18.645 108.373-13.653l0.939 5.035-86.101 20.053c51.328 4.693 99.115 5.461 144.384-16.555 12.757-6.229 26.027-11.349 38.272-18.432 5.973-3.413 9.941-10.24 14.848-15.573 3.84-4.181 6.997-9.813 11.776-12.373 18.091-9.6 37.973-9.984 58.283-9.515l0.427 6.827c20.437-6.4 43.392-29.952 43.392-47.147-33.109 0-66.133 0.128-99.2-0.171-3.541 0-7.040-2.603-10.539-4.011 3.328-1.963 6.613-5.461 10.027-5.589zM627.328 868.139c-1.461-0.899-2.42-2.488-2.42-4.302 0-1.516 0.67-2.876 1.731-3.799l0.006-0.005c1.344-2.305 3.804-3.83 6.62-3.83 1.429 0 2.767 0.393 3.91 1.076l-0.035-0.019c3.2 1.621 6.315 3.328 10.155 5.333-3.072 2.645-5.547 4.864-8.107 6.955-4.523 3.712-8.235 1.365-11.861-1.408z" />
|
||||
<glyph unicode="" glyph-name="raspap" horiz-adv-x="1031" d="M540.058 281.983c0-104.182-84.446-188.637-188.625-188.637-104.176 0-188.62 84.455-188.62 188.637 0 104.171 84.444 188.625 188.62 188.625 104.179 0 188.625-84.455 188.625-188.625zM351.437 550.062c-147.818 0-268.074-120.259-268.074-268.080 0-147.826 120.257-268.091 268.074-268.091s268.077 120.265 268.077 268.091c0 147.821-120.259 268.080-268.077 268.080zM351.437-58.985c-188 0-340.95 152.958-340.95 340.967 0 188.003 152.95 340.956 340.95 340.956 188.003 0 340.953-152.953 340.953-340.956 0-188.009-152.95-340.967-340.953-340.967zM404.82 698.222c185.52 0 339.484-137.497 365.479-315.929l79.208-5.253c-24.125 224.046-214.339 399.077-444.686 399.077-10.909 0-21.723-0.412-32.433-1.186l5.16-77.823c9.017 0.661 18.093 1.113 27.272 1.113zM404.989 874.303c285.73 0 520.41-222.659 539.731-503.584l78.375-5.205c-16.843 326.355-287.644 586.685-618.106 586.685-14.884 0-29.644-0.561-44.264-1.6l5.157-77.719c12.919 0.928 25.958 1.424 39.106 1.424z" />
|
||||
<glyph unicode="" glyph-name="tailscale" d="M131.2 323.8c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8-127.6-57.4-127.6-127.8 57.2-127.8 127.6-127.8zM514.4 323.8c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8c-70.6 0-127.8-57.2-127.8-127.8s57.2-127.8 127.8-127.8zM514.4-64c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8c-70.6 0-127.8-57.2-127.8-127.8s57.2-127.8 127.8-127.8zM892.8 323.8c70.6 0 127.8 57.2 127.8 127.8s-57.2 127.8-127.8 127.8c-70.6 0-127.8-57.2-127.8-127.8s57.2-127.8 127.8-127.8z" />
|
||||
<glyph unicode="" glyph-name="torproxy" d="M750.016 439.488c-32.512 29.504-73.504 53.344-115.328 77.152-19.008 10.496-77.344 56.16-57.152 120.992l-36.32 15.328c57.152 88.672 131.68 176.32 223.008 258.336-73.344-24.672-138.176-62.848-186.848-130.496 28.672 60 75.328 119.168 126.848 179.168-70.496-50.496-131.488-107.68-169.664-184l26.656 106.848c-38.176-68.672-64.832-138.336-75.328-207.84l-56.16 22.848-9.504-7.68c49.504-88.672 23.84-135.328-0.992-151.68-49.504-33.344-120.992-76.16-157.344-113.344-68.672-70.656-88.672-137.344-82.016-226.016 6.656-113.504 89.664-207.84 199.328-244.992 48.672-16.32 93.344-18.176 143.008-18.176 80 0 162.016 20.992 222.176 71.488 63.84 52.992 100.832 131.488 100.992 214.496 0.32 82.656-34.336 161.664-95.328 217.504zM598.336 60.832c-3.84-17.152-16.16-38.176-31.328-57.152 5.664 10.496 10.496 20.992 13.344 32.512 23.84 84.832 34.336 123.84 22.848 217.344-1.824 9.504-5.664 40-20 73.344-20 50.656-50.496 98.336-54.336 108.832-6.656 16.16-16.16 84.832-17.152 131.488 0.992-40 3.84-113.344 14.336-142.016 2.848-9.664 30.496-52.512 50.496-104.832 13.344-36.32 16.16-69.664 19.008-79.168 9.664-43.008-1.824-115.488-16.992-184-4.832-24.832-18.176-53.504-35.328-75.328 9.504 13.344 17.152 30.496 22.848 50.496 11.488 40 16.16 91.488 15.168 124-0.832 19.008-9.504 60-23.84 97.152-8.512 20-20.992 40.992-29.504 55.328-9.504 14.336-9.504 45.664-13.344 82.016 0.832-39.168-2.848-59.168 6.656-86.848 5.664-16.16 26.656-39.008 32.32-60.992 8.672-29.504 17.152-62.016 16.32-82.016 0-22.848-0.992-64.832-11.488-110.656-6.656-34.176-22.016-63.84-46.656-82.848 10.496 13.344 16.16 26.656 19.008 40 3.84 20 4.832 39.168 6.656 63.008 2.016 24.512 0.32 49.344-4.672 73.344-7.68 34.336-20 68.672-25.824 92.512 0.992-26.656 11.488-60 16.32-95.328 3.68-25.824 1.824-51.488 0.832-74.336-0.832-26.656-9.504-73.504-20.992-96.32-11.488 4.832-15.168 11.488-22.848 20.992-9.664 12.32-15.328 25.664-20.992 40.992-5.344 12.672-9.504 25.664-12.512 39.008-4.512 33.504 4.16 67.168 23.84 94.496 20 28.672 24 30.496 30.496 63.84-9.504-29.504-16.16-32.32-37.152-57.152-23.84-27.68-27.488-67.68-27.488-100.16 0-13.344 5.664-28.672 10.496-43.008 5.664-15.168 11.328-30.336 19.008-41.824 5.664-9.504 13.344-16.16 20-20.992-24.832 6.656-50.496 16.16-66.656 29.504-40 34.496-75.328 92.512-80.16 144-3.84 42.016 34.336 103.008 88.672 133.504 45.824 26.656 56.32 56.32 65.824 104.992-13.344-42.016-26.656-78.336-70.656-100.16-62.848-34.336-95.328-89.664-92.32-143.008 4.672-67.68 31.328-114.496 85.824-151.68 12.32-8.672 29.504-17.152 47.68-23.84-67.84 16.16-76.32 25.664-99.168 52.32 0 2.016-5.824 5.824-5.824 6.656-30.496 34.336-68.512 93.504-82.016 147.84-4.672 19.008-9.504 39.008-3.68 58.176 24.672 89.664 79.008 124 133.344 160.992 13.504 9.664 26.848 18.176 39.168 27.68 30.496 24 38.176 85.824 44.832 121.152-12.32-43.008-25.824-96.32-49.664-113.504-12.32-9.504-27.68-17.152-40-25.664-56.16-38.176-112.512-74.496-138.176-166.848-5.824-24-2.016-41.152 3.68-64 14.336-56.16 52.512-117.152 84.992-153.504l5.664-5.664c14.336-16.32 32.512-28.672 54.336-37.152-19.168 4.512-37.664 11.168-55.328 20-88.672 42.848-147.68 135.328-151.488 210.656-7.68 153.504 65.824 198.336 134.336 254.656 38.176 31.328 91.68 46.656 122.176 102.848 5.664 12.512 9.504 39.168 1.824 67.84-2.848 9.504-17.152 43.84-22.848 51.488l84.832-37.344c-1.824-40-2.848-72.32 4.672-102.016 8.672-32.32 50.656-79.008 67.84-133.504 33.344-102.848 24.832-237.152 0.832-342.176z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 5.0 KiB |
BIN
dist/raspap/css/fonts/RaspAP.ttf
vendored
BIN
dist/raspap/css/fonts/RaspAP.woff
vendored
57
dist/raspap/css/style.css
vendored
@@ -1,20 +1,23 @@
|
||||
/*!
|
||||
* RaspAP-Brands Brand Icons - https://raspap.com
|
||||
* License - https://github.com/billz/RaspAP-Brands-webgui/blob/master/LICENSE
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'RaspAP';
|
||||
src: url('fonts/RaspAP.eot?3vjloy');
|
||||
src: url('fonts/RaspAP.eot?3vjloy#iefix') format('embedded-opentype'),
|
||||
url('fonts/RaspAP.ttf?3vjloy') format('truetype'),
|
||||
url('fonts/RaspAP.woff?3vjloy') format('woff'),
|
||||
url('fonts/RaspAP.svg?3vjloy#RaspAP') format('svg');
|
||||
src: url('fonts/RaspAP.eot?e76qs3');
|
||||
src: url('fonts/RaspAP.eot?e76qs3#iefix') format('embedded-opentype'),
|
||||
url('fonts/RaspAP.ttf?e76qs3') format('truetype'),
|
||||
url('fonts/RaspAP.woff?e76qs3') format('woff'),
|
||||
url('fonts/RaspAP.svg?e76qs3#RaspAP') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
|
||||
[class^="ra-"], [class*=" ra-"] {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
/* use !important to prevent issues with browser extensions that change ..webfonts */
|
||||
font-family: 'RaspAP' !important;
|
||||
speak: never;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
@@ -27,38 +30,24 @@
|
||||
}
|
||||
|
||||
.ra-wireguard:before {
|
||||
font-size: 1.1rem;
|
||||
font-size: 1.2rem;
|
||||
content: "\e900";
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.card-header .ra-wireguard:before {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sidebar .nav-item.active .nav-link
|
||||
span.ra-wireguard:before {
|
||||
color: #6e707e;
|
||||
}
|
||||
|
||||
.ra-raspap:before {
|
||||
font-size: 4.35rem;
|
||||
content: "\e901";
|
||||
color: #2b8080;
|
||||
margin-left: 0.1em;
|
||||
}
|
||||
.ra-tailscale:before {
|
||||
font-size: 1.1rem;
|
||||
content: "\e902";
|
||||
vertical-align: top;
|
||||
}
|
||||
.ra-torproxy:before {
|
||||
font-size: 1.3rem;
|
||||
content: "\e903";
|
||||
vertical-align: top;
|
||||
}
|
||||
.card-header .ra-wireguard:before,
|
||||
.card-header .ra-tailscale:before,
|
||||
.card-header .ra-torproxy:before {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sidebar .nav-item.active .nav-link span.ra-wireguard:before,
|
||||
.sidebar .nav-item.active .nav-link span.ra-tailscale:before,
|
||||
.sidebar .nav-item.active .nav-link span.ra-torproxy:before {
|
||||
color: #6e707e;
|
||||
}
|
||||
|
||||
.sb-nav-link-icon .ra-tailscale {
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace RaspAP\Tokens;
|
||||
|
||||
class CSRF
|
||||
{
|
||||
protected static ?CSRFTokenizer $instance = null;
|
||||
|
||||
/*
|
||||
* Get the CSRFTokenizer instance (singleton)
|
||||
*
|
||||
* @return CSRFTokenizer
|
||||
*/
|
||||
public static function instance(): CSRFTokenizer
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new CSRFTokenizer();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function token(): string
|
||||
{
|
||||
return self::instance()->getToken();
|
||||
}
|
||||
|
||||
public static function verify(): bool
|
||||
{
|
||||
if (!isset($_POST['csrf_token'])) {
|
||||
return false;
|
||||
}
|
||||
return self::instance()->csrfValidateRequest() &&
|
||||
self::instance()->CSRFValidate($_POST['csrf_token']);
|
||||
}
|
||||
|
||||
public static function metaTag(): string
|
||||
{
|
||||
return self::instance()->CSRFMetaTag();
|
||||
}
|
||||
|
||||
public static function hiddenField(): string
|
||||
{
|
||||
return self::instance()->CSRFTokenFieldTag();
|
||||
}
|
||||
|
||||
public static function handleInvalidToken(): void
|
||||
{
|
||||
self::instance()->handleInvalidCSRFToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a CSRF Request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateRequest(): bool
|
||||
{
|
||||
$methods = ['POST', 'PUT', 'DELETE', 'PATCH'];
|
||||
return in_array($_SERVER['REQUEST_METHOD'], $methods) &&
|
||||
self::instance()->csrfValidateRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if (\RaspAP\Tokens\CSRF::validateRequest()) {
|
||||
if (!\RaspAP\Tokens\CSRF::verify()) {
|
||||
error_log("CSRF verification failed: Token missing or invalid");
|
||||
\RaspAP\Tokens\CSRF::handleInvalidToken();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ 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)) {
|
||||
@@ -63,13 +64,6 @@ 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,8 +35,6 @@ function DisplayAuthConfig($username)
|
||||
} else {
|
||||
$status->addMessage('Old password does not match', 'danger');
|
||||
}
|
||||
} elseif (isset($_POST['logout'])) {
|
||||
$auth->logout();
|
||||
}
|
||||
|
||||
echo renderTemplate(
|
||||
|
||||
@@ -9,13 +9,31 @@
|
||||
*/
|
||||
spl_autoload_register(function ($class) {
|
||||
|
||||
// base directory where all class files are stored
|
||||
$base_dir = __DIR__ . '/../src/';
|
||||
// project-specific namespace prefix
|
||||
$prefix = '';
|
||||
|
||||
// convert the fully qualified class name into a file path
|
||||
$file = $base_dir . str_replace('\\', '/', $class) . '.php';
|
||||
// base directory for the namespace prefix
|
||||
$base_dir = 'src/';
|
||||
|
||||
// require the file if it exists
|
||||
// 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
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
|
||||
7
includes/csrf.php
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once 'functions.php';
|
||||
|
||||
if (csrfValidateRequest() && !CSRFValidate()) {
|
||||
handleInvalidCSRFToken();
|
||||
}
|
||||
@@ -5,170 +5,226 @@ require_once 'includes/wifi_functions.php';
|
||||
require_once 'includes/functions.php';
|
||||
|
||||
/**
|
||||
* Displays the dashboard
|
||||
* Show dashboard page.
|
||||
*/
|
||||
function DisplayDashboard(&$extraFooterScripts): void
|
||||
function DisplayDashboard(&$extraFooterScripts)
|
||||
{
|
||||
// instantiate RaspAP objects
|
||||
$system = new \RaspAP\System\Sysinfo;
|
||||
$dashboard = new \RaspAP\UI\Dashboard;
|
||||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
$pluginManager = \RaspAP\Plugins\PluginManager::getInstance();
|
||||
|
||||
// set AP and client interface session vars
|
||||
getWifiInterface();
|
||||
|
||||
$interface = $_SESSION['ap_interface'] ?? 'wlan0';
|
||||
$clientInterface = $_SESSION['wifi_client_interface'];
|
||||
$hostname = $system->hostname();
|
||||
$revision = $system->rpiRevision();
|
||||
$deviceImage = $dashboard->getDeviceImage($revision);
|
||||
$hostapd = $system->hostapdStatus();
|
||||
$adblock = $system->adBlockStatus();
|
||||
$vpn = $system->getActiveVpnInterface();
|
||||
$frequency = $dashboard->getFrequencyBand($interface);
|
||||
$details = $dashboard->getInterfaceDetails($interface);
|
||||
$wireless = $dashboard->getWirelessDetails($interface);
|
||||
$connectionType = $dashboard->getConnectionType();
|
||||
$connectionIcon = $dashboard->getConnectionIcon($connectionType);
|
||||
$state = strtolower($details['state']);
|
||||
$wirelessClients = $dashboard->getWirelessClients($interface);
|
||||
$ethernetClients = $dashboard->getEthernetClients();
|
||||
$totalClients = $wirelessClients + $ethernetClients;
|
||||
$plugins = $pluginManager->getInstalledPlugins();
|
||||
$bridgedEnable = getBridgedState();
|
||||
|
||||
// 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']);
|
||||
$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;
|
||||
}
|
||||
|
||||
$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>';
|
||||
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);
|
||||
|
||||
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 {
|
||||
$firewallManaged = '<a href="/plugin__Firewall">';
|
||||
$firewallStatus = ($dashboard->firewallEnabled() == true) ? "active" : "";
|
||||
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'];
|
||||
$clientInterface = $_SESSION['wifi_client_interface'];
|
||||
$apInterface = $_SESSION['ap_interface'];
|
||||
$MACPattern = '"([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}"';
|
||||
|
||||
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);
|
||||
}
|
||||
$ifaceStatus = $wlan0up ? "up" : "down";
|
||||
|
||||
echo renderTemplate(
|
||||
"dashboard", compact(
|
||||
"revision",
|
||||
"deviceImage",
|
||||
"interface",
|
||||
"clients",
|
||||
"moreLink",
|
||||
"apInterface",
|
||||
"clientInterface",
|
||||
"state",
|
||||
"bridgedStatus",
|
||||
"hostapdStatus",
|
||||
"adblockStatus",
|
||||
"vpnStatus",
|
||||
"vpnManaged",
|
||||
"firewallUnavailable",
|
||||
"firewallStatus",
|
||||
"firewallManaged",
|
||||
"ipv4Address",
|
||||
"ipv4Netmask",
|
||||
"macAddress",
|
||||
"ssid",
|
||||
"ifaceStatus",
|
||||
"bridgedEnable",
|
||||
"status",
|
||||
"ipv4Addrs",
|
||||
"ipv4Netmasks",
|
||||
"ipv6Addrs",
|
||||
"macAddr",
|
||||
"strRxPackets",
|
||||
"strRxBytes",
|
||||
"strTxPackets",
|
||||
"strTxBytes",
|
||||
"connectedSSID",
|
||||
"connectedBSSID",
|
||||
"bitrate",
|
||||
"signalLevel",
|
||||
"txPower",
|
||||
"frequency",
|
||||
"freq5active",
|
||||
"freq24active",
|
||||
"wirelessClients",
|
||||
"wirelessClientLabel",
|
||||
"wirelessClientActive",
|
||||
"ethernetClients",
|
||||
"ethernetClientLabel",
|
||||
"ethernetClientActive",
|
||||
"totalClients",
|
||||
"totalClientsActive",
|
||||
"connectionType",
|
||||
"connectionIcon",
|
||||
"ethernetActive",
|
||||
"wirelessActive",
|
||||
"tetheringActive",
|
||||
"cellularActive",
|
||||
"status"
|
||||
"strLinkQuality",
|
||||
"wlan0up"
|
||||
)
|
||||
);
|
||||
$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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ if (!defined('RASPI_CONFIG')) {
|
||||
$defaults = [
|
||||
'RASPI_BRAND_TEXT' => 'RaspAP',
|
||||
'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel',
|
||||
'RASPI_VERSION' => '3.3.5',
|
||||
'RASPI_VERSION' => '3.2.6',
|
||||
'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json',
|
||||
'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json',
|
||||
'RASPI_CONFIG_API' => RASPI_CONFIG.'/api',
|
||||
|
||||
6
includes/exceptions.php
Executable file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
require_once 'src/RaspAP/Exceptions/ExceptionHandler.php';
|
||||
|
||||
$handler = new RaspAP\Exceptions\ExceptionHandler;
|
||||
?>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<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"><?php echo sprintf(_('Created by the <a href="%s" target="_blank" rel="noopener">%s</a>'), 'https://github.com/RaspAP', _('RaspAP Team')); ?></span>
|
||||
<span class="ps-2">Created by the <a href="https://github.com/RaspAP" target="_blank" rel="noopener">RaspAP Team</a></span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<i class="fas fa-heart heart"></i> <a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener"><?php echo _("Get Insiders"); ?></a>
|
||||
<i class="fas fa-heart heart"></i> <a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener">Get Insiders</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -177,17 +177,15 @@ 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 (!isset($obj['providers']) || !is_array($obj['providers'])) {
|
||||
if ($obj === null) {
|
||||
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 */
|
||||
@@ -306,6 +304,82 @@ 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 (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
|
||||
error_log('Session expired or CSRF token is missing.');
|
||||
header('Location: /login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$post_token = $_POST['csrf_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?
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -555,13 +629,6 @@ 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"));
|
||||
@@ -637,6 +704,7 @@ function formatDateAgo($datetime, $full = false)
|
||||
function initializeApp()
|
||||
{
|
||||
$_SESSION["theme_url"] = getThemeOpt();
|
||||
$_SESSION["toggleState"] = getSidebarState();
|
||||
$_SESSION["bridgedEnabled"] = getBridgedState();
|
||||
$_SESSION["providerID"] = getProviderID();
|
||||
}
|
||||
@@ -662,17 +730,22 @@ function getColorOpt()
|
||||
return $color;
|
||||
}
|
||||
|
||||
function getSidebarState()
|
||||
{
|
||||
if(isset($_COOKIE['sidebarToggled'])) {
|
||||
if ($_COOKIE['sidebarToggled'] == 'true' ) {
|
||||
return "toggled";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns bridged AP mode status
|
||||
function getBridgedState()
|
||||
{
|
||||
|
||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||
if (!file_exists($hostapdIni)) {
|
||||
return 0;
|
||||
} else {
|
||||
$arrHostapdConf = parse_ini_file($hostapdIni);
|
||||
}
|
||||
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
|
||||
// defaults to false
|
||||
return $arrHostapdConf['BridgedEnable'];
|
||||
}
|
||||
}
|
||||
|
||||
// Returns VPN provider ID, if defined
|
||||
function getProviderID()
|
||||
@@ -823,23 +896,6 @@ function loadFooterScripts($extraFooterScripts)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether the given network interface exists on the system.
|
||||
* This function retrieves all currently available network interfaces using the `ip link show` command
|
||||
* and checks if the provided interface name is in the list.
|
||||
*/
|
||||
function validateInterface($interface)
|
||||
{
|
||||
// Retrieve all available network interfaces
|
||||
$valid_interfaces = shell_exec('ip -o link show | awk -F": " \'{print $2}\'');
|
||||
|
||||
// Convert to array (one interface per line)
|
||||
$valid_interfaces = explode("\n", trim($valid_interfaces));
|
||||
|
||||
// Check if the provided interface exists in the list
|
||||
return in_array($interface, $valid_interfaces, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ISO standard 2-letter country codes
|
||||
*
|
||||
@@ -971,7 +1027,7 @@ function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cp
|
||||
<div class="col ml-2">
|
||||
<div class="ml-1 sb-status">Status</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle hostapd-led <?php echo ($hostapd_led); ?>"></i></span> <?php echo _("Hotspot").' '. _($hostapd_status); ?>
|
||||
<i class="fas fa-circle <?php echo ($hostapd_led); ?>"></i></span> <?php echo _("Hotspot").' '. _($hostapd_status); ?>
|
||||
</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($memused_led); ?>"></i></span> <?php echo _("Mem Use").': '. htmlspecialchars(strval($memused), ENT_QUOTES); ?>%
|
||||
|
||||
@@ -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/5 GHz',
|
||||
'n' => '802.11n - 2.4 GHz',
|
||||
'ac' => '802.11ac - 5 GHz'
|
||||
];
|
||||
$languageCode = strtok($_SESSION['locale'], '_');
|
||||
@@ -34,7 +34,7 @@ function DisplayHostAPDConfig()
|
||||
|
||||
$reg_domain = shell_exec("iw reg get | grep -o 'country [A-Z]\{2\}' | awk 'NR==1{print $2}'");
|
||||
|
||||
$cmd = "iw dev ".escapeshellarg($_SESSION['ap_interface'])." info | awk '$1==\"txpower\" {print $2}'";
|
||||
$cmd = "iw dev ".$_SESSION['ap_interface']." info | awk '$1==\"txpower\" {print $2}'";
|
||||
exec($cmd, $txpower);
|
||||
$txpower = intval($txpower[0]);
|
||||
|
||||
@@ -46,28 +46,17 @@ function DisplayHostAPDConfig()
|
||||
SaveHostAPDConfig($arrSecurity, $arrEncType, $arr80211Standard, $interfaces, $reg_domain, $status);
|
||||
}
|
||||
}
|
||||
|
||||
$arrHostapdConf = [];
|
||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||
if (file_exists($hostapdIni)) {
|
||||
$arrHostapdConf = parse_ini_file($hostapdIni);
|
||||
}
|
||||
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
|
||||
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
if (isset($_POST['StartHotspot']) || isset($_POST['RestartHotspot'])) {
|
||||
$status->addMessage('Attempting to start hotspot', 'info');
|
||||
if ($arrHostapdConf['BridgedEnable'] == 1) {
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface br0 --seconds 1', $return);
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface br0 --seconds 2', $return);
|
||||
} elseif ($arrHostapdConf['WifiAPEnable'] == 1) {
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface uap0 --seconds 1', $return);
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface uap0 --seconds 2', $return);
|
||||
} else {
|
||||
// systemctl expects a unit name like raspap-network-activity@wlan0.service, no extra quotes
|
||||
$iface_nonescaped = $_POST['interface'];
|
||||
if (preg_match('/^[a-zA-Z0-9_-]+$/', $iface_nonescaped)) { // validate interface name
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface ' .$iface_nonescaped. ' --seconds 1', $return);
|
||||
} else {
|
||||
throw new \Exception('Invalid network interface');
|
||||
}
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --seconds 2', $return);
|
||||
}
|
||||
foreach ($return as $line) {
|
||||
$status->addMessage($line, 'info');
|
||||
@@ -75,7 +64,6 @@ function DisplayHostAPDConfig()
|
||||
} elseif (isset($_POST['StopHotspot'])) {
|
||||
$status->addMessage('Attempting to stop hotspot', 'info');
|
||||
exec('sudo /bin/systemctl stop hostapd.service', $return);
|
||||
exec('sudo systemctl stop "raspap-network-activity@*.service"');
|
||||
foreach ($return as $line) {
|
||||
$status->addMessage($line, 'info');
|
||||
}
|
||||
@@ -83,7 +71,7 @@ function DisplayHostAPDConfig()
|
||||
}
|
||||
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
|
||||
if (isset($_SESSION['wifi_client_interface'])) {
|
||||
exec('iwgetid '.escapeshellarg($_SESSION['wifi_client_interface']). ' -r', $wifiNetworkID);
|
||||
exec('iwgetid '.$_SESSION['wifi_client_interface']. ' -r', $wifiNetworkID);
|
||||
if (!empty($wifiNetworkID[0])) {
|
||||
$managedModeEnabled = true;
|
||||
}
|
||||
@@ -148,9 +136,6 @@ 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);
|
||||
|
||||
@@ -256,18 +241,17 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/disablelog.sh');
|
||||
}
|
||||
}
|
||||
|
||||
// set AP interface default, override for ap-sta & bridged options
|
||||
$iface = validateInterface($_POST['interface']) ? $_POST['interface'] : RASPI_WIFI_AP_INTERFACE;
|
||||
|
||||
$ap_iface = $iface; // the hostap AP interface
|
||||
$cli_iface = $iface; // the wifi client interface
|
||||
$session_iface = $iface; // the interface that the UI needs to monitor for data usage etc.
|
||||
$ap_iface = $_POST['interface']; // the hostap AP interface
|
||||
$cli_iface = $_POST['interface']; // the wifi client interface
|
||||
$session_iface = $_POST['interface']; // the interface that the UI needs to monitor for data usage etc.
|
||||
if ($wifiAPEnable) { // for AP-STA we monitor the uap0 interface, which is always the ap interface.
|
||||
$ap_iface = $session_iface = 'uap0';
|
||||
$ap_iface = 'uap0';
|
||||
$session_iface = 'uap0';
|
||||
}
|
||||
if ($bridgedEnable) { // for bridged mode we monitor the bridge, but keep the selected interface as AP.
|
||||
$cli_iface = $session_iface = 'br0';
|
||||
$session_iface = 'br0';
|
||||
$cli_iface = 'br0';
|
||||
}
|
||||
|
||||
// persist user options to /etc/raspap
|
||||
@@ -297,13 +281,18 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
|
||||
$good_input = false;
|
||||
}
|
||||
|
||||
$ignore_broadcast_ssid = $_POST['hiddenSSID'] ?? '0';
|
||||
if (!ctype_digit($ignore_broadcast_ssid)) {
|
||||
$status->addMessage('Parameter hiddenSSID not a number.', 'danger');
|
||||
$good_input = false;
|
||||
} elseif ((int)$ignore_broadcast_ssid < 0 || (int)$ignore_broadcast_ssid >= 3) {
|
||||
$status->addMessage('Parameter hiddenSSID contains an invalid configuration value.', 'danger');
|
||||
$good_input = false;
|
||||
if (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';
|
||||
}
|
||||
|
||||
if (! in_array($_POST['interface'], $interfaces)) {
|
||||
@@ -375,17 +364,14 @@ 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 = 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'];
|
||||
$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'];
|
||||
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';
|
||||
@@ -399,11 +385,15 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
|
||||
$config[] = 'static ip_address='.$ip_address;
|
||||
$config[] = 'nohook wpa_supplicant';
|
||||
$config[] = PHP_EOL;
|
||||
|
||||
} else {
|
||||
$config = updateDhcpcdConfig($ap_iface, $jsonData, $ip_address, $routers, $domain_name_server);
|
||||
$def_ip = array();
|
||||
$config = [ '# RaspAP '.$ap_iface.' configuration' ];
|
||||
$config[] = 'interface '.$ap_iface;
|
||||
$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']; }
|
||||
}
|
||||
|
||||
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
||||
|
||||
$skip_dhcp = false;
|
||||
@@ -528,70 +518,6 @@ function updateHostapdConfig($ignore_broadcast_ssid,$wifiAPEnable,$bridgedEnable
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dhcpcd configuration for a given interface, preserving existing settings
|
||||
*
|
||||
* @param string $ap_iface
|
||||
* @param array $jsonData
|
||||
* @param string $ip_address
|
||||
* @param string $routers
|
||||
* @param string $domain_name_server
|
||||
* @return array updated configuration
|
||||
*/
|
||||
function updateDhcpcdConfig($ap_iface, $jsonData, $ip_address, $routers, $domain_name_server) {
|
||||
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);
|
||||
$existing_config = [];
|
||||
$section_regex = '/^#\sRaspAP\s'.preg_quote($ap_iface, '/').'\s.*?(?=\s*^\s*$)/ms';
|
||||
|
||||
// extract existing interface configuration
|
||||
if (preg_match($section_regex, $dhcp_cfg, $matches)) {
|
||||
$lines = explode(PHP_EOL, $matches[0]);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (preg_match('/^(interface|static|metric|nogateway|nohook)/', $line)) {
|
||||
$existing_config[] = $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize with comment
|
||||
$config = [ '# RaspAP '.$ap_iface.' configuration' ];
|
||||
$config[] = 'interface '.$ap_iface;
|
||||
$static_settings = [
|
||||
'static ip_address' => $ip_address,
|
||||
'static routers' => $routers,
|
||||
'static domain_name_server' => $domain_name_server
|
||||
];
|
||||
|
||||
// merge existing settings with updates
|
||||
foreach ($existing_config as $line) {
|
||||
$matched = false;
|
||||
foreach ($static_settings as $key => $value) {
|
||||
if (strpos($line, $key) === 0) {
|
||||
$config[] = "$key=$value";
|
||||
$matched = true;
|
||||
unset($static_settings[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$matched && !preg_match('/^interface/', $line)) {
|
||||
$config[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
// add any new static settings
|
||||
foreach ($static_settings as $key => $value) {
|
||||
$config[] = "$key=$value";
|
||||
}
|
||||
|
||||
// add metric if provided
|
||||
if (!empty($jsonData['Metric']) && !in_array('metric '.$jsonData['Metric'], $config)) {
|
||||
$config[] = 'metric '.$jsonData['Metric'];
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes iw to set the specified ISO 2-letter country code
|
||||
*
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*
|
||||
* Refer to: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
||||
*/
|
||||
if (empty($_SESSION['locale']) && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2) {
|
||||
if (empty($_SESSION['locale']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2) {
|
||||
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
|
||||
switch ($lang) {
|
||||
case "de":
|
||||
@@ -90,10 +90,9 @@ if (empty($_SESSION['locale']) && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) && st
|
||||
// Use: 'sudo raspi-configure' and select 'Localisation Options'
|
||||
|
||||
// activate the locale setting
|
||||
if (!empty($_SESSION['locale'])) {
|
||||
putenv("LANG=" . $_SESSION['locale']);
|
||||
setlocale(LC_ALL, $_SESSION['locale']);
|
||||
}
|
||||
putenv("LANG=" . $_SESSION['locale']);
|
||||
setlocale(LC_ALL, $_SESSION['locale']);
|
||||
|
||||
bindtextdomain(LOCALE_DOMAIN, LOCALE_ROOT);
|
||||
bind_textdomain_codeset(LOCALE_DOMAIN, 'UTF-8');
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -50,6 +50,6 @@ if ($hostapd[0] ==1) {
|
||||
$hostapd_led = "service-status-up";
|
||||
} else {
|
||||
$hostapd_status = "down";
|
||||
$hostapd_led = "service-status-warn";
|
||||
$hostapd_led = "service-status-down";
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ 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'])) {
|
||||
@@ -87,7 +86,6 @@ function DisplaySystem(&$extraFooterScripts)
|
||||
$kernel = $system->kernelVersion();
|
||||
$systime = $system->systime();
|
||||
$revision = $system->rpiRevision();
|
||||
$deviceImage = $dashboard->getDeviceImage($revision);
|
||||
|
||||
// memory use
|
||||
$memused = $system->usedMemory();
|
||||
@@ -131,7 +129,6 @@ function DisplaySystem(&$extraFooterScripts)
|
||||
"uptime",
|
||||
"systime",
|
||||
"revision",
|
||||
"deviceImage",
|
||||
"cores",
|
||||
"os",
|
||||
"kernel",
|
||||
|
||||
@@ -160,24 +160,19 @@ function sortNetworksByRSSI(&$networks)
|
||||
*/
|
||||
function getWifiInterface()
|
||||
{
|
||||
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
|
||||
$arrHostapdConf = file_exists($hostapdIni) ? parse_ini_file($hostapdIni) : [];
|
||||
$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]));
|
||||
|
||||
$iface = $_SESSION['ap_interface'] = $arrHostapdConf['WifiInterface'] ?? RASPI_WIFI_AP_INTERFACE;
|
||||
|
||||
if (!validateInterface($iface)) {
|
||||
$iface = RASPI_WIFI_AP_INTERFACE;
|
||||
}
|
||||
|
||||
// check for 2nd wifi interface -> wifi client on different interface
|
||||
exec("iw dev | awk '$1==\"Interface\" && $2!=\"$iface\" {print $2}'", $iface2);
|
||||
$client_iface = $_SESSION['wifi_client_interface'] = empty($iface2) ? $iface : trim($iface2[0]);
|
||||
|
||||
// handle special case for RPi Zero W in AP-STA mode
|
||||
if ($client_iface === "uap0" && ($arrHostapdConf['WifiAPEnable'] ?? 0)) {
|
||||
$_SESSION['wifi_client_interface'] = $iface;
|
||||
$_SESSION['ap_interface'] = $client_iface;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -10,15 +10,14 @@ function DisplayWireGuardConfig()
|
||||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
$parseFlag = true;
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
$optRules = isset($_POST['wgRules']) ? $_POST['wgRules'] : null;
|
||||
$optInterface = isset($_POST['wgInterface']) ? $_POST['wgInterface'] : null;
|
||||
$optConf = isset($_POST['wgCnfOpt']) ? $_POST['wgCnfOpt'] : null;
|
||||
$optSrvEnable = isset($_POST['wgSrvEnable']) ? $_POST['wgSrvEnable'] : null;
|
||||
$optLogEnable = isset($_POST['wgLogEnable']) ? $_POST['wgLogEnable'] : null;
|
||||
$optRules = $_POST['wgRules'];
|
||||
$optConf = $_POST['wgCnfOpt'];
|
||||
$optSrvEnable = $_POST['wgSrvEnable'];
|
||||
$optLogEnable = $_POST['wgLogEnable'];
|
||||
if (isset($_POST['savewgsettings']) && $optConf == 'manual' && $optSrvEnable == 1 ) {
|
||||
SaveWireGuardConfig($status);
|
||||
} elseif (isset($_POST['savewgsettings']) && $optConf == 'upload' && is_uploaded_file($_FILES["wgFile"]["tmp_name"])) {
|
||||
SaveWireGuardUpload($status, $_FILES['wgFile'], $optRules, $optInterface);
|
||||
SaveWireGuardUpload($status, $_FILES['wgFile'], $optRules);
|
||||
} elseif (isset($_POST['savewgsettings']) && isset($_POST['wg_penabled']) ) {
|
||||
SaveWireGuardConfig($status);
|
||||
} elseif (isset($_POST['startwg'])) {
|
||||
@@ -70,25 +69,12 @@ 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";
|
||||
|
||||
// fetch available interfaces
|
||||
exec("ip -o link show | awk -F': ' '{print $2}'", $interfaces);
|
||||
sort($interfaces);
|
||||
|
||||
echo renderTemplate(
|
||||
"wireguard", compact(
|
||||
"status",
|
||||
"wg_state",
|
||||
"serviceStatus",
|
||||
"public_ip",
|
||||
"interfaces",
|
||||
"optRules",
|
||||
"optLogEnable",
|
||||
"peer_id",
|
||||
@@ -103,8 +89,7 @@ function DisplayWireGuardConfig()
|
||||
"wg_peerpubkey",
|
||||
"wg_pendpoint",
|
||||
"wg_pallowedips",
|
||||
"wg_pkeepalive",
|
||||
"wg_log"
|
||||
"wg_pkeepalive"
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -116,10 +101,9 @@ function DisplayWireGuardConfig()
|
||||
* @param object $status
|
||||
* @param object $file
|
||||
* @param boolean $optRules
|
||||
* @param string $optInterface
|
||||
* @return object $status
|
||||
*/
|
||||
function SaveWireGuardUpload($status, $file, $optRules, $optInterface)
|
||||
function SaveWireGuardUpload($status, $file, $optRules)
|
||||
{
|
||||
define('KB', 1024);
|
||||
$tmp_destdir = '/tmp/';
|
||||
@@ -154,7 +138,7 @@ function SaveWireGuardUpload($status, $file, $optRules, $optInterface)
|
||||
$rules[] = 'PostDown = '.getDefaultNetValue('wireguard','server','PostDown');
|
||||
$rules[] = '';
|
||||
$rules = join(PHP_EOL, $rules);
|
||||
$rules = preg_replace('/wlan0/m', $optInterface, $rules);
|
||||
$rules = preg_replace('/wlan0/m', $_SESSION['ap_interface'], $rules);
|
||||
$tmp_contents = preg_replace('/^\s*$/ms', $rules, $tmp_contents, 1);
|
||||
file_put_contents($tmp_wgconfig, $tmp_contents);
|
||||
}
|
||||
|
||||
29
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.28 installed via apt)
|
||||
* php-cgi (version 8.2.26 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.3.5
|
||||
* @version 3.2.6
|
||||
* @link https://github.com/RaspAP/raspap-webgui/
|
||||
* @link https://raspap.com/
|
||||
* @see http://sirlagz.net/2013/02/08/raspap-webgui/
|
||||
@@ -23,12 +23,13 @@
|
||||
* as you leave these references intact in the header comments of your source files.
|
||||
*/
|
||||
|
||||
require 'includes/session.php';
|
||||
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';
|
||||
@@ -58,7 +59,7 @@ initializeApp();
|
||||
<html lang="en" <?php setTheme();?>>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<?php echo \RaspAP\Tokens\CSRF::metaTag(); ?>
|
||||
<?php echo CSRFMetaTag() ?>
|
||||
<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="">
|
||||
@@ -83,11 +84,15 @@ initializeApp();
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link href="<?php echo $_SESSION["theme_url"]; ?>" title="main" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" href="/app/icons/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/app/icons/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/app/icons/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/app/icons/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="RaspAP" />
|
||||
<link rel="shortcut icon" type="image/png" href="app/icons/favicon.png?ver=2.0">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="app/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="app/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="app/icons/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" href="app/icons/favicon.png" />
|
||||
<link rel="manifest" href="app/icons/site.webmanifest">
|
||||
<link rel="mask-icon" href="app/icons/safari-pinned-tab.svg" color="#b91d47">
|
||||
<meta name="msapplication-config" content="app/icons/browserconfig.xml">
|
||||
<meta name="msapplication-TileColor" content="#b91d47">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ function _update_raspap() {
|
||||
_download_latest_files
|
||||
_change_file_ownership
|
||||
_patch_system_files
|
||||
_enable_network_activity_monitor
|
||||
_create_plugin_scripts
|
||||
_install_complete
|
||||
}
|
||||
@@ -319,7 +318,7 @@ function _create_hostapd_scripts() {
|
||||
# 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'"
|
||||
sudo mkdir $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"
|
||||
@@ -601,7 +600,6 @@ function _download_latest_files() {
|
||||
if [ -n "$username" ] && [ -n "$acctoken" ]; then
|
||||
insiders_source_url="https://${username}:${acctoken}@github.com/$repo"
|
||||
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"
|
||||
@@ -609,7 +607,6 @@ function _download_latest_files() {
|
||||
fi
|
||||
if [ -z "$insiders_source_url" ]; then
|
||||
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"
|
||||
@@ -819,38 +816,9 @@ function _configure_networking() {
|
||||
echo -e
|
||||
_enable_raspap_daemon
|
||||
fi
|
||||
|
||||
# Enable RaspAP network activity monitor
|
||||
_enable_network_activity_monitor
|
||||
|
||||
_install_status 0
|
||||
}
|
||||
|
||||
# Install and enable RaspAP network activity monitor
|
||||
function _enable_network_activity_monitor() {
|
||||
_install_log "Enabling RaspAP network activity monitor"
|
||||
echo "Compiling raspap-network-monitor.c to /usr/local/bin/"
|
||||
if ! command -v gcc >/dev/null 2>&1; then
|
||||
echo "gcc not found, installing..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential || _install_status 1 "Failed to install build tools"
|
||||
fi
|
||||
sudo gcc -O2 -o /usr/local/bin/raspap-network-monitor $webroot_dir/installers/raspap-network-monitor.c || _install_status 1 "Failed to compile raspap-network-monitor.c"
|
||||
echo "Copying raspap-network-activity@.service to /lib/systemd/system/"
|
||||
sudo cp $webroot_dir/installers/raspap-network-activity@.service /lib/systemd/system/ || _install_status 1 "Unable to move raspap-network-activity.service file"
|
||||
sudo systemctl daemon-reload
|
||||
echo "Enabling raspap-network-activity@wlan0.service"
|
||||
sudo systemctl enable raspap-network-activity@wlan0.service || _install_status 1 "Failed to enable raspap-network-activity.service"
|
||||
echo "Starting raspap-network-activity@wlan0.service"
|
||||
sudo systemctl start raspap-network-activity@wlan0.service || _install_status 1 "Failed to start raspap-network-activity.service"
|
||||
sleep 0.5
|
||||
echo "Symlinking /dev/shm/net_activity to $webroot_dir/app/net_activity"
|
||||
sudo ln -sf /dev/shm/net_activity $webroot_dir/app/net_activity || _install_status 1 "Failed to link net_activity to ${webroot_dir}/app"
|
||||
echo "Setting ownership for ${raspap_user} on ${webroot_dir}/app/net_activity"
|
||||
sudo chown -R $raspap_user:$raspap_user $webroot_dir/app/net_activity || _install_status 1 "Unable to set ownership of ${webroot_dir}/app/net_activity"
|
||||
echo "Network activity monitor enabled"
|
||||
}
|
||||
|
||||
# Prompt to configure TCP BBR option
|
||||
function _prompt_configure_tcp_bbr() {
|
||||
_install_log "Configure TCP BBR congestion control"
|
||||
|
||||
@@ -46,7 +46,7 @@ case "$action" in
|
||||
|
||||
username=$1
|
||||
password=$2
|
||||
|
||||
|
||||
if id "$username" &>/dev/null; then # user already exists
|
||||
echo "OK"
|
||||
exit 0
|
||||
@@ -73,52 +73,10 @@ case "$action" in
|
||||
|
||||
mkdir -p "$(dirname "$destination")"
|
||||
cp "$source" "$destination"
|
||||
chown -R $raspap_user:$raspap_user "$destination"
|
||||
|
||||
echo "OK"
|
||||
;;
|
||||
|
||||
"permissions")
|
||||
[ $# -lt 4 ] && { echo "Usage: $0 permissions <filepath> <user> <group> <mode>"; exit 1; }
|
||||
|
||||
filepath="$1"
|
||||
user="$2"
|
||||
group="$3"
|
||||
mode="$4"
|
||||
|
||||
if [ ! -e "$filepath" ]; then
|
||||
echo "File not found: $filepath" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chown "$user:$group" "$filepath" || exit 1
|
||||
chmod "$mode" "$filepath" || exit 1
|
||||
|
||||
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; }
|
||||
|
||||
@@ -131,7 +89,7 @@ case "$action" in
|
||||
fi
|
||||
|
||||
plugin_dir=$(dirname "$destination")
|
||||
if [ ! -d "$plugin_dir" ]; then
|
||||
if [ ! -d "$lugin_dir" ]; then
|
||||
mkdir -p "$plugin_dir"
|
||||
fi
|
||||
|
||||
@@ -141,50 +99,15 @@ case "$action" in
|
||||
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"
|
||||
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 " plugin <source <destination> Copies a plugin directory"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Author: BillZ <billzimmerman@gmail.com>
|
||||
|
||||
[Unit]
|
||||
Description=RaspAP Network Activity Monitor for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/raspap-network-monitor %i
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
// raspap-network-monitor.c
|
||||
|
||||
/*
|
||||
RaspAP Network Activity Monitor
|
||||
Author: @billz <billzimmerman@gmail.com>
|
||||
Author URI: https://github.com/billz/
|
||||
License: GNU General Public License v3.0
|
||||
License URI: https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||
|
||||
Usage: raspap-network-monitor [interface]
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TMPFILE "/dev/shm/net_activity"
|
||||
#define POLL_INTERVAL_MS 100 // 100 milliseconds
|
||||
|
||||
unsigned long read_interface_bytes(const char *iface) {
|
||||
FILE *fp = fopen("/proc/net/dev", "r");
|
||||
if (!fp) return 0;
|
||||
|
||||
char line[512];
|
||||
unsigned long rx = 0, tx = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (strstr(line, iface)) {
|
||||
char *ptr = strchr(line, ':');
|
||||
if (ptr) {
|
||||
sscanf(ptr + 1, "%lu %*u %*u %*u %*u %*u %*u %*u %lu", &rx, &tx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return rx + tx;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <interface>\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char *iface = argv[1];
|
||||
unsigned long prev_total = read_interface_bytes(iface);
|
||||
|
||||
// create a timerfd
|
||||
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
|
||||
if (tfd == -1) {
|
||||
perror("timerfd_create");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct itimerspec timer;
|
||||
timer.it_interval.tv_sec = 0;
|
||||
timer.it_interval.tv_nsec = POLL_INTERVAL_MS * 1000000; // interval
|
||||
timer.it_value.tv_sec = 0;
|
||||
timer.it_value.tv_nsec = POLL_INTERVAL_MS * 1000000; // initial expiration
|
||||
|
||||
if (timerfd_settime(tfd, 0, &timer, NULL) == -1) {
|
||||
perror("timerfd_settime");
|
||||
close(tfd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct pollfd fds;
|
||||
fds.fd = tfd;
|
||||
fds.events = POLLIN;
|
||||
|
||||
for (;;) {
|
||||
int ret = poll(&fds, 1, -1);
|
||||
if (ret == -1) {
|
||||
perror("poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds.revents & POLLIN) {
|
||||
uint64_t expirations;
|
||||
read(tfd, &expirations, sizeof(expirations)); // clear timer
|
||||
|
||||
unsigned long curr_total = read_interface_bytes(iface);
|
||||
unsigned long diff = (curr_total >= prev_total) ? (curr_total - prev_total) : 0;
|
||||
prev_total = curr_total;
|
||||
|
||||
FILE *out = fopen(TMPFILE, "w");
|
||||
if (out) {
|
||||
fprintf(out, "%lu\n", diff);
|
||||
fclose(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(tfd);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -80,5 +80,3 @@ www-data ALL=(ALL) NOPASSWD:/bin/truncate -s 0 /tmp/*.log,/bin/truncate -s 0 /va
|
||||
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
|
||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl start raspap-network-activity@*.service
|
||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl stop raspap-network-activity@*.service
|
||||
|
||||
@@ -16,7 +16,7 @@ After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash /etc/raspap/hostapd/servicestart.sh --seconds 1
|
||||
ExecStart=/bin/bash /etc/raspap/hostapd/servicestart.sh --interface uap0 --seconds 3
|
||||
RemainAfterExit=no
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -44,7 +44,7 @@ OPTIONS:
|
||||
-a, --adblock <flag> Used with -y, --yes, sets Adblock install option (0=no install)
|
||||
-w, --wireguard <flag> Used with -y, --yes, sets WireGuard install option (0=no install)
|
||||
-e, --provider <value> Used with -y, --yes, sets the VPN provider install option
|
||||
-g, --tcp-bbr <value> Used with -y, --yes, sets the TCP BBR congestion control algorithm option
|
||||
-g, --tcp-bbr <value> Used with -y, --yes, sets the TCP BBR congestion control algorithm option
|
||||
-r, --repo, --repository <name> Overrides the default GitHub repo (RaspAP/raspap-webgui)
|
||||
-b, --branch <name> Overrides the default git branch (latest release)
|
||||
-t, --token <accesstoken> Specify a GitHub token to access a private repository
|
||||
@@ -137,7 +137,7 @@ function _parse_params() {
|
||||
pv_option="$2"
|
||||
shift
|
||||
;;
|
||||
-g|--tcp-bbr)
|
||||
-g|--tcp-bbr)
|
||||
bbr_option="$2"
|
||||
shift
|
||||
;;
|
||||
@@ -205,9 +205,9 @@ function _parse_params() {
|
||||
|
||||
function _setup_colors() {
|
||||
ANSI_RED="\033[0;31m"
|
||||
ANSI_AQUA="\033[38;5;44m"
|
||||
ANSI_GREEN="\033[0;32m"
|
||||
ANSI_YELLOW="\033[0;33m"
|
||||
ANSI_LT_AQUA="\033[38;5;30m"
|
||||
ANSI_RASPBERRY="\033[0;35m"
|
||||
ANSI_ERROR="\033[1;37;41m"
|
||||
ANSI_RESET="\033[m"
|
||||
}
|
||||
@@ -229,7 +229,7 @@ function _version() {
|
||||
|
||||
# Outputs a welcome message
|
||||
function _display_welcome() {
|
||||
echo -e "${ANSI_AQUA}\n"
|
||||
echo -e "${ANSI_RASPBERRY}\n"
|
||||
echo -e " 888888ba .d888888 888888ba"
|
||||
echo -e " 88 8b d8 88 88 8b"
|
||||
echo -e "a88aaaa8P' .d8888b. .d8888b. 88d888b. 88aaaaa88a a88aaaa8P"
|
||||
@@ -238,7 +238,7 @@ function _display_welcome() {
|
||||
echo -e " dP dP 88888P8 88888P 88Y888P 88 88 dP"
|
||||
echo -e " 88"
|
||||
echo -e " dP version ${RASPAP_RELEASE}"
|
||||
echo -e "${ANSI_LT_AQUA}"
|
||||
echo -e "${ANSI_GREEN}"
|
||||
echo -e "The Quick Installer will guide you through a few easy steps${ANSI_RESET}\n\n"
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ function _get_release() {
|
||||
|
||||
# Outputs a RaspAP Install log line
|
||||
function _install_log() {
|
||||
echo -e "${ANSI_LT_AQUA}RaspAP ${component}: $1${ANSI_RESET}"
|
||||
echo -e "${ANSI_GREEN}RaspAP ${component}: $1${ANSI_RESET}"
|
||||
}
|
||||
|
||||
# Outputs a RaspAP divider
|
||||
@@ -283,7 +283,7 @@ function _install_divider() {
|
||||
function _install_status() {
|
||||
case $1 in
|
||||
0)
|
||||
echo -e "[$ANSI_LT_AQUA \U2713 ok $ANSI_RESET] $2"
|
||||
echo -e "[$ANSI_GREEN \U2713 ok $ANSI_RESET] $2"
|
||||
;;
|
||||
1)
|
||||
echo -e "[$ANSI_RED \U2718 error $ANSI_RESET] $ANSI_ERROR $2 $ANSI_RESET"
|
||||
@@ -292,7 +292,7 @@ function _install_status() {
|
||||
echo -e "[$ANSI_YELLOW \U26A0 warning $ANSI_RESET] $2"
|
||||
;;
|
||||
3)
|
||||
echo -e "[$ANSI_AQUA ! important $ANSI_RESET] $2"
|
||||
echo -e "[$ANSI_RASPBERRY ! important $ANSI_RESET] $2"
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
@@ -34,25 +34,6 @@ esac
|
||||
done
|
||||
set -- "${positional[@]}"
|
||||
|
||||
# Load config file into associative array
|
||||
declare -A config
|
||||
if [ -r "$CONFIGFILE" ]; then
|
||||
while IFS=" = " read -r key value; do
|
||||
config["$key"]="$value"
|
||||
done < "$CONFIGFILE"
|
||||
fi
|
||||
|
||||
# Set interface from config if not set by parameter
|
||||
if [ -z "$interface" ]; then
|
||||
if [ -n "${config[WifiInterface]}" ]; then
|
||||
interface="${config[WifiInterface]}"
|
||||
echo "Interface not provided. Using interface from config: $interface"
|
||||
else
|
||||
interface="wlan0"
|
||||
echo "Interface not provided and not found in config. Defaulting to: $interface"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Stopping network services..."
|
||||
if [ $OPENVPNENABLED -eq 1 ]; then
|
||||
systemctl stop openvpn-client@client
|
||||
@@ -61,58 +42,64 @@ systemctl stop systemd-networkd
|
||||
systemctl stop hostapd.service
|
||||
systemctl stop dnsmasq.service
|
||||
systemctl stop dhcpcd.service
|
||||
systemctl stop 'raspap-network-activity@*.service'
|
||||
|
||||
if [ "${action}" = "stop" ]; then
|
||||
echo "Services stopped. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -f "$DAEMONPATH" ] && [ -n "$interface" ]; then
|
||||
if [ -f "$DAEMONPATH" ] && [ ! -z "$interface" ]; then
|
||||
echo "Changing RaspAP Daemon --interface to $interface"
|
||||
sed -i "s/\(--interface \)[[:alnum:]]*/\1$interface/" "$DAEMONPATH"
|
||||
fi
|
||||
|
||||
if [ "${config[BridgedEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "br0" ]; then
|
||||
echo "Stopping systemd-networkd"
|
||||
systemctl stop systemd-networkd
|
||||
if [ -r "$CONFIGFILE" ]; then
|
||||
declare -A config
|
||||
while IFS=" = " read -r key value; do
|
||||
config["$key"]="$value"
|
||||
done < "$CONFIGFILE"
|
||||
|
||||
echo "Restarting eth0 interface..."
|
||||
ip link set down eth0
|
||||
ip link set up eth0
|
||||
if [ "${config[BridgedEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "br0" ]; then
|
||||
echo "Stopping systemd-networkd"
|
||||
systemctl stop systemd-networkd
|
||||
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
echo "Restarting eth0 interface..."
|
||||
ip link set down eth0
|
||||
ip link set up eth0
|
||||
|
||||
echo "Enabling systemd-networkd"
|
||||
systemctl start systemd-networkd
|
||||
systemctl enable systemd-networkd
|
||||
fi
|
||||
else
|
||||
echo "Disabling systemd-networkd"
|
||||
systemctl disable systemd-networkd
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
|
||||
ip link ls up | grep -q 'br0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing br0 interface..."
|
||||
ip link set down br0
|
||||
ip link del dev br0
|
||||
fi
|
||||
echo "Enabling systemd-networkd"
|
||||
systemctl start systemd-networkd
|
||||
systemctl enable systemd-networkd
|
||||
fi
|
||||
else
|
||||
echo "Disabling systemd-networkd"
|
||||
systemctl disable systemd-networkd
|
||||
|
||||
if [ "${config[WifiAPEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "uap0" ]; then
|
||||
ip link ls up | grep -q 'br0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing br0 interface..."
|
||||
ip link set down br0
|
||||
ip link del dev br0
|
||||
fi
|
||||
|
||||
ip link ls up | grep -q 'uap0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
if [ "${config[WifiAPEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "uap0" ]; then
|
||||
|
||||
ip link ls up | grep -q 'uap0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
fi
|
||||
|
||||
echo "Adding uap0 interface to ${config[WifiManaged]}"
|
||||
iw dev ${config[WifiManaged]} interface add uap0 type __ap
|
||||
# Bring up uap0 interface
|
||||
ifconfig uap0 up
|
||||
fi
|
||||
|
||||
echo "Adding uap0 interface to ${config[WifiManaged]}"
|
||||
iw dev ${config[WifiManaged]} interface add uap0 type __ap
|
||||
# Bring up uap0 interface
|
||||
ifconfig uap0 up
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -127,13 +114,12 @@ sleep "${seconds}"
|
||||
|
||||
systemctl start dnsmasq.service
|
||||
|
||||
echo "Starting raspap-network-activity@${interface}.service"
|
||||
systemctl start raspap-network-activity@${interface}.service
|
||||
|
||||
if [ $OPENVPNENABLED -eq 1 ]; then
|
||||
systemctl start openvpn-client@client
|
||||
fi
|
||||
|
||||
# @mp035 found that the wifi client interface would stop every 8 seconds
|
||||
# for about 16 seconds. Reassociating seems to solve this
|
||||
if [ "${config[WifiAPEnable]}" = 1 ]; then
|
||||
echo "Reassociating wifi client interface..."
|
||||
sleep "${seconds}"
|
||||
|
||||
@@ -10,7 +10,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1.2.1\n"
|
||||
"Report-Msgid-Bugs-To: Bill Zimmerman <billzimmerman@gmail.com>\n"
|
||||
"POT-Creation-Date: 2017-10-19 08:56+0000\n"
|
||||
"PO-Revision-Date: 2022-01-05 20:35+0000\n"
|
||||
"PO-Revision-Date: 2021-08-04 00:35+0000\n"
|
||||
"Last-Translator: Bill Zimmerman <billzimmerman@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: en_US\n"
|
||||
@@ -25,8 +25,8 @@ msgstr "RaspAP Wifi Configuration Portal"
|
||||
msgid "Toggle navigation"
|
||||
msgstr "Toggle navigation"
|
||||
|
||||
msgid "RaspAP Admin Panel"
|
||||
msgstr "RaspAP Admin Panel"
|
||||
msgid "RaspAP Wifi Portal"
|
||||
msgstr "RaspAP Wifi Portal"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr "Dashboard"
|
||||
@@ -101,9 +101,6 @@ msgstr "Repeat new password"
|
||||
msgid "Please provide a valid username."
|
||||
msgstr "Please provide a valid username."
|
||||
|
||||
msgid "Please provide a valid password."
|
||||
msgstr "Please provide a valid password."
|
||||
|
||||
msgid "Please enter your old password."
|
||||
msgstr "Please enter your old password."
|
||||
|
||||
@@ -113,54 +110,6 @@ msgstr "Please enter a new password."
|
||||
msgid "Please re-enter your new password."
|
||||
msgstr "Please re-enter your new password."
|
||||
|
||||
msgid "Avatar"
|
||||
msgstr "Avatar"
|
||||
|
||||
msgid "Click or tap to upload a new user avatar."
|
||||
msgstr "Click or tap to upload a new user avatar."
|
||||
|
||||
msgid "Image files of type <code>JPG, GIF or PNG</code> are accepted. Max file size: 2 MB."
|
||||
msgstr "Image files of type <code>JPG, GIF or PNG</code> are accepted. Max file size: 2 MB."
|
||||
|
||||
msgid "Reset avatar"
|
||||
msgstr "Reset avatar"
|
||||
|
||||
msgid "Enable limited privilege user"
|
||||
msgstr "Enable limited privilege user"
|
||||
|
||||
msgid "This option enables a non-admin user who can access RaspAP's management interface, but has <strong>limited ability to modify the existing configuration</strong>. This user becomes active when the current admin user is logged-out."
|
||||
msgstr "This option enables a non-admin user who can access RaspAP's management interface, but has <strong>limited ability to modify the existing configuration</strong>. This user becomes active when the current admin user is logged-out."
|
||||
|
||||
msgid "Admin login failed. Please try again."
|
||||
msgstr "Admin login failed. Please try again."
|
||||
|
||||
msgid "Limited privilege user mode enabled"
|
||||
msgstr "Limited privilege user mode enabled"
|
||||
|
||||
msgid "Failed to enable limited privilege user mode"
|
||||
msgstr "Failed to enable limited privilege user mode"
|
||||
|
||||
msgid "Logout and enable limited user mode"
|
||||
msgstr "Logout and enable limited user mode"
|
||||
|
||||
msgid "Limited user login"
|
||||
msgstr "Limited user login"
|
||||
|
||||
msgid "Limited user password"
|
||||
msgstr "Limited user password"
|
||||
|
||||
msgid "This action will save the limited user's credentials and logout the current admin user. Save and enable limited privilege mode?"
|
||||
msgstr "This action will save the limited user's credentials and logout the current admin user. Save and enable limited privilege mode?"
|
||||
|
||||
msgid "Save and logout"
|
||||
msgstr "Save and logout"
|
||||
|
||||
msgid "Admin credentials updated successfully"
|
||||
msgstr "Admin credentials updated successfully"
|
||||
|
||||
msgid "Limited user credentials updated successfully"
|
||||
msgstr "Limited user credentials updated successfully"
|
||||
|
||||
#: includes/configure_client.php
|
||||
msgid "Client settings"
|
||||
msgstr "Client settings"
|
||||
@@ -295,8 +244,8 @@ msgstr "Frequency"
|
||||
msgid "Link Quality"
|
||||
msgstr "Link Quality"
|
||||
|
||||
msgid "Information provided by raspap.system"
|
||||
msgstr "Information provided by raspap.system"
|
||||
msgid "Information provided by ip and iw and from system"
|
||||
msgstr "Information provided by ip and iw and from system"
|
||||
|
||||
msgid "No MAC Address Found"
|
||||
msgstr "No MAC Address Found"
|
||||
@@ -334,9 +283,6 @@ msgstr "Connected Devices"
|
||||
msgid "Client: Ethernet cable"
|
||||
msgstr "Client: Ethernet cable"
|
||||
|
||||
msgid "Current status"
|
||||
msgstr "Current status"
|
||||
|
||||
msgid "Ethernet"
|
||||
msgstr "Ethernet"
|
||||
|
||||
@@ -349,44 +295,6 @@ msgstr "Smartphone"
|
||||
msgid "WiFi"
|
||||
msgstr "WiFi"
|
||||
|
||||
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 "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 "Mobile Data Client"
|
||||
msgstr "Mobile Data Client"
|
||||
|
||||
@@ -426,22 +334,6 @@ msgstr "Signal strength"
|
||||
msgid "No Client device or not yet configured"
|
||||
msgstr "No Client device or not yet configured"
|
||||
|
||||
msgid "No Client device found"
|
||||
msgstr "No Client device found"
|
||||
|
||||
#: 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"
|
||||
|
||||
msgid "Thanks for being an Insider"
|
||||
msgstr "Thanks for being an Insider"
|
||||
|
||||
#: includes/dhcp.php
|
||||
msgid "DHCP server settings"
|
||||
msgstr "DHCP server settings"
|
||||
@@ -617,9 +509,6 @@ msgstr "Invalid static IP address."
|
||||
msgid "Invalid default gateway."
|
||||
msgstr "Invalid default gateway."
|
||||
|
||||
msgid "Invalid DHCP range start."
|
||||
msgstr "Invalid DHCP range start."
|
||||
|
||||
msgid "Invalid DHCP range end."
|
||||
msgstr "Invalid DHCP range end."
|
||||
|
||||
@@ -654,8 +543,8 @@ msgstr "Security type"
|
||||
msgid "Encryption Type"
|
||||
msgstr "Encryption Type"
|
||||
|
||||
msgid "Pre-shared key (PSK)"
|
||||
msgstr "Pre-shared key (PSK)"
|
||||
msgid "PSK"
|
||||
msgstr "PSK"
|
||||
|
||||
msgid "Advanced settings"
|
||||
msgstr "Advanced settings"
|
||||
@@ -705,11 +594,11 @@ msgstr "Interface %s has no default settings."
|
||||
msgid "Configure settings in <strong>DHCP Server</strong> before starting AP."
|
||||
msgstr "Configure settings in <strong>DHCP Server</strong> before starting AP."
|
||||
|
||||
msgid "Wifi hotspot settings saved."
|
||||
msgstr "Wifi hotspot settings saved."
|
||||
msgid "Wifi Hotspot settings saved"
|
||||
msgstr "Wifi Hotspot settings saved"
|
||||
|
||||
msgid "Unable to save WiFi hotspot settings."
|
||||
msgstr "Unable to save WiFi hotspot settings."
|
||||
msgid "Unable to save wifi hotspot settings"
|
||||
msgstr "Unable to save wifi hotspot settings"
|
||||
|
||||
msgid "Start hotspot"
|
||||
msgstr "Start hotspot"
|
||||
@@ -732,9 +621,6 @@ msgstr "WiFi client AP mode"
|
||||
msgid "Bridged AP mode"
|
||||
msgstr "Bridged AP mode"
|
||||
|
||||
msgid "WiFi repeater mode"
|
||||
msgstr "WiFi repeater mode"
|
||||
|
||||
msgid "Hide SSID in broadcast"
|
||||
msgstr "Hide SSID in broadcast"
|
||||
|
||||
@@ -771,39 +657,6 @@ msgstr "Sets the <code>txpower</code> option for the AP interface and the config
|
||||
msgid "dBm is a unit of level used to indicate that a power ratio is expressed in decibels (dB) with reference to one milliwatt (mW). 30 dBm is equal to 1000 mW, while 0 dBm equals 1.25 mW."
|
||||
msgstr "dBm is a unit of level used to indicate that a power ratio is expressed in decibels (dB) with reference to one milliwatt (mW). 30 dBm is equal to 1000 mW, while 0 dBm equals 1.25 mW."
|
||||
|
||||
msgid "WPA and WPA2"
|
||||
msgstr "WPA and WPA2"
|
||||
|
||||
msgid "WPA2 and WPA3-Personal (transitional mode)"
|
||||
msgstr "WPA2 and WPA3-Personal (transitional mode)"
|
||||
|
||||
msgid "WPA3-Personal (required)"
|
||||
msgstr "WPA3-Personal (required)"
|
||||
|
||||
msgid "Enabled (for supported clients)"
|
||||
msgstr "Enabled (for supported clients)"
|
||||
|
||||
msgid "Required (for supported clients)"
|
||||
msgstr "Required (for supported clients)"
|
||||
|
||||
msgid "802.11w extends strong cryptographic protection to a select set of robust management frames, including Deauthentication, Disassociation and certain categories of Action Management frames. Collectively, this is known as Management Frame Protection (MFP)."
|
||||
msgstr "802.11w extends strong cryptographic protection to a select set of robust management frames, including Deauthentication, Disassociation and certain categories of Action Management frames. Collectively, this is known as Management Frame Protection (MFP)."
|
||||
|
||||
msgid "Scan this QR code directly or %s %sprint a sign%s for your users."
|
||||
msgstr "Scan this QR code directly or %s %sprint a sign%s for your users."
|
||||
|
||||
msgid "Printable Wi-Fi sign"
|
||||
msgstr "Printable Wi-Fi sign"
|
||||
|
||||
msgid "To connect with your phone or tablet, scan the QR code above with your camera app."
|
||||
msgstr "To connect with your phone or tablet, scan the QR code above with your camera app."
|
||||
|
||||
msgid "For other devices, use the login credentials below."
|
||||
msgstr "For other devices, use the login credentials below."
|
||||
|
||||
msgid "Network"
|
||||
msgstr "Network"
|
||||
|
||||
msgid "The selected interface (%s) has support for the 2.4 GHz wireless band only."
|
||||
msgstr "The selected interface (%s) has support for the 2.4 GHz wireless band only."
|
||||
|
||||
@@ -819,24 +672,6 @@ msgstr "The selected interface (%s) does not support wireless mode operation."
|
||||
msgid "The 802.11ac 5 GHz option is disabled until a compatible wireless regulatory domain is set."
|
||||
msgstr "The 802.11ac 5 GHz option is disabled until a compatible wireless regulatory domain is set."
|
||||
|
||||
msgid "WiFi repeater mode: A metric value is already defined for DHCP."
|
||||
msgstr "WiFi repeater mode: A metric value is already defined for DHCP."
|
||||
|
||||
msgid "Restart hotspot to enable WiFi repeater mode."
|
||||
msgstr "Restart hotspot to enable WiFi repeater mode."
|
||||
|
||||
msgid "Unable to obtain metric value for client interface. Repeater mode inactive."
|
||||
msgstr "Unable to obtain metric value for client interface. Repeater mode inactive."
|
||||
|
||||
msgid "Metric value configured for the %s interface."
|
||||
msgstr "Metric value configured for the %s interface."
|
||||
|
||||
msgid "Parameter hiddenSSID contains invalid configuration value."
|
||||
msgstr "Parameter hiddenSSID contains invalid configuration value."
|
||||
|
||||
msgid "Parameter hiddenSSID is not a number."
|
||||
msgstr "Parameter hiddenSSID is not a number."
|
||||
|
||||
#: includes/networking.php
|
||||
msgid "Summary"
|
||||
msgstr "Summary"
|
||||
@@ -880,11 +715,11 @@ msgstr "Apply settings"
|
||||
msgid "Information provided by /sys/class/net"
|
||||
msgstr "Information provided by /sys/class/net"
|
||||
|
||||
msgid "Devices"
|
||||
msgstr "Devices"
|
||||
msgid "Network Devices"
|
||||
msgstr "Network Devices"
|
||||
|
||||
msgid "Diagnostics"
|
||||
msgstr "Diagnostics"
|
||||
msgid "Mobile Data Settings"
|
||||
msgstr "Mobile Data Settings"
|
||||
|
||||
msgid "Properties of network devices"
|
||||
msgstr "Properties of network devices"
|
||||
@@ -946,90 +781,6 @@ msgstr "Please provide a valid SSID."
|
||||
msgid "Please provide a valid PSK."
|
||||
msgstr "Please provide a valid PSK."
|
||||
|
||||
msgid "Speedtest"
|
||||
msgstr "Speedtest"
|
||||
|
||||
msgid "Selecting a server"
|
||||
msgstr "Selecting a server"
|
||||
|
||||
msgid "Privacy"
|
||||
msgstr "Privacy"
|
||||
|
||||
msgid "Server"
|
||||
msgstr "Server"
|
||||
|
||||
msgid "ms"
|
||||
msgstr "ms"
|
||||
|
||||
msgid "Mbps"
|
||||
msgstr "Mbps"
|
||||
|
||||
msgid "Ping"
|
||||
msgstr "Ping"
|
||||
|
||||
msgid "Jitter"
|
||||
msgstr "Jitter"
|
||||
|
||||
msgid "Upload"
|
||||
msgstr "Upload"
|
||||
|
||||
msgid "Start"
|
||||
msgstr "Start"
|
||||
|
||||
msgid "Abort"
|
||||
msgstr "Abort"
|
||||
|
||||
msgid "Wireless LAN routing"
|
||||
msgstr "Wireless LAN routing"
|
||||
|
||||
msgid "Stop WLAN routing"
|
||||
msgstr "Stop WLAN routing"
|
||||
|
||||
msgid "Start WLAN routing"
|
||||
msgstr "Start WLAN routing"
|
||||
|
||||
msgid "Restart WLAN routing"
|
||||
msgstr "Restart WLAN routing"
|
||||
|
||||
msgid "This option configures RaspAP to <b>route network traffic from your wireless client (STA) interface</b> to another available interface."
|
||||
msgstr "This option configures RaspAP to <b>route network traffic from your wireless client (STA) interface</b> to another available interface."
|
||||
|
||||
msgid "When an output interface is selected, <code>iptables</code> rules are added to route packets using network address translation (NAT). This is often done to share internet connectivity from a WLAN with devices on an <code>eth0</code>, <code>usb0</code> or predictable <code>enx</code> interface."
|
||||
msgstr "When an output interface is selected, <code>iptables</code> rules are added to route packets using network address translation (NAT). This is often done to share internet connectivity from a WLAN with devices on an <code>eth0</code>, <code>usb0</code> or predictable <code>enx</code> interface."
|
||||
|
||||
msgid "Wireless client interface"
|
||||
msgstr "Wireless client interface"
|
||||
|
||||
msgid "Output interface"
|
||||
msgstr "Output interface"
|
||||
|
||||
msgid "Configure a static IP address and DHCP for output interface"
|
||||
msgstr "Configure a static IP address and DHCP for output interface"
|
||||
|
||||
msgid "Attempting to enable routing between %s and %s interfaces"
|
||||
msgstr "Attempting to enable routing between %s and %s interfaces"
|
||||
|
||||
msgid "Attempting to disable routing between %s and %s interfaces"
|
||||
msgstr "Attempting to disable routing between %s and %s interfaces"
|
||||
|
||||
msgid "No default DHCP configuration exists for the %s interface"
|
||||
msgstr "No default DHCP configuration exists for the %s interface"
|
||||
|
||||
msgid "Configure a static IP and DHCP for this interface in DHCP Server settings"
|
||||
msgstr "Configure a static IP and DHCP for this interface in DHCP Server settings"
|
||||
|
||||
msgid "WLAN routing configuration saved"
|
||||
msgstr "WLAN routing configuration saved"
|
||||
|
||||
msgid "Unable to save WLAN routing configuration"
|
||||
msgstr "Unable to save WLAN routing configuration"
|
||||
|
||||
msgid "Successfully restarted dnsmasq"
|
||||
msgstr "Successfully restarted dnsmasq"
|
||||
|
||||
msgid "Failed to restart dnsmasq"
|
||||
msgstr "Failed to restart dnsmasq"
|
||||
|
||||
#: includes/system.php
|
||||
msgid "System Information"
|
||||
msgstr "System Information"
|
||||
@@ -1142,12 +893,6 @@ msgstr "Select a theme"
|
||||
msgid "Color"
|
||||
msgstr "Color"
|
||||
|
||||
msgid "Enable this option for resizable, drag and drop widgets. Best for large displays."
|
||||
msgstr "Enable this option for resizable, drag and drop widgets. Best for large displays."
|
||||
|
||||
msgid "Dynamic widgets"
|
||||
msgstr "Dynamic widgets"
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "Tools"
|
||||
|
||||
@@ -1556,14 +1301,14 @@ msgstr "Upload a WireGuard config"
|
||||
msgid "This option uploads and installs an existing WireGuard <code>.conf</code> file on this device."
|
||||
msgstr "This option uploads and installs an existing WireGuard <code>.conf</code> file on this device."
|
||||
|
||||
msgid "Apply iptables rules to the selected interface"
|
||||
msgstr "Apply iptables rules to the selected interface"
|
||||
msgid "Apply iptables rules for AP interface"
|
||||
msgstr "Apply iptables rules for AP interface"
|
||||
|
||||
msgid "Recommended if you wish to forward network traffic from the wg0 interface to clients connected on a desired interface. The active AP interface is the default."
|
||||
msgstr "Recommended if you wish to forward network traffic from the wg0 interface to clients connected on a desired interface. The active AP interface is the default."
|
||||
msgid "Recommended if you wish to forward network traffic from the wg0 interface to clients connected on the AP interface."
|
||||
msgstr "Recommended if you wish to forward network traffic from the wg0 interface to clients connected on the AP interface."
|
||||
|
||||
msgid "This option adds <strong>iptables</strong> <code>Postup</code> and <code>PostDown</code> rules for the interface selected below."
|
||||
msgstr "This option adds <strong>iptables</strong> <code>Postup</code> and <code>PostDown</code> rules for the interface selected below."
|
||||
msgid "This option adds <strong>iptables</strong> <code>Postup</code> and <code>PostDown</code> rules for the configured AP interface (%s)."
|
||||
msgstr "This option adds <strong>iptables</strong> <code>Postup</code> and <code>PostDown</code> rules for the configured AP interface (%s)."
|
||||
|
||||
msgid "Select WireGuard configuration file (.conf)"
|
||||
msgstr "Select WireGuard configuration file (.conf)"
|
||||
@@ -1652,27 +1397,6 @@ msgstr "WireGuard configuration updated successfully"
|
||||
msgid "WireGuard configuration failed to be updated"
|
||||
msgstr "WireGuard configuration failed to be updated"
|
||||
|
||||
msgid "Enable kill switch"
|
||||
msgstr "Enable kill switch"
|
||||
|
||||
msgid "This option adds <strong>iptables</strong> <code>PostUp</code> and <code>PreDown</code> rules for the configured interface."
|
||||
msgstr "This option adds <strong>iptables</strong> <code>PostUp</code> and <code>PreDown</code> rules for the configured interface."
|
||||
|
||||
msgid "Recommended if you wish to prevent the flow of unencrypted packets through non-WireGuard interfaces."
|
||||
msgstr "Recommended if you wish to prevent the flow of unencrypted packets through non-WireGuard interfaces."
|
||||
|
||||
msgid "iptables rules added to WireGuard configuration"
|
||||
msgstr "iptables rules added to WireGuard configuration"
|
||||
|
||||
msgid "Existing iptables rules found in WireGuard configuration - not added"
|
||||
msgstr "Existing iptables rules found in WireGuard configuration - not added"
|
||||
|
||||
msgid "Currently available WireGuard file configurations are displayed below."
|
||||
msgstr "Currently available WireGuard file configurations are displayed below."
|
||||
|
||||
msgid "Activating a configuration will restart the <code>wg-quick</code> service."
|
||||
msgstr "Activating a configuration will restart the <code>wg-quick</code> service."
|
||||
|
||||
#: templates/firewall.php
|
||||
|
||||
msgid "Client Firewall"
|
||||
@@ -1741,92 +1465,9 @@ msgstr "Disable Firewall"
|
||||
msgid "Enable Firewall"
|
||||
msgstr "Enable Firewall"
|
||||
|
||||
msgid "Changing the firewall status may disrupt or allow incoming traffic. Choose <strong>Proceed</strong> to continue."
|
||||
msgstr "Changing the firewall status may disrupt or allow incoming traffic. Choose <strong>Proceed</strong> to continue."
|
||||
|
||||
msgid "Proceed"
|
||||
msgstr "Proceed"
|
||||
|
||||
msgid "Apply changes"
|
||||
msgstr "Apply changes"
|
||||
|
||||
#: templates/ddclient.php
|
||||
|
||||
msgid "Dynamic DNS"
|
||||
msgstr "Dynamic DNS"
|
||||
|
||||
msgid "Service provider"
|
||||
msgstr "Service provider"
|
||||
|
||||
msgid "Select a Dynamic DNS service supported by <strong>ddclient</strong> from the list below. Selecting a known service provider will populate the <code>protocol</code> and <code>server</code> fields. You may also configure the service manually."
|
||||
msgstr "Select a Dynamic DNS service supported by <strong>ddclient</strong> from the list below. Selecting a known service provider will populate the <code>protocol</code> and <code>server</code> fields. You may also configure the service manually."
|
||||
|
||||
msgid "Method to obtain IP"
|
||||
msgstr "Method to obtain IP"
|
||||
|
||||
msgid "Select the method used by <strong>ddclient</strong> to obtain an IP address. This value is specified in the <code>-use</code> option."
|
||||
msgstr "Select the method used by <strong>ddclient</strong> to obtain an IP address. This value is specified in the <code>-use</code> option."
|
||||
|
||||
msgid "Discovery page on the web"
|
||||
msgstr "Discovery page on the web"
|
||||
|
||||
msgid "Network interface"
|
||||
msgstr "Network interface"
|
||||
|
||||
msgid "Network address"
|
||||
msgstr "Network address"
|
||||
|
||||
msgid "Firewall status page"
|
||||
msgstr "Firewall status page"
|
||||
|
||||
msgid "External command"
|
||||
msgstr "External command"
|
||||
|
||||
msgid "Web address"
|
||||
msgstr "Web address"
|
||||
|
||||
msgid "Firewall"
|
||||
msgstr "Firewall"
|
||||
|
||||
msgid "Command"
|
||||
msgstr "Command"
|
||||
|
||||
msgid "Example: <code>192.168.1.254/status.htm</code>."
|
||||
msgstr "Example: <code>192.168.1.254/status.htm</code>."
|
||||
|
||||
msgid "Example: <code>/usr/local/bin/get-ip</code>."
|
||||
msgstr "Example: <code>/usr/local/bin/get-ip</code>."
|
||||
|
||||
msgid "Domain"
|
||||
msgstr "Domain"
|
||||
|
||||
msgid "Enable SSL"
|
||||
msgstr "Enable SSL"
|
||||
|
||||
msgid "Use an encrypted SSL connection for updates. Not supported by all providers."
|
||||
msgstr "Use an encrypted SSL connection for updates. Not supported by all providers."
|
||||
|
||||
msgid "Value specified in milliseconds (ms). Default is 300."
|
||||
msgstr "Value specified in milliseconds (ms). Default is 300."
|
||||
|
||||
msgid "Use the <strong>Generate log</strong> button to output detailed <code>ddclient daemon</code> debug info"
|
||||
msgstr "Use the <strong>Generate log</strong> button to output detailed <code>ddclient daemon</code> debug info"
|
||||
|
||||
msgid "Generate log"
|
||||
msgstr "Generate log"
|
||||
|
||||
msgid "Information provided by ddclient"
|
||||
msgstr "Information provided by ddclient"
|
||||
|
||||
msgid "Start Dynamic DNS"
|
||||
msgstr "Start Dynamic DNS"
|
||||
|
||||
msgid "Stop Dynamic DNS"
|
||||
msgstr "Stop Dynamic DNS"
|
||||
|
||||
msgid "Restart Dynamic DNS"
|
||||
msgstr "Restart Dynamic DNS"
|
||||
|
||||
#: includes/provider.php
|
||||
|
||||
msgid "Account details"
|
||||
@@ -2020,199 +1661,3 @@ msgstr "Forgot password"
|
||||
msgid "Login failed"
|
||||
msgstr "Login failed"
|
||||
|
||||
#: includes/ntp.php
|
||||
|
||||
msgid "NTP Server"
|
||||
msgstr "NTP Server"
|
||||
|
||||
msgid "NTP Server settings"
|
||||
msgstr "NTP Server settings"
|
||||
|
||||
msgid "NTP daemon"
|
||||
msgstr "NTP daemon"
|
||||
|
||||
msgid "Synchronized time"
|
||||
msgstr "Synchronized time"
|
||||
|
||||
msgid "NTP servers"
|
||||
msgstr "NTP servers"
|
||||
|
||||
msgid "Add an NTP server"
|
||||
msgstr "Add an NTP server"
|
||||
|
||||
msgid "Start NTP service"
|
||||
msgstr "Start NTP service"
|
||||
|
||||
msgid "Stop NTP service"
|
||||
msgstr "Stop NTP service"
|
||||
|
||||
msgid "Edit mode"
|
||||
msgstr "Edit mode"
|
||||
|
||||
msgid "Use the <strong>Edit mode</strong> toggle to manually edit the current <code>ntp.config</code> configuration."
|
||||
msgstr "Use the <strong>Edit mode</strong> toggle to manually edit the current <code>ntp.config</code> configuration."
|
||||
|
||||
msgid "Specify a public NTP server or a private one on your local network. IPv4 and IPv6 address, or a fully qualified domain name (FQDN) are acceptable values."
|
||||
msgstr "Specify a public NTP server or a private one on your local network. IPv4 and IPv6 address, or a fully qualified domain name (FQDN) are acceptable values."
|
||||
|
||||
msgid "Public NTP servers supporting Network Time Security (NTS) may be specified with the <code>nts</code> suffix."
|
||||
msgstr "Public NTP servers supporting Network Time Security (NTS) may be specified with the <code>nts</code> suffix."
|
||||
|
||||
msgid "Examples of valid server entries include <code>%s</code>, <code>%s</code> and <code>%s</code>."
|
||||
msgstr "Examples of valid server entries include <code>%s</code>, <code>%s</code> and <code>%s</code>."
|
||||
|
||||
msgid "Current <code>ntpq peer</code> status is displayed below. An asterisk (<code>*</code>) indicates the preferred server."
|
||||
msgstr "Current <code>ntpq peer</code> status is displayed below. An asterisk (<code>*</code>) indicates the preferred server."
|
||||
|
||||
msgid "NTP configuration cannot be empty"
|
||||
msgstr "NTP configuration cannot be empty"
|
||||
|
||||
msgid "Restarting ntpd.service"
|
||||
msgstr "Restarting ntpd.service"
|
||||
|
||||
msgid "Please enter a valid NTP server"
|
||||
msgstr "Please enter a valid NTP server"
|
||||
|
||||
msgid "Attempting to start ntp.service"
|
||||
msgstr "Attempting to start ntp.service"
|
||||
|
||||
msgid "Attempting to stop ntp.service"
|
||||
msgstr "Attempting to stop ntp.service"
|
||||
|
||||
msgid "NTP configuration not found at %s"
|
||||
msgstr "NTP configuration not found at %s"
|
||||
|
||||
msgid "NTP configuration updated"
|
||||
msgstr "NTP configuration updated"
|
||||
|
||||
#: tailscale plugin
|
||||
|
||||
msgid "Advertising device as a Tailscale exit node"
|
||||
msgstr "Advertising device as a Tailscale exit node"
|
||||
|
||||
msgid "Attempting to optimize UDP throughput"
|
||||
msgstr "Attempting to optimize UDP throughput"
|
||||
|
||||
msgid "Kernel transport layer offloads enabled for UDP"
|
||||
msgstr "Kernel transport layer offloads enabled for UDP"
|
||||
|
||||
msgid "Failed to enable kernel transport layer offloads for UDP"
|
||||
msgstr "Failed to enable kernel transport layer offloads for UDP"
|
||||
|
||||
msgid "Attempting to set tailscale up"
|
||||
msgstr "Attempting to set tailscale up"
|
||||
|
||||
msgid "Attempting to set tailscale down"
|
||||
msgstr "Attempting to set tailscale down"
|
||||
|
||||
msgid "Attempting to disconnect from tailscale"
|
||||
msgstr "Attempting to disconnect from tailscale"
|
||||
|
||||
msgid "Disconnected from tailscale and expired node key"
|
||||
msgstr "Disconnected from tailscale and expired node key"
|
||||
|
||||
msgid "Unable to disconnect from tailscale"
|
||||
msgstr "Unable to disconnect from tailscale"
|
||||
|
||||
msgid "Expected tailscale binary not found at: %"
|
||||
msgstr "Expected tailscale binary not found at: %"
|
||||
|
||||
msgid "A Tailscale VPN exit node extension for RaspAP"
|
||||
msgstr "A Tailscale VPN exit node extension for RaspAP"
|
||||
|
||||
msgid "Unable to retrieve Tailscale login. Choose <strong>%s</strong> to continue."
|
||||
msgstr "Unable to retrieve Tailscale login. Choose <strong>%s</strong> to continue."
|
||||
|
||||
msgid "Start Tailscale"
|
||||
msgstr "Start Tailscale"
|
||||
|
||||
msgid "Stop Tailscale"
|
||||
msgstr "Stop Tailscale"
|
||||
|
||||
msgid "Next"
|
||||
msgstr "Next"
|
||||
|
||||
msgid "Device approved and activated as a Tailscale exit node"
|
||||
msgstr "Device approved and activated as a Tailscale exit node"
|
||||
|
||||
msgid "Not connected: Login required."
|
||||
msgstr "Not connected: Login required."
|
||||
|
||||
msgid "Tailscale VPN"
|
||||
msgstr "Tailscale VPN"
|
||||
|
||||
msgid "Exit node activated"
|
||||
msgstr "Exit node activated"
|
||||
|
||||
msgid "The device <code>%s</code> is connected with the address <code>%s</code> and offers an <strong>exit node</strong>."
|
||||
msgstr "The device <code>%s</code> is connected with the address <code>%s</code> and offers an <strong>exit node</strong>."
|
||||
|
||||
msgid "See the %s on how to use this exit node with your devices."
|
||||
msgstr "See the %s on how to use this exit node with your devices."
|
||||
|
||||
msgid "Allow exit node"
|
||||
msgstr "Allow exit node"
|
||||
|
||||
msgid "The device <code>%s</code> is pending approval as an exit node."
|
||||
msgstr "The device <code>%s</code> is pending approval as an exit node."
|
||||
|
||||
msgid "Locate the <code>%s</code> <strong>Exit Node</strong> badge in the machines list."
|
||||
msgstr "Locate the <code>%s</code> <strong>Exit Node</strong> badge in the machines list."
|
||||
|
||||
msgid "Open Tailscale Machines"
|
||||
msgstr "Open Tailscale Machines"
|
||||
|
||||
msgid "To allow this device as an exit node, choose <strong>Open Tailscale Machines</strong>."
|
||||
msgstr "To allow this device as an exit node, choose <strong>Open Tailscale Machines</strong>."
|
||||
|
||||
msgid "From the %s icon menu of the exit node, open the %s panel."
|
||||
msgstr "From the %s icon menu of the exit node, open the %s panel."
|
||||
|
||||
msgid "Edit route settings"
|
||||
msgstr "Edit route settings"
|
||||
|
||||
msgid "Login to Tailscale"
|
||||
msgstr "Login to Tailscale"
|
||||
|
||||
msgid "To connect device %s to your tailnet, choose %s."
|
||||
msgstr "To connect device %s to your tailnet, choose %s."
|
||||
|
||||
msgid "After logging in, choose <strong>Next</strong> to continue."
|
||||
msgstr "After logging in, choose <strong>Next</strong> to continue."
|
||||
|
||||
msgid "Configure exit node"
|
||||
msgstr "Configure exit node"
|
||||
|
||||
msgid "The device <code>%s</code> is connected to your tailnet with the address <code>%s</code>."
|
||||
msgstr "The device <code>%s</code> is connected to your tailnet with the address <code>%s</code>."
|
||||
|
||||
msgid "By default, Tailscale only routes traffic between the devices on which it's been installed. By configuring <code>%s</code> as an <strong>exit node</strong>, your public internet traffic will be routed through this device"
|
||||
msgstr "By default, Tailscale only routes traffic between the devices on which it's been installed. By configuring <code>%s</code> as an <strong>exit node</strong>, your public internet traffic will be routed through this device"
|
||||
|
||||
msgid "When you route all traffic through an exit node, you're effectively using default routes (0.0.0.0/0, ::/0), similar to how you would if you were using a typical VPN."
|
||||
msgstr "When you route all traffic through an exit node, you're effectively using default routes (0.0.0.0/0, ::/0), similar to how you would if you were using a typical VPN."
|
||||
|
||||
msgid "Advertise <code>%s</code> as an exit node"
|
||||
msgstr "Advertise <code>%s</code> as an exit node"
|
||||
|
||||
msgid "This effectively configures Tailscale as a VPN to mask your real location, access region-restricted content, or enhance privacy when connecting from untrusted networks."
|
||||
msgstr "This effectively configures Tailscale as a VPN to mask your real location, access region-restricted content, or enhance privacy when connecting from untrusted networks."
|
||||
|
||||
msgid "This option lets Tailscale know your device is ready to route traffic."
|
||||
msgstr "This option lets Tailscale know your device is ready to route traffic."
|
||||
|
||||
msgid "Recommended for Tailscale exit nodes with Linux 6.2 or later kernels, this uses UDP generic receive offload (GRO) forwarding to reduce CPU overhead."
|
||||
msgstr "Recommended for Tailscale exit nodes with Linux 6.2 or later kernels, this uses UDP generic receive offload (GRO) forwarding to reduce CPU overhead."
|
||||
|
||||
msgid "This option enables transport layer offloads for better performance."
|
||||
msgstr "This option enables transport layer offloads for better performance."
|
||||
|
||||
msgid "Choose <strong>Save settings</strong> to continue."
|
||||
msgstr "Choose <strong>Save settings</strong> to continue."
|
||||
|
||||
msgid "Tailnet status"
|
||||
msgstr "Tailnet status"
|
||||
|
||||
msgid "Current <code>tailnet</code> status is displayed below."
|
||||
msgstr "Current <code>tailnet</code> status is displayed below."
|
||||
|
||||
|
||||
2
plugins
@@ -74,21 +74,6 @@ 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
|
||||
|
||||
@@ -22,8 +22,6 @@ class PluginInstaller
|
||||
private $refModules;
|
||||
private $rootPath;
|
||||
private $pluginsManifest;
|
||||
private $repoPublic;
|
||||
private $helperScriptPath;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -34,8 +32,6 @@ class PluginInstaller
|
||||
$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
|
||||
@@ -67,70 +63,51 @@ class PluginInstaller
|
||||
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 = [];
|
||||
$installed = false;
|
||||
|
||||
foreach ($pluginManifest as $plugin) {
|
||||
$installed = false;
|
||||
|
||||
if (!empty($plugin['namespace'])) {
|
||||
foreach ($installedPlugins as $installedPlugin) {
|
||||
if (strpos($installedPlugin['class'], $plugin['namespace']) !== false) {
|
||||
$installed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if the plugin is installed
|
||||
foreach ($installedPlugins as $plugin) {
|
||||
if (str_contains($plugin, $pluginManifest[0]['namespace'])) {
|
||||
$installed = true;
|
||||
break;
|
||||
}
|
||||
$pluginEntries[] = [
|
||||
'manifest' => $plugin,
|
||||
'installed' => $installed
|
||||
];
|
||||
}
|
||||
$plugins[] = $pluginEntries;
|
||||
$plugins[] = [
|
||||
'manifest' => $pluginManifest,
|
||||
'installed' => $installed
|
||||
];
|
||||
}
|
||||
return array_merge(...$plugins);
|
||||
return $plugins;
|
||||
} catch (\Exception $e) {
|
||||
error_log("An error occurred: " . $e->getMessage());
|
||||
throw $e; // re-throw to global ExceptionHandler
|
||||
throw $e; // re-throw to global ExceptionHandler
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function getPlugins(): array
|
||||
{
|
||||
$plugins = [];
|
||||
$pluginPath = $path ?? $this->pluginPath;
|
||||
|
||||
if (file_exists($pluginPath)) {
|
||||
$directories = scandir($pluginPath);
|
||||
if (file_exists($this->pluginPath)) {
|
||||
$directories = scandir($this->pluginPath);
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
if ($directory === '.' || $directory === '..') {
|
||||
continue;
|
||||
}
|
||||
$pluginClass = "RaspAP\\Plugins\\$directory\\$directory";
|
||||
$pluginFile = "$pluginPath/$directory/$directory.php";
|
||||
$pluginFile = $this->pluginPath . "/$directory/$directory.php";
|
||||
|
||||
if (file_exists($pluginFile)) {
|
||||
if ($path === 'plugins-available') {
|
||||
require_once $pluginFile;
|
||||
}
|
||||
if (class_exists($pluginClass)) {
|
||||
$plugins[] = [
|
||||
'class' => $pluginClass,
|
||||
'installPath' => $pluginPath
|
||||
];
|
||||
}
|
||||
if (file_exists($pluginFile) && class_exists($pluginClass)) {
|
||||
$plugins[] = $pluginClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,61 +115,30 @@ class PluginInstaller
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a plugin by either extracting an archive or creating a symlink,
|
||||
* then performs required actions as defined in the plugin manifest
|
||||
* Retrieves a plugin archive and performs install actions defined in the manifest
|
||||
*
|
||||
* @param string $pluginUri
|
||||
* @param string $pluginVersion
|
||||
* @param string $installPath
|
||||
* @param string $archiveUrl
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function installPlugin(string $pluginUri, string $pluginVersion, string $installPath): bool
|
||||
public function installPlugin($archiveUrl): 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);
|
||||
}
|
||||
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
|
||||
$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';
|
||||
@@ -205,30 +151,26 @@ class PluginInstaller
|
||||
$this->copyConfigFiles($manifest['configuration'], $pluginDir);
|
||||
$rollbackStack[] = 'removeConfigFiles';
|
||||
}
|
||||
if (!empty($manifest['permissions'])) {
|
||||
$this->setFilePermissions($manifest['permissions']);
|
||||
$rollbackStack[] = 'revertFilePermissions';
|
||||
}
|
||||
if (!empty($manifest['javascript'])) {
|
||||
$this->copyJavaScriptFiles($manifest['javascript'], $pluginDir);
|
||||
$rollbackStack[] = 'removeJavaScript';
|
||||
}
|
||||
if ($installPath === 'plugins') {
|
||||
$this->copyPluginFiles($pluginDir, $this->rootPath);
|
||||
$rollbackStack[] = 'removePluginFiles';
|
||||
}
|
||||
$this->copyPluginFiles($pluginDir, $this->rootPath);
|
||||
$rollbackStack[] = 'removePluginFiles';
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
//$this->rollback($rollbackStack, $manifest, $pluginDir);
|
||||
throw new \Exception('Installation step failed: ' . $e->getMessage());
|
||||
error_log('Plugin installation failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
error_log('Plugin installation failed: ' . $e->getMessage());
|
||||
throw new \Exception($e->getMessage());
|
||||
error_log('An error occured: ' .$e->getMessage());
|
||||
throw new \Exception( $e->getMessage());
|
||||
//throw $e;
|
||||
} finally {
|
||||
if (isset($tempFile) && file_exists($tempFile)) {
|
||||
if (!empty($tempFile) && file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
if (isset($extractDir) && is_dir($extractDir)) {
|
||||
if (!empty($extractDir) && is_dir($extractDir)) {
|
||||
$this->deleteDir($extractDir);
|
||||
}
|
||||
}
|
||||
@@ -252,7 +194,7 @@ class PluginInstaller
|
||||
$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));
|
||||
$cmd = sprintf('sudo /etc/raspap/plugins/plugin_helper.sh sudoers %s', escapeshellarg($tmpSudoers));
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception('Plugin helper failed to install sudoers.');
|
||||
@@ -269,16 +211,10 @@ class PluginInstaller
|
||||
*/
|
||||
private function installDependencies(array $dependencies): void
|
||||
{
|
||||
if (empty($dependencies)) {
|
||||
return; // nothing to do
|
||||
}
|
||||
$packages = array_keys($dependencies);
|
||||
$packageList = implode(' ', $packages);
|
||||
|
||||
$packageList = implode(' ', array_map('escapeshellarg', array_keys($dependencies)));
|
||||
$cmd = sprintf(
|
||||
'sudo %s packages %s',
|
||||
escapeshellarg($this->helperScriptPath),
|
||||
$packageList
|
||||
);
|
||||
$cmd = sprintf('sudo /etc/raspap/plugins/plugin_helper.sh packages %s', escapeshellarg($packageList));
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception('Plugin helper failed to install depedencies.');
|
||||
@@ -298,7 +234,7 @@ class PluginInstaller
|
||||
$username = escapeshellarg($user['name']);
|
||||
$password = escapeshellarg($user['pass']);
|
||||
|
||||
$cmd = sprintf('sudo %s user %s %s', escapeshellarg($this->helperScriptPath), $username, $password);
|
||||
$cmd = sprintf('sudo /etc/raspap/plugins/plugin_helper.sh user %s %s', $username, $password);
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception('Plugin helper failed to create user: ' . $user['name']);
|
||||
@@ -315,13 +251,8 @@ class PluginInstaller
|
||||
{
|
||||
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);
|
||||
$destination = escapeshellarg($config['destination']);
|
||||
$cmd = sprintf('sudo /etc/raspap/plugins/plugin_helper.sh config %s %s', $source, $destination);
|
||||
$return = shell_exec($cmd);
|
||||
if (strpos(strtolower($return), 'ok') === false) {
|
||||
throw new \Exception("Failed to copy configuration file: $source to $destination");
|
||||
@@ -329,57 +260,6 @@ class PluginInstaller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets permissions on a specified file, including owner/group and mode
|
||||
*
|
||||
* @param array $permissions): void
|
||||
*/
|
||||
private function setFilePermissions(array $permissions): void
|
||||
{
|
||||
foreach ($permissions as $entry) {
|
||||
$file = $entry['file'] ?? null;
|
||||
$owner = $entry['owner'] ?? null;
|
||||
$group = $entry['group'] ?? null;
|
||||
$mode = $entry['mode'] ?? null;
|
||||
|
||||
if (!$file || !$owner || !$group || !$mode) {
|
||||
error_log("Incomplete permission entry for file: " . json_encode($entry));
|
||||
continue;
|
||||
}
|
||||
|
||||
$cmd = escapeshellcmd('sudo '.RASPI_CONFIG.'/plugins/plugin_helper.sh') .
|
||||
' permissions ' .
|
||||
escapeshellarg($file) .' '.
|
||||
escapeshellarg($owner) .' '.
|
||||
escapeshellarg($group) .' '.
|
||||
escapeshellarg($mode);
|
||||
exec($cmd . ' 2>&1', $output, $return);
|
||||
|
||||
if ($return !== 0) {
|
||||
throw new \Exception("Failed to set permissions on $file: " . implode("\n", $output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
@@ -390,43 +270,13 @@ class PluginInstaller
|
||||
{
|
||||
$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);
|
||||
$cmd = sprintf('sudo /etc/raspap/plugins/plugin_helper.sh plugin %s %s', $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
|
||||
*
|
||||
@@ -463,7 +313,6 @@ class PluginInstaller
|
||||
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) {
|
||||
@@ -532,27 +381,30 @@ class PluginInstaller
|
||||
$html .= '</tr></thead><tbody>';
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
$manifestData = $plugin['manifest'] ?? [];
|
||||
$installed = $plugin['installed'] ?? false;
|
||||
$manifest = htmlspecialchars(json_encode($manifestData), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$manifestData = $plugin['manifest'][0] ?? []; // Access the first manifest entry or default to an empty array
|
||||
|
||||
$manifest = htmlspecialchars(json_encode($manifestData), ENT_QUOTES, 'UTF-8');
|
||||
$installed = $plugin['installed'];
|
||||
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>';
|
||||
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>';
|
||||
data-plugin-manifest="' .$manifest. '"> ' . _("Details") .'</button>';
|
||||
}
|
||||
|
||||
|
||||
$icon = htmlspecialchars($manifestData['icon'] ?? '');
|
||||
$pluginDocs = htmlspecialchars($manifestData['plugin_docs'] ?? '');
|
||||
$pluginUri = htmlspecialchars($manifestData['plugin_uri'] ?? '');
|
||||
$nameText = htmlspecialchars($manifestData['name'] ?? 'Unknown Plugin');
|
||||
$name = '<i class="' .$icon. ' link-secondary me-2"></i><a href="'
|
||||
.$pluginDocs
|
||||
.'" target="_blank">'
|
||||
.$nameText. '</a>';
|
||||
|
||||
$name = '<i class="' . $icon . ' link-secondary me-2"></i><a href="'
|
||||
. $pluginUri
|
||||
. '" target="_blank">'
|
||||
. $nameText. '</a>';
|
||||
|
||||
$version = htmlspecialchars($manifestData['version'] ?? 'N/A');
|
||||
$description = htmlspecialchars($manifestData['description'] ?? 'No description available');
|
||||
|
||||
@@ -564,25 +416,6 @@ class PluginInstaller
|
||||
$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,50 +95,48 @@ class Sysinfo
|
||||
|
||||
/*
|
||||
* Returns RPi Model and PCB Revision from Pi Revision Code (cpuinfo)
|
||||
* @see https://github.com/raspberrypi/documentation/blob/develop/documentation/asciidoc/computers/raspberry-pi/revision-codes.adoc
|
||||
* @see http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/
|
||||
*/
|
||||
public function rpiRevision()
|
||||
{
|
||||
$revisions = array(
|
||||
'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+',
|
||||
'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',
|
||||
'a02100' => 'Compute Module 3+',
|
||||
'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',
|
||||
'a03111' => 'Model 4B Revision 1.1 (1 GB)',
|
||||
'b03111' => 'Model 4B Revision 1.1 (2 GB)',
|
||||
'c03111' => 'Model 4B Revision 1.1 (4 GB)',
|
||||
'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' => 'Raspberry Pi 5 (4 GB)',
|
||||
'd04170' => 'Raspberry Pi 5 (8 GB)'
|
||||
'c04170' => 'Pi 5 (4 GB)',
|
||||
'd04170' => 'Pi 5 (8 GB)'
|
||||
);
|
||||
|
||||
$cpuinfo_array = '';
|
||||
@@ -157,48 +155,5 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,370 +0,0 @@
|
||||
<?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
|
||||
*
|
||||
* @param string $interface
|
||||
* @return integer $clientCount
|
||||
*/
|
||||
public function getWirelessClients($interface): int
|
||||
{
|
||||
$cmd = 'iw dev '. escapeshellarg($interface) .' station dump';
|
||||
exec($cmd, $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'), 'fas fa-bullseye', 'hostapd_conf', 20,
|
||||
$this->addItem(_('Hotspot'), 'far fa-dot-circle', 'hostapd_conf', 20,
|
||||
fn() => RASPI_HOTSPOT_ENABLED
|
||||
);
|
||||
$this->addItem(_('DHCP Server'), 'fas fa-exchange-alt', 'dhcpd_conf', 30,
|
||||
@@ -39,6 +39,9 @@ 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
|
||||
|
||||