1
0
mirror of https://github.com/billz/raspap-webgui.git synced 2023-10-10 13:37:24 +02:00

Merge branch 'RaspAP:master' into master

This commit is contained in:
Yo 2023-04-11 22:40:22 +02:00 committed by GitHub
commit be8b3c5b3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
239 changed files with 26302 additions and 6638 deletions

3
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
github: billz
github: RaspAP

View File

@ -7,39 +7,53 @@ assignees: ''
---
**Before submitting an issue**
Please read this first https://github.com/billz/raspap-webgui/wiki/Reporting-issues
<!-- These comments will NOT appear in your issue, so it's OK to ignore them -->
<!--
Thanks for reporting a bug for RaspAP.
* [x] This is a bug report
* [ ] I searched existing issues before opening this one
* [ ] I checked the FAQ before creating this issue
* [ ] I have read and understand the issue reporting guidelines
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.
**Describe the bug**
A clear and concise description of what the bug is.
All submitters MUST read the issue policy and reporting guidelines:
https://docs.raspap.com/issues/
**Your environment**
* Raspberry Pi hardware (examples: Pi 3 Model B+, Pi Zero W)
* Raspbian version (examples: Buster Lite, Buster Desktop)
* Followed the project prerequisites? (Y/N)
* Checked the project FAQ? (Y/N)
* RaspAP Quick Install or Manual setup?
* Using default configuration? (Y/N)
* Simultaneous AP and managed mode? (Y/N)
* Onboard wireless chipset or external adapter?
* Other software or services running with RaspAP?
Refer to the frequently asked questions (FAQ) and official documentation:
https://docs.raspap.com/faq/
**Steps to reproduce**
Tell us how to reproduce this issue. Provide as much detailed information as possible.
Do you have a question or want to suggest a new feature? Start a Discussion here:
https://github.com/RaspAP/raspap-webgui/discussions
**Expected behavior**
A clear and concise description of what you expected to happen.
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 -->
**Actual behavior**
Tell us what you observed instead.
## 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
**Screenshots**
If applicable, add screenshots to help explain your problem.
## Bug description
<!-- Provide a detailed description of the issue -->
**Additional context**
Add any other context about the problem here.
## 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

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
## Is your feature request related to a problem?
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
## Describe the solution you'd like
A clear and concise description of what you want to happen.
## Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
## Additional context
Add any other context or screenshots about the feature request here.

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

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '17 9 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

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

23
.travis.yml Normal file
View File

@ -0,0 +1,23 @@
sudo: false
language: php
matrix:
fast_finish: true
include:
- php: '7.0'
- php: '7.1'
- php: '7.2'
- php: '7.3'
- php: '7.4'
- php: 'nightly'
allow_failures:
- php: nightly
before_script:
- composer install --prefer-source --quiet --no-interaction
# Run test script commands.
script:
- composer test

View File

