Compare commits

..

No commits in common. "master" and "2.8.9" have entirely different histories.

1879 changed files with 59253 additions and 64913 deletions

59
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,59 @@
---
name: Bug report
about: Create a report to help us improve RaspAP
title: ''
labels: ''
assignees: ''
---
<!-- These comments will NOT appear in your issue, so it's OK to ignore them -->
<!--
Thanks for reporting a bug for RaspAP.
Important: If you are NOT using a clean installation of a compatible OS, start with a fresh SD card, install RaspAP and replicate your bug BEFORE reporting an issue.
All submitters MUST read the issue policy and reporting guidelines:
https://docs.raspap.com/issues/
Refer to the frequently asked questions (FAQ) and official documentation:
https://docs.raspap.com/faq/
Do you have a question or want to suggest a new feature? Start a Discussion here:
https://github.com/RaspAP/raspap-webgui/discussions
Be sure there are no issues similar to yours that are already open. You can check this by searching the issues in this repository. If there is a duplicate issue, please close this one and add a comment to the existing issue instead.
-->
<!-- Provide a general summary of the issue in the Title above -->
## Checklist
<!-- IMPORTANT! Fill in the boxes that apply by marking them like so: [x] -->
- [ ] This is a bug report
- [ ] I observed this bug on a clean install of the OS
- [ ] I have followed the project prerequisites
- [ ] I have searched this repository for existing issues
- [ ] I checked the FAQ and official documentation before creating this issue
- [ ] I have read and understand the issue reporting guidelines
## Bug description
<!-- Provide a detailed description of the issue -->
## Your environment
1. Operating System: **ENTER HERE** <!-- RPi OS 32-bit Lite, Armbian, Debian, etc. -->
2. Hardware and version: <!-- RPi Zero/3B+/4, OrangePi 3, etc. -->
3. RaspAP version: <!-- reported by the Quick Installer or About page -->
4. Clean install of a compatible operating system? <!-- Yes/No -->
5. RaspAP Quick Install or Manual setup? <!-- Quick Install/Manual -->
6. Using default configuration? <!-- Yes/No -->
7. Simultaneous AP and managed mode? <!-- Yes/No -->
8. Onboard wireless chipset or external adapter? <!-- Onboard/External -->
9. Other software or services running with RaspAP?
## Steps to reproduce
<!-- Tell us how to reproduce this issue. Provide as much detailed information as possible -->
## Screenshots
<!-- If applicable, add screenshots to help explain your problem -->
## Additional context
<!-- Add any other context about the problem here -->

View file

@ -1,5 +1 @@
blank_issues_enabled: false
contact_links:
- name: RaspAP Community Support
url: https://github.com/RaspAP/raspap-webgui/discussions
about: Please ask and answer questions here.

View file

@ -1,169 +0,0 @@
name: Bug Report
description: Create a report to help us improve RaspAP
title: "[Bug]: "
labels: ["bug-report", "triage"]
projects: ["RaspAP-webgui"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug for RaspAP.
**Important:** If you are NOT using a clean installation of a compatible OS, start with a fresh SD card, install RaspAP and replicate your bug BEFORE reporting an issue.
All submitters MUST read the [issue policy and reporting guidelines](https://docs.raspap.com/issues/). Bug reports that do not follow the issue policy will be marked as `invalid` and closed. Likewise, bug reports missing required fields will be marked `incomplete` and closed (no hard feelings).
In this event, we ask you to re-submit a complete bug report.
Be sure there are no issues similar to yours that are already open. You can check this by searching the issues in this repository.
If there is a duplicate issue, please comment on the existing issue instead.
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/RaspAP/raspap-webgui/blob/master/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow this project's Code of Conduct.
required: true
- type: checkboxes
id: policies
attributes:
label: Issue reporting checklist
options:
- label: I have read and understand the [issue reporting policy](https://docs.raspap.com/issues/).
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).
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
- type: dropdown
id: os
attributes:
label: Operating System
options:
- Raspberry Pi OS (64-bit) Lite Bookworm
- Raspberry Pi OS (32-bit) Lite Bookworm
- Raspberry Pi OS (64-bit) Desktop Bookwom
- Raspberry Pi OS (64-bit) Lite Bullseye
- Raspberry Pi OS (32-bit) Lite Bullseye
- Armbian 23.05 (Suni)
- Debian Bookworm
validations:
required: true
- type: dropdown
id: install
attributes:
label: Installation method
options:
- Pre-built image
- Quick install
- Manual setup
validations:
required: true
- type: dropdown
id: adapter
attributes:
label: Onboard wireless chipset or external adapter?
options:
- Onboard wireless chipset
- External adapter
validations:
required: true
- type: dropdown
id: hardware
attributes:
label: Hardware
options:
- Raspberry Pi 5
- Raspberry Pi 4 Model B
- Raspberry Pi 3 Model B+
- Raspberry Pi 3 Model A+
- Raspberry Pi 3 Model B
- Raspberry Pi Zero 2 W
- Raspberry Pi Zero W
- Raspberry Pi Compute Module
- Orange Pi family
- Other
validations:
required: true
- type: dropdown
id: version
attributes:
label: RaspAP version
options:
- Latest
- Other (specify below)
validations:
required: true
- type: dropdown
id: software
attributes:
label: Other software or services running with RaspAP?
options:
- Yes (specify below)
- No other software
- Not sure
validations:
required: true
- type: input
id: contact
attributes:
label: Contact details (optional)
description: How can we get in touch with you if we need more info?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: bug-description
attributes:
label: Bug description
description: Also tell us, what did you expect to happen?
placeholder: Provide a detailed description of the issue.
value:
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: Tell us how to reproduce this issue.
placeholder: Provide as much detailed information as possible.
value:
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
placeholder: Upload your screenshot(s) here.
value:
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional context
description:
placeholder: Add any other context about the problem here.
value:
validations:
required: false
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
validations:
required: false

18
.github/stale.yml vendored Normal file
View file

@ -0,0 +1,18 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- enhancement
- feature request
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View file

@ -1,23 +0,0 @@
# This workflow was added by CodeSee. Learn more at https://codesee.io/
# This is v2.0 of this workflow file
on:
push:
branches:
- master
pull_request_target:
types: [opened, synchronize, reopened]
name: CodeSee
permissions: read-all
jobs:
codesee:
runs-on: ubuntu-latest
continue-on-error: true
name: Analyze the repo with CodeSee
steps:
- uses: Codesee-io/codesee-action@v2
with:
codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
codesee-url: https://app.codesee.io

16
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,16 @@
on:
issues:
types: [opened, edited]
jobs:
auto_close_issues:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Automatically close issues that don't follow the issue template
uses: lucasbento/auto-close-issues@v1.0.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-close-message: "@${issue.user.login}: hello! :wave:\n\nThis issue is being automatically closed because it does not follow the issue template.\nPlease review this project's issue policy https://docs.raspap.com/issues" # optional property
closed-issues-label: "invalid" # optional property

View file

@ -1,80 +0,0 @@
name: Build and publish RaspAP images
permissions:
contents: write
on:
release:
types: [published]
jobs:
build-raspap-image:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: "32-bit"
pi_gen_version: "master"
- arch: "64-bit"
pi_gen_version: "arm64"
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Add RaspAP Stage
run: |
mkdir -p stage-raspap/package-raspap &&
{
cat > stage-raspap/package-raspap/00-run-chroot.sh <<-EOF
#!/bin/bash
apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps
curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0
# Set Wi-Fi country to prevent RF kill
raspi-config nonint do_wifi_country "US"
# Fetch RaspAP version and set MOTD
RASPAP_VERSION=\$(curl -sL https://install.raspap.com | bash -s -- --version)
echo "\$RASPAP_VERSION" | tee /etc/motd
EOF
} &&
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
{
cat > stage-raspap/prerun.sh <<-EOF
#!/bin/bash -e
if [ ! -d "\${ROOTFS_DIR}" ]; then
copy_previous
fi
EOF
} &&
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
{
cat > stage-raspap/prerun.sh <<-EOF
#!/bin/bash -e
if [ ! -d "\${ROOTFS_DIR}" ]; then
copy_previous
fi
EOF
} &&
chmod +x stage-raspap/prerun.sh
- name: Build RaspAP Image
id: build
uses: usimd/pi-gen-action@v1
with:
image-name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}"
enable-ssh: 1
stage-list: stage0 stage1 stage2 ./stage-raspap
verbose-output: true
pi-gen-version: ${{ matrix.pi_gen_version }}
pi-gen-repository: RaspAP/pi-gen
- name: Upload Artifact
uses: svenstaro/upload-release-action@v2
with:
asset_name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}.img.zip"
file: ${{ steps.build.outputs.image-path }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.event.inputs.tag || github.ref }}
overwrite: true

View file

@ -1,23 +0,0 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8.0.0
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
exempt-issue-labels: pinned, enhancement, feature request
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View file

@ -4,6 +4,3 @@ yarn-error.log
*.swp
includes/config.php
rootCA.pem
vendor
.env
locale/**/*.mo

4
.gitmodules vendored
View file

@ -1,4 +0,0 @@
[submodule "plugins"]
path = plugins
url = https://github.com/RaspAP/plugins
branch = master

View file