@ -1,12 +1,51 @@
# RaspAP Sponors
<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 our awesome sponsors!
You can join them by [becoming a sponsor](https://github.com/sponsors/billz).
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.
### 💖 Benefactors
## How sponsorship works
New features first land in **Insiders**, which means that *sponsors will have access to them immediately*. Every feature is tied to a funding goal in monthly subscriptions. When a funding goal is hit, the features that are tied to it are merged back into the [public RaspAP repository](https://github.com/RaspAP/raspap-webgui) and released for general availability. Bugfixes and minor enhancements are always released simultaneously in both editions.
### 🏆 Gilded supporters
Don't want to sponsor? No problem, RaspAP already has tons of features available, so chances are that most of your requirements are already satisfied. See the list of **exclusive features** to learn which features are currently only available to sponsors.
### 🤖 Robot fuelers
## How to become a sponsor
You can become a sponsor using your individual or organization's GitHub account. Just pick any tier from $10/month and complete the checkout. Then, after a few hours, you will be added as a team member to the super-secret private GitHub repository containing the Insiders edition, which has all exclusive features. In addition, you get access to Insiders-only team discussions and content.
### ☕️ Coffee supporters
**Important**: If you're sponsoring [RaspAP](https://github.com/RaspAP/sponsors) through a GitHub organization, please send a short email to [sponsors@raspap.com](mailto:sponsors@raspap.com) with the name of your organization and the account that should be added as a collaborator.
## Exclusive features
The following features are currently available exclusively to sponsors. A tangible side benefit of sponsorship is that Insiders are able to help steer future development of RaspAP. This is done through Insiders' access to discussions, feature requests, issues and pull requests in the private GitHub repository.
✅ [Network device management](https://docs.raspap.com/net-devices/)
✅ [Firewall settings](https://docs.raspap.com/firewall/)
✅ [WPA3-Personal AP security](https://docs.raspap.com/ap-basics/#wpa3-personal)
✅ [802.11w Protected Management Frames](https://docs.raspap.com/ap-basics/#80211w)
✅ [Printable Wi-Fi signs](https://docs.raspap.com/ap-basics/#printable-signs)
✅ [Drag & drop dashboard widgets](https://docs.raspap.com/ap-basics/#drag-drop-widgets)
✅ [MAC address cloning](https://docs.raspap.com/net-devices/#changing-the-mac-address)
✅ [Network diagnostics](https://docs.raspap.com/net-devices/#diagnostics)
✅ [WireGuard VPN kill switch](https://docs.raspap.com/wireguard/#kill-switch)
⚙️ Dynamic DNS support (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.
## Funding targets
Below is a list of funding targets. When a funding target is reached, the features that are tied to it are merged back into RaspAP and released to the public for general availability.
### $1000
The second **Insiders Edition** includes the features listed above.
### $500
The [first Insiders Edition goal](https://docs.raspap.com/insiders/#500-1st-insiders-edition) was reached in December 2021. Thank you sponsors!
## Quarterly giving
Beginning in 2022, each quarter 15% of all proceeds from Insiders will be donated directly to the [Raspberry Pi Foundation](https://www.raspberrypi.org/). The Raspberry Pi Foundation is a UK-based charity that works to put the power of computing and digital making into the hands of people all over the world.
[![Get involved with the Raspberry Pi Foundation](https://img.youtube.com/vi/dEzg92g1LHw/0.jpg)](https://www.youtube.com/watch?v=dEzg92g1LHw)
When you become an Insider, not only do you support development of RaspAP but you also help inspire young people by harnessing the power of computing to solve problems and express themselves creatively.
## Frequently asked questions
We've covered all you need to know [here](https://docs.raspap.com/insiders/#frequently-asked-questions).
### Terms
See our [official project documentation](https://docs.raspap.com/insiders/#terms).

1
CNAME
View File

@ -1 +0,0 @@
raspap.com

View File

@ -1,10 +1,13 @@
## How to contribute
1. File an issue in the repository, using the bug tracker, describing the
contribution you'd like to make. This will help us to get you started on the
right foot.
2. Fork the project in your account and create a new branch:
`your-great-feature`.
3. Commit your changes in that branch.
4. Open a pull request, and reference the initial issue in the pull request
message.
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.
### 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.

283
README.md
View File

@ -1,13 +1,13 @@
![](https://i.imgur.com/xeKD93p.png)
# `$raspap` [![Release 2.1](https://img.shields.io/badge/Release-2.1-green.svg)](https://github.com/billz/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Sponsor](https://img.shields.io/badge/sponsor-%F0%9F%92%96-green)](https://github.com/sponsors/billz)
[![Release 2.9.0](https://img.shields.io/badge/release-v2.9.0-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) [![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) [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/RaspAP?style=social)](https://www.reddit.com/r/RaspAP/)
A simple, responsive web interface to control wifi, hostapd and related services on the Raspberry Pi.
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.
This project was inspired by a [blog post](http://sirlagz.net/2013/02/06/script-web-configuration-page-for-raspberry-pi/) by SirLagz about using a web page rather than ssh to configure wifi and hostapd settings on the Raspberry Pi. I began by prettifying the UI by wrapping it in [SB Admin 2](https://github.com/BlackrockDigital/startbootstrap-sb-admin-2), a Bootstrap based admin theme. Since then, the project has evolved to include greater control over many aspects of a networked RPi, better security, authentication, a Quick Installer, support for OpenVPN, themes and more. RaspAP has been featured on sites such as [Instructables](http://www.instructables.com/id/Raspberry-Pi-As-Completely-Wireless-Router/), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/) and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in countless projects.
RaspAP has been featured 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'd be curious to hear about how you use this with [your own RPi-powered projects](https://github.com/billz/raspap-awesome). Until then, here are some screenshots:
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).
![](https://i.imgur.com/fwekyGE.gif)
![](https://i.imgur.com/uhBFoOB.png)
![](https://i.imgur.com/EiIpdOS.gif)
![](https://i.imgur.com/eCjUS1H.gif)
![](https://i.imgur.com/5FT2BcS.gif)
@ -16,211 +16,129 @@ We'd be curious to hear about how you use this with [your own RPi-powered projec
- [Prerequisites](#prerequisites)
- [Quick installer](#quick-installer)
- [Join Insiders](#join-insiders)
- [WireGuard support](#wireguard-support)
- [OpenVPN support](#openvpn-support)
- [Ad Blocking](#ad-blocking)
- [Bridged AP](#bridged-ap)
- [Simultaneous AP and Wifi client](#simultaneous-ap-and-wifi-client)
- [Support us](#support-us)
- [Manual installation](#manual-installation)
- [802.11ac 5GHz support](#80211ac-5ghz-support)
- [Supported operating systems](#supported-operating-systems)
- [Multilingual support](#multilingual-support)
- [HTTPS support](#https-support)
- [OpenVPN support](#openvpn-support)
- [How to contribute](#how-to-contribute)
- [Reporting issues](#reporting-issues)
- [License](#license)
## Prerequisites
Start with a clean install of the [latest release of Raspbian](https://www.raspberrypi.org/downloads/raspbian/) (currently Buster). Raspbian Buster Lite is recommended.
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 dist-upgrade
sudo apt-get full-upgrade
sudo reboot
```
2. 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 an older Raspberry Pi without an onboard WiFi 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.
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 RaspberryPi's shell prompt:
Install RaspAP from your device's shell prompt:
```sh
curl -sL https://install.raspap.com | bash
```
The [installer](https://github.com/billz/raspap-webgui/wiki/Quick-Installer-usage) will complete the steps in the manual installation (below) for you.
The [installer](https://docs.raspap.com/quick/) will complete the steps in the manual installation (below) for you.
After the reboot at the end of the installation the wireless network will be
configured as an access point as follows:
* IP address: 10.3.141.1
* Username: admin
* Password: secret
* DHCP range: 10.3.141.50 to 10.3.141.255
* DHCP range: 10.3.141.50 — 10.3.141.254
* SSID: `raspi-webgui`
* Password: ChangeMe
**Note:** As the name suggests, the Quick Installer is a great way to quickly setup a new AP. However, it does not automagically detect the unique configuration of your RPi. Best results are obtained by connecting an RPi to ethernet (`eth0`) or as a WiFi client, also known as managed mode, with `wlan0`. For the latter, refer to [this FAQ](https://github.com/billz/raspap-webgui/wiki/FAQs#how-do-i-prepare-the-sd-card-to-connect-to-wifi-in-headless-mode). Please [read this](https://github.com/billz/raspap-webgui/wiki/Reporting-issues) before reporting an issue.
**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.
## Join Insiders
[![](https://i.imgur.com/eml7k0b.png)](https://github.com/sponsors/RaspAP/)
RaspAP is free software, but powered by _your_ support. If you find RaspAP useful for your personal or commercial projects, [become an Insider](https://github.com/sponsors/RaspAP/) and get early access to [exclusive features](https://docs.raspap.com/insiders/#exclusive-features) in the [Insiders Edition](https://docs.raspap.com/insiders/).
A tangible side benefit of sponsorship is that **Insiders** are able to help _steer future development of RaspAP_. This is done through Insiders' team access to discussions, feature requests, issues and more in the private GitHub repository.
## WireGuard support
![](https://i.imgur.com/5YDv37e.png)
WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be considerably more performant than OpenVPN, and is generally regarded as the most secure, easiest to use, and simplest VPN solution for modern Linux distributions.
WireGuard may be optionally installed by the [Quick Installer](https://docs.raspap.com/quick/). Once this is done, you can manage local (server) settings, create a peer configuration and control the `wg-quick` service with RaspAP.
Details are [provided here](https://docs.raspap.com/wireguard/).
## OpenVPN support
![](https://i.imgur.com/ta7tCon.png)
OpenVPN may be optionally installed by the Quick Installer. Once this is done, you can [manage client configurations](https://docs.raspap.com/openvpn/) and the `openvpn-client` service with RaspAP.
To configure an OpenVPN client, upload a valid .ovpn file and, optionally, specify your login credentials. RaspAP will store your client configuration and add firewall rules to forward traffic from OpenVPN's `tun0` interface to your configured wireless interface.
See our [OpenVPN documentation](https://docs.raspap.com/openvpn/) 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.
Details are [provided here](https://docs.raspap.com/adblock/).
## Bridged AP
By default RaspAP configures a routed AP for your clients to connect to. A bridged AP configuration is also possible. Slide the **Bridged AP mode** toggle under the **Advanced** tab of **Configure hotspot**, then save and restart the hotspot.
**Note:** In bridged mode, all routing capabilities are handled by your upstream router. Because your router assigns IP addresses to your device's hotspot and its clients, you might not be able to reach the RaspAP web interface from the default `10.3.141.1` address. Instead use your RPi's hostname followed by `.local` to access the RaspAP web interface. With Raspbian default settings, this should look like `raspberrypi.local`. Alternate methods are [discussed here](https://www.raspberrypi.org/documentation/remote-access/ip-address.md).
More information on Bridged AP mode is provided [in our documentation](https://docs.raspap.com/bridged/).
## Simultaneous AP and Wifi client
RaspAP lets you easily create an AP with a Wifi client configuration. With your RPi 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.
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.
![](https://i.imgur.com/YObvd32.gif)
**Note:** This option is disabled until you configure your RPi as a wireless client. For a Raspberry Pi operating in [managed mode](https://github.com/billz/raspap-webgui/wiki/FAQs#how-do-i-prepare-the-sd-card-to-connect-to-wifi-in-headless-mode) without an `eth0` connection, this configuration must be enabled _before_ a reboot.
## Support us
RaspAP is free software, but powered by your support. If you find RaspAP useful for your personal or commercial projects, please [become a sponsor](https://github.com/sponsors/billz) or make a one-time donation with [Beerpay](https://beerpay.io/billz/raspap-webgui). Either option makes a big difference!
[![Beerpay](https://beerpay.io/billz/raspap-webgui/badge.svg)](https://beerpay.io/billz/raspap-webgui)
**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
These steps apply to the latest release of Raspbian (currently [Buster](https://www.raspberrypi.org/downloads/raspbian/)). Notes for previously released versions are provided, where applicable. Start off by installing git, lighttpd, php7, hostapd and dnsmasq.
```sh
sudo apt-get install git lighttpd php7.1-cgi hostapd dnsmasq vnstat
```
**Note:** for Raspbian Stretch, replace `php7.1-cgi` with `php7.0-cgi`. For Raspbian Jessie and older versions, use `php5-cgi`. After that, enable PHP for lighttpd and restart it for the settings to take effect.
```sh
sudo lighttpd-enable-mod fastcgi-php
sudo service lighttpd restart
```
Now comes the fun part. For security reasons, the `www-data` user which lighttpd runs under is not allowed to start or stop daemons, or run commands like ifdown and ifup, all of which we want our page to do.
So what I have done is added the `www-data` user to the sudoers file, but with restrictions on what commands the user can run. Add the following to the end of `/etc/sudoers`:
Detailed manual setup instructions are provided [on our documentation site](https://docs.raspap.com/manual/).
```sh
www-data ALL=(ALL) NOPASSWD:/sbin/ifdown
www-data ALL=(ALL) NOPASSWD:/sbin/ifup
www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wpa_supplicant/wpa_supplicant.conf
www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wpa_supplicant/wpa_supplicant-wlan[0-9].conf
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant.conf
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant-wlan[0-9].conf
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] scan_results
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] scan
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] reconfigure
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] select_network
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/hostapddata /etc/hostapd/hostapd.conf
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start hostapd.service
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop hostapd.service
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start dnsmasq.service
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop dnsmasq.service
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start openvpn-client@client
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop openvpn-client@client
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/openvpn.ovpn /etc/openvpn/client/client.conf
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/authdata /etc/openvpn/client/login.conf
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dnsmasqdata /etc/dnsmasq.conf
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dhcpddata /etc/dhcpcd.conf
www-data ALL=(ALL) NOPASSWD:/sbin/shutdown -h now
www-data ALL=(ALL) NOPASSWD:/sbin/reboot
www-data ALL=(ALL) NOPASSWD:/sbin/ip link set wlan[0-9] down
www-data ALL=(ALL) NOPASSWD:/sbin/ip link set wlan[0-9] up
www-data ALL=(ALL) NOPASSWD:/sbin/ip -s a f label wlan[0-9]
www-data ALL=(ALL) NOPASSWD:/bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/enablelog.sh
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/disablelog.sh
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/servicestart.sh
www-data ALL=(ALL) NOPASSWD:/etc/raspap/lighttpd/configport.sh
www-data ALL=(ALL) NOPASSWD:/etc/raspap/openvpn/configauth.sh
```
## 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 FAQ](https://docs.raspap.com/faq/#80211ac) for more information.
Once those modifications are done, git clone the files to `/var/www/html`.
**Note:** for older versions of Raspbian (before Jessie, May 2016) use
`/var/www` instead.
```sh
sudo rm -rf /var/www/html
sudo git clone https://github.com/billz/raspap-webgui /var/www/html
```
Move the high-res favicons to the web root.
```
sudo mv /var/www/html/app/icons/* /var/www/html
```
Set the files ownership to `www-data` user.
```sh
sudo chown -R www-data:www-data /var/www/html
```
Move the RaspAP configuration file to the correct location.
```sh
sudo mkdir /etc/raspap
sudo mv /var/www/html/raspap.php /etc/raspap/
sudo chown -R www-data:www-data /etc/raspap
```
Move the HostAPD logging and service control shell scripts to the correct location.
```sh
sudo mkdir /etc/raspap/hostapd
sudo mv /var/www/html/installers/*log.sh /etc/raspap/hostapd
sudo mv /var/www/html/installers/service*.sh /etc/raspap/hostapd
```
Set ownership and permissions for logging and service control scripts.
```sh
sudo chown -c root:www-data /etc/raspap/hostapd/*.sh
sudo chmod 750 /etc/raspap/hostapd/*.sh
```
Add the following lines to `/etc/rc.local` before `exit 0`.
```sh
echo 1 > /proc/sys/net/ipv4/ip_forward #RASPAP
iptables -t nat -A POSTROUTING -j MASQUERADE #RASPAP
iptables -t nat -A POSTROUTING -s 192.168.50.0/24 ! -d 192.168.50.0/24 -j MASQUERADE #RASPAP
```
Force a reload of new settings in `/etc/rc.local`.
```sh
sudo systemctl restart rc-local.service
sudo systemctl daemon-reload
```
Unmask and enable the hostapd service.
```sh
sudo systemctl unmask hostapd.service
sudo systemctl enable hostapd.service
```
Move the raspap service to the correct location and enable it.
```
sudo mv /var/www/html/installers/raspap.service /lib/systemd/system
sudo systemctl enable raspap.service
```
Copy the configuration files for dhcpcd, dnsmasq, and hostapd.
```
sudo mv /var/www/html/config/default_hostapd /etc/default/hostapd
sudo mv /var/www/html/config/hostapd.conf /etc/hostapd/hostapd.conf
sudo mv /var/www/html/config/dnsmasq.conf /etc/dnsmasq.conf
sudo mv /var/www/html/config/dhcpcd.conf /etc/dhcpcd.conf
sudo mv /var/www/html/config/config.php /var/www/html/includes/
```
(Optional) Optimize PHP
```
sudo sed -i -E 's/^session\.cookie_httponly\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/session.cookie_httponly = 1/' /etc/php/7.1/cgi/php.ini
sudo sed -i -E 's/^;?opcache\.enable\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/opcache.enable = 1/' /etc/php/7.1/cgi/php.ini
sudo phpenmod opcache
```
Reboot and it should be up and running!
```sh
sudo reboot
```
## Supported operating systems
RaspAP was originally made for Raspbian, but now also installs on the following Debian-based distros.
The default username is 'admin' and the default password is 'secret'.
| Distribution | Release | Architecture | Support |
|---|:---:|:---:|:---:|
| Raspberry Pi OS | (32-bit) Lite Bullseye | ARM | Official |
| 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 |
![](https://i.imgur.com/luiyYNw.png)
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 [wiki](https://github.com/billz/raspap-webgui/wiki/Translations#raspap-in-your-language).
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/).
The following translations are currently maintained by the project:
- Deutsch
- Français
- Italiano
- Português
- Svenska
- Nederlands
- 简体中文 (Chinese Simplified)
- Indonesian
- 한국어 (Korean)
- 日本語 (Japanese)
- Tiếng Việt (Vietnamese)
- Čeština
- Русский
- Español
- Finnish
- Sinhala
- Türkçe
If your language is not in the list above, why not [contribute a translation](https://github.com/billz/raspap-webgui/wiki/Translations#contributing-a-translation)? Contributors will receive credit as the original translators.
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://github.com/billz/raspap-webgui/wiki/SSL-certificates-(Quick-Installer)) with `mkcert`. The installer automates the manual steps [described in the wiki](https://github.com/billz/raspap-webgui/wiki/SSL-(Manual-steps)), including configuring lighttpd with SSL 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.
Simply append the `-c` or `--cert` option to the Quick Installer, like so:
@ -230,27 +148,30 @@ 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 [on our wiki](https://github.com/billz/raspap-webgui/wiki/SSL-certificates-(Quick-Installer)).
## OpenVPN support
OpenVPN may be optionally installed by the Quick Installer. Once this is done, you can managage a client configuration and the `openvpn-client` service with RaspAP.
To configure an OpenVPN client, upload a valid .ovpn file and, optionally, specify your login credentials. RaspAP will store your client configuration and add firewall rules to forward traffic from OpenVPN's `tun0` interface to your configured wireless interface.
**Note**: this feature is currently in beta. Please [read this](https://github.com/billz/raspap-webgui/wiki/FAQs#-openvpn-fails-to-start-andor-i-have-no-internet-help) before reporting an issue.
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`.
2. Open an issue in the repository describing the feature contribution you'd like to make.
3. Commit changes in your feature branch.
4. Open a pull request and reference the initial issue in the pull request message.
1. File an issue in the repository describing the contribution you'd like to make. This will help us get you started on the
right foot.
2. Fork the project in your account and create a new branch: `your-great-feature`.
3. Commit your changes in that branch.
4. Open a pull request, and reference the initial issue in the pull request message.
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.
Find out more about our [coding style guidelines and recommended tools](CONTRIBUTING.md).
## Reporting issues
Please [read this](https://github.com/billz/raspap-webgui/wiki/Reporting-issues) before reporting a bug.
Please [read this](https://docs.raspap.com/issues/) before reporting a bug.
## Contributors
### Code Contributors
This project exists thanks to all the awesome people who [contribute](CONTRIBUTING.md) their time and expertise.
<a href="https://github.com/raspap/raspap-webgui/graphs/contributors"><img src="https://opencollective.com/raspap/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
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**](https://github.com/sponsors/RaspAP).
Learn more about [how sponsorship works](https://docs.raspap.com/insiders/#how-sponsorship-works), and how easy it is to get access to Insiders.
## License
See the [LICENSE](./LICENSE) file.

27
SECURITY.md Normal file
View File

@ -0,0 +1,27 @@
# 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

@ -0,0 +1,24 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/config.php';
if (isset($_POST['blocklist_id'])) {
$blocklist_id = $_POST['blocklist_id'];
$notracking_url = "https://raw.githubusercontent.com/notracking/hosts-blocklists/master/";
switch ($blocklist_id) {
case "notracking-hostnames":
$file = "hostnames.txt";
break;
case "notracking-domains":
$file = "domains.txt";
break;
}
$blocklist = $notracking_url . $file;
exec("sudo /etc/raspap/adblock/update_blocklist.sh $blocklist $file " .RASPI_ADBLOCK_LISTPATH, $return);
$jsonData = ['return'=>$return];
echo json_encode($jsonData);
}

View File

@ -1,6 +1,6 @@
<?php
require('../../includes/csrf.php');
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once RASPI_CONFIG.'/raspap.php';
@ -30,35 +30,42 @@ if (strlen($interface) > IFNAMSIZ) {
require_once './get_bandwidth_hourly.php';
exec(sprintf('vnstat -i %s --json ', escapeshellarg($interface)), $jsonstdoutvnstat,
$exitcodedaily);
exec(
sprintf('vnstat -i %s --json ', escapeshellarg($interface)), $jsonstdoutvnstat,
$exitcodedaily
);
if ($exitcodedaily !== 0) {
exit('vnstat error');
exit('vnstat error');
}
$jsonobj = json_decode($jsonstdoutvnstat[0], true);
$timeunits = filter_input(INPUT_GET, 'tu');
if ($timeunits === 'm') {
// months
$jsonData = $jsonobj['interfaces'][0]['traffic']['months'];
$jsonData = $jsonobj['interfaces'][0]['traffic']['month'];
} else {
// default: days
$jsonData = $jsonobj['interfaces'][0]['traffic']['days'];
$jsonData = $jsonobj['interfaces'][0]['traffic']['day'];
}
$datasizeunits = filter_input(INPUT_GET, 'dsu');
$dsu_factor = $datasizeunits == "mb" ? 1024 * 1024 : 1024;
header('X-Content-Type-Options: nosniff');
header('Content-Type: application/json');
echo '[ ';
$firstelm = true;
for ($i = count($jsonData) - 1; $i >= 0; --$i) {
if ($timeunits === 'm') {
$dt = DateTime::createFromFormat('Y n', $jsonData[$i]['date']['year'].' '.
$jsonData[$i]['date']['month']);
$dt = DateTime::createFromFormat(
'Y n', $jsonData[$i]['date']['year'].' '.
$jsonData[$i]['date']['month']
);
} else {
$dt = DateTime::createFromFormat('Y n j', $jsonData[$i]['date']['year'].' '.
$dt = DateTime::createFromFormat(
'Y n j', $jsonData[$i]['date']['year'].' '.
$jsonData[$i]['date']['month'].' '.
$jsonData[$i]['date']['day']);
$jsonData[$i]['date']['day']
);
}
if ($firstelm) {
@ -67,13 +74,8 @@ for ($i = count($jsonData) - 1; $i >= 0; --$i) {
echo ',';
}
if ($datasizeunits == 'mb') {
$datasend = round($jsonData[$i]['tx'] / 1024, 0);
$datareceived = round($jsonData[$i]['rx'] / 1024, 0);
} else {
$datasend = $jsonData[$i]['rx'];
$datareceived = $jsonData[$i]['rx'];
}
$datasend = round($jsonData[$i]['tx'] / $dsu_factor, 0);
$datareceived = round($jsonData[$i]['rx'] / $dsu_factor, 0);
if ($timeunits === 'm') {
echo '{ "date": "' , $dt->format('Y-m') , '", "rx": "' , $datareceived ,

View File

@ -1,66 +1,66 @@
<?php
require('../../includes/csrf.php');
if (filter_input(INPUT_GET, 'tu') == 'h') {
header('X-Content-Type-Options: nosniff');
header('Content-Type: application/json');
$data_template = array(
0 => array('date' => '00:00', 'rx' => 0, 'tx' => 0),
1 => array('date' => '01:00', 'rx' => 0, 'tx' => 0),
2 => array('date' => '02:00', 'rx' => 0, 'tx' => 0),
3 => array('date' => '03:00', 'rx' => 0, 'tx' => 0),
4 => array('date' => '04:00', 'rx' => 0, 'tx' => 0),
5 => array('date' => '05:00', 'rx' => 0, 'tx' => 0),
6 => array('date' => '06:00', 'rx' => 0, 'tx' => 0),
7 => array('date' => '07:00', 'rx' => 0, 'tx' => 0),
8 => array('date' => '08:00', 'rx' => 0, 'tx' => 0),
9 => array('date' => '09:00', 'rx' => 0, 'tx' => 0),
10 => array('date' => '10:00', 'rx' => 0, 'tx' => 0),
11 => array('date' => '11:00', 'rx' => 0, 'tx' => 0),
12 => array('date' => '12:00', 'rx' => 0, 'tx' => 0),
13 => array('date' => '13:00', 'rx' => 0, 'tx' => 0),
14 => array('date' => '14:00', 'rx' => 0, 'tx' => 0),
15 => array('date' => '15:00', 'rx' => 0, 'tx' => 0),
16 => array('date' => '16:00', 'rx' => 0, 'tx' => 0),
17 => array('date' => '17:00', 'rx' => 0, 'tx' => 0),
18 => array('date' => '18:00', 'rx' => 0, 'tx' => 0),
19 => array('date' => '19:00', 'rx' => 0, 'tx' => 0),
20 => array('date' => '20:00', 'rx' => 0, 'tx' => 0),
21 => array('date' => '21:00', 'rx' => 0, 'tx' => 0),
22 => array('date' => '22:00', 'rx' => 0, 'tx' => 0),
23 => array('date' => '23:00', 'rx' => 0, 'tx' => 0)
);
exec(sprintf('vnstat -i %s --json h', escapeshellarg($interface)), $jsonstdoutvnstat, $exitcodedaily);
if ($exitcodedaily !== 0) {
exit('vnstat error');
}
$jsonobj = json_decode($jsonstdoutvnstat[0], true)['interfaces'][0];
$jsonData = $jsonobj['traffic']['hours'];
for ($i = count($jsonData) - 1; $i >= 0; --$i) {
$data_template[$jsonData[$i]['id']]['rx'] = round($jsonData[$i]['rx'] / 1024, 0);
$data_template[$jsonData[$i]['id']]['tx'] = round($jsonData[$i]['tx'] / 1024, 0);
}
$data = array();
$hour = $jsonobj['updated']['time']['hour'];
foreach ($data_template as $key => $value) {
if ($key > $hour) {
array_push($data, $value);
}
}
foreach ($data_template as $key => $value) {
if ($key <= $hour) {
array_push($data, $value);
}
}
echo json_encode($data);
exit(0);
}
<?php
require '../../includes/csrf.php';
if (filter_input(INPUT_GET, 'tu') == 'h') {
header('X-Content-Type-Options: nosniff');
header('Content-Type: application/json');
$data_template = array(
0 => array('date' => '00:00', 'rx' => 0, 'tx' => 0),
1 => array('date' => '01:00', 'rx' => 0, 'tx' => 0),
2 => array('date' => '02:00', 'rx' => 0, 'tx' => 0),
3 => array('date' => '03:00', 'rx' => 0, 'tx' => 0),
4 => array('date' => '04:00', 'rx' => 0, 'tx' => 0),
5 => array('date' => '05:00', 'rx' => 0, 'tx' => 0),
6 => array('date' => '06:00', 'rx' => 0, 'tx' => 0),
7 => array('date' => '07:00', 'rx' => 0, 'tx' => 0),
8 => array('date' => '08:00', 'rx' => 0, 'tx' => 0),
9 => array('date' => '09:00', 'rx' => 0, 'tx' => 0),
10 => array('date' => '10:00', 'rx' => 0, 'tx' => 0),
11 => array('date' => '11:00', 'rx' => 0, 'tx' => 0),
12 => array('date' => '12:00', 'rx' => 0, 'tx' => 0),
13 => array('date' => '13:00', 'rx' => 0, 'tx' => 0),
14 => array('date' => '14:00', 'rx' => 0, 'tx' => 0),
15 => array('date' => '15:00', 'rx' => 0, 'tx' => 0),
16 => array('date' => '16:00', 'rx' => 0, 'tx' => 0),
17 => array('date' => '17:00', 'rx' => 0, 'tx' => 0),
18 => array('date' => '18:00', 'rx' => 0, 'tx' => 0),
19 => array('date' => '19:00', 'rx' => 0, 'tx' => 0),
20 => array('date' => '20:00', 'rx' => 0, 'tx' => 0),
21 => array('date' => '21:00', 'rx' => 0, 'tx' => 0),
22 => array('date' => '22:00', 'rx' => 0, 'tx' => 0),
23 => array('date' => '23:00', 'rx' => 0, 'tx' => 0)
);
exec(sprintf('vnstat -i %s --json h', escapeshellarg($interface)), $jsonstdoutvnstat, $exitcodedaily);
if ($exitcodedaily !== 0) {
exit('vnstat error');
}
$datasizeunits = filter_input(INPUT_GET, 'dsu');
$dsu_factor = $datasizeunits == "mb" ? 1024 * 1024 : 1024;
$jsonobj = json_decode($jsonstdoutvnstat[0], true)['interfaces'][0];
$jsonData = $jsonobj['traffic']['hour'];
for ($i = count($jsonData) - 1; $i >= 0 && $i >= count($jsonData)-25; --$i) {
$data_template[$jsonData[$i]['time']['hour']]['rx'] = round($jsonData[$i]['rx'] / $dsu_factor, 0);
$data_template[$jsonData[$i]['time']['hour']]['tx'] = round($jsonData[$i]['tx'] / $dsu_factor, 0);
}
$data = array();
$hour = $jsonobj['updated']['time']['hour'];
foreach ($data_template as $key => $value) {
if ($key > $hour) {
array_push($data, $value);
}
}
foreach ($data_template as $key => $value) {
if ($key <= $hour) {
array_push($data, $value);
}
}
echo json_encode($data);
exit(0);
}

13
ajax/logging/clearlog.php Normal file
View File

@ -0,0 +1,13 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/functions.php';
if (isset($_POST['logfile'])) {
$logfile = escapeshellcmd($_POST['logfile']);
// truncate requested log file
exec("sudo truncate -s 0 $logfile", $return);
echo json_encode($return);
}

View File

@ -0,0 +1,35 @@
<?php
require_once '../../includes/config.php';
require_once '../../includes/session.php';
require_once '../../includes/functions.php';
if (isset($_POST['csrf_token'])) {
if (csrfValidateRequest() && !CSRFValidate()) {
handleInvalidCSRFToken();
}
$return = 0;
$path = "../../config";
$configs = array(
array("src" => $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
);
foreach ($configs as $config) {
try {
$tmp = file_get_contents($config["src"]);
file_put_contents($config["tmp"], $tmp);
system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
} catch (Exception $e) {
$return = $e->getCode();
}
}
$jsonData = ['return'=>$return];
echo json_encode($jsonData);
} else {
handleInvalidCSRFToken();
}

View File

@ -1,42 +0,0 @@
<?php
require('../../includes/csrf.php');
include_once('../../includes/config.php');
include_once('../../includes/functions.php');
if (isset($_POST['generate'])) {
$cnfNetworking = array_diff(scandir(RASPI_CONFIG_NETWORKING, 1), array('..','.','dhcpcd.conf'));
$cnfNetworking = array_combine($cnfNetworking, $cnfNetworking);
$strConfFile = "";
foreach ($cnfNetworking as $index => $file) {
if ($index != "defaults") {
$cnfFile = parse_ini_file(RASPI_CONFIG_NETWORKING.'/'.$file, false, INI_SCANNER_RAW);
if ($cnfFile['static'] === 'true') {
$strConfFile .= "interface ".$cnfFile['interface']."\n";
$strConfFile .= "static ip_address=".$cnfFile['ip_address']."\n";
$strConfFile .= "static routers=".$cnfFile['routers']."\n";
$strConfFile .= "static domain_name_servers=".$cnfFile['domain_name_server']."\n";
} elseif ($cnfFile['static'] === 'false' && $cnfFile['failover'] === 'true') {
$strConfFile .= "profile static_".$cnfFile['interface']."\n";
$strConfFile .= "static ip_address=".$cnfFile['ip_address']."\n";
$strConfFile .= "static routers=".$cnfFile['routers']."\n";
$strConfFile .= "static domain_name_servers=".$cnfFile['domain_name_server']."\n\n";
$strConfFile .= "interface ".$cnfFile['interface']."\n";
$strConfFile .= "fallback static_".$cnfFile['interface']."\n\n";
} else {
$strConfFile .= "#DHCP configured for ".$cnfFile['interface']."\n\n";
}
} else {
$strConfFile .= file_get_contents(RASPI_CONFIG_NETWORKING.'/'.$index)."\n\n";
}
}
if (file_put_contents(RASPI_CONFIG_NETWORKING.'/dhcpcd.conf', $strConfFile)) {
exec('sudo /bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf');
$output = ['return'=>0,'output'=>'Settings successfully applied'];
} else {
$output = ['return'=>2,'output'=>'Unable to write to apply settings'];
}
echo json_encode($output);
}

View File

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

View File

@ -0,0 +1,17 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/config.php';
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
$arrConfig = array();
foreach ($hostapdconfig as $hostapdconfigline) {
if (strlen($hostapdconfigline) === 0) {
continue;
}
$arrLine = explode("=", $hostapdconfigline);
$arrConfig[$arrLine[0]]=$arrLine[1];
};
$channel = intval($arrConfig['channel']);
echo json_encode($channel);

View File

@ -0,0 +1,42 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/locale.php';
if (isset($_POST['interface'])) {
define( 'NL80211_BAND_24GHZ', 0x1 );
define( 'NL80211_BAND_5GHZ', 0x2 );
$iface = escapeshellcmd($_POST['interface']);
// 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,23 +0,0 @@
<?php
require('../../includes/csrf.php');
include_once('../../includes/config.php');
include_once('../../includes/functions.php');
if (isset($_POST['interface'])) {
$int = preg_replace('/[^a-z0-9]/', '', $_POST['interface']);
if (!file_exists(RASPI_CONFIG_NETWORKING.'/'.$int.'.ini')) {
touch(RASPI_CONFIG_NETWORKING.'/'.$int.'.ini');
}
$intConfig = parse_ini_file(RASPI_CONFIG_NETWORKING.'/'.$int.'.ini', false, INI_SCANNER_RAW);
$jsonData = ['return'=>1,'output'=>['intConfig'=>$intConfig]];
echo json_encode($jsonData);
// Todo - get dhcp lease information from `dhcpcd -U eth0` ? maybe ?
} else {
$jsonData = ['return'=>2,'output'=>['Error getting data']];
echo json_encode($jsonData);
}

View File

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

View File

@ -0,0 +1,58 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/config.php';
$interface = $_GET['iface'];
if (isset($interface)) {
// fetch dnsmasq.conf settings for interface
exec('cat '. escapeshellarg(RASPI_DNSMASQ_PREFIX.$interface.'.conf'), $return);
$conf = ParseConfig($return);
$dhcpdata['DHCPEnabled'] = empty($conf) ? false : true;
$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']) ? $conf['server'] : [ $conf['server'] ];
$dhcpdata['upstreamServersEnabled'] = empty($conf['server']) ? false: true;
$dhcpdata['upstreamServers'] = array_filter($upstreamServers);
preg_match('/([0-9]*)([a-z])/i', $dhcpdata['leaseTime'], $arrRangeLeaseTime);
$dhcpdata['leaseTime'] = $arrRangeLeaseTime[1];
$dhcpdata['leaseTimeInterval'] = $arrRangeLeaseTime[2];
if (isset($conf['dhcp-option'])) {
$arrDns = explode(",", $conf['dhcp-option']);
if ($arrDns[0] == '6') {
if (count($arrDns) > 1) {
$dhcpdata['DNS1'] = $arrDns[1];
}
if (count($arrDns) > 2) {
$dhcpdata['DNS2'] = $arrDns[2];
}
}
}
// fetch dhcpcd.conf settings for interface
$conf = file_get_contents(RASPI_DHCPCD_CONFIG);
preg_match('/^#\sRaspAP\s'.$interface.'\s.*?(?=\s*+$)/ms', $conf, $matched);
preg_match('/metric\s(\d*)/', $matched[0], $metric);
preg_match('/static\sip_address=(.*)/', $matched[0], $static_ip);
preg_match('/static\srouters=(.*)/', $matched[0], $static_routers);
preg_match('/static\sdomain_name_server=(.*)/', $matched[0], $static_dns);
preg_match('/fallback\sstatic_'.$interface.'/', $matched[0], $fallback);
preg_match('/(?:no)?gateway/', $matched[0], $gateway);
$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";
echo json_encode($dhcpdata);
}

View File

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

View File

@ -0,0 +1,22 @@
<?php
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);
exec("sudo mv $pubkey_tmp $pubkey", $return);
echo json_encode($wgdata);
}

View File

@ -1,34 +0,0 @@
<?php
require('../../includes/csrf.php');
include_once('../../includes/config.php');
include_once('../../includes/functions.php');
if (isset($_POST['interface'])) {
$int = $_POST['interface'];
$cfg = [];
$file = $int.".ini";
$ip = $_POST[$int.'-ipaddress'];
$netmask = mask2cidr($_POST[$int.'-netmask']);
$dns1 = $_POST[$int.'-dnssvr'];
$dns2 = $_POST[$int.'-dnssvralt'];
$cfg['interface'] = $int;
$cfg['routers'] = $_POST[$int.'-gateway'];
$cfg['ip_address'] = $ip."/".$netmask;
$cfg['domain_name_server'] = $dns1." ".$dns2;
$cfg['static'] = $_POST[$int.'-static'];
$cfg['failover'] = $_POST[$int.'-failover'];
if (write_php_ini($cfg, RASPI_CONFIG_NETWORKING.'/'.$file)) {
$jsonData = ['return'=>0,'output'=>['Successfully Updated Network Configuration']];
} else {
$jsonData = ['return'=>1,'output'=>['Error saving network configuration to file']];
}
} else {
$jsonData = ['return'=>2,'output'=>'Unable to detect interface'];
}
echo json_encode($jsonData);

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,10 @@
<?php
require('../../includes/csrf.php');
include_once('../../includes/config.php');
include_once('../../includes/functions.php');
include_once('../../includes/wifi_functions.php');
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/defaults.php';
require_once '../../includes/functions.php';
require_once '../../includes/wifi_functions.php';
$networks = [];
$network = null;
@ -12,5 +13,11 @@ $ssid = null;
knownWifiStations($networks);
nearbyWifiStations($networks, !isset($_REQUEST["refresh"]));
connectedWifiStations($networks);
sortNetworksByRSSI($networks);
foreach ($networks as $ssid => $network) $networks[$ssid]["ssidutf8"] = ssid2utf8( $ssid );
echo renderTemplate('wifi_stations', compact('networks'));
$connected = array_filter($networks, function($n) { return $n['connected']; } );
$known = array_filter($networks, function($n) { return !$n['connected'] && $n['configured']; } );
$nearby = array_filter($networks, function($n) { return !$n['configured']; } );
echo renderTemplate('wifi_stations', compact('networks', 'connected', 'known', 'nearby'), true);

View File

@ -0,0 +1,27 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/functions.php';
if (isset($_POST['cfg_id'])) {
$ovpncfg_id = escapeshellcmd($_POST['cfg_id']);
$ovpncfg_client = RASPI_OPENVPN_CLIENT_PATH.$ovpncfg_id.'_client.conf';
$ovpncfg_login = RASPI_OPENVPN_CLIENT_PATH.$ovpncfg_id.'_login.conf';
// remove existing client config +login and symbolically link the selected one
system("sudo rm ".RASPI_OPENVPN_CLIENT_CONFIG, $return);
system("sudo ln -s $ovpncfg_client ".RASPI_OPENVPN_CLIENT_CONFIG, $return);
system("sudo rm ".RASPI_OPENVPN_CLIENT_LOGIN, $return);
system("sudo ln -s $ovpncfg_login ".RASPI_OPENVPN_CLIENT_LOGIN, $return);
// restart service
exec("sudo /bin/systemctl stop openvpn-client@client", $return);
sleep(1);
exec("sudo /bin/systemctl enable openvpn-client@client", $return);
sleep(1);
exec("sudo /bin/systemctl start openvpn-client@client", $return);
echo json_encode($return);
}

View File

@ -0,0 +1,14 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/config.php';
require_once '../../includes/functions.php';
if (isset($_POST['cfg_id'])) {
$ovpncfg_id = escapeshellcmd($_POST['cfg_id']);
$ovpncfg_files = pathinfo(RASPI_OPENVPN_CLIENT_LOGIN, PATHINFO_DIRNAME).'/'.$ovpncfg_id.'_*.conf';
exec("sudo rm $ovpncfg_files", $return);
$jsonData = ['return'=>$return];
echo json_encode($jsonData);
}

230
app/css/all.css Normal file
View File

@ -0,0 +1,230 @@
/*
Name: all.css
Author: @billz
Author URI: https://github.com/billz
Description: Classes shared by all themes
License: GNU General Public License v3.0
*/
/* 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; }
}
.sidebar-brand-text {
text-transform: none;
color: #212529;
font-size: 2.0rem;
font-weight: 500;
font-family: Helvetica, Arial, sans-serif;
}
.h-underlined {
border-bottom: 1px solid #e3e6f0;
padding-bottom: 0.3rem;
}
.navbar-logo {
margin-top: 0.5em;
margin-left: 0.5em;
}
.page-header {
font-size: 26pt;
margin: 20px 0 20px;
}
.info-item {
text-transform: uppercase;
font-size: 0.7em;
color: #858796;
}
.info-value {
font-size: 0.7rem;
margin-left: 0.7rem;
}
.info-item-xs {
font-size: 0.7rem;
margin-left: 0.3rem;
}
.info-item-wifi {
width: 6rem;
float: left;
}
.service-status {
border-width: 0;
align-items: center;
}
.service-status-up {
color: #a1ec38 !important;
}
.service-status-warn {
color: #f6f044 !important;
}
.service-status-down {
color: #f80107 !important;
animation: flash 1s linear infinite;
}
@keyframes flash {
50% {
opacity: 0;
}
}
.logoutput {
width:100%;
height: 20rem;
border: 1px solid #d1d3e2;
border-radius: .35rem;
}
.dhcp-static-leases {
margin-top: 1em;
margin-bottom: 1em;
}
.dhcp-static-lease-row {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.loading-spinner {
background: url("../../app/img/loading-spinner.gif") no-repeat scroll center center transparent;
min-height: 450px;
width: 100%;
}
@media (min-width: 576px) {
.card-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) 50%;
grid-gap: 1rem;
}
}
.toggle-off.btn {
padding-left: 1.2rem;
font-size: 0.9rem!important;
}
.toggle-on.btn {
font-size: 0.9rem!important;
}
canvas#divDBChartBandwidthhourly {
height: 350px!important;
}
.chart-container {
height: 150px;
width: 200px;
}
.dbChart {
display: flex;
height: 80%;
}
.table {
margin-bottom: 0rem;
}
.check-hidden {
visibility: hidden;
}
.check-progress {
color: #999;
}
.fa-check {
color: #90ee90;
}
.fa-times {
color: #ff4500;
}
button.btn.btn-light.js-toggle-password {
border: 1px solid lightgrey;
}
.signal-icon {
margin-top: 2px;
height: 16px;
width: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
}
.signal-icon .signal-bar {
width: 4px;
border-radius: 1px;
opacity: 30%;
background: <?php echo $color; ?>;
}
.signal-icon .signal-bar:nth-child(1) { height: 40%; }
.signal-icon .signal-bar:nth-child(2) { height: 70%; }
.signal-icon .signal-bar:nth-child(3) { height: 100%; }
.signal-icon.weak .signal-bar:nth-child(1),
.signal-icon.medium .signal-bar:nth-child(1),
.signal-icon.medium .signal-bar:nth-child(2),
.signal-icon.strong .signal-bar:nth-child(1),
.signal-icon.strong .signal-bar:nth-child(2),
.signal-icon.strong .signal-bar:nth-child(3)
{ opacity: 100%; }.signal-icon {
margin-top: 2px;
height: 16px;
width: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
}
.signal-icon .signal-bar {
width: 4px;
border-radius: 1px;
opacity: 30%;
}
.signal-icon .signal-bar:nth-child(1) { height: 40%; }
.signal-icon .signal-bar:nth-child(2) { height: 70%; }
.signal-icon .signal-bar:nth-child(3) { height: 100%; }
.signal-icon.weak .signal-bar:nth-child(1),
.signal-icon.medium .signal-bar:nth-child(1),
.signal-icon.medium .signal-bar:nth-child(2),
.signal-icon.strong .signal-bar:nth-child(1),
.signal-icon.strong .signal-bar:nth-child(2),
.signal-icon.strong .signal-bar:nth-child(3)
{ opacity: 100%; }
.gs-edit {
border: 1px dashed #ccc;
background-color: #f1faee;
border-radius: 4px;
}
figcaption.figure-caption a {
color: #858796;
}
button > i.fas {
pointer-events: none;
}

View File

@ -1,193 +0,0 @@
body {
color: #212529;
}
.page-header {
margin: 20px 0 20px;
}
.page-header .logo {
margin-right: 5px;
}
/* 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; }
.js-wifi-stations { margin-left: -0.5rem; margin-right: -0.5rem; }
h4.mt-3 { margin-left: 0.5rem; }
}
.sidebar {
background-color: #f8f9fc;
}
.sidebar-brand-text {
text-transform: none;
color: #212529;
font-size: 2.0rem;
font-weight: 500;
font-family: Helvetica, Arial, sans-serif;
}
.sidebar .nav-item.active .nav-link {
font-weight: 500;
}
.card .card-header {
border-color: #d8224c;
background-color: #d8224c;
color: #fff;
}
.card-footer {
background-color: #f2f1f0;
}
.nav-item {
font-size: 0.85rem;
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-link {
font-size: 1.0rem;
}
.nav-tabs a.nav-link {
color: #6e707e;
}
a.nav-link.active {
font-weight: bolder;
}
.sidebar .nav-item .nav-link {
padding: 0.6rem;
}
.alert-success {
background-color: #d4edda;
}
.btn-primary {
color: #d8224c;
background-color: #fff;
border-color: #d8224c;
}
.btn-warning {
color: #333;
}
.btn-primary:hover {
background-color: #c61931;
border-color: #c61931;
}
i.fa.fa-bars {
color: #d1d3e2;
}
i.fa.fa-bars:hover{
color: #6e707e;
}
.info-item {
width: 10rem;
float: left;
}
.info-item-wifi {
width: 6rem;
float: left;
}
.webconsole {
width:100%;
height:100%;
border:1px solid;
}
#console {
height:500px;
}
.systemtabcontent {
height:100%;
min-height:500px;
}
.service-status {
border-width: 0;
}
.service-status-up {
color: green;
}
.service-status-down {
color: red;
animation: flash 1s linear infinite;
}
@keyframes flash {
50% {
opacity: 0;
}
}
.logoutput {
width:100%;
height: 20rem;
border: 1px solid #d1d3e2;
border-radius: .35rem;
}
pre.unstyled {
border-width: 0;
background-color: transparent;
padding: 0;
}
.dhcp-static-leases {
margin-top: 1em;
margin-bottom: 1em;
}
.dhcp-static-lease-row {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.loading-spinner {
background: url("../../app/img/loading-spinner.gif") no-repeat scroll center center transparent;
min-height: 150px;
width: 100%;
}
.js-reload-wifi-stations {
min-width: 10rem;
}
.sidebar.toggled .nav-item .nav-link span {
display: none;
} .sidebar .nav-item .nav-link i,
.sidebar .nav-item .nav-link span {
font-size: 1.0rem;
}
.btn-warning:hover {
color: #000;
}
.toggle-off.btn {
padding-left: 1.2rem;
font-size: 0.9rem!important;
}
.toggle-on.btn {
font-size: 0.9rem!important;
}

115
app/css/custom.php Normal file
View File

@ -0,0 +1,115 @@
<?php header("Content-Type: text/css; charset=utf-8"); ?>
<?php
require_once '../../includes/functions.php';
$color = getColorOpt();
?>
/*
Theme Name: RaspAP default
Author: @billz
Author URI: https://github.com/billz
Description: Default theme for RaspAP
License: GNU General Public License v3.0
*/
@import url('all.css');
body {
color: #212529;
}
.sidebar {
background-color: #f8f9fc;
}
.sidebar .nav-item.active .nav-link {
font-weight: 500;
}
.card .card-header, .modal-header {
border-color: <?php echo $color; ?>;
color: #fff;
background-color: <?php echo $color; ?>;
}
.modal-header {
border-radius: 0px;
}
.btn-primary {
color: <?php echo $color; ?>;
border-color: <?php echo $color; ?>;
background-color: #fff;
}
.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;
}
.nav-tabs a.nav-link {
color: #6e707e;
}
a.nav-link.active {
font-weight: bolder;
}
.sidebar .nav-item .nav-link {
padding: 0.6rem 0.6rem 0.6rem 1.0rem;
}
.alert-success {
background-color: #d4edda;
}
.btn-primary {
background-color: #fff;
}
.btn-warning {
color: #333;
}
.btn-primary:hover {
background-color: <?php echo $color; ?>;
border-color: <?php echo $color; ?>;
}
i.fa.fa-bars {
color: #d1d3e2;
}
i.fa.fa-bars:hover{
color: #6e707e;
}
pre.unstyled {
border-width: 0;
background-color: transparent;
padding: 0;
}
.sidebar.toggled .nav-item .nav-link span {
display: none;
} .sidebar .nav-item .nav-link i,
.sidebar .nav-item .nav-link span {
font-size: 1.0rem;
}
.btn-warning:hover {
color: #000;
}
.signal-icon .signal-bar {
background: <?php echo $color; ?>;
}

View File

@ -1,6 +1,15 @@
/*
Theme Name: HackerNews
Author: @billz
Author URI: https://github.com/billz
Description: A theme inspired by HackerNews for RaspAP
License: GNU General Public License v3.0
*/
@import url('all.css');
html * {
font-family: Verdana, Geneva, sans-serif;
font-size: 0.9rem;
color: #828282;
}
@ -12,6 +21,10 @@ a:focus, a:hover {
color: #666;
}
h2 {
font-size: 2rem !important;
}
h4 {
font-size: 1.3rem;
color: #212529;
@ -22,12 +35,12 @@ h5.card-title {
color: #212529;
}
.card {
border-radius: 1px;
.card, .modal-dialog {
border-radius: 3px;
border-color: #ff6600;
}
.card>.card-header {
.card>.card-header, .modal-header {
border-color: #ff6600;
background-color: #ff6600;
color: #000;
@ -41,19 +54,23 @@ h5.card-title {
font-size: 1.0rem;
}
.card-header [class^="fa"] {
.card-header [class^="fa"], .modal-header [class^="fa"] {
color: #fff;
font-size: 1.0rem;
}
.sidebar-brand-text {
text-transform: none;
color: #212529;
font-size: 2.0rem;
font-weight: 500;
font-family: Verdana, Geneva, sans-serif;
.modal-title {
color: #000;
font-size: 1.0rem;
}
.modal-content {
border-radius: 0px;
}
.sidebar-light hr.sidebar-divider {
padding-top: 0.5rem;
}
ul.nav-tabs, .nav-tabs .nav-link {
background-color: #f6f6ef;
@ -62,34 +79,19 @@ ul.nav-tabs, .nav-tabs .nav-link {
.sidebar .nav-item .nav-link {
padding: 0.6rem;
margin-left: 0.6rem;
}
.sidebar-light .nav-item.active .nav-link {
font-weight: 300;
}
.page-header {
font-size: 26pt;
margin: 10px 0 20px;
}
#wrapper,#page-wrapper,
#wrapper #content-wrapper,
.nav>li>a,.nav {
background-color: #fff;
}
/* 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; }
.js-wifi-stations { margin-left: -0.5rem; margin-right: -0.5rem; }
h4.mt-3 { margin-left: 0.5rem; }
}
.card-body {
background-color: #f6f6ef;
}
@ -120,44 +122,10 @@ ul.nav-tabs, .nav-tabs .nav-link {
color: #eee;
}
.info-item {
width: 10rem;
float: left;
.fas.fa-circle {
font-size: 0.7rem;
}
.info-item-wifi {
width: 6rem;
float: left;
}
.logoutput {
width: 100%;
height: 20rem;
border: 1px solid #d1d3e2;
border-radius: .35rem;
}
.service-status {
border-width: 0;
}
i.fas.fa-circle.service-status-up {
color: green;
}
i.fas.fa-circle.service-status-down {
color: red;
}
.service-status-down {
animation: flash 1s linear infinite;
}
@keyframes flash {
50% {
opacity: 0;
}
}
.logoutput {
width:100%;
height:300px;
@ -169,26 +137,6 @@ pre.unstyled {
padding: 0;
}
.dhcp-static-leases {
margin-top: 1em;
margin-bottom: 1em;
}
.dhcp-static-lease-row {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.loading-spinner {
background: url("../../app/img/loading-spinner.gif") no-repeat scroll center center transparent;
min-height: 150px;
width: 100%;
}
.js-reload-wifi-stations {
min-width: 10rem;
}
.sidebar.toggled .nav-item .nav-link {
text-align: center;
padding: .6rem 1rem;
@ -206,12 +154,7 @@ pre.unstyled {
color: #000;
}
.toggle-off.btn {
padding-left: 0.9rem;
font-size: 0.9rem!important;
}
.toggle-on.btn {
font-size: 0.9rem!important;
.signal-icon .signal-bar {
background: #ff6600;
}

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,396 +0,0 @@
html * {
font-family: Courier New, Andale Mono, monospace;
font-size: 1.0rem;
color: #2ee600;
}
h4 {
font-size: 1.3rem;
}
h5.card-title {
font-size: 1.2rem;
}
.page-header {
padding: 0 20px;
border-left: 1px solid #2ee600;
}
.sidebar-light .nav-item.active .nav-link i {
color: #2ee600;
}
.sidebar .nav-item.active .nav-link {
font-weight: 700;
}
#wrapper #content-wrapper #content {
background-color: #000;
}
/* Small devices (portait 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; }
.js-wifi-stations { margin-left: -0.5rem; margin-right: -0.5rem; }
h4.mt-3 { margin-left: 0.5rem; }
}
.topbar {
background-color: #000;
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-link {
font-size: 1.0rem;
}
.nav-tabs>li.active>a,
.nav-tabs>li.active>a:focus,
.nav-tabs>li.active>a:hover,
.nav-tabs .nav-link:hover,
.input-group-addon {
color: #2ee600;
cursor: default;
background-color: #000;
border: 1px solid #2ee600;
border-bottom-color: #2ee600;
border-radius: inherit;
}
.nav-tabs>li>a,.nav-tabs>li>a:hover {
border: 1px solid #2ee600;
}
.nav-tabs {
border-bottom: 1px solid #2ee600;
}
.navbar-default .navbar-brand,
.navbar-default .navbar-brand:hover {
color: #2ee600;
}
.navbar-default .navbar-toggle {
border-color: #2ee600;
}
.navbar-default .navbar-toggle .icon-bar {
background-color: #2ee600;
}
.navbar-default .navbar-toggle:focus,
.navbar-default .navbar-toggle:hover {
background-color: #000;
}
.nav-tabs .nav-link.active {
color: #000;
background-color: #2ee600;
border-color: #2ee600;
border-radius: inherit;
}
.sidebar-brand-icon {
filter: invert(65%) sepia(900%) saturate(536%) hue-rotate(68deg) brightness(100%) contrast(120%);
width: 30px;
}
a:focus, a:hover {
color: #2ee600;
}
.card>.card-header {
border-color: #2ee600;
background-color: #2ee600;
color: #000;
border-radius: unset;
font-size: 1.1rem;
font-weight: bold;
}
.card>.card-header .fa {
color: #000;
}
.card-header [class^="fa"] {
color: #000;
font-size: 1.0rem;
}
.col {
color: #000;
}
.card, .card-body {
margin-bottom: 20px;
border: 1px solid #2ee600;
border-radius: 0px;
background-color: #000;
}
hr {
border-top: 1px solid #2ee600;
}
.page-header {
font-size: 24pt;
margin: 10px 0 20px;
border-bottom: 1px solid #2ee600;
}
.sidebar-brand-text {
text-transform: none;
color: #2ee600;
font-size: 2.0rem;
font-weight: 500;
font-family: inherit;
}
.sidebar-light #sidebarToggle {
background-color: #2ee600;
}
.sidebar-light #sidebarToggle::after {
color: #000;
}
.sidebar-light .nav-item .nav-link:hover i {
color: #81ff61;
}
.sidebar-light #sidebarToggle:hover {
background-color: #81ff61;
}
.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 {
background-color: #000;
border-top: 1px solid #2ee600;
}
.card>.card-header::before, .navbar-default::before {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
z-index: 2;
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
.sidebar-light, .sticky-footer {
background-color: #000;
}
.sidebar-light .nav-item .nav-link i {
color: #2ee600;
}
.sidebar .nav-item .nav-link {
padding: 0.6rem;
}
.sidebar-light hr.sidebar-divider {
border-top: 1px solid #2ee600;
}
.topbar .topbar-divider {
border-right: 1px solid #2ee600;
}
.info-item {
width: 12rem;
float: left;
}
.info-item-wifi {
width: 6rem;
float: left;
}
.label-warning {
background-color: #2ee600;
}
span.label.label-warning {
color: #000;
}
.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: #000;
border-top: 1px solid #000;
}
.table>thead>tr>th {
vertical-align: bottom;
border-bottom: 1px solid #2ee600;
}
[class*="btn"], [class*="btn"]:focus, [class*="btn"]:disabled {
background-color: #000;
border-color: #2ee600;
border-color: #2ee600;
border-radius: 0px;
color: #2ee600;
}
[class*="btn"]:hover {
background-color: #81ff61;
border-color: #81ff61;
border-color: #81ff61;
border-radius: 0px;
color: #000;
}
[class*="btn"]:hover .disabled {
background-color:red;
}
[class*="alert"] {
border-radius: 0px;
color: #2ee600;
background-color: #000;
border-color: #2ee600;
border: 1px dashed;
}
.close {
font-size: 18px;
font-weight: normal;
text-shadow: 0 0px 0 #000;
opacity: 1;
}
.form-control,
.form-control:focus {
color: #2ee600;
background-color: #000;
border: 1px solid #2ee600;
border-radius: 0px;
transition: unset;
}
.form-control:disabled,
.form-control[readonly] {
background-color: #000;
opacity: 0.5;
}
.form-control::-webkit-input-placeholder { color: #2ee600; }
.form-control:-moz-placeholder { color: #2ee600; }
.form-control::-moz-placeholder { color: #2ee600; }
.form-control:-ms-input-placeholder { color: #2ee600; }
.form-control::-ms-input-placeholder { color: #2ee600; }
input[type="text"]{
color: #2ee600 !important
}
.progress {
background-color: #000;
border-radius: 0px;
}
.progress-bar {
color: #000;
}
.progress-bar.progress-bar-info.progress-bar-striped.active {
background-color: #2ee600;
}
.logoutput {
width: 100%;
height: 300px;
background-color: #000;
border-color: #2ee600;
}
.webconsole {
width: 100%;
height: 100%;
border-color: #2ee600;
border-bottom: 1px solid;
border-left: 1px solid;
border-top: 0px;
border-right: 1px solid;
}
#console {
height: 500px;
}
tspan, rect {
fill: #2ee600;
}
.service-status {
border-width: 0;
}
span.text.service-status {
font-size: 0.75rem;
margin-top: 0.2rem;
}
i.fas.fa-circle.service-status-up {
color: #2ee600;
}
i.fas.fa-circle.service-status-down {
color: #2ee600;
animation: flash 1s linear infinite;
}
@keyframes flash {
50% {
opacity: 0;
}
}
pre {
background-color: #000;
border: #000;
}
.dhcp-static-leases {
margin-top: 1em;
margin-bottom: 1em;
}
.dhcp-static-lease-row {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.toggle-off.btn {
padding-left: 1.2rem;
font-size: 0.9rem!important;
}
.toggle-on.btn {
font-size: 0.9rem!important;
}

View File

@ -3,7 +3,7 @@
"short_name": "RaspAP",
"icons": [
{
"src": "/dist/icons/android-chrome-192x192.png",
"src": "/app/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

BIN
app/img/insiders.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

51
app/img/raspAP-logo.php Normal file
View File

@ -0,0 +1,51 @@
<?php header("Content-Type: image/svg+xml; charset=utf-8"); ?>
<?php
require_once '../../includes/functions.php';
$color = getColorOpt();
?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 490.66666 487.11066"
height="487.11066"
width="490.66666"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6"><clipPath
id="clipPath18"
clipPathUnits="userSpaceOnUse"><path
id="path16"
d="M 0,365.333 H 368 V 0 H 0 Z" /></clipPath></defs><g
transform="matrix(1.3333333,0,0,-1.3333333,0,487.11067)"
id="g10"><g
id="g12"><g
clip-path="url(#clipPath18)"
id="g14"><g
transform="translate(192.6768,123.4365)"
id="g20"><path
id="path22"
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 0,-37.169 -30.128,-67.3 -67.296,-67.3 -37.167,0 -67.294,30.131 -67.294,67.3 0,37.165 30.127,67.296 67.294,67.296 C -30.128,67.296 0,37.165 0,0" /></g><g
transform="translate(125.3823,219.0791)"
id="g24"><path
id="path26"
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -52.737,0 -95.641,-42.905 -95.641,-95.643 0,-52.74 42.904,-95.647 95.641,-95.647 52.737,0 95.642,42.907 95.642,95.647 C 95.642,-42.905 52.737,0 0,0 m 0,-217.29 c -67.073,0 -121.641,54.571 -121.641,121.647 C -121.641,-28.569 -67.073,26 0,26 67.074,26 121.642,-28.569 121.642,-95.643 121.642,-162.719 67.074,-217.29 0,-217.29" /></g><g
transform="translate(144.4277,271.9385)"
id="g28"><path
id="path30"
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 66.188,0 121.118,-49.055 130.392,-112.714 l 28.259,-1.874 C 150.044,-34.655 82.181,27.791 0,27.791 c -3.892,0 -7.75,-0.147 -11.571,-0.423 L -9.73,-0.397 C -6.513,-0.161 -3.275,0 0,0" /></g><g
transform="translate(144.4883,334.7588)"
id="g32"><path
id="path34"
style="fill:<?php echo $color; ?>;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 101.94,0 185.667,-79.438 192.56,-179.664 l 27.962,-1.857 C 214.513,-65.087 117.899,27.791 0,27.791 c -5.31,0 -10.576,-0.2 -15.792,-0.571 l 1.84,-27.728 C -9.343,-0.177 -4.691,0 0,0" /></g></g></g></g></svg>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

28
app/img/wg-qr-code.php Normal file
View File

@ -0,0 +1,28 @@
<?php
require_once '../../includes/config.php';
require_once '../../includes/defaults.php';
require_once '../../includes/functions.php';
// prevent direct file access
if (!isset($_SERVER['HTTP_REFERER'])) {
header('HTTP/1.0 403 Forbidden');
exit;
}
exec("sudo cat " .RASPI_WIREGUARD_PATH.'client.conf', $return);
$peer_conf = implode(PHP_EOL,$return);
$peer_conf.= PHP_EOL;
$command = "qrencode -t svg -m 0 -o - " . mb_escapeshellarg($peer_conf);
$svg = shell_exec($command);
$etag = hash('sha256', $peer_conf);
$content_length = strlen($svg);
$last_modified = date("Y-m-d H:i:s");
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");
echo shell_exec($command);

54
app/img/wifi-qr-code.php Normal file
View File

@ -0,0 +1,54 @@
<?php
require_once '../../includes/config.php';
require_once '../../includes/defaults.php';
require_once '../../includes/functions.php';
// prevent direct file access
if (!isset($_SERVER['HTTP_REFERER'])) {
header('HTTP/1.0 403 Forbidden');
exit;
}
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
// 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']);
$wep_key = 'wep_key' . $wep_default_key;
if (array_key_exists($wep_key, $hostapd)) {
$type = "WEP";
$password = $hostapd[$wep_key];
}
// if password is still empty, assume nopass
if (empty($password)) {
$type = "nopass";
}
$ssid = $hostapd['ssid'];
$hidden = intval($hostapd['ignore_broadcast_ssid']) != 0 ? "H:true" : "";
$ssid = qr_encode($ssid);
$password = qr_encode($password);
$data = "WIFI:S:$ssid;T:$type;P:$password;$hidden;";
$command = "qrencode -t svg -m 1 -o - " . mb_escapeshellarg($data);
$svg = shell_exec($command);
$config_mtime = filemtime(RASPI_HOSTAPD_CONFIG);
$last_modified = gmdate('D, d M Y H:i:s ', $config_mtime) . 'GMT';
$etag = hash('sha256', $data);
$content_length = strlen($svg);
header("Content-Type: image/svg+xml");
header("Content-Length: $content_length");
header("Last-Modified: $last_modified");
header("Content-Disposition: attachment; filename=\"qr.svg\"");
header("ETag: \"$etag\"");
header("X-QR-Code-Content: $data");
echo $svg;

View File

@ -1,6 +1,5 @@
function msgShow(retcode,msg) {
if(retcode == 0) {
var alertType = 'success';
if(retcode == 0) { var alertType = 'success';
} else if(retcode == 2 || retcode == 1) {
var alertType = 'danger';
}
@ -21,7 +20,6 @@ function createNetmaskAddr(bitCount) {
function loadSummary(strInterface) {
$.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) {
@ -49,91 +47,24 @@ function setupTabs() {
});
}
function loadCurrentSettings(strInterface) {
$.post('ajax/networking/get_int_config.php',{interface:strInterface},function(data){
jsonData = JSON.parse(data);
$.each(jsonData['output'],function(i,v) {
var int = v['interface'];
$.each(v,function(i2,v2) {
switch(i2) {
case "static":
if(v2 == 'true') {
$('#'+int+'-static').click();
$('#'+int+'-nofailover').click();
} else {
$('#'+int+'-dhcp').click();
}
break;
case "failover":
if(v2 === 'true') {
$('#'+int+'-failover').click();
} else {
$('#'+int+'-nofailover').click();
}
break;
case "ip_address":
var arrIPNetmask = v2.split('/');
$('#'+int+'-ipaddress').val(arrIPNetmask[0]);
$('#'+int+'-netmask').val(createNetmaskAddr(arrIPNetmask[1]));
break;
case "routers":
$('#'+int+'-gateway').val(v2);
break;
case "domain_name_server":
svrsDNS = v2.split(" ");
$('#'+int+'-dnssvr').val(svrsDNS[0]);
$('#'+int+'-dnssvralt').val(svrsDNS[1]);
break;
}
});
});
});
}
function saveNetworkSettings(int) {
var frmInt = $('#frm-'+int).find(':input');
var arrFormData = {};
$.each(frmInt,function(i3,v3){
if($(v3).attr('type') == 'radio') {
arrFormData[$(v3).attr('id')] = $(v3).prop('checked');
} else {
arrFormData[$(v3).attr('id')] = $(v3).val();
}
});
arrFormData['interface'] = int;
$.post('ajax/networking/save_int_config.php',arrFormData,function(data){
var jsonData = JSON.parse(data);
$('#msgNetworking').html(msgShow(jsonData['return'],jsonData['output']));
});
}
function applyNetworkSettings() {
var int = $(this).data('int');
arrFormData = {};
arrFormData['generate'] = '';
$.post('ajax/networking/gen_int_config.php',arrFormData,function(data){
console.log(data);
var jsonData = JSON.parse(data);
$('#msgNetworking').html(msgShow(jsonData['return'],jsonData['output']));
});
}
$(document).on("click", ".js-add-dhcp-static-lease", function(e) {
e.preventDefault();
var container = $(".js-new-dhcp-static-lease");
var mac = $("input[name=mac]", container).val().trim();
var ip = $("input[name=ip]", container).val().trim();
var comment = $("input[name=comment]", container).val().trim();
if (mac == "" || ip == "") {
return;
}
var row = $("#js-dhcp-static-lease-row").html()
.replace("{{ mac }}", mac)
.replace("{{ ip }}", ip);
.replace("{{ ip }}", ip)
.replace("{{ comment }}", comment);
$(".js-dhcp-static-lease-container").append(row);
$("input[name=mac]", container).val("");
$("input[name=ip]", container).val("");
$("input[name=comment]", container).val("");
});
$(document).on("click", ".js-remove-dhcp-static-lease", function(e) {
@ -145,14 +76,90 @@ $(document).on("submit", ".js-dhcp-settings-form", function(e) {
$(".js-add-dhcp-static-lease").trigger("click");
});
$(document).on("click", ".js-add-dhcp-upstream-server", function(e) {
e.preventDefault();
var field = $("#add-dhcp-upstream-server-field")
var row = $("#dhcp-upstream-server").html().replace("{{ server }}", field.val())
if (field.val().trim() == "") { return }
$(".js-dhcp-upstream-servers").append(row)
field.val("")
});
$(document).on("click", ".js-remove-dhcp-upstream-server", function(e) {
e.preventDefault();
$(this).parents(".js-dhcp-upstream-server").remove();
});
$(document).on("submit", ".js-dhcp-settings-form", function(e) {
$(".js-add-dhcp-upstream-server").trigger("click");
});
/**
* mark a form field, e.g. a select box, with the class `.js-field-preset`
* and give it an attribute `data-field-preset-target` with a text field's
* css selector.
*
* now, if the element marked `.js-field-preset` receives a `change` event,
* its value will be copied to all elements matching the selector in
* data-field-preset-target.
*/
$(document).on("change", ".js-field-preset", function(e) {
var selector = this.getAttribute("data-field-preset-target")
var value = "" + this.value
var syncValue = function(el) { el.value = value }
if (value.trim() === "") { return }
document.querySelectorAll(selector).forEach(syncValue)
});
$(document).on("click", "#gen_wpa_passphrase", function(e) {
$('#txtwpapassphrase').val(genPassword(63));
});
$(document).on("click", "#js-clearhostapd-log", function(e) {
$.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) {
$.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) {
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/openvpn.log'},function(data){
jsonData = JSON.parse(data);
$("#openvpn-log").val("");
});
});
// Enable Bootstrap tooltips
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
function genPassword(pwdLen) {
var pwdChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var rndPass = Array(pwdLen).fill(pwdChars).map(function(x) { return x[Math.floor(Math.random() * x.length)] }).join('');
return rndPass;
}
function setupBtns() {
$('#btnSummaryRefresh').click(function(){getAllInterfaces();});
$('.intsave').click(function(){
var int = $(this).data('int');
saveNetworkSettings(int);
});
$('.intapply').click(function(){
applyNetworkSettings();
});
@ -166,13 +173,19 @@ function setCSRFTokenHeader(event, xhr, settings) {
}
function contentLoaded() {
pageCurrent = window.location.href.split("?")[1].split("=")[1];
pageCurrent = pageCurrent.replace("#","");
pageCurrent = window.location.href.split("/").pop();
switch(pageCurrent) {
case "network_conf":
getAllInterfaces();
setupTabs();
setupBtns();
break;
case "hostapd_conf":
loadChannel();
setHardwareModeTooltip();
break;
case "dhcpd_conf":
loadInterfaceDHCPSelect();
break;
}
}
@ -187,42 +200,322 @@ function loadWifiStations(refresh) {
.load('ajax/networking/wifi_stations.php'+qs, complete);
};
}
$(".js-reload-wifi-stations").on("click", loadWifiStations(true));
/*
Populates the DHCP server form fields
Option toggles are set dynamically depending on the loaded configuration
*/
function loadInterfaceDHCPSelect() {
var strInterface = $('#cbxdhcpiface').val();
$.get('ajax/networking/get_netcfg.php?iface='+strInterface,function(data){
jsonData = JSON.parse(data);
$('#dhcp-iface')[0].checked = jsonData.DHCPEnabled;
$('#txtipaddress').val(jsonData.StaticIP);
$('#txtsubnetmask').val(jsonData.SubnetMask);
$('#txtgateway').val(jsonData.StaticRouters);
$('#chkfallback')[0].checked = jsonData.FallbackEnabled;
$('#default-route').prop('checked', jsonData.DefaultRoute);
$('#txtrangestart').val(jsonData.RangeStart);
$('#txtrangeend').val(jsonData.RangeEnd);
$('#txtrangeleasetime').val(jsonData.leaseTime);
$('#txtdns1').val(jsonData.DNS1);
$('#txtdns2').val(jsonData.DNS2);
$('#cbxrangeleasetimeunits').val(jsonData.leaseTimeInterval);
$('#no-resolv')[0].checked = jsonData.upstreamServersEnabled;
$('#cbxdhcpupstreamserver').val(jsonData.upstreamServers[0]);
$('#txtmetric').val(jsonData.Metric);
if (jsonData.StaticIP !== null && jsonData.StaticIP !== '' && !jsonData.FallbackEnabled) {
$('#chkstatic').closest('.btn').button('toggle');
$('#chkstatic').closest('.btn').button('toggle').blur();
$('#chkstatic').blur();
$('#chkfallback').prop('disabled', true);
} else {
$('#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);
}
});
}
function setDHCPToggles(state) {
if ($('#chkfallback').is(':checked') && state) {
$('#chkfallback').prop('checked', state);
}
if ($('#dhcp-iface').is(':checked') && !state) {
$('#dhcp-iface').prop('checked', state);
}
$('#chkfallback').prop('disabled', state);
$('#dhcp-iface').prop('disabled', !state);
//$('#dhcp-iface').prop('checked', state);
}
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 = 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 --;
}, 1000);
});
$('#configureClientModal').on('shown.bs.modal', function (e) {
});
$('#ovpn-confirm-delete').on('click', '.btn-delete', function (e) {
var cfg_id = $(this).data('recordId');
$.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));
row.fadeOut( "slow", function() {
row.remove();
});
});
});
$('#ovpn-confirm-delete').on('show.bs.modal', function (e) {
var data = $(e.relatedTarget).data();
$('.btn-delete', this).data('recordId', data.recordId);
});
$('#ovpn-confirm-activate').on('click', '.btn-activate', function (e) {
var cfg_id = $(this).data('record-id');
$.post('ajax/openvpn/activate_ovpncfg.php',{'cfg_id':cfg_id},function(data){
jsonData = JSON.parse(data);
$("#ovpn-confirm-activate").modal('hide');
setTimeout(function(){
window.location.reload();
},300);
});
});
$('#ovpn-confirm-activate').on('shown.bs.modal', function (e) {
var data = $(e.relatedTarget).data();
$('.btn-activate', this).data('recordId', data.recordId);
});
$('#ovpn-userpw,#ovpn-certs').on('click', function (e) {
if (this.id == 'ovpn-userpw') {
$('#PanelCerts').hide();
$('#PanelUserPW').show();
} else if (this.id == 'ovpn-certs') {
$('#PanelUserPW').hide();
$('#PanelCerts').show();
}
});
$('#js-system-reset-confirm').on('click', function (e) {
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 ml-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);
});
});
$(document).ready(function(){
$("#PanelManual").hide();
});
$('#wg-upload,#wg-manual').on('click', function (e) {
if (this.id == 'wg-upload') {
$('#PanelManual').hide();
$('#PanelUpload').show();
} else if (this.id == 'wg-manual') {
$('#PanelUpload').hide();
$('#PanelManual').show();
}
});
// 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);
});
/*
Sets the wirelss channel select options based on hw_mode and country_code.
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) {
// 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 data = json["wireless_regdb"];
var selectablechannels = Array.range(1,14);
// 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["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();
$.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.
*/
function setHardwareModeTooltip() {
var iface = $('#cbxinterface').val();
var hwmodeText = '';
// 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_frequencies.php?',{'interface': iface},function(data){
var responseText = JSON.parse(data);
$('#tiphwmode').attr('data-original-title', responseText + '\n' + hwmodeText );
});
}
/* Updates the selected blocklist
* Request is passed to an ajax handler to download the associated list.
* Interface elements are updated to indicate current progress, status.
*/
function updateBlocklist() {
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 },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_id).text("Just now");
}
})
}
function clearBlocklistStatus() {
$('#cbxblocklist-status').removeClass('check-updated').addClass('check-hidden');
}
// Handler for the wireguard generate key button
$('.wg-keygen').click(function(){
var entity_pub = $(this).parent('div').prev('input[type="text"]');
var entity_priv = $(this).parent('div').next('input[type="hidden"]');
var updated = entity_pub.attr('name')+"-pubkey-status";
$.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(){
var req = new XMLHttpRequest();
var url = 'ajax/networking/get_wgcfg.php';
req.open('get', url, true);
req.responseType = 'blob';
req.setRequestHeader('Content-type', 'text/plain; charset=UTF-8');
req.onreadystatechange = function (event) {
if(req.readyState == 4 && req.status == 200) {
var blob = req.response;
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download = 'client.conf';
link.click();
}
}
req.send();
})
// Event listener for Bootstrap's form validation
window.addEventListener('load', function() {
// Fetch all the forms we want to apply custom Bootstrap validation styles to
var forms = document.getElementsByClassName('needs-validation');
// Loop over them and prevent submission
var validation = Array.prototype.filter.call(forms, function(form) {
form.addEventListener('submit', function(event) {
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
}, false);
});
}, false);
// 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.target)
var field = $(button.data("target"));
if (field.is(":input")) {
e.preventDefault();
if (!button.data("__toggle-with-initial")) {
button.data("__toggle-with-initial", button.text())
$("i", this).removeClass("fas fa-eye").addClass(button.attr("data-toggle-with"));
}
if (field.attr("type") === "password") {
button.text(button.data("toggle-with"));
field.attr("type", "text");
} else {
button.text(button.data("__toggle-with-initial"));
$("i", this).removeClass("fas fa-eye-slash").addClass("fas fa-eye");
field.attr("type", "password");
}
}
});
$(document).on("keyup", ".js-validate-psk", function(e) {
var field = $(e.target);
var colors = field.data("colors").split(",");
var target = $(field.data("target"));
if (field.val().length < 8 || field.val().length > 63) {
field.css("backgroundColor", colors[0]);
target.attr("disabled", true);
} else {
field.css("backgroundColor", colors[1]);
target.attr("disabled", false);
}
});
$(function() {
$('#theme-select').change(function() {
var theme = themes[$( "#theme-select" ).val() ];
@ -236,6 +529,17 @@ function set_theme(theme) {
setCookie('theme',theme,90);
}
$(function() {
$('#night-mode').change(function() {
var state = $(this).is(':checked');
if (state == true && getCookie('theme') != 'lightsout.css') {
set_theme('lightsout.css');
} else {
set_theme('custom.php');
}
});
});
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
@ -249,10 +553,11 @@ function getCookie(cname) {
return (value != null) ? unescape(value[1]) : null;
}
// Define themes
var themes = {
"default": "custom.css",
"default": "custom.php",
"hackernews" : "hackernews.css",
"terminal" : "terminal.css",
"lightsout" : "lightsout.css",
}
// Toggles the sidebar navigation.

106
app/js/dashboardchart.js Normal file
View File

@ -0,0 +1,106 @@
(function($, _t) {
"use strict";
/**
* Create a Chart.js barchart.
*/
function CreateChart(ctx, labels) {
var barchart = new Chart(ctx,{
type: 'line',
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [{
scaleLabel: {
display: true
},
ticks: {
maxRotation: 0,
minRotation: 0
}
}],
yAxes: [{
id: 'y-axis-1',
type: 'linear',
display: true,
position: 'left',
ticks: {
beginAtZero: true
}
}]
}
},
data: {
labels: labels,
datasets: []
}
});
return barchart;
}
function ShowBandwidthChartHandler(e) {
// Remove hourly chartjs chart
$('#divDBChartBandwidthhourly').empty();
// Construct ajax uri for getting the proper data
var timeunit = 'hourly';
var uri = 'ajax/bandwidth/get_bandwidth.php?';
uri += 'inet=';
uri += encodeURIComponent($('#divInterface').text());
uri += '&tu=';
uri += encodeURIComponent(timeunit.substr(0, 1));
var datasizeunits = 'mb';
uri += '&dsu='+encodeURIComponent(datasizeunits);
// Get data for chart
$.ajax({
url: uri,
dataType: 'json',
beforeSend: function() {}
}).done(function(jsondata) {
// Map json values to label array
var labels = jsondata.map(function(e) {
return e.date;
});
// Init. chart with label series
var barchart = CreateChart('divDBChartBandwidth'+timeunit, labels);
var dataRx = jsondata.map(function(e) {
return e.rx;
});
var dataTx = jsondata.map(function(e) {
return e.tx;
});
addData(barchart, dataTx, dataRx, datasizeunits);
}).fail(function(xhr, textStatus) {
if (window.console) {
console.error('server error');
} else {
alert("server error");
}
});
}
/**
* Add data array to datasets of current chart.
*/
function addData(chart, dataTx, dataRx, datasizeunits) {
chart.data.datasets.push({
label: 'Send'+' '+datasizeunits.toUpperCase(),
yAxisID: 'y-axis-1',
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
data: dataTx
});
chart.data.datasets.push({
label: 'Receive'+' '+datasizeunits.toUpperCase(),
yAxisID: 'y-axis-1',
borderColor: 'rgba(192, 192, 192, 1)',
backgroundColor: 'rgba(192, 192, 192, 0.2)',
data: dataRx
});
chart.update();
}
$(document).ready(function() {
ShowBandwidthChartHandler();
});
})(jQuery, t);

22
app/js/huebee.js Normal file
View File

@ -0,0 +1,22 @@
// Initialize Huebee color picker
var elem = document.querySelector('.color-input');
var hueb = new Huebee( elem, {
notation: 'hex',
saturations: 2,
customColors: [ '#d8224c', '#dd4814', '#ea0', '#19f', '#333' ],
className: 'light-picker',
hue0: 210
});
// Set custom color if defined
var color = getCookie('color');
if (color == null || color == '') {
color = '#2b8080';
}
hueb.setColor(color);
// Change event
hueb.on( 'change', function( color, hue, sat, lum ) {
setCookie('color',color,90);
})

View File

@ -1,86 +1,86 @@
// Link quality gauge for ChartJS
// Support for dark terminal theme
// Support for dark theme
theme = getCookie('theme');
if (theme == 'terminal.css') {
var bgColor1 = '#000';
var bgColor2 = '#000';
var borderColor = 'rgba(46, 230, 0, 1)';
var labelColor = 'rgba(46, 230, 0, 1)';
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 {
var bgColor1 = '#d4edda';
var bgColor2 = '#eaecf4';
var borderColor = 'rgba(147, 210, 162, 1)';
var labelColor = 'rgba(130, 130, 130, 1)';
var bgColor1 = '#d4edda';
var bgColor2 = '#eaecf4';
var borderColor = 'rgba(147, 210, 162, 1)';
var labelColor = 'rgba(130, 130, 130, 1)';
}
let data1 = {
datasets: [{
data: [linkQ, 100-linkQ],
backgroundColor: [bgColor1, bgColor2],
borderColor: borderColor,
}],
datasets: [{
data: [linkQ, 100-linkQ],
backgroundColor: [bgColor1, bgColor2],
borderColor: borderColor,
}],
};
let config = {
type: 'doughnut',
data: data1,
options: {
aspectRatio: 2,
responsive: true,
tooltips: {enabled: false},
hover: {mode: null},
legend: {
display: false,
},
rotation: (2/3)*Math.PI,//2+(1/3),
circumference: (1+(2/3)) * Math.PI, // * Math.PI,
cutoutPercentage: 80,
animation: {
animateScale: false,
animateRotate: true
},
tooltips: {
enabled: false
}
type: 'doughnut',
data: data1,
options: {
aspectRatio: 2,
responsive: true,
maintainAspectRatio: false,
tooltips: {enabled: false},
hover: {mode: null},
legend: {
display: false,
},
centerText: {
display: true,
text: linkQ + "%"
rotation: (2/3)*Math.PI,//2+(1/3),
circumference: (1+(2/3)) * Math.PI, // * Math.PI,
cutoutPercentage: 80,
animation: {
animateScale: false,
animateRotate: true
},
tooltips: {
enabled: false
}
};
Chart.Chart.pluginService.register({
},
centerText: {
display: true,
text: linkQ + "%"
},
plugins: [{
beforeDraw: function(chart) {
if (chart.config.centerText.display !== null &&
typeof chart.config.centerText.display !== 'undefined' &&
chart.config.centerText.display) {
drawLinkQ(chart);
}
if (chart.config.centerText.display !== null &&
typeof chart.config.centerText.display !== 'undefined' &&
chart.config.centerText.display) {
drawLinkQ(chart);
}
}
});
}]
};
function drawLinkQ(chart) {
let width = chart.chart.width;
let height = chart.chart.height;
let ctx = chart.chart.ctx;
let width = chart.chart.width;
let height = chart.chart.height;
let ctx = chart.chart.ctx;
ctx.restore();
let fontSize = (height / 100).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.fillStyle = labelColor;
ctx.textBaseline = "middle";
ctx.restore();
let fontSize = (height / 100).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.fillStyle = labelColor;
ctx.textBaseline = "middle";
let text = chart.config.centerText.text;
let textX = Math.round((width - ctx.measureText(text).width) * 0.5);
let textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
let text = chart.config.centerText.text;
let textX = Math.round((width - ctx.measureText(text).width) * 0.5);
let textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
window.onload = function() {
let ctx = document.getElementById("canvas").getContext("2d");
window.myDoughnut = new Chart(ctx, config);
let ctx = document.getElementById("divChartLinkQ").getContext("2d");
var chart = new Chart(ctx, config);
};

1994
app/lib/Parsedown.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +1,90 @@
<?php
class System {
public function hostname() {
return shell_exec("hostname -f");
}
/**
* 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
*/
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 ':' ');
namespace RaspAP\System;
class Sysinfo
{
public function hostname()
{
return shell_exec("hostname -f");
}
return $uptime;
}
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 ':' ');
}
public function usedMemory() {
$used = shell_exec("free -m | awk '/Mem:/ { total=$2 ; used=$3 } END { print used/total*100}'");
return floor($used);
}
return $uptime;
}
public function processorCount() {
$procs = shell_exec("nproc --all");
return intval($proc);
}
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 loadAvg1Min() {
$load = exec("awk '{print $1}' /proc/loadavg");
return floatval($load);
}
public function processorCount()
{
$procs = shell_exec("nproc --all");
return intval($procs);
}
public function systemLoadPercentage() {
return intval(($this->loadAvg1Min() * 100) / $this->processorCount());
}
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);
}
}

98
app/pitft/stats.py Normal file
View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
#
# Author: @billz
# Author URI: https://github.com/billz
# Description: RaspAP stats display for the Adafruit Mini PiTFT,
# a 135x240 Color TFT add-on for the Raspberry Pi.
# Based on Adafruit's rgb_display_ministats.py
# See: https://github.com/adafruit/Adafruit_CircuitPython_RGB_Display
# License: MIT License
import time
import subprocess
import digitalio
import board
from PIL import Image, ImageDraw, ImageFont
import adafruit_rgb_display.st7789 as st7789
# Configuration for CS and DC pins
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None
# Config for display baudrate (default max is 24mhz)
BAUDRATE = 64000000
# Setup SPI bus using hardware SPI
spi = board.SPI()
# Create the ST7789 display
disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE,
width=135, height=240, x_offset=53, y_offset=40)
# Create blank image with mode 'RGB'
height = disp.width # swap height/width to rotate it to landscape
width = disp.height
image = Image.new('RGB', (width, height))
rotation = 90
# Get a drawing object and clear the image
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image,rotation)
# Define some constants
padding = -2
top = padding
bottom = height-padding
# Move left to right keeping track of the current x position
x = 0
# Load DejaVu TTF Font
# Install with: sudo apt-get install ttf-dejavu
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 24)
# Turn on the backlight
backlight = digitalio.DigitalInOut(board.D22)
backlight.switch_to_output()
backlight.value = True
while True:
# Draw a black filled box to clear the image
draw.rectangle((0, 0, width, height), outline=0, fill=0)
# Collect basic system stats
cmd = "hostname -I | cut -d\' \' -f1"
IP = "IP: "+subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "pidof hostapd | wc -l | awk '{printf \"Hotspot: %s\", $1 == 1 ? \"Active\" : \"Down\"}'"
Hostapd = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "vnstat -i wlan0 | grep tx: | awk '{printf \"Data Tx: %d %s\", $5,$6}'"
DataTx = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "free -m | awk 'NR==2{printf \"Mem: %sMB %.2f%%\", $3,$3*100/$2 }'"
MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "cat /sys/class/thermal/thermal_zone0/temp | awk \'{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}\'" # pylint: disable=line-too-long
Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")
# Write five lines of stats
y = top
draw.text((x, y), IP, font=font, fill="#ffaaaa")
y += font.getsize(IP)[1]
draw.text((x, y), Hostapd, font=font, fill="#d46a6a")
y += font.getsize(Hostapd)[1]
draw.text((x, y), DataTx, font=font, fill="#aa3939")
y += font.getsize(DataTx)[1]
draw.text((x, y), MemUsage, font=font, fill="#801515")
y += font.getsize(MemUsage)[1]
draw.text((x, y), Temp, font=font, fill="#550000")
# Display image
disp.image(image, rotation)
time.sleep(.1)

31
composer.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "raspap/raspap-webgui",
"description": "Simple wireless AP setup and mangement for Debian-based devices",
"license": "GPL-3.0",
"homepage": "https://raspap.com/",
"keywords": ["raspberrypi", "debian", "armbian", "wifi"],
"type": "raspap-core",
"authors": [
{
"name": "RaspAP Team",
"email": "billzimmerman@gmail.com",
"homepage": "https://raspap.com/"
}
],
"require": {
"php": "^7.0"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.2.0",
"phpcompatibility/php-compatibility": "^9.3.5",
"squizlabs/php_codesniffer": "^3.5.5"
},
"scripts": {
"lint": "parallel-lint . --exclude vendor",
"phpcs": "phpcs -p -s --config-set installed_paths vendor/phpcompatibility/php-compatibility .",
"test": [
"composer lint",
"composer phpcs"
]
}
}

4
config/090_raspap.conf Normal file
View File

@ -0,0 +1,4 @@
# RaspAP default config
log-facility=/var/log/dnsmasq.log
conf-dir=/etc/dnsmasq.d

6
config/090_wlan0.conf Normal file
View File

@ -0,0 +1,6 @@
# RaspAP wlan0 configuration for wired (ethernet) AP mode
interface=wlan0
domain-needed
dhcp-range=10.3.141.50,10.3.141.254,255.255.255.0,12h
dhcp-option=6,9.9.9.9,1.1.1.1

View File

@ -0,0 +1,9 @@
server.modules += (
"mod_rewrite",
)
$HTTP["url"] =~ "^/REPLACE_ME/(?!(dist|app|ajax|config)).*" {
url.rewrite-once = ( "^/REPLACE_ME/(.*?)(\?.+)?$"=>"/REPLACE_ME/index.php/$1$2" )
server.error-handler-404 = "/REPLACE_ME/index.php"
}

6
config/blocklists.json Normal file
View File

@ -0,0 +1,6 @@
{
"notracking/hosts-blocklist": [
"notracking-hostnames",
"notracking-domains"
]
}

View File

@ -0,0 +1,4 @@
# mobile data modem - ttyUSB0 device appears
SUBSYSTEM=="tty", KERNEL=="ttyUSB0", TAG+="systemd", ENV{SYSTEMD_WANTS}="start start_ppp0_device.service"

View File

@ -0,0 +1,3 @@
SUBSYSTEM=="net", ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="14db", NAME="hilink%n", TAG+="systemd", ENV{SYSTEMD_WANTS}="start start_huawei_hilink@hilink%n.service"

View File

@ -0,0 +1,505 @@
#!/bin/bash
#
# Huawei Hilink API
# =================
# - communication with Hilink devices via HTTP
# - send a standard http request with a xml formatted string to the device (default IP 192.169.8.1)
# - Howto:
# o "source" this script in your own script from the command line
# o if hilink_host ip/name differs, set "hilink_host=192.168.178.1" before calling any function
# o if the device is locked by a password, set hilink_user="admin"; hilink_password"1234secret"
# _login is called automatically
# only password type 4 is supported
# o if the SIM is requiring a PIN, set "hilink_pin=1234"
# o connect device to network: _switchMobileData ON ( or 1 )
# o disconnect device: _switchMobileData OFF ( or 0 )
# o get informations about the device: _getDeviceInformation and _getStatus and _getNetProvider
# all functions return XML formatted data in $response.
# o _getAllInformations: returns all available informations as key/value pairs (outputs text)
# o Check if device is connected: "if _isConnected; then .... fi"
# o $response can be parsed by calling _valueFromResponse
# e.g "_valueFromResponse msisdn" to get the phone number after a call to _getDeviceInformation
#
#
# Usage of functions
# - call the function with parameters (if required)
# - return code: 0 - success; 1 - failed
# - $status: status information (OK, ERROR)
# - $response: xml response to be parsed for the required information
#
#
# required software: curl, base64, sha256sum, sed
#
# ToDo: improve error handling
#
# zbchristian 2021
#
# Initialization procedure
# ========================
#
# hilink_host=192.168.8.1 # ip address of device
# hilink_user="admin" # user name if locked (default admin)
# hilink_password="1234Secret" # password if locked
# hilink_pin="1234" # PIN of SIM
# _initHilinkAPI # initialize the API
#
# Termination
# ===========
# cleanup the API before quitting the shell
# _closeHilinkAPI (optional: add parameter "save" to save the session/token data for subsequent calls. Valid for a few minutes.)
#
# BE AWARE, THAT THE API USES SOME GLOBAL VARIABLES : hilink_host, user, password, pin, response, status
# USE THESE ONLY TO COMMUNICATE WITH THE API.
# DO NOT USE THE VARIABLE PRE_FIX "hilink_" FOR YOUR OWN VARIABLES
#
hilink_host_default="192.168.8.1"
hilink_save_file="/tmp/hilink_api_saved.dat"
hilink_save_age=60
hilink_header_file="/tmp/hilink_login_hdr.txt"
# initialize
function _initHilinkAPI() {
local age
if [ -z "$hilink_host" ]; then hilink_host=$hilink_host_default; fi
if ! _hostReachable; then return 1; fi
if [ -f $hilink_save_file ]; then # found file with saved data
_getSavedData
age=$(( $(date +%s) - $(stat $hilink_save_file -c %Y) ))
if [[ $age -gt $hilink_save_age ]]; then
rm -f $hilink_save_file
_logout
_sessToken
fi
fi
if [ -z "$hilink_sessID" ] || [ -z "$hilink_token" ]; then _sessToken; fi
_login
return $?
}
function _getSavedData() {
local dat
if [ -f $hilink_save_file ]; then # restore saved session data
dat=$(cat $hilink_save_file)
hilink_sessID=$(echo "$dat" | sed -nr 's/sessionid: ([a-z0-9]*)/\1/ip')
hilink_token=$(echo "$dat" | sed -nr 's/token: ([a-z0-9]*)/\1/ip')
hilink_tokenlist=( $(echo "$dat" | sed -nr 's/tokenlist: ([a-z0-9 ]*)/\1/ip') )
fi
}
# Cleanup
# parameter: "save" - will store sessionid and tokens in file
function _closeHilinkAPI() {
local opt
if [ -z "$hilink_host" ]; then hilink_host=$hilink_host_default; fi
if ! _hostReachable; then return 1; fi
rm -f $hilink_save_file
[ ! -z "$1" ] && opt="${1,,}"
if [ ! -z "$opt" ] && [ "$opt" = "save" ]; then
echo "sessionid: $hilink_sessID" > $hilink_save_file
echo "token: $hilink_token" >> $hilink_save_file
echo "tokenlist: ${hilink_tokenlist[@]}" >> $hilink_save_file
fi
_logout
hilink_tokenlist=""
hilink_sessID=""
hilink_token=""
return 0
}
# get status (connection status, DNS, )
# parameter: none
function _getStatus() {
if _login; then
if _sendRequest "api/monitoring/status"; then
if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
fi
return $?
fi
return 1
}
function _isConnected() {
local conn
conn=$(_getStatus "connectionstatus")
status="NO"
if [ ! -z "$conn" ] && [ $conn -eq 901 ]; then
status="YES"
return 0
fi
return 1
}
# get device information (device name, imei, imsi, msisdn-phone number, MAC, WAN IP ...)
# parameter: name of parameter to return
function _getDeviceInformation() {
if _login; then
if _sendRequest "api/device/information"; then
if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
fi
return $?
fi
return 1
}
# get net provider information
# parameter: name of parameter to return
function _getNetProvider() {
if _login; then
if _sendRequest "api/net/current-plmn"; then
if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
fi
return $?
fi
return 1
}
# get signal level
# parameter: name of parameter to return
function _getSignal() {
if _login; then
if _sendRequest "api/device/signal"; then
if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
fi
return $?
fi
return 1
}
function _getAllInformations() {
if _getDeviceInformation; then _keyValuePairs; fi
if _getSignal; then _keyValuePairs; fi
if _getNetProvider; then _keyValuePairs; fi
}
# get status of mobile data connection
# parameter: none
function _getMobileDataStatus() {
if _login; then
if _sendRequest "api/dialup/mobile-dataswitch"; then
status=$(_valueFromResponse "dataswitch")
if [ $? -eq 0 ] && [ ! -z "$status" ]; then echo "$status"; fi
fi
return $?
fi
return 1
}
# PIN of SIM can be passed either as $hilink_pin, or as parameter
# parameter: PIN number of SIM card
function _enableSIM() {
#SimState:
#255 - no SIM,
#256 - error CPIN,
#257 - ready,
#258 - PIN disabled,
#259 - check PIN,
#260 - PIN required,
#261 - PUK required
local simstate
if [ ! -z "$1" ]; then hilink_pin="$1"; fi
if ! _login; then return 1; fi
if _sendRequest "api/pin/status"; then
simstate=$(echo $response | sed -rn 's/.*<simstate>([0-9]*)<\/simstate>.*/\1/pi')
if [[ $simstate -eq 257 ]]; then status="SIM ready"; return 0; fi
if [[ $simstate -eq 260 ]]; then
status="PIN required"
if [ ! -z "$hilink_pin" ]; then _setPIN "$hilink_pin"; fi
return $?
fi
if [[ $simstate -eq 255 ]]; then status="NO SIM"; return 1; fi
fi
return 1
}
# obtain session and verification token - stored in vars $hilink_sessID and $token
# parameter: none
function _sessToken() {
hilink_tokenlist=""
hilink_token=""
hilink_sessID=""
response=$(curl -s http://$hilink_host/api/webserver/SesTokInfo -m 5 2> /dev/null)
if [ -z "$response" ]; then echo "No access to device at $hilink_host"; return 1; fi
status=$(echo "$response" | sed -nr 's/.*<code>([0-9]*)<\/code>.*/\1/ip')
if [ -z "$status" ]; then
hilink_token=$(echo $response | sed -r 's/.*<TokInfo>(.*)<\/TokInfo>.*/\1/')
hilink_sessID=$(echo $response | sed -r 's/.*<SesInfo>(.*)<\/SesInfo>.*/\1/')
if [ ! -z "$hilink_sessID" ] && [ ! -z "$hilink_token" ]; then
hilink_sessID="SessionID=$hilink_sessID"
return 0
fi
fi
return 1
}
# unlock device (if locked) with user name and password
# requires stored hilink_user="admin"; hilink_password"1234secret";hilink_host=$hilink_host_default
# parameter: none
function _login() {
local ret encpw pwtype pwtype3 hashedpw pwtype4
if _loginState; then return 0; fi # login not required, or already done
_sessToken
# get password type
if ! _sendRequest "api/user/state-login"; then return 1; fi
pwtype=$(echo "$response" | sed -rn 's/.*<password_type>([0-9])<\/password_type>.*/\1/pi')
if [ -z "$pwtype" ];then pwtype=4; fi # fallback is type 4
ret=1
if [[ ! -z "$hilink_user" ]] && [[ ! -z "$hilink_password" ]]; then
# password encoding
# type 3 : base64(pw) encoded
# type 4 : base64(sha256sum(user + base64(sha256sum(pw)) + token))
pwtype3=$(echo -n "$hilink_password" | base64 --wrap=0)
hashedpw=$(echo -n "$hilink_password" | sha256sum -b | sed -nr 's/^([0-9a-z]*).*$/\1/ip' )
hashedpw=$(echo -n "$hashedpw" | base64 --wrap=0)
pwtype4=$(echo -n "$hilink_user$hashedpw$hilink_token" | sha256sum -b | sed -nr 's/^([0-9a-z]*).*$/\1/ip' )
encpw=$(echo -n "$pwtype4" | base64 --wrap=0)
if [ $pwtype -ne 4 ]; then encpw=$pwtype3; fi
hilink_xmldata="<?xml version='1.0' encoding='UTF-8'?><request><Username>$hilink_user</Username><Password>$encpw</Password><password_type>$pwtype</password_type></request>"
hilink_xtraopts="--dump-header $hilink_header_file"
rm -f $hilink_header_file
_sendRequest "api/user/login"
if [ ! -z "$status" ] && [ "$status" = "OK" ]; then
# store the list of 30 tokens. Each token is valid for a single request
hilink_tokenlist=( $(cat $hilink_header_file | sed -rn 's/^__RequestVerificationToken:\s*([0-9a-z#]*).*$/\1/pi' | sed 's/#/ /g') )
_getToken
hilink_sessID=$(cat $hilink_header_file | grep -ioP 'SessionID=([a-z0-9]*)')
if [ ! -z "$hilink_sessID" ] && [ ! -z "$hilink_token" ]; then ret=0; fi
fi
rm -f $hilink_header_file
fi
return $ret
}
# logout of hilink device
# parameter: none
function _logout() {
if _loginState; then
hilink_xmldata="<?xml version: '1.0' encoding='UTF-8'?><request><Logout>1</Logout></request>"
if _sendRequest "api/user/logout"; then
hilink_tokenlist=""
hilink_sessID=""
hilink_token=""
hilink_login_enabled=""
fi
return $?
fi
return 1
}
# parameter: none
function _loginState() {
local state
status="OK"
if [ -z "$hilink_login_enabled" ]; then _checkLoginEnabled; fi
if [ $hilink_login_enabled -eq 1 ]; then return 0; fi # login is disabled
_sendRequest "api/user/state-login"
state=`echo "$response" | sed -rn 's/.*<state>(.*)<\/state>.*/\1/pi'`
if [ ! -z "$state" ] && [ $state -eq 0 ]; then # already logged in
return 0
fi
return 1
}
function _checkLoginEnabled() {
local state
if _sendRequest "api/user/hilink_login"; then
hilink_login_enabled=0
state=$(echo $response | sed -rn 's/.*<hilink_login>(.*)<\/hilink_login>.*/\1/pi')
if [ ! -z "$state" ] && [ $state -eq 0 ]; then # no login enabled
hilink_login_enabled=1
fi
else
hilink_login_enabled=""
fi
}
# switch mobile data on/off 1/0
# if SIM is locked, $hilink_pin has to be set
# parameter: state - ON/OFF or 1/0
function _switchMobileData() {
local mode
if [ -z "$1" ]; then return 1; fi
_login
mode="${1,,}"
[ "$mode" = "on" ] && mode=1
[ "$mode" = "off" ] && mode=0
if [[ $mode -ge 0 ]]; then
if _enableSIM "$hilink_pin"; then
hilink_xmldata="<?xml version: '1.0' encoding='UTF-8'?><request><dataswitch>$mode</dataswitch></request>"
_sendRequest "api/dialup/mobile-dataswitch"
return $?
fi
fi
return 1
}
# parameter: PIN of SIM card
function _setPIN() {
local pin
if [ -z "$1" ]; then return 1; fi
pin="$1"
hilink_xmldata="<?xml version: '1.0' encoding='UTF-8'?><request><OperateType>0</OperateType><CurrentPin>$pin</CurrentPin><NewPin></NewPin><PukCode></PukCode></request>"
_sendRequest "api/pin/operate"
return $?
}
# Send request to host at http://$hilink_host/$apiurl
# data in $hilink_xmldata and options in $hilink_xtraopts
# parameter: apiurl (e.g. "api/user/login")
function _sendRequest() {
local ret apiurl
status="ERROR"
if [ -z "$1" ]; then return 1; fi
apiurl="$1"
ret=1
if [ -z "$hilink_sessID" ] || [ -z "$hilink_token" ]; then _sessToken; fi
if [ -z "$hilink_xmldata" ];then
response=$(curl -s http://$hilink_host/$apiurl -m 10 \
-H "Cookie: $hilink_sessID")
else
response=$(curl -s -X POST http://$hilink_host/$apiurl -m 10 \
-H "Content-Type: text/xml" \
-H "Cookie: $hilink_sessID" \
-H "__RequestVerificationToken: $hilink_token" \
-d "$hilink_xmldata" $hilink_xtraopts 2> /dev/null)
_getToken
fi
if [ ! -z "$response" ];then
response=$(echo $response | tr -d '\012\015') # delete newline chars
status=$(echo "$response" | sed -nr 's/.*<code>([0-9]*)<\/code>.*/\1/ip') # check for error code
if [ -z "$status" ]; then
status="OK"
response=$(echo "$response" | sed -nr 's/.*<response>(.*)<\/response>.*/\1/ip')
[ -z "$response" ] && response="none"
ret=0
else
status="ERROR $status"
fi
else
status="ERROR"
fi
if [[ "$status" =~ ERROR ]]; then _handleError; fi
hilink_xtraopts=""
hilink_xmldata=""
return $ret
}
# handle the list of tokens available after login
# parameter: none
function _getToken() {
if [ ! -z "$hilink_tokenlist" ] && [ ${#hilink_tokenlist[@]} -gt 0 ]; then
hilink_token=${hilink_tokenlist[0]} # get first token in list
hilink_tokenlist=("${hilink_tokenlist[@]:1}") # remove used token from list
if [ ${#hilink_tokenlist[@]} -eq 0 ]; then
_logout # use the last token to logout
fi
else
_sessToken # old token has been used - need new session
fi
}
# Analyse $status for error code
# return error text in $status
function _handleError() {
local ret txt
txt=$(_getErrorText)
if [ -z "$code" ]; then return 1; fi
ret=0
case "$code" in
101|108003|108007)
ret=1
status="$txt"
;;
108001|108002|108006)
ret=1
status="$txt"
;;
125001|125002|125003)
_sessToken
ret=0
;;
*)
;;
esac
return "$ret"
}
declare -A hilink_err_api
hilink_err_api[101]="Unable to get session ID/token"
hilink_err_api[108001]="Invalid username/password"
hilink_err_api[108002]=${hilink_err_api[108001]}
hilink_err_api[108006]=${hilink_err_api[108001]}
hilink_err_api[108003]="User already logged in - need to wait a bit"
hilink_err_api[108007]="Too many login attempts - need to wait a bit"
hilink_err_api[125001]="Invalid session/request token"
hilink_err_api[125002]=${hilink_err_api[125001]}
hilink_err_api[125003]=${hilink_err_api[125001]}
# check error and return error text
# status passsed in $status, or $1
function _getErrorText() {
local err code errortext
err="$status"
code="0"
if [ ! -z "$1" ]; then err="$1"; fi
if [ -z "$err" ]; then return 1; fi
errortext="$err"
if [[ "$err" =~ ERROR\ *([0-9]*) ]] && [ ! -z "${BASH_REMATCH[1]}" ]; then
code=${BASH_REMATCH[1]}
if [ ! -z "$code" ] && [ ! -z "${hilink_err_api[$code]}" ]; then
errortext="${hilink_err_api[$code]}"
fi
fi
echo $errortext
return 0
}
function _hostReachable() {
local avail
avail=$( timeout 0.5 ping -c 1 $hilink_host | sed -rn 's/.*time=.*/1/p' )
if [ -z "$avail" ]; then status="ERROR: Not reachable"; return 1; fi
status="OK"
return 0;
}
# helper function to parse $response (xml format) for a value
# call another function first!
# parameter: tag-name
function _valueFromResponse() {
local par value
if [ -z "$response" ] || [ -z "$1" ]; then return 1; fi
par="$1"
value=$(echo $response | sed -rn 's/.*<'$par'>(.*)<\/'$par'>.*/\1/pi')
if [ -z "$value" ]; then return 1; fi
echo "$value"
return 0
}
# list all keys of the current xml response
function _keysFromResponse() {
if [ -z "$response" ]; then return 1; fi
echo $response | grep -oiP "(?<=<)[a-z_-]*(?=>)"
return 0
}
# return all key=value pairs of the current xml response
function _keyValuePairs() {
if [ -z "$response" ]; then return 1; fi
echo $response | sed -n 's/<\([^>]*\)>\(.*\)<\/\1>[^<]*/\1=\"\2\"\n/gpi'
return 0
}
hilink_token=""
hilink_tokenlist=""
hilink_sessID=""
hilink_xmldata=""
hilink_xtraopts=""
hilink_host=$hilink_host_default
hilink_user="admin"
hilink_password=""
hilink_pin=""
response=""
status=""

View File

@ -0,0 +1,109 @@
#!/bin/bash
# get info about device and signal of Huawei mobile USB devices
# parm:
# $1 : requested information (manufacturer, device, imei, imsi, telnumber, ipaddress, mode, signal, operator)
# $2 : (optional) type - hilink or modem (default: hilink)
# $3 : (optional) for hilink: ip address of the device (default: 192.168.8.1)
# for modem: tty interface for communication (default: /dev/ttypUSB2)
# $4 : more options can be added for Hilink devices ('-u user -P password -p pin'). These are passed to the corresponding script
#
# requires: bc
# calls the scripts info_huawei_hilink.sh and info_huawei_modem.sh (same path as this script)
#
# zbchristian 2020
#
path=$(dirname "$0")
opt="device"
if [ ! -z "$1" ]; then opt=${1,,}; fi
type="hilink"
if [ ! -z "$2" ]; then type=${2,,}; fi
parms=""
if [ "$type" = "hilink" ]; then
connect="-h 192.168.8.1"
if [ ! -z "$3" ]; then connect="-h $3"; fi
if [ ! -z "$4" ]; then parms="$4"; fi
script="$path/info_huawei_hilink.sh"
else
connect="/dev/ttyUSB2"
if [ ! -z "$3" ]; then connect=$3; fi
script="$path/info_huawei_modem.sh"
fi
res=$($script $opt $connect $parms)
# some results require special treatment
case $opt in
# manufacturer)
# if [ "$res" = "none" ]; then res="Huawei"; fi
# ;;
# device)
# if [ ! "$res" = "none" ]; then res="Huawei $res";
# else res="Huawei"; fi
# ;;
mode)
if [ ! "$res" = "none" ]; then
if [ "$type" = "hilink" ]; then
if [ "$res" = "LTE" ]; then res="4G"
elif [ "$res" = "WCDMA" ]; then res="3G";
else res="2G"; fi
else
if [ $res -eq 7 ]; then res="4G"
elif [ $res -lt 7 ] && [ $res -gt 2 ] ; then res="3G";
else res="2G"; fi
fi
fi
;;
signal)
# return signal strength/quality in %
if [ "$type" = "hilink" ]; then
# signal request tries to get RSRQ value
# try to get RSRQ (4G), EC/IO (3G) or RSSI (2G) value
if [ "$res" = "none" ]; then res=$($script "ecio"); fi
if [ ! "$res" = "none" ]; then
# for rsrq and ecio assume: -3dB (100%) downto -20dB (0%)
qual=${res//dB/}
if [[ ! "$qual" =~ [-0-9\.]* ]]; then qual=-100; fi
qual=$(bc <<< "scale=0;res=$qual-0.5;res/1") # just round to next integer
if [ $qual -le -20 ]; then qual=0;
elif [ $qual -ge -3 ]; then qual=100;
else qual=$(bc <<< "scale=0;res=100.0/17.0*$qual+2000.0/17.0;res/1"); fi
else
# try rssi: >-70dBm (100%) downto -100dBm (0%)
res=$($script "rssi");
if [ ! "$res" = "none" ]; then
if [[ ! $res =~ [-0-9\.]* ]]; then res="-120 dBm"; fi
qual=${res//dBm/}
qual=$(bc <<< "scale=0;res=$qual+0.5;res/1") # just round to next integer
if [ $qual -le -110 ]; then qual=0;
elif [ $qual -ge -70 ]; then qual=100;
else qual=$(bc <<< "scale=0;res=2.5*$qual+275;res/1"); fi
fi
fi
else
# modem returns RSSI as number 0-31 - 0 = -113dB (0%), 1 = -111dB, 31 = >=51dB (100%)
qual=$(bc <<< "scale=0;res=$res*3.5+0.5;res/1")
if [ $qual -gt 100 ]; then res=100; fi
fi
if [ ! "$res" = "none" ]; then res="$res (${qual}%)"; fi
;;
operator)
# check if operator/network is just a 5 digit number -> extract network name from table
if [[ $res =~ ^[0-9]{5}$ ]]; then
mcc=${res:0:3}
mnc=${res:3:2}
op=$(cat $path/mcc-mnc-table.csv | sed -rn 's/^'$mcc'\,[0-9]*\,'$mnc'\,(.*\,){4}(.*)$/\2/p')
if [ ! -z "$op" ]; then res="$op ($res)"; fi
fi
;;
*)
;;
esac
echo $res

View File

@ -0,0 +1,95 @@
#!/bin/bash
# Information about HUAWEI hilink
# -------------------------------
# get info about the device and signal
# parameter: $1 - "connected", "device", "ipaddress", "mode", "signal" (see case statement below)
# -u,--user - username
# -P,--password - password
# -p,--pin - SIM pin
# -h,--host - host ip address for API calls (optional)
# returns the value of the parameter, or "none" if not found or empty
#
# All device informations are buffered for 5 secs to speed up subsequent calls
#
# zbchristian 2021
function _setAPIParams() {
if [ ! -z "$hostip" ]; then hilink_host="$hostip"; fi
if [ ! -z "$username" ]; then hilink_user="$username"; fi
if [ ! -z "$password" ]; then hilink_password="$password"; fi
if [ ! -z "$simpin" ]; then hilink_pin="$simpin"; fi
}
if [ -z "$1" ]; then echo "none"; exit; fi
property="${1,,}"
shift
hostip="192.168.8.1"
while [ -n "$1" ]; do
case "$1" in
-u|--user) username="$2"; shift ;;
-P|--password) password="$2"; shift ;;
-p|--pin) simpin="$2"; shift ;;
-h|--host) hostip="$2"; shift ;;
esac
shift
done
status="no valid option given"
result="none"
hostip="192.168.8.1"
if [ "$opt" = "connected" ]; then
source /usr/local/sbin/huawei_hilink_api.sh
_setAPIParams
if ! _initHilinkAPI; then echo "none"; exit; fi
result=$(_getMobileDataStatus)
_closeHilinkAPI
else
info_file="/tmp/huawei_infos_${hostip}_$(id -u).dat"
if [ -f "$info_file" ]; then
age=$(( $(date +%s) - $(stat $info_file -c %Y) ))
if [[ $age -gt 10 ]]; then rm -f $info_file; fi
fi
if [ -f "$info_file" ]; then
infos=$(cat $info_file)
else
source /usr/local/sbin/huawei_hilink_api.sh
_setAPIParams
if ! _initHilinkAPI; then echo "none"; exit; fi
infos=$(_getAllInformations)
_closeHilinkAPI
if [ ! -z "$infos" ]; then echo -n "$infos" > $info_file; fi
fi
case "$property" in
device|devicename)
key="devicename"
;;
ipaddress|wanipaddress)
key="wanipaddress"
;;
mode)
key="workmode"
;;
telnumber)
key="msisdn"
;;
imei|imsi|rssi|rsrq|rsrp|sinr|ecio)
key="$property"
;;
signal)
key="rsrq"
;;
operator|fullname)
key="fullname"
;;
*)
key="device"
;;
esac
if [ -z "$key" ]; then result="none"; fi
result=$(echo "$infos" | sed -rn 's/'$key'=\"([^ \s]*)\"/\1/ip')
if [ -z "$result" ]; then result="none"; fi
fi
echo -n "$result"

View File

@ -0,0 +1,52 @@
#!/bin/bash
# Information about HUAWEI modem - via AT commands
# ------------------------------------------------
# get info about the device and signal
# parameter: $1 - see opts list below
# $2 - tty device name for the communicaton (optional)
# returns the value of the parameter, or "none" if not found or empty
#
# requires: socat
#
# zbchristian 2020
opts=("manufacturer" "device" "imei" "imsi" "telnumber" "mode" "signal" "operator")
# at command to extract information
atcmds=("AT+CGMI" "AT+CGMM" "AT+CGSN" "AT+CIMI" "AT+CNUM" "AT+COPS?" "AT+CSQ" "AT+COPS?")
# regexp pattern to extract wanted information from result string
pats=( " " " " " " " " ".*\,\"([0-9\+]*)\".*" '.*\,([0-9])$' ".*: ([0-9]*).*" '.*\,\"([^ ]*)\".*$')
# tty device for communication - usually 3 tty devices are created and the 3rd ttyUSB2 is available, even, when the device is connected
dev="/dev/ttyUSB2"
atsilent="AT^CURC=0"
if [ ! -z $2 ]; then dev=$2; fi
idx=-1
opt=${opts[0]}
if [ ! -z $1 ]; then opt=$1; fi
for i in "${!opts[@]}"; do
if [[ ${opts[$i]} == $opt ]]; then idx=$i; fi
done
if [[ $idx == -1 ]];then echo "none"; exit; fi
atcmd=${atcmds[$idx]}
pat=${pats[$idx]}
result=`(echo $atsilent; echo $atcmd) | sudo /usr/bin/socat - $dev`
# escape the AT command to be used in the regexp
atesc=${atcmd//[\+]/\\+}
atesc=${atesc//[\?]/\\?}
result=`echo $result | sed -rn 's/.*'"$atesc"'\s([^ ]+|[^ ]+ [^ ]+)\sOK.*$/\1/pg'`
if [[ $pat != " " ]]; then
result=`echo $result | sed -rn 's/'"$pat"'/\1/pg'`
fi
if [ -z "$result" ]; then result="none"; fi
echo $result

View File

@ -0,0 +1,13 @@
# interfaces(5) file used by ifup(8) and ifdown(8)
# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'
# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d
auto ppp0
iface ppp0 inet wvdial
provider connect
pre-up /usr/local/sbin/ppp0_setpin.sh
up /usr/local/sbin/ppp0_route.sh

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
#!/bin/bash
# connect/disconnect Huawei mobile data stick in Hilink mode (e.g. E3372h)
# ========================================================================
#
# options: -u, --user - user name (default "admin")
# -P, --password - password
# -h, --host - host ip address (default 192.168.8.1)
# -d, --devname - device name (IP is extracted using default route)
# -p, --pin - PIN of SIM card
# -c, --connect - connect 0/1 to set datamode off/on
#
# required software: curl, base64, sha256sum
#
# zbchristian 2021
# include the hilink API (defaults: hilink_user=admin, hilink_host=192.168.8.1)
source /usr/local/sbin/huawei_hilink_api.sh
# include the raspap helper functions
source /usr/local/sbin/raspap_helpers.sh
datamode=""
devname=""
while [ -n "$1" ]; do
case "$1" in
-u|--user) hilink_user="$2"; shift ;;
-P|--password) hilink_password="$2"; shift ;;
-p|--pin) if [[ $2 =~ ^[0-9]{4,8} ]]; then hilink_pin="$2"; fi; shift ;;
-h|--host) hilink_host="$2"; shift ;;
-d|--devname) devname="$2"; shift ;;
-c|--connect) if [ "$2" = "1" ]; then datamode=1; else datamode=0; fi; shift ;;
esac
shift
done
if [ ! -z "$devname" ]; then # get host IP for given device name
gw=$(ip route list | sed -rn "s/default via (([0-9]{1,3}\.){3}[0-9]{1,3}).*dev $devname.*/\1/p")
if [ -z "$gw" ]; then exit; fi # device name not found in routing list -> abort
hilink_host="$gw"
fi
if [ -z "$hilink_password" ] || [ -z "$hilink_pin" ]; then
_getAuthRouter
if [ ! -z "$raspap_user" ]; then hilink_user="$raspap_user"; fi
if [ ! -z "$raspap_password" ]; then hilink_password="$raspap_password"; fi
if [ ! -z "$raspap_pin" ]; then hilink_pin="$raspap_pin"; fi
fi
echo "Hilink: switch device at $hilink_host to mode $datamode" | systemd-cat
status="usage: -c 1/0 to disconnect/connect"
if [ -z "$datamode" ] || [ ! _initHilinkAPI ]; then echo "Hilink: failed - return status: $status"; exit; fi
if ! _switchMobileData "$datamode"; then echo -n "Hilink: could not switch the data mode on/off . Error: ";_getErrorText; fi
if ! _closeHilinkAPI; then echo -n "Hilink: failed - return status: $status . Error: ";_getErrorText; fi

View File

@ -0,0 +1,21 @@
#!/bin/bash
#
# get gateway and ip address of UTMS modem connected to ppp0
# add a default route
# called by /etc/network/interfaces.d/ppp0, when device is coming up
#
ppp0rt=""
let i=1
while [ -z "$ppp0rt" ] ; do
let i+=1
if [ $i -gt 20 ]; then
exit 1
fi
sleep 1
ppp0rt=`ip route list | grep -m 1 ppp0`
done
gate=`echo $ppp0rt | sed -rn 's/(([0-9]{1,3}\.){3}[0-9]{1,3}).*ppp0.*src (([0-9]{1,3}\.){3}[0-9]{1,3})/\1/p'`
src=`echo $ppp0rt | sed -rn 's/(([0-9]{1,3}\.){3}[0-9]{1,3}).*ppp0.*src (([0-9]{1,3}\.){3}[0-9]{1,3})/\3/p'`
ip route add default via $gate proto dhcp src $src metric 10
exit 0

View File

@ -0,0 +1,21 @@
#!/bin/bash
# in case /dev/ttyUSB0 does not exist, wait for it at most 30 seconds
let i=1
while ! test -c /dev/ttyUSB0; do
let i+=1
if [ $i -gt 2 ]; then
logger -s -t setpin "/dev/ttyUSB0 does not exist"
exit 3
fi
logger -s -t setpin "waiting 3 seconds for /dev/ttyUSB0"
sleep 3
done
# check for pin and set it if necessary
wvdial pinstatus 2>&1 | grep -q '^+CPIN: READY'
if [ $? -eq 0 ]; then
logger -s -t setpin "SIM card is ready to use :-)"
else
logger -s -t setpin "setting PIN"
wvdial pin 2>/dev/null
fi
exit 0

View File

@ -0,0 +1,51 @@
#!/bin/bash
#
# Helper functions to extract informations from RaspAP config/settings
#
# zbchristian 2021
#
# get the values of a RaspAP config variable
# call: _getRaspapConfig RASPAP_MOBILEDATA_CONFIG
raspap_webroot="/var/www/html"
function _getWebRoot() {
local path
path=$(cat /etc/lighttpd/lighttpd.conf | sed -rn "s/server.document-root \s*= \"([^ \s]*)\"/\1/p")
if [ ! -z "$path" ]; then raspap_webroot="$path"; fi
if [ -z "$path" ]; then return 1; else return 0; fi
}
# expand an RaspAP config variable utilizing PHP
function _getRaspapConfig() {
local conf var
raspap_config=""
var="$1"
if [ ! -z "$var" ]; then
if ! _getWebRoot; then return 1; fi
conf="$raspap_webroot/includes/config.php"
if [ -f "$conf" ]; then
conf=$(php -r 'include "'$conf'"; echo '$var';' 2> /dev/null)
if [ ! -z "$conf" ] && [ -d ${conf%/*} ]; then raspap_config="$conf"; fi
fi
fi
if [ -z "$raspap_config" ]; then return 1; else return 0; fi
}
# Username and password for mobile data devices is stored in a file (RASPAP_MOBILEDATA_CONFIG)
function _getAuthRouter() {
local mfile mdata pin user pw
if ! _getRaspapConfig "RASPI_MOBILEDATA_CONFIG"; then return 1; fi
mfile="$raspap_config"
if [ -f $mfile ]; then
mdata=$(cat "$mfile")
pin=$(echo "$mdata" | sed -rn 's/pin = ([^ \s]*)/\1/ip')
if [ ! -z "$pin" ]; then raspap_pin="$pin"; fi
user=$(echo "$mdata" | sed -rn 's/router_user = ([^ \s]*)/\1/ip')
if [ ! -z "$user" ]; then raspap_user="$user"; fi
pw=$(echo "$mdata" | sed -rn 's/router_pw = ([^ \s]*)/\1/ip')
if [ ! -z "$pw" ]; then raspap_password="$pw"; fi
return 0
fi
return 1
}

View File

@ -0,0 +1,13 @@
[Unit]
Description=Bring up HUAWEI mobile hilink device
[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/bin/sleep 15
ExecStart=/usr/local/sbin/onoff_huawei_hilink.sh -c 1 -d %i
[Install]
Alias=start_ltemodem.service
WantedBy=multi-user.target

View File

@ -0,0 +1,16 @@
[Unit]
Description=Start ppp0 interface
BindsTo=dev-ttyUSB0.device
After=dev-ttyUSB0.device
[Service]
Type=forking
RemainAfterExit=yes
ExecStart=/sbin/ifup ppp0
ExecStop=/sbin/ifdown ppp0
[Install]
Alias=startppp0.service
WantedBy=multi-user.target

View File

@ -0,0 +1,21 @@
[Dialer Defaults]
Modem Type = Analog Modem
ISDN = 0
Baud = 9600
Modem = /dev/ttyUSB0
[Dialer pin]
Init1 = AT+CPIN="XXXX"
[Dialer connect]
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Init3 = AT+CGDCONT=1,"IP","web.vodafone.de"
New PPPD = yes
Phone = *99#
Password = me
Username = vodafone
Stupid Mode = 1
[Dialer pinstatus]
Init1 = AT+CPIN?

View File

@ -0,0 +1,64 @@
{
"info": "UDEV rules for different client types. $...$ expressions will be replaces automatically ($MAC$, $IDVENDOR$, $IDPRODUCT$, $DEVNAME$)",
"udev_rules_file": "/etc/udev/rules.d/80-raspap-net-devices.rules",
"script_path": "/usr/local/sbin",
"network_devices": [
{
"type": "eth",
"type_info": "ethernet port",
"clientid": 0,
"comment": "standard ethernet port",
"name_prefix": "eth",
"udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", ATTR{address}==\"$MAC$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"eth\" "
},
{
"type": "usb",
"type_info": "usb network interface",
"clientid": 1,
"comment": "network interface - e.g. USB tethering of an Android phone ",
"name_prefix": "usb",
"udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"$IDVENDOR$\", ATTRS{idProduct}==\"$IDPRODUCT$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"eth\" "
},
{
"type": "wlan",
"type_info": "wireless adapter",
"clientid": 2,
"comment": "standard wireless interface",
"name_prefix": "wlan",
"udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", ATTR{address}==\"$MAC$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"wlan\" "
},
{
"type": "ppp",
"type_info": "mobile data modem",
"clientid": 3,
"name_prefix": "ppp",
"comment": "recognized mobile data modems are automatically named as ppp0-9. Renaming is not possible. Dialin service relies on the name",
"udev_rule": "SUBSYSTEM==\"tty\", KERNEL==\"ttyUSB0\", TAG+=\"systemd\", ENV{SYSTEMD_WANTS}=\"start start_ppp0_device.service\" "
},
{
"type": "hilink",
"type_info": "Huawei Hilink",
"clientid": 4,
"comment": "Huawei mobile data device in router mode. Control via HTTP. Device is connecting via service",
"name_prefix": "hilink",
"default_ip": "192.168.8.1",
"udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"$IDVENDOR$\", ATTRS{idProduct}==\"$IDPRODUCT$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"hilink\", TAG+=\"systemd\", ENV{SYSTEMD_WANTS}=\"start start_huawei_hilink@hilink%n.service\" "
},
{
"type": "phone",
"type_info": "USB tethered phone",
"clientid": 5,
"comment": "ethernet access provided by tethering from phone via USB",
"name_prefix": "phone",
"udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"$IDVENDOR$\", ATTRS{idProduct}==\"$IDPRODUCT$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"phone\" "
},
{
"type": "tun",
"type_info": "tunnel device",
"clientid": -1,
"comment": "tunneling device used by OpenVPN",
"name_prefix": "tun"
}
]
}

View File

@ -1,33 +1,49 @@
<?php
define('RASPI_VERSION', '2.1');
define('RASPI_BRAND_TEXT', 'RaspAP');
define('RASPI_CONFIG', '/etc/raspap');
define('RASPI_CONFIG_NETWORKING', RASPI_CONFIG.'/networking');
define('RASPI_CONFIG_NETWORK', RASPI_CONFIG.'/networking/defaults.json');
define('RASPI_ADMIN_DETAILS', RASPI_CONFIG.'/raspap.auth');
define('RASPI_WIFI_CLIENT_INTERFACE', 'wlan0');
define('RASPI_WIFI_AP_INTERFACE', 'wlan0');
define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap');
// Constants for configuration file paths.
// These are typical for default RPi installs. Modify if needed.
define('RASPI_DNSMASQ_CONFIG', '/etc/dnsmasq.conf');
define('RASPI_DNSMASQ_LEASES', '/var/lib/misc/dnsmasq.leases');
define('RASPI_DNSMASQ_PREFIX', '/etc/dnsmasq.d/090_');
define('RASPI_ADBLOCK_LISTPATH', '/etc/raspap/adblock/');
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_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');
define('RASPI_OPENVPN_CLIENT_PATH', '/etc/openvpn/client/');
define('RASPI_OPENVPN_CLIENT_CONFIG', '/etc/openvpn/client/client.conf');
define('RASPI_OPENVPN_CLIENT_LOGIN', '/etc/openvpn/client/login.conf');
define('RASPI_OPENVPN_SERVER_CONFIG', '/etc/openvpn/server/server.conf');
define('RASPI_WIREGUARD_PATH', '/etc/wireguard/');
define('RASPI_WIREGUARD_CONFIG', RASPI_WIREGUARD_PATH.'wg0.conf');
define('RASPI_TORPROXY_CONFIG', '/etc/tor/torrc');
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');
// 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);
// Optional services, set to true to enable.
define('RASPI_WIFICLIENT_ENABLED', true);
define('RASPI_HOTSPOT_ENABLED', true);
define('RASPI_NETWORK_ENABLED', true);
define('RASPI_DHCP_ENABLED', true);
define('RASPI_ADBLOCK_ENABLED', false);
define('RASPI_OPENVPN_ENABLED', false);
define('RASPI_WIREGUARD_ENABLED', false);
define('RASPI_TORPROXY_ENABLED', false);
define('RASPI_CONFAUTH_ENABLED', true);
define('RASPI_CHANGETHEME_ENABLED', true);

View File

@ -1,12 +0,0 @@
# Location of hostapd configuration file
DAEMON_CONF="/etc/hostapd/hostapd.conf"
# Additional daemon options to be appended to hostapd command:-
# -d show more debug messages (-dd for even more)
# -K include key data in debug messages
# -t include timestamps in some debug messages
#
# Note that -B (daemon mode) and -P (pidfile) options are automatically
# configured by the init.d script and must not be added to DAEMON_OPTS.
#
#DAEMON_OPTS=""

57
config/defaults.json Normal file
View File

@ -0,0 +1,57 @@
{
"dhcp": {
"wlan0": {
"static ip_address": [ "10.3.141.1/24" ],
"static routers": [ "10.3.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" ],
"static domain_name_server": [ "1.1.1.1 8.8.8.8" ],
"subnetmask": [ "255.255.255.0" ]
},
"options": {
"# RaspAP default configuration": null,
"hostname": null,
"clientid": null,
"persistent": null,
"option rapid_commit": null,
"option domain_name_servers, domain_name, domain_search, host_name": null,
"option classless_static_routes": null,
"option ntp_servers": null,
"require dhcp_server_identifier": null,
"slaac private": null,
"nohook lookup-hostname": null
}
},
"dnsmasq": {
"wlan0": {
"dhcp-range": [ "10.3.141.50,10.3.141.254,255.255.255.0,12h" ]
},
"uap0": {
"dhcp-range": [ "192.168.50.50,192.168.50.150,12h" ]
}
},
"wireguard": {
"server": {
"Address": [ "10.8.2.1/24" ],
"ListenPort": [ "51820" ],
"DNS": [ "9.9.9.9" ],
"PostUp": [ "iptables -A FORWARD -i wlan0 -o wg0 -j ACCEPT; iptables -A FORWARD -i wg0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE" ],
"PostDown": [ "iptables -D FORWARD -i wlan0 -o wg0 -j ACCEPT; iptables -D FORWARD -i wg0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE" ]
},
"peer": {
"Address": [ "10.8.1.2/24" ],
"Endpoint": [ "10.8.2.1:51820" ],
"ListenPort": [ "21841" ],
"AllowedIPs": ["10.8.2.0/24"],
"PersistentKeepalive": [ "15" ]
}
},
"txpower": {
"dbm": [ "auto", "30", "20", "17", "10", "6", "3", "1", "0" ]
}
}

View File

@ -1,4 +1,4 @@
# Defaults from Raspberry Pi configuration
# RaspAP default configuration
hostname
clientid
persistent
@ -14,9 +14,5 @@ nohook lookup-hostname
interface wlan0
static ip_address=10.3.141.1/24
static routers=10.3.141.1
static domain_name_server=1.1.1.1 8.8.8.8
static domain_name_server=9.9.9.9 1.1.1.1
# RaspAP uap0 configuration
interface uap0
static ip_address=192.168.50.1/24
nohook wpa_supplicant

32
config/dns-servers.json Normal file
View File

@ -0,0 +1,32 @@
{
"Cloudflare": [
"1.0.0.1",
"1.1.1.1"
],
"Freenom World": [
"80.80.80.80",
"80.80.81.81"
],
"German Privacy Foundation": [
"62.141.58.13",
"85.25.251.254",
"87.118.100.175",
"94.75.228.29"
],
"Google": [
"8.8.4.4",
"8.8.8.8"
],
"OpenDNS": [
"208.67.220.220",
"208.67.222.222"
],
"Quad9": [
"9.9.9.9",
"149.112.112.112"
],
"Yandex.DNS": [
"77.88.8.2",
"77.88.8.88"
]
}

View File

@ -1,13 +0,0 @@
# RaspAP wlan0 configuration for wired (ethernet) AP mode
interface=wlan0
dhcp-range=10.3.141.50,10.3.141.255,255.255.255.0,12h
dhcp-option=6,1.1.1.1,8.8.8.8
# RaspAP uap0 configuration for wireless client AP mode
#interface=lo,uap0 # Use interfaces lo and uap0
#bind-interfaces # Bind to the interfaces
#server=8.8.8.8 # Forward DNS requests to Google DNS
#domain-needed # Don't forward short names
#bogus-priv # Never forward addresses in the non-routed address spaces
#dhcp-range=192.168.50.50,192.168.50.150,12h

View File

@ -9,13 +9,16 @@ channel=1
hw_mode=g
wpa_passphrase=ChangeMe
interface=wlan0
wpa=1
wpa_pairwise=TKIP
country_code=
wpa=2
wpa_pairwise=CCMP
country_code=GB
## Rapberry Pi 3 specific to on board WLAN/WiFi
#ieee80211n=1 # 802.11n support (Raspberry Pi 3)
#wmm_enabled=1 # QoS support (Raspberry Pi 3)
#ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40] # (Raspberry Pi 3)
## RaspAP wireless client AP mode
#interface=uap0
#interface=uap0
## RaspAP bridge AP mode (disabled by default)
#bridge=br0

205
config/iptables_rules.json Normal file
View File

@ -0,0 +1,205 @@
{
"info": "IPTABLES rules. $...$ expressions will be replaces automatically ($INTERFACE$, $PORT$, $IPADDRESS$)",
"rules_v4_file": "/etc/iptables/rules.v4",
"rules_v6_file": "/etc/iptables/rules.v6",
"order": [ "pre_rules", "restriction_rules", "main_rules", "exception_rules" ],
"pre_rules": [
{
"name": "firewall policies",
"fw-state": true,
"comment": "Policy rules (firewall)",
"rules": [
"-P INPUT DROP",
"-P FORWARD ACCEPT",
"-P OUTPUT ACCEPT",
"-t nat -P PREROUTING ACCEPT",
"-t nat -P POSTROUTING ACCEPT",
"-t nat -P INPUT ACCEPT",
"-t nat -P OUTPUT ACCEPT"
]
},
{
"name": "policies",
"fw-state": false,
"comment": "Policy rules",
"rules": [
"-P INPUT ACCEPT",
"-P FORWARD ACCEPT",
"-P OUTPUT ACCEPT",
"-t nat -P PREROUTING ACCEPT",
"-t nat -P POSTROUTING ACCEPT",
"-t nat -P INPUT ACCEPT",
"-t nat -P OUTPUT ACCEPT"
]
},
{
"name": "loopback",
"fw-state": true,
"comment": "allow loopback device",
"rules": [
"-A INPUT -i lo -j ACCEPT",
"-A OUTPUT -o lo -j ACCEPT"
]
},
{
"name": "ping",
"fw-state": true,
"ip-version": 4,
"comment": "allow ping request and echo",
"rules": [
"-A INPUT -p icmp --icmp-type 8/0 -j ACCEPT",
"-A INPUT -p icmp --icmp-type 0/0 -j ACCEPT"
]
},
{
"name": "ping IPv6",
"fw-state": true,
"ip-version": 6,
"comment": "allow ping request and echo for IPv6",
"rules": [
"-A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT",
"-A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT"
]
},
{
"name": "ntp",
"fw-state": true,
"comment": "allow ntp request via udp (tcp should work w/o rule)",
"rules": [
"-A INPUT -p udp --sport 123 -j ACCEPT"
]
},
{
"name": "dns",
"fw-state": true,
"comment": "allow dns request via tcp and udp",
"rules": [
"-A INPUT -p udp -m multiport --sport 53,853 -j ACCEPT",
"-A INPUT -p tcp -m multiport --sport 53,853 -j ACCEPT"
]
}
],
"main_rules": [
{
"name": "accesspoint",
"fw-state": true,
"comment": "Access point interface by default no restrictions",
"dependson": [
{ "var": "ap-device", "type": "string", "replace": "$INTERFACE$" }
],
"rules": [
"-A INPUT -i $INTERFACE$ -j ACCEPT",
"-A OUTPUT -o $INTERFACE$ -j ACCEPT"
]
},
{
"name": "NAT for access point",
"comment": "Masquerading needed for access point",
"rules": [
"-t nat -A POSTROUTING -j MASQUERADE"
]
},
{
"name": "clients",
"fw-state": true,
"comment": "Rules for client interfaces (includes tun device)",
"rules": [
"-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT"
]
},
{
"name": "openvpn",
"comment": "Rules for tunnel device (tun)",
"ip-version": 4,
"dependson": [
{ "var": "openvpn-enable", "type": "bool" },
{ "var": "openvpn-serverip", "type": "string", "replace": "$IPADDRESS$" },
{ "var": "ap-device", "type": "string", "replace": "$INTERFACE$" }
],
"rules": [
"-A INPUT -p udp -s $IPADDRESS$ -j ACCEPT",
"-A FORWARD -i tun+ -o $INTERFACE$ -m state --state RELATED,ESTABLISHED -j ACCEPT",
"-A FORWARD -i $INTERFACE$ -o tun+ -j ACCEPT",
"-t nat -A POSTROUTING -o tun+ -j MASQUERADE"
]
},
{
"name": "wireguard",
"comment": "Rules for wireguard device (wg)",
"ip-version": 4,
"dependson": [
{ "var": "wireguard-enable", "type": "bool" },
{ "var": "wireguard-serverip", "type": "string", "replace": "$IPADDRESS$" },
{ "var": "client-device", "type": "string", "replace": "$INTERFACE$" }
],
"rules": [
"-A INPUT -p udp -s $IPADDRESS$ -j ACCEPT",
"-A FORWARD -i wg+ -j ACCEPT",
"-t nat -A POSTROUTING -o $INTERFACE$ -j MASQUERADE"
]
}
],
"exception_rules": [
{
"name": "ssh",
"fw-state": true,
"comment": "Allow ssh access to RaspAP on port 22",
"dependson": [
{ "var": "ssh-enable", "type": "bool" }
],
"rules": [
"-A INPUT -p tcp --dport 22 -j ACCEPT"
]
},
{
"name": "http",
"fw-state": true,
"comment": "Allow access to RaspAP GUI (https)",
"dependson": [
{ "var": "http-enable", "type": "bool" }
],
"rules": [
"-A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT"
]
},
{
"name": "interface",
"fw-state": true,
"comment": "Exclude interface from firewall",
"dependson": [
{ "var": "excl-devices", "type": "list", "replace": "$INTERFACE$" }
],
"rules": [
"-A INPUT -i $INTERFACE$ -j ACCEPT",
"-A OUTPUT -o $INTERFACE$ -j ACCEPT"
]
},
{
"name": "ipaddress",
"fw-state": true,
"ip-version": 4,
"comment": "allow access from/to IP",
"dependson": [
{ "var": "excluded-ips", "type": "list", "replace": "$IPADDRESS$" }
],
"rules": [
"-A INPUT -s $IPADDRESS$ -j ACCEPT",
"-A INPUT -d $IPADDRESS$ -j ACCEPT"
]
}
],
"restriction_rules": [
{
"name": "ipaddress",
"fw-state": true,
"ip-version": 4,
"dependson": [
{ "var": "restricted-ips", "type": "list", "replace": "$IPADDRESS$" }
],
"comment": "Block access from IP-address",
"rules": [
"-A INPUT -s $IPADDRESS$ -j DROP"
]
}
]
}

View File

@ -0,0 +1,5 @@
[Match]
Name=eth0
[Network]
Bridge=br0

View File

@ -0,0 +1,3 @@
[NetDev]
Name=br0
Kind=bridge

View File

@ -0,0 +1,11 @@
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: no
bridges:
br0:
dhcp4: yes
interfaces:
- eth0

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 ]
}
}}

3
crowdin.yml Normal file
View File

@ -0,0 +1,3 @@
files:
- source: /locale/en_US/LC_MESSAGES/messages.po
translation: /locale/%locale_with_underscore%/LC_MESSAGES/%original_file_name%

View File

@ -1,70 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## [3.5.0](https://github.com/gitbrent/bootstrap4-toggle/tree/v3.5.0) 2019-07-02
### [Full Changelog](https://github.com/gitbrent/bootstrap4-toggle/compare/v3.4.0...v3.5.0)
### Added
- Added ARIA `role="button"` tag to toggle
- Added `cursor: pointer;` style to toggle
### Changed
- Fixed: Touch not working on mobile [\#2](https://github.com/gitbrent/bootstrap4-toggle/issue/2) ([wilecoyte78](https://github.com/wilecoyte78))
- Updated to Bootstrap version 4.3.1
- Updated README with better Yarn instructions
## [3.4.0](https://github.com/gitbrent/bootstrap4-toggle/tree/v3.4.0) 2019-01-03
### [Full Changelog](https://github.com/gitbrent/bootstrap4-toggle/compare/v3.3.0...v3.4.0)
### Added
- Outline button styles are now available
### Changed
- Updated to Bootstrap version 4.2.1
## [3.3.0](https://github.com/gitbrent/bootstrap4-toggle/tree/v3.3.0) 2018-12-19
### [Full Changelog](https://github.com/gitbrent/bootstrap4-toggle/compare/v3.2.0...v3.3.0)
### Added
- New test created to compare core bootstrap sizes to bootstrap4-toggle
### Changed
- Introduced new `size` values that mirror bootstrap 4: (`lg`, `sm`, `xs`)
- Converted all css units from `px` to `rem`
- Properly added border on `light` button (moved from .toggle class)
### Removed
**DEPRECATED** Classic `size` values (`large`, `small`, `mini`)
## [3.2.0](https://github.com/gitbrent/bootstrap4-toggle/tree/v3.2.0) 2018-11-27
### [Full Changelog](https://github.com/gitbrent/bootstrap4-toggle/compare/v3.1.0...v3.2.0)
### Added
### Changed
- Removed permanent `active` state from "Off" label so mouse-over highlighting works the same as "On"
### Removed
## [3.1.0](https://github.com/gitbrent/bootstrap4-toggle/tree/v3.1.0) 2018-10-25
### [Full Changelog](https://github.com/gitbrent/bootstrap4-toggle/compare/v3.0.0...v3.1.0)
### Added
- `index.html` includes new section with dark mode colors
### Changed
- `index.html` now fully responsive, better menu, rearranged sections
- Tweaked `border` property to work with all backgrounds and colors
### Removed
## [3.0.0](https://github.com/gitbrent/bootstrap4-toggle/tree/v3.0.0) 2018-10-21
### [Full Changelog](https://github.com/gitbrent/bootstrap4-toggle/compare/v2.2.2...v3.0.0)
### Added
- Touch support
### Changed
- Implements Bootstrap 4 colors/styles
### Removed
- Old Bootstrap 2 files
[Unreleased]: https://github.com/gitbrent/bootstrap4-toggle/compare/v1.9.0...HEAD
[3.2.0]: https://github.com/gitbrent/bootstrap4-toggle/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/gitbrent/bootstrap4-toggle/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/gitbrent/bootstrap4-toggle/compare/v2.2.2...v3.0.0

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2011-2014 Min Hur, The New York Times Company
Copyright (c) 2018 Brent Ely
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,176 +0,0 @@
[![MIT Licence](https://img.shields.io/github/license/gitbrent/bootstrap4-toggle.svg)](https://opensource.org/licenses/mit-license.php) [![Bootstrap 4.2.1](https://img.shields.io/badge/bootstrap-4.3.1-green.svg?style=flat-square)](https://getbootstrap.com/docs/4.1) [![Known Vulnerabilities](https://snyk.io/test/npm/bootstrap4-toggle/badge.svg)](https://snyk.io/test/npm/bootstrap4-toggle) [![npm downloads](https://img.shields.io/npm/dm/bootstrap4-toggle.svg)](https://www.npmjs.com/package/bootstrap4-toggle) [![JSDelivr Badge](https://data.jsdelivr.com/v1/package/gh/gitbrent/bootstrap4-toggle/badge)](https://www.jsdelivr.com/package/gh/gitbrent/bootstrap4-toggle)
# Bootstrap 4 Toggle
**Bootstrap 4 Toggle** is a bootstrap plugin/widget that converts checkboxes into toggles.
**************************************************************************************************
#### Library Distributions
Project |Description
-------------------------------------------------------------------------------------------|-------------------------------------------------------
[bootstrap4-toggle](https://github.com/gitbrent/bootstrap4-toggle) | Supports bootstrap4 (requires jQuery)
[bootstrap-switch-button](https://github.com/gitbrent/bootstrap-switch-button) | Supports bootstrap4+ (ES6 class, no dependencies)
[bootstrap-switch-button-react](https://github.com/gitbrent/bootstrap-switch-button-react) | Supports bootstrap4+ (React component, no dependencies)
# Demos
**Demos and API Docs:** https://gitbrent.github.io/bootstrap4-toggle/
![Demo GIF](https://github.com/gitbrent/bootstrap4-toggle/blob/master/doc/bootstrap4-toggle-demo.gif?raw=true)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Installation](#installation)
- [CDN](#cdn)
- [Download](#download)
- [NPM](#npm)
- [Yarn](#yarn)
- [Usage](#usage)
- [Initialize With HTML](#initialize-with-html)
- [Initialize With Code](#initialize-with-code)
- [API](#api)
- [Options](#options)
- [Methods](#methods)
- [Events](#events)
- [Event Propagation](#event-propagation)
- [API vs Input](#api-vs-input)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
**************************************************************************************************
# Installation
## CDN
```html
<link href="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.5.0/css/bootstrap4-toggle.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.5.0/js/bootstrap4-toggle.min.js"></script>
```
## Download
[Latest GitHub Release](https://github.com/gitbrent/bootstrap4-toggle/releases/latest)
## NPM
```ksh
npm install bootstrap4-toggle
```
## Yarn
```ksh
yarn add bootstrap4-toggle
```
# Usage
## Initialize With HTML
Simply add `data-toggle="toggle"` to automatically convert a plain checkbox into a bootstrap 4 toggle.
```html
<input id="chkToggle" type="checkbox" data-toggle="toggle">
```
## Initialize With Code
Toggles can also be initialized via JavaScript code.
EX: Initialize id `chkToggle` with a single line of JavaScript.
```html
<input id="chkToggle" type="checkbox" checked>
<script>
$(function(){
$('#chkToggle').bootstrapToggle();
});
</script>
```
# API
## Options
* Options can be passed via data attributes or JavaScript
* For data attributes, append the option name to `data-` (ex: `data-on="Enabled"`)
```html
<input type="checkbox" data-toggle="toggle" data-on="Enabled" data-off="Disabled">
<input type="checkbox" id="toggle-two">
<script>
$(function() {
$('#toggle-two').bootstrapToggle({
on: 'Enabled',
off: 'Disabled'
});
})
</script>
```
Name |Type |Default |Description |
----------|-----------|----------|----------------------------|
`on` |string/html|"On" |Text of the on toggle
`off` |string/html|"Off" |Text of the off toggle
`size` |string |"normal" |Size of the toggle. Possible values are: `large`, `normal`, `small`, `mini`.
`onstyle` |string |"primary" |Style of the on toggle. Possible values are: `primary`,`secondary`,`success`,`danger`,`warning`,`info`,`light`,`dark`
`offstyle`|string |"light" |Style of the off toggle. Possible values are: `primary`,`secondary`,`success`,`danger`,`warning`,`info`,`light`,`dark`
`style` |string | |Appends the value to the class attribute of the toggle. This can be used to apply custom styles. Refer to Custom Styles for reference.
`width` |integer |*null* |Sets the width of the toggle. if set to *null*, width will be auto-calculated.
`height` |integer |*null* |Sets the height of the toggle. if set to *null*, height will be auto-calculated.
## Methods
Methods can be used to control toggles directly.
```html
<input id="toggle-demo" type="checkbox" data-toggle="toggle">
```
Method |Example |Description
-----------|------------------------------------------------|------------------------------------------
initialize | `$('#toggle-demo').bootstrapToggle()` |Initializes the toggle plugin with options
destroy | `$('#toggle-demo').bootstrapToggle('destroy')` |Destroys the toggle
on | `$('#toggle-demo').bootstrapToggle('on')` |Sets the toggle to 'On' state
off | `$('#toggle-demo').bootstrapToggle('off')` |Sets the toggle to 'Off' state
toggle | `$('#toggle-demo').bootstrapToggle('toggle')` |Toggles the state of the toggle on/off
enable | `$('#toggle-demo').bootstrapToggle('enable')` |Enables the toggle
disable | `$('#toggle-demo').bootstrapToggle('disable')` |Disables the toggle
# Events
## Event Propagation
Note All events are propagated to and from input element to the toggle.
You should listen to events from the `<input type="checkbox">` directly rather than look for custom events.
```html
<input id="toggle-event" type="checkbox" data-toggle="toggle">
<div id="console-event"></div>
<script>
$(function() {
$('#toggle-event').change(function() {
$('#console-event').html('Toggle: ' + $(this).prop('checked'))
})
})
</script>
```
## API vs Input
This also means that using the API or Input to trigger events will work both ways.
```html
<input id="toggle-trigger" type="checkbox" data-toggle="toggle">
<button class="btn btn-success" onclick="toggleApiOn()" >On by API</button>
<button class="btn btn-danger" onclick="toggleApiOff()">Off by API</button>
<button class="btn btn-success" onclick="toggleInpOn()" >On by Input</button>
<button class="btn btn-danger" onclick="toggleInpOff()">Off by Input</button>
<script>
function toggleApiOn() {
$('#toggle-trigger').bootstrapToggle('on')
}
function toggleApiOff() {
$('#toggle-trigger').bootstrapToggle('off')
}
function toggleInpOn() {
$('#toggle-trigger').prop('checked', true).change()
}
function toggleInpOff() {
$('#toggle-trigger').prop('checked', false).change()
}
</script>
```

View File

@ -1,42 +0,0 @@
/*\
|*| ========================================================================
|*| Bootstrap Toggle: bootstrap4-toggle.css v3.5.0
|*| https://gitbrent.github.io/bootstrap-toggle/
|*| ========================================================================
|*| Copyright 2018-2019 Brent Ely
|*| Licensed under MIT
|*| ========================================================================
\*/
.btn-group-xs>.btn,.btn-xs{padding:.35rem .4rem .25rem;font-size:.875rem;line-height:.5;border-radius:.2rem}
.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-1.25rem;margin-right:.35rem}
.toggle{position:relative;overflow:hidden}
.toggle.btn.btn-light,.toggle.btn.btn-outline-light{border-color:rgba(0,0,0,.15)}
.toggle input[type=checkbox]{display:none}
.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none}
.toggle-group label,.toggle-group span{cursor:pointer}
.toggle.off .toggle-group{left:-100%}
.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0}
.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0;box-shadow:none}
.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px;background-color:#fff}
.toggle.btn-outline-primary .toggle-handle{background-color:var(--primary);border-color:var(--primary)}
.toggle.btn-outline-secondary .toggle-handle{background-color:var(--secondary);border-color:var(--secondary)}
.toggle.btn-outline-success .toggle-handle{background-color:var(--success);border-color:var(--success)}
.toggle.btn-outline-danger .toggle-handle{background-color:var(--danger);border-color:var(--danger)}
.toggle.btn-outline-warning .toggle-handle{background-color:var(--warning);border-color:var(--warning)}
.toggle.btn-outline-info .toggle-handle{background-color:var(--info);border-color:var(--info)}
.toggle.btn-outline-light .toggle-handle{background-color:var(--light);border-color:var(--light)}
.toggle.btn-outline-dark .toggle-handle{background-color:var(--dark);border-color:var(--dark)}
.toggle[class*=btn-outline]:hover .toggle-handle{background-color:var(--light);opacity:.5}
.toggle.btn{min-width:3.7rem;min-height:2.15rem}
.toggle-on.btn{padding-right:1.5rem}
.toggle-off.btn{padding-left:1.5rem}
.toggle.btn-lg{min-width:5rem;min-height:2.815rem}
.toggle-on.btn-lg{padding-right:2rem}
.toggle-off.btn-lg{padding-left:2rem}
.toggle-handle.btn-lg{width:2.5rem}
.toggle.btn-sm{min-width:3.125rem;min-height:1.938rem}
.toggle-on.btn-sm{padding-right:1rem}
.toggle-off.btn-sm{padding-left:1rem}
.toggle.btn-xs{min-width:2.19rem;min-height:1.375rem}
.toggle-on.btn-xs{padding-right:.8rem}
.toggle-off.btn-xs{padding-left:.8rem}

View File

@ -1,11 +0,0 @@
/*\
|*| ========================================================================
|*| Bootstrap Toggle: bootstrap4-toggle.js v3.5.0
|*| https://gitbrent.github.io/bootstrap-toggle/
|*| ========================================================================
|*| Copyright 2018-2019 Brent Ely
|*| Licensed under MIT
|*| ========================================================================
\*/
+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="3.5.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"light",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size||"lg"===this.options.size?"btn-lg":"small"===this.options.size||"sm"===this.options.size?"btn-sm":"mini"===this.options.size||"xs"===this.options.size?"btn-xs":"",c=a('<label class="btn">').html(this.options.on).addClass(this._onstyle+" "+b),d=a('<label class="btn">').html(this.options.off).addClass(this._offstyle+" "+b),e=a('<span class="toggle-handle btn btn-light">').addClass(b),f=a('<div class="toggle-group">').append(c,d,e),g=a('<div class="toggle btn" data-toggle="toggle" role="button">').addClass(this.$element.prop("checked")?this._onstyle:this._offstyle+" off").addClass(b).addClass(this.options.style);this.$element.wrap(g),a.extend(this,{$toggle:this.$element.parent(),$toggleOn:c,$toggleOff:d,$toggleGroup:f}),this.$toggle.append(f);var h=this.options.width||Math.max(c.outerWidth(),d.outerWidth())+e.outerWidth()/2,i=this.options.height||Math.max(c.outerHeight(),d.outerHeight());c.addClass("toggle-on"),d.addClass("toggle-off"),this.$toggle.css({width:h,height:i}),this.options.height&&(c.css("line-height",c.height()+"px"),d.css("line-height",d.height()+"px")),this.update(!0),this.trigger(!0)},c.prototype.toggle=function(){this.$element.prop("checked")?this.off():this.on()},c.prototype.on=function(a){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._offstyle+" off").addClass(this._onstyle),this.$element.prop("checked",!0),a||this.trigger()},c.prototype.off=function(a){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._onstyle).addClass(this._offstyle+" off"),this.$element.prop("checked",!1),a||this.trigger()},c.prototype.enable=function(){this.$toggle.removeAttr("disabled"),this.$element.prop("disabled",!1)},c.prototype.disable=function(){this.$toggle.attr("disabled","disabled"),this.$element.prop("disabled",!0)},c.prototype.update=function(a){this.$element.prop("disabled")?this.disable():this.enable(),this.$element.prop("checked")?this.on(a):this.off(a)},c.prototype.trigger=function(b){this.$element.off("change.bs.toggle"),b||this.$element.change(),this.$element.on("change.bs.toggle",a.proxy(function(){this.update()},this))},c.prototype.destroy=function(){this.$element.off("change.bs.toggle"),this.$toggleGroup.remove(),this.$element.removeData("bs.toggle"),this.$element.unwrap()};var d=a.fn.bootstrapToggle;a.fn.bootstrapToggle=b,a.fn.bootstrapToggle.Constructor=c,a.fn.toggle.noConflict=function(){return a.fn.bootstrapToggle=d,this},a(function(){a("input[type=checkbox][data-toggle^=toggle]").bootstrapToggle()}),a(document).on("click.bs.toggle","div[data-toggle^=toggle]",function(b){a(this).find("input[type=checkbox]").bootstrapToggle("toggle"),b.preventDefault()})}(jQuery);
//# sourceMappingURL=bootstrap4-toggle.min.js.map

View File

@ -1,45 +0,0 @@
{
"name": "bootstrap4-toggle",
"version": "3.5.0",
"author": {
"name": "Brent Ely",
"url": "https://github.com/gitbrent/"
},
"description": "Bootstrap 4 Toggle is a bootstrap 4 plugin that converts checkboxes into toggles.",
"homepage": "https://gitbrent.github.io/bootstrap4-toggle/",
"license": "MIT",
"main": "js/bootstrap4-toggle.min.js",
"files": [
"css/bootstrap4-toggle.min.css",
"js/bootstrap4-toggle.min.js"
],
"scripts": {
"build": "grunt"
},
"keywords": [
"bootstrap",
"bootstrap4",
"bootstrap-4",
"bootstrap 4",
"bootstrap 4 checkbox",
"bootstrap 4 switch",
"bootstrap 4 toggle",
"bootstrap-checkbox",
"bootstrap-switch",
"bootstrap-switch-button",
"bootstrap-toggle"
],
"repository": {
"type": "git",
"url": "https://github.com/gitbrent/bootstrap4-toggle.git"
},
"bugs": {
"url": "https://github.com/gitbrent/bootstrap4-toggle/issues"
},
"devDependencies": {
"grunt": "^1.0.4",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-cssmin": "^0.10.0",
"grunt-contrib-uglify": "^0.6.0"
}
}

83
dist/huebee/huebee.css vendored Normal file
View File

@ -0,0 +1,83 @@
/*! Huebee v2.1.0
https://huebee.buzz
---------------------------------------------- */
.huebee {
position: absolute;
z-index: 1;
transform: translateY(0px);
transition: opacity 0.15s, transform 0.15s;
}
.huebee.is-hidden {
opacity: 0;
transform: translateY(10px);
}
.huebee.is-static-open {
position: relative;
z-index: auto;
}
.huebee__container {
position: absolute;
left: 0;
top: 5px;
padding: 10px;
background: #EEE;
border-radius: 5px;
box-shadow: 0 5px 10px hsla(0, 0%, 0%, 0.3);
}
.huebee.is-static-open .huebee__container {
position: relative;
display: inline-block;
left: auto;
top: auto;
box-shadow: none;
}
.huebee__canvas {
display: block;
cursor: pointer;
}
.huebee__cursor {
width: 15px;
height: 15px;
position: absolute;
left: 0px;
top: 0px;
box-sizing: content-box;
border: 3px solid white;
border-radius: 5px;
pointer-events: none;
}
.huebee__cursor.is-hidden { opacity: 0; }
.huebee__close-button {
display: block;
position: absolute;
width: 24px;
height: 24px;
top: -9px;
right: -9px;
border-radius: 12px;
background: #222;
}
.huebee__close-button__x {
stroke: white;
stroke-width: 3;
stroke-linecap: round;
}
.huebee__close-button:hover {
background: white;
cursor: pointer;
}
.huebee__close-button:hover .huebee__close-button__x {
stroke: #222;
}

4
dist/huebee/huebee.min.css vendored Normal file
View File

@ -0,0 +1,4 @@
/*! Huebee v2.1.0
https://huebee.buzz
---------------------------------------------- */
.huebee{position:absolute;z-index:1;transform:translateY(0);transition:opacity .15s,transform .15s}.huebee.is-hidden{opacity:0;transform:translateY(10px)}.huebee.is-static-open{position:relative;z-index:auto}.huebee__container{position:absolute;left:0;top:5px;padding:10px;background:#eee;border-radius:5px;box-shadow:0 5px 10px hsla(0,0%,0%,.3)}.huebee.is-static-open .huebee__container{position:relative;display:inline-block;left:auto;top:auto;box-shadow:none}.huebee__canvas{display:block;cursor:pointer}.huebee__cursor{width:15px;height:15px;position:absolute;left:0;top:0;box-sizing:content-box;border:3px solid #fff;border-radius:5px;pointer-events:none}.huebee__cursor.is-hidden{opacity:0}.huebee__close-button{display:block;position:absolute;width:24px;height:24px;top:-9px;right:-9px;border-radius:12px;background:#222}.huebee__close-button__x{stroke:#fff;stroke-width:3;stroke-linecap:round}.huebee__close-button:hover{background:#fff;cursor:pointer}.huebee__close-button:hover .huebee__close-button__x{stroke:#222}

1125
dist/huebee/huebee.pkgd.js vendored Normal file

File diff suppressed because it is too large Load Diff

22
dist/huebee/huebee.pkgd.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/raspap/css/fonts/RaspAP.eot vendored Executable file

Binary file not shown.

12
dist/raspap/css/fonts/RaspAP.svg vendored Executable file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="RaspAP" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="wireguard" d="M1023.147 463.147c0 0 23.595 496.853-522.453 496.853-482.859 0-497.963-476.587-497.963-476.587s-70.997-547.413 509.141-547.413c556.501 0 511.275 527.147 511.275 527.147zM347.947 636.757c102.4 62.72 233.344 24.363 282.368-69.888 9.301-17.877 10.496-45.355 4.608-64.128-20.352-64.683-68.309-100.949-134.187-116.395 19.413 16.64 34.859 35.499 39.808 61.525 1.195 5.504 1.88 11.827 1.88 18.31 0 20.027-6.533 38.528-17.584 53.488l0.174-0.246c-16.797 22.874-43.588 37.556-73.809 37.556-11.257 0-22.038-2.037-31.995-5.763l0.63 0.207c-40.533-15.36-62.72-52.395-58.752-97.877 3.712-42.24 35.797-69.632 95.787-80.043-8.96-4.736-15.872-8.235-22.613-11.989-27.988-15.524-51.374-35.995-69.74-60.451l-0.404-0.562c-6.101-8.192-10.24-8.875-19.541-3.2-120.619 73.771-128.384 258.859 3.371 339.456zM257.707 180.992c-19.413-4.949-38.187-12.203-57.984-18.688 9.685 65.365 86.229 125.568 150.997 118.699-18.043-24.598-29.583-54.982-31.551-87.945l-0.022-0.46c-21.504-3.968-41.813-6.613-61.44-11.605zM669.995 819.2c19.115-0.725 38.315-0.427 57.472-0.853 5.287-0.363 10.162-1.075 14.91-2.128l-0.659 0.123c-4.574-6.938-9.348-12.986-14.582-18.599l0.076 0.082c-6.827-6.4-14.549-12.629-24.448-2.944-2.347 2.347-7.979 1.792-12.075 1.877-19.072 0.213-38.144 0.853-57.173 0.128-17.856-0.589-34.82-2.396-51.386-5.353l2.149 0.318c-3.072-0.555-7.595-10.667-6.229-14.421 3.328-8.832 8.149-18.56 15.317-24.192 26.411-20.907 54.485-39.595 81.067-60.288 25.771-20.139 49.792-42.24 64.427-72.533 19.029-39.595 19.627-81.067 11.392-122.752-13.739-69.547-48.939-127.147-105.941-169.045-22.955-16.853-51.413-26.453-77.696-38.528-23.168-10.667-46.933-19.84-70.144-30.379-41.813-19.029-65.28-64.427-58.411-111.573 6.357-43.307 44.373-79.445 87.851-86.912 52.181-8.96 106.069 25.003 118.827 78.080 14.336 59.605-18.048 112.896-78.72 129.024l-10.923 2.816c16.213 7.253 30.208 12.416 43.179 19.541q33.835 18.645 66.475 39.467c6.4 4.096 9.856 4.096 15.36-0.597 41.685-36.096 66.56-80.981 73.557-135.979 11.52-91.093-31.573-174.763-112.896-217.643-125.781-66.347-279.765 9.173-307.541 148.651-23.808 119.467 60.501 227.84 162.005 248.747 43.648 9.003 83.541 27.179 114.56 60.8 20.053 21.675 29.739 40.277 33.067 48.683 5.86 14.568 9.259 31.458 9.259 49.142 0 0.094 0 0.187 0 0.281v-0.014c-0.72 15.473-4.371 29.921-10.408 43.044l0.296-0.719c-10.581 24.149-51.2 62.549-61.227 70.656l-95.573 74.837c-3.371 2.773-7.168 2.56-15.36 2.005-9.813-0.683-34.773-2.048-45.525 0.768 8.704 6.613 32.427 16.213 42.667 23.893-30.976 20.907-66.304 13.397-98.773 19.627 7.509 13.995 44.629 35.456 65.749 37.888-1.455 13.545-3.483 25.484-6.166 37.173l0.406-2.101c-1.28 4.736-6.571 9.387-11.221 12.075-11.179 6.571-23.083 11.989-35.968 18.517 10.935 7.156 24.244 11.558 38.555 11.945l0.101 0.002c1.66 0.068 3.608 0.107 5.566 0.107 11.77 0 23.21-1.408 34.163-4.064l-0.987 0.202c23.040-5.248 41.387-1.792 59.691 13.824-14.421 5.803-28.843 11.093-42.795 17.365-16.163 7.396-29.343 14.415-42.082 22.091l1.89-1.056c36.267-5.035 71.296-18.645 108.373-13.653l0.939 5.035-86.101 20.053c51.328 4.693 99.115 5.461 144.384-16.555 12.757-6.229 26.027-11.349 38.272-18.432 5.973-3.413 9.941-10.24 14.848-15.573 3.84-4.181 6.997-9.813 11.776-12.373 18.091-9.6 37.973-9.984 58.283-9.515l0.427 6.827c20.437-6.4 43.392-29.952 43.392-47.147-33.109 0-66.133 0.128-99.2-0.171-3.541 0-7.040-2.603-10.539-4.011 3.328-1.963 6.613-5.461 10.027-5.589zM627.328 868.139c-1.461-0.899-2.42-2.488-2.42-4.302 0-1.516 0.67-2.876 1.731-3.799l0.006-0.005c1.344-2.305 3.804-3.83 6.62-3.83 1.429 0 2.767 0.393 3.91 1.076l-0.035-0.019c3.2 1.621 6.315 3.328 10.155 5.333-3.072 2.645-5.547 4.864-8.107 6.955-4.523 3.712-8.235 1.365-11.861-1.408z" />
<glyph unicode="&#xe901;" glyph-name="raspap" horiz-adv-x="1031" d="M540.058 281.983c0-104.182-84.446-188.637-188.625-188.637-104.176 0-188.62 84.455-188.62 188.637 0 104.171 84.444 188.625 188.62 188.625 104.179 0 188.625-84.455 188.625-188.625zM351.437 550.062c-147.818 0-268.074-120.259-268.074-268.080 0-147.826 120.257-268.091 268.074-268.091s268.077 120.265 268.077 268.091c0 147.821-120.259 268.080-268.077 268.080zM351.437-58.985c-188 0-340.95 152.958-340.95 340.967 0 188.003 152.95 340.956 340.95 340.956 188.003 0 340.953-152.953 340.953-340.956 0-188.009-152.95-340.967-340.953-340.967zM404.82 698.222c185.52 0 339.484-137.497 365.479-315.929l79.208-5.253c-24.125 224.046-214.339 399.077-444.686 399.077-10.909 0-21.723-0.412-32.433-1.186l5.16-77.823c9.017 0.661 18.093 1.113 27.272 1.113zM404.989 874.303c285.73 0 520.41-222.659 539.731-503.584l78.375-5.205c-16.843 326.355-287.644 586.685-618.106 586.685-14.884 0-29.644-0.561-44.264-1.6l5.157-77.719c12.919 0.928 25.958 1.424 39.106 1.424z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

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