@ -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.
@ -15,21 +15,16 @@ You can become a sponsor using your individual or organization's GitHub account.
## Exclusive features
The following features are currently available exclusively to sponsors. A tangible side benefit of sponsorship is that Insiders are able to help steer future development of RaspAP. This is done through Insiders' access to discussions, feature requests, issues and pull requests in the private GitHub repository.
✅ [Network device management](https://docs.raspap.com/net-devices/)
✅ [Firewall settings](https://docs.raspap.com/firewall/)
✅ [Network device management](https://docs.raspap.com/net-devices/)
✅ [Firewall settings](https://docs.raspap.com/firewall/)
✅ [WPA3-Personal AP security](https://docs.raspap.com/ap-basics/#wpa3-personal)
✅ [802.11w Protected Management Frames](https://docs.raspap.com/ap-basics/#80211w)
✅ [802.11w Protected Management Frames](https://docs.raspap.com/ap-basics/#80211w)
✅ [Printable Wi-Fi signs](https://docs.raspap.com/ap-basics/#printable-signs)
✅ [Drag & drop dashboard widgets](https://docs.raspap.com/ap-basics/#drag-drop-widgets)
✅ [MAC address cloning](https://docs.raspap.com/net-devices/#changing-the-mac-address)
✅ [Network diagnostics](https://docs.raspap.com/net-devices/#diagnostics)
✅ [WireGuard VPN kill switch](https://docs.raspap.com/wireguard/#kill-switch)
✅ [Dynamic DNS support](https://docs.raspap.com/dynamicdns/)
✅ [Multiple WireGuard configs](https://docs.raspap.com/wireguard/#multiple-configs)
✅ [Wireless LAN routing](https://docs.raspap.com/wlanrouting/)
✅ [Custom user avatars](https://docs.raspap.com/authentication/#custom-user-avatars)
✅ [WiFi repeater mode](https://docs.raspap.com/ap-basics/#wifi-repeater-mode)
✅ [NTP Service](https://docs.raspap.com/ntp/)
✅ [Limited privilege user role](https://docs.raspap.com/authentication/#limited-privilege-user-role)
⚙️ Dynamic DNS support (in progress)
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.

View file

@ -1,64 +0,0 @@
# RaspAP Code of Conduct
## Our Pledge
RaspAP is an open source community project devoted to building simple yet
powerful wireless router software for Debian-based devices. We as members, contributors,
and leaders pledge to make participation in our community an experience consisent with
the norms of civil behavior and respectful discourse.
To this end, we pledge to act and interact in ways that uphold the following standards.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Being excellent to one another (treat others as you wish to be treated)
* Giving and accepting constructive feedback
* Being respectful of differing opinions and viewpoints
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior are provided below. Quotes in parantheses
are portions of actual comments or issues that have been posted to this project:
* Unconstructive comments (this software "fails miserably")
* Complaining and/or emotional language ("I'm pulling my hair out!")
* Comments that are derogatory, trolling or are personal attacks ("what's wrong with you guys?")
* Public or private harassment of any kind
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
We have a zero tolerance policy for any and all of the above behavior.
## Enforcement Responsibilities
We reserve the right to remove, edit, or reject comments, issues, commits,
discussions and other contributions that are not consistent with this Code of Conduct.
We may ban, temporarily or permanently, any contributor for violating this code, as appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -1,22 +1,13 @@
## How to contribute
Everyone is invited and welcome to contribute to RaspAP. There's a lot to do — if you're not a developer we can always use help with the official [project documentation](https://docs.raspap.com) and language [translations](https://crowdin.com/project/raspap). If you have experience in Linux networking, you can share your knowledge by answering questions from RaspAP users in our [GitHub discussions](https://github.com/RaspAP/raspap-webgui/discussions) and/or the [/r/RaspAP](https://reddit.com/r/RaspAP) subreddit.
If you're a devloper, the process of contributing code is straightforward:
1. Fork the project in your account and create a new branch: `feat/your-feature` or `fix/your-bugfix`.
2. Open [an issue](https://github.com/RaspAP/raspap-webgui/issues) describing the feature or bug fix contribution you'd like to make.
3. Commit changes in your branch.
4. Open a pull request and reference the initial issue in the pull request message, or by linking it in GitHub.
1. Fork the project in your account and create a new branch: `your-great-feature`.
2. Open an issue in the repository describing the feature contribution you'd like to make. This will help us get you started on the right foot.
3. Commit changes in your feature branch.
4. Open a pull request and reference the initial issue in the pull request message.
### Coding standards
This project follows the [PSR-2](http://www.php-fig.org/psr/psr-2/) coding style guidelines. There are many ways to check your code for PSR-2. An excellent tool is [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). The command line tool phpcs can be run against any single file. [Phing](https://www.phing.info/), a PHP build tool, integrates nicely with `phpcs` to automate PSR-2 checks across all source files in a project.
### RaspAP community
RaspAP is made possible by a strong [community of developers](https://github.com/RaspAP/raspap-webgui/graphs/contributors). If you have any questions or would like to get involved in RaspAP, dive into any of these channels:
### Development process
Development processes used by contributors to the project are described [on this page](https://docs.raspap.com/developers/). It does not endorse one over the other; rather it is meant to share two different approaches.
* [GitHub discussions](https://github.com/RaspAP/raspap-webgui/discussions)
* [Discord chat](https://discord.gg/KVAsaAR)
* [Twitter](https://twitter.com/rasp_ap)
* [Reddit](https://www.reddit.com/r/RaspAP/)
If you enjoy using RaspAP and would like to support our work financially, consider becoming an [Insider](https://github.com/sponsors/RaspAP).

117
README.md
View file

@ -1,77 +1,60 @@
![RaspAP Custom OS images](https://github.com/user-attachments/assets/e871adf1-123c-450b-94eb-80a185c242cc)
[![Release 3.3.2](https://img.shields.io/badge/release-v3.3.2-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR)
![](https://i.imgur.com/xeKD93p.png)
[![Release 2.8.9](https://img.shields.io/badge/release-v2.8.9-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Join%20Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) ![https://travis-ci.com/github/raspap/raspap-webgui/](https://api.travis-ci.org/RaspAP/raspap-webgui.svg) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/RaspAP?style=social)](https://www.reddit.com/r/RaspAP/)
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our [custom OS images](#pre-built-image), [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), [ad blocking](#ad-blocking), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) creates a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl-quick/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
RaspAP has been featured by [PC World](https://www.pcwelt.de/article/1789512/raspberry-pi-als-wlan-router.html), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/), and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in [countless projects](https://github.com/RaspAP/raspap-awesome#projects).
RaspAP has been featured 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).
![dashboard](https://github.com/user-attachments/assets/f7cf5c32-4d95-4ac8-8a30-6d892d7ac6ed)
<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">
<img width="32.5%" alt="About" src="https://github.com/user-attachments/assets/ba62d8bb-34f0-44ee-9fe8-504763a03726">
<img width="32.5%" alt="Wireguard" src="https://github.com/user-attachments/assets/4ba16118-8671-4654-9a36-92ac7bc8507f">
<img width="32.5%" alt="System" src="https://github.com/user-attachments/assets/f54e04fc-dc2c-4a21-903b-23641795822b">
![](https://i.imgur.com/uhBFoOB.png)
![](https://i.imgur.com/EiIpdOS.gif)
![](https://i.imgur.com/eCjUS1H.gif)
![](https://i.imgur.com/5FT2BcS.gif)
![](https://i.imgur.com/RKaBFrZ.gif)
## 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)
- [HTTPS support](#https-support)
- [Docker support](#docker-support)
- [Custom user plugins](#custom-user-plugins)
- [Multilingual support](#multilingual-support)
- [HTTPS support](#https-support)
- [How to contribute](#how-to-contribute)
- [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 (32-bit) Lite](https://www.raspberrypi.org/software/operating-systems/#raspberry-pi-os-32-bit). The Raspberry Pi OS desktop and 64-bit beta distros are unsupported.
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 +62,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.
@ -110,11 +93,6 @@ To configure an OpenVPN client, upload a valid .ovpn file and, optionally, speci
See our [OpenVPN documentation](https://docs.raspap.com/openvpn/) for more information.
## VPN provider support
Several popular VPN providers include a Linux Command Line Interface (CLI) for interacting with their services. As a new beta feature, you may optionally control these VPN services from within RaspAP. After your provider's CLI is installed on your system you may administer it thereafter by using RaspAP's UI.
See our [VPN provider documentation](https://docs.raspap.com/providers/) for more information.
## Ad Blocking
This feature uses DNS blacklisting to block requests for ads, trackers and other undesirable hosts. To enable ad blocking, simply respond to the prompt during the installation. As a beta release, we encourage testing and feedback from users of RaspAP.
@ -128,28 +106,36 @@ 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/).
## 802.11ac 5GHz support
RaspAP provides an 802.11ac wireless mode option for supported hardware (currently the RPi 3B+/4 and compatible Orange Pi models) and wireless regulatory domains. See [this](https://docs.raspap.com/ap-basics/#80211ac-5-ghz) for more information.
RaspAP provides an 802.11ac wireless mode option for supported hardware (currently the RPi 3B+/4 and compatible Orange Pi models) and wireless regulatory domains. See [this FAQ](https://docs.raspap.com/faq/#80211ac) for more information.
## Supported operating systems
RaspAP was originally made for Raspbian, but now also installs on the following Debian-based distros.
| Distribution | Release | Architecture | Support |
|---|:---:|:---:|:---:|
| Raspberry Pi OS | (64-bit) Lite Bookworm | ARM | Official |
| Raspberry Pi OS | (32-bit) Lite Bookworm | ARM | Official |
| Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official |
| Raspberry Pi OS | (64-bit) 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 |
| Debian | Bookworm | ARM / x86_64 | Beta |
| Raspberry Pi OS | (64-bit) Lite Bullseye | ARM | Official |
| Armbian | Bullseye | [ARM](https://docs.armbian.com/#supported-socs) | Official |
| Debian | Bullseye | ARM / x86_64 | Beta |
| Ubuntu | 18.04 LTS / 19.10 | ARM / x86_64 | Beta |
<img src="https://i.imgur.com/XiAJNKb.png" style="width:480px;" />
![](https://i.imgur.com/luiyYNw.png)
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.
We find Armbian particularly well-suited for this project. 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.
## Multilingual support
RaspAP uses [GNU Gettext](https://www.gnu.org/software/gettext/) to manage multilingual messages. In order to use RaspAP with one of our supported translations, you must configure a corresponding language package on your RPi. To list languages currently installed on your system, use `locale -a` at the shell prompt. To generate new locales, run `sudo dpkg-reconfigure locales` and select any other desired locales. Details are provided on our [documentation site](https://docs.raspap.com/translations/).
See this list of [supported languages](https://docs.raspap.com/translations/#supported-languages) that are actively maintained by volunteer translators. If your language is not supported, why not [contribute a translation](https://docs.raspap.com/translations/#contributing-to-a-translation)? Contributors will receive credit as the original translators.
## HTTPS support
The Quick Installer may be used to [generate SSL certificates](https://docs.raspap.com/ssl-quick/) with `mkcert`. The installer automates the manual steps [described here](https://docs.raspap.com/ssl-manual/), including configuring lighttpd with SSL support.
@ -162,22 +148,7 @@ curl -sL https://install.raspap.com | bash -s -- --cert
**Note**: this only installs mkcert and generates an SSL certificate with the input you provide. It does *not* (re)install RaspAP.
More information on SSL certificates and HTTPS support is available [in our documentation](https://docs.raspap.com/ssl/).
## Docker support
<img src="https://github.com/RaspAP/raspap-webgui/assets/229399/dc40dfc4-e9b8-405f-8ffb-6c5f88482b8e" width="450">
As an alternative to the [Quick installer](#quick-installer), RaspAP may be run in an isolated, portable [Docker container](https://docs.raspap.com/docker/).
See the [RaspAP-docker repo](https://github.com/RaspAP/raspap-docker/) for more information.
## Custom user plugins
RaspAP's integrated `PluginManager` provides a framework for developers to create custom plugins. To facilitate this, a `SamplePlugin` [repository](https://github.com/RaspAP/SamplePlugin) is available to get developers started on the right track. If you'd like to develop your own plugin for RaspAP, see the [documentation](https://docs.raspap.com/custom-plugins/) or get started right away by forking the [SamplePlugin](https://github.com/RaspAP/SamplePlugin).
## Multilingual support
RaspAP uses [GNU Gettext](https://www.gnu.org/software/gettext/) to manage multilingual messages. In order to use RaspAP with one of our supported translations, you must configure a corresponding language package on your RPi. To list languages currently installed on your system, use `locale -a` at the shell prompt. To generate new locales, run `sudo dpkg-reconfigure locales` and select any other desired locales. Details are provided on our [documentation site](https://docs.raspap.com/translations/).
See this list of [supported languages](https://docs.raspap.com/translations/#supported-languages) that are actively maintained by volunteer translators. If your language is not supported, why not [contribute a translation](https://docs.raspap.com/translations/#contributing-to-a-translation)? Contributors will receive credit as the original translators.
More information on SSL certificates and HTTPS support is available [in our documentation](https://docs.raspap.com/ssl-quick/).
## How to contribute
1. Fork the project in your account and create a new branch: `your-great-feature`.

View file

@ -1,27 +0,0 @@
# Security Policy
The RaspAP team and community take all security vulnerabilities seriously. This document outlines security procedures and general policies for the RaspAP open source projects as found on https://github.com/RaspAP/.
If you believe you have found a security vulnerability in any RaspAP-owned repository, please report it to us as described below.
## Reporting a vulnerability in RaspAP
Thank you for improving the security of our open source software.
We appreciate your efforts and responsible disclosure, and will make every effort to acknowledge your contributions.
Please report (suspected) security vulnerabilities to [security@raspap.com](mailto:security@raspap.com). The requested information listed below will help us better understand the nature and scope of the possible issue:
1. Type of issue (eg. shell exploit, cross-site scripting, etc.)
2. Full paths of source file(s) related to the manifestation of the issue
3. The location of the affected source code (tag/branch/commit or direct URL)
4. Any special configuration required to reproduce the issue
5. Step-by-step instructions to reproduce the issue
6. Proof-of-concept or exploit code (if possible)
7. Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
You will receive a response from us within 48 hours. Developers may ask for additional information or clarity on your report.
If the issue is confirmed, we will release a patch as soon as possible depending on complexity, but historically within a few days.
## Third-party modules
Report security vulnerabilities in third-party modules to the person or team maintaining the module.

View file

@ -1,54 +1,24 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['blocklist_id'])) {
$blocklist_id = escapeshellcmd($_POST['blocklist_id']);
$blocklist_id = $_POST['blocklist_id'];
$notracking_url = "https://raw.githubusercontent.com/notracking/hosts-blocklists/master/";
switch ($blocklist_id) {
case "StevenBlack/hosts \(default\)":
$list_url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
$dest_file = "hostnames.txt";
break;
case "badmojr/1Hosts \(Mini\)":
$list_url = "https://badmojr.github.io/1Hosts/mini/hosts.txt";
$dest_file = "hostnames.txt";
break;
case "badmojr/1Hosts \(Lite\)":
$list_url = "https://badmojr.github.io/1Hosts/Lite/hosts.txt";
$dest_file = "hostnames.txt";
break;
case "badmojr/1Hosts \(Pro\)":
$list_url = "https://badmojr.github.io/1Hosts/Pro/hosts.txt";
$dest_file = "hostnames.txt";
break;
case "badmojr/1Hosts \(Xtra\)":
$list_url = "https://badmojr.github.io/1Hosts/Xtra/hosts.txt";
$dest_file = "hostnames.txt";
break;
case "oisd/big \(default\)":
$list_url = "https://big.oisd.nl/dnsmasq";
$dest_file = "domains.txt";
break;
case "oisd/small":
$list_url = "https://small.oisd.nl/dnsmasq";
$dest_file = "domains.txt";
break;
case "oisd/nsfw":
$list_url = "https://nsfw.oisd.nl/dnsmasq";
$dest_file = "domains.txt";
break;
case "notracking-hostnames":
$file = "hostnames.txt";
break;
case "notracking-domains":
$file = "domains.txt";
break;
}
$blocklist = $list_url . $dest_file;
$dest = substr($dest_file, 0, strrpos($dest_file, "."));
$blocklist = $notracking_url . $file;
exec("sudo /etc/raspap/adblock/update_blocklist.sh $list_url $dest_file " .RASPI_ADBLOCK_LISTPATH, $return);
$jsonData = ['return'=>$return,'list'=>$dest];
echo json_encode($jsonData);
} else {
$jsonData = ['return'=>2,'output'=>['Error getting data']];
exec("sudo /etc/raspap/adblock/update_blocklist.sh $blocklist $file " .RASPI_ADBLOCK_LISTPATH, $return);
$jsonData = ['return'=>$return];
echo json_encode($jsonData);
}

View file

@ -1,10 +1,15 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once RASPI_CONFIG.'/raspap.php';
header('X-Frame-Options: DENY');
header("Content-Security-Policy: default-src 'none'; connect-src 'self'");
require_once '../../includes/authenticate.php';
$interface = filter_input(INPUT_GET, 'inet', FILTER_SANITIZE_SPECIAL_CHARS);
if (empty($interface)) {
// Use first interface if inet parameter not provided.
@ -14,7 +19,8 @@ if (empty($interface)) {
} else {
exit('No network interfaces found.');
}
}
}
define('IFNAMSIZ', 16);
if (strlen($interface) > IFNAMSIZ) {
exit('Interface name too long.');
@ -72,12 +78,14 @@ for ($i = count($jsonData) - 1; $i >= 0; --$i) {
$datareceived = round($jsonData[$i]['rx'] / $dsu_factor, 0);
if ($timeunits === 'm') {
echo '{ "date": "' , $dt->format('Y-m') , '", "rx": "' , $datareceived ,
echo '{ "date": "' , $dt->format('Y-m') , '", "rx": "' , $datareceived ,
'", "tx": "' , $datasend , '" }';
} else {
echo '{ "date": "' , $dt->format('Y-m-d') , '", "rx": "' , $datareceived ,
echo '{ "date": "' , $dt->format('Y-m-d') , '", "rx": "' , $datareceived ,
'", "tx": "' , $datasend , '" }';
}
}
echo ' ]';

View file

@ -1,9 +1,6 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
<?php
require '../../includes/csrf.php';
if (filter_input(INPUT_GET, 'tu') == 'h') {

View file

@ -1,20 +1,13 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';
if (isset($_POST['logfile'])) {
$logfile = escapeshellarg($_POST['logfile']);
$valid = '/(\/var\/log|\/tmp)/';
$logfile = escapeshellcmd($_POST['logfile']);
if (preg_match($valid, $logfile)) {
// truncate requested log file
exec("sudo truncate -s 0 $logfile", $return);
} else {
$return = 1;
}
// truncate requested log file
exec("sudo truncate -s 0 $logfile", $return);
echo json_encode($return);
}

View file

@ -1,29 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/config.php';
require_once '../../includes/session.php';
require_once '../../includes/authenticate.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();
}
}
$jsonData = ['return'=>$return];
echo json_encode($jsonData);

View file

@ -1,9 +1,6 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require '../../includes/csrf.php';
exec("ls /sys/class/net | grep -v lo", $interfaces);
echo json_encode($interfaces);

View file

@ -1,9 +1,7 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
$arrConfig = array();
@ -17,4 +15,3 @@ foreach ($hostapdconfig as $hostapdconfigline) {
};
$channel = intval($arrConfig['channel']);
echo json_encode($channel);

View file

@ -1,15 +1,42 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/locale.php';
if (isset($_POST['interface'])) {
define( 'NL80211_BAND_24GHZ', 0x1 );
define( 'NL80211_BAND_5GHZ', 0x2 );
$iface = escapeshellcmd($_POST['interface']);
$parser = new \RaspAP\Parsers\IwParser($iface);
$supportedFrequencies = $parser->parseIwInfo($iface);
echo json_encode($supportedFrequencies);
// get physical device for selected interface
exec("iw dev | awk '/$iface/ {print line}{line = $0}'", $return);
$phy = $return[0];
// get frequencies supported by device
exec('iw '.$phy.' info | sed -rn "s/^.*\*\s([0-9]{4})\sMHz.*/\1/p"', $frequencies);
if (count(preg_grep('/^24[0-9]{2}/i', $frequencies)) >0) {
$flags += NL80211_BAND_24GHZ;
}
if (count(preg_grep('/^5[0-9]{3}/i', $frequencies)) >0) {
$flags += NL80211_BAND_5GHZ;
}
switch ($flags) {
case NL80211_BAND_24GHZ:
$msg = sprintf(_("The selected interface (%s) has support for the 2.4 GHz wireless band only."), $iface);
break;
case NL80211_BAND_5GHZ:
$msg = sprintf(_("The selected interface (%s) has support for the 5 GHz wireless band only."), $iface);
break;
case NL80211_BAND_24GHZ | NL80211_BAND_5GHZ:
$msg = sprintf(_("The selected interface (%s) has support for both the 2.4 and 5 GHz wireless bands."), $iface);
break;
default:
$msg = sprintf(_("The selected interface (%s) does not support wireless mode operation."), $iface);
}
echo json_encode($msg);
}

View file

@ -1,10 +1,8 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/functions.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['interface'])) {
$int = preg_replace('/[^a-z0-9]/', '', $_POST['interface']);

View file

@ -1,12 +1,9 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';
$interface = $_POST['iface'];
require '../../includes/csrf.php';
require_once '../../includes/config.php';
$interface = $_GET['iface'];
if (isset($interface)) {
// fetch dnsmasq.conf settings for interface
@ -14,19 +11,15 @@ if (isset($interface)) {
$conf = ParseConfig($return);
$dhcpdata['DHCPEnabled'] = empty($conf) ? false : true;
if (is_string($conf['dhcp-range'])) {
$arrRange = explode(",", $conf['dhcp-range']);
} else {
$arrRange = explode(",", $conf['dhcp-range'][0]);
}
$dhcpdata['RangeStart'] = $arrRange[0] ?? null;
$dhcpdata['RangeEnd'] = $arrRange[1] ?? null;
$dhcpdata['RangeMask'] = $arrRange[2] ?? null;
$dhcpdata['leaseTime'] = $arrRange[3] ?? null;
$dhcpHost = $conf["dhcp-host"] ?? null;
$arrRange = explode(",", $conf['dhcp-range']);
$dhcpdata['RangeStart'] = $arrRange[0];
$dhcpdata['RangeEnd'] = $arrRange[1];
$dhcpdata['RangeMask'] = $arrRange[2];
$dhcpdata['leaseTime'] = $arrRange[3];
$dhcpHost = $conf["dhcp-host"];
$dhcpHost = empty($dhcpHost) ? [] : $dhcpHost;
$dhcpdata['dhcpHost'] = is_array($dhcpHost) ? $dhcpHost : [ $dhcpHost ];
$upstreamServers = is_array($conf['server'] ?? null) ? $conf['server'] : [ $conf['server'] ?? '' ];
$upstreamServers = is_array($conf['server']) ? $conf['server'] : [ $conf['server'] ];
$dhcpdata['upstreamServersEnabled'] = empty($conf['server']) ? false: true;
$dhcpdata['upstreamServers'] = array_filter($upstreamServers);
preg_match('/([0-9]*)([a-z])/i', $dhcpdata['leaseTime'], $arrRangeLeaseTime);
@ -36,10 +29,10 @@ if (isset($interface)) {
$arrDns = explode(",", $conf['dhcp-option']);
if ($arrDns[0] == '6') {
if (count($arrDns) > 1) {
$dhcpdata['DNS1'] = $arrDns[1] ?? null;
$dhcpdata['DNS1'] = $arrDns[1];
}
if (count($arrDns) > 2) {
$dhcpdata['DNS2'] = $arrDns[2] ?? null;
$dhcpdata['DNS2'] = $arrDns[2];
}
}
}
@ -53,16 +46,13 @@ if (isset($interface)) {
preg_match('/static\sdomain_name_server=(.*)/', $matched[0], $static_dns);
preg_match('/fallback\sstatic_'.$interface.'/', $matched[0], $fallback);
preg_match('/(?:no)?gateway/', $matched[0], $gateway);
preg_match('/nohook\swpa_supplicant/', $matched[0], $nohook_wpa_supplicant);
$dhcpdata['Metric'] = $metric[1] ?? null;
$dhcpdata['StaticIP'] = isset($static_ip[1]) && strpos($static_ip[1], '/') !== false
? substr($static_ip[1], 0, strpos($static_ip[1], '/'))
: ($static_ip[1] ?? '');
$dhcpdata['SubnetMask'] = cidr2mask($static_ip[1] ?? '');
$dhcpdata['StaticRouters'] = $static_routers[1] ?? null;
$dhcpdata['StaticDNS'] = $static_dns[1] ?? null;
$dhcpdata['Metric'] = $metric[1];
$dhcpdata['StaticIP'] = strpos($static_ip[1],'/') ? substr($static_ip[1], 0, strpos($static_ip[1],'/')) : $static_ip[1];
$dhcpdata['SubnetMask'] = cidr2mask($static_ip[1]);
$dhcpdata['StaticRouters'] = $static_routers[1];
$dhcpdata['StaticDNS'] = $static_dns[1];
$dhcpdata['FallbackEnabled'] = empty($fallback) ? false: true;
$dhcpdata['DefaultRoute'] = $gateway[0] == "gateway";
$dhcpdata['NoHookWPASupplicant'] = ($nohook_wpa_supplicant[0] ?? '') == "nohook wpa_supplicant";
echo json_encode($dhcpdata);
}

View file

@ -1,49 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/locale.php';
if (isset($_POST['interface'])) {
define( 'NL80211_BAND_24GHZ', 0x1 );
define( 'NL80211_BAND_5GHZ', 0x2 );
if(!preg_match('/^[a-zA-Z0-9]+$/', $_POST['interface'])) {
exit('Invalid interface name.');
}
$iface = escapeshellcmd($_POST['interface']);
$flags = 0;
// get physical device for selected interface
exec("iw dev | awk -v iface=".$iface." '/^phy#/ { phy = $0 } $1 == \"Interface\" { interface = $2 } interface == iface { print phy }'", $return);
$phy = $return[0];
// get frequencies supported by device
exec('iw '.$phy.' info | sed -rn "s/^.*\*\s([0-9]{4})\sMHz.*/\1/p"', $frequencies);
if (count(preg_grep('/^24[0-9]{2}/i', $frequencies)) >0) {
$flags += NL80211_BAND_24GHZ;
}
if (count(preg_grep('/^5[0-9]{3}/i', $frequencies)) >0) {
$flags += NL80211_BAND_5GHZ;
}
switch ($flags) {
case NL80211_BAND_24GHZ:
$msg = sprintf(_("The selected interface (%s) has support for the 2.4 GHz wireless band only."), $iface);
break;
case NL80211_BAND_5GHZ:
$msg = sprintf(_("The selected interface (%s) has support for the 5 GHz wireless band only."), $iface);
break;
case NL80211_BAND_24GHZ | NL80211_BAND_5GHZ:
$msg = sprintf(_("The selected interface (%s) has support for both the 2.4 and 5 GHz wireless bands."), $iface);
break;
default:
$msg = sprintf(_("The selected interface (%s) does not support wireless mode operation."), $iface);
}
echo json_encode($msg);
}

View file

@ -1,10 +1,9 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
// fetch wg client.conf
exec('sudo cat '. RASPI_WIREGUARD_PATH.'client.conf', $return);
echo implode(PHP_EOL,$return);

View file

@ -1,20 +1,18 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$entity = escapeshellcmd($_POST['entity']);
require '../../includes/csrf.php';
require_once '../../includes/config.php';
$entity = $_POST['entity'];
if (isset($entity)) {
// generate public/private key pairs for entity
$pubkey = RASPI_WIREGUARD_PATH.$entity.'-public.key';
$privkey = RASPI_WIREGUARD_PATH.$entity.'-private.key';
$pubkey_tmp = '/tmp/'.$entity.'-public.key';
$privkey_tmp = '/tmp/'.$entity.'-private.key';
exec("sudo wg genkey | tee $privkey_tmp | wg pubkey > $pubkey_tmp", $return);
$wgdata['pubkey'] = str_replace("\n",'',file_get_contents($pubkey_tmp));
exec("sudo mv $privkey_tmp $privkey", $return);

View file

@ -0,0 +1,93 @@
<?php
/*
Save settings of network devices (type, name, PW, APN ...)
Called by js saveNetDeviceSettings (App/js/custom.js)
*/
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/functions.php';
if (isset($_POST['interface'])) {
$int = $_POST['interface'];
$cfg = [];
$file = $RASPI_MOBILEDATA_CONFIG;
$cfgfile="/etc/wvdial.conf";
if ( $int == "mobiledata") {
$cfg['pin'] = $_POST["pin-mobile"];
$cfg['apn'] = $_POST["apn-mobile"];
$cfg['apn_user'] = $_POST["apn-user-mobile"];
$cfg['apn_pw'] = $_POST["apn-pw-mobile"];
$cfg['router_user'] = $cfg['apn_user'] ;
$cfg['router_pw'] = $cfg['apn_pw'] ;
if (file_exists($cfgfile)) {
if($cfg["pin"] !== "") exec('sudo /bin/sed -i "s/CPIN=\".*\"/CPIN=\"'.$cfg["pin"].'\"/gi" '.$cfgfile);
if($cfg["apn"] !== "") exec('sudo /bin/sed -i "s/\"IP\"\,\".*\"/\"IP\"\,\"'.$cfg["apn"].'\"/gi" '.$cfgfile);
if($cfg["apn_user"] !== "") exec('sudo /bin/sed -i "s/^username = .*$/Username = '.$cfg["apn_user"].'/gi" '.$cfgfile);
if($cfg["apn_pw"] !== "") exec('sudo /bin/sed -i "s/^password = .*$/Password = '.$cfg["apn_pw"].'/gi" '.$cfgfile);
}
if (write_php_ini($cfg, RASPI_MOBILEDATA_CONFIG)) {
$jsonData = ['return'=>0,'output'=>['Successfully saved mobile data settings']];
} else {
$jsonData = ['return'=>1,'output'=>['Error saving mobile data settings']];
}
} else if ( preg_match("/netdevices/",$int)) {
if(!isset($_POST['opts']) ) {
$jsonData = ['return'=>0,'output'=>['No valid data to add/delete udev rule ']];
echo json_encode($jsonData);
return;
} else {
$opts=explode(" ",$_POST['opts'] );
$dev=$opts[0];
$vid=$_POST["int-vid-".$dev];
$pid=$_POST["int-pid-".$dev];
$mac=$_POST["int-mac-".$dev];
$name=trim($_POST["int-name-".$dev]);
// limit device name to letters and numbers. Total length max 20
$name=preg_replace("/[^a-z0-9]/", "", strtolower($name));
$name=substr($name, 0, min(strlen($name),20));
$type=$_POST["int-type-".$dev];
$newtype=$_POST["int-new-type-".$dev];
$udevfile=$_SESSION["udevrules"]["udev_rules_file"]; // default file /etc/udev/rules.d/80-net-devices.rules";
// find the rule prototype and prefix
$rule = "";
foreach($_SESSION["udevrules"]["network_devices"] as $devt) {
if($devt["type"]==$newtype) {
$rulenew = $devt["udev_rule"];
$prefix = $devt["name_prefix"];
}
}
// check for an existing rule and delete lines with same MAC or same VID/PID
if (!empty($vid) && !empty($pid)) {
$rule = '^.*ATTRS{idVendor}==\"' . $vid . '\".*ATTRS{idProduct}==\"' . $pid . '\".*$';
exec('sudo sed -i "/'.$rule.'/Id" '.$udevfile); // clear all entries with this VID/PID
$rule = '^.*ATTRS{idProduct}==\"' . $pid . '\".*ATTRS{idVendor}==\"' . $vid . '\".*$';
exec('sudo sed -i "/'.$rule.'/Id" '.$udevfile); // clear all entries with this VID/PID
}
if (!empty($mac)) {
exec('sudo sed -i "/^.*'.$mac.'.*$/d" '.$udevfile); // clear all entries with same MAC
}
// create new entry
if ( ($type != $newtype) || !empty($name) ) { // new device type or new name
if (empty($name)) $name = $prefix."%n";
if (!empty($mac)) $rule = preg_replace("/\\\$MAC\\\$/i", $mac, $rulenew);
if (!empty($vid)) $rule = preg_replace("/\\\$IDVENDOR\\\$/i", $vid, $rule);
if (!empty($pid)) $rule = preg_replace("/\\\$IDPRODUCT\\\$/i", $pid, $rule);
if (!empty($name)) $rule = preg_replace("/\\\$DEVNAME\\\$/i",$name,$rule);
if (!empty($rule)) exec('echo \''.$rule.'\' | sudo /usr/bin/tee -a '.$udevfile);
}
$jsonData = ['return'=>0,'output'=>['Settings changed for device '.$dev. '<br>Changes will only be in effect after reconnecting the device' ] ];
}
} else {
$jsonData = ['return'=>1,'output'=>['Unknown network configuration']];
}
} else {
$jsonData = ['return'=>2,'output'=>'Unable to detect interface'];
}
echo json_encode($jsonData);

View file

@ -1,9 +1,7 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/defaults.php';
require_once '../../includes/functions.php';
require_once '../../includes/wifi_functions.php';
@ -16,7 +14,7 @@ knownWifiStations($networks);
nearbyWifiStations($networks, !isset($_REQUEST["refresh"]));
connectedWifiStations($networks);
sortNetworksByRSSI($networks);
foreach ($networks as $ssid => $network) $networks[$ssid]["ssidutf8"] = ssid2utf8( $ssid );
foreach ($networks as $ssid => $network) $networks[$ssid]["ssidutf8"] = ssid2utf8( $ssid );
$connected = array_filter($networks, function($n) { return $n['connected']; } );
$known = array_filter($networks, function($n) { return !$n['connected'] && $n['configured']; } );

View file

@ -1,9 +1,7 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';
if (isset($_POST['cfg_id'])) {
@ -26,3 +24,4 @@ if (isset($_POST['cfg_id'])) {
echo json_encode($return);
}

View file

@ -1,9 +1,7 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';
if (isset($_POST['cfg_id'])) {
@ -13,3 +11,4 @@ if (isset($_POST['cfg_id'])) {
$jsonData = ['return'=>$return];
echo json_encode($jsonData);
}

View file

@ -1,26 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
$plugin_uri = $_POST['plugin_uri'] ?? null;
$plugin_version = $_POST['plugin_version'] ?? null;
$install_path = $_POST['install_path'] ?? null;
if (isset($plugin_uri, $plugin_version, $install_path)) {
try {
$return = $pluginInstaller->installPlugin($plugin_uri, $plugin_version, $install_path);
echo json_encode($return);
} catch (Exception $e) {
http_response_code(422); // unprocessable content
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(400); // Bad Request
echo json_encode(['error' => 'Plugin URI, version, and install path are required']);
exit;
}

View file

@ -1,31 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$lastActivity = $_SESSION['lastActivity'] ?? time();
$sessionLifetime = time() - $lastActivity;
$status = $sessionLifetime >= RASPI_SESSION_TIMEOUT ? 'session_expired' : 'active';
if ($status === 'session_expired') {
session_unset(); // unset all session variables
session_destroy(); // destroy the session
}
// send response
header('Content-Type: application/json');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
header('Pragma: no-cache');
$response = [
'status' => $status,
'last_activity' => $lastActivity,
'session_lifetime' => $sessionLifetime
];
echo json_encode($response);
exit();

View file

@ -1,23 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$action = escapeshellcmd($_POST['a']);
if (isset($action)) {
switch($action) {
case "reboot":
$response = shell_exec("sudo /sbin/reboot");
break;
case "shutdown":
$response = shell_exec("sudo /sbin/shutdown -h now");
break;
default:
$response = 'Unknown action: '.$action;
}
echo json_encode($response);
}

View file

@ -1,22 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/defaults.php';
require_once '../../includes/functions.php';
$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);
$response['tag'] = $tagName;
$response['update'] = $updateAvailable;
echo json_encode($response);

View file

@ -1,18 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$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);

View file

@ -1,23 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$tempDir = sys_get_temp_dir();
$filePath = $tempDir . DIRECTORY_SEPARATOR . RASPI_DEBUG_LOG;
if (isset($filePath)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($filePath));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: '.filesize($filePath));
readfile($filePath);
exit();
} else {
header('Location: '.'/system_info');
exit();
}

View file

@ -1,23 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['csrf_token'])) {
if (csrfValidateRequest() && !CSRFValidate()) {
handleInvalidCSRFToken();
}
// set installer path + options
$path = getenv("DOCUMENT_ROOT");
$opts = " --update --yes --check 0 --path $path";
$installer = "sudo /etc/raspap/system/raspbian.sh";
$execUpdate = $installer.$opts;
$response = shell_exec($execUpdate);
echo json_encode($response);
} else {
handleInvalidCSRFToken();
}

View file

@ -1,47 +0,0 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$logFile = '/tmp/raspap_install.log';
$searchStrings = [
'Configure update' => 1,
'Updating sources' => 2,
'Installing required packages' => 3,
'Cloning latest files' => 4,
'Installing application' => 5,
'Installation completed' => 6,
'error' => 7
];
usleep(500);
if (file_exists($logFile)) {
$handle = fopen($logFile, 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
foreach ($searchStrings as $searchString => $value) {
if (strpos($line, $searchString) !== false) {
echo $value .PHP_EOL;
flush();
ob_flush();
if ($value === 6) {
fclose($handle);
exit();
} elseif ($value === 7) {
echo $line .PHP_EOL;
fclose($handle);
exit();
}
}
}
}
fclose($handle);
} else {
echo json_encode("Unable to open file: $logFile");
}
} else {
echo json_encode("File does not exist: $logFile");
}

View file

@ -1,24 +0,0 @@
import os
from fastapi.security.api_key import APIKeyHeader
from fastapi import Security, HTTPException
from starlette.status import HTTP_403_FORBIDDEN
from dotenv import load_dotenv
load_dotenv()
apikey=os.getenv('RASPAP_API_KEY')
#if env not set, set the api key to "insecure"
if apikey == None:
apikey = "insecure"
print(apikey)
api_key_header = APIKeyHeader(name="access_token", auto_error=False)
async def get_api_key(api_key_header: str = Security(api_key_header)):
if api_key_header ==apikey:
return api_key_header
else:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="403: Unauthorized"
)

View file

@ -1,156 +0,0 @@
from fastapi import FastAPI, Depends
from fastapi.security.api_key import APIKey
import auth
import json
import modules.system as system
import modules.ap as ap
import modules.client as client
import modules.dns as dns
import modules.dhcp as dhcp
import modules.ddns as ddns
import modules.firewall as firewall
import modules.networking as networking
import modules.openvpn as openvpn
import modules.wireguard as wireguard
tags_metadata = [
]
app = FastAPI(
title="API for RaspAP",
openapi_tags=tags_metadata,
version="0.0.1",
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
}
)
@app.get("/system", tags=["system"])
async def get_system(api_key: APIKey = Depends(auth.get_api_key)):
return{
'hostname': system.hostname(),
'uptime': system.uptime(),
'systime': system.systime(),
'usedMemory': system.usedMemory(),
'processorCount': system.processorCount(),
'LoadAvg1Min': system.LoadAvg1Min(),
'systemLoadPercentage': system.systemLoadPercentage(),
'systemTemperature': system.systemTemperature(),
'hostapdStatus': system.hostapdStatus(),
'operatingSystem': system.operatingSystem(),
'kernelVersion': system.kernelVersion(),
'rpiRevision': system.rpiRevision()
}
@app.get("/ap", tags=["accesspoint/hotspot"])
async def get_ap(api_key: APIKey = Depends(auth.get_api_key)):
return{
'driver': ap.driver(),
'ctrl_interface': ap.ctrl_interface(),
'ctrl_interface_group': ap.ctrl_interface_group(),
'auth_algs': ap.auth_algs(),
'wpa_key_mgmt': ap.wpa_key_mgmt(),
'beacon_int': ap.beacon_int(),
'ssid': ap.ssid(),
'channel': ap.channel(),
'hw_mode': ap.hw_mode(),
'ieee80211n': ap.ieee80211n(),
'wpa_passphrase': ap.wpa_passphrase(),
'interface': ap.interface(),
'wpa': ap.wpa(),
'wpa_pairwise': ap.wpa_pairwise(),
'country_code': ap.country_code(),
'ignore_broadcast_ssid': ap.ignore_broadcast_ssid()
}
@app.get("/clients/{wireless_interface}", tags=["Clients"])
async def get_clients(wireless_interface, api_key: APIKey = Depends(auth.get_api_key)):
return{
'active_clients_amount': client.get_active_clients_amount(wireless_interface),
'active_clients': json.loads(client.get_active_clients(wireless_interface))
}
@app.get("/dhcp", tags=["DHCP"])
async def get_dhcp(api_key: APIKey = Depends(auth.get_api_key)):
return{
'range_start': dhcp.range_start(),
'range_end': dhcp.range_end(),
'range_subnet_mask': dhcp.range_subnet_mask(),
'range_lease_time': dhcp.range_lease_time(),
'range_gateway': dhcp.range_gateway(),
'range_nameservers': dhcp.range_nameservers()
}
@app.get("/dns/domains", tags=["DNS"])
async def get_domains(api_key: APIKey = Depends(auth.get_api_key)):
return{
'domains': json.loads(dns.adblockdomains())
}
@app.get("/dns/hostnames", tags=["DNS"])
async def get_hostnames(api_key: APIKey = Depends(auth.get_api_key)):
return{
'hostnames': json.loads(dns.adblockhostnames())
}
@app.get("/dns/upstream", tags=["DNS"])
async def get_upstream(api_key: APIKey = Depends(auth.get_api_key)):
return{
'upstream_nameserver': dns.upstream_nameserver()
}
@app.get("/dns/logs", tags=["DNS"])
async def get_dnsmasq_logs(api_key: APIKey = Depends(auth.get_api_key)):
return(dns.dnsmasq_logs())
@app.get("/ddns", tags=["DDNS"])
async def get_ddns(api_key: APIKey = Depends(auth.get_api_key)):
return{
'use': ddns.use(),
'method': ddns.method(),
'protocol': ddns.protocol(),
'server': ddns.server(),
'login': ddns.login(),
'password': ddns.password(),
'domain': ddns.domain()
}
@app.get("/firewall", tags=["Firewall"])
async def get_firewall(api_key: APIKey = Depends(auth.get_api_key)):
return json.loads(firewall.firewall_rules())
@app.get("/networking", tags=["Networking"])
async def get_networking(api_key: APIKey = Depends(auth.get_api_key)):
return{
'interfaces': json.loads(networking.interfaces()),
'throughput': json.loads(networking.throughput())
}
@app.get("/openvpn", tags=["OpenVPN"])
async def get_openvpn(api_key: APIKey = Depends(auth.get_api_key)):
return{
'client_configs': openvpn.client_configs(),
'client_config_names': openvpn.client_config_names(),
'client_config_active': openvpn.client_config_active(),
'client_login_names': openvpn.client_login_names(),
'client_login_active': openvpn.client_login_active()
}
@app.get("/openvpn/{config}", tags=["OpenVPN"])
async def client_config_list(config, api_key: APIKey = Depends(auth.get_api_key)):
return{
'client_config': openvpn.client_config_list(config)
}
@app.get("/wireguard", tags=["WireGuard"])
async def get_wireguard(api_key: APIKey = Depends(auth.get_api_key)):
return{
'client_configs': wireguard.configs(),
'client_config_names': wireguard.client_config_names(),
'client_config_active': wireguard.client_config_active()
}

View file

@ -1,64 +0,0 @@
import subprocess
import json
def driver():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep driver= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def ctrl_interface():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ctrl_interface= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()
def ctrl_interface_group():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ctrl_interface_group= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def auth_algs():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep auth_algs= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def wpa_key_mgmt():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_key_mgmt= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def beacon_int():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep beacon_int= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def ssid():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ssid= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()
def channel():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep channel= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def hw_mode():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep hw_mode= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def ieee80211n():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ieee80211n= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def wpa_passphrase():
return subprocess.run("sed -En 's/wpa_passphrase=(.*)/\1/p' /etc/hostapd/hostapd.conf", shell=True, capture_output=True, text=True).stdout.strip()
def interface():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep interface= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()
def wpa():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def wpa_pairwise():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_pairwise= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def country_code():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep country_code= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def ignore_broadcast_ssid():
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ignore_broadcast_ssid= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def logging():
log_output = subprocess.run(f"cat /tmp/hostapd.log", shell=True, capture_output=True, text=True).stdout.strip()
logs = {}
for line in log_output.split('\n'):
parts = line.split(': ')
if len(parts) >= 2:
interface, message = parts[0], parts[1]
if interface not in logs:
logs[interface] = []
logs[interface].append(message)
return json.dumps(logs, indent=2)

View file

@ -1,38 +0,0 @@
import subprocess
import json
def get_active_clients_amount(interface):
arp_output = subprocess.run(['arp', '-i', interface], capture_output=True, text=True)
mac_addresses = arp_output.stdout.splitlines()
if mac_addresses:
grep_pattern = '|'.join(mac_addresses)
output = subprocess.run(['grep', '-iwE', grep_pattern, '/var/lib/misc/dnsmasq.leases'], capture_output=True, text=True)
return len(output.stdout.splitlines())
else:
return 0
def get_active_clients(interface):
arp_output = subprocess.run(['arp', '-i', interface], capture_output=True, text=True)
arp_mac_addresses = set(line.split()[2] for line in arp_output.stdout.splitlines()[1:])
dnsmasq_output = subprocess.run(['cat', '/var/lib/misc/dnsmasq.leases'], capture_output=True, text=True)
active_clients = []
for line in dnsmasq_output.stdout.splitlines():
fields = line.split()
mac_address = fields[1]
if mac_address in arp_mac_addresses:
client_data = {
"timestamp": int(fields[0]),
"mac_address": fields[1],
"ip_address": fields[2],
"hostname": fields[3],
"client_id": fields[4],
}
active_clients.append(client_data)
json_output = json.dumps(active_clients, indent=2)
return json_output

View file

@ -1,24 +0,0 @@
import subprocess
def use():
return subprocess.run("cat /etc/ddclient.conf | grep use= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def method():
#get the contents of the line below "use="
return subprocess.run("awk '/^use=/ {getline; print}' /etc/ddclient.conf | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def protocol():
return subprocess.run("cat /etc/ddclient.conf | grep protocol= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def server():
return subprocess.run("cat /etc/ddclient.conf | grep server= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def login():
return subprocess.run("cat /etc/ddclient.conf | grep login= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def password():
return subprocess.run("cat /etc/ddclient.conf | grep password= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def domain():
#get the contents of the line below "password="
return subprocess.run("awk '/^password=/ {getline; print}' /etc/ddclient.conf", shell=True, capture_output=True, text=True).stdout.strip()

View file

@ -1,30 +0,0 @@
import subprocess
import json
def range_start():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f1", shell=True, capture_output=True, text=True).stdout.strip()
def range_end():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def range_subnet_mask():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f3", shell=True, capture_output=True, text=True).stdout.strip()
def range_lease_time():
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f4", shell=True, capture_output=True, text=True).stdout.strip()
def range_gateway():
return subprocess.run("cat /etc/dhcpcd.conf | grep routers | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
def range_nameservers():
output = subprocess.run("cat /etc/dhcpcd.conf", shell=True, capture_output=True, text=True).stdout.strip()
nameservers = []
lines = output.split('\n')
for line in lines:
if "static domain_name_server" in line:
servers = line.split('=')[1].strip().split()
nameservers.extend(servers)
return nameservers

View file

@ -1,38 +0,0 @@
import subprocess
import json
def adblockdomains():
output = subprocess.run("cat /etc/raspap/adblock/domains.txt", shell=True, capture_output=True, text=True).stdout.strip()
domains =output.split('\n')
domainlist=[]
for domain in domains:
if domain.startswith('#') or domain=="":
continue
domainlist.append(domain.split('=/')[1])
return domainlist
def adblockhostnames():
output = subprocess.run("cat /etc/raspap/adblock/hostnames.txt", shell=True, capture_output=True, text=True).stdout.strip()
hostnames = output.split('\n')
hostnamelist=[]
for hostname in hostnames:
if hostname.startswith('#') or hostname=="":
continue
hostnamelist.append(hostname.replace('0.0.0.0 ',''))
return hostnamelist
def upstream_nameserver():
return subprocess.run("awk '/nameserver/ {print $2}' /run/dnsmasq/resolv.conf", shell=True, capture_output=True, text=True).stdout.strip()
def dnsmasq_logs():
output = subprocess.run("cat /var/log/dnsmasq.log", shell=True, capture_output=True, text=True).stdout.strip()
log_entries = []
for line in output.split("\n"):
fields = line.split(" ")
log_dict = {
'timestamp': ' '.join(fields[:3]),
'process': fields[3][:-1], # Remove the trailing colon
'message': ' '.join(fields[4:]),
}
log_entries.append(log_dict)
return log_entries

View file

@ -1,4 +0,0 @@
import subprocess
def firewall_rules():
return subprocess.run("cat /etc/raspap/networking/firewall/iptables_rules.json", shell=True, capture_output=True, text=True).stdout.strip()

View file

@ -1,68 +0,0 @@
import psutil
import json
def throughput():
interface_info = {}
# Get network interfaces
interfaces = psutil.net_if_stats()
for interface, stats in interfaces.items():
if interface.startswith("lo") or interface.startswith("docker"):
# Skip loopback and docker interface
continue
try:
# Get network traffic statistics
traffic_stats = psutil.net_io_counters(pernic=True)[interface]
rx_packets = traffic_stats[1]
rx_bytes = traffic_stats[0]
tx_packets = traffic_stats[3]
tx_bytes = traffic_stats[4]
interface_info[interface] = {
"RX_packets": rx_packets,
"RX_bytes": rx_bytes,
"TX_packets": tx_packets,
"TX_bytes": tx_bytes
}
except KeyError:
# Handle the case where network interface statistics are not available
pass
return json.dumps(interface_info, indent=2)
def interfaces():
interface_info = {}
# Get network interfaces
interfaces = psutil.net_if_addrs()
for interface, addrs in interfaces.items():
if interface.startswith("lo") or interface.startswith("docker"):
# Skip loopback and docker interface
continue
ip_address = None
netmask = None
mac_address = None
for addr in addrs:
if addr.family == 2: # AF_INET corresponds to the integer value 2
# IPv4 address
ip_address = addr.address
netmask = addr.netmask
# Get MAC address
for addr in psutil.net_if_addrs().get(interface, []):
if addr.family == psutil.AF_LINK:
mac_address = addr.address
interface_info[interface] = {
"IP_address": ip_address,
"Netmask": netmask,
"MAC_address": mac_address
}
return json.dumps(interface_info, indent=2)
#TODO: migrate to vnstat, to lose psutil dependency

View file

@ -1,41 +0,0 @@
import subprocess
def client_configs():
return subprocess.run("find /etc/openvpn/client/ -type f | wc -l", shell=True, capture_output=True, text=True).stdout.strip()
def client_config_names():
config_names_list = []
output = subprocess.run('''ls /etc/openvpn/client/ | grep -v "^client.conf$"''', shell=True, capture_output=True, text=True).stdout.strip()
lines = output.split("\n")
for client in lines:
if "_client" in client:
config_names_dict ={'config':client}
config_names_list.append(config_names_dict)
return config_names_list
def client_login_names():
config_names_list = []
output = subprocess.run('''ls /etc/openvpn/client/ | grep -v "^client.conf$"''', shell=True, capture_output=True, text=True).stdout.strip()
lines = output.split("\n")
for client in lines:
if "_login" in client:
config_names_dict ={'login':client}
config_names_list.append(config_names_dict)
return config_names_list
def client_config_active():
output = subprocess.run('''ls -al /etc/openvpn/client/ | grep "client.conf -"''', shell=True, capture_output=True, text=True).stdout.strip()
active_config = output.split("/etc/openvpn/client/")
return(active_config[1])
def client_login_active():
output = subprocess.run('''ls -al /etc/openvpn/client/ | grep "login.conf -"''', shell=True, capture_output=True, text=True).stdout.strip()
active_config = output.split("/etc/openvpn/client/")
return(active_config[1])
def client_config_list(client_config):
output = subprocess.run(["cat", f"/etc/openvpn/client/{client_config}"], capture_output=True, text=True).stdout.strip()
return output.split('\n')
#TODO: where is the logfile??
#TODO: is service connected?

View file

@ -1,86 +0,0 @@
import subprocess
revisions = {
'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': 'Model 4B Revision 1.1 (1 GB)',
'b03111': 'Model 4B Revision 1.1 (2 GB)',
'c03111': 'Model 4B Revision 1.1 (4 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': 'Pi 5 (4 GB)',
'd04170': 'Pi 5 (8 GB)'
}
def hostname():
return subprocess.run("hostname", shell=True, capture_output=True, text=True).stdout.strip()
def uptime():
return subprocess.run("uptime -p", shell=True, capture_output=True, text=True).stdout.strip()
def systime():
return subprocess.run("date", shell=True, capture_output=True, text=True).stdout.strip()
def usedMemory():
return round(float(subprocess.run("free -m | awk 'NR==2{total=$2 ; used=$3 } END { print used/total*100}'", shell=True, capture_output=True, text=True).stdout.strip()),2)
def processorCount():
return int(subprocess.run("nproc --all", shell=True, capture_output=True, text=True).stdout.strip())
def LoadAvg1Min():
return round(float(subprocess.run("awk '{print $1}' /proc/loadavg", shell=True, capture_output=True, text=True).stdout.strip()),2)
def systemLoadPercentage():
return round((float(LoadAvg1Min())*100)/float(processorCount()),2)
def systemTemperature():
try:
output = subprocess.run("cat /sys/class/thermal/thermal_zone0/temp", shell=True, capture_output=True, text=True).stdout.strip()
return round(float(output)/1000,2)
except ValueError:
return 0
def hostapdStatus():
return int(subprocess.run("pidof hostapd | wc -l", shell=True, capture_output=True, text=True).stdout.strip())
def operatingSystem():
return subprocess.run('''grep PRETTY_NAME /etc/os-release | cut -d= -f2- | sed 's/"//g' ''', shell=True, capture_output=True, text=True).stdout.strip()
def kernelVersion():
return subprocess.run("uname -r", shell=True, capture_output=True, text=True).stdout.strip()
def rpiRevision():
output = subprocess.run("grep Revision /proc/cpuinfo | awk '{print $3}'", shell=True, capture_output=True, text=True).stdout.strip()
try:
return revisions[output]
except KeyError:
return 'Unknown Device'

View file

@ -1,23 +0,0 @@
import subprocess
import re
def configs():
#ignore symlinks, because wg0.conf is in production the main config, but in insiders it is a symlink
return subprocess.run("find /etc/wireguard/ -type f | wc -l", shell=True, capture_output=True, text=True).stdout.strip()
def client_config_names():
config_names_list = []
output = subprocess.run('''ls /etc/wireguard/ | grep -v "^wg0.conf$"''', shell=True, capture_output=True, text=True).stdout.strip()
lines = output.split("\n")
for client in lines:
config_names_dict ={'config':client}
config_names_list.append(config_names_dict)
return config_names_list
def client_config_active():
output = subprocess.run('''ls -al /etc/wireguard/ | grep "wg0.conf -"''', shell=True, capture_output=True, text=True).stdout.strip()
active_config = output.split("/etc/wireguard/")
return(active_config[1])
#TODO: where is the logfile??
#TODO: is service connected?

View file

@ -1,5 +0,0 @@
fastapi==0.109.1
uvicorn==0.25.0
psutil==5.9.8
python-dotenv==1.0.1

View file

@ -6,42 +6,24 @@ Description: Classes shared by all themes
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;
/* Small devices (portrait phones, up to 576px) */
@media (max-width: 576px) {
.container-fluid, .card-body, .col-md-6 { padding-left: 0.5rem; padding-right: 0.5rem; }
.card .card-header { padding: .75rem .5rem; font-size: 1.0rem; }
.row { margin-left: 0rem; margin-right: 0rem; }
.col-lg-12 { padding-right: 0.25rem; padding-left: 0.25rem; }
.form-group.col-md-6 { margin-left: -0.5rem; }
h4.mt-3 { margin-left: 0.5rem; }
}
a {
color: var(--raspap-brand-color);
text-decoration: none;
}
.sb-sidenav .sb-sidenav-menu .nav .nav-link {
padding-top: 0.6rem;
padding-bottom: 0.6rem;
}
.sb-sidenav-light .sb-sidenav-menu .nav-link, .card-title, h4 {
color: var(--raspap-content-main);
}
.sb-topnav.navbar-light #sidebarToggle {
color: var(--raspap-content-main);
}
.sidebar-brand-text {
text-transform: none;
color: #212529;
font-size: 2.0rem;
font-weight: 500;
font-family: Helvetica, Arial, sans-serif;
}
th {
color: var(--raspap-content-main) !important;
}
.h-underlined {
border-bottom: 1px solid #e3e6f0;
padding-bottom: 0.3rem;
@ -60,7 +42,7 @@ th {
.info-item {
text-transform: uppercase;
font-size: 0.7em;
color: var(--raspap-text-muted);
color: #858796;
}
.info-value {
@ -70,11 +52,7 @@ th {
.info-item-xs {
font-size: 0.7rem;
margin-left: 0.75rem;
}
.sb-status {
margin-left: 0.75rem!important;
margin-left: 0.3rem;
}
.info-item-wifi {
@ -92,13 +70,13 @@ th {
}
.service-status-warn {
color: #ffbf00 !important;
color: #f6f044 !important;
}
.service-status-down {
color: #f80107 !important;
animation: flash 1s linear infinite;
}
@keyframes flash {
50% {
opacity: 0;
@ -110,8 +88,6 @@ th {
height: 20rem;
border: 1px solid #d1d3e2;
border-radius: .35rem;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace;
font-size: 0.8rem;
}
.dhcp-static-leases {
@ -124,31 +100,10 @@ th {
margin-bottom: 0.5em;
}
#wifiClientContent #wpaConf {
min-height: calc(100vh / 3);
}
.loading-spinner::before {
position: absolute;
top: 0;
left: 0;
.loading-spinner {
background: url("../../app/img/loading-spinner.gif") no-repeat scroll center center transparent;
min-height: 450px;
width: 100%;
height: calc(100vh / 4);
display: flex;
justify-content: center;
align-items: center;
color: var(--raspap-text-muted);
content: "\f1ce"; /* Unicode for the circle-notch icon */
font-family: "Font Awesome 5 Free";
font-weight: 900; /* Adjust as needed */
font-size: 54px; /* Adjust icon size as needed */
animation: spin 1.2s linear infinite;
width: 100%;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (min-width: 576px) {
@ -169,7 +124,12 @@ th {
}
canvas#divDBChartBandwidthhourly {
height: 509px!important;
height: 350px!important;
}
.chart-container {
height: 150px;
width: 200px;
}
.dbChart {
@ -186,7 +146,7 @@ canvas#divDBChartBandwidthhourly {
}
.check-progress {
color: var(--raspap-text-light);
color: #999;
}
.fa-check {
@ -214,6 +174,7 @@ button.btn.btn-light.js-toggle-password {
width: 4px;
border-radius: 1px;
opacity: 30%;
background: <?php echo $color; ?>;
}
.signal-icon .signal-bar:nth-child(1) { height: 40%; }
@ -260,404 +221,10 @@ button.btn.btn-light.js-toggle-password {
}
figcaption.figure-caption a {
color: var(--raspap-text-muted);
color: #858796;
}
button > i.fas {
pointer-events: none;
}
.close {
font-weight: 400;
font-size: 1.3rem;
}
.was-validated .form-control:valid,
.was-validated .form-control:invalid {
background-position: center right calc(.375em + .4875rem);
}
.was-validated .form-control:invalid {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
}
.was-validated .form-control:valid {
background-size: calc(0.6em + 0.375rem) calc(0.6em + 0.375rem);
}
.input-group>.input-group-append:not(:last-child)>.btn {
border-top-right-radius: 0.35rem;
border-bottom-right-radius: 0.35rem;
}
.nav-user {
position: relative;
bottom: 11px;
right: 3px;
}
.nav-item {
font-size: 0.85rem;
}
/* Font Awesome 5 brands */
.fa-reddit {
color: #ff4500;
}
.fa-twitter {
color: #55acee
}
.fa-discord {
color: #7289da
}
.fa-github {
color: #151b23
}
@keyframes heart {
0%, 40%, 80%, 100% {
transform: scale(1);
}
20%, 60% {
transform: scale(1.15);
}
}
.heart {
color: #e63946;
animation: heart 1000ms infinite;
}
#modal-admin-login .modal-content {
background: radial-gradient(circle at 120% -20%, #032626, #052c2c, #073232, #0a3838, #0d3f3f, #114545, #144c4c);
align-items: center;
}
#modal-admin-login .modal-body {
min-width: 330px;
}
.login-brand {
color: var(--raspap-theme-color);
filter: brightness(150%);
}
.admin-login {
color: var(--raspap-offwhite);
font-size: 1.2em
}
.btn-admin-login {
color: var(--raspap-offwhite);
background-color: var(--raspap-theme-color);
}
.btn-admin-login:hover {
color: var(--raspap-offwhite);
background-color: #236969;
}
.no-right-radius {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.btn-passwd-append {
border: 1px solid #ced4da;
}
#passwd-toggle:active,
#passwd-toggle:hover,
#passwd-toggle:focus {
border: 1px solid #ced4da;
}
textarea.plugin-log {
width: 100%;
height: 150px;
resize: none;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 0.5rem;
background-color: #f8f9fa;
font-family: monospace;
font-size: 0.9rem;
}
.card-wrapper {
margin: 1rem;
}
.dashboard-container {
display: flex;
position: relative;
width: 100%;
min-height: 400px;
padding: 2rem;
}
.connections-left,
.connections-right {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.connection-item {
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
z-index: 5;
color: var(--raspap-text-light);
}
.connection-right {
align-items: center;
margin-left: 10rem;
}
.connections-left i {
height: 40px;
display: flex;
align-items: center;
justify-content: left;
}
.connections-left i:first-child {
margin-top: 0;
}
.connections-left i:last-child {
margin-bottom: 0;
margin-left: 0.5rem;
}
.center-device {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
z-index: 1;
}
.center-device-top {
margin-bottom: 30px;
}
.client-group {
display: flex;
align-items: center;
flex-direction: row-reverse;
gap: 0.5rem;
}
.client-count {
text-align: right;
}
.clients-status {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
padding-right: 1rem;
}
.dashed-lines,
.solid-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
padding: 1rem;
left: 112px;
}
.dashed-lines-right,
.solid-lines-right {
left: -80px;
}
.solid-lines, .solid-lines-right {
z-index: 3;
}
.dashed-lines, .dashed-lines-right {
z-index 0;
}
.device-status {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
margin: 0.8rem 0;
}
.wifi-bands {
display: flex;
gap: 0.5rem;
}
.band {
padding: 0.25rem 1rem;
border: 2px solid var(--raspap-text-light);
border-radius: 4px;
background: transparent;
font-weight: 600;
color: var(--raspap-text-light);
}
.band.active {
border-color: var(--raspap-theme-color);
color: var(--raspap-theme-color);
}
.device-label {
font-size: 1.3rem;
text-align: center;
color: var(--raspap-theme-color);
margin-top: 1rem;
}
.status-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.3rem;
color: var(--raspap-text-light);
}
.bottom {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.3rem;
width: 100%;
}
.status-item .fa-stack {
width: 1.5em!important;
}
.connection-item>i {
color: var(--raspap-text-light);
}
.connection-item .fa-stack {
min-width: 2.5em;
}
.connections-left>.connection-item>span {
color: var(--raspap-text-light);
margin-right: 0.5rem;
}
.inactive {
color: var(--raspap-text-light)!important;
}
a.inactive:hover,
a.inactive:focus {
color: var(--raspap-text-light) !important;
}
@media (max-width: 1200px) {
.connection-item a > span:not(.fa-stack) {
display: none!important;
}
}
@media (max-width: 991px) {
.connections-right,
.connections-left {
display: none!important;
}
.dashboard-container {
width: auto;
padding: 0;
}
.device-status {
gap: 0.5rem;
}
.clients-mobile {
display: flex!important;
flex-direction: row!important;
}
}
.connection-item.active > span {
color: var(--raspap-theme-color)!important;
}
.connection-item.active > i {
color: var(--raspap-theme-color)!important;
}
.status-item.active > span {
color: var(--raspap-theme-color)!important;
}
.status-item.active > i {
color: var(--raspap-theme-color)!important;
}
.clients-mobile {
display: none;
flex-direction: column;
gap: 1rem;
margin-top: 2rem;
}
.client-type {
position: relative;
display: inline-flex;
align-items: center;
gap: 1rem;
}
.client-type i {
font-size: 1.5rem;
color: var(--raspap-theme-color);
width: 45px;
height: 45px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--raspap-theme-color);
}
.client-type i.badge-icon {
font-size: 0.7rem;
background: var(--raspap-theme-color);
color: var(--raspap-offwhite);
width: 20px;
height: 20px;
border: none;
}
.client-count {
position: absolute;
top: -5px;
right: -5px;
background: var(--raspap-theme-color);
color: var(--raspap-offwhite);
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
}
.device-illustration {
min-width: 220px;
max-width: 250px;
}

View file

@ -3,6 +3,7 @@
require_once '../../includes/functions.php';
$color = getColorOpt();
?>
/*
Theme Name: RaspAP default
Author: @billz
@ -13,60 +14,22 @@ License: GNU General Public License v3.0
@import url('all.css');
:root {
--raspap-theme-color: <?php echo $color; ?>;
--raspap-theme-lighter: <?php echo lightenColor($color, 20); ?>;
--raspap-theme-darker: <?php echo darkenColor($color, 20); ?>;
}
body {
color: #212529;
background-color: #f8f9fc;
}
a {
color: var(--raspap-theme-color);
text-decoration: none;
}
a:focus, a:hover {
color: var(--raspap-theme-darker);
}
.sb-sidenav-light .sb-sidenav-menu .nav-link:hover {
color: var(--raspap-theme-color);
}
.sidebar-brand-text:focus,
.sidebar-brand-text:hover {
color: var(--raspap-theme-darker);
}
.form-check-input:checked {
background-color: var(--raspap-theme-color);
border-color: var(--raspap-theme-color);
}
.sidebar {
background-color: #f8f9fc;
}
.sb-nav-link-icon.active {
font-weight: 600;
}
.sidebar .nav-item.active .nav-link {
font-weight: 500;
}
.sidebar-brand-text {
color: var(--raspap-theme-color);
}
.card .card-header, .modal-header {
border-color: var(--raspap-theme-color);
border-color: <?php echo $color; ?>;
color: #fff;
background-color: var(--raspap-theme-color);
background-color: <?php echo $color; ?>;
}
.modal-header {
@ -74,25 +37,19 @@ a:focus, a:hover {
}
.btn-primary {
color: var(--raspap-theme-color);
border-color: var(--raspap-theme-color);
color: <?php echo $color; ?>;
border-color: <?php echo $color; ?>;
background-color: #fff;
}
.btn-primary:disabled {
color: var(--raspap-theme-color) !important;
border-color: var(--raspap-theme-color) !important;
background-color: #fff !important;
}
.card-body {
color: #495057;
}
.card-footer, .modal-footer {
background-color: #f2f1f0;
}
.nav-item {
font-size: 0.85rem;
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-link {
font-size: 1.0rem;
@ -110,6 +67,10 @@ a.nav-link.active {
padding: 0.6rem 0.6rem 0.6rem 1.0rem;
}
.alert-success {
background-color: #d4edda;
}
.btn-primary {
background-color: #fff;
}
@ -119,8 +80,8 @@ a.nav-link.active {
}
.btn-primary:hover {
background-color: var(--raspap-theme-color);
border-color: var(--raspap-theme-color);
background-color: <?php echo $color; ?>;
border-color: <?php echo $color; ?>;
}
i.fa.fa-bars {
@ -149,6 +110,6 @@ pre.unstyled {
}
.signal-icon .signal-bar {
background: var(--raspap-theme-color);
background: <?php echo $color; ?>;
}

View file

@ -1,83 +0,0 @@
/*
Theme Name: Lights Out
Author: @billz
Author URI: https://github.com/billz
Description: A Bootstrap dark mode theme for RaspAP
License: GNU General Public License v3.0
*/
@import url('custom.php');
html[data-bs-theme="dark"] {
background-color: var(--bs-dark);
color: var(--bs-light);
}
html[data-bs-theme="dark"] body,
html[data-bs-theme="dark"] footer,
html[data-bs-theme="dark"] .sb-sidenav,
html[data-bs-theme="dark"] .sb-topnav,
html[data-bs-theme="dark"] .card,
html[data-bs-theme="dark"] .card-footer {
background-color: var(--bs-dark);
}
html[data-bs-theme="dark"] .card-body,
html[data-bs-theme="dark"] .card-footer,
html[data-bs-theme="dark"] .info-item-xs,
html[data-bs-theme="dark"] .table>:not(caption)>*>* {
color: var(--bs-secondary);
}
html[data-bs-theme="dark"] .sb-topnav.navbar {
background-color: var(--bs-dark) !important;
color: var(--bs-light);
}
html[data-bs-theme="dark"] .sb-topnav.navbar,
html[data-bs-theme="dark"] .sb-topnav.navbar a {
color: var(--raspap-theme-color) !important;
}
html[data-bs-theme="dark"] .sb-topnav.navbar a:hover,
html[data-bs-theme="dark"] .sb-topnav.navbar a:focus {
color: var(--raspap-theme-darker) !important;
}
html[data-bs-theme="dark"] .sb-topnav.navbar {
--bs-navbar-bg: var(--bs-dark);
}
html[data-bs-theme="dark"] .sb-topnav.navbar.navbar-light {
color: var(--bs-light);
}
html[data-bs-theme="dark"] .card {
border-color: var(--bs-secondary);
color: var(--bs-light);
}
html[data-bs-theme="dark"] .card-footer {
border-color: var(--bs-secondary);
}
html[data-bs-theme="dark"] .nav-tabs {
--bs-nav-tabs-link-active-color: var(--bs-light);
--bs-nav-tabs-link-active-bg: var(--bs-dark);
--bs-nav-tabs-link-active-border-color: var(--bs-secondary) var(--bs-secondary) var(--bs-dark);
--bs-nav-tabs-border-color: var(--bs-secondary);
--bs-nav-tabs-link-hover-border-color: var(--bs-secondary);
}
html[data-bs-theme="dark"] .btn {
color: var(--bs-gray-800);
opacity: 75%;
}
html[data-bs-theme="dark"] select,
html[data-bs-theme="dark"] .form-control {
background-color: var(--bs-dark);
border-color: var(--bs-secondary);
color: var(--bs-light);
}

View file

@ -35,12 +35,8 @@ h5.card-title {
color: #212529;
}
.sb-sidenav-menu, footer {
background-color: var(--bs-body-bg);
}
.card, .modal-dialog {
border-radius: 5px;
border-radius: 3px;
border-color: #ff6600;
}
@ -79,20 +75,8 @@ h5.card-title {
ul.nav-tabs, .nav-tabs .nav-link {
background-color: #f6f6ef;
border-bottom: 1px solid #dddfeb;
color: #495057;
font-size: 1.0rem;
}
.form-switch .form-check-input:checked {
background-color: #ff6600;
border-color: #828282;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
.sidebar .nav-item .nav-link {
padding: 0.6rem;
margin-left: 0.6rem;
@ -102,14 +86,16 @@ code {
font-weight: 300;
}
.card-body {
background-color: #f6f6ef;
border-radius: 5px;
#wrapper,#page-wrapper,
#wrapper #content-wrapper,
.nav>li>a,.nav {
background-color: #fff;
}
th {
color: #495057 !important;
.card-body {
background-color: #f6f6ef;
}
.card-footer {
background-color: #eee;
}

360
app/css/lightsout.css Normal file
View file

@ -0,0 +1,360 @@
/*
Theme Name: Lights Out
Author: @billz
Author URI: https://github.com/billz
Description: A dark mode theme for RaspAP
License: GNU General Public License v3.0
*/
@import url('all.css');
html * {
font-family: Helvetica,Arial,sans-serif;
color: #afafaf;
}
h2 {
font-size: 2rem !important;
}
h4 {
font-size: 1.3rem;
}
h5.card-title {
font-size: 1.2rem;
}
.page-header {
border-left: .01rem solid #d2d2d2;
border-bottom: .01rem solid #d2d2d2;
}
.sidebar-light .nav-item.active .nav-link i {
color: #d2d2d2;
}
.sidebar .nav-item.active .nav-link {
font-weight: 400;
}
#wrapper #content-wrapper #content {
background-color: #202020;
}
.topbar {
background-color: #202020;
}
.nav-tabs {
border-bottom: 1px solid #404040;
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-link {
font-size: 1.0rem;
}
.nav-tabs .nav-link:hover {
border-color: transparent;
}
.navbar-default .navbar-brand:hover {
color: #d2d2d2;
}
.navbar-default .navbar-toggle {
border-color: #d2d2d2;
}
.navbar-default .navbar-toggle .icon-bar {
background-color: #d2d2d2;
}
.navbar-default .navbar-toggle:focus,
.navbar-default .navbar-toggle:hover {
background-color: #202020;
}
#content, .navbar, .sidebar, .footer, .sticky-footer {
background-attachment: scroll;
background-repeat: repeat;
background-size: auto;
background-position: 0 0;
background-origin: padding-box;
background-clip: border-box;
}
.sticky-footer {
background-position: 30px 0;
}
.sidebar {
background-position: 0 20px;
}
.nav-tabs .nav-link.active {
color: #d2d2d2;
background-color: #141414;
border-color: #404040 #404040 #141414;
}
a:focus, a:hover {
color: #d2d2d2;
}
.card>.card-header, .modal-content, .modal-header {
border-color: #404040;
background-color: #202020;
color: #afafaf;
border-top-right-radius: 3px;
border-top-left-radius: 3px;
font-size: 1.0rem;
font-weight: 400;
}
.modal-body {
background-color: #141414;
}
.card>.card-header .fa {
color: #202020;
}
.card-header [class^="fa"] {
color: #afafaf;
font-size: 1.0rem;
}
.col {
color: #afafaf;
}
.card, .card-body {
border-color: #343434;
border-radius: 3px;
background-color: #141414;
}
hr {
border-top: .01rem solid #d2d2d2;
}
.sidebar-brand-text {
color: #2b8080 !important;
}
.ra-raspap:before {
color: #ac1b3d !important;
}
.sidebar-light #sidebarToggle {
background-color: #202020;
border: 1px solid #afafaf !important
}
.sidebar-light #sidebarToggle::after {
color: #afafaf;
}
.sidebar-light .nav-item .nav-link:hover i {
color: #d2d2d2;
}
.sidebar-light #sidebarToggle:hover {
background-color: #202020;
}
.sidebar.toggled .nav-item .nav-link span {
display: none;
}
.sidebar.toggled .nav-item .nav-link {
text-align: center;
padding: .6rem 1rem;
width: 6.5rem;
}
.card-footer, .modal-footer {
background-color: #202020;
border-top: 0px;
}
.modal-footer {
border-radius: 0.3rem;
}
.card>.card-header::before, .navbar-default::before {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 2;
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
.sidebar-light, .sticky-footer {
background-color: #202020;
}
.sidebar-light .nav-item .nav-link i {
color: rgba(230, 230, 230, .3);
}
.sidebar .nav-item .nav-link {
padding: 0.6rem;
padding-left: 1.2rem;
}
.sidebar-light hr.sidebar-divider {
border-top: 1px solid #404040;
padding-top: 0.5rem;
}
.sidebar .nav-item .nav-link span {
font-size: 1.0rem;
}
.topbar .topbar-divider {
border-right: 1px solid #404040;
}
.label-warning {
background-color: #d2d2d2;
}
span.label.label-warning {
color: #202020;
}
.table>tbody>tr>td,
.table>tbody>tr>th,
.table>tfoot>tr>td,
.table>tfoot>tr>th,
.table>thead>tr>td,
.table>thead>tr>th {
background-color: #202020;
border-top: .01rem solid #202020;
}
.table>thead>tr>th {
vertical-align: bottom;
border-bottom: .01rem solid #d2d2d2;
}
[class*="btn"], [class*="btn"]:focus, [class*="btn"]:disabled {
background-color: #202020;
border-color: #404040;
border-radius: 3px;
color: #d2d2d2;
}
[class*="btn"]:hover {
border-radius: 3px;
color: #d2d2d2;
background-color: #202020;
border-color: #afafaf;
}
[class*="btn"]:hover .disabled {
background-color:red;
}
[class*="alert"] {
border-radius: .35rem;
color: #d2d2d2;
background-color: #202020;
border: 1px solid #404040;
}
.close {
font-size: 1.2em;
font-weight: 400;
text-shadow: none;
color: #d2d2d2;
}
.form-control,
.form-control:focus,
.custom-select {
color: #d2d2d2;
background-color: #202020;
border: 1px solid #404040;
border-radius: 3px;
}
.form-control:disabled,
.form-control[readonly] {
background-color: #202020;
opacity: 0.5;
}
.form-control::-webkit-input-placeholder { color: #d2d2d2; }
.form-control:-moz-placeholder { color: #d2d2d2; }
.form-control::-moz-placeholder { color: #d2d2d2; }
.form-control:-ms-input-placeholder { color: #d2d2d2; }
.form-control::-ms-input-placeholder { color: #d2d2d2; }
input[type="text"]{
color: #d2d2d2 !important
}
.progress {
background-color: #202020;
border-radius: 0px;
}
.progress-bar {
color: #202020;
}
.progress-bar.progress-bar-info.progress-bar-striped.active {
background-color: #d2d2d2;
}
.figure-img {
filter: opacity(0.7);
}
.ra-wireguard:before {
color: #404040 !important;
}
.ra-wireguard:hover:before {
color: #d1d3e2 !important;
}
.sidebar .nav-item.active .nav-link span.ra-wireguard:before {
color: #d2d2d2 !important;
}
.logoutput {
background-color: #202020;
border-color: #404040;
}
.text-muted {
font-size: 0.8rem;
}
.fas.fa-circle {
font-size: 0.7rem;
}
pre {
background-color: #202020;
border: #202020;
}
button.btn.btn-light.js-toggle-password {
border: 1px solid #343434;
}
.signal-icon .signal-bar {
background: #2b8080;
}

View file

@ -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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

BIN
app/img/loading-spinner.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

0
app/img/raspAP-logo.php Executable file → Normal file
View file

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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;

3
app/img/wg-qr-code.php Executable file → Normal file
View file

@ -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);

8
app/img/wifi-qr-code.php Executable file → Normal file
View file

@ -12,12 +12,12 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
// assume WPA encryption and get the passphrase
// assume wpa encryption and get the passphrase
$type = "WPA";
$password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase'];
// use WEP if configured
$wep_default_key = intval($hostapd['wep_default_key'] ?? 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);

View file

@ -3,7 +3,7 @@ function msgShow(retcode,msg) {
} else if(retcode == 2 || retcode == 1) {
var alertType = 'danger';
}
var htmlMsg = '<div class="alert alert-'+alertType+' alert-dismissible" role="alert"><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>'+msg+'</div>';
var htmlMsg = '<div class="alert alert-'+alertType+' alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>'+msg+'</div>';
return htmlMsg;
}
@ -18,13 +18,13 @@ function createNetmaskAddr(bitCount) {
}
function loadSummary(strInterface) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/networking/get_ip_summary.php',{'interface': strInterface, 'csrf_token': csrfToken},function(data){
$.post('ajax/networking/get_ip_summary.php',{interface:strInterface},function(data){
jsonData = JSON.parse(data);
console.log(jsonData);
if(jsonData['return'] == 0) {
$('#'+strInterface+'-summary').html(jsonData['output'].join('<br />'));
} else if(jsonData['return'] == 2) {
$('#'+strInterface+'-summary').append('<div class="alert alert-danger alert-dismissible" role="alert"><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>'+jsonData['output'].join('<br />')+'</div>');
$('#'+strInterface+'-summary').append('<div class="alert alert-danger alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>'+jsonData['output'].join('<br />')+'</div>');
}
});
}
@ -39,7 +39,7 @@ function getAllInterfaces() {
}
function setupTabs() {
$('a[data-bs-toggle="tab"]').on('shown.bs.tab',function(e){
$('a[data-toggle="tab"]').on('shown.bs.tab',function(e){
var target = $(e.target).attr('href');
if(!target.match('summary')) {
var int = target.replace("#","");
@ -122,29 +122,22 @@ $(document).on("click", "#gen_wpa_passphrase", function(e) {
$('#txtwpapassphrase').val(genPassword(63));
});
$(document).on("click", "#gen_apikey", function(e) {
$('#txtapikey').val(genPassword(32).toLowerCase());
});
$(document).on("click", "#js-clearhostapd-log", function(e) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log', 'csrf_token': csrfToken},function(data){
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log'},function(data){
jsonData = JSON.parse(data);
$("#hostapd-log").val("");
});
});
$(document).on("click", "#js-cleardnsmasq-log", function(e) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/logging/clearlog.php?',{'logfile':'/var/log/dnsmasq.log', 'csrf_token': csrfToken},function(data){
$.post('ajax/logging/clearlog.php?',{'logfile':'/var/log/dnsmasq.log'},function(data){
jsonData = JSON.parse(data);
$("#dnsmasq-log").val("");
});
});
$(document).on("click", "#js-clearopenvpn-log", function(e) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/openvpn.log', 'csrf_token': csrfToken},function(data){
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/openvpn.log'},function(data){
jsonData = JSON.parse(data);
$("#openvpn-log").val("");
});
@ -153,7 +146,7 @@ $(document).on("click", "#js-clearopenvpn-log", function(e) {
// Enable Bootstrap tooltips
$(function () {
$('[data-bs-toggle="tooltip"]').tooltip()
$('[data-toggle="tooltip"]').tooltip()
})
function genPassword(pwdLen) {
@ -189,7 +182,7 @@ function contentLoaded() {
setupBtns();
break;
case "hostapd_conf":
getChannel();
loadChannel();
setHardwareModeTooltip();
break;
case "dhcpd_conf":
@ -216,8 +209,7 @@ Option toggles are set dynamically depending on the loaded configuration
*/
function loadInterfaceDHCPSelect() {
var strInterface = $('#cbxdhcpiface').val();
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/networking/get_netcfg.php', {'iface' : strInterface, 'csrf_token': csrfToken}, function(data){
$.get('ajax/networking/get_netcfg.php?iface='+strInterface,function(data){
jsonData = JSON.parse(data);
$('#dhcp-iface')[0].checked = jsonData.DHCPEnabled;
$('#txtipaddress').val(jsonData.StaticIP);
@ -225,12 +217,6 @@ function loadInterfaceDHCPSelect() {
$('#txtgateway').val(jsonData.StaticRouters);
$('#chkfallback')[0].checked = jsonData.FallbackEnabled;
$('#default-route').prop('checked', jsonData.DefaultRoute);
if (strInterface.startsWith("wl")) {
$('#nohook-wpa-supplicant').parent().parent().parent().show()
$('#nohook-wpa-supplicant').prop('checked', jsonData.NoHookWPASupplicant);
} else {
$('#nohook-wpa-supplicant').parent().parent().parent().hide()
}
$('#txtrangestart').val(jsonData.RangeStart);
$('#txtrangeend').val(jsonData.RangeEnd);
$('#txtrangeleasetime').val(jsonData.leaseTime);
@ -242,17 +228,18 @@ function loadInterfaceDHCPSelect() {
$('#txtmetric').val(jsonData.Metric);
if (jsonData.StaticIP !== null && jsonData.StaticIP !== '' && !jsonData.FallbackEnabled) {
$('#chkstatic').prop('checked', true).closest('.btn').addClass('active');
$('#chkdhcp').prop('checked', false).closest('.btn').removeClass('active');
$('#chkstatic').closest('.btn').button('toggle');
$('#chkstatic').closest('.btn').button('toggle').blur();
$('#chkstatic').blur();
$('#chkfallback').prop('disabled', true);
$('#dhcp-iface').removeAttr('disabled');
} else {
$('#chkdhcp').closest('.btn').addClass('active');
$('#chkdhcp').closest('.btn').button.blur();
$('#chkdhcp').closest('.btn').button('toggle');
$('#chkdhcp').closest('.btn').button('toggle').blur();
$('#chkdhcp').blur();
$('#chkfallback').prop('disabled', false);
}
if (jsonData.FallbackEnabled || $('#chkdhcp').is(':checked')) {
$('#dhcp-iface').prop('disabled', true);
setDhcpFieldsDisabled();
}
});
}
@ -263,126 +250,30 @@ function setDHCPToggles(state) {
}
if ($('#dhcp-iface').is(':checked') && !state) {
$('#dhcp-iface').prop('checked', state);
setDhcpFieldsDisabled();
}
$('#chkfallback').prop('disabled', state);
$('#dhcp-iface').prop('disabled', !state);
//$('#dhcp-iface').prop('checked', state);
}
$('#chkfallback').change(function() {
if ($('#chkfallback').is(':checked')) {
setStaticFieldsEnabled();
} else {
setStaticFieldsDisabled();
}
});
$('#debugModal').on('shown.bs.modal', function (e) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/system/sys_debug.php',{'csrf_token': csrfToken},function(data){
window.location.replace('/ajax/system/sys_get_logfile.php');
$('#debugModal').modal('hide');
});
});
$('#chkupdateModal').on('shown.bs.modal', function (e) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/system/sys_chk_update.php',{'csrf_token': csrfToken},function(data){
var response = JSON.parse(data);
var tag = response.tag;
var update = response.update;
var msg;
var msgUpdate = $('#msgUpdate').data('message');
var msgLatest = $('#msgLatest').data('message');
var msgInstall = $('#msgInstall').data('message');
var msgDismiss = $('#js-check-dismiss').data('message');
var faCheck = '<i class="fas fa-check ms-2"></i><br />';
$("#updateSync").removeClass("fa-spin");
if (update === true) {
msg = msgUpdate +' '+tag;
$("#msg-check-update").html(msg);
$("#msg-check-update").append(faCheck);
$("#msg-check-update").append("<p>"+msgInstall+"</p>");
$("#js-sys-check-update").removeClass("collapse");
} else {
msg = msgLatest;
dismiss = $("#js-check-dismiss");
$("#msg-check-update").html(msg);
$("#msg-check-update").append(faCheck);
$("#js-sys-check-update").remove();
dismiss.text(msgDismiss);
dismiss.removeClass("btn-outline-secondary");
dismiss.addClass("btn-primary");
}
});
});
$('#performUpdate').on('submit', function(event) {
event.preventDefault();
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/system/sys_perform_update.php',{
'csrf_token': csrfToken
})
$('#chkupdateModal').modal('hide');
$('#performupdateModal').modal('show');
});
$('#performupdateModal').on('shown.bs.modal', function (e) {
fetchUpdateResponse();
});
function fetchUpdateResponse() {
const complete = 6;
const error = 7;
let phpFile = 'ajax/system/sys_read_logfile.php';
$.ajax({
url: phpFile,
type: 'GET',
success: function(response) {
for (let i = 1; i <= 6; i++) {
let divId = '#updateStep' + i;
if (response.includes(i.toString())) {
$(divId).removeClass('invisible');
}
}
// check if the update is complete or if there's an error
if (response.includes(complete)) {
var successMsg = $('#successMsg').data('message');
$('#updateMsg').after('<span class="small">' + successMsg + '</span>');
$('#updateMsg').addClass('fa-check');
$('#updateMsg').removeClass('invisible');
$('#updateStep6').removeClass('invisible');
$('#updateSync2').removeClass("fa-spin");
$('#updateOk').removeAttr('disabled');
} else if (response.includes(error)) {
var errorMsg = $('#errorMsg').data('message');
$('#updateMsg').after('<span class="small">' + errorMsg + '</span>');
$('#updateMsg').addClass('fa-times');
$('#updateMsg').removeClass('invisible');
$('#updateSync2').removeClass("fa-spin");
$('#updateOk').removeAttr('disabled');
} else {
setTimeout(fetchUpdateResponse, 500);
}
},
error: function(xhr, status, error) {
console.error("AJAX Error:", error);
}
function loadChannel() {
$.get('ajax/networking/get_channel.php',function(data){
jsonData = JSON.parse(data);
loadChannelSelect(jsonData);
});
}
$('#hostapdModal').on('shown.bs.modal', function (e) {
var seconds = 3;
var pct = 0;
var seconds = 9;
var countDown = setInterval(function(){
if(seconds <= 0){
clearInterval(countDown);
}
var pct = Math.floor(100-(seconds*100/9));
document.getElementsByClassName('progress-bar').item(0).setAttribute('style','width:'+Number(pct)+'%');
seconds --;
pct = Math.floor(100-(seconds*100/4));
}, 500);
}, 1000);
});
$('#configureClientModal').on('shown.bs.modal', function (e) {
@ -390,8 +281,7 @@ $('#configureClientModal').on('shown.bs.modal', function (e) {
$('#ovpn-confirm-delete').on('click', '.btn-delete', function (e) {
var cfg_id = $(this).data('recordId');
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/openvpn/del_ovpncfg.php',{'cfg_id':cfg_id, 'csrf_token': csrfToken},function(data){
$.post('ajax/openvpn/del_ovpncfg.php',{'cfg_id':cfg_id},function(data){
jsonData = JSON.parse(data);
$("#ovpn-confirm-delete").modal('hide');
var row = $(document.getElementById("openvpn-client-row-" + cfg_id));
@ -408,8 +298,7 @@ $('#ovpn-confirm-delete').on('show.bs.modal', function (e) {
$('#ovpn-confirm-activate').on('click', '.btn-activate', function (e) {
var cfg_id = $(this).data('record-id');
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/openvpn/activate_ovpncfg.php',{'cfg_id':cfg_id, 'csrf_token': csrfToken},function(data){
$.post('ajax/openvpn/activate_ovpncfg.php',{'cfg_id':cfg_id},function(data){
jsonData = JSON.parse(data);
$("#ovpn-confirm-activate").modal('hide');
setTimeout(function(){
@ -433,196 +322,8 @@ $('#ovpn-userpw,#ovpn-certs').on('click', function (e) {
}
});
$('#js-system-reset-confirm').on('click', function (e) {
var progressText = $('#js-system-reset-confirm').attr('data-message');
var successHtml = $('#system-reset-message').attr('data-message');
var closeHtml = $('#js-system-reset-cancel').attr('data-message');
var csrfToken = $('meta[name=csrf_token]').attr('content');
var progressHtml = $('<div>').text(progressText).html() + '<i class="fas fa-cog fa-spin ms-2"></i>';
$('#system-reset-message').html(progressHtml);
$.post('ajax/networking/do_sys_reset.php?',{'csrf_token':csrfToken},function(data){
setTimeout(function(){
jsonData = JSON.parse(data);
if(jsonData['return'] == 0) {
$('#system-reset-message').text(successHtml);
} else {
$('#system-reset-message').text('Error occured: '+ jsonData['return']);
}
$("#js-system-reset-confirm").hide();
$("#js-system-reset-cancel").text(closeHtml);
},750);
});
});
$('#js-sys-reboot, #js-sys-shutdown').on('click', function (e) {
e.preventDefault();
var csrfToken = $('meta[name=csrf_token]').attr('content');
var action = $(this).data('action');
$.post('ajax/system/sys_actions.php?',{'a': action, 'csrf_token': csrfToken},function(data){
var response = JSON.parse(data);
});
});
$('#install-user-plugin').on('shown.bs.modal', function (e) {
var button = $(e.relatedTarget);
$(this).data('button', button);
var manifestData = button.data('plugin-manifest');
var installed = button.data('plugin-installed') || false;
var repoPublic = button.data('repo-public') || false;
var installPath = manifestData.install_path;
if (!installed && repoPublic && installPath === 'plugins-available') {
insidersHTML = 'Available with <i class="fas fa-heart heart me-1"></i><a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener">Insiders</a>';
$('#plugin-additional').html(insidersHTML);
} else {
$('#plugin-additional').empty();
}
if (manifestData) {
$('#plugin-docs').html(manifestData.plugin_docs
? `<a href="${manifestData.plugin_docs}" target="_blank">${manifestData.plugin_docs}</a>`
: 'Unknown');
$('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`);
$('#plugin-name').text(manifestData.name || 'Unknown');
$('#plugin-version').text(manifestData.version || 'Unknown');
$('#plugin-description').text(manifestData.description || 'No description provided');
$('#plugin-author').html(manifestData.author
? manifestData.author + (manifestData.author_uri
? ` (<a href="${manifestData.author_uri}" target="_blank">profile</a>)` : '') : 'Unknown');
$('#plugin-license').text(manifestData.license || 'Unknown');
$('#plugin-locale').text(manifestData.default_locale || 'Unknown');
$('#plugin-configuration').html(formatProperty(manifestData.configuration || 'None'));
$('#plugin-packages').html(formatProperty(manifestData.keys || 'None'));
$('#plugin-dependencies').html(formatProperty(manifestData.dependencies || 'None'));
$('#plugin-javascript').html(formatProperty(manifestData.javascript || 'None'));
$('#plugin-sudoers').html(formatProperty(manifestData.sudoers || 'None'));
$('#plugin-user-name').html((manifestData.user_nonprivileged && manifestData.user_nonprivileged.name) || 'None');
}
if (installed) {
$('#js-install-plugin-confirm').html('OK');
} else if (!installed && repoPublic && installPath == 'plugins-available') {
$('#js-install-plugin-confirm').html('Get Insiders');
} else {
$('#js-install-plugin-confirm').html('Install now');
}
});
$('#js-install-plugin-confirm').on('click', function (e) {
var button = $('#install-user-plugin').data('button');
var manifestData = button.data('plugin-manifest');
var installPath = manifestData.install_path;
var pluginUri = manifestData.plugin_uri;
var pluginVersion = manifestData.version;
var pluginConfirm = $('#js-install-plugin-confirm').text();
var progressText = $('#js-install-plugin-confirm').attr('data-message');
var successHtml = $('#plugin-install-message').attr('data-message');
var successText = $('<div>').text(successHtml).text();
var csrfToken = $('meta[name=csrf_token]').attr('content');
if (pluginConfirm === 'Install now') {
$("#install-user-plugin").modal('hide');
$("#install-plugin-progress").modal('show');
$.post(
'ajax/plugins/do_plugin_install.php',
{
'plugin_uri': pluginUri,
'plugin_version': pluginVersion,
'install_path': installPath,
'csrf_token': csrfToken
},
function (data) {
setTimeout(function () {
response = JSON.parse(data);
if (response === true) {
$('#plugin-install-message').contents().first().text(successText);
$('#plugin-install-message')
.find('i')
.removeClass('fas fa-cog fa-spin link-secondary')
.addClass('fas fa-check');
$('#js-install-plugin-ok').removeAttr("disabled");
} else {
const errorMessage = jsonData.error || 'An unknown error occurred.';
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
$('#plugin-install-message')
.contents()
.first()
.replaceWith('An error occurred installing the plugin:');
$('#plugin-install-message').append(errorLog);
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
$('#js-install-plugin-ok').removeAttr("disabled");
}
}, 200);
}
).fail(function (xhr) {
const jsonData = JSON.parse(xhr.responseText);
const errorMessage = jsonData.error || 'An unknown error occurred.';
$('#plugin-install-message')
.contents()
.first()
.replaceWith('An error occurred installing the plugin:');
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
$('#plugin-install-message').append(errorLog);
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
$('#js-install-plugin-ok').removeAttr("disabled");
});
} else if (pluginConfirm === 'Get Insiders') {
window.open('https://docs.raspap.com/insiders/', '_blank');
return;
} else if (pluginConfirm === 'OK') {
$("#install-user-plugin").modal('hide');
}
});
$('#js-install-plugin-ok').on('click', function (e) {
$("#install-plugin-progress").modal('hide');
window.location.reload();
});
function formatProperty(prop) {
if (Array.isArray(prop)) {
if (typeof prop[0] === 'object') {
return prop.map(item => {
return Object.entries(item)
.map(([key, value]) => `${key}: ${value}`)
.join('<br/>');
}).join('<br/><br/>');
}
return prop.map(line => `${line}<br/>`).join('');
}
if (typeof prop === 'object') {
return Object.entries(prop)
.map(([key, value]) => `${key}: ${value}`)
.join('<br/>');
}
return prop || 'None';
}
$(document).ready(function(){
$("#PanelManual").hide();
$('.ip_address').mask('0ZZ.0ZZ.0ZZ.0ZZ', {
translation: {
'Z': {
pattern: /[0-9]/, optional: true
}
},
placeholder: "___.___.___.___"
});
$('.date').mask('FF:FF:FF:FF:FF:FF', {
translation: {
"F": {
pattern: /[0-9a-z]/, optional: true
}
},
placeholder: "__:__:__:__:__:__"
});
});
$(document).ready(function() {
$('.cidr').mask('099.099.099.099/099', {
translation: {
'0': { pattern: /[0-9]/ }
},
placeholder: "___.___.___.___/___"
});
$("#PanelManual").hide();
});
$('#wg-upload,#wg-manual').on('click', function (e) {
@ -635,91 +336,68 @@ $('#wg-upload,#wg-manual').on('click', function (e) {
}
});
// Add the following code if you want the name of the file appear on select
$(".custom-file-input").on("change", function() {
var fileName = $(this).val().split("\\").pop();
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
});
// Retrieves the 'channel' value specified in hostapd.conf
function getChannel() {
$.get('ajax/networking/get_channel.php',function(data){
jsonData = JSON.parse(data);
loadChannelSelect(jsonData);
});
}
/*
Sets the wirelss channel select options based on frequencies reported by iw.
Sets the wirelss channel select options based on hw_mode and country_code.
See: https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git
Also: https://en.wikipedia.org/wiki/List_of_WLAN_channels
Methodology: In North America up to channel 11 is the maximum allowed WiFi 2.4Ghz channel,
except for the US that allows channel 12 & 13 in low power mode with additional restrictions.
Canada allows channel 12 in low power mode. Because it's unsure if low powered mode can be
supported the channels are not selectable for those countries. Also Uzbekistan and Colombia
allow up to channel 11 as maximum channel on the 2.4Ghz WiFi band.
Source: https://en.wikipedia.org/wiki/List_of_WLAN_channels
Additional: https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git
*/
function loadChannelSelect(selected) {
var iface = $('#cbxinterface').val();
var hwmodeText = '';
var csrfToken = $('meta[name=csrf_token]').attr('content');
// update hardware mode tooltip
setHardwareModeTooltip();
$.post('ajax/networking/get_frequencies.php',{'interface': iface, 'csrf_token': csrfToken, 'selected': selected},function(response){
// Fetch wireless regulatory data
$.getJSON("config/wireless.json", function(json) {
var hw_mode = $('#cbxhwmode').val();
var country_code = $('#cbxcountries').val();
var channel_select = $('#cbxchannel');
var btn_save = $('#btnSaveHostapd');
var data = JSON.parse(response);
var selectableChannels = [];
var data = json["wireless_regdb"];
var selectablechannels = Array.range(1,14);
// Map selected hw_mode to available channels
// Assign array of countries to valid frequencies (channels)
var countries_2_4Ghz_max11ch = data["2_4GHz_max11ch"].countries;
var countries_2_4Ghz_max14ch = data["2_4GHz_max14ch"].countries;
var countries_5Ghz_max48ch = data["5Ghz_max48ch"].countries;
// Map selected hw_mode and country to determine channel list
if (hw_mode === 'a') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
} else if (hw_mode !== 'ac') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('24'));
} else if (hw_mode === 'b') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('24'));
} else if (hw_mode === 'ac') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
}
// If selected channel doeesn't exist in allowed channels, set default or null (unsupported)
if (!selectableChannels.find(item => item.Channel === selected)) {
if (selectableChannels.length === 0) {
selectableChannels[0] = { Channel: null };
} else {
defaultChannel = selectableChannels[0].Channel;
selected = defaultChannel
}
selectablechannels = data["5Ghz_max48ch"].channels;
} else if (($.inArray(country_code, countries_2_4Ghz_max11ch) !== -1) && (hw_mode !== 'ac') ) {
selectablechannels = data["2_4GHz_max11ch"].channels;
} else if (($.inArray(country_code, countries_2_4Ghz_max14ch) !== -1) && (hw_mode === 'b')) {
selectablechannels = data["2_4GHz_max14ch"].channels;
} else if (($.inArray(country_code, countries_5Ghz_max48ch) !== -1) && (hw_mode === 'ac')) {
selectablechannels = data["5Ghz_max48ch"].channels;
}
// Set channel select with available values
selected = (typeof selected === 'undefined') ? selectablechannels[0] : selected;
channel_select.empty();
if (selectableChannels[0].Channel === null) {
channel_select.append($("<option></option>").attr("value", "").text("---"));
channel_select.prop("disabled", true);
btn_save.prop("disabled", true);
} else {
channel_select.prop("disabled", false);
btn_save.prop("disabled", false);
$.each(selectableChannels, function(key,value) {
channel_select.append($("<option></option>").attr("value", value.Channel).text(value.Channel));
});
channel_select.val(selected);
}
$.each(selectablechannels, function(key,value) {
channel_select.append($("<option></option>").attr("value", value).text(value));
});
channel_select.val(selected);
});
}
/* Sets hardware mode tooltip text for selected interface
* and calls loadChannelSelect()
/* Sets hardware mode tooltip text for selected interface.
*/
function setHardwareModeTooltip() {
var iface = $('#cbxinterface').val();
var hwmodeText = '';
var csrfToken = $('meta[name=csrf_token]').attr('content');
// Explanatory text if 802.11ac is disabled
if ($('#cbxhwmode').find('option[value="ac"]').prop('disabled') == true ) {
var hwmodeText = $('#hwmode').attr('data-tooltip');
}
$.post('ajax/networking/get_nl80211_band.php?',{'interface': iface, 'csrf_token': csrfToken},function(data){
$.post('ajax/networking/get_frequencies.php?',{'interface': iface},function(data){
var responseText = JSON.parse(data);
$('#tiphwmode').attr('data-original-title', responseText + '\n' + hwmodeText );
});
@ -730,18 +408,16 @@ function setHardwareModeTooltip() {
* Interface elements are updated to indicate current progress, status.
*/
function updateBlocklist() {
var opt = $('#cbxblocklist option:selected');
var blocklist_id = opt.val();
var csrfToken = $('meta[name=csrf_token]').attr('content');
var blocklist_id = $('#cbxblocklist').val();
if (blocklist_id == '') { return; }
$('#cbxblocklist-status').find('i').removeClass('fas fa-check').addClass('fas fa-cog fa-spin');
$('#cbxblocklist-status').removeClass('check-hidden').addClass('check-progress');
$.post('ajax/adblock/update_blocklist.php',{ 'blocklist_id':blocklist_id, 'csrf_token': csrfToken},function(data){
$.post('ajax/adblock/update_blocklist.php',{ 'blocklist_id':blocklist_id },function(data){
var jsonData = JSON.parse(data);
if (jsonData['return'] == '0') {
$('#cbxblocklist-status').find('i').removeClass('fas fa-cog fa-spin').addClass('fas fa-check');
$('#cbxblocklist-status').removeClass('check-progress').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
$('#blocklist-'+jsonData['list']).text("Just now");
$('#'+blocklist_id).text("Just now");
}
})
}
@ -750,18 +426,17 @@ function clearBlocklistStatus() {
$('#cbxblocklist-status').removeClass('check-updated').addClass('check-hidden');
}
// Handler for the WireGuard generate key button
// Handler for the wireguard generate key button
$('.wg-keygen').click(function(){
var parentGroup = $(this).closest('.input-group');
var entity_pub = parentGroup.find('input[type="text"]');
var entity_pub = $(this).parent('div').prev('input[type="text"]');
var entity_priv = $(this).parent('div').next('input[type="hidden"]');
var updated = entity_pub.attr('name')+"-pubkey-status";
var csrfToken = $('meta[name="csrf_token"]').attr('content');
$.post('ajax/networking/get_wgkey.php',{'entity':entity_pub.attr('name'), 'csrf_token': csrfToken},function(data){
$.post('ajax/networking/get_wgkey.php',{'entity':entity_pub.attr('name') },function(data){
var jsonData = JSON.parse(data);
entity_pub.val(jsonData.pubkey);
$('#' + updated).removeClass('check-hidden').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
});
});
})
})
// Handler for wireguard client.conf download
$('.wg-client-dl').click(function(){
@ -789,6 +464,7 @@ window.addEventListener('load', function() {
// Loop over them and prevent submission
var validation = Array.prototype.filter.call(forms, function(form) {
form.addEventListener('submit', function(event) {
//console.log(event.submitter);
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
@ -798,132 +474,24 @@ window.addEventListener('load', function() {
});
}, false);
let sessionCheckInterval = setInterval(checkSession, 5000);
function checkSession() {
// skip session check if on login page
if (window.location.pathname === '/login') {
return;
}
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/session/do_check_session.php',{'csrf_token': csrfToken},function (data) {
if (data.status === 'session_expired') {
clearInterval(sessionCheckInterval);
showSessionExpiredModal();
}
}).fail(function (jqXHR, status, err) {
console.error("Error checking session status:", status, err);
});
}
function showSessionExpiredModal() {
$('#sessionTimeoutModal').modal('show');
}
$(document).on("click", "#js-session-expired-login", function(e) {
const loginModal = $('#modal-admin-login');
const redirectUrl = window.location.pathname;
window.location.href = `/login?action=${encodeURIComponent(redirectUrl)}`;
});
// show modal login on page load
$(document).ready(function () {
const params = new URLSearchParams(window.location.search);
const redirectUrl = $('#redirect-url').val() || params.get('action') || '/';
$('#modal-admin-login').modal('show');
$('#redirect-url').val(redirectUrl);
$('#username').focus();
$('#username').addClass("focusedInput");
});
// DHCP or Static IP option group
$('#chkstatic').on('change', function() {
if (this.checked) {
setStaticFieldsEnabled();
}
});
$('#chkdhcp').on('change', function() {
this.checked ? setStaticFieldsDisabled() : null;
});
$('input[name="dhcp-iface"]').change(function() {
if ($('input[name="dhcp-iface"]:checked').val() == '1') {
setDhcpFieldsEnabled();
} else {
setDhcpFieldsDisabled();
}
});
function setStaticFieldsEnabled() {
$('#txtipaddress').prop('required', true);
$('#txtsubnetmask').prop('required', true);
$('#txtgateway').prop('required', true);
$('#txtipaddress').removeAttr('disabled');
$('#txtsubnetmask').removeAttr('disabled');
$('#txtgateway').removeAttr('disabled');
}
function setStaticFieldsDisabled() {
$('#txtipaddress').prop('disabled', true);
$('#txtsubnetmask').prop('disabled', true);
$('#txtgateway').prop('disabled', true);
$('#txtipaddress').removeAttr('required');
$('#txtsubnetmask').removeAttr('required');
$('#txtgateway').removeAttr('required');
}
function setDhcpFieldsEnabled() {
$('#txtrangestart').prop('required', true);
$('#txtrangeend').prop('required', true);
$('#txtrangeleasetime').prop('required', true);
$('#cbxrangeleasetimeunits').prop('required', true);
$('#txtrangestart').removeAttr('disabled');
$('#txtrangeend').removeAttr('disabled');
$('#txtrangeleasetime').removeAttr('disabled');
$('#cbxrangeleasetimeunits').removeAttr('disabled');
$('#txtdns1').removeAttr('disabled');
$('#txtdns2').removeAttr('disabled');
$('#txtmetric').removeAttr('disabled');
}
function setDhcpFieldsDisabled() {
$('#txtrangestart').removeAttr('required');
$('#txtrangeend').removeAttr('required');
$('#txtrangeleasetime').removeAttr('required');
$('#cbxrangeleasetimeunits').removeAttr('required');
$('#txtrangestart').prop('disabled', true);
$('#txtrangeend').prop('disabled', true);
$('#txtrangeleasetime').prop('disabled', true);
$('#cbxrangeleasetimeunits').prop('disabled', true);
$('#txtdns1').prop('disabled', true);
$('#txtdns2').prop('disabled', true);
$('#txtmetric').prop('disabled', true);
}
// Static Array method
Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);
$(document).on("click", ".js-toggle-password", function(e) {
var button = $(e.currentTarget);
var field = $(button.data("bsTarget"));
var button = $(e.target)
var field = $(button.data("target"));
if (field.is(":input")) {
e.preventDefault();
if (!button.data("__toggle-with-initial")) {
$("i", button).removeClass("fas fa-eye").addClass(button.attr("data-toggle-with"));
$("i", this).removeClass("fas fa-eye").addClass(button.attr("data-toggle-with"));
}
if (field.attr("type") === "password") {
field.attr("type", "text");
} else {
$("i", button).removeClass("fas fa-eye-slash").addClass("fas fa-eye");
$("i", this).removeClass("fas fa-eye-slash").addClass("fas fa-eye");
field.attr("type", "password");
}
}
@ -931,18 +499,8 @@ $(document).on("click", ".js-toggle-password", function(e) {
$(function() {
$('#theme-select').change(function() {
var theme = themes[$( "#theme-select" ).val() ];
var hasDarkTheme = theme === 'custom.php';
var nightModeChecked = $("#night-mode").prop("checked");
if (nightModeChecked && hasDarkTheme) {
if (theme === "custom.php") {
set_theme("dark.css");
}
} else {
set_theme(theme);
}
var theme = themes[$( "#theme-select" ).val() ];
set_theme(theme);
});
});
@ -953,23 +511,12 @@ function set_theme(theme) {
}
$(function() {
var currentTheme = getCookie('theme');
// Check if the current theme is a dark theme
var isDarkTheme = currentTheme === 'dark.css';
$('#night-mode').prop('checked', isDarkTheme);
$('#night-mode').change(function() {
var state = $(this).is(':checked');
var currentTheme = getCookie('theme');
if (state == true) {
if (currentTheme == 'custom.php') {
set_theme('dark.css');
}
if (state == true && getCookie('theme') != 'lightsout.css') {
set_theme('lightsout.css');
} else {
if (currentTheme == 'dark.css') {
set_theme('custom.php');
}
set_theme('custom.php');
}
});
});
@ -990,47 +537,46 @@ function getCookie(cname) {
// Define themes
var themes = {
"default": "custom.php",
"hackernews" : "hackernews.css"
"hackernews" : "hackernews.css",
"lightsout" : "lightsout.css",
}
// Adds active class to current nav-item
$(window).bind("load", function() {
var url = window.location;
$('.sb-nav-link-icon a').filter(function() {
return this.href == url;
}).parent().addClass('active');
// 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");
});
// 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();
// 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');
}
}
});
function disableValidation(form) {
form.removeAttribute("novalidate");
form.classList.remove("needs-validation");
form.querySelectorAll("[required]").forEach(function (field) {
field.removeAttribute("required");
});
}
$(document).ready(function() {
const $htmlElement = $('html');
const $modeswitch = $('#night-mode');
$modeswitch.on('change', function() {
const isChecked = $(this).is(':checked');
const newTheme = isChecked ? 'dark' : 'light';
$htmlElement.attr('data-bs-theme', newTheme);
localStorage.setItem('bsTheme', newTheme);
});
// Adds active class to current nav-item
$(window).bind("load", function() {
var url = window.location;
$('ul.navbar-nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
});
$(document)

View file

@ -3,15 +3,13 @@
// Support for dark theme
theme = getCookie('theme');
if (theme == 'lightsout.css') {
var bgColor1 = '#141414';
var bgColor2 = '#141414';
var borderColor = 'rgba(37, 153, 63, 1)';
var labelColor = 'rgba(37, 153, 63, 1)';
} else if (theme == 'material-light.php') {
var borderColor = '#f2f2fb';
var labelColor = '#f2f2fb';
} else if (theme == 'material-dark.php') {
var borderColor = '#f2f2fb';
var labelColor = '#f2f2fb';
} else {
var bgColor1 = '#d4edda';
var bgColor2 = '#eaecf4';
var borderColor = 'rgba(147, 210, 162, 1)';
var labelColor = 'rgba(130, 130, 130, 1)';
}
@ -19,7 +17,7 @@ if (theme == 'lightsout.css') {
let data1 = {
datasets: [{
data: [linkQ, 100-linkQ],
backgroundColor: 'transparent',
backgroundColor: [bgColor1, bgColor2],
borderColor: borderColor,
}],
};

90
app/lib/system.php Normal file
View file

@ -0,0 +1,90 @@
<?php
/**
* Sytem info class
*
* @description System info class for RaspAP
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
*/
namespace RaspAP\System;
class Sysinfo
{
public function hostname()
{
return shell_exec("hostname -f");
}
public function uptime()
{
$uparray = explode(" ", exec("cat /proc/uptime"));
$seconds = round($uparray[0], 0);
$minutes = $seconds / 60;
$hours = $minutes / 60;
$days = floor($hours / 24);
$hours = floor($hours - ($days * 24));
$minutes = floor($minutes - ($days * 24 * 60) - ($hours * 60));
$uptime= '';
if ($days != 0) {
$uptime .= $days . ' day' . (($days > 1)? 's ':' ');
}
if ($hours != 0) {
$uptime .= $hours . ' hour' . (($hours > 1)? 's ':' ');
}
if ($minutes != 0) {
$uptime .= $minutes . ' minute' . (($minutes > 1)? 's ':' ');
}
return $uptime;
}
public function usedMemory()
{
$used = shell_exec("free -m | awk 'NR==2{ total=$2 ; used=$3 } END { print used/total*100}'");
return floor($used);
}
public function processorCount()
{
$procs = shell_exec("nproc --all");
return intval($procs);
}
public function loadAvg1Min()
{
$load = exec("awk '{print $1}' /proc/loadavg");
return floatval($load);
}
public function systemLoadPercentage()
{
return intval(($this->loadAvg1Min() * 100) / $this->processorCount());
}
public function systemTemperature()
{
$cpuTemp = file_get_contents("/sys/class/thermal/thermal_zone0/temp");
return number_format((float)$cpuTemp/1000, 1);
}
public function hostapdStatus()
{
exec('pidof hostapd | wc -l', $status);
return $status;
}
public function operatingSystem()
{
$os_desc = shell_exec("lsb_release -sd");
return $os_desc;
}
public function kernelVersion()
{
$kernel = shell_exec("uname -r");
return $kernel;
}
}

505
app/lib/uploader.php Normal file
View file

@ -0,0 +1,505 @@
<?php
/**
* Simple PHP upload class
*
* Adapted from aivis/PHP-file-upload-class
*
* @description File upload class for RaspAP
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @author Aivis Silins
* @link https://github.com/aivis/PHP-file-upload-class
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
*/
namespace RaspAP\Uploader;
class Upload
{
/**
* Default directory persmissions (destination)
*/
protected $default_permissions = 0750;
/**
* File post array
*
* @var array
*/
protected $file_post = array();
/**
* Destination directory
*
* @var string
*/
protected $destination;
/**
* Fileinfo
*
* @var object
*/
protected $finfo;
/**
* Data about file
*
* @var array
*/
public $file = array();
/**
* Max. file size
*
* @var int
*/
protected $max_file_size;
/**
* Allowed mime types
*
* @var array
*/
protected $mimes = array();
/**
* Temp path
*
* @var string
*/
protected $tmp_name;
/**
* Validation errors
*
* @var array
*/
protected $validation_errors = array();
/**
* Filename (new)
*
* @var string
*/
protected $filename;
/**
* Internal callbacks (filesize check, mime, etc)
*
* @var array
*/
private $callbacks = array();
/**
* Root dir
*
* @var string
*/
protected $root;
/**
* Return upload object
*
* $destination = 'path/to/file/destination/';
*
* @param string $destination
* @param string $root
* @return Upload
*/
public static function factory($destination, $root = false)
{
return new Upload($destination, $root);
}
/**
* Define root constant and set & create destination path
*
* @param string $destination
* @param string $root
*/
public function __construct($destination, $root = false)
{
if ($root) {
$this->root = $root;
} else {
$this->root = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR;
}
// set & create destination path
if (!$this->set_destination($destination)) {
throw new Exception('Upload: Unable to create destination. '.$this->root . $this->destination);
}
//create finfo object
$this->finfo = new \finfo();
}
/**
* Set target filename
*
* @param string $filename
*/
public function set_filename($filename)
{
$this->filename = $filename;
}
/**
* Check & Save file
*
* Return data about current upload
*
* @return array
*/
public function upload($filename = false)
{
if($filename ) {
$this->set_filename($filename);
}
$this->set_filename($filename);
if ($this->check()) {
$this->save();
}
// return state data
return $this->get_state();
}
/**
* Save file on server
* Return state data
*
* @return array
*/
public function save()
{
$this->save_file();
return $this->get_state();
}
/**
* Validate file (execute callbacks)
* Returns TRUE if validation successful
*
* @return bool
*/
public function check()
{
//execute callbacks (check filesize, mime, also external callbacks
$this->validate();
//add error messages
$this->file['errors'] = $this->get_errors();
//change file validation status
$this->file['status'] = empty($this->validation_errors);
return $this->file['status'];
}
/**
* Get current state data
*
* @return array
*/
public function get_state()
{
return $this->file;
}
/**
* Save file on server
*/
protected function save_file()
{
//create & set new filename
if(empty($this->filename)) {
$this->create_new_filename();
}
//set filename
$this->file['filename'] = $this->filename;
//set full path
$this->file['full_path'] = $this->root . $this->destination . $this->filename;
$this->file['path'] = $this->destination . $this->filename;
$status = move_uploaded_file($this->tmp_name, $this->file['full_path']);
//checks whether upload successful
if (!$status) {
throw new Exception('Upload: Failed to upload file.');
}
//done
$this->file['status'] = true;
}
/**
* Set data about file
*/
protected function set_file_data()
{
$file_size = $this->get_file_size();
$this->file = array(
'status' => false,
'destination' => $this->destination,
'size_in_bytes' => $file_size,
'size_in_mb' => $this->bytes_to_mb($file_size),
'mime' => $this->get_file_mime(),
'filename' => $this->file_post['name'],
'tmp_name' => $this->file_post['tmp_name'],
'post_data' => $this->file_post,
);
}
/**
* Set validation error
*
* @param string $message
*/
public function set_error($message)
{
$this->validation_errors[] = $message;
}
/**
* Return validation errors
*
* @return array
*/
public function get_errors()
{
return $this->validation_errors;
}
/**
* Set external callback methods
*
* @param object $instance_of_callback_object
* @param array $callback_methods
*/
public function callbacks($instance_of_callback_object, $callback_methods)
{
if (empty($instance_of_callback_object)) {
throw new Exception('Upload: $instance_of_callback_object cannot be empty.');
}
if (!is_array($callback_methods)) {
throw new Exception('Upload: $callback_methods data type need to be array.');
}
$this->external_callback_object = $instance_of_callback_object;
$this->external_callback_methods = $callback_methods;
}
/**
* Execute callbacks
*/
protected function validate()
{
//get curent errors
$errors = $this->get_errors();
if (empty($errors)) {
//set data about current file
$this->set_file_data();
//execute internal callbacks
$this->execute_callbacks($this->callbacks, $this);
//execute external callbacks
$this->execute_callbacks($this->external_callback_methods, $this->external_callback_object);
}
}
/**
* Execute callbacks
*/
protected function execute_callbacks($callbacks, $object)
{
foreach($callbacks as $method) {
$object->$method($this);
}
}
/**
* File mime type validation callback
*
* @param object $object
*/
protected function check_mime_type($object)
{
if (!empty($object->mimes)) {
if (!in_array($object->file['mime'], $object->mimes)) {
$object->set_error('MIME type not allowed.');
}
}
}
/**
* Set allowed mime types
*
* @param array $mimes
*/
public function set_allowed_mime_types($mimes)
{
$this->mimes = $mimes;
//if mime types is set -> set callback
$this->callbacks[] = 'check_mime_type';
}
/**
* File size validation callback
*
* @param object $object
*/
protected function check_file_size($object)
{
if (!empty($object->max_file_size)) {
$file_size_in_mb = $this->bytes_to_mb($object->file['size_in_bytes']);
if ($object->max_file_size <= $file_size_in_mb) {
$object->set_error('File exceeds maximum allowed size.');
}
}
}
/**
* Set max file size
*
* @param int $size
*/
public function set_max_file_size($size)
{
$this->max_file_size = $size;
//if max file size is set -> set callback
$this->callbacks[] = 'check_file_size';
}
/**
* Set File array to object
*
* @param array $file
*/
public function file($file)
{
$this->set_file_array($file);
}
/**
* Set file array
*
* @param array $file
*/
protected function set_file_array($file)
{
//checks whether file array is valid
if (!$this->check_file_array($file)) {
//file not selected or some bigger problems (broken files array)
$this->set_error('Please select file.');
}
//set file data
$this->file_post = $file;
//set tmp path
$this->tmp_name = $file['tmp_name'];
}
/**
* Checks whether Files post array is valid
*
* @return bool
*/
protected function check_file_array($file)
{
return isset($file['error'])
&& !empty($file['name'])
&& !empty($file['type'])
&& !empty($file['tmp_name'])
&& !empty($file['size']);
}
/**
* Get file mime type
*
* @return string
*/
protected function get_file_mime()
{
return $this->finfo->file($this->tmp_name, FILEINFO_MIME_TYPE);
}
/**
* Get file size
*
* @return int
*/
protected function get_file_size()
{
return filesize($this->tmp_name);
}
/**
* Set destination path (return TRUE on success)
*
* @param string $destination
* @return bool
*/
protected function set_destination($destination)
{
$this->destination = $destination . DIRECTORY_SEPARATOR;
return $this->destination_exist() ? true : $this->create_destination();
}
/**
* Checks whether destination folder exists
*
* @return bool
*/
protected function destination_exist()
{
return is_writable($this->root . $this->destination);
}
/**
* Create path to destination
*
* @param string $dir
* @return bool
*/
protected function create_destination()
{
return mkdir($this->root . $this->destination, $this->default_permissions, true);
}
/**
* Set unique filename
*
* @return string
*/
protected function create_new_filename()
{
$filename = sha1(mt_rand(1, 9999) . $this->destination . uniqid()) . time();
$this->set_filename($filename);
}
/**
* Convert bytes to MB
*
* @param int $bytes
* @return int
*/
protected function bytes_to_mb($bytes)
{
return round(($bytes / 1048576), 2);
}
}

View file

@ -13,15 +13,12 @@
}
],
"require": {
"php": "^8.2",
"phpoption/phpoption": "^1.9",
"ext-mbstring": "*"
"php": "^7.0"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.2.0",
"phpcompatibility/php-compatibility": "^9.3.5",
"squizlabs/php_codesniffer": "^3.9.0",
"ext-simplexml": "*"
"squizlabs/php_codesniffer": "^3.5.5"
},
"scripts": {
"lint": "parallel-lint . --exclude vendor",

View file

@ -1,17 +1,6 @@
{
"StevenBlack/hosts": [
"StevenBlack/hosts (default)"
],
"badmojr/hosts": [
"badmojr/1Hosts (Mini)",
"badmojr/1Hosts (Lite)",
"badmojr/1Hosts (Pro)",
"badmojr/1Hosts (Xtra)"
],
"OISD/domains": [
"oisd/big (default)",
"oisd/small",
"oisd/nsfw"
]
"notracking/hosts-blocklist": [
"notracking-hostnames",
"notracking-domains"
]
}

View file

@ -1,18 +1,11 @@
<?php
define('RASPI_BRAND_TEXT', 'RaspAP');
define('RASPI_BRAND_TITLE', RASPI_BRAND_TEXT.' Admin Panel');
define('RASPI_CONFIG', '/etc/raspap');
define('RASPI_CONFIG_NETWORK', RASPI_CONFIG.'/networking/defaults.json');
define('RASPI_CONFIG_PROVIDERS', 'config/vpn-providers.json');
define('RASPI_CONFIG_API', RASPI_CONFIG.'/api');
define('RASPI_ADMIN_DETAILS', RASPI_CONFIG.'/raspap.auth');
define('RASPI_WIFI_AP_INTERFACE', 'wlan0');
define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap');
define('RASPI_ERROR_LOG', sys_get_temp_dir() . '/raspap_error.log');
define('RASPI_DEBUG_LOG', 'raspap_debug.log');
define('RASPI_LOG_SIZE_LIMIT', 64);
define('RASPI_SESSION_TIMEOUT', 1440);
// Constants for configuration file paths.
// These are typical for default RPi installs. Modify if needed.
@ -23,7 +16,6 @@ define('RASPI_ADBLOCK_CONFIG', RASPI_DNSMASQ_PREFIX.'adblock.conf');
define('RASPI_HOSTAPD_CONFIG', '/etc/hostapd/hostapd.conf');
define('RASPI_DHCPCD_CONFIG', '/etc/dhcpcd.conf');
define('RASPI_DHCPCD_LOG', '/var/log/dnsmasq.log');
define('RASPI_HOSTAPD_LOG', '/tmp/hostapd.log');
define('RASPI_WPA_SUPPLICANT_CONFIG', '/etc/wpa_supplicant/wpa_supplicant.conf');
define('RASPI_HOSTAPD_CTRL_INTERFACE', '/var/run/hostapd');
define('RASPI_WPA_CTRL_INTERFACE', '/var/run/wpa_supplicant');
@ -37,12 +29,9 @@ define('RASPI_LIGHTTPD_CONFIG', '/etc/lighttpd/lighttpd.conf');
define('RASPI_ACCESS_CHECK_IP', '1.1.1.1');
define('RASPI_ACCESS_CHECK_DNS', 'one.one.one.one');
// Constant for the GitHub API latest release endpoint
define('RASPI_API_ENDPOINT', 'https://api.github.com/repos/RaspAP/raspap-webgui/releases/latest');
// Constant for the 5GHz wireless regulatory domain
define("RASPI_5GHZ_CHANNEL_MIN", 100);
define("RASPI_5GHZ_CHANNEL_MAX", 192);
// Constants for the 5GHz wireless regulatory domain.
define('RASPI_5GHZ_ISO_ALPHA2', array('NL','US'));
define('RASPI_5GHZ_MAX_CHANNEL', 165);
// Enable basic authentication for the web admin.
define('RASPI_AUTH_ENABLED', true);
@ -54,7 +43,6 @@ define('RASPI_NETWORK_ENABLED', true);
define('RASPI_DHCP_ENABLED', true);
define('RASPI_ADBLOCK_ENABLED', false);
define('RASPI_OPENVPN_ENABLED', false);
define('RASPI_VPN_PROVIDER_ENABLED', false);
define('RASPI_WIREGUARD_ENABLED', false);
define('RASPI_TORPROXY_ENABLED', false);
define('RASPI_CONFAUTH_ENABLED', true);
@ -62,8 +50,6 @@ define('RASPI_CHANGETHEME_ENABLED', true);
define('RASPI_VNSTAT_ENABLED', true);
define('RASPI_SYSTEM_ENABLED', true);
define('RASPI_MONITOR_ENABLED', false);
define('RASPI_RESTAPI_ENABLED', false);
define('RASPI_PLUGINS_ENABLED', true);
// Locale settings
define('LOCALE_ROOT', 'locale');

View file

@ -6,12 +6,6 @@
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
"subnetmask": [ "255.255.255.0" ]
},
"wlan1": {
"static ip_address": [ "10.9.141.1/24" ],
"static routers": [ "10.9.141.1" ],
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
"subnetmask": [ "255.255.255.0" ]
},
"uap0": {
"static ip_address": [ "192.168.50.1/24" ],
"static routers": [ "192.168.50.1" ],
@ -36,9 +30,6 @@
"wlan0": {
"dhcp-range": [ "10.3.141.50,10.3.141.254,255.255.255.0,12h" ]
},
"wlan1": {
"dhcp-range": [ "10.9.141.50,10.9.141.254,255.255.255.0,12h" ]
},
"uap0": {
"dhcp-range": [ "192.168.50.50,192.168.50.150,12h" ]
}

View file

@ -15,5 +15,4 @@ interface wlan0
static ip_address=10.3.141.1/24
static routers=10.3.141.1
static domain_name_server=9.9.9.9 1.1.1.1
nogateway

View file

@ -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
@ -20,5 +20,5 @@ country_code=GB
## RaspAP wireless client AP mode
#interface=uap0
## RaspAP bridge AP mode, disabled by default
## RaspAP bridge AP mode (disabled by default)
#bridge=br0

View file

@ -1,75 +0,0 @@
{
"providers": [
{
"id": 1,
"name": "ExpressVPN",
"bin_path": "/usr/bin/expressvpn",
"install_page": "https://www.expressvpn.com/support/vpn-setup/app-for-linux/",
"account_page": "https://www.expressvpn.com/subscriptions",
"cmd_overrides": {
"countries": "list all",
"log": "diagnostics",
"version": "-v"
},
"regex": {
"status": "\/not connected\/",
"pattern": "\/^(.{2,5})\\s(?:.{1,28})(.{1,26}).*$\/",
"replace": "$1,$2",
"slice": 3
}
},
{
"id": 2,
"name": "Mullvad VPN",
"bin_path": "/usr/bin/mullvad",
"install_page": "https://mullvad.net/en/download/vpn/linux",
"account_page": "https://mullvad.net/en/account",
"cmd_overrides": {
"account": "account get",
"countries": "relay list",
"log": "status -v",
"version": "--version"
},
"regex": {
"status": "\/disconnected\/",
"pattern": "\/^(.*),.*$\/",
"replace": "$1"
}
},
{
"id": 3,
"name": "NordVPN",
"bin_path": "/usr/bin/nordvpn",
"install_page": "https://nordvpn.com/download/linux/",
"account_page": "https://my.nordaccount.com/dashboard/",
"cmd_overrides": {
"log": "status"
},
"regex": {
"status": "\/status: disconnected\/",
"pattern": "(\\w+)\\s+",
"replace": "$1,$1\\n"
}
},
{
"id": 4,
"name": "AdGuard VPN",
"bin_path": "/usr/local/bin/adguardvpn-cli",
"install_page": "https://adguard-vpn.com/kb/adguard-vpn-for-linux/installation/",
"account_page": "https://my.adguard-vpn.com/en/account/product/vpn",
"cmd_overrides": {
"countries": "list-locations",
"connect": "connect -y -l",
"log": "status",
"account": "license",
"version": "--version"
},
"regex": {
"status": "\/vpn is disconnected\/",
"pattern": "/^([A-Z]{2})\\s+.*?\\s([A-Za-z]+(?:\\s[A-Za-z]+)?)\\s+\\d+$/m",
"replace": "$2",
"slice": 3
}
}
]
}

17
config/wireless.json Normal file
View file

@ -0,0 +1,17 @@
{"wireless_regdb": {
"debug": "off",
"2_4GHz_max11ch": {
"countries": [ "AG", "BS", "BB", "BZ", "CR", "CU", "DM", "DO", "SV", "GD", "GT",
"HT", "HN", "JM", "MX", "NI", "PA", "KN", "LC", "VC", "TT", "US", "CA", "UZ", "CO" ],
"channels": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
},
"2_4GHz_max14ch": {
"countries": [ "JP", "NL" ],
"channels": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ]
},
"5Ghz_max48ch": {
"countries": [ "NL","US" ],
"channels": [ 36, 40, 44, 48 ]
}
}}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,19 +0,0 @@
/*!
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
.far,
.fa-regular {
font-weight: 400; }

View file

@ -1,6 +0,0 @@
/*!
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}

View file

@ -1,19 +0,0 @@
/*!
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
.fas,
.fa-solid {
font-weight: 900; }

View file

@ -1,6 +0,0 @@
/*!
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}

View file

@ -1,459 +0,0 @@
/*!
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:root, :host {
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free';
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free';
--fa-font-light: normal 300 1em/1 'Font Awesome 6 Pro';
--fa-font-thin: normal 100 1em/1 'Font Awesome 6 Pro';
--fa-font-duotone: normal 900 1em/1 'Font Awesome 6 Duotone';
--fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands';
--fa-font-sharp-solid: normal 900 1em/1 'Font Awesome 6 Sharp';
--fa-font-sharp-regular: normal 400 1em/1 'Font Awesome 6 Sharp';
--fa-font-sharp-light: normal 300 1em/1 'Font Awesome 6 Sharp';
--fa-font-sharp-thin: normal 100 1em/1 'Font Awesome 6 Sharp';
--fa-font-sharp-duotone-solid: normal 900 1em/1 'Font Awesome 6 Sharp Duotone'; }
svg:not(:root).svg-inline--fa, svg:not(:host).svg-inline--fa {
overflow: visible;
box-sizing: content-box; }
.svg-inline--fa {
display: var(--fa-display, inline-block);
height: 1em;
overflow: visible;
vertical-align: -.125em; }
.svg-inline--fa.fa-2xs {
vertical-align: 0.1em; }
.svg-inline--fa.fa-xs {
vertical-align: 0em; }
.svg-inline--fa.fa-sm {
vertical-align: -0.07143em; }
.svg-inline--fa.fa-lg {
vertical-align: -0.2em; }
.svg-inline--fa.fa-xl {
vertical-align: -0.25em; }
.svg-inline--fa.fa-2xl {
vertical-align: -0.3125em; }
.svg-inline--fa.fa-pull-left {
margin-right: var(--fa-pull-margin, 0.3em);
width: auto; }
.svg-inline--fa.fa-pull-right {
margin-left: var(--fa-pull-margin, 0.3em);
width: auto; }
.svg-inline--fa.fa-li {
width: var(--fa-li-width, 2em);
top: 0.25em; }
.svg-inline--fa.fa-fw {
width: var(--fa-fw-width, 1.25em); }
.fa-layers svg.svg-inline--fa {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0; }
.fa-layers-text, .fa-layers-counter {
display: inline-block;
position: absolute;
text-align: center; }
.fa-layers {
display: inline-block;
height: 1em;
position: relative;
text-align: center;
vertical-align: -.125em;
width: 1em; }
.fa-layers svg.svg-inline--fa {
transform-origin: center center; }
.fa-layers-text {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
transform-origin: center center; }
.fa-layers-counter {
background-color: var(--fa-counter-background-color, #ff253a);
border-radius: var(--fa-counter-border-radius, 1em);
box-sizing: border-box;
color: var(--fa-inverse, #fff);
line-height: var(--fa-counter-line-height, 1);
max-width: var(--fa-counter-max-width, 5em);
min-width: var(--fa-counter-min-width, 1.5em);
overflow: hidden;
padding: var(--fa-counter-padding, 0.25em 0.5em);
right: var(--fa-right, 0);
text-overflow: ellipsis;
top: var(--fa-top, 0);
transform: scale(var(--fa-counter-scale, 0.25));
transform-origin: top right; }
.fa-layers-bottom-right {
bottom: var(--fa-bottom, 0);
right: var(--fa-right, 0);
top: auto;
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: bottom right; }
.fa-layers-bottom-left {
bottom: var(--fa-bottom, 0);
left: var(--fa-left, 0);
right: auto;
top: auto;
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: bottom left; }
.fa-layers-top-right {
top: var(--fa-top, 0);
right: var(--fa-right, 0);
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: top right; }
.fa-layers-top-left {
left: var(--fa-left, 0);
right: auto;
top: var(--fa-top, 0);
transform: scale(var(--fa-layers-scale, 0.25));
transform-origin: top left; }
.fa-1x {
font-size: 1em; }
.fa-2x {
font-size: 2em; }
.fa-3x {
font-size: 3em; }
.fa-4x {
font-size: 4em; }
.fa-5x {
font-size: 5em; }
.fa-6x {
font-size: 6em; }
.fa-7x {
font-size: 7em; }
.fa-8x {
font-size: 8em; }
.fa-9x {
font-size: 9em; }
.fa-10x {
font-size: 10em; }
.fa-2xs {
font-size: 0.625em;
line-height: 0.1em;
vertical-align: 0.225em; }
.fa-xs {
font-size: 0.75em;
line-height: 0.08333em;
vertical-align: 0.125em; }
.fa-sm {
font-size: 0.875em;
line-height: 0.07143em;
vertical-align: 0.05357em; }
.fa-lg {
font-size: 1.25em;
line-height: 0.05em;
vertical-align: -0.075em; }
.fa-xl {
font-size: 1.5em;
line-height: 0.04167em;
vertical-align: -0.125em; }
.fa-2xl {
font-size: 2em;
line-height: 0.03125em;
vertical-align: -0.1875em; }
.fa-fw {
text-align: center;
width: 1.25em; }
.fa-ul {
list-style-type: none;
margin-left: var(--fa-li-margin, 2.5em);
padding-left: 0; }
.fa-ul > li {
position: relative; }
.fa-li {
left: calc(-1 * var(--fa-li-width, 2em));
position: absolute;
text-align: center;
width: var(--fa-li-width, 2em);
line-height: inherit; }
.fa-border {
border-color: var(--fa-border-color, #eee);
border-radius: var(--fa-border-radius, 0.1em);
border-style: var(--fa-border-style, solid);
border-width: var(--fa-border-width, 0.08em);
padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); }
.fa-pull-left {
float: left;
margin-right: var(--fa-pull-margin, 0.3em); }
.fa-pull-right {
float: right;
margin-left: var(--fa-pull-margin, 0.3em); }
.fa-beat {
animation-name: fa-beat;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
.fa-bounce {
animation-name: fa-bounce;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); }
.fa-fade {
animation-name: fa-fade;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
.fa-beat-fade {
animation-name: fa-beat-fade;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
.fa-flip {
animation-name: fa-flip;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
.fa-shake {
animation-name: fa-shake;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, linear); }
.fa-spin {
animation-name: fa-spin;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 2s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, linear); }
.fa-spin-reverse {
--fa-animation-direction: reverse; }
.fa-pulse,
.fa-spin-pulse {
animation-name: fa-spin;
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, steps(8)); }
@media (prefers-reduced-motion: reduce) {
.fa-beat,
.fa-bounce,
.fa-fade,
.fa-beat-fade,
.fa-flip,
.fa-pulse,
.fa-shake,
.fa-spin,
.fa-spin-pulse {
animation-delay: -1ms;
animation-duration: 1ms;
animation-iteration-count: 1;
transition-delay: 0s;
transition-duration: 0s; } }
@keyframes fa-beat {
0%, 90% {
transform: scale(1); }
45% {
transform: scale(var(--fa-beat-scale, 1.25)); } }
@keyframes fa-bounce {
0% {
transform: scale(1, 1) translateY(0); }
10% {
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
30% {
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
50% {
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
57% {
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
64% {
transform: scale(1, 1) translateY(0); }
100% {
transform: scale(1, 1) translateY(0); } }
@keyframes fa-fade {
50% {
opacity: var(--fa-fade-opacity, 0.4); } }
@keyframes fa-beat-fade {
0%, 100% {
opacity: var(--fa-beat-fade-opacity, 0.4);
transform: scale(1); }
50% {
opacity: 1;
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
@keyframes fa-flip {
50% {
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
@keyframes fa-shake {
0% {
transform: rotate(-15deg); }
4% {
transform: rotate(15deg); }
8%, 24% {
transform: rotate(-18deg); }
12%, 28% {
transform: rotate(18deg); }
16% {
transform: rotate(-22deg); }
20% {
transform: rotate(22deg); }
32% {
transform: rotate(-12deg); }
36% {
transform: rotate(12deg); }
40%, 100% {
transform: rotate(0deg); } }
@keyframes fa-spin {
0% {
transform: rotate(0deg); }
100% {
transform: rotate(360deg); } }
.fa-rotate-90 {
transform: rotate(90deg); }
.fa-rotate-180 {
transform: rotate(180deg); }
.fa-rotate-270 {
transform: rotate(270deg); }
.fa-flip-horizontal {
transform: scale(-1, 1); }
.fa-flip-vertical {
transform: scale(1, -1); }
.fa-flip-both,
.fa-flip-horizontal.fa-flip-vertical {
transform: scale(-1, -1); }
.fa-rotate-by {
transform: rotate(var(--fa-rotate-angle, 0)); }
.fa-stack {
display: inline-block;
vertical-align: middle;
height: 2em;
position: relative;
width: 2.5em; }
.fa-stack-1x,
.fa-stack-2x {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0;
z-index: var(--fa-stack-z-index, auto); }
.svg-inline--fa.fa-stack-1x {
height: 1em;
width: 1.25em; }
.svg-inline--fa.fa-stack-2x {
height: 2em;
width: 2.5em; }
.fa-inverse {
color: var(--fa-inverse, #fff); }
.sr-only,
.fa-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0; }
.sr-only-focusable:not(:focus),
.fa-sr-only-focusable:not(:focus) {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0; }
.svg-inline--fa .fa-primary {
fill: var(--fa-primary-color, currentColor);
opacity: var(--fa-primary-opacity, 1); }
.svg-inline--fa .fa-secondary {
fill: var(--fa-secondary-color, currentColor);
opacity: var(--fa-secondary-opacity, 0.4); }
.svg-inline--fa.fa-swap-opacity .fa-primary {
opacity: var(--fa-secondary-opacity, 0.4); }
.svg-inline--fa.fa-swap-opacity .fa-secondary {
opacity: var(--fa-primary-opacity, 1); }
.svg-inline--fa mask .fa-primary,
.svg-inline--fa mask .fa-secondary {
fill: black; }
.fad.fa-inverse,
.fa-duotone.fa-inverse {
color: var(--fa-inverse, #fff); }

File diff suppressed because one or more lines are too long

View file

@ -1,26 +0,0 @@
/*!
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype");
unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; }
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype");
unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; }

Some files were not shown because too many files have changed in this diff Show more