1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter-feeds.git synced 2025-03-09 15:40:03 +00:00

merge raw and fix conflict

This commit is contained in:
libin 2018-08-08 09:57:09 +08:00
commit 9cc0532f39
260 changed files with 20787 additions and 12047 deletions

View file

@ -2,7 +2,6 @@ version: 2
jobs:
prepare:
machine: true
timezone: Europe/Paris
working_directory: ~/
@ -13,9 +12,8 @@ jobs:
sudo apt-get update
sudo apt-get install -yq build-essential git unzip ncurses-dev libz-dev libssl-dev python subversion gettext gawk wget curl rsync perl
build_x86:
build_x86_64:
machine: true
timezone: Europe/Paris
environment:
- OMR_VERSION: $CIRCLE_TAG
@ -27,8 +25,65 @@ jobs:
- run:
name: cache
command: |
echo "cache 5 $OMR_TARGET" > /tmp/cache-target
echo "cache 9 $OMR_TARGET $OMR_VERSION" > /tmp/cache-version
echo "cache 10 $OMR_TARGET" > /tmp/cache-target
echo "cache 14 $OMR_TARGET $OMR_VERSION" > /tmp/cache-version
- restore_cache:
keys:
- cache-{{ checksum "/tmp/cache-version" }}
- cache-{{ checksum "/tmp/cache-target" }}
- run:
name: Build toolchain
no_output_timeout: 30m
command: |
git clone https://github.com/ysurac/openmptcprouter || true
cd openmptcprouter
git pull || true
export OMR_PATH="$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/${CIRCLE_BRANCH:-$CIRCLE_TAG}"
export OMR_FEED_URL="$CIRCLE_REPOSITORY_URL"
export OMR_FEED_SRC="${CIRCLE_BRANCH:-$CIRCLE_TAG}"
sh build.sh prepare {tools,toolchain}/install -j2
echo -e "$OMR_PRIVKEY" > ~/openmptcprouter/$OMR_TARGET/source/key-build
echo -e "$OMR_PUBKEY" > ~/openmptcprouter/$OMR_TARGET/source/key-build.pub
- save_cache:
key: cache-{{ checksum "/tmp/cache-target" }}
paths:
- openmptcprouter
- run:
name: Build
no_output_timeout: 30m
command: make IGNORE_ERRORS=m -C ~/openmptcprouter/$OMR_TARGET/source package/{compile,install,index} target/install -j2
- run:
name: Deploy
command: |
ssh -p ${OMR_DEPLOY_PORT:-22} deploy@$OMR_DEPLOY_HOST mkdir -p deploy/release
rsync -av --delete-after ~/openmptcprouter/$OMR_TARGET/source/bin/ -e "ssh -q -p ${OMR_DEPLOY_PORT:-22}" deploy@$OMR_DEPLOY_HOST:deploy/release/$OMR_TARGET
rm -rf ~/openmptcprouter/$OMR_TARGET/source/bin
- save_cache:
key: cache-{{ checksum "/tmp/cache-version" }}
paths:
- openmptcprouter
build_x86:
machine: true
environment:
- OMR_VERSION: $CIRCLE_TAG
- OMR_TARGET: x86
working_directory: ~/
steps:
- run:
name: cache
command: |
echo "cache 10 $OMR_TARGET" > /tmp/cache-target
echo "cache 14 $OMR_TARGET $OMR_VERSION" > /tmp/cache-version
- restore_cache:
keys:
@ -73,7 +128,6 @@ jobs:
build_rpi3:
machine: true
timezone: Europe/Paris
environment:
- OMR_VERSION: $CIRCLE_TAG
@ -85,8 +139,65 @@ jobs:
- run:
name: cache
command: |
echo "cache 7 $OMR_TARGET" > /tmp/cache-target
echo "cache 11 $OMR_TARGET $OMR_VERSION" > /tmp/cache-version
echo "cache 14 $OMR_TARGET" > /tmp/cache-target
echo "cache 18 $OMR_TARGET $OMR_VERSION" > /tmp/cache-version
- restore_cache:
keys:
- cache-{{ checksum "/tmp/cache-version" }}
- cache-{{ checksum "/tmp/cache-target" }}
- run:
name: Build toolchain
no_output_timeout: 30m
command: |
git clone https://github.com/ysurac/openmptcprouter || true
cd openmptcprouter
git pull || true
export OMR_PATH="$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/${CIRCLE_BRANCH:-$CIRCLE_TAG}"
export OMR_FEED_URL="$CIRCLE_REPOSITORY_URL"
export OMR_FEED_SRC="${CIRCLE_BRANCH:-$CIRCLE_TAG}"
sh build.sh prepare {tools,toolchain}/install -j2
echo -e "$OMR_PRIVKEY" > ~/openmptcprouter/$OMR_TARGET/source/key-build
echo -e "$OMR_PUBKEY" > ~/openmptcprouter/$OMR_TARGET/source/key-build.pub
- save_cache:
key: cache-{{ checksum "/tmp/cache-target" }}
paths:
- openmptcprouter
- run:
name: Build
no_output_timeout: 30m
command: make IGNORE_ERRORS=m -C ~/openmptcprouter/$OMR_TARGET/source package/{compile,install,index} target/compile -j2 package/compile -j2 target/install -j2
- run:
name: Deploy
command: |
ssh -p ${OMR_DEPLOY_PORT:-22} deploy@$OMR_DEPLOY_HOST mkdir -p deploy/release
rsync -av --delete-after ~/openmptcprouter/$OMR_TARGET/source/bin/ -e "ssh -q -p ${OMR_DEPLOY_PORT:-22}" deploy@$OMR_DEPLOY_HOST:deploy/release/$OMR_TARGET
rm -rf ~/openmptcprouter/$OMR_TARGET/source/bin
- save_cache:
key: cache-{{ checksum "/tmp/cache-version" }}
paths:
- openmptcprouter
build_rpi2:
machine: true
environment:
- OMR_VERSION: $CIRCLE_TAG
- OMR_TARGET: rpi2
working_directory: ~/
steps:
- run:
name: cache
command: |
echo "cache 14 $OMR_TARGET" > /tmp/cache-target
echo "cache 18 $OMR_TARGET $OMR_VERSION" > /tmp/cache-version
- restore_cache:
keys:
@ -134,9 +245,15 @@ workflows:
main:
jobs:
- prepare
- build_x86_64:
requires:
- prepare
- build_x86:
requires:
- prepare
- build_rpi3:
requires:
- prepare
- build_rpi2:
requires:
- prepare

5
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,5 @@
Thanks for your contribution to OpenMPTCProuter!
You need to follow contributing rules.
Please remove this message before posting the pull request.

View file

@ -1,5 +1,7 @@
#
# Copyright (C) 2010-2015 OpenWrt.org
# Copyright (C) 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
# - Added gateway setting
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
@ -8,7 +10,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=6in4
PKG_VERSION:=26
PKG_VERSION:=270
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0

View file

@ -41,7 +41,7 @@ proto_6in4_setup() {
return
}
( proto_add_host_dependency "$cfg" "$peeraddr" "$tunlink" )
[ -n "$tunlink" ] && ( proto_add_host_dependency "$cfg" "$peeraddr" "$tunlink" )
[ -z "$ipaddr" ] && {
local wanif="$tunlink"

82
CLA-entity.md Normal file
View file

@ -0,0 +1,82 @@
OpenMPTCProuter Entity Contributor License Agreement
==================================================
Thank you for your interest in contributing to OpenMPTCProuter ("We" or "Us").
This contributor agreement ("Agreement") documents the rights granted by contributors to Us. To make this document effective, please submit a pull request with a file under the `/contributors` directory indicating your acceptance of this agreement.
This is a legally binding document, so please read it carefully before agreeing to it. The Agreement may cover more than one software project managed by Us.
## 1. Definitions
"You" means any Legal Entity on behalf of whom a Contribution has been received by Us. "Legal Entity" means an entity which is not a natural person. "Affiliates" means other Legal Entities that control, are controlled by, or under common control with that Legal Entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such Legal Entity, whether by contract or otherwise, (ii) ownership of fifty percent (50%) or more of the outstanding shares or securities which vote to elect the management or other persons who direct such Legal Entity or (iii) beneficial ownership of such entity.
"Contribution" means any work of authorship that is Submitted by You to Us in which You own or assert ownership of the Copyright. If You do not own the Copyright in the entire work of authorship, please follow the instructions in .
"Copyright" means all rights protecting works of authorship owned or controlled by You or Your Affiliates, including copyright, moral and neighboring rights, as appropriate, for the full term of their existence including any extensions by You.
"Material" means the work of authorship which is made available by Us to third parties. When this Agreement covers more than one software project, the Material means the work of authorship to which the Contribution was Submitted. After You Submit the Contribution, it may be included in the Material.
"Submit" means any form of electronic, verbal, or written communication sent to Us or our representatives, including but not limited to electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Us for the purpose of discussing and improving the Material, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
"Submission Date" means the date on which You Submit a Contribution to Us.
"Effective Date" means the date You execute this Agreement or the date You first Submit a Contribution to Us, whichever is earlier.
## 2. Grant of Rights
2.1 Copyright License
(a) You retain ownership of the Copyright in Your Contribution and have the same rights to use or license the Contribution which You would have had without entering into the Agreement.
(b) To the maximum extent permitted by the relevant law, You grant to Us a perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable license under the Copyright covering the Contribution, with the right to sublicense such rights through multiple tiers of sublicensees, to reproduce, modify, display, perform and distribute the Contribution as part of the Material; provided that this license is conditioned upon compliance with Section 2.3.
2.2 Patent License
For patent claims including, without limitation, method, process, and apparatus claims which You or Your Affiliates own, control or have the right to grant, now or in the future, You grant to Us a perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable patent license, with the right to sublicense these rights to multiple tiers of sublicensees, to make, have made, use, sell, offer for sale, import and otherwise transfer the Contribution and the Contribution in combination with the Material (and portions of such combination). This license is granted only to the extent that the exercise of the licensed rights infringes such patent claims; and provided that this license is conditioned upon compliance with Section 2.3.
2.3 Outbound License
Based on the grant of rights in Sections 2.1 and 2.2, if We include Your Contribution in a Material, We may license the Contribution under any license, including copyleft, permissive, commercial, or proprietary licenses. As a condition on the exercise of this right, We agree to also license the Contribution under the terms of the license or licenses which We are using for the Material on the Submission Date.
2.4 Moral Rights. If moral rights apply to the Contribution, to the maximum extent permitted by law, You waive and agree not to assert such moral rights against Us or our successors in interest, or any of our licensees, either direct or indirect.
2.5 Our Rights. You acknowledge that We are not obligated to use Your Contribution as part of the Material and may decide to include any Contribution We consider appropriate.
2.6 Reservation of Rights. Any rights not expressly licensed under this section are expressly reserved by You.
## 3. Agreement
You confirm that:
(a) You have the legal authority to enter into this Agreement.
(b) You or Your Affiliates own the Copyright and patent claims covering the Contribution which are required to grant the rights under Section 2.
(c) The grant of rights under Section 2 does not violate any grant of rights which You or Your Affiliates have made to third parties.
(d) You have followed the instructions in , if You do not own the Copyright in the entire work of authorship Submitted.
## 4. Disclaimer
EXCEPT FOR THE EXPRESS WARRANTIES IN SECTION 3, THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US. TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION TO THE MINIMUM PERIOD PERMITTED BY LAW.
## 5. Consequential Damage Waiver
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED.
## 6. Miscellaneous
6.1 This Agreement will be governed by and construed in accordance with the laws of excluding its conflicts of law provisions. Under certain circumstances, the governing law in this section might be superseded by the United Nations Convention on Contracts for the International Sale of Goods ("UN Convention") and the parties intend to avoid the application of the UN Convention to this Agreement and, thus, exclude the application of the UN Convention in its entirety to this Agreement.
6.2 This Agreement sets out the entire agreement between You and Us for Your Contributions to Us and overrides all other agreements or understandings.
6.3 If You or We assign the rights or obligations received through this Agreement to a third party, as a condition of the assignment, that third party must agree in writing to abide by all the rights and obligations in the Agreement.
6.4 The failure of either party to require performance by the other party of any provision of this Agreement in one situation shall not affect the right of a party to require such performance at any time in the future. A waiver of performance under a provision in one situation shall not be considered a waiver of the performance of the provision in the future or a waiver of the provision in its entirety.
6.5 If any provision of this Agreement is found void and unenforceable, such provision will be replaced to the extent possible with a provision that comes closest to the meaning of the original provision and which is enforceable. The terms and conditions set forth in this Agreement shall apply notwithstanding any failure of essential purpose of this Agreement or any limited remedy to the maximum extent possible under law.
This agreement is derived from the Project Harmony CLA generator:
http://www.harmonyagreements.org/
Harmony (HA-CLA-E-ANY) Version 1.0

83
CLA-individual.md Normal file
View file

@ -0,0 +1,83 @@
OpenMPTCProuter Individual Contributor License Agreement
======================================================
Thank you for your interest in contributing to OpenMPTCProuter ("We" or "Us").
This contributor agreement ("Agreement") documents the rights granted by contributors to Us. To make this document effective, please submit a pull request with a file under the `/contributors` directory indicating your acceptance of this agreement.
This is a legally binding document, so please read it carefully before agreeing to it. The Agreement may cover more than one software project managed by Us.
## 1. Definitions
"You" means the individual who Submits a Contribution to Us.
"Contribution" means any work of authorship that is Submitted by You to Us in which You own or assert ownership of the Copyright. If You do not own the Copyright in the entire work of authorship, please follow the instructions in .
"Copyright" means all rights protecting works of authorship owned or controlled by You, including copyright, moral and neighboring rights, as appropriate, for the full term of their existence including any extensions by You.
"Material" means the work of authorship which is made available by Us to third parties. When this Agreement covers more than one software project, the Material means the work of authorship to which the Contribution was Submitted. After You Submit the Contribution, it may be included in the Material.
"Submit" means any form of electronic, verbal, or written communication sent to Us or our representatives, including but not limited to electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Us for the purpose of discussing and improving the Material, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
"Submission Date" means the date on which You Submit a Contribution to Us.
"Effective Date" means the date You execute this Agreement or the date You first Submit a Contribution to Us, whichever is earlier.
## 2. Grant of Rights
2.1 Copyright License
(a) You retain ownership of the Copyright in Your Contribution and have the same rights to use or license the Contribution which You would have had without entering into the Agreement.
(b) To the maximum extent permitted by the relevant law, You grant to Us a perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable license under the Copyright covering the Contribution, with the right to sublicense such rights through multiple tiers of sublicensees, to reproduce, modify, display, perform and distribute the Contribution as part of the Material; provided that this license is conditioned upon compliance with Section 2.3.
2.2 Patent License
For patent claims including, without limitation, method, process, and apparatus claims which You own, control or have the right to grant, now or in the future, You grant to Us a perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable patent license, with the right to sublicense these rights to multiple tiers of sublicensees, to make, have made, use, sell, offer for sale, import and otherwise transfer the Contribution and the Contribution in combination with the Material (and portions of such combination). This license is granted only to the extent that the exercise of the licensed rights infringes such patent claims; and provided that this license is conditioned upon compliance with Section 2.3.
2.3 Outbound License
Based on the grant of rights in Sections 2.1 and 2.2, if We include Your Contribution in a Material, We may license the Contribution under any license, including copyleft, permissive, commercial, or proprietary licenses. As a condition on the exercise of this right, We agree to also license the Contribution under the terms of the license or licenses which We are using for the Material on the Submission Date.
2.4 Moral Rights. If moral rights apply to the Contribution, to the maximum extent permitted by law, You waive and agree not to assert such moral rights against Us or our successors in interest, or any of our licensees, either direct or indirect.
2.5 Our Rights. You acknowledge that We are not obligated to use Your Contribution as part of the Material and may decide to include any Contribution We consider appropriate.
2.6 Reservation of Rights. Any rights not expressly licensed under this section are expressly reserved by You.
3. Agreement
You confirm that:
(a) You have the legal authority to enter into this Agreement.
(b) You own the Copyright and patent claims covering the Contribution which are required to grant the rights under Section 2.
(c) The grant of rights under Section 2 does not violate any grant of rights which You have made to third parties, including Your employer. If You are an employee, You have had Your employer approve this Agreement or sign the Entity version of this document. If You are less than eighteen years old, please have Your parents or guardian sign the Agreement.
(d) You have followed the instructions in , if You do not own the Copyright in the entire work of authorship Submitted.
## 4. Disclaimer
EXCEPT FOR THE EXPRESS WARRANTIES IN SECTION 3, THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US. TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION TO THE MINIMUM PERIOD PERMITTED BY LAW.
## 5. Consequential Damage Waiver
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED.
## 6. Miscellaneous
6.1 This Agreement will be governed by and construed in accordance with the laws of excluding its conflicts of law provisions. Under certain circumstances, the governing law in this section might be superseded by the United Nations Convention on Contracts for the International Sale of Goods ("UN Convention") and the parties intend to avoid the application of the UN Convention to this Agreement and, thus, exclude the application of the UN Convention in its entirety to this Agreement.
6.2 This Agreement sets out the entire agreement between You and Us for Your Contributions to Us and overrides all other agreements or understandings.
6.3 If You or We assign the rights or obligations received through this Agreement to a third party, as a condition of the assignment, that third party must agree in writing to abide by all the rights and obligations in the Agreement.
6.4 The failure of either party to require performance by the other party of any provision of this Agreement in one situation shall not affect the right of a party to require such performance at any time in the future. A waiver of performance under a provision in one situation shall not be considered a waiver of the performance of the provision in the future or a waiver of the provision in its entirety.
6.5 If any provision of this Agreement is found void and unenforceable, such provision will be replaced to the extent possible with a provision that comes closest to the meaning of the original provision and which is enforceable. The terms and conditions set forth in this Agreement shall apply notwithstanding any failure of essential purpose of this Agreement or any limited remedy to the maximum extent possible under law.
This agreement is derived from the Project Harmony CLA generator:
http://www.harmonyagreements.org/
Harmony (HA-CLA-I-ANY) Version 1.0

74
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at contact@openmptcprouter.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

5
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,5 @@
Contribution
When submitting a pull request for the first time, you will need to agree to the contributor license agreement (for individuals or entities). To do this, in the pull request please create a file with a name like /contributors/{github_username}.md, and in the content of that file indicate your agreement. An example of what that file should contain can be seen in example agreement file.
(This method of CLA "signing" is borrowed from Medium's open source project.)

View file

@ -60,9 +60,9 @@ Added support to gateway set by user for 6in4. Used for IPv6 over the glorytun I
## luci-omr-bypass
*Source:* [https://github.com/Ysurac/openmptcprouter-feeds/tree/master/luci-app-omr-bypass](https://github.com/Ysurac/openmptcprouter-feeds/tree/master/luci-app-omr-bypass)
*Description:* Luci interface to bypass domains with shadowsocks
*Description:* Luci interface to bypass domains, IPs and networks with shadowsocks
Domains added are bypassed when shadowsocks is used. This can be used when VPS IP is blacklisted from some sites.
Domains, IPs, networks and protocol (using DPI) added are bypassed when shadowsocks is used. This can be used when VPS IP is blacklisted from some sites.
## omr-tracker
@ -73,6 +73,18 @@ Domains added are bypassed when shadowsocks is used. This can be used when VPS I
This is used for OpenMPTCProuter failover.
## omr-6in4
*Source:* [https://github.com/Ysurac/openmptcprouter-feeds/tree/master/omr-6in4](https://github.com/Ysurac/openmptcprouter-feeds/tree/master/omr-6in4)
*Description:* Set tunnel configuration by tracking tunnel configuration.
## omr-update
*Source:* [https://github.com/Ysurac/openmptcprouter-feeds/tree/master/omr-update](https://github.com/Ysurac/openmptcprouter-feeds/tree/master/omr-update)
*Description:* Update old config with new settings.
## luci-omr-tracker
*Source:* [https://github.com/Ysurac/openmptcprouter-feeds/tree/master/luci-app-omr-tracker](https://github.com/Ysurac/openmptcprouter-feeds/tree/master/luci-app-omr-tracker)
@ -97,3 +109,34 @@ Interface to omr-tracker.
*Source:* [https://github.com/Ysurac/openmptcprouter-feeds/tree/master/mptcp](https://github.com/Ysurac/openmptcprouter-feeds/tree/master/mptcp)
*Description:* This package set all MPTCP settings
## ndisc6
*Source:* [http://www.remlab.net/files/ndisc6](http://www.remlab.net/files/ndisc6)
*Description:* An ICMPv6 neighbour discovery tool
This is used to check if there is no other IPv6 route announced on the network
## mlvpn
*Source:* [https://github.com/markfoodyburton/MLVPN/tree/new-reorder](https://github.com/markfoodyburton/MLVPN/tree/new-reorder)
*Description:* Multi-link VPN
This is an other way to aggregate same latency connections
## ndpi-filter
*Source:* [https://github.com/vel21ripn/nDPI](https://github.com/vel21ripn/nDPI)
*Description:* Open Source Deep Packet Inspection Software Toolkit
This is used to bypass a protocol
## tracebox
*Source:* [https://github.com/tracebox/tracebox](https://github.com/tracebox/tracebox)
*Description:* A middlebox detection tool

9
contributors/example.md Normal file
View file

@ -0,0 +1,9 @@
2018-05-19
I hereby agree to the terms of the "OpenMPTCProuter Individual Contributor License Agreement", with MD5 checksum bc827a07eb93611d793ddb7c75083c00.
I furthermore declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
John Doe https://github.com/johndoe

View file

@ -1,6 +1,6 @@
#
# Copyright (C) 2015 OVH
# Copyright (C) 2017 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
# Copyright (C) 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
@ -8,13 +8,16 @@
include $(TOPDIR)/rules.mk
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/angt/glorytun.git
PKG_SOURCE_VERSION:=c15343f8f6fb275fe9bed56ca251bb801ea5d67b
PKG_NAME:=glorytun-udp
PKG_VERSION:=0.0.98-mud
PKG_RELEASE:=1
PKG_SOURCE:=glorytun-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/angt/glorytun/releases/download/v$(PKG_VERSION)
PKG_BUILD_DIR:=$(BUILD_DIR)/glorytun-$(PKG_VERSION)
PKG_HASH:=34a02c83efbfa1742d639643eeeeeb35f43611c92e2fd93f2eedba065c2f9417
PKG_VERSION:=0.0.99-mud
PKG_RELEASE:=17
PKG_FIXUP:=autoreconf
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
@ -42,3 +45,4 @@ define Package/$(PKG_NAME)/install
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View file

@ -34,7 +34,7 @@ validate_section() {
}
start_instance() {
local enable key host port dev listener proto bind bindport mtu mtuauto chacha20
local enable key host port listener proto bind bindport mtu mtuauto chacha20 dev
validate_section "${1}" || {
_err "validation failed"
@ -45,24 +45,30 @@ start_instance() {
[ "${proto}" = "udp" ] || return 1
[ -n "${key}" ] || return 1
[ "${key}" != "secretkey" ] || return 1
[ -n "${port}" ] || return 1
[ -n "${dev}" ] || return 1
echo "${key}" > /tmp/${PROG_NAME}-${1}.key
key=""
if [ "$(uci -q get network.omrvpn)" != "" ]; then
uci -q set network.omrvpn.ifname=$dev
uci -q commit
fi
_log "starting ${PROG_NAME} ${1} instance $*"
procd_open_instance
procd_set_param command ${PROG} \
${bind:+bind "$bind"} \
${bindport:+ "$port"} \
${bindport:+ "$bindport"} \
${host:+to "$host"} \
${port:+ "$port"} \
${dev:+dev "$dev"} \
keyfile /tmp/${PROG_NAME}-${1}.key \
${mtu:+mtu "$mtu"}
persist
[ "${chacha20}" = "1" ] && procd_append_param command chacha
@ -73,13 +79,37 @@ start_instance() {
procd_set_param stderr 1
procd_close_instance
#config_load network
#config_foreach add_glorytun_path interface
}
add_glorytun_path() {
case "$1" in
loopback|lan|if0|*tun*|ifb*) return ;;
esac
local multipath
network_get_ipaddr ipaddr "$1"
config_get multipath "$1" multipath "off"
case "$multipath" in
master|on) glorytun-udp path "$ipaddr" dev $dev up ;;
backup) glorytun-udp path "$ipaddr" dev $dev backup ;;
*) glorytun-udp path "$ipaddr" dev $dev down ;;
esac
}
start_service() {
local dev
config_load glorytun
config_foreach start_instance glorytun
}
reload_service() {
stop
start
}
service_triggers() {
procd_add_reload_trigger glorytun network
}

View file

@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=glorytun
PKG_VERSION:=0.0.35
PKG_RELEASE:=5
PKG_RELEASE:=6
PKG_SOURCE:=glorytun-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/angt/glorytun/releases/download/v$(PKG_VERSION)
PKG_HASH:=49e4d8ea4ff2990300b37947b0bd0da3c8e0985bc6eddf29f4146306188fff64

View file

@ -3,7 +3,8 @@ config glorytun 'vpn'
option host '127.0.0.1'
option port '65001'
option dev 'tun0'
option key 'secretkey'
option key ''
option mptcp '1'
option proto 'tcp'
option chacha20 '1'
option mtuauto '1'

View file

@ -42,12 +42,17 @@ start_instance() {
[ "${enable}" = "1" ] || return 1
[ "${proto}" = "tcp" ] || return 1
[ -n "${key}" ] || return 1
[ "${key}" != "secretkey" ] || return 1
[ -n "${port}" ] || return 1
[ -n "${dev}" ] || return 1
echo "${key}" > /tmp/${PROG_NAME}-${1}.key
key=""
if [ "$(uci -q get network.omrvpn)" != "" ]; then
uci -q set network.omrvpn.ifname=${dev}
uci -q commit network
fi
_log "starting ${PROG_NAME} ${1} instance $*"
procd_open_instance
@ -64,7 +69,7 @@ start_instance() {
procd_append_param command \
retry count -1 const 5000000 \
timeout 9000 \
timeout 40000 \
keepalive count 5 idle 60 interval 2
procd_set_param respawn 0 30 0

View file

@ -7,10 +7,10 @@ require("luci.model.uci")
local basicParams = {
--
--
-- Widget, Name, Default(s), Description
--
{ Flag,"enable",0, translate("Enable") },
{ Value,"port",65001, translate("TCP port # for both local and remote") },
{ Value,"dev","tun0", translate("Interface name") },

View file

@ -17,9 +17,9 @@ set_default() {
local iface
config_get iface "$config" dev
[ "$iface" = "$DEVICE" ] && {
config_get localip "$config" localip
config_get localip "$config" localip
config_get remoteip "$config" remoteip
[ "$remoteip" != "" ] && [ "$localip" != "" ] && ip addr add $localip peer $remoteip dev $DEVICE
[ "$remoteip" != "" ] && [ "$localip" != "" ] && ifconfig $DEVICE $localip pointopoint $remoteip up
}
}

View file

@ -0,0 +1,56 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@glorytun[-1]
add ucitrack glorytun
set ucitrack.@glorytun[-1].init=glorytun
set ucitrack.@glorytun[-1].affects=glorytun-udp
delete ucitrack.@glorytun-udp[-1]
add ucitrack glorytun-udp
set ucitrack.@glorytun-udp[-1].init=glorytun-udp
commit ucitrack
EOF
if [ "$(uci -q get network.glorytun)" = "" ] && [ "$(uci -q get network.omrvpn)" = "" ]; then
uci -q batch <<-EOF >/dev/null
delete network.glorytun
set network.glorytun=interface
set network.glorytun.ifname=tun0
set network.glorytun.proto=dhcp
set network.glorytun.ip4table=vpn
set network.glorytun.multipath=off
set network.glorytun.leasetime=12h
set network.glorytun.mtu=1280
commit network
EOF
# set network.glorytun.proto=static
# set network.glorytun.ipaddr=10.0.0.2
# set network.glorytun.netmask=255.255.255.0
# set network.glorytun.gateway=10.0.0.1
fi
if [ "$(uci -q show firewall | grep glorytun)" = "" ] && [ "$(uci -q get network.omrvpn)" = "" ]; then
uci -q batch <<-EOF >/dev/null
set firewall.zone_vpn=zone
set firewall.zone_vpn.name=vpn
set firewall.zone_vpn.network=glorytun
set firewall.zone_vpn.masq=1
set firewall.zone_vpn.input=REJECT
set firewall.zone_vpn.forward=ACCEPT
set firewall.zone_vpn.output=ACCEPT
commit firewall
EOF
fi
if [ "$(uci -q show firewall | grep Allow-All-LAN-to-VPN)" = "" ]; then
uci -q batch <<-EOF >/dev/null
add firewall rule
set firewall.@rule[-1].enabled='1'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].name='Allow-All-LAN-to-VPN'
set firewall.@rule[-1].dest='vpn'
set firewall.@rule[-1].src='lan'
commit firewall
EOF
fi
rm -f /tmp/luci-indexcache
exit 0

View file

@ -1,50 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@glorytun[-1]
add ucitrack glorytun
set ucitrack.@glorytun[-1].init=glorytun
set ucitrack.@glorytun[-1].affects=glorytun-udp
delete ucitrack.@glorytun-udp[-1]
add ucitrack glorytun-udp
set ucitrack.@glorytun-udp[-1].init=glorytun-udp
commit ucitrack
EOF
uci -q batch <<-EOF >/dev/null
delete network.glorytun=interface
set network.glorytun=interface
set network.glorytun.ifname=tun0
set network.glorytun.proto=dhcp
set network.glorytun.ip4table=vpn
set network.glorytun.multipath=off
set network.glorytun.leasetime=12h
commit network
EOF
uci -q batch <<-EOF >/dev/null
add firewall zone
set firewall.@zone[-1].name=vpn
set firewall.@zone[-1].network=glorytun
set firewall.@zone[-1].masq=1
set firewall.@zone[-1].input=REJECT
set firewall.@zone[-1].forward=REJECT
set firewall.@zone[-1].output=ACCEPT
set firewall.allow_dhcp_request_vpn=rule
set firewall.allow_dhcp_request_vpn.name=Allow-DHCP-Request-VPN
set firewall.allow_dhcp_request_vpn.src=glorytun
set firewall.allow_dhcp_request_vpn.proto=udp
set firewall.allow_dhcp_request_vpn.dest_port=67
set firewall.allow_dhcp_request_vpn.target=ACCEPT
set firewall.allow_dhcp_request_vpn.family=ipv4
set firewall.redirect_vpn_to_lan=redirect
set firewall.redirect_vpn_to_lan.name=Redirect-VPN-to-LAN
set firewall.redirect_vpn_to_lan.src=vpn
set firewall.redirect_vpn_to_lan.dest=lan
set firewall.redirect_vpn_to_lan.proto=all
set firewall.redirect_vpn_to_lan.enabled=1
set firewall.redirect_vpn_to_lan.src_dip=192.168.100.1
commit firewall
EOF
rm -f /tmp/luci-indexcache
exit 0

15
luci-app-iperf/Makefile Normal file
View file

@ -0,0 +1,15 @@
#
# Copyright (C) 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
#
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Support for iPerf3
LUCI_DEPENDS:=+iperf3-ssl
PKG_LICENSE:=GPLv3
include ../luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,50 @@
local uci = luci.model.uci.cursor()
local ut = require "luci.util"
module("luci.controller.iperf", package.seeall)
function index()
--entry({"admin", "openmptcprouter", "iperf"}, cbi("iperf"), _("iperf"))
entry({"admin", "services", "iperf"}, alias("admin", "services", "iperf", "test"), _("iPerf"),8)
entry({"admin", "services", "iperf", "test"}, template("iperf/test"), nil,1)
entry({"admin", "services", "iperf", "run_test"}, post("run_test")).leaf = true
end
function run_test(server,proto,mode,updown,omit,parallel,transmit,bitrate)
luci.http.prepare_content("text/plain")
local iperf
local addr = uci:get("iperf",server,"host")
local ports = uci:get("iperf",server,"ports")
local ipv = "4"
if proto == "ipv6" then
local ipv = "6"
end
local t={}
for pt in ports:gmatch("([^,%s]+)") do
table.insert(t,pt)
end
local port = t[ math.random( #t ) ]
if mode == "tcp" then
if updown == "upload" then
iperf = io.popen("iperf3 -c %s -P %s -%s -p %s -O %s -t %s -J" % {ut.shellquote(addr),parallel,ipv,port,omit,transmit})
else
iperf = io.popen("iperf3 -c %s -P %s -%s -p %s -O %s -R -t %s -J" % {ut.shellquote(addr),parallel,ipv,port,omit,transmit})
end
else
if updown == "upload" then
iperf = io.popen("iperf3 -c %s -P %s -%s -p %s -O %s -t %s -u -b %s -J" % {ut.shellquote(addr),parallel,ipv,port,omit,transmit,bitrate})
else
iperf = io.popen("iperf3 -c %s -P %s -%s -p %s -O %s -R -t %s -u -b %s -J" % {ut.shellquote(addr),parallel,ipv,port,omit,transmit,bitrate})
end
end
if iperf then
while true do
local ln = iperf:read("*l")
if not ln then break end
luci.http.write(ln)
luci.http.write("\n")
end
end
return
end

View file

@ -0,0 +1,174 @@
<%+header%>
<%
local uci = require("luci.model.uci").cursor()
%>
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-18.193.28471-ee087a1"></script>
<script type="text/javascript">//<![CDATA[
var stxhr = new XHR();
function update_speed(field, proto, mode,omit,parallel,transmit,bitrate)
{
update_upload(field,proto,mode,omit,parallel,transmit,bitrate);
}
function update_upload(field, proto, mode,omit,parallel,transmit,bitrate)
{
var tool = field.name;
var addr = field.value;
var upload = document.getElementById('iperf-upload');
if (upload)
{
upload.innerHTML =
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' + '<%:Upload%> - ' +
'<%:Waiting for command to complete...%>'
;
stxhr.post('<%=url('admin/services/iperf')%>/run_test' + '/' + addr + '/' + proto + '/' + mode + '/upload' + '/' + omit + '/' + parallel + '/' + transmit + '/' + bitrate, { token: '<%=token%>' },
function(x)
{
if (x.responseText)
{
var response = JSON.parse(x.responseText);
if (response.error)
{
upload.innerHTML = String.format('<%:Upload%> - <pre>%s</pre>', response.error );
} else {
var sent_speed = (response.end.sum_sent.bits_per_second/1000000);
var received_speed = (response.end.sum_received.bits_per_second/1000000);
var server = response.start.connecting_to.host;
upload.innerHTML = String.format('<pre><%:Upload%> - Server: %s - Sender: %sMb/s - Receiver: %sMb/s</pre>', server, sent_speed.toFixed(2), received_speed.toFixed(2) );
}
}
else
{
upload.innerHTML = '<%:Upload%> - <span class="error"><%:Bad address specified!%></span>';
}
update_download(field,proto,mode,omit,parallel,transmit,bitrate);
}
);
}
}
function update_download(field, proto, mode,omit,parallel,transmit,bitrate)
{
var tool = field.name;
var addr = field.value;
var download = document.getElementById('iperf-download');
if (download)
{
download.innerHTML =
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' + '<%:Download%> - ' +
'<%:Waiting for command to complete...%>'
;
stxhr.post('<%=url('admin/services/iperf')%>/run_test' + '/' + addr + '/' + proto + '/' + mode + '/download' + '/' + omit + '/' + parallel + '/' + transmit + '/' + bitrate, { token: '<%=token%>' },
function(x)
{
if (x.responseText)
{
var response = JSON.parse(x.responseText);
if (response.error)
{
download.innerHTML = String.format('<%:Download%> - <pre>%s</pre>', response.error );
} else {
var sent_speed = (response.end.sum_sent.bits_per_second/1000000);
var received_speed = (response.end.sum_received.bits_per_second/1000000);
var server = response.start.connecting_to.host;
download.innerHTML = String.format('<pre><%:Download%> - Server: %s - Sender: %sMb/s - Receiver: %sMb/s</pre>', server, sent_speed.toFixed(2), received_speed.toFixed(2) );
}
}
else
{
download.innerHTML = '<%:Download%> - <span class="error"><%:Bad address specified!%></span>';
}
}
);
}
}
//]]></script>
<% if stderr and #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
<form class="inline" method="post" action="<%=url('admin/services/iperf/run_test')%>">
<div class="cbi-map">
<h2 name="content"><%:iPerf speed tests%></h2>
<div class="cbi-map-descr"><%:This iPerf interface is in bêta. No support for this.%></div>
<fieldset class="cbi-section" id="networks">
<legend><%:Settings%></legend>
<div class="cbi-section-descr"></div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Mode of operation%></label>
<div class="cbi-value-field">
<select class="cbi-input-select" name="mode">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Internet protocol%></label>
<div class="cbi-value-field">
<select class="cbi-input-select" name="proto">
<option value="ipv4">IPv4</option>
<option value="ipv6">IPv6</option>
</select>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Target bitrate (Mbits/s)%></label>
<div class="cbi-value-field">
<input name="bitrate" data-type="uinteger" type="text" class="cbi-input-text" value="0"/>
<br />
<div class="cbi-value-description">
<%:0 for unlimited. Need to be limited for UDP test%>
</div>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Number of parallel client streams to run%></label>
<div class="cbi-value-field">
<input name="parallel" data-type="uinteger" type="text" class="cbi-input-text" value="1"/>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Omit the first n seconds%></label>
<div class="cbi-value-field">
<input name="omit" data-type="uinteger" type="text" class="cbi-input-text" value="3"/>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Time to transmit for (s)%></label>
<div class="cbi-value-field">
<input name="transmit" data-type="uinteger" type="text" class="cbi-input-text" value="5"/>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Server%></label>
<div class="cbi-value-field">
<select class="cbi-input-select" name="addr">
<%
uci:foreach("iperf","server", function(s)
local server = s[".name"]
%>
<option value="<%=server%>"><%=string.gsub(server,"_","-")%></option>
<%
end)
%>
</select>
</div>
</div>
<input type="button" value="<%:Test%>" class="cbi-button cbi-button-apply" onclick="update_speed(this.form.addr,this.form.proto.value,this.form.mode.value,this.form.omit.value,this.form.parallel.value,this.form.transmit.value,this.form.bitrate.value)" />
</fieldset>
</div>
</form>
<div class="cbi-section">
<span id="iperf-upload"></span>
<span id="iperf-download"></span>
</div>
<%+footer%>

View file

@ -0,0 +1,96 @@
config server 'bouygues'
option host 'bouygues.iperf.fr'
option ipv4 '1'
option ipv6 '1'
option speed '10000'
option ports '5200,5201,5202,5203,5204,5205,5206,5207,5208,5209'
option tcp '1'
option udp '0'
option location 'Europe'
config server 'online_ipv4'
option host 'ping.online.net'
option ipv4 '1'
option ipv6 '0'
option speed '10000'
option ports '5200,5201,5202,5203,5204,5205,5206,5207,5208,5209'
option tcp '1'
option udp '1'
option location 'Europe'
config server 'online_ipv6'
option host 'ping.online.net'
option ipv4 '0'
option ipv6 '1'
option speed '10000'
option ports '5200,5201,5202,5203,5204,5205,5206,5207,5208,5209'
option tcp '1'
option udp '1'
option location 'Europe'
config server 'serverius'
option host 'speedtest.serverius.net'
option ipv4 '1'
option ipv6 '1'
option speed '10000'
option ports '5002'
option tcp '1'
option udp '1'
option location 'Europe'
config server 'eenet'
option host 'iperf.eenet.ee'
option ipv4 '1'
option ipv6 '0'
option ports '5201'
option tcp '1'
option udp '1'
option location 'Europe'
config server 'volia'
option host 'iperf.volia.net'
option ipv4 '1'
option ipv6 '0'
option ports '5201'
option tcp '1'
option udp '1'
option location 'Europe'
config server 'it_north'
option host 'iperf.it-north.net'
option ipv4 '1'
option ipv6 '0'
option speed '1000'
option ports '5200,5201,5202,5203,5204,5205,5206,5207,5208,5209'
option tcp '1'
option udp '1'
option location 'Asia'
config server 'biznet'
option host 'iperf.biznetnetworkds.com'
option ipv4 '1'
option ipv6 '1'
option speed '1000'
option ports '5201,5202,5203'
option tcp '1'
option udp '0'
option location 'Asia'
config server 'scottlinux'
option host 'iperf.scottlinux.com'
option ipv4 '1'
option ipv6 '1'
option speed '1000'
option ports '5201'
option tcp '1'
option udp '1'
option location 'America'
config server 'he'
option host 'iperf.he.net'
option ipv4 '1'
option ipv6 '1'
option ports '5201'
option tcp '1'
option udp '1'
option location 'America'

15
luci-app-mlvpn/Makefile Normal file
View file

@ -0,0 +1,15 @@
#
# Copyright (C) 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
#
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Support for MLVPN
LUCI_DEPENDS:=+mlvpn
PKG_LICENSE:=GPLv3
include ../luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,6 @@
module("luci.controller.mlvpn", package.seeall)
function index()
--entry({"admin", "openmptcprouter", "mlvpn"}, cbi("mlvpn"), _("MLVPN"))
entry({"admin", "services", "mlvpn"}, cbi("mlvpn"), _("MLVPN"))
end

View file

@ -0,0 +1,77 @@
local net = require "luci.model.network".init()
local sys = require "luci.sys"
local ifaces = sys.net:devices()
local m, s, o
m = Map("mlvpn", translate("MLVPN"))
s = m:section(TypedSection, "mlvpn", translate("Settings"))
s.anonymous = true
s.addremove = false
o = s:option(Flag, "enable", translate("Enable"))
o.rmempty = false
o = s:option(Value, "timeout", translate("Timeout (s)"))
o.placeholder = "30"
o.default = "30"
o.datatype = "uinteger"
o.rmempty = false
o = s:option(Value, "reorder_buffer_size", translate("Reorder buffer size"))
o.placeholder = "64"
o.default = "64"
o.datatype = "uinteger"
o.rmempty = false
o = s:option(Value, "loss_tolerance", translate("Loss tolerance"))
o.placeholder = "50"
o.default = "50"
o.datatype = "uinteger"
o.rmempty = false
o = s:option(Value, "host", translate("Remote host"))
o.placeholder = "128.128.128.128"
o.default = "128.128.128.128"
o.datatype = "host"
o.rmempty = false
o = s:option(Value, "firstport", translate("First remote port"),translate("Interface will increase port used beginning with this"))
o.default = "65201"
o.datatype = "port"
o.rmempty = false
o = s:option(Value, "password", translate("Password"))
o.password = true
o.rmempty = false
o = s:option(Value, "interface_name", translate("Interface name"))
o.placeholder = "mlvpn0"
o.default = "mlvpn0"
o.rmempty = false
--o = s:option(Value, "mode", translate("Mode"))
--o:value("client")
--o:value("server")
--o.default = "client"
--o.rmempty = false
--s = m:section(TypedSection, "interface", translate("Interfaces"))
--s.template_addremove = "mlvpn/cbi-select-add"
--s.addremove = true
--s.add_select_options = { }
--s.add_select_options[''] = ''
--for _, iface in ipairs(ifaces) do
-- if not (iface == "lo" or iface:match("^ifb.*")) then
-- s.add_select_options[iface] = iface
-- end
--end
--o = s:option(Value, "port", translate("Remote/Bind port"))
--o.placeholder = "65201"
--o.default = "65201"
--o.datatype = "port"
return m

View file

@ -0,0 +1,10 @@
<div class="cbi-section-create">
<% if self.invalid_cts then -%><div class="cbi-section-error"><% end %>
<select class="cbi-section-create-name" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.select">
<%- for k, v in luci.util.kspairs(self.add_select_options) do %>
<option value="<%=k%>"><%=luci.util.pcdata(v)%></option>
<% end -%>
</select>
<input class="cbi-button cbi-button-add" type="submit" value="<%:Add%>" title="<%:Add%>" />
<% if self.invalid_cts then %><br /><%:Invalid%></div><% end %>
</div>

View file

@ -0,0 +1,61 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.6\n"
"Last-Translator: Ycarus <ycarus@zugaina.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Language: fr\n"
msgid "Add"
msgstr "Ajouter"
msgid "Enable"
msgstr "Activé"
msgid "First remote port"
msgstr "Premier port distant"
msgid "Interface name"
msgstr "Nom de l'interface"
msgid "Interface will increase port used beginning with this"
msgstr ""
msgid "Interfaces"
msgstr "Interfaces"
msgid "Invalid"
msgstr "Invalide"
msgid "Loss tolerance"
msgstr ""
msgid "MLVPN"
msgstr ""
msgid "Mode"
msgstr ""
msgid "Password"
msgstr "Mot de passe"
msgid "Remote host"
msgstr "Nom de l'hôte distant ou adresse IP"
msgid "Remote/Bind port"
msgstr "Port de connexion distant"
msgid "Reorder buffer size"
msgstr ""
msgid "Settings"
msgstr "Paramètres"
msgid "Timeout (s)"
msgstr "Délais d'attente (s)"

View file

@ -0,0 +1,50 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Add"
msgstr ""
msgid "Enable"
msgstr ""
msgid "First remote port"
msgstr ""
msgid "Interface name"
msgstr ""
msgid "Interface will increase port used beginning with this"
msgstr ""
msgid "Interfaces"
msgstr ""
msgid "Invalid"
msgstr ""
msgid "Loss tolerance"
msgstr ""
msgid "MLVPN"
msgstr ""
msgid "Mode"
msgstr ""
msgid "Password"
msgstr ""
msgid "Remote host"
msgstr ""
msgid "Remote/Bind port"
msgstr ""
msgid "Reorder buffer size"
msgstr ""
msgid "Settings"
msgstr ""
msgid "Timeout (s)"
msgstr ""

View file

@ -1,127 +1,127 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
-- Copyright 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.controller.mptcp", package.seeall)
function index()
entry({"admin", "network", "mptcp"}, alias("admin", "network", "mptcp", "settings"), _("MPTCP"))
entry({"admin", "network", "mptcp", "settings"}, cbi("mptcp"), _("Settings"),2).leaf = true
entry({"admin", "network", "mptcp", "bandwidth"}, template("mptcp/multipath"), _("Bandwidth"), 3).leaf = true
entry({"admin", "network", "mptcp", "multipath_bandwidth"}, call("multipath_bandwidth")).leaf = true
entry({"admin", "network", "mptcp", "interface_bandwidth"}, call("interface_bandwidth")).leaf = true
end
function interface_bandwidth(iface)
luci.http.prepare_content("application/json")
local bwc = io.popen("luci-bwc -i %q 2>/dev/null" % iface)
if bwc then
luci.http.write("[")
while true do
local ln = bwc:read("*l")
if not ln then break end
luci.http.write(ln)
end
luci.http.write("]")
bwc:close()
end
end
function string.split(input, delimiter)
input = tostring(input)
delimiter = tostring(delimiter)
if (delimiter=='') then return false end
local pos,arr = 0, {}
-- for each divider found
for st,sp in function() return string.find(input, delimiter, pos, true) end do
table.insert(arr, string.sub(input, pos, st - 1))
pos = sp + 1
end
table.insert(arr, string.sub(input, pos))
return arr
end
function multipath_bandwidth()
local result = { };
local uci = luci.model.uci.cursor()
local multipath="";
local proto="";
local res={ };
local str="";
local tmpstr="";
for _, dev in luci.util.vspairs(luci.sys.net.devices()) do
if dev ~= "lo" then
if dev == "eth0.2" then
multipath = uci:get("network", "wan", "multipath")
elseif dev == "4g-wwan0" then
multipath = uci:get("network", "wwan0", "multipath")
else
multipath = uci:get("network", dev, "multipath")
end
if multipath == "on" or multipath == "master" or multipath == "backup" or multipath == "handover" then
result[dev] = "[" .. string.gsub((luci.sys.exec("luci-bwc -i %q 2>/dev/null" % dev)), '[\r\n]', '') .. "]"
end
end
end
---先初始化求和数组
res["total"]={ };
for i=1,60 do
res["total"][i]={}
for j=1,5 do
res["total"][i][j]=0
end
end
--遍历所有接口表求和
for key,value in pairs(result) do
res[key]={}
value=(string.gsub(value, "^%[%[", ""))
value=(string.gsub(value, "%]%]", ""))
local temp1 = string.split(value, "],")
res[key][1]=temp1[1]
for i=2,60 do
res[key][i]={}
res[key][i]=(string.gsub(temp1[i], "%[", " "))
end
for i=1,60 do
res[key][i] = string.split(res[key][i], ",")
for j=1,5 do
if "string"== type(res[key][i][j]) then
res[key][i][j]= tonumber(res[key][i][j])
end
if "string"==type(res["total"][i][j]) then
res["total"][i][j]= tonumber(res["total"][i][j])
end
if j ==1 then
res["total"][i][j] = res[key][i][j]
else
res["total"][i][j] = res["total"][i][j] + res[key][i][j]
end
end
end
end
---数值类型转成字符串
for i=1,60 do
for j=1,5 do
if "number"== type(res["total"][i][j]) then
res["total"][i][j]= tostring(res["total"][i][j])
end
end
end
---数组转成字符串
for i=1,60 do
if i == 60 then
tmpstr = "["..table.concat(res["total"][i], ",")
else
tmpstr = "["..table.concat(res["total"][i], ",").."],"
end
str = str..tmpstr
end
str = "["..str.."]]"
result["total"]=str
luci.http.prepare_content("application/json")
luci.http.write_json(result)
end
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
-- Copyright 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.controller.mptcp", package.seeall)
function index()
entry({"admin", "network", "mptcp"}, alias("admin", "network", "mptcp", "settings"), _("MPTCP"))
entry({"admin", "network", "mptcp", "settings"}, cbi("mptcp"), _("Settings"),2).leaf = true
entry({"admin", "network", "mptcp", "bandwidth"}, template("mptcp/multipath"), _("Bandwidth"), 3).leaf = true
entry({"admin", "network", "mptcp", "multipath_bandwidth"}, call("multipath_bandwidth")).leaf = true
entry({"admin", "network", "mptcp", "interface_bandwidth"}, call("interface_bandwidth")).leaf = true
end
function interface_bandwidth(iface)
luci.http.prepare_content("application/json")
local bwc = io.popen("luci-bwc -i %q 2>/dev/null" % iface)
if bwc then
luci.http.write("[")
while true do
local ln = bwc:read("*l")
if not ln then break end
luci.http.write(ln)
end
luci.http.write("]")
bwc:close()
end
end
function string.split(input, delimiter)
input = tostring(input)
delimiter = tostring(delimiter)
if (delimiter=='') then return false end
local pos,arr = 0, {}
-- for each divider found
for st,sp in function() return string.find(input, delimiter, pos, true) end do
table.insert(arr, string.sub(input, pos, st - 1))
pos = sp + 1
end
table.insert(arr, string.sub(input, pos))
return arr
end
function multipath_bandwidth()
local result = { };
local uci = luci.model.uci.cursor()
local multipath="";
local proto="";
local res={ };
local str="";
local tmpstr="";
for _, dev in luci.util.vspairs(luci.sys.net.devices()) do
if dev ~= "lo" then
if dev == "eth0.2" then
multipath = uci:get("network", "wan", "multipath")
elseif dev == "4g-wwan0" then
multipath = uci:get("network", "wwan0", "multipath")
else
multipath = uci:get("network", dev, "multipath")
end
if multipath == "on" or multipath == "master" or multipath == "backup" or multipath == "handover" then
result[dev] = "[" .. string.gsub((luci.sys.exec("luci-bwc -i %q 2>/dev/null" % dev)), '[\r\n]', '') .. "]"
end
end
end)
---先初始化求和数组
res["total"]={ };
for i=1,60 do
res["total"][i]={}
for j=1,5 do
res["total"][i][j]=0
end
end
--遍历所有接口表求和
for key,value in pairs(result) do
res[key]={}
value=(string.gsub(value, "^%[%[", ""))
value=(string.gsub(value, "%]%]", ""))
local temp1 = string.split(value, "],")
res[key][1]=temp1[1]
for i=2,60 do
res[key][i]={}
res[key][i]=(string.gsub(temp1[i], "%[", " "))
end
for i=1,60 do
res[key][i] = string.split(res[key][i], ",")
for j=1,5 do
if "string"== type(res[key][i][j]) then
res[key][i][j]= tonumber(res[key][i][j])
end
if "string"==type(res["total"][i][j]) then
res["total"][i][j]= tonumber(res["total"][i][j])
end
if j ==1 then
res["total"][i][j] = res[key][i][j]
else
res["total"][i][j] = res["total"][i][j] + res[key][i][j]
end
end
end
end
---数值类型转成字符串
for i=1,60 do
for j=1,5 do
if "number"== type(res["total"][i][j]) then
res["total"][i][j]= tostring(res["total"][i][j])
end
end
end
---数组转成字符串
for i=1,60 do
if i == 60 then
tmpstr = "["..table.concat(res["total"][i], ",")
else
tmpstr = "["..table.concat(res["total"][i], ",").."],"
end
str = str..tmpstr
end
str = "["..str.."]]"
result["total"]=str
luci.http.prepare_content("application/json")
luci.http.write_json(result)
end

View file

@ -3,20 +3,20 @@ local sys = require "luci.sys"
local ifaces = sys.net:devices()
local m, s, o
m = Map("network", translate("MPTCP"), translate("Networks MPTCP settings"))
m = Map("network", translate("MPTCP"), translate("Networks MPTCP settings. Visit <a href='http://multipath-tcp.org/pmwiki.php/Users/ConfigureMPTCP'>http://multipath-tcp.org/pmwiki.php/Users/ConfigureMPTCP</a> for help."))
s = m:section(TypedSection, "globals")
local mtcpg = s:option(ListValue, "multipath", translate("Multipath TCP"))
mtcpg:value("enable", translate("enable"))
mtcpg:value("disable", translate("disable"))
local mtcpck = s:option(ListValue, "mptcp_checksum", translate("Multipath TCP checksum"))
mtcpck:value("enable", translate("enable"))
mtcpck:value("disable", translate("disable"))
mtcpck:value(1, translate("enable"))
mtcpck:value(0, translate("disable"))
local mtcppm = s:option(ListValue, "mptcp_path_manager", translate("Multipath TCP path-manager"))
mtcppm:value("default", translate("default"))
mtcppm:value("fullmesh", translate("fullmesh"))
mtcppm:value("ndiffports", translate("ndiffports"))
mtcppm:value("blinder", translate("blinder"))
mtcppm:value("binder", translate("binder"))
local mtcpsch = s:option(ListValue, "mptcp_scheduler", translate("Multipath TCP scheduler"))
mtcpsch:value("default", translate("default"))
mtcpsch:value("roundrobin", translate("round-robin"))
@ -29,6 +29,16 @@ local availablecong = sys.exec("sysctl net.ipv4.tcp_available_congestion_control
for cong in string.gmatch(availablecong, "[^%s]+") do
congestion:value(cong, translate(cong))
end
local mtcpfm_subflows = s:option(Value, "mptcp_fullmesh_num_subflows", translate("Fullmesh subflows for each pair of IP addresses"))
mtcpfm_subflows.datatype = "uinteger"
mtcpfm_subflows.rmempty = false
local mtcpfm_createonerr = s:option(ListValue, "mptcp_fullmesh_create_on_err", translate("Re-create fullmesh subflows after a timeout"))
mtcpfm_createonerr:value(1, translate("enable"))
mtcpfm_createonerr:value(0, translate("disable"))
local mtcpnd_subflows = s:option(Value, "mptcp_ndiffports_num_subflows", translate("ndiffports subflows number"))
mtcpnd_subflows.datatype = "uinteger"
mtcpnd_subflows.rmempty = false
s = m:section(TypedSection, "interface", translate("Interfaces Settings"))
mptcp = s:option(ListValue, "multipath", translate("Multipath TCP"), translate("One interface must be set as master"))

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Support for nginx load balancing
LUCI_DEPENDS:=+nginx
KCONFIG:=CONFIG_NGINX_STREAM
PKG_LICENSE:=MIT

View file

@ -6,29 +6,20 @@ else
m = Map("nginx-ha", translate("Nginx High Availability"), "%s - %s" %{translate("Nginx High Availability"), translate("NOT RUNNING")})
end
s = m:section(TypedSection, "general", translate("General Setting"))
s.anonymous = true
s = m:section(TypedSection, "nginxha", translate("Settings"))
s.addremove = true
o = s:option(Flag, "enable", translate("Enable"))
o.rmempty = false
o = s:option(Value, "startup_delay", translate("Startup Delay"))
o:value(0, translate("Not enabled"))
for _, v in ipairs({5, 10, 15, 25, 40}) do
o:value(v, translate("%u seconds") %{v})
end
o.datatype = "uinteger"
o.default = 0
o.rmempty = false
o = s:option(Value, "listen", translate("Listen Address:Port"))
o.placeholder = "0.0.0.0:6666"
o.default = "0.0.0.0:6666"
o.rmempty = false
o = s:option(Value, "timeout", translate("Timeout Connect (ms)"))
o.placeholder = "666"
o.default = "666"
o.placeholder = "1000"
o.default = "1000"
o.datatype = "range(33, 10000)"
o.rmempty = false

View file

@ -1,8 +1,14 @@
config general 'general'
config nginxha 'ShadowSocks'
option enable '0'
option retries '1'
option timeout '1000'
option listen '0.0.0.0:65101'
option startup_delay '5'
list upstreams '1.2.3.4:65101 weight=1 max_fails=3 fail_timeout=30s'
config nginxha 'VPN'
option enable '0'
option retries '1'
option timeout '1000'
option listen '0.0.0.0:65001'
list upstreams '1.2.3.4:65001 weight=1 max_fails=3 fail_timeout=30s'

View file

@ -20,11 +20,10 @@ _err() {
}
validate_section() {
uci_validate_section nginx-ha general "${1}" \
uci_validate_section nginx-ha nginxha "${1}" \
'enable:bool:0' \
'retries:uinteger:3' \
'timeout:uinteger:4000' \
'startup_delay:uinteger:5' \
'listen:string' \
'upstreams:list(string)'
}
@ -33,12 +32,6 @@ genline_srv(){
echo " server $1;"
}
boot() {
local delay=$(uci -q get $NAME.general.startup_delay)
(sleep ${delay:-0} && start >/dev/null 2>&1) &
return 0
}
start_instance() {
local enable retries timeout startup_delay listen upstreams
@ -49,6 +42,28 @@ start_instance() {
[ "$enable" = 1 ] || return 1
stream="${stream}
upstream ${1} {
zone dynamic 64k;
$(config_list_foreach "${1}" "upstreams" genline_srv)
}
server {
listen ${listen:-0.0.0.0:6666} udp;
listen ${listen:-0.0.0.0:6666} so_keepalive=off;
proxy_pass ${1};
}
"
}
start_service() {
local stream=""
config_load nginx-ha
config_foreach start_instance nginxha
[ -z "$stream" ] && return
mkdir -p /var/log/nginx
mkdir -p /var/etc
cat <<-EOF > /var/etc/$PROG_NAME.cfg
user nobody nogroup;
@ -62,19 +77,7 @@ start_instance() {
}
stream {
upstream allservers {
zone dynamic 64k;
$(config_list_foreach "${1}" "upstreams" genline_srv)
}
server {
listen ${listen:-0.0.0.0:6666} udp;
proxy_pass allservers;
}
server {
listen ${listen:-0.0.0.0:6666};
proxy_pass allservers;
}
${stream}
}
EOF
@ -83,11 +86,8 @@ start_instance() {
procd_set_param file /var/etc/$PROG_NAME.cfg
procd_set_param respawn
procd_close_instance
}
start_service() {
config_load nginx-ha
config_foreach start_instance general
}
reload_service() {

View file

@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Interface to bypass domains
LUCI_DEPENDS:=+dnsmasq-full +shadowsocks-libev-ss-rules
LUCI_DEPENDS:=+dnsmasq-full +shadowsocks-libev-ss-rules +iptables-mod-ndpi +iptables-mod-extra +kmod-ipt-ndpi +iptables
PKG_LICENSE:=GPLv3

View file

@ -17,25 +17,35 @@ function bypass_add()
local ip_ipset = {}
for _, k in pairs(hosts) do
if k ~= "" then
if dt.ipaddr(k) then
if dt.ipmask(k) then
table.insert(ip_ipset, k)
else
domains_ipset = domains_ipset .. '/' .. k
end
end
end
ucic:delete("omr-bypass","ips","ip")
if table.getn(ip_ipset) > 0 then
for _, i in pairs(ip_ipset) do
ucic:set_list("omr-bypass","ips","ip",ip_ipset)
ucic:set_list("omr-bypass","ips","ip",ip_ipset)
local dpi = luci.http.formvalue("cbid.omr-bypass.dpi")
if dpi ~= "" then
if (type(dpi) ~= "table") then
dpi = {dpi}
end
ucic:set_list("omr-bypass","dpi","proto",dpi)
else
ucic:delete("omr-bypass","dpi","proto")
end
local interface = luci.http.formvalue("cbid.omr-bypass.interface") or ""
ucic:set("omr-bypass","defaults","ifname",interface)
ucic:save("omr-bypass")
ucic:commit("omr-bypass")
ucic:set_list("dhcp",ucic:get_first("dhcp","dnsmasq"),"ipset",domains_ipset .. "/ss_rules_dst_bypass")
ucic:save("dhcp")
ucic:commit("dhcp")
--luci.sys.exec("/etc/init.d/dnsmasq restart")
luci.sys.exec("/etc/init.d/omr-bypass restart")
luci.http.redirect(luci.dispatcher.build_url("admin/services/omr-bypass"))
return
end

View file

@ -1,13 +1,18 @@
<%+header%>
<script type="text/javascript" src="<%=resource%>/cbi.js" data-strings="{&#34;path&#34;:{&#34;resource&#34;:&#34;\/luci-static\/resources&#34;,&#34;browser&#34;:&#34;\/cgi-bin\/luci\/admin\/filebrowser&#34;},&#34;label&#34;:{&#34;choose&#34;:&#34;-- Choisir --&#34;,&#34;custom&#34;:&#34;-- autre --&#34;}}"></script>
<script type="text/javascript" src="<%=resource%>/cbi.js" data-strings="{&#34;path&#34;:{&#34;resource&#34;:&#34;\/luci-static\/resources&#34;,&#34;browser&#34;:&#34;\/cgi-bin\/luci\/admin\/filebrowser&#34;}}"></script>
<%
local uci = require("luci.model.uci").cursor()
local hosts = uci:get_list("dhcp", uci:get_first("dhcp","dnsmasq"), "ipset")
local ips = uci:get_list("omr-bypass", "ips", "ip")
local dpi = uci:get_list("omr-bypass", "dpi", "proto")
local tmpfile = os.tmpname()
local dpi_available_proto = luci.util.execi("cat /proc/net/xt_ndpi/proto | awk '{print $3}' | sort -u | head -n -1")
local sys = require "luci.sys"
local ifaces = sys.net:devices()
local bypassif = uci:get("omr-bypass","defaults","ifname") or ""
%>
<% if stderr and #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
<form class="inline" method="post" action="<%=url('admin/services/omr-bypass/add')%>">
<div class="cbi-map">
@ -25,7 +30,8 @@
for hst in string.gmatch(host,"([^/]*)/") do
if hst ~= "" then
%>
<input class="cbi-input-text" value="<%=hst%>" data-update="change" type="text" id="cbid.omr-bypass.hosts.<%=j%>" name="cbid.omr-bypass.hosts" placeholder="google.com" /><br />
<input class="cbi-input-text" value="<%=hst%>" data-update="change" type="text" id="cbid.omr-bypass.hosts.<%=j%>" name="cbid.omr-bypass.hosts" placeholder="google.com" />
<br />
<%
end
end
@ -34,14 +40,87 @@
j = j+1
%>
<input class="cbi-input-text" value="<%=ip%>" data-update="change" type="text" id="cbid.omr-bypass.hosts.<%=j%>" name="cbid.omr-bypass.hosts" placeholder="google.com" /><br />
</div>
<div class="cbi-value-description">
<%:You need to use OpenMPTCProuter as DNS server when you want to bypass a domain%>
</div>
<%
end
if j == 1 then
%>
<input class="cbi-input-text" value="" data-update="change" type="text" id="cbid.omr-bypass.hosts.1" name="cbid.omr-bypass.hosts" placeholder="google.com" /><br />
<input class="cbi-input-text" value="" data-update="change" type="text" id="cbid.omr-bypass.hosts.1" name="cbid.omr-bypass.hosts" placeholder="google.com" />
<br />
</div>
<div class="cbi-value-description">
<%:You need to use OpenMPTCProuter as DNS server when you want to bypass a domain%>
</div>
<%
end
%>
</div>
</div>
</fieldset>
<fieldset class="cbi-section" id="dpi">
<div class="cbi-section-descr"><%:Set protocols you want to bypass.%></div>
<div class="cbi-value cbi-value-last" id="cbi-omr-tracker-dpi" data-depends="[]" data-index="<%=table.getn(dpi)%>">
<label class="cbi-value-title" for="cbid.omr-tracker.dpi"><%:Protocol%></label>
<div class="cbi-value-field">
<%
local allprt="&#34;&#34;"
local protos = {}
for l in io.lines("/proc/net/xt_ndpi/proto") do
local a,b,c,d = l:match('(%w+) (%w+)')
if b ~= "2" and not string.match(b,"custom") then
table.insert(protos,b)
end
end
table.sort(protos)
for _,b in ipairs(protos) do
allprt=allprt .. ",&#34;" .. b .. "&#34;"
end
%>
<div data-prefix="cbid.omr-bypass.dpi" data-browser-path="" data-dynlist="[[<%=allprt%>],[<%=allprt%>],null,false]">
<%
local k = 1
for _ , proto in pairs(dpi) do
k = k+1
%>
<input class="cbi-input-text" id="cbid.omr-bypass.dpi.<%=k%>" name="cbid.omr-bypass.dpi" data-update="change" value="<%=proto%>" /><br />
<%
end
if k == 1 then
%>
<input class="cbi-input-text" id="cbid.omr-bypass.dpi.<%=k%>" name="cbid.omr-bypass.dpi" data-update="change" /><br />
<%
end
%>
</div>
</div>
</div>
</fieldset>
<fieldset>
<div class="cbi-section-descr"><%:Set interface you want to use for bypassed traffic.%></div>
<div class="cbi-value">
<label class="cbi-value-title">Interface</label>
<div class="cbi-value-field">
<select class="cbi-input-select" name="cbid.omr-bypass.interface" size="1">
<option value="" <% if iface == bypassif then %>selected="selected"<% end %>><%=iface%></option>
<%
for _, iface in ipairs(ifaces) do
if not (iface == "lo" or iface:match("^ifb.*")) then
%>
<option value="<%=iface%>" <% if iface == bypassif then %>selected="selected"<% end %>><%=iface%></option>
<%
end
end
%>
</select>
<br />
<div class="cbi-value-description">
<%:If empty, multipath master interface is used if up else any other up interface.%>
</div>
</div>
</div>

View file

@ -18,11 +18,37 @@ msgstr "Contourne"
msgid "Domain, IP or network"
msgstr "Domaine, IP ou réseau"
msgid ""
"If empty, multipath master interface is used if up else any other up "
"interface."
msgstr ""
"Si vide, l'interface définie en tant que maître multipath est utilisée si "
"elle fonctionne, sinon une autre interface sera utilisée."
msgid "OMR-Bypass"
msgstr ""
msgid "Protocol"
msgstr "Protocole"
msgid "Set domains name, ips or networks you want to bypass."
msgstr "Configurer les domaines, adresses IPs ou réseaux que vous voulez contourner."
msgstr ""
"Configurer les domaines, adresses IPs ou réseaux que vous voulez contourner."
msgid "Set interface you want to use for bypassed traffic."
msgstr ""
"Configurer l'interface que vous souhaitez utiliser pour le trafic à "
"contourner."
msgid "Set protocols you want to bypass."
msgstr "Configurer les protocoles que vous voulez contourner."
msgid ""
"You need to use OpenMPTCProuter as DNS server when you want to bypass a "
"domain"
msgstr ""
"Vous devez utiliser OpenMPTCProuter en tant que serveur DNS quand vous "
"souhaitez contourner un domaine"
#~ msgid "Domains"
#~ msgstr "Domaines"

View file

@ -7,8 +7,27 @@ msgstr ""
msgid "Domain, IP or network"
msgstr ""
msgid ""
"If empty, multipath master interface is used if up else any other up "
"interface."
msgstr ""
msgid "OMR-Bypass"
msgstr ""
msgid "Protocol"
msgstr ""
msgid "Set domains name, ips or networks you want to bypass."
msgstr ""
msgid "Set interface you want to use for bypassed traffic."
msgstr ""
msgid "Set protocols you want to bypass."
msgstr ""
msgid ""
"You need to use OpenMPTCProuter as DNS server when you want to bypass a "
"domain"
msgstr ""

View file

@ -1 +1,5 @@
config bypass 'ips'
config defaults 'defaults'
config bypass 'ips'
config bypass 'dpi'

View file

@ -1,22 +1,62 @@
#!/bin/sh /etc/rc.common
# shellcheck disable=SC2039
# vim: set noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 :
# Copyright (C) 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
# shellcheck disable=SC2034
{
START=90
STOP=10
USE_PROCD=1
}
START=99
STOP=10
USE_PROCD=1
. /usr/lib/unbound/iptools.sh
_bypass_ip() {
local ip="$1"
ipset add ss_rules_dst_bypass $ip
valid_ip4=$( valid_subnet4 $ip)
valid_ip6=$( valid_subnet6 $ip)
if [ "$valid_ip4" = "ok" ]; then
ipset add ss_rules_dst_bypass $ip
elif [ "$valid_ip6" = "ok" ]; then
ipset add ss_rules6_dst_bypass $ip
fi
}
_bypass_proto() {
local proto="$1"
[ -z "$proto" ] && return
ndpi_rules="-A omr-bypass-dpi -m ndpi --$proto -j MARK --set-mark 0x539
$ndpi_rules"
}
start_service() {
ipset -q flush ss_rules_dst_bypass > /dev/null 2>&1
ipset -q --exist restore <<-EOF
create ss_rules_dst_bypass hash:net hashsize 64
EOF
config_load omr-bypass
config_list_foreach ips "ip" _bypass_ip
ip rule add prio 1 fwmark 0x539 lookup 991337 > /dev/null 2>&1
if [ "$(iptables -w 40 -t mangle -L | grep 'match-set ss_rules_dst_bypass dst MARK set')" = "" ]; then
iptables-restore --wait=60 --noflush <<-EOF
*mangle
-A PREROUTING -m set --match-set ss_rules_dst_bypass dst -j MARK --set-mark 0x539
COMMIT
EOF
fi
iptables-save --counters | grep -v omr-bypass-dpi | iptables-restore --counters
local ndpi_rules=""
config_list_foreach dpi "proto" _bypass_proto
ndpi_rules=$(echo $ndpi_rules | awk 'NF')
if [ "$ndpi_rules" != "" ]; then
iptables-restore --wait=60 --noflush <<-EOF
*mangle
:omr-bypass-dpi -
-A PREROUTING -m addrtype ! --dst-type LOCAL -j omr-bypass-dpi
$ndpi_rules
COMMIT
EOF
fi
}
service_triggers() {
@ -24,6 +64,5 @@ service_triggers() {
}
reload_service() {
stop
start
}

View file

@ -7,5 +7,22 @@ uci -q batch <<-EOF >/dev/null
commit ucitrack
EOF
if [ "$(uci -q get omr-bypass.dpi)" = "" ]; then
uci -q batch <<-EOF >/dev/null
set omr-bypass.dpi=bypass
EOF
fi
if [ "$(uci -q get omr-bypass.default)" = "" ]; then
uci -q batch <<-EOF >/dev/null
set omr-bypass.defaults=defaults
EOF
fi
if [ "$(uci -q get ucitrack.@shadowsocks-libev[-1].affects)" = "" ]; then
uci -q batch <<-EOF >/dev/null
set ucitrack.@shadowsocks-libev[-1].affects=omr-bypass
EOF
fi
rm -f /tmp/luci-indexcache
exit 0

View file

@ -5,7 +5,7 @@ local m, s, o
m = Map("omr-tracker", translate("OMR-Tracker"))
s = m:section(TypedSection, "shadowsocks", translate("ShadowSocks tracker Settings"), translate("Detect if ShadowSocks is down and stop traffic redirection over it"))
s = m:section(TypedSection, "shadowsocks", translate("ShadowSocks tracker Settings"), translate("Detect if ShadowSocks is down and stop traffic redirection over it."))
s.anonymous = true
s.addremove = false
@ -36,7 +36,7 @@ o.default = "2"
o.datatype = "range(1, 100)"
o.rmempty = false
o = s:option(DynamicList, "hosts", translate("Hosts"))
o = s:option(DynamicList, "hosts", translate("Hosts"), translate("IPs or domains must be available over http"))
o.placeholder = "bing.com"
o.default = { "bing.com", "google.com" }
o.rmempty = false

View file

@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Support for OpenMPTCProuter
LUCI_DEPENDS:=+luci-lib-json
LUCI_DEPENDS:=+luci-lib-json +rdisc6 +curl +whois +bind-dig
PKG_LICENSE:=GPLv3
include ../luci/luci.mk

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -1,6 +1,7 @@
local tools = require "luci.tools.status"
local sys = require "luci.sys"
local json = require("luci.json")
local fs = require("nixio.fs")
local ucic = luci.model.uci.cursor()
module("luci.controller.openmptcprouter", package.seeall)
@ -22,30 +23,39 @@ function wizard_add()
local gostatus = true
if add_interface ~= "" then
local i = 1
local multipath_master = false
ucic:foreach("network", "interface", function(s)
local sectionname = s[".name"]
if sectionname:match("^wan(%d+)$") then
i = i + 1
end
if ucic:get("network",sectionname,"multipath") == "master" then
multipath_master = true
end
end)
local defif = ucic:get("network","wan1","ifname") or "eth0"
local defif = ucic:get("network","wan1_dev","ifname") or "eth0"
ucic:set("network","wan" .. i,"interface")
ucic:set("network","wan" .. i,"ifname",defif)
ucic:set("network","wan" .. i,"proto","static")
ucic:set("network","wan" .. i,"type","macvlan")
ucic:set("network","wan" .. i,"ip4table","wan")
ucic:set("network","wan" .. i,"multipath","on")
if multipath_master then
ucic:set("network","wan" .. i,"multipath","on")
else
ucic:set("network","wan" .. i,"multipath","master")
end
ucic:set("network","wan" .. i,"defaultroute","0")
ucic:save("network")
ucic:commit("network")
-- Dirty way to add new interface to firewall...
luci.sys.call("uci -q add_list firewall.@zone[1].network=wan" .. i)
luci.sys.call("uci -q commit firewall")
luci.sys.call("/etc/init.d/macvlan restart >/dev/null 2>/dev/null")
gostatus = false
end
local delete_intf = luci.http.formvaluetable("delete")
local delete_intf = luci.http.formvaluetable("delete") or ""
if delete_intf ~= "" then
for intf, _ in pairs(delete_intf) do
ucic:delete("network",intf)
@ -55,34 +65,6 @@ function wizard_add()
end
gostatus = false
end
local server_ip = luci.http.formvalue("server_ip")
-- Set ShadowSocks settings
local shadowsocks_key = luci.http.formvalue("shadowsocks_key")
if shadowsocks_key ~= "" then
ucic:set("shadowsocks-libev","sss0","server",server_ip)
ucic:set("shadowsocks-libev","sss0","key",shadowsocks_key)
ucic:set("shadowsocks-libev","sss0","method","aes-256-cfb")
ucic:set("shadowsocks-libev","sss0","server_port","65101")
ucic:set("shadowsocks-libev","sss0","disabled",0)
ucic:save("shadowsocks-libev")
ucic:commit("shadowsocks-libev")
end
-- Set Glorytun TCP settings
local glorytun_key = luci.http.formvalue("glorytun_key")
if glorytun_key ~= "" then
ucic:set("glorytun","vpn","host",server_ip)
ucic:set("glorytun","vpn","port","65001")
ucic:set("glorytun","vpn","key",glorytun_key)
ucic:set("glorytun","vpn","enable",1)
ucic:set("glorytun","vpn","mptcp",1)
ucic:set("glorytun","vpn","chacha20",1)
ucic:set("glorytun","vpn","proto","tcp")
ucic:save("glorytun")
ucic:commit("glorytun")
end
-- Set interfaces settings
local interfaces = luci.http.formvaluetable("intf")
@ -93,12 +75,221 @@ function wizard_add()
ucic:set("network",intf,"ipaddr",ipaddr)
ucic:set("network",intf,"netmask",netmask)
ucic:set("network",intf,"gateway",gateway)
local downloadspeed = luci.http.formvalue("cbid.sqm.%s.download" % intf) or ""
local uploadspeed = luci.http.formvalue("cbid.sqm.%s.upload" % intf) or ""
if downloadspeed ~= "" and uploadspeed ~= "" then
ucic:set("sqm",intf,"download",downloadspeed)
ucic:set("sqm",intf,"upload",uploadspeed)
ucic:set("sqm",intf,"enabled","1")
else
ucic:set("sqm",intf,"enabled","0")
end
end
ucic:save("sqm")
ucic:commit("sqm")
ucic:save("network")
ucic:commit("network")
-- Get VPN set by default
local default_vpn = luci.http.formvalue("default_vpn") or "glorytun_tcp"
local vpn_port = ""
local vpn_intf = ""
if default_vpn:match("^glorytun.*") then
vpn_port = 65001
vpn_intf = "tun0"
elseif default_vpn == "mlvpn" then
vpn_port = 65201
vpn_intf = "mlvpn0"
elseif default_vpn == "openvpn" then
vpn_port = 65301
vpn_intf = "tun0"
end
if vpn_intf ~= "" then
ucic:set("network","omrvpn","ifname",vpn_intf)
ucic:save("network")
ucic:commit("network")
end
ucic:set("openmptcprouter","settings","vpn",default_vpn)
ucic:save("openmptcprouter")
ucic:commit("openmptcprouter")
-- Get all servers ips
local server_ip = luci.http.formvalue("server_ip") or ""
-- We have an IP, so set it everywhere
if server_ip ~= "" then
local ss_ip
-- Check if we have more than one IP, in this case use Nginx HA
if (type(server_ip) == "table") then
local ss_servers = {}
local vpn_servers = {}
local k = 0
for _, ip in pairs(server_ip) do
if k == 0 then
ss_ip=ip
table.insert(ss_servers,ip .. ":65101 max_fails=3 fail_timeout=30s")
if vpn_port ~= "" then
table.insert(vpn_servers,ip .. ":" .. vpn_port .. " max_fails=3 fail_timeout=30s")
end
ucic:set("qos","serverin","srchost",ip)
ucic:set("qos","serverout","dsthost",ip)
ucic:save("qos")
ucic:commit("qos")
else
table.insert(ss_servers,ip .. ":65101 backup")
if vpn_port ~= "" then
table.insert(vpn_servers,ip .. ":" .. vpn_port .. " backup")
end
end
k = k + 1
end
ucic:set("nginx-ha","ShadowSocks","enable","1")
ucic:set("nginx-ha","VPN","enable","1")
ucic:set("nginx-ha","ShadowSocks","upstreams",ss_servers)
ucic:set("nginx-ha","VPN","upstreams",vpn_servers)
ucic:save("nginx-ha")
ucic:commit("nginx-ha")
server_ip = "127.0.0.1"
ucic:set("shadowsocks-libev","sss0","server",ss_ip)
ucic:save("shadowsocks-libev")
ucic:commit("shadowsocks-libev")
else
ucic:set("nginx-ha","ShadowSocks","enable","0")
ucic:set("nginx-ha","VPN","enable","0")
ucic:set("qos","serverin","srchost",server_ip)
ucic:set("qos","serverout","dsthost",server_ip)
ucic:save("qos")
ucic:commit("qos")
ucic:set("shadowsocks-libev","sss0","server",server_ip)
ucic:save("shadowsocks-libev")
ucic:commit("shadowsocks-libev")
end
ucic:set("glorytun","vpn","host",server_ip)
ucic:save("glorytun")
ucic:commit("glorytun")
ucic:set("mlvpn","general","host",server_ip)
ucic:save("mlvpn")
ucic:commit("mlvpn")
luci.sys.call("uci -q del openvpn.omr.remote")
luci.sys.call("uci -q add_list openvpn.omr.remote=" .. server_ip)
ucic:save("openvpn")
ucic:commit("openvpn")
ucic:set("qos","serverin","srchost",server_ip)
ucic:set("qos","serverout","dsthost",server_ip)
ucic:save("qos")
ucic:commit("qos")
end
-- Set ShadowSocks settings
local shadowsocks_key = luci.http.formvalue("shadowsocks_key")
if shadowsocks_key ~= "" then
ucic:set("shadowsocks-libev","sss0","key",shadowsocks_key)
ucic:set("shadowsocks-libev","sss0","method","chacha20")
ucic:set("shadowsocks-libev","sss0","server_port","65101")
ucic:set("shadowsocks-libev","sss0","disabled",0)
ucic:save("shadowsocks-libev")
ucic:commit("shadowsocks-libev")
else
ucic:set("shadowsocks-libev","sss0","key","")
ucic:set("shadowsocks-libev","sss0","disabled",1)
ucic:save("shadowsocks-libev")
ucic:commit("shadowsocks-libev")
end
-- Set Glorytun settings
if default_vpn:match("^glorytun.*") then
ucic:set("glorytun","vpn","enable",1)
ucic:save("glorytun")
ucic:commit("glorytun")
else
ucic:set("glorytun","vpn","enable",0)
ucic:save("glorytun")
ucic:commit("glorytun")
end
local glorytun_key = luci.http.formvalue("glorytun_key")
if glorytun_key ~= "" then
ucic:set("glorytun","vpn","port","65001")
ucic:set("glorytun","vpn","key",glorytun_key)
ucic:set("glorytun","vpn","mptcp",1)
ucic:set("glorytun","vpn","chacha20",1)
if default_vpn == "glorytun_udp" then
ucic:set("glorytun","vpn","proto","udp")
else
ucic:set("glorytun","vpn","proto","tcp")
end
ucic:save("glorytun")
ucic:commit("glorytun")
else
ucic:set("glorytun","vpn","key","")
ucic:set("glorytun","vpn","enable",0)
ucic:set("glorytun","vpn","proto","tcp")
ucic:save("glorytun")
ucic:commit("glorytun")
end
-- Set MLVPN settings
if default_vpn == "mlvpn" then
ucic:set("mlvpn","general","enable",1)
ucic:save("mlvpn")
ucic:commit("mlvpn")
else
ucic:set("mlvpn","general","enable",0)
ucic:save("mlvpn")
ucic:commit("mlvpn")
end
local mlvpn_password = luci.http.formvalue("mlvpn_password")
if mlvpn_password ~= "" then
ucic:set("mlvpn","general","password",mlvpn_password)
ucic:set("mlvpn","general","firstport","65201")
ucic:set("mlvpn","general","interface_name","mlvpn0")
ucic:save("mlvpn")
ucic:commit("mlvpn")
else
ucic:set("mlvpn","general","enable",0)
ucic:set("mlvpn","general","password","")
ucic:save("mlvpn")
ucic:commit("mlvpn")
end
local openvpn_key = luci.http.formvalue("openvpn_key")
if openvpn_key ~= "" then
local openvpn_key_path = "/etc/luci-uploads/openvpn.key"
local fp
luci.http.setfilehandler(
function(meta, chunk, eof)
if not fp and meta and meta.name == "openvpn_key" then
fp = io.open(openvpn_key_path, "w")
end
if fp and chunk then
fp:write(chunk)
end
if fp and eof then
fp:close()
end
end)
ucic:set("openvpn","omr","secret",openvpn_key_path)
ucic:commit("openvpn")
end
if default_vpn == "openvpn" then
ucic:set("openvpn","omr","enabled",1)
ucic:save("openvpn")
ucic:commit("openvpn")
else
ucic:set("openvpn","omr","enabled",0)
ucic:save("openvpn")
ucic:commit("openvpn")
end
luci.sys.call("(env -i /bin/ubus call network reload) >/dev/null 2>/dev/null")
luci.sys.call("/etc/init.d/shadowsocks restart >/dev/null 2>/dev/null")
luci.sys.call("/etc/init.d/glorytun restart >/dev/null 2>/dev/null")
if gostatus then
luci.sys.call("/etc/init.d/glorytun-udp restart >/dev/null 2>/dev/null")
luci.sys.call("/etc/init.d/mlvpn restart >/dev/null 2>/dev/null")
luci.sys.call("/etc/init.d/openvpn restart >/dev/null 2>/dev/null")
if gostatus == true then
luci.http.redirect(luci.dispatcher.build_url("admin/system/openmptcprouter/status"))
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/openmptcprouter/wizard"))
@ -110,20 +301,39 @@ function settings_add()
-- Set tcp_keepalive_time
local tcp_keepalive_time = luci.http.formvalue("tcp_keepalive_time")
luci.sys.exec("sysctl -w net.ipv4.tcp_keepalive_time=%s" % tcp_keepalive_time)
luci.sys.exec("sed -i 's:^net.ipv4.tcp_keepalive_time = [0-9]*:net.ipv4.tcp_keepalive_time=%s:' /etc/sysctl.d/zzz_openmptcprouter.conf" % tcp_keepalive_time)
luci.sys.exec("sed -i 's:^net.ipv4.tcp_keepalive_time=[0-9]*:net.ipv4.tcp_keepalive_time=%s:' /etc/sysctl.d/zzz_openmptcprouter.conf" % tcp_keepalive_time)
-- Set tcp_fin_timeout
local tcp_fin_timeout = luci.http.formvalue("tcp_fin_timeout")
luci.sys.exec("sysctl -w net.ipv4.tcp_fin_timeoute=%s" % tcp_fin_timeout)
luci.sys.exec("sed -i 's:^net.ipv4.tcp_fin_timeout=[0-9]*:net.ipv4.tcp_fin_timeout=%s:' /etc/sysctl.d/zzz_openmptcprouter.conf" % tcp_fin_timeout)
-- Disable IPv6
local disable_ipv6 = luci.http.formvalue("disable_ipv6") or 0
luci.sys.exec("sysctl -w net.ipv6.conf.all.disable_ipv6=%s" % disable_ipv6)
luci.sys.exec("sed -i 's:^net.ipv6.conf.all.disable_ipv6 = [0-9]*:net.ipv6.conf.all.disable_ipv6=%s:' /etc/sysctl.d/zzz_openmptcprouter.conf" % disable_ipv6)
luci.sys.exec("sed -i 's:^net.ipv6.conf.all.disable_ipv6=[0-9]*:net.ipv6.conf.all.disable_ipv6=%s:' /etc/sysctl.d/zzz_openmptcprouter.conf" % disable_ipv6)
ucic:set("firewall",ucic:get_first("firewall","defaults"),"disable_ipv6",disable_ipv6)
ucic:save("firewall")
ucic:commit("firewall")
ucic:set("dhcp","lan","ra_default",disable_ipv6)
if disable_ipv6 == 1 then
luci.sys.call("uci -q del dhcp.lan.dhcpv6")
luci.sys.call("uci -q del dhcp.lan.ra")
ucic:set("shadowsocks-libev","hi","local_address","0.0.0.0")
else
ucic:set("dhcp","lan","dhcpv6","server")
ucic:set("dhcp","lan","ra","server")
ucic:set("shadowsocks-libev","hi","local_address","::")
end
ucic:save("dhcp")
ucic:commit("dhcp")
local obfs = luci.http.formvalue("obfs") or 0
ucic:foreach("shadowsocks-libev", "ss_redir", function (section)
ucic:set("shadowsocks-libev",section[".name"],"obfs",obfs)
end)
ucic:set("shadowsocks-libev","tracker","obfs",obfs)
ucic:save("shadowsocks-libev")
ucic:commit("shadowsocks-libev")
@ -160,17 +370,65 @@ end
function get_ip(interface)
local dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {})
local ip
local ip = ""
if dump and dump['ipv4-address'] then
local _, ipv4address
for _, ipv4address in ipairs(dump['ipv4-address']) do
ip = dump['ipv4-address'][_].address
end
end
if ip == "" then
local dump = require("luci.util").ubus("network.interface.%s_4" % interface, "status", {})
if dump and dump['ipv4-address'] then
local _, ipv4address
for _, ipv4address in ipairs(dump['ipv4-address']) do
ip = dump['ipv4-address'][_].address
end
end
end
return ip
end
-- This function come from OverTheBox by OVH with very small changes
function get_device(interface)
local dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {})
return dump['l3_device']
end
function get_gateway(interface)
local gateway = ""
local dump = nil
dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {})
if dump and dump.route then
local _, route
for _, route in ipairs(dump.route) do
if dump.route[_].target == "0.0.0.0" then
gateway = dump.route[_].nexthop
end
end
end
if gateway == "" then
dump = require("luci.util").ubus("network.interface.%s_4" % interface, "status", {})
if dump and dump.route then
local _, route
for _, route in ipairs(dump.route) do
if dump.route[_].target == "0.0.0.0" then
gateway = dump.route[_].nexthop
end
end
end
end
return gateway
end
-- This function come from OverTheBox by OVH with some changes
-- Copyright 2015 OVH <OverTheBox@ovh.net>
-- Simon Lelievre (simon.lelievre@corp.ovh.com)
-- Sebastien Duponcheel <sebastien.duponcheel@ovh.net>
-- Under GPL3+
function interfaces_status()
local ut = require "luci.util"
local ntm = require "luci.model.network".init()
@ -181,22 +439,35 @@ function interfaces_status()
-- OpenMPTCProuter info
mArray.openmptcprouter = {}
mArray.openmptcprouter["version"] = ut.trim(sys.exec("cat /etc/os-release | grep VERSION= | sed -e 's:VERSION=::'"))
-- Check that requester is in same network
mArray.openmptcprouter["service_addr"] = uci:get("shadowsocks", "proxy", "server") or "0.0.0.0"
mArray.openmptcprouter["service_addr"] = uci:get("shadowsocks-libev", "proxy", "server") or "0.0.0.0"
mArray.openmptcprouter["local_addr"] = uci:get("network", "lan", "ipaddr")
-- shadowsocksaddr
mArray.openmptcprouter["ss_addr"] = sys.exec("curl -s -4 --socks5 127.0.0.1:1111 -m 5 http://ip.openmptcprouter.com")
-- wanaddr
mArray.openmptcprouter["wan_addr"] = sys.exec("wget -4 -qO- -T 1 http://ip.openmptcprouter.com")
-- dns
mArray.openmptcprouter["dns"] = false
local dns_test = sys.exec("dig openmptcprouter.com | grep 'ANWER: 0'")
local dns_test = sys.exec("dig openmptcprouter.com | grep 'ANSWER: 0'")
if dns_test == "" then
mArray.openmptcprouter["dns"] = true
end
mArray.openmptcprouter["ss_addr"] = ""
--mArray.openmptcprouter["ss_addr6"] = ""
mArray.openmptcprouter["wan_addr"] = ""
mArray.openmptcprouter["wan_addr6"] = ""
local tracker_ip = ""
if mArray.openmptcprouter["dns"] == true then
-- shadowsocksaddr
tracker_ip = uci:get("shadowsocks-libev","tracker","local_address") or ""
local tracker_port = uci:get("shadowsocks-libev","tracker","local_port")
if tracker_ip ~= "" then
mArray.openmptcprouter["ss_addr"] = sys.exec("curl -s -4 --socks5 " .. tracker_ip .. ":" .. tracker_port .. " -m 3 http://ip.openmptcprouter.com")
--mArray.openmptcprouter["ss_addr6"] = sys.exec("curl -s -6 --socks5 " .. tracker_ip .. ":" .. tracker_port .. " -m 3 http://ipv6.openmptcprouter.com")
end
-- wanaddr
mArray.openmptcprouter["wan_addr"] = sys.exec("wget -4 -qO- -T 1 http://ip.openmptcprouter.com")
mArray.openmptcprouter["wan_addr6"] = sys.exec("wget -6 -qO- -T 1 http://ipv6.openmptcprouter.com")
end
mArray.openmptcprouter["remote_addr"] = luci.http.getenv("REMOTE_ADDR") or ""
mArray.openmptcprouter["remote_from_lease"] = false
local leases=tools.dhcp_leases()
@ -209,23 +480,50 @@ function interfaces_status()
-- Check openmptcprouter service are running
mArray.openmptcprouter["tun_service"] = false
if string.find(sys.exec("/usr/bin/pgrep '^(/usr/sbin/)?glorytun(-udp)?$'"), "%d+") then
if string.find(sys.exec("/usr/bin/pgrep '^(/usr/sbin/)?glorytun(-udp)?$'"), "%d+") or string.find(sys.exec("/usr/bin/pgrep '^(/usr/sbin/)?mlvpn?$'"), "%d+") or string.find(sys.exec("/usr/bin/pgrep '^(/usr/sbin/)?openvpn?$'"), "%d+") then
mArray.openmptcprouter["tun_service"] = true
mArray.openmptcprouter["tun_ip"] = get_ip("glorytun")
local tunnel_ping_test = ut.trim(sys.exec("ping -W 1 -c 1 10.0.0.1 | grep '100% packet loss'"))
if tunnel_ping_test == "" then
mArray.openmptcprouter["tun_state"] = 'UP'
else
mArray.openmptcprouter["tun_state"] = 'DOWN'
mArray.openmptcprouter["tun_ip"] = get_ip("omrvpn")
local tun_dev = uci:get("network","omrvpn","ifname")
if tun_dev == "" then
tun_dev = get_device("omrvpn")
end
if tun_dev ~= "" then
local peer = get_gateway("omrvpn")
if peer == "" then
peer = ut.trim(sys.exec("ip -4 r list dev " .. tun_dev .. " | grep kernel | awk '/proto kernel/ {print $1}' | grep -v / | tr -d '\n'"))
end
if peer ~= "" then
local tunnel_ping_test = ut.trim(sys.exec("ping -W 1 -c 1 " .. peer .. " -I " .. tun_dev .. " | grep '100% packet loss'"))
if tunnel_ping_test == "" then
mArray.openmptcprouter["tun_state"] = 'UP'
else
mArray.openmptcprouter["tun_state"] = 'DOWN'
end
local tunnel_ping6_test = ut.trim(sys.exec("ping6 -W 1 -c 1 fe80::a00:1 -I 6in4-omr6in4 | grep '100% packet loss'"))
if tunnel_ping6_test == "" then
mArray.openmptcprouter["tun6_state"] = 'UP'
else
mArray.openmptcprouter["tun6_state"] = 'DOWN'
end
else
mArray.openmptcprouter["tun_state"] = 'DOWN'
mArray.openmptcprouter["tun6_state"] = 'DOWN'
end
end
end
-- check Shadowsocks is running
mArray.openmptcprouter["socks_service"] = false
if string.find(sys.exec("/usr/bin/pgrep ss-redir"), "%d+") then
mArray.openmptcprouter["socks_service"] = true
end
mArray.openmptcprouter["socks_service_enabled"] = true
local ss_server = uci:get("shadowsocks-libev","sss0","disabled") or "0"
if ss_server == "1" then
mArray.openmptcprouter["socks_service_enabled"] = false
end
-- Add DHCP infos by parsing dnsmasq config file
mArray.openmptcprouter.dhcpd = {}
dnsmasq = ut.trim(sys.exec("cat /var/etc/dnsmasq.conf*"))
@ -249,6 +547,7 @@ function interfaces_status()
end
end
end
-- Parse mptcp kernel info
local mptcp = {}
local fullmesh = ut.trim(sys.exec("cat /proc/net/mptcp_fullmesh"))
@ -275,61 +574,165 @@ function interfaces_status()
local ipaddr = net:ipaddr()
local gateway = section['gateway'] or ""
local multipath = section['multipath']
local enabled = section['auto']
--if not ipaddr or not gateway then return end
-- Don't show if0 in the overview
--if interface == "lo" then return end
local ifname = section['ifname'] or ""
if ifname == "" then
ifname = get_device(interface)
end
--if multipath == "off" and not ifname:match("^tun.*") then return end
if multipath == "off" then return end
local asn
if enabled == "0" then return end
local connectivity
local multipath_state = ut.trim(sys.exec("multipath " .. ifname .. " | grep deactivated"))
if multipath_state == "" and ifname ~= "" then
connectivity = 'OK'
if ifname ~= "" and ifname ~= nil then
if fs.access("/sys/class/net/" .. ifname) then
local multipath_state = ut.trim(sys.exec("multipath " .. ifname .. " | grep deactivated"))
if multipath_state == "" then
connectivity = 'OK'
else
connectivity = 'ERROR'
end
else
connectivity = 'ERROR'
end
else
connectivity = 'ERROR'
end
local gw_ping
if gateway ~= "" then
if ipaddr == "" then
connectivity = 'ERROR'
end
-- Detect WAN gateway status
local gw_ping = 'UP'
if gateway == "" then
gateway = get_gateway(interface)
end
if connectivity ~= "ERROR" and gateway == "" and ifname ~= nil then
if fs.access("/sys/class/net/" .. ifname) then
gateway = ut.trim(sys.exec("ip -4 r list dev " .. ifname .. " | grep kernel | awk '/proto kernel/ {print $1}' | grep -v / | tr -d '\n'"))
end
end
if connectivity ~= "ERROR" and gateway ~= "" then
local gw_ping_test = ut.trim(sys.exec("ping -W 1 -c 1 " .. gateway .. " | grep '100% packet loss'"))
if gw_ping_test == "" then
gw_ping = 'UP'
else
if gw_ping_test ~= "" then
gw_ping = 'DOWN'
if connectivity == "OK" then
connectivity = 'WARNING'
end
end
else
gw_ping = 'DOWN'
connectivity = 'ERROR'
end
local latency = ""
local server_ping = ''
if connectivity ~= "ERROR" and ifname ~= "" and gateway ~= "" and gw_ping ~= "DOWN" and ifname ~= nil and mArray.openmptcprouter["wan_addr"] ~= "" then
local server_ping_test = sys.exec("ping -W 1 -c 1 -I " .. ifname .. " " .. mArray.openmptcprouter["wan_addr"])
local server_ping_result = ut.trim(sys.exec("echo '" .. server_ping_test .. "' | grep '100% packet loss'"))
if server_ping_result ~= "" then
server_ping = 'DOWN'
if connectivity == "OK" then
connectivity = 'WARNING'
end
else
server_ping = 'UP'
latency = ut.trim(sys.exec("echo '" .. server_ping_test .. "' | cut -d '/' -s -f4 | cut -d '.' -f1"))
end
end
local publicIP = "-"
local multipath_available
if connectivity ~= "ERROR" and mArray.openmptcprouter["dns"] == true and ifname ~= nil and ifname ~= "" and gateway ~= "" and gw_ping == "UP" then
-- Test if multipath can work on the connection
local multipath_available_state = ut.trim(sys.exec("omr-mptcp-intf " .. ifname .. " | grep 'Nay, Nay, Nay'"))
if multipath_available_state == "" then
multipath_available = 'OK'
else
multipath_available = 'ERROR'
if mArray.openmptcprouter["socks_service"] == true and connectivity == "OK" then
connectivity = 'ERROR'
elseif connectivity == "OK" then
connectivity = 'WARNING'
end
end
else
multipath_available = 'NO CHECK'
end
local latency = "-"
-- Detect if WAN get an IPv6
local ipv6_discover = 'NONE'
if ifname ~= nil then
if tonumber((sys.exec("sysctl net.ipv6.conf.all.disable_ipv6")):match(" %d+")) == 0 then
local ipv6_result = _ipv6_discover(ifname)
if type(ipv6_result) == "table" and #ipv6_result > 0 then
local ipv6_addr_test
for k,v in ipairs(ipv6_result) do
if v.RecursiveDnsServer then
if type(v.RecursiveDnsServer) ~= "table" then
ipv6_addr_test = sys.exec('ip -6 addr | grep ' .. v.RecursiveDnsServer)
if ipv6_addr_test == "" then
ipv6_discover = 'DETECTED'
if connectivity == "OK" then
connectivity = 'WARNING'
end
end
else
for i,j in ipairs(ipv6_result) do
ipv6_addr_test = sys.exec('ip -6 addr | grep ' .. j)
if ipv6_addr_test == "" then
ipv6_discover = 'DETECTED'
if connectivity == "OK" then
connectivity = 'WARNING'
end
end
end
end
end
end
end
end
end
local publicIP = ut.trim(sys.exec("omr-ip-intf " .. ifname))
local whois = ""
if publicIP ~= "" then
whois = ut.trim(sys.exec("whois " .. publicIP .. " | grep -i 'netname' | awk '{print $2}'"))
end
local data = {
label = section['label'] or interface,
name = interface,
link = net:adminlink(),
ifname = ifname,
ipaddr = ipaddr,
gateway = gateway,
multipath = section['multipath'],
status = connectivity,
wanip = publicIP,
latency = latency,
whois = asn and asn.as_description or "unknown",
qos = section['trafficcontrol'],
download = section['download'],
upload = section['upload'],
gw_ping = gw_ping,
name = interface,
link = net:adminlink(),
ifname = ifname,
ipaddr = ipaddr,
gateway = gateway,
multipath = section['multipath'],
status = connectivity,
wanip = publicIP,
latency = latency,
whois = whois or "unknown",
qos = section['trafficcontrol'],
download = section['download'],
upload = section['upload'],
gw_ping = gw_ping,
server_ping = server_ping,
ipv6_discover = ipv6_discover,
multipath_available = multipath_available,
}
if ifname:match("^tun.*") then
if ifname ~= nil and ifname:match("^tun.*") then
table.insert(mArray.tunnels, data);
elseif ifname ~= nil and ifname:match("^mlvpn.*") then
table.insert(mArray.tunnels, data);
else
table.insert(mArray.wans, data);
@ -338,4 +741,65 @@ function interfaces_status()
luci.http.prepare_content("application/json")
luci.http.write_json(mArray)
end
end
-- This come from OverTheBox by OVH
-- Copyright 2015 OVH <OverTheBox@ovh.net>
-- Simon Lelievre (simon.lelievre@corp.ovh.com)
-- Sebastien Duponcheel <sebastien.duponcheel@ovh.net>
-- Under GPL3+
function _ipv6_discover(interface)
local result = {}
--local ra6_list = (sys.exec("rdisc6 -nm " .. interface))
local ra6_list = (sys.exec("rdisc6 -n1 -r1 " .. interface))
-- dissect results
local lines = {}
local index = {}
ra6_list:gsub('[^\r\n]+', function(c)
table.insert(lines, c)
if c:match("Hop limit") then
table.insert(index, #lines)
end
end)
local ra6_result = {}
for k,v in ipairs(index) do
local istart = v
local iend = index[k+1] or #lines
local entry = {}
for i=istart,iend - 1 do
local level = lines[i]:find('%w')
local line = lines[i]:sub(level)
local param, value
if line:match('^from') then
param, value = line:match('(from)%s+(.*)$')
else
param, value = line:match('([^:]+):(.*)$')
-- Capitalize param name and remove spaces
param = param:gsub("(%a)([%w_']*)", function(first, rest) return first:upper()..rest:lower() end):gsub("[%s-]",'')
param = param:gsub("%.$", '')
-- Remove text between brackets, seconds and spaces
value = value:lower()
value = value:gsub("%(.*%)", '')
value = value:gsub("%s-seconds%s-", '')
value = value:gsub("^%s+", '')
value = value:gsub("%s+$", '')
end
if entry[param] == nil then
entry[param] = value
elseif type(entry[param]) == "table" then
table.insert(entry[param], value)
else
old = entry[param]
entry[param] = {}
table.insert(entry[param], old)
table.insert(entry[param], value)
end
end
table.insert(ra6_result, entry)
end
return ra6_result
end

View file

@ -13,12 +13,28 @@
<input type="text" name="tcp_keepalive_time" class="cbi-input-text" value="<%=tonumber((luci.sys.exec("sysctl net.ipv4.tcp_keepalive_time")):match(" %d+"))%>">
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:IPv4 TCP FIN timeout%></label>
<div class="cbi-value-field">
<input type="text" name="tcp_fin_timeout" class="cbi-input-text" value="<%=tonumber((luci.sys.exec("sysctl net.ipv4.tcp_fin_timeout")):match(" %d+"))%>">
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Disable IPv6%></label>
<div class="cbi-value-field">
<input type="checkbox" name="disable_ipv6" class="cbi-input-checkbox" value="1" <% if tonumber((luci.sys.exec("sysctl net.ipv6.conf.all.disable_ipv6")):match(" %d+")) == 1 then %>checked<% end %>>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Enable ShadowSocks OBFS%></label>
<div class="cbi-value-field">
<input type="checkbox" name="obfs" class="cbi-input-checkbox" value="1" <% if luci.model.uci.cursor():get("shadowsocks-libev","tracker","obfs") == "1" then %>checked<% end %>>
<br />
<div class="cbi-value-description">
<%:OBFS must be enabled on VPS%>
</div>
</div>
</div>
</fieldset>
<% if nixio.fs.access("/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq") then %>
<fieldset class="cbi-section" id="system">
@ -57,7 +73,7 @@
</div>
<div class="cbi-page-actions">
<input type="hidden" name="token" value="<%=token%>" />
<button class="btn" type="submit">Submit</button>
<input class="cbi-button cbi-button-apply" type="submit" value="<%:Save & Apply%>" /> <input class="cbi-button cbi-button-reset" type="button" value="Reset" onclick="location.href='<%=url('admin/system/openmptcprouter/settings')%>'" />
</div>
</form>
<%+footer%>

View file

@ -22,13 +22,14 @@
-- Copyright 2018 Ycarus (Yannick Chabanois) ycarus@zugaina.org
--
-- Small changes to make this work with OpenMPTCProuter
-- New features: DNS detection, IPv6 route received,...
-%>
<%+header%>
<link rel="stylesheet" type="text/css" href="<%=resource%>/openmptcprouter/css/wanstatus.css?v=git-18.120.38690-2678b12"/>
<script type="text/javascript" src="<%=resource%>/seedrandom.js?v=git-18.120.38690-2678b12"></script>
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-18.120.38690-2678b12"></script>
<script type="text/javascript">//<![CDATA[
XHR.poll(15, '/cgi-bin/luci/admin/system/openmptcprouter/interfaces_status', null,
XHR.poll(20, '/cgi-bin/luci/admin/system/openmptcprouter/interfaces_status', null,
function(x, mArray)
{
var status = document.getElementById('openmptcprouter_status');
@ -71,18 +72,18 @@
}
if (mArray.openmptcprouter.loadavg)
{
content += "Load : " + mArray.openmptcprouter.loadavg;
content += "Load: " + mArray.openmptcprouter.loadavg;
content += "<br />";
}
if (mArray.openmptcprouter.core_temp)
{
content += "Core temp : " + (mArray.openmptcprouter.core_temp / 1000).toFixed(1) + " &#176;";
content += "Core temp: " + (mArray.openmptcprouter.core_temp / 1000).toFixed(1) + " &#176;";
content += "<br />";
}
if (mArray.openmptcprouter.uptime)
{
var date = new Date(null);
content += "Uptime : " + String.format('%t', mArray.openmptcprouter.uptime);
content += "Uptime: " + String.format('%t', mArray.openmptcprouter.uptime);
content += "<br />";
}
if (mArray.openmptcprouter.dhcpd)
@ -101,9 +102,14 @@
temp += String.format('lan (%s)', mArray.openmptcprouter.local_addr);
}
if (mArray.openmptcprouter.socks_service == false)
{
statusMessage += 'ShadowSocks is not running<br/>';
if (mArray.openmptcprouter.socks_service_enabled == true) {
if (mArray.openmptcprouter.socks_service == false)
{
statusMessage += 'ShadowSocks is not running<br/>';
} else if (mArray.openmptcprouter.ss_addr == "")
{
statusMessage += 'ShadowSocks not working<br/>';
}
}
if (mArray.openmptcprouter.tun_service == false)
{
@ -117,20 +123,37 @@
{
statusMessage += 'DNS issue: can\'t resolve hostname<br/>';
}
if (mArray.openmptcprouter.ss_addr == "")
if (mArray.openmptcprouter.wan_addr6)
{
statusMessage += 'ShadowSocks not working<br/>';
content += "IPv6: " + mArray.openmptcprouter.wan_addr6;
content += "<br />";
}
if(statusMessage !== "")
{
statusMessageClass = "error";
statusIcon = "<%=resource%>/openmptcprouter/images/statusError.png";
} else if (mArray.openmptcprouter.tun_state == "DOWN")
{
statusMessageClass = "warning";
statusIcon = "<%=resource%>/openmptcprouter/images/statusWarning.png";
statusMessage += 'Glorytun VPN tunnel DOWN';
} else {
if (mArray.openmptcprouter.socks_service_enabled == false)
{
statusMessage += 'ShadowSocks is DISABLED<br/>';
}
if (mArray.openmptcprouter.tun_state == "DOWN")
{
statusMessage += 'VPN tunnel DOWN<br/>';
}
if (mArray.openmptcprouter.tun6_state == "DOWN")
{
statusMessage += 'VPN IPv6 tunnel DOWN<br/>';
} else if (mArray.openmptcprouter.wan_addr6 == '')
{
statusMessage += 'No IPv6 access<br/>';
}
if (statusMessage !== "")
{
statusMessageClass = "warning";
statusIcon = "<%=resource%>/openmptcprouter/images/statusWarning.png";
}
}
temp += getNetworkNodeTemplate(equipmentIcon, statusIcon, title, statusMessageClass,statusMessage,content);
temp += '</a></td>'
@ -138,6 +161,7 @@
if (mArray.wans)
{
temp += '<td><ul>';
var master = 0;
for( var i = 0; i < mArray.wans.length; i++ )
{
// Get link color
@ -173,9 +197,16 @@
var ipaddr = mArray.wans[i].ipaddr;
var whois = mArray.wans[i].whois;
var multipath = mArray.wans[i].multipath;
if(multipath == 'master')
{
master++;
}
var latency = mArray.wans[i].latency;
var gateway = mArray.wans[i].gateway;
var gw_ping = mArray.wans[i].gw_ping;
var server_ping = mArray.wans[i].server_ping;
var ipv6_discover = mArray.wans[i].ipv6_discover;
var multipath_available = mArray.wans[i].multipath_available;
// Generate template
if(mArray.openmptcprouter.remote_from_lease == true && mArray.wans.length == 1)
{
@ -188,14 +219,53 @@
var title = mArray.wans[i].label + " (" + mArray.wans[i].gateway + ")";
//var content = String.format('%s<br />wan address: <strong>%s</strong><br />whois: %s<br />latency: %s ms<br />multipath: %s', stat, wanip, whois, latency, multipath);
var content = "";
if(gw_ping == 'DOWN')
if(wanip !== '')
{
content += String.format('wan address: <strong>%s</strong><br />', wanip);
}
if(whois !== '')
{
content += String.format('whois: %s<br />', whois);
}
if(latency !== '')
{
content += String.format('latency: %s ms<br />', latency);
}
if(ipaddr == '')
{
statusMessage += 'No IP defined<br />'
}
if(gateway == '')
{
statusMessage += 'No gateway defined<br />'
} else if(gw_ping == 'DOWN')
{
statusMessage += 'Gateway DOWN<br />'
} else if(multipath_available == 'ERROR')
{
statusMessage += 'Multipath blocked on the connection<br />'
}
content += String.format('ip address: <strong>%s</strong><br />multipath: %s', ipaddr,multipath);
if(server_ping == 'DOWN')
{
statusMessage += 'No Server ping response after 1 second<br />'
}
if(multipath == 'master' && master > 1)
{
statusMessage += 'Multipath master already defined<br />';
statusMessageClass = "error";
}
if(ipv6_discover == 'DETECTED')
{
statusMessage += 'IPv6 route received<br />'
}
if(ipaddr != '')
{
content += String.format('ip address: <strong>%s</strong><br />', ipaddr);
}
content += String.format('multipath: %s<br />',multipath);
if(mArray.wans[i].qos && mArray.wans[i].download > 0 && mArray.wans[i].upload > 0)
{
content += String.format('<br />traffic control: %s/%s kbps (%s)', mArray.wans[i].download, mArray.wans[i].upload, mArray.wans[i].qos)
content += String.format('traffic control: %s/%s kbps (%s)', mArray.wans[i].download, mArray.wans[i].upload, mArray.wans[i].qos)
}
temp += getNetworkNodeTemplate(equipmentIcon, statusIcon, title, statusMessageClass,statusMessage,content);
@ -302,6 +372,6 @@
<h2><%:Network overview%></h2>
<fieldset id="interface_field" class="cbi-section">
<!-- <legend><%:Network overview%></legen> -->
<div id="openmptcprouter_status"></div>
<div id="openmptcprouter_status"><img src="<%=resource%>/spinner.gif" /></div>
</fieldset>
<%+footer%>

View file

@ -3,33 +3,115 @@
<%
local uci = require("luci.model.uci").cursor()
local net = require "luci.model.network".init()
local fs = require "nixio.fs"
local ifaces = net:get_interfaces()
local servers_ip = {}
local server_ip = uci:get("shadowsocks-libev","sss0","server")
if server_ip == '127.0.0.1' then
local upstreams = uci:get("nginx-ha","ShadowSocks","upstreams")
for _, up in pairs(upstreams) do
local a = up:match("^([^:]+):")
table.insert(servers_ip,a)
end
else
table.insert(servers_ip,server_ip)
end
%>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<script type="text/javascript" src="<%=resource%>/cbi.js" data-strings="{&#34;path&#34;:{&#34;resource&#34;:&#34;\/luci-static\/resources&#34;,&#34;browser&#34;:&#34;\/cgi-bin\/luci\/admin\/filebrowser&#34;}}"></script>
<% if stderr and #stderr > 0 then %><pre class="error"><%=pcdata(stderr)%></pre><% end %>
<form class="inline" method="post" action="<%=url('admin/system/openmptcprouter/wizard_add')%>">
<form class="inline" method="post" action="<%=url('admin/system/openmptcprouter/wizard_add')%>" enctype="multipart/form-data">
<div class="cbi-map">
<h2 name="content"><%:Wizard%></h2>
<fieldset class="cbi-section" id="server">
<legend><%:Server settings%></legend>
<div class="cbi-section-descr"><%:Put the values given by OpenMPTCProuter VPS script.%></div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Server IP%></label>
<div class="cbi-value cbi-value-last" id="server_ip" data-depends="[]" data-index="0">
<label class="cbi-value-title" for="server_ip"><%:Server IP%></label>
<div class="cbi-value-field">
<input type="text" name="server_ip" placeholder="Server IP" class="cbi-input-text" value="<%=uci:get("shadowsocks-libev","sss0","server")%>" data-type="ip4addr">
<%
local has_nginxha = fs.access("/etc/config/nginx-ha")
if has_nginxha then
%>
<div data-prefix="server_ip" data-browser-path="" data-dynlist="[[],[],null,false]" data-placeholder="123.123.123.123">
<%
end
local k = 0
for _,server in ipairs(servers_ip) do
k = k+1
%>
<input name="server_ip" id="server_ip.<%=k%>" placeholder="Server IP" class="cbi-input-text" value="<%=server%>" data-type="ip4addr">
<br />
<%
end
%>
</div>
<div class="cbi-value-description">
<%:Server IP will be set for ShadowSocks, Glorytun, OpenVPN and MLVPN%>
</div>
</div>
<br />
<div class="cbi-value">
<label class="cbi-value-title"><%:ShadowSocks key%></label>
<div class="cbi-value-field">
<input type="text" name="shadowsocks_key" placeholder="ShadowSocks key" class="cbi-input-text" value="<%=uci:get("shadowsocks-libev","sss0","key")%>" data-type="base64">
<br />
<div class="cbi-value-description">
<%:ShadowSocks is used for TCP%>
</div>
</div>
</div>
<% if nixio.fs.access("/usr/sbin/glorytun") or nixio.fs.access("/usr/sbin/glorytun-udp") then %>
<div class="cbi-value">
<label class="cbi-value-title"><%:Glorytun key%></label>
<div class="cbi-value-field">
<input type="text" name="glorytun_key" placeholder="Glorytun key" class="cbi-input-text" value="<%=uci:get("glorytun","vpn","key")%>">
<br />
<div class="cbi-value-description">
<%:Glorytun TCP is used by default for UDP and ICMP%>
</div>
</div>
</div>
<% end %>
<% if nixio.fs.access("/usr/sbin/mlvpn") then %>
<div class="cbi-value">
<label class="cbi-value-title"><%:MLVPN password%></label>
<div class="cbi-value-field">
<input type="text" name="mlvpn_password" placeholder="MLVPN password" class="cbi-input-text" value="<%=uci:get("mlvpn","general","password")%>">
<br />
<div class="cbi-value-description">
<%:MLVPN can replace Glorytun with connections with same latency%>
</div>
</div>
</div>
<% end %>
<% if nixio.fs.access("/usr/sbin/openvpn") then %>
<div class="cbi-value">
<label class="cbi-value-title"><%:OpenVPN key%></label>
<div class="cbi-value-field">
<input type="file" name="openvpn_key" class="cbi-input-file">
<input type="text" class="cbi-input-text" data-update="change" value="<%=uci:get("openvpn","omr","secret")%>" />
<br />
<div class="cbi-value-description">
<%:You need to upload OpenVPN key file generated by OpenMPTCProuter VPS script to use OpenVPN TCP%>
</div>
</div>
</div>
<% end %>
<div class="cbi-value">
<label class="cbi-value-title"><%:Default VPN%></label>
<div class="cbi-value-field">
<select class="cbi-input-select" name="default_vpn" size="1">
<% if nixio.fs.access("/usr/sbin/glorytun") then %><option value="glorytun_tcp" <% if uci:get("glorytun","vpn","enable") == "1" and uci:get("glorytun","vpn","proto") == "tcp" then %>selected="selected"<% end %>>Glorytun TCP</option><% end %>
<% if nixio.fs.access("/usr/sbin/glorytun-udp") then %><option value="glorytun_udp" <% if uci:get("glorytun","vpn","enable") == "1" and uci:get("glorytun","vpn","proto") == "udp" then %>selected="selected"<% end %>>Glorytun UDP</option><% end %>
<% if nixio.fs.access("/usr/sbin/mlvpn") then %><option value="mlvpn" <% if uci:get("mlvpn","general","enable") == "1" then %>selected="selected"<% end %>>MLVPN</option><% end %>
<% if nixio.fs.access("/usr/sbin/openvpn") then %><option value="openvpn" <% if uci:get("openvpn","omr","enabled") == "1" then %>selected="selected"<% end %>>OpenVPN</option><% end %>
<option value="none" <% if uci:get("openmptcprouter","settings","vpn") == "none" then %>selected="selected"<% end %>>None</option>
</select>
<br />
<div class="cbi-value-description">
<%:Set the default VPN used for UDP and ICMP when ShadowSocks is enabled, for all traffic if ShadowSocks is disabled.%>
</div>
</div>
</div>
</fieldset>
@ -54,7 +136,6 @@
<input type="text" name="cbid.network.<%=ifname%>.ipaddr" class="cbi-input-text" value="<%=uci:get("network",ifname,"ipaddr")%>" data-type="ip4addr">
<br />
<div class="cbi-value-description">
<span class="cbi-value-helpicon"><img src="/luci-static/resources/cbi/help.gif" alt="help" /></span>
<%:Set an IP in the same network as the modem%>
</div>
</div>
@ -71,11 +152,36 @@
<input type="text" name="cbid.network.<%=ifname%>.gateway" class="cbi-input-text" value="<%=uci:get("network",ifname,"gateway")%>" data-type="ip4addr">
<br />
<div class="cbi-value-description">
<span class="cbi-value-helpicon"><img src="/luci-static/resources/cbi/help.gif" alt="help" /></span>
<%:Set here IP of the modem%>
</div>
</div>
</div>
<%
if nixio.fs.access("/etc/init.d/sqm") then
%>
<div class="cbi-value">
<label class="cbi-value-title"><%:Download speed (Kb/s)%></label>
<div class="cbi-value-field">
<input type="text" name="cbid.sqm.<%=ifname%>.download" class="cbi-input-text" value="<%=uci:get("sqm",ifname,"download")%>" data-type="uinteger">
<br />
<div class="cbi-value-description">
<%:Set value between 80-95% of max download speed link. Used for SQM. Empty to disable.%>
</div>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Upload speed (Kb/s)%></label>
<div class="cbi-value-field">
<input type="text" name="cbid.sqm.<%=ifname%>.upload" class="cbi-input-text" value="<%=uci:get("sqm",ifname,"upload")%>" data-type="uinteger">
<br />
<div class="cbi-value-description">
<%:Set value between 80-95% of max upload speed link. Used for SQM. Empty to disable.%>
</div>
</div>
</div>
<%
end
%>
</fieldset>
<%
end
@ -89,7 +195,8 @@
</div>
<div class="cbi-page-actions">
<input type="hidden" name="token" value="<%=token%>" />
<button class="btn" type="submit">Submit</button>
<input class="cbi-button cbi-button-apply" type="submit" value="<%:Save & Apply%>" /> <input class="cbi-button cbi-button-reset" type="button" value="Reset" onclick="location.href='<%=url('admin/system/openmptcprouter/wizard')%>'" />
</div>
</form>
<script type="text/javascript">cbi_init();</script>
<%+footer%>

View file

@ -1,16 +1,16 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Ycarus <ycarus@zugaina.org>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.6\n"
"Last-Translator: Ycarus <ycarus@zugaina.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Language: fr\n"
msgid "Add an interface"
msgstr "Ajouter une interface"
@ -18,12 +18,18 @@ msgstr "Ajouter une interface"
msgid "Advanced Settings"
msgstr "Configuration avancé"
msgid "Default VPN"
msgstr "VPN par défaut"
msgid "Delete"
msgstr "Supprimer"
msgid "Disable IPv6"
msgstr "Désactiver IPv6"
msgid "Glorytun TCP is used by default for UDP and ICMP"
msgstr "Glorytun est utilisé par défaut pour UDP et ICMP"
msgid "Glorytun key"
msgstr "Clef Glorytun"
@ -37,11 +43,17 @@ msgid "IPv4 gateway"
msgstr "Passerelle IPv4"
msgid "IPv4 netmask"
msgstr "Masque de sous-réseau IPv6"
msgstr "Masque de sous-réseau IPv4"
msgid "Interfaces settings"
msgstr "Paramètres des interfaces"
msgid "MLVPN can replace Glorytun with connections with same latency"
msgstr "MLVPN peut remplacer Glorytun pour les connexions avec la même latence"
msgid "MLVPN password"
msgstr "Mot de passe MLVPN"
msgid "Maximum scaling CPU frequency"
msgstr ""
@ -57,6 +69,9 @@ msgstr "Paramètres réseaux"
msgid "OpenMPTCProuter"
msgstr ""
msgid "OpenVPN key"
msgstr "Clef OpenVPN"
msgid "Put the values given by OpenMPTCProuter VPS script."
msgstr "Mettez les valeurs données par le script OpenMPTCProuter VPS."
@ -66,6 +81,10 @@ msgstr ""
msgid "Server IP"
msgstr "IP du serveur"
msgid "Server IP will be set for ShadowSocks, Glorytun, OpenVPN and MLVPN"
msgstr ""
"L'IP du serveur sera configuré pour ShadowSocks, Glorytun, OpenVPN et MLVPN"
msgid "Server settings"
msgstr "Paramètres du serveur"
@ -75,9 +94,19 @@ msgstr "Mettez une IP dans le même réseau que le modem"
msgid "Set here IP of the modem"
msgstr "Mettez ici l'IP du modem"
msgid ""
"Set the default VPN used for UDP and ICMP when ShadowSocks is enabled, for "
"all traffic if ShadowSocks is disabled."
msgstr ""
"Configure le VPN utilisé par défaut pour UDP et ICMP quand ShadowSocks est "
"activé, pour tout le trafic quand ShadowSocks est désactivé."
msgid "Settings Wizard"
msgstr "Assistant de configuration"
msgid "ShadowSocks is used for TCP"
msgstr "ShadowSocks est utilisé pour le TCP"
msgid "ShadowSocks key"
msgstr "Clef de ShadowSocks"
@ -91,4 +120,23 @@ msgid "Wizard"
msgstr "Assistant"
msgid "You must disable DHCP on your modems and set IP in different networks."
msgstr "Vous devez désactiver DHCP sur vos modems et configurer leurs IP dans des réseaux différents."
msgstr ""
"Vous devez désactiver DHCP sur vos modems et configurer leurs IP dans des "
"réseaux différents."
msgid ""
"You need to upload OpenVPN key file generated by OpenMPTCProuter VPS script "
"to use OpenVPN TCP"
msgstr ""
"Vous devez ajouter le fichier contenant la clef OpenVPN générée par le "
"script OpenMPTCProuter VPS pour utiliser OpenVPN TCP"
#~ msgid "Glorytun is used for UDP and ICMP"
#~ msgstr "Glorytun est utilisé pour UDP et ICMP"
#~ msgid "MLVPN can replace Glorytun with connection with same latency"
#~ msgstr ""
#~ "MLVPN peut remplacer Glorytun pour les connexions avec la même latence"
#~ msgid "Server IP will be set for ShadowSocks, Glorytun and MLVPN"
#~ msgstr "L'IP du serveur sera configuré pour ShadowSocks, Glorytun et MLVPN"

View file

@ -7,12 +7,18 @@ msgstr ""
msgid "Advanced Settings"
msgstr ""
msgid "Default VPN"
msgstr ""
msgid "Delete"
msgstr ""
msgid "Disable IPv6"
msgstr ""
msgid "Glorytun TCP is used by default for UDP and ICMP"
msgstr ""
msgid "Glorytun key"
msgstr ""
@ -31,6 +37,12 @@ msgstr ""
msgid "Interfaces settings"
msgstr ""
msgid "MLVPN can replace Glorytun with connections with same latency"
msgstr ""
msgid "MLVPN password"
msgstr ""
msgid "Maximum scaling CPU frequency"
msgstr ""
@ -46,6 +58,9 @@ msgstr ""
msgid "OpenMPTCProuter"
msgstr ""
msgid "OpenVPN key"
msgstr ""
msgid "Put the values given by OpenMPTCProuter VPS script."
msgstr ""
@ -55,6 +70,9 @@ msgstr ""
msgid "Server IP"
msgstr ""
msgid "Server IP will be set for ShadowSocks, Glorytun, OpenVPN and MLVPN"
msgstr ""
msgid "Server settings"
msgstr ""
@ -64,9 +82,17 @@ msgstr ""
msgid "Set here IP of the modem"
msgstr ""
msgid ""
"Set the default VPN used for UDP and ICMP when ShadowSocks is enabled, for "
"all traffic if ShadowSocks is disabled."
msgstr ""
msgid "Settings Wizard"
msgstr ""
msgid "ShadowSocks is used for TCP"
msgstr ""
msgid "ShadowSocks key"
msgstr ""
@ -81,3 +107,8 @@ msgstr ""
msgid "You must disable DHCP on your modems and set IP in different networks."
msgstr ""
msgid ""
"You need to upload OpenVPN key file generated by OpenMPTCProuter VPS script "
"to use OpenVPN TCP"
msgstr ""

View file

@ -0,0 +1,5 @@
#!/bin/sh
checkip=$(dig +short A ip.openmptcprouter.com | tr -d "\n")
ipset add ss_rules_dst_bypass $checkip > /dev/null 2>&1
curl -s -4 -m 3 --interface $1 http://ip.openmptcprouter.com
ipset del ss_rules_dst_bypass $checkip > /dev/null 2>&1

View file

@ -0,0 +1,5 @@
#!/bin/sh
multipathip=$(dig +short A multipath-tcp.org | tr -d "\n")
ipset add ss_rules_dst_bypass $multipathip > /dev/null 2>&1
curl -s -4 -m 3 --interface $1 http://www.multipath-tcp.org
ipset del ss_rules_dst_bypass $multipathip > /dev/null 2>&1

View file

@ -27,6 +27,7 @@ start_service() {
}
# remove sysctl already defined in /etc/sysctl.d/
sed -i -e '/tcp_fin_timeout/d' -e '/tcp_keepalive_time/d' -e '/nf_conntrack_max/d' /etc/sysctl.conf
sed -i -e '/tcp_fin_timeout/d' -e '/tcp_keepalive_time/d' -e '/nf_conntrack_max/d' /etc/sysctl.d/10-default.conf
}
reload_service() {

View file

@ -1,2 +1,3 @@
net.ipv4.tcp_keepalive_time = 1200
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv4.tcp_keepalive_time=7200
net.ipv6.conf.all.disable_ipv6=0
net.ipv4.tcp_fin_timeout=40

View file

@ -4,4 +4,20 @@ uci -q batch <<-EOF
add ucitrack openmptcprouter
set ucitrack.@openmptcprouter[-1].init=openmptcprouter
commit ucitrack
EOF
EOF
if [ "$(uci -q get qos.serverin)" = "" ]; then
uci -q batch <<-EOF
set qos.serverin=classify
set qos.serverin.target='Priority'
set qos.serverout=classify
set qos.serverout.target='Priority'
commit qos
EOF
fi
if [ "$(uci -q get qos.serverin.target)" = "" ]; then
uci -q batch <<-EOF
set qos.serverin.target='Priority'
set qos.serverout.target='Priority'
commit qos
EOF
fi

View file

@ -7,6 +7,7 @@ local ut = require("luci.util")
local sys = require("luci.sys")
local ds = require("luci.dispatcher")
local nw = require("luci.model.network")
local ucic = luci.model.uci.cursor()
nw.init()
module("luci.model.shadowsocks-libev", function(m)
setmetatable(m, {__index=function (self, k)
@ -24,7 +25,7 @@ function values_actions(o)
end
function values_redir(o, xmode)
o.map.uci.foreach("shadowsocks-libev", "ss_redir", function(sdata)
ucic:foreach("shadowsocks-libev", "ss_redir", function(sdata)
local disabled = ucival_to_bool(sdata["disabled"])
local sname = sdata[".name"]
local mode = sdata["mode"] or "tcp_only"
@ -38,7 +39,7 @@ function values_redir(o, xmode)
end
function values_serverlist(o)
o.map.uci.foreach("shadowsocks-libev", "server", function(sdata)
ucic:foreach("shadowsocks-libev", "server", function(sdata)
local sname = sdata[".name"]
local server = sdata["server"]
local server_port = sdata["server_port"]

View file

@ -1,24 +1,17 @@
<div class="cbi-section-create cbi-tblsection-create">
<br />
<table class="cbi-section-table">
<tr class="cbi-section-table-row">
<td class="cbi-section-table-cell" style="width:140px">
<select class="cbi-input-select" id="_newinst.type" name="_newinst.type">
<option value="_dummy">-- instance type --</option>
<option value="ss_local">ss-local</option>
<option value="ss_tunnel">ss-tunnel</option>
<option value="ss_redir">ss-redir</option>
<option value="ss_server">ss-server</option>
</select>
</td>
<td class="cbi-section-table-cell" style="width:110px">
<input type="text" class="cbi-input-text" id="_newinst.name" name="_newinst.name" placeholder="<%:Name%>"/>
</td>
<td class="cbi-section-table-cell left">
<input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>" value="<%:Add%>" />
</td>
</tr>
</table>
<div>
<select class="cbi-input-select" id="_newinst.type" name="_newinst.type">
<option value="_dummy">-- instance type --</option>
<option value="ss_local">ss-local</option>
<option value="ss_tunnel">ss-tunnel</option>
<option value="ss_redir">ss-redir</option>
<option value="ss_server">ss-server</option>
</select>
</div>
<div>
<input type="text" class="cbi-input-text" id="_newinst.name" name="_newinst.name" placeholder="<%:Name%>"/>
</div>
<input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>" value="<%:Add%>" />
</div>
<script type="text/javascript">//<![CDATA[
XHR.poll(5, '<%=url('admin/services/shadowsocks-libev/status')%>', null,

View file

@ -14,12 +14,13 @@ LUCI_BASENAME:=base
LUCI_TITLE:=LuCI core libraries
LUCI_DEPENDS:=+lua +luci-lib-nixio +luci-lib-ip +rpcd +libubus-lua +luci-lib-jsonc +liblucihttp-lua
PKG_SOURCE:=LuaSrcDiet-0.12.1.tar.bz2
PKG_SOURCE_URL:=https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/luasrcdiet
PKG_HASH:=ed7680f2896269ae8633756e7edcf09050812f78c8f49e280e63c30d14f35aea
PKG_LICENSE:=Apache-2.0
HOST_BUILD_DIR:=$(BUILD_DIR_HOST)/LuaSrcDiet-0.12.1
PKG_SOURCE:=v1.0.0.tar.gz
PKG_SOURCE_URL:=https://github.com/jirutka/luasrcdiet/archive/
PKG_HASH:=48162e63e77d009f5848f18a5cabffbdfc867d0e5e73c6d407f6af5d6880151b
PKG_LICENSE:=MIT
HOST_BUILD_DIR:=$(BUILD_DIR_HOST)/luasrcdiet-1.0.0
include $(INCLUDE_DIR)/host-build.mk
@ -36,13 +37,14 @@ endef
define Host/Compile
$(MAKE) -C src/ clean po2lmo
$(MAKE) -C $(HOST_BUILD_DIR) bin/LuaSrcDiet.lua
endef
define Host/Install
$(INSTALL_DIR) $(1)/bin
$(INSTALL_DIR) $(1)/lib/lua/5.1
$(INSTALL_BIN) src/po2lmo $(1)/bin/po2lmo
$(INSTALL_BIN) $(HOST_BUILD_DIR)/bin/LuaSrcDiet.lua $(1)/bin/LuaSrcDiet
$(INSTALL_BIN) $(HOST_BUILD_DIR)/bin/luasrcdiet $(1)/bin/luasrcdiet
$(CP) $(HOST_BUILD_DIR)/luasrcdiet $(1)/lib/lua/5.1/
endef
$(eval $(call HostBuild))

View file

@ -465,31 +465,16 @@ function cbi_d_add(field, dep, index) {
}
function cbi_d_checkvalue(target, ref) {
var t = document.getElementById(target);
var value;
var value = null,
query = 'input[id="'+target+'"], input[name="'+target+'"], ' +
'select[id="'+target+'"], select[name="'+target+'"]';
if (!t) {
var tl = document.getElementsByName(target);
document.querySelectorAll(query).forEach(function(i) {
if (value === null && ((i.type !== 'radio' && i.type !== 'checkbox') || i.checked === true))
value = i.value;
});
if( tl.length > 0 && (tl[0].type == 'radio' || tl[0].type == 'checkbox'))
for( var i = 0; i < tl.length; i++ )
if( tl[i].checked ) {
value = tl[i].value;
break;
}
value = value ? value : "";
} else if (!t.value) {
value = "";
} else {
value = t.value;
if (t.type == "checkbox") {
value = t.checked ? value : "";
}
}
return (value == ref)
return (((value !== null) ? value : "") == ref);
}
function cbi_d_check(deps) {
@ -634,6 +619,26 @@ function cbi_init() {
node.getAttribute('data-type'));
}
document.querySelectorAll('.cbi-dropdown').forEach(function(s) {
cbi_dropdown_init(s);
});
document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) {
s.parentNode.classList.add('cbi-tooltip-container');
});
document.querySelectorAll('.cbi-section-remove > input[name^="cbi.rts"]').forEach(function(i) {
var handler = function(ev) {
var bits = this.name.split(/\./),
section = document.getElementById('cbi-' + bits[2] + '-' + bits[3]);
section.style.opacity = (ev.type === 'mouseover') ? 0.5 : '';
};
i.addEventListener('mouseover', handler);
i.addEventListener('mouseout', handler);
});
cbi_d_update();
}
@ -825,9 +830,9 @@ function cbi_dynlist_init(parent, datatype, optional, choices)
t.placeholder = holder;
}
var b = document.createElement('img');
b.src = cbi_strings.path.resource + ((i+1) < values.length ? '/cbi/remove.gif' : '/cbi/add.gif');
b.className = 'cbi-image-button';
var b = E('div', {
class: 'cbi-button cbi-button-' + ((i+1) < values.length ? 'remove' : 'add')
}, (i+1) < values.length ? '×' : '+');
parent.appendChild(t);
parent.appendChild(b);
@ -993,8 +998,7 @@ function cbi_dynlist_init(parent, datatype, optional, choices)
input = input.previousSibling;
}
if (se.src.indexOf('remove') > -1)
{
if (se.classList.contains('cbi-button-remove')) {
input.value = '';
cbi_dynlist_keydown({
@ -1002,8 +1006,7 @@ function cbi_dynlist_init(parent, datatype, optional, choices)
keyCode: 8
});
}
else
{
else {
cbi_dynlist_keydown({
target: input,
keyCode: 13
@ -1243,51 +1246,53 @@ function cbi_validate_field(cbid, optional, type)
function cbi_row_swap(elem, up, store)
{
var tr = elem.parentNode;
while (tr && tr.nodeName.toLowerCase() != 'tr')
tr = tr.parentNode;
var tr = findParent(elem.parentNode, '.cbi-section-table-row');
if (!tr)
return false;
var table = tr.parentNode;
while (table && table.nodeName.toLowerCase() != 'table')
table = table.parentNode;
tr.classList.remove('flash');
if (!table)
return false;
if (up) {
var prev = tr.previousElementSibling;
var s = up ? 3 : 2;
var e = up ? table.rows.length : table.rows.length - 1;
if (prev && prev.classList.contains('cbi-section-table-row'))
tr.parentNode.insertBefore(tr, prev);
else
return;
}
else {
var next = tr.nextElementSibling ? tr.nextElementSibling.nextElementSibling : null;
for (var idx = s; idx < e; idx++)
{
if (table.rows[idx] == tr)
{
if (up)
tr.parentNode.insertBefore(table.rows[idx], table.rows[idx-1]);
else
tr.parentNode.insertBefore(table.rows[idx+1], table.rows[idx]);
break;
}
if (next && next.classList.contains('cbi-section-table-row'))
tr.parentNode.insertBefore(tr, next);
else if (!next)
tr.parentNode.appendChild(tr);
else
return;
}
var ids = [ ];
for (idx = 2; idx < table.rows.length; idx++)
{
table.rows[idx].className = table.rows[idx].className.replace(
/cbi-rowstyle-[12]/, 'cbi-rowstyle-' + (1 + (idx % 2))
);
if (table.rows[idx].id && table.rows[idx].id.match(/-([^\-]+)$/) )
ids.push(RegExp.$1);
for (var i = 0, n = 0; i < tr.parentNode.childNodes.length; i++) {
var node = tr.parentNode.childNodes[i];
if (node.classList && node.classList.contains('cbi-section-table-row')) {
node.classList.remove('cbi-rowstyle-1');
node.classList.remove('cbi-rowstyle-2');
node.classList.add((n++ % 2) ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1');
if (/-([^\-]+)$/.test(node.id))
ids.push(RegExp.$1);
}
}
var input = document.getElementById(store);
if (input)
input.value = ids.join(' ');
window.scrollTo(0, tr.offsetTop);
window.setTimeout(function() { tr.classList.add('flash'); }, 1);
return false;
}
@ -1311,56 +1316,26 @@ function cbi_tag_last(container)
}
}
String.prototype.serialize = function()
function cbi_submit(elem, name, value, action)
{
var o = this;
switch(typeof(o))
{
case 'object':
// null
if( o == null )
{
return 'null';
}
var form = elem.form || findParent(elem, 'form');
// array
else if( o.length )
{
var i, s = '';
if (!form)
return false;
for( var i = 0; i < o.length; i++ )
s += (s ? ', ' : '') + String.serialize(o[i]);
if (action)
form.action = action;
return '[ ' + s + ' ]';
}
if (name) {
var hidden = form.querySelector('input[type="hidden"][name="%s"]'.format(name)) ||
E('input', { type: 'hidden', name: name });
// object
else
{
var k, s = '';
for( k in o )
s += (s ? ', ' : '') + k + ': ' + String.serialize(o[k]);
return '{ ' + s + ' }';
}
break;
case 'string':
// complex string
if( o.match(/[^a-zA-Z0-9_,.: -]/) )
return 'decodeURIComponent("' + encodeURIComponent(o) + '")';
// simple string
else
return '"' + o + '"';
break;
default:
return o.toString();
hidden.value = value || '1';
form.appendChild(hidden);
}
form.submit();
return true;
}
String.prototype.format = function()
@ -1473,10 +1448,6 @@ String.prototype.format = function()
subst = esc(param, quot_esc);
break;
case 'j':
subst = String.serialize(param);
break;
case 't':
var td = 0;
var th = 0;
@ -1543,14 +1514,6 @@ String.prototype.nobr = function()
return this.replace(/[\s\n]+/g, '&#160;');
}
String.serialize = function()
{
var a = [ ];
for (var i = 1; i < arguments.length; i++)
a.push(arguments[i]);
return ''.serialize.apply(arguments[0], a);
}
String.format = function()
{
var a = [ ];
@ -1566,3 +1529,640 @@ String.nobr = function()
a.push(arguments[i]);
return ''.nobr.apply(arguments[0], a);
}
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
var dummyElem, domParser;
function isElem(e)
{
return (typeof(e) === 'object' && e !== null && 'nodeType' in e);
}
function toElem(s)
{
var elem;
try {
domParser = domParser || new DOMParser();
elem = domParser.parseFromString(s, 'text/html').body.firstChild;
}
catch(e) {}
if (!elem) {
try {
dummyElem = dummyElem || document.createElement('div');
dummyElem.innerHTML = s;
elem = dummyElem.firstChild;
}
catch (e) {}
}
return elem || null;
}
function findParent(node, selector)
{
while (node)
if (node.msMatchesSelector && node.msMatchesSelector(selector))
return node;
else if (node.matches && node.matches(selector))
return node;
else
node = node.parentNode;
return null;
}
function E()
{
var html = arguments[0],
attr = (arguments[1] instanceof Object && !Array.isArray(arguments[1])) ? arguments[1] : null,
data = attr ? arguments[2] : arguments[1],
elem;
if (isElem(html))
elem = html;
else if (html.charCodeAt(0) === 60)
elem = toElem(html);
else
elem = document.createElement(html);
if (!elem)
return null;
if (attr)
for (var key in attr)
if (attr.hasOwnProperty(key) && attr[key] !== null && attr[key] !== undefined)
elem.setAttribute(key, attr[key]);
if (typeof(data) === 'function')
data = data(elem);
if (isElem(data)) {
elem.appendChild(data);
}
else if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++)
if (isElem(data[i]))
elem.appendChild(data[i]);
else
elem.appendChild(document.createTextNode('' + data[i]));
}
else if (data !== null && data !== undefined) {
elem.innerHTML = '' + data;
}
return elem;
}
if (typeof(window.CustomEvent) !== 'function') {
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
}
CBIDropdown = {
openDropdown: function(sb) {
var st = window.getComputedStyle(sb, null),
ul = sb.querySelector('ul'),
li = ul.querySelectorAll('li'),
sel = ul.querySelector('[selected]'),
rect = sb.getBoundingClientRect(),
h = sb.clientHeight - parseFloat(st.paddingTop) - parseFloat(st.paddingBottom),
mh = this.dropdown_items * h,
eh = Math.min(mh, li.length * h);
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
});
ul.style.maxHeight = mh + 'px';
sb.setAttribute('open', '');
ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight, 0) : 0;
ul.querySelectorAll('[selected] input[type="checkbox"]').forEach(function(c) {
c.checked = true;
});
ul.style.top = ul.style.bottom = '';
ul.style[((sb.getBoundingClientRect().top + eh) > window.innerHeight) ? 'bottom' : 'top'] = rect.height + 'px';
ul.classList.add('dropdown');
var pv = ul.cloneNode(true);
pv.classList.remove('dropdown');
pv.classList.add('preview');
sb.insertBefore(pv, ul.nextElementSibling);
li.forEach(function(l) {
l.setAttribute('tabindex', 0);
});
sb.lastElementChild.setAttribute('tabindex', 0);
this.setFocus(sb, sel || li[0], true);
},
closeDropdown: function(sb, no_focus) {
if (!sb.hasAttribute('open'))
return;
var pv = sb.querySelector('ul.preview'),
ul = sb.querySelector('ul.dropdown'),
li = ul.querySelectorAll('li');
li.forEach(function(l) { l.removeAttribute('tabindex'); });
sb.lastElementChild.removeAttribute('tabindex');
sb.removeChild(pv);
sb.removeAttribute('open');
sb.style.width = sb.style.height = '';
ul.classList.remove('dropdown');
if (!no_focus)
this.setFocus(sb, sb);
this.saveValues(sb, ul);
},
toggleItem: function(sb, li, force_state) {
if (li.hasAttribute('unselectable'))
return;
if (this.multi) {
var cbox = li.querySelector('input[type="checkbox"]'),
items = li.parentNode.querySelectorAll('li'),
label = sb.querySelector('ul.preview'),
sel = li.parentNode.querySelectorAll('[selected]').length,
more = sb.querySelector('.more'),
ndisplay = this.display_items,
n = 0;
if (li.hasAttribute('selected')) {
if (force_state !== true) {
if (sel > 1 || this.optional) {
li.removeAttribute('selected');
cbox.checked = cbox.disabled = false;
sel--;
}
else {
cbox.disabled = true;
}
}
}
else {
if (force_state !== false) {
li.setAttribute('selected', '');
cbox.checked = true;
cbox.disabled = false;
sel++;
}
}
while (label.firstElementChild)
label.removeChild(label.firstElementChild);
for (var i = 0; i < items.length; i++) {
items[i].removeAttribute('display');
if (items[i].hasAttribute('selected')) {
if (ndisplay-- > 0) {
items[i].setAttribute('display', n++);
label.appendChild(items[i].cloneNode(true));
}
var c = items[i].querySelector('input[type="checkbox"]');
if (c)
c.disabled = (sel == 1 && !this.optional);
}
}
if (ndisplay < 0)
sb.setAttribute('more', '');
else
sb.removeAttribute('more');
if (ndisplay === this.display_items)
sb.setAttribute('empty', '');
else
sb.removeAttribute('empty');
more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : '···';
}
else {
var sel = li.parentNode.querySelector('[selected]');
if (sel) {
sel.removeAttribute('display');
sel.removeAttribute('selected');
}
li.setAttribute('display', 0);
li.setAttribute('selected', '');
this.closeDropdown(sb, true);
}
this.saveValues(sb, li.parentNode);
},
transformItem: function(sb, li) {
var cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' })),
label = E('label');
while (li.firstChild)
label.appendChild(li.firstChild);
li.appendChild(cbox);
li.appendChild(label);
},
saveValues: function(sb, ul) {
var sel = ul.querySelectorAll('[selected]'),
div = sb.lastElementChild;
while (div.lastElementChild)
div.removeChild(div.lastElementChild);
sel.forEach(function (s) {
div.appendChild(E('input', {
type: 'hidden',
name: s.hasAttribute('name') ? s.getAttribute('name') : (sb.getAttribute('name') || ''),
value: s.hasAttribute('value') ? s.getAttribute('value') : s.innerText
}));
});
cbi_d_update();
},
setFocus: function(sb, elem, scroll) {
if (sb && sb.hasAttribute && sb.hasAttribute('locked-in'))
return;
document.querySelectorAll('.focus').forEach(function(e) {
if (e.nodeName.toLowerCase() !== 'input') {
e.classList.remove('focus');
e.blur();
}
});
if (elem) {
elem.focus();
elem.classList.add('focus');
if (scroll)
elem.parentNode.scrollTop = elem.offsetTop - elem.parentNode.offsetTop;
}
},
createItems: function(sb, value) {
var sbox = this,
val = (value || '').trim().split(/\s+/),
ul = sb.querySelector('ul');
if (!sbox.multi)
val.length = Math.min(val.length, 1);
val.forEach(function(item) {
var new_item = null;
ul.childNodes.forEach(function(li) {
if (li.getAttribute && li.getAttribute('value') === item)
new_item = li;
});
if (!new_item) {
var markup,
tpl = sb.querySelector(sbox.template);
if (tpl)
markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^<!--|-->$/, '').trim();
else
markup = '<li value="{{value}}">{{value}}</li>';
new_item = E(markup.replace(/{{value}}/g, item));
if (sbox.multi) {
sbox.transformItem(sb, new_item);
}
else {
var old = ul.querySelector('li[created]');
if (old)
ul.removeChild(old);
new_item.setAttribute('created', '');
}
new_item = ul.insertBefore(new_item, ul.lastElementChild);
}
sbox.toggleItem(sb, new_item, true);
sbox.setFocus(sb, new_item, true);
});
},
closeAllDropdowns: function() {
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
});
}
};
function cbi_dropdown_init(sb) {
if (!(this instanceof cbi_dropdown_init))
return new cbi_dropdown_init(sb);
this.multi = sb.hasAttribute('multiple');
this.optional = sb.hasAttribute('optional');
this.placeholder = sb.getAttribute('placeholder') || '---';
this.display_items = parseInt(sb.getAttribute('display-items') || 3);
this.dropdown_items = parseInt(sb.getAttribute('dropdown-items') || 5);
this.create = sb.getAttribute('item-create') || '.create-item-input';
this.template = sb.getAttribute('item-template') || 'script[type="item-template"]';
var sbox = this,
ul = sb.querySelector('ul'),
items = ul.querySelectorAll('li'),
more = sb.appendChild(E('span', { class: 'more', tabindex: -1 }, '···')),
open = sb.appendChild(E('span', { class: 'open', tabindex: -1 }, '▾')),
canary = sb.appendChild(E('div')),
create = sb.querySelector(this.create),
ndisplay = this.display_items,
n = 0;
if (this.multi) {
for (var i = 0; i < items.length; i++) {
sbox.transformItem(sb, items[i]);
if (items[i].hasAttribute('selected') && ndisplay-- > 0)
items[i].setAttribute('display', n++);
}
}
else {
var sel = sb.querySelectorAll('[selected]');
sel.forEach(function(s) {
s.removeAttribute('selected');
});
var s = sel[0] || items[0];
if (s) {
s.setAttribute('selected', '');
s.setAttribute('display', n++);
}
ndisplay--;
if (this.optional && !ul.querySelector('li[value=""]')) {
var placeholder = E('li', { placeholder: '' }, this.placeholder);
ul.firstChild ? ul.insertBefore(placeholder, ul.firstChild) : ul.appendChild(placeholder);
}
}
sbox.saveValues(sb, ul);
ul.setAttribute('tabindex', -1);
sb.setAttribute('tabindex', 0);
if (ndisplay < 0)
sb.setAttribute('more', '')
else
sb.removeAttribute('more');
if (ndisplay === this.display_items)
sb.setAttribute('empty', '')
else
sb.removeAttribute('empty');
more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : '···';
sb.addEventListener('click', function(ev) {
if (!this.hasAttribute('open')) {
if (ev.target.nodeName.toLowerCase() !== 'input')
sbox.openDropdown(this);
}
else {
var li = findParent(ev.target, 'li');
if (li && li.parentNode.classList.contains('dropdown'))
sbox.toggleItem(this, li);
}
ev.preventDefault();
ev.stopPropagation();
});
sb.addEventListener('keydown', function(ev) {
if (ev.target.nodeName.toLowerCase() === 'input')
return;
if (!this.hasAttribute('open')) {
switch (ev.keyCode) {
case 37:
case 38:
case 39:
case 40:
sbox.openDropdown(this);
ev.preventDefault();
}
}
else
{
var active = findParent(document.activeElement, 'li');
switch (ev.keyCode) {
case 27:
sbox.closeDropdown(this);
break;
case 13:
if (active) {
if (!active.hasAttribute('selected'))
sbox.toggleItem(this, active);
sbox.closeDropdown(this);
ev.preventDefault();
}
break;
case 32:
if (active) {
sbox.toggleItem(this, active);
ev.preventDefault();
}
break;
case 38:
if (active && active.previousElementSibling) {
sbox.setFocus(this, active.previousElementSibling);
ev.preventDefault();
}
break;
case 40:
if (active && active.nextElementSibling) {
sbox.setFocus(this, active.nextElementSibling);
ev.preventDefault();
}
break;
}
}
});
sb.addEventListener('cbi-dropdown-close', function(ev) {
sbox.closeDropdown(this, true);
});
if ('ontouchstart' in window) {
sb.addEventListener('touchstart', function(ev) { ev.stopPropagation(); });
window.addEventListener('touchstart', sbox.closeAllDropdowns);
}
else {
sb.addEventListener('mouseover', function(ev) {
if (!this.hasAttribute('open'))
return;
var li = findParent(ev.target, 'li');
if (li) {
if (li.parentNode.classList.contains('dropdown'))
sbox.setFocus(this, li);
ev.stopPropagation();
}
});
sb.addEventListener('focus', function(ev) {
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
if (s !== this || this.hasAttribute('open'))
s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
});
});
canary.addEventListener('focus', function(ev) {
sbox.closeDropdown(this.parentNode);
});
window.addEventListener('mouseover', sbox.setFocus);
window.addEventListener('click', sbox.closeAllDropdowns);
}
if (create) {
create.addEventListener('keydown', function(ev) {
switch (ev.keyCode) {
case 13:
sbox.createItems(sb, this.value);
ev.preventDefault();
this.value = '';
this.blur();
break;
}
});
create.addEventListener('focus', function(ev) {
var cbox = findParent(this, 'li').querySelector('input[type="checkbox"]');
if (cbox) cbox.checked = true;
sb.setAttribute('locked-in', '');
});
create.addEventListener('blur', function(ev) {
var cbox = findParent(this, 'li').querySelector('input[type="checkbox"]');
if (cbox) cbox.checked = false;
sb.removeAttribute('locked-in');
});
var li = findParent(create, 'li');
li.setAttribute('unselectable', '');
li.addEventListener('click', function(ev) {
this.querySelector(sbox.create).focus();
});
}
}
cbi_dropdown_init.prototype = CBIDropdown;
function cbi_update_table(table, data, placeholder) {
target = isElem(table) ? table : document.querySelector(table);
if (!isElem(target))
return;
target.querySelectorAll('.tr.table-titles, .cbi-section-table-titles').forEach(function(thead) {
var titles = [];
thead.querySelectorAll('.th').forEach(function(th) {
titles.push(th);
});
if (Array.isArray(data)) {
var n = 0, rows = target.querySelectorAll('.tr');
data.forEach(function(row) {
var trow = E('div', { 'class': 'tr' });
for (var i = 0; i < titles.length; i++) {
var text = (titles[i].innerText || '').trim();
var td = trow.appendChild(E('div', {
'class': titles[i].className,
'data-title': (text !== '') ? text : null
}, row[i] || ''));
td.classList.remove('th');
td.classList.add('td');
}
trow.classList.add('cbi-rowstyle-%d'.format((n++ % 2) ? 2 : 1));
if (rows[n])
target.replaceChild(trow, rows[n]);
else
target.appendChild(trow);
});
while (rows[++n])
target.removeChild(rows[n]);
if (placeholder && target.firstElementChild === target.lastElementChild) {
var trow = target.appendChild(E('div', { 'class': 'tr placeholder' }));
var td = trow.appendChild(E('div', { 'class': titles[0].className }, placeholder));
td.classList.remove('th');
td.classList.add('td');
}
}
else {
thead.parentNode.style.display = 'none';
thead.parentNode.querySelectorAll('.tr, .cbi-section-table-row').forEach(function(trow) {
if (trow !== thead) {
var n = 0;
trow.querySelectorAll('.th, .td').forEach(function(td) {
if (n < titles.length) {
var text = (titles[n++].innerText || '').trim();
if (text !== '')
td.setAttribute('data-title', text);
}
});
}
});
thead.parentNode.style.display = '';
}
});
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.table').forEach(cbi_update_table);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -43,6 +43,7 @@ XHR = function()
{
this.reinit();
var ts = Date.now();
var xhr = this._xmlHttp;
var code = this._encode(data);
@ -64,15 +65,11 @@ XHR = function()
if (xhr.readyState == 4) {
var json = null;
if (xhr.getResponseHeader("Content-Type") == "application/json") {
try {
json = eval('(' + xhr.responseText + ')');
}
catch(e) {
json = null;
}
try { json = JSON.parse(xhr.responseText); }
catch(e) { json = null; }
}
callback(xhr, json);
callback(xhr, json, Date.now() - ts);
}
}
@ -83,13 +80,21 @@ XHR = function()
{
this.reinit();
var ts = Date.now();
var xhr = this._xmlHttp;
var code = this._encode(data);
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
callback(xhr);
if (xhr.readyState == 4) {
var json = null;
if (xhr.getResponseHeader("Content-Type") == "application/json") {
try { json = JSON.parse(xhr.responseText); }
catch(e) { json = null; }
}
callback(xhr, json, Date.now() - ts);
}
}
xhr.open('POST', url, true);
@ -188,7 +193,7 @@ XHR.poll = function(interval, url, data, callback, post)
for (var i = 0, e = XHR._q[0]; i < XHR._q.length; e = XHR._q[++i])
{
if (!(XHR._t % e.interval) && !e.xhr.busy())
e.xhr[post ? 'post' : 'get'](e.url, e.data, e.callback, e.interval * 1000 - 5);
e.xhr[post ? 'post' : 'get'](e.url, e.data, e.callback, e.interval * 1000 * 5 - 5);
}
XHR._t++;
@ -204,7 +209,6 @@ XHR.poll = function(interval, url, data, callback, post)
};
XHR._q.push(e);
XHR.run();
return e;
}
@ -260,3 +264,5 @@ XHR.running = function()
{
return !!(XHR._r && XHR._i);
}
document.addEventListener('DOMContentLoaded', XHR.run);

View file

@ -388,21 +388,21 @@ function Map.parse(self, readinput, ...)
if self.save then
self:_run_hooks("on_save", "on_before_save")
local i, config
for i, config in ipairs(self.parsechain) do
self.uci:save(config)
end
self:_run_hooks("on_after_save")
if (not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply") then
self:_run_hooks("on_before_commit")
for i, config in ipairs(self.parsechain) do
self.uci:commit(config)
-- Refresh data because commit changes section names
self.uci:load(config)
if self.apply_on_parse == false then
for i, config in ipairs(self.parsechain) do
self.uci:commit(config)
end
end
self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
if self.apply_on_parse then
self.uci:apply(self.parsechain)
if self.apply_on_parse == true or self.apply_on_parse == false then
self.uci:apply(self.apply_on_parse)
self:_run_hooks("on_apply", "on_after_apply")
else
-- This is evaluated by the dispatcher and delegated to the
@ -1226,13 +1226,14 @@ function TypedSection.parse(self, novld)
local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
local order = self.map:formvalue(stval)
if order and #order > 0 then
local sid
local num = 0
local sids, sid = { }, nil
for sid in util.imatch(order) do
self.map.uci:reorder(self.config, sid, num)
num = num + 1
sids[#sids+1] = sid
end
if #sids > 0 then
self.map.uci:reorder(self.config, sids)
self.changed = true
end
self.changed = (num > 0)
end
end
@ -1416,6 +1417,12 @@ function AbstractValue.parse(self, section, novld)
self:add_error(section, "invalid", val_err)
end
if self.alias then
self.section.aliased = self.section.aliased or {}
self.section.aliased[section] = self.section.aliased[section] or {}
self.section.aliased[section][self.alias] = true
end
if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
if self:write(section, fvalue) then
-- Push events
@ -1425,10 +1432,16 @@ function AbstractValue.parse(self, section, novld)
end
else -- Unset the UCI or error
if self.rmempty or self.optional then
if self:remove(section) then
-- Push events
self.section.changed = true
--luci.util.append(self.map.events, self.events)
if not self.alias or
not self.section.aliased or
not self.section.aliased[section] or
not self.section.aliased[section][self.alias]
then
if self:remove(section) then
-- Push events
self.section.changed = true
--luci.util.append(self.map.events, self.events)
end
end
elseif cvalue ~= fvalue and not novld then
-- trigger validator with nil value to get custom user error msg.
@ -1454,7 +1467,7 @@ function AbstractValue.cfgvalue(self, section)
if self.tag_error[section] then
value = self:formvalue(section)
else
value = self.map:get(section, self.option)
value = self.map:get(section, self.alias or self.option)
end
if not value then
@ -1495,12 +1508,12 @@ AbstractValue.transform = AbstractValue.validate
-- Write to UCI
function AbstractValue.write(self, section, value)
return self.map:set(section, self.option, value)
return self.map:set(section, self.alias or self.option, value)
end
-- Remove from UCI
function AbstractValue.remove(self, section)
return self.map:del(section, self.option)
return self.map:del(section, self.alias or self.option)
end
@ -1833,6 +1846,15 @@ function DynamicList.formvalue(self, section)
end
DropDown = class(MultiValue)
function DropDown.__init__(self, ...)
ListValue.__init__(self, ...)
self.template = "cbi/dropdown"
self.delimiter = " "
end
--[[
TextValue - A multi-line value
rows: Rows

View file

@ -1,49 +0,0 @@
-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.controller.admin.servicectl", package.seeall)
function index()
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "root"
entry({"servicectl", "status"}, call("action_status")).leaf = true
entry({"servicectl", "restart"}, post("action_restart")).leaf = true
end
function action_status()
local data = nixio.fs.readfile("/var/run/luci-reload-status")
if data then
luci.http.write("/etc/config/")
luci.http.write(data)
else
luci.http.write("finish")
end
end
function action_restart(args)
local uci = require "luci.model.uci".cursor()
if args then
local service
local services = { }
for service in args:gmatch("[%w_-]+") do
services[#services+1] = service
end
local command = uci:apply(services, true)
if nixio.fork() == 0 then
local i = nixio.open("/dev/null", "r")
local o = nixio.open("/dev/null", "w")
nixio.dup(i, nixio.stdin)
nixio.dup(o, nixio.stdout)
i:close()
o:close()
nixio.exec("/bin/sh", unpack(command))
else
luci.http.write("OK")
os.exit(0)
end
end
end

View file

@ -182,6 +182,7 @@ local function session_retrieve(sid, allowed_users)
(not allowed_users or
util.contains(allowed_users, sdat.values.username))
then
uci:set_session_id(sid)
return sid, sdat.values
end
@ -357,7 +358,7 @@ function dispatch(request)
elseif key == "REQUEST_URI" then
return build_url(unpack(ctx.requestpath))
elseif key == "FULL_REQUEST_URI" then
local url = { http.getenv("SCRIPT_NAME"), http.getenv("PATH_INFO") }
local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
local query = http.getenv("QUERY_STRING")
if query and #query > 0 then
url[#url+1] = "?"
@ -428,7 +429,9 @@ function dispatch(request)
return
end
http.header("Set-Cookie", 'sysauth=%s; path=%s' %{ sid, build_url() })
http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
})
http.redirect(build_url(unpack(ctx.requestpath)))
end
@ -504,10 +507,11 @@ function dispatch(request)
else
ok, err = util.copcall(target, unpack(args))
end
assert(ok,
"Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
" dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
if not ok then
error500("Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
" dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
end
else
local root = node()
if not root or not root.target then
@ -699,15 +703,22 @@ function _create_node(path)
local last = table.remove(path)
local parent = _create_node(path)
c = {nodes={}, auto=true}
-- the node is "in request" if the request path matches
-- at least up to the length of the node path
if parent.inreq and context.path[#path+1] == last then
c.inreq = true
c = {nodes={}, auto=true, inreq=true}
local _, n
for _, n in ipairs(path) do
if context.path[_] ~= n then
c.inreq = false
break
end
end
c.inreq = c.inreq and (context.path[#path + 1] == last)
parent.nodes[last] = c
context.treecache[name] = c
end
return c
end
@ -908,7 +919,6 @@ local function _cbi(self, ...)
for i, res in ipairs(maps) do
res:render({
firstmap = (i == 1),
applymap = applymap,
redirect = redirect,
messages = messages,
pageaction = pageaction,
@ -918,11 +928,12 @@ local function _cbi(self, ...)
if not config.nofooter then
tpl.render("cbi/footer", {
flow = config,
pageaction = pageaction,
redirect = redirect,
state = state,
autoapply = config.autoapply
flow = config,
pageaction = pageaction,
redirect = redirect,
state = state,
autoapply = config.autoapply,
trigger_apply = applymap
})
end
end

View file

@ -14,7 +14,7 @@ local table, ipairs, pairs, type, tostring, tonumber, error =
module "luci.http"
HTTP_MAX_CONTENT = 1024*8 -- 8 kB maximum content size
HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size
context = util.threadlocal()
@ -416,7 +416,7 @@ function mimedecode_message_body(src, msg, file_cb)
end
return true
end)
end, HTTP_MAX_CONTENT)
return ltn12.pump.all(src, function (chunk)
len = len + (chunk and #chunk or 0)
@ -460,7 +460,7 @@ function urldecode_message_body(src, msg)
end
return true
end)
end, HTTP_MAX_CONTENT)
return ltn12.pump.all(src, function (chunk)
len = len + (chunk and #chunk or 0)

View file

@ -23,6 +23,22 @@ IFACE_PATTERNS_VIRTUAL = { }
IFACE_PATTERNS_IGNORE = { "^wmaster%d", "^wifi%d", "^hwsim%d", "^imq%d", "^ifb%d", "^mon%.wlan%d", "^sit%d", "^gre%d", "^gretap%d", "^ip6gre%d", "^ip6tnl%d", "^tunl%d", "^lo$" }
IFACE_PATTERNS_WIRELESS = { "^wlan%d", "^wl%d", "^ath%d", "^%w+%.network%d" }
IFACE_ERRORS = {
CONNECT_FAILED = lng.translate("Connection attempt failed"),
INVALID_ADDRESS = lng.translate("IP address in invalid"),
INVALID_GATEWAY = lng.translate("Gateway address is invalid"),
INVALID_LOCAL_ADDRESS = lng.translate("Local IP address is invalid"),
MISSING_ADDRESS = lng.translate("IP address is missing"),
MISSING_PEER_ADDRESS = lng.translate("Peer address is missing"),
NO_DEVICE = lng.translate("Network device is not present"),
NO_IFACE = lng.translate("Unable to determine device name"),
NO_IFNAME = lng.translate("Unable to determine device name"),
NO_WAN_ADDRESS = lng.translate("Unable to determine external IP address"),
NO_WAN_LINK = lng.translate("Unable to determine upstream interface"),
PEER_RESOLVE_FAIL = lng.translate("Unable to resolve peer host name"),
PIN_FAILED = lng.translate("PIN code rejected")
}
protocol = utl.class()
@ -495,6 +511,17 @@ function register_pattern_virtual(self, pat)
IFACE_PATTERNS_VIRTUAL[#IFACE_PATTERNS_VIRTUAL+1] = pat
end
function register_error_code(self, code, message)
if type(code) == "string" and
type(message) == "string" and
not IFACE_ERRORS[code]
then
IFACE_ERRORS[code] = message
return true
end
return false
end
function has_ipv6(self)
return nfs.access("/proc/net/ipv6_route")
@ -520,6 +547,13 @@ end
function get_network(self, n)
if n and _uci:get("network", n) == "interface" then
return network(n)
elseif n then
local stat = utl.ubus("network.interface", "status", { interface = n })
if type(stat) == "table" and
type(stat.proto) == "string"
then
return network(n, stat.proto)
end
end
end
@ -532,6 +566,23 @@ function get_networks(self)
nls[s['.name']] = network(s['.name'])
end)
local dump = utl.ubus("network.interface", "dump", { })
if type(dump) == "table" and
type(dump.interface) == "table"
then
local _, net
for _, net in ipairs(dump.interface) do
if type(net) == "table" and
type(net.proto) == "string" and
type(net.interface) == "string"
then
if not nls[net.interface] then
nls[net.interface] = network(net.interface, net.proto)
end
end
end
end
local n
for n in utl.kspairs(nls) do
nets[#nets+1] = nls[n]
@ -929,6 +980,16 @@ function protocol.metric(self)
return self:_ubus("metric") or 0
end
function protocol.zonename(self)
local d = self:_ubus("data")
if type(d) == "table" and type(d.zone) == "string" then
return d.zone
end
return nil
end
function protocol.ipaddr(self)
local addrs = self:_ubus("ipv4-address")
return addrs and #addrs > 0 and addrs[1].address
@ -1043,6 +1104,22 @@ function protocol.ip6prefix(self)
end
end
function protocol.errors(self)
local _, err, rv
local errors = self:_ubus("errors")
if type(errors) == "table" then
for _, err in ipairs(errors) do
if type(err) == "table" and
type(err.code) == "string"
then
rv = rv or { }
rv[#rv+1] = IFACE_ERRORS[err.code] or lng.translatef("Unknown error (%s)", err.code)
end
end
end
return rv
end
function protocol.is_bridge(self)
return (not self:is_virtual() and self:type() == "bridge")
end
@ -1063,6 +1140,24 @@ function protocol.is_floating(self)
return false
end
function protocol.is_dynamic(self)
return (self:_ubus("dynamic") == true)
end
function protocol.is_alias(self)
local ifn, parent = nil, nil
for ifn in utl.imatch(_uci:get("network", self.sid, "ifname")) do
if #ifn > 1 and ifn:byte(1) == 64 then
parent = ifn:sub(2)
elseif parent ~= nil then
parent = nil
end
end
return parent
end
function protocol.is_empty(self)
if self:is_floating() then
return false
@ -1081,6 +1176,10 @@ function protocol.is_empty(self)
end
end
function protocol.is_up(self)
return (self:_ubus("up") == true)
end
function protocol.add_interface(self, ifname)
ifname = _M:ifnameof(ifname)
if ifname and not self:is_floating() then
@ -1116,12 +1215,16 @@ function protocol.get_interface(self)
_bridge["br-" .. self.sid] = true
return interface("br-" .. self.sid, self)
else
local ifn = nil
local num = { }
local ifn = self:_ubus("l3_device") or self:_ubus("device")
if ifn then
return interface(ifn, self)
end
for ifn in utl.imatch(_uci:get("network", self.sid, "ifname")) do
ifn = ifn:match("^[^:/]+")
return ifn and interface(ifn, self)
end
ifn = _wifi_netid_by_netname(self.sid)
return ifn and interface(ifn, self)
end
@ -1245,7 +1348,9 @@ function interface.ip6addrs(self)
end
function interface.type(self)
if self.wif or _wifi_iface(self.ifname) then
if self.ifname and self.ifname:byte(1) == 64 then
return "alias"
elseif self.wif or _wifi_iface(self.ifname) then
return "wifi"
elseif _bridge[self.ifname] then
return "bridge"
@ -1273,7 +1378,7 @@ function interface.get_i18n(self)
return "%s: %s %q" %{
lng.translate("Wireless Network"),
self.wif:active_mode(),
self.wif:active_ssid() or self.wif:active_bssid() or self.wif:id()
self.wif:active_ssid() or self.wif:active_bssid() or self.wif:id() or "?"
}
else
return "%s: %q" %{ self:get_type_i18n(), self:name() }
@ -1282,7 +1387,9 @@ end
function interface.get_type_i18n(self)
local x = self:type()
if x == "wifi" then
if x == "alias" then
return lng.translate("Alias Interface")
elseif x == "wifi" then
return lng.translate("Wireless Adapter")
elseif x == "bridge" then
return lng.translate("Bridge")
@ -1335,7 +1442,11 @@ function interface.bridge_stp(self)
end
function interface.is_up(self)
return self:_ubus("up") or false
local up = self:_ubus("up")
if up == nil then
up = (self:type() == "alias")
end
return up or false
end
function interface.is_bridge(self)
@ -1428,7 +1539,7 @@ function wifidev.hwmodes(self)
end
function wifidev.get_i18n(self)
local t = "Generic"
local t = self.iwinfo.hardware_name or "Generic"
if self.iwinfo.type == "wl" then
t = "Broadcom"
end
@ -1601,7 +1712,7 @@ end
function wifinet.ifname(self)
local ifname = self:ubus("net", "ifname") or self.iwinfo.ifname
if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then
ifname = self.wdev
ifname = self.netid
end
return ifname
end

View file

@ -8,7 +8,7 @@ local table = require "table"
local setmetatable, rawget, rawset = setmetatable, rawget, rawset
local require, getmetatable, assert = require, getmetatable, assert
local error, pairs, ipairs = error, pairs, ipairs
local error, pairs, ipairs, select = error, pairs, ipairs, select
local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
-- The typical workflow for UCI is: Get a cursor instance from the
@ -106,7 +106,7 @@ function changes(self, config)
local _, change
for _, change in ipairs(changes) do
local operation, section, option, value = unpack(change)
if option and value and operation ~= "add" then
if option and operation ~= "add" then
res[package][section] = res[package][section] or { }
if operation == "list-add" then
@ -143,22 +143,129 @@ function commit(self, config)
return (err == nil), ERRSTR[err]
end
--[[
function apply(self, configs, command)
local _, config
function apply(self, rollback)
local _, err
assert(not command, "Apply command not supported anymore")
if rollback then
local sys = require "luci.sys"
local conf = require "luci.config"
local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 30) or 0
if type(configs) == "table" then
for _, config in ipairs(configs) do
call("service", "event", {
type = "config.change",
data = { package = config }
_, err = call("apply", {
timeout = (timeout > 30) and timeout or 30,
rollback = true
})
if not err then
local now = os.time()
local token = sys.uniqueid(16)
util.ubus("session", "set", {
ubus_rpc_session = "00000000000000000000000000000000",
values = {
rollback = {
token = token,
session = session_id,
timeout = now + timeout
}
}
})
return token
end
else
_, err = call("changes", {})
if not err then
if type(_) == "table" and type(_.changes) == "table" then
local k, v
for k, v in pairs(_.changes) do
_, err = call("commit", { config = k })
if err then
break
end
end
end
end
if not err then
_, err = call("apply", { rollback = false })
end
end
return (err == nil), ERRSTR[err]
end
function confirm(self, token)
local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending()
if is_pending then
if token ~= rollback_token then
return false, "Permission denied"
end
local _, err = util.ubus("uci", "confirm", {
ubus_rpc_session = rollback_sid
})
if not err then
util.ubus("session", "set", {
ubus_rpc_session = "00000000000000000000000000000000",
values = { rollback = {} }
})
end
return (err == nil), ERRSTR[err]
end
return false, "No data"
end
function rollback(self)
local is_pending, time_remaining, rollback_sid = self:rollback_pending()
if is_pending then
local _, err = util.ubus("uci", "rollback", {
ubus_rpc_session = rollback_sid
})
if not err then
util.ubus("session", "set", {
ubus_rpc_session = "00000000000000000000000000000000",
values = { rollback = {} }
})
end
return (err == nil), ERRSTR[err]
end
return false, "No data"
end
function rollback_pending(self)
local rv, err = util.ubus("session", "get", {
ubus_rpc_session = "00000000000000000000000000000000",
keys = { "rollback" }
})
local now = os.time()
if type(rv) == "table" and
type(rv.values) == "table" and
type(rv.values.rollback) == "table" and
type(rv.values.rollback.token) == "string" and
type(rv.values.rollback.session) == "string" and
type(rv.values.rollback.timeout) == "number" and
rv.values.rollback.timeout > now
then
return true,
rv.values.rollback.timeout - now,
rv.values.rollback.session,
rv.values.rollback.token
end
return false, ERRSTR[err]
end
]]
function foreach(self, config, stype, callback)
@ -310,15 +417,15 @@ function add(self, config, stype)
return self:section(config, stype)
end
function set(self, config, section, option, value)
if value == nil then
function set(self, config, section, option, ...)
if select('#', ...) == 0 then
local sname, err = self:section(config, option, section)
return (not not sname), err
else
local _, err = call("set", {
config = config,
section = section,
values = { [option] = value }
values = { [option] = select(1, ...) }
})
return (err == nil), ERRSTR[err]
end
@ -425,59 +532,3 @@ function delete_all(self, config, stype, comparator)
return (err == nil), ERRSTR[err]
end
function apply(self, configlist, command)
configlist = self:_affected(configlist)
if command then
return { "/sbin/luci-reload", unpack(configlist) }
else
return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
% util.shellquote(table.concat(configlist, " ")))
end
end
-- Return a list of initscripts affected by configuration changes.
function _affected(self, configlist)
configlist = type(configlist) == "table" and configlist or { configlist }
-- Resolve dependencies
local reloadlist = { }
local function _resolve_deps(name)
local reload = { name }
local deps = { }
self:foreach("ucitrack", name,
function(section)
if section.affects then
for i, aff in ipairs(section.affects) do
deps[#deps+1] = aff
end
end
end)
local i, dep
for i, dep in ipairs(deps) do
local j, add
for j, add in ipairs(_resolve_deps(dep)) do
reload[#reload+1] = add
end
end
return reload
end
-- Collect initscripts
local j, config
for j, config in ipairs(configlist) do
local i, e
for i, e in ipairs(_resolve_deps(config)) do
if not util.contains(reloadlist, e) then
reloadlist[#reloadlist+1] = e
end
end
end
return reloadlist
end

View file

@ -28,12 +28,63 @@ Create a new Cursor initialized to the state directory.
]]
---[[
Applies UCI configuration changes
Applies UCI configuration changes.
If the rollback parameter is set to true, the apply function will invoke the
rollback mechanism which causes the configuration to be automatically reverted
if no confirm() call occurs within a certain timeout.
The current default timeout is 30s and can be increased using the
"luci.apply.timeout" uci configuration key.
@class function
@name Cursor.apply
@param configlist List of UCI configurations
@param command Don't apply only return the command
@param rollback Enable rollback mechanism
@return Boolean whether operation succeeded
]]
---[[
Confirms UCI apply process.
If a previous UCI apply with rollback has been invoked using apply(true),
this function confirms the process and cancels the pending rollback timer.
If no apply with rollback session is active, the function has no effect and
returns with a "No data" error.
@class function
@name Cursor.confirm
@return Boolean whether operation succeeded
]]
---[[
Cancels UCI apply process.
If a previous UCI apply with rollback has been invoked using apply(true),
this function cancels the process and rolls back the configuration to the
pre-apply state.
If no apply with rollback session is active, the function has no effect and
returns with a "No data" error.
@class function
@name Cursor.rollback
@return Boolean whether operation succeeded
]]
---[[
Checks whether a pending rollback is scheduled.
If a previous UCI apply with rollback has been invoked using apply(true),
and has not been confirmed or rolled back yet, this function returns true
and the remaining time until rollback in seconds. If no rollback is pending,
the function returns false. On error, the function returns false and an
additional string describing the error.
@class function
@name Cursor.rollback_pending
@return Boolean whether rollback is pending
@return Remaining time in seconds
]]
---[[

View file

@ -6,9 +6,25 @@ module("luci.tools.status", package.seeall)
local uci = require "luci.model.uci".cursor()
local ipc = require "luci.ip"
local function duid_to_mac(duid)
local b1, b2, b3, b4, b5, b6
-- DUID-LLT / Ethernet
if type(duid) == "string" and #duid == 28 then
b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$")
-- DUID-LL / Ethernet
elseif type(duid) == "string" and #duid == 20 then
b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
end
return b1 and ipc.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":"))
end
local function dhcp_leases_common(family)
local rv = { }
local nfs = require "nixio.fs"
local sys = require "luci.sys"
local leasefile = "/tmp/dhcp.leases"
uci:foreach("dhcp", "dnsmasq",
@ -87,6 +103,22 @@ local function dhcp_leases_common(family)
fd:close()
end
if family == 6 then
local _, lease
local hosts = sys.net.host_hints()
for _, lease in ipairs(rv) do
local mac = duid_to_mac(lease.duid)
local host = mac and hosts[mac]
if host then
if not lease.name then
lease.host_hint = host.name or host.ipv4 or host.ipv6
elseif host.name and lease.hostname ~= host.name then
lease.host_hint = host.name
end
end
end
end
return rv
end
@ -113,6 +145,11 @@ function wifi_networks()
local net
for _, net in ipairs(dev:get_wifinets()) do
local a, an = nil, 0
for _, a in pairs(net:assoclist() or {}) do
an = an + 1
end
rd.networks[#rd.networks+1] = {
name = net:shortname(),
link = net:adminlink(),
@ -128,10 +165,10 @@ function wifi_networks()
noise = net:noise(),
bitrate = net:bitrate(),
ifname = net:ifname(),
assoclist = net:assoclist(),
country = net:country(),
txpower = net:txpower(),
txpoweroff = net:txpower_offset(),
num_assoc = an,
disabled = (dev:get("disabled") == "1" or
net:get("disabled") == "1")
}
@ -165,7 +202,6 @@ function wifi_network(id)
noise = net:noise(),
bitrate = net:bitrate(),
ifname = net:ifname(),
assoclist = net:assoclist(),
country = net:country(),
txpower = net:txpower(),
txpoweroff = net:txpower_offset(),
@ -182,6 +218,52 @@ function wifi_network(id)
return { }
end
function wifi_assoclist()
local sys = require "luci.sys"
local ntm = require "luci.model.network".init()
local hosts = sys.net.host_hints()
local assoc = {}
local _, dev, net, bss
for _, dev in ipairs(ntm:get_wifidevs()) do
local radioname = dev:get_i18n()
for _, net in ipairs(dev:get_wifinets()) do
local netname = net:shortname()
local netlink = net:adminlink()
local ifname = net:ifname()
for _, bss in pairs(net:assoclist() or {}) do
local host = hosts[_]
bss.bssid = _
bss.ifname = ifname
bss.radio = radioname
bss.name = netname
bss.link = netlink
bss.host_name = (host) and (host.name or host.ipv4 or host.ipv6)
bss.host_hint = (host and host.name and (host.ipv4 or host.ipv6)) and (host.ipv4 or host.ipv6)
assoc[#assoc+1] = bss
end
end
end
table.sort(assoc, function(a, b)
if a.radio ~= b.radio then
return a.radio < b.radio
elseif a.ifname ~= b.ifname then
return a.ifname < b.ifname
else
return a.bssid < b.bssid
end
end)
return assoc
end
function switch_status(devs)
local dev
local switches = { }

View file

@ -16,7 +16,7 @@ local _ubus = require "ubus"
local _ubus_connection = nil
local getmetatable, setmetatable = getmetatable, setmetatable
local rawget, rawset, unpack = rawget, rawset, unpack
local rawget, rawset, unpack, select = rawget, rawset, unpack, select
local tostring, type, assert, error = tostring, type, assert, error
local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring
local require, pcall, xpcall = require, pcall, xpcall
@ -100,6 +100,8 @@ end
-- Scope manipulation routines
--
coxpt = setmetatable({}, { __mode = "kv" })
local tl_meta = {
__mode = "k",
@ -645,6 +647,17 @@ local ubus_codes = {
"CONNECTION_FAILED"
}
local function ubus_return(...)
if select('#', ...) == 2 then
local rv, err = select(1, ...), select(2, ...)
if rv == nil and type(err) == "number" then
return nil, err, ubus_codes[err]
end
end
return ...
end
function ubus(object, method, data)
if not _ubus_connection then
_ubus_connection = _ubus.connect()
@ -655,8 +668,7 @@ function ubus(object, method, data)
if type(data) ~= "table" then
data = { }
end
local rv, err = _ubus_connection:call(object, method, data)
return rv, err, ubus_codes[err]
return ubus_return(_ubus_connection:call(object, method, data))
elseif object then
return _ubus_connection:signatures(object)
else
@ -697,73 +709,69 @@ function checklib(fullpathexe, wantedlib)
return false
end
-------------------------------------------------------------------------------
-- Coroutine safe xpcall and pcall versions
--
-- Coroutine safe xpcall and pcall versions modified for Luci
-- original version:
-- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
-- Encapsulates the protected calls with a coroutine based loop, so errors can
-- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines
-- yielding inside the call to pcall or xpcall.
--
-- Copyright © 2005 Kepler Project.
-- 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:
-- Authors: Roberto Ierusalimschy and Andre Carregal
-- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
-- Copyright 2005 - Kepler Project
--
-- 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.
-- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $
-------------------------------------------------------------------------------
local performResume, handleReturnValue
local oldpcall, oldxpcall = pcall, xpcall
coxpt = {}
setmetatable(coxpt, {__mode = "kv"})
-------------------------------------------------------------------------------
-- Implements xpcall with coroutines
-------------------------------------------------------------------------------
local coromap = setmetatable({}, { __mode = "k" })
-- Identity function for copcall
local function copcall_id(trace, ...)
return ...
end
-- values of either the function or the error handler
function coxpcall(f, err, ...)
local res, co = oldpcall(coroutine.create, f)
if not res then
local params = {...}
local newf = function() return f(unpack(params)) end
co = coroutine.create(newf)
end
local c = coroutine.running()
coxpt[co] = coxpt[c] or c or 0
return performResume(err, co, ...)
end
-- values of the function or the error object
function copcall(f, ...)
return coxpcall(f, copcall_id, ...)
end
-- Handle return value of protected call
function handleReturnValue(err, co, status, ...)
local function handleReturnValue(err, co, status, ...)
if not status then
return false, err(debug.traceback(co, (...)), ...)
end
if coroutine.status(co) ~= 'suspended' then
if coroutine.status(co) == 'suspended' then
return performResume(err, co, coroutine.yield(...))
else
return true, ...
end
return performResume(err, co, coroutine.yield(...))
end
-- Resume execution of protected function call
function performResume(err, co, ...)
return handleReturnValue(err, co, coroutine.resume(co, ...))
end
local function id(trace, ...)
return trace
end
function coxpcall(f, err, ...)
local current = coroutine.running()
if not current then
if err == id then
return pcall(f, ...)
else
if select("#", ...) > 0 then
local oldf, params = f, { ... }
f = function() return oldf(unpack(params)) end
end
return xpcall(f, err)
end
else
local res, co = pcall(coroutine.create, f)
if not res then
local newf = function(...) return f(...) end
co = coroutine.create(newf)
end
coromap[co] = current
coxpt[co] = coxpt[current] or current or 0
return performResume(err, co, ...)
end
end
function copcall(f, ...)
return coxpcall(f, id, ...)
end

View file

@ -0,0 +1,228 @@
<% export("cbi_apply_widget", function(redirect_ok, rollback_token) -%>
<style type="text/css">
#cbi_apply_overlay {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
display: none;
z-index: 20000;
}
#cbi_apply_overlay .alert-message {
position: relative;
top: 10%;
width: 60%;
margin: auto;
display: flex;
flex-wrap: wrap;
min-height: 32px;
align-items: center;
}
#cbi_apply_overlay .alert-message > h4,
#cbi_apply_overlay .alert-message > p,
#cbi_apply_overlay .alert-message > div {
flex-basis: 100%;
}
#cbi_apply_overlay .alert-message > img {
margin-right: 1em;
flex-basis: 32px;
}
body.apply-overlay-active {
overflow: hidden;
height: 100vh;
}
body.apply-overlay-active #cbi_apply_overlay {
display: block;
}
</style>
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-18.138.59467-72fe5dd"></script>
<script type="text/javascript">//<![CDATA[
var xhr = new XHR(),
uci_apply_auth = { sid: '<%=luci.dispatcher.context.authsession%>', token: '<%=token%>' },
uci_apply_rollback = <%=math.max(luci.config and luci.config.apply and luci.config.apply.rollback or 30, 30)%>,
uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>,
uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>,
uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>,
uci_confirm_auth = <% if rollback_token then %>{ token: '<%=rollback_token%>' }<% else %>null<% end %>,
was_xhr_poll_running = false;
function uci_status_message(type, content) {
var overlay = document.getElementById('cbi_apply_overlay') || document.body.appendChild(E('<div id="cbi_apply_overlay"><div class="alert-message"></div></div>')),
message = overlay.querySelector('.alert-message');
if (message && type) {
if (!message.classList.contains(type)) {
message.classList.remove('notice');
message.classList.remove('warning');
message.classList.add(type);
}
if (content)
message.innerHTML = content;
document.body.classList.add('apply-overlay-active');
if (!was_xhr_poll_running) {
was_xhr_poll_running = XHR.running();
XHR.halt();
}
}
else {
document.body.classList.remove('apply-overlay-active');
if (was_xhr_poll_running)
XHR.run();
}
}
function uci_rollback(checked) {
if (checked) {
uci_status_message('warning',
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> ' +
'<%:Failed to confirm apply within %ds, waiting for rollback…%>'.format(uci_apply_rollback));
var call = function(r, data, duration) {
if (r.status === 204) {
uci_status_message('warning',
'<h4><%:Configuration has been rolled back!%></h4>' +
'<p><%:The device could not be reached within %d seconds after applying the pending changes, which caused the configuration to be rolled back for safety reasons. If you believe that the configuration changes are correct nonetheless, perform an unchecked configuration apply. Alternatively, you can dismiss this warning and edit changes before attempting to apply again, or revert all pending changes to keep the currently working configuration state.%></p>'.format(uci_apply_rollback) +
'<div class="right">' +
'<input type="button" class="btn" onclick="uci_status_message(false)" value="<%:Dismiss%>" /> ' +
'<input type="button" class="btn cbi-button-action important" onclick="uci_revert()" value="<%:Revert changes%>" /> ' +
'<input type="button" class="btn cbi-button-negative important" onclick="uci_apply(false)" value="<%:Apply unchecked%>" />' +
'</div>');
return;
}
var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
window.setTimeout(function() {
xhr.post('<%=url("admin/uci/confirm")%>', uci_apply_auth, call, uci_apply_timeout * 1000);
}, delay);
};
call({ status: 0 });
}
else {
uci_status_message('warning',
'<h4><%:Device unreachable!%></h4>' +
'<p><%:Could not regain access to the device after applying the configuration changes. You might need to reconnect if you modified network related settings such as the IP address or wireless security credentials.%></p>');
}
}
function uci_confirm(checked, deadline) {
var tt;
var ts = Date.now();
uci_status_message('notice');
var call = function(r, data, duration) {
if (Date.now() >= deadline) {
uci_rollback(checked);
return;
}
else if (r && (r.status === 200 || r.status === 204)) {
var indicator = document.querySelector('.uci_change_indicator');
if (indicator) indicator.style.display = 'none';
uci_status_message('notice', '<%:Configuration has been applied.%>');
window.clearTimeout(tt);
window.setTimeout(function() {
<% if redirect_ok then -%>
location.href = decodeURIComponent('<%=luci.util.urlencode(redirect_ok)%>');
<%- else -%>
window.location = window.location.href.split('#')[0];
<% end %>
}, uci_apply_display * 1000);
return;
}
var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
window.setTimeout(function() {
xhr.post('<%=url("admin/uci/confirm")%>', uci_confirm_auth, call, uci_apply_timeout * 1000);
}, delay);
};
var tick = function() {
var now = Date.now();
uci_status_message('notice',
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> ' +
'<%:Waiting for configuration to get applied… %ds%>'.format(Math.max(Math.floor((deadline - Date.now()) / 1000), 0)));
if (now >= deadline)
return;
tt = window.setTimeout(tick, 1000 - (now - ts));
ts = now;
};
tick();
/* wait a few seconds for the settings to become effective */
window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 - ((ts + uci_apply_rollback * 1000) - deadline), 1));
}
function uci_apply(checked) {
uci_status_message('notice',
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> ' +
'<%:Starting configuration apply…%>');
xhr.post('<%=url("admin/uci")%>/' + (checked ? 'apply_rollback' : 'apply_unchecked'), uci_apply_auth, function(r, tok) {
if (r.status === (checked ? 200 : 204)) {
if (checked && tok !== null && typeof(tok) === 'object' && typeof(tok.token) === 'string')
uci_confirm_auth = tok;
uci_confirm(checked, Date.now() + uci_apply_rollback * 1000);
}
else if (checked && r.status === 204) {
uci_status_message('notice', '<%:There are no changes to apply.%>');
window.setTimeout(function() {
<% if redirect_ok then -%>
location.href = decodeURIComponent('<%=luci.util.urlencode(redirect_ok)%>');
<%- else -%>
uci_status_message(false);
<%- end %>
}, uci_apply_display * 1000);
}
else {
uci_status_message('warning', '<%_Apply request failed with status <code>%h</code>%>'.format(r.responseText || r.statusText || r.status));
window.setTimeout(function() { uci_status_message(false); }, uci_apply_display * 1000);
}
});
}
function uci_revert() {
uci_status_message('notice',
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> ' +
'<%:Reverting configuration…%>');
xhr.post('<%=url("admin/uci/revert")%>', uci_apply_auth, function(r) {
if (r.status === 200) {
uci_status_message('notice', '<%:Changes have been reverted.%>');
window.setTimeout(function() {
<% if redirect_ok then -%>
location.href = decodeURIComponent('<%=luci.util.urlencode(redirect_ok)%>');
<%- else -%>
window.location = window.location.href.split('#')[0];
<%- end %>
}, uci_apply_display * 1000);
}
else {
uci_status_message('warning', '<%_Revert request failed with status <code>%h</code>%>'.format(r.statusText || r.status));
window.setTimeout(function() { uci_status_message(false); }, uci_apply_display * 1000);
}
});
}
//]]></script>
<%- end) %>

View file

@ -1,43 +0,0 @@
<% export("cbi_apply_xhr", function(id, configs, redirect) -%>
<fieldset class="cbi-section" id="cbi-apply-<%=id%>">
<legend><%:Applying changes%></legend>
<script type="text/javascript">//<![CDATA[
var apply_xhr = new XHR();
apply_xhr.post('<%=url('servicectl/restart', table.concat(configs, ","))%>', { token: '<%=token%>' },
function() {
var checkfinish = function() {
apply_xhr.get('<%=url('servicectl/status')%>', null,
function(x) {
if( x.responseText == 'finish' )
{
var e = document.getElementById('cbi-apply-<%=id%>-status');
if( e )
{
e.innerHTML = '<%:Configuration applied.%>';
window.setTimeout(function() {
e.parentNode.style.display = 'none';
<% if redirect then %>location.href='<%=redirect%>';<% end %>
}, 1000);
}
}
else
{
var e = document.getElementById('cbi-apply-<%=id%>-status');
if( e && x.responseText ) e.innerHTML = x.responseText;
window.setTimeout(checkfinish, 1000);
}
}
);
}
window.setTimeout(checkfinish, 1000);
}
);
//]]></script>
<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
<span id="cbi-apply-<%=id%>-status"><%:Waiting for changes to be applied...%></span>
</fieldset>
<%- end) %>

View file

@ -1,6 +1,6 @@
<%+cbi/valueheader%>
<% if self:cfgvalue(section) ~= false then %>
<input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
<input class="cbi-button cbi-button-<%=self.inputstyle or "button" %>" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
<% else %>
-
<% end %>

View file

@ -1,2 +1,2 @@
</div>
</td>
</div>

View file

@ -1,2 +1,12 @@
<td class="cbi-value-field<% if self.error and self.error[section] then %> cbi-value-error<% end %>">
<%-
local title = luci.util.trim(striptags(self.title))
local descr = luci.util.trim(striptags(self.description))
local ftype = self.typename or (self.template and self.template:gsub("^.+/", ""))
-%>
<div class="td cbi-value-field<% if self.error and self.error[section] then %> cbi-value-error<% end %>"<%=
attr("data-name", self.option) ..
ifattr(ftype and #ftype > 0, "data-type", ftype) ..
ifattr(title and #title > 0, "data-title", title) ..
ifattr(descr and #descr > 0, "data-description", descr)
%>>
<div id="cbi-<%=self.config.."-"..section.."-"..self.option%>" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>">

View file

@ -0,0 +1,54 @@
<%+cbi/valueheader%>
<%-
local selected = { }
if self.multiple then
local val
for val in luci.util.imatch(self:cfgvalue(section)) do
selected[val] = true
end
else
selected[self:cfgvalue(section)] = true
end
if not next(selected) and self.default then
selected[self.default] = true
end
-%>
<div class="cbi-dropdown"<%=
attr("name", cbid) ..
attr("display-items", self.display or self.size or 3) ..
attr("dropdown-items", self.dropdown or self.display or self.size or 5) ..
attr("placeholder", self.placeholder or translate("-- please select --")) ..
ifattr(self.multiple, "multiple", "multiple") ..
ifattr(self.optional or self.rmempty, "optional", "optional")
%>>
<ul>
<% local i, key; for i, key in pairs(self.keylist) do %>
<li<%=
attr("data-index", i) ..
attr("data-depends", self:deplist2json(section, self.deplist[i])) ..
attr("value", key) ..
ifattr(selected[key], "selected", "selected")
%>>
<%=pcdata(self.vallist[i])%>
</li>
<% end %>
<% if self.custom then %>
<li>
<input type="password" style="display:none" />
<input class="create-item-input" type="text"<%=
attr("placeholder", self.custom ~= true and
self.custom or
(self.multiple and
translate("Enter custom values") or
translate("Enter custom value")))
%> />
</li>
<% end %>
</ul>
</div>
<%+cbi/valuefooter%>

View file

@ -14,46 +14,59 @@
local def = fwm:get_defaults()
local zone = fwm:get_zone(value)
local empty = true
local function render_zone(zone)
-%>
<label class="zonebadge" style="background-color:<%=zone:get_color()%>">
<strong><%=zone:name()%></strong>
<div class="cbi-tooltip">
<%-
local zempty = true
for _, net in ipairs(zone:get_networks()) do
net = nwm:get_network(net)
if net then
zempty = false
-%>
<span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>:&#160;
<%
local nempty = true
for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
nempty = false
%>
<img<%=attr("title", iface:get_i18n())%> src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<% end %>
<% if nempty then %><em><%:(empty)%></em><% end %>
</span>
<%- end end -%>
<% if zempty then %><span class="ifacebadge"><em><%:(empty)%></em></span><% end %>
</div>
</label>
<%-
end
-%>
<% if zone then %>
<div style="white-space:nowrap">
<label class="zonebadge" style="background-color:<%=zone:get_color()%>">
<strong><%=zone:name()%>:</strong>
<%-
local zempty = true
for _, net in ipairs(zone:get_networks()) do
net = nwm:get_network(net)
if net then
zempty = false
-%>
<span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>:
<%
local nempty = true
for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
nempty = false
%>
<img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<% end %>
<% if nempty then %><em><%:(empty)%></em><% end %>
</span>
<%- end end -%>
<%- if zempty then %><em><%:(empty)%></em><% end -%>
</label>
&#160;&#8658;&#160;
<% for _, fwd in ipairs(zone:get_forwardings_by("src")) do
fz = fwd:dest_zone()
if fz then
empty = false %>
<label class="zonebadge" style="background-color:<%=fz:get_color()%>">
<strong><%=fz:name()%></strong>
</label>&#160;
<% end end %>
<% if empty then %>
<div class="zone-forwards">
<div class="zone-src">
<%=render_zone(zone)%>
</div>
<span>&#8658;</span>
<div class="zone-dest">
<%
for _, fwd in ipairs(zone:get_forwardings_by("src")) do
fz = fwd:dest_zone()
if fz then
empty = false
render_zone(fz)
end
end
if empty then
%>
<label class="zonebadge zonebadge-empty">
<strong><%=zone:forward():upper()%></strong>
</label>
<% end %>
</div>
</div>
<% end %>

View file

@ -24,26 +24,42 @@
end
-%>
<span>
<ul style="margin:0; list-style-type:none; text-align:left">
<div class="cbi-dropdown" dropdown-items="5" placeholder="<%:-- please select -- %>"<%=
attr("name", cbid) ..
ifattr(self.widget == "checkbox", "multiple", "multiple") ..
ifattr(self.rmempty or self.optional, "optional", "optional")
%>>
<script type="item-template"><!--
<li value="{{value}}">
<span class="zonebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">
<strong>{{value}}:</strong><em>(<%:create%>)</em>
</span>
</li>
--></script>
<ul>
<% if self.allowlocal then %>
<li style="padding:0.5em">
<input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_empty") .. attr("name", cbid) .. attr("value", "") .. ifattr(checked[""], "checked", "checked")%> /> &#160;
<label<%=attr("for", cbid .. "_empty")%>></label>
<label<%=attr("for", cbid .. "_empty")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<li value=""<%=ifattr(checked[""], "selected", "selected")%>>
<span style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<strong><%:Device%></strong>
<% if self.allowany and self.allowlocal then %>(<%:input%>)<% end %>
</label>
<% if self.allowany and self.allowlocal then -%>
(<%= self.alias ~= "dest"
and translate("output") or translate("input") %>)
<%- end %>
</span>
</li>
<% elseif self.widget ~= "checkbox" and (self.rmempty or self.optional) then %>
<li value=""<%=ifattr(checked[""], "selected", "selected")%>>
<span class="zonebadge">
<em><%:unspecified%></em>
</span>
</li>
<% end %>
<% if self.allowany then %>
<li style="padding:0.5em">
<input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_any") .. attr("name", cbid) .. attr("value", "*") .. ifattr(checked["*"], "checked", "checked")%> /> &#160;
<label<%=attr("for", cbid .. "_any")%>></label>
<label<%=attr("for", cbid .. "_any")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<li value="*"<%=ifattr(checked["*"], "selected", "selected")%>>
<span style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
<strong><%:Any zone%></strong>
<% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %>
</label>
</span>
</li>
<% end %>
<%
@ -51,45 +67,42 @@
if zone:name() ~= self.exclude then
selected = selected or (value == zone:name())
%>
<li style="padding:0.5em">
<input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "." .. zone:name()) .. attr("name", cbid) .. attr("value", zone:name()) .. ifattr(checked[zone:name()], "checked", "checked")%> /> &#160;
<label<%=attr("for", cbid .. "." .. zone:name())%>></label>
<label<%=attr("for", cbid .. "." .. zone:name())%> style="background-color:<%=zone:get_color()%>" class="zonebadge">
<li<%=attr("value", zone:name()) .. ifattr(checked[zone:name()], "selected", "selected")%>>
<span style="background-color:<%=zone:get_color()%>" class="zonebadge">
<strong><%=zone:name()%>:</strong>
<%
<%-
local zempty = true
for _, net in ipairs(zone:get_networks()) do
net = nwm:get_network(net)
if net then
zempty = false
%>
-%>
<span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>:
<%
<%-
local nempty = true
for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
nempty = false
%>
<img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<img<%=attr("title", iface:get_i18n())%> src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<% end %>
<% if nempty then %><em><%:(empty)%></em><% end %>
<% if nempty then %><em><%:(empty)%></em><% end -%>
</span>
<% end end %>
<% if zempty then %><em><%:(empty)%></em><% end %>
</label>
<%- end end -%>
<%- if zempty then %><em><%:(empty)%></em><% end -%>
</span>
</li>
<% end end %>
<% if self.widget ~= "checkbox" and not self.nocreate then %>
<li style="padding:0.5em">
<input class="cbi-input-radio" data-update="click change" type="radio"<%=attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not selected, "checked", "checked")%> /> &#160;
<label<%=attr("for", cbid .. "_new")%>></label>
<div onclick="document.getElementById('<%=cbid%>_new').checked=true" class="zonebadge" style="background-color:<%=fwm.zone.get_color()%>">
<em><%:unspecified -or- create:%>&#160;</em>
<input type="text"<%=attr("name", cbid .. ".newzone") .. ifattr(not selected, "value", luci.http.formvalue(cbid .. ".newzone") or self.default)%> onfocus="document.getElementById('<%=cbid%>_new').checked=true" />
</div>
<li value="-">
<span class="zonebadge">
<em><%:create%>:</em>
<input type="password" style="display:none" />
<input class="create-item-input" type="text" />
</span>
</li>
<% end %>
</ul>
</span>
</div>
<%+cbi/valuefooter%>

View file

@ -1,25 +1,39 @@
<%- if pageaction then -%>
<div class="cbi-page-actions">
<% if redirect and not flow.hidebackbtn then %>
<div style="float:left">
<input class="cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(redirect)%>'" />
</div>
<% end %>
<%
local display_back = (redirect and not flow.hidebackbtn)
local display_skip = (flow.skip)
local display_apply = (not autoapply and not flow.hideapplybtn)
local display_save = (not flow.hidesavebtn)
local display_reset = (not flow.hideresetbtn)
if pageaction and
(display_back or display_skip or display_apply or display_save or display_reset)
then
%><div class="cbi-page-actions"><%
if display_back then
%><input class="cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(redirect)%>'" /> <%
end
if display_skip then
%><input class="cbi-button cbi-button-skip" type="button" value="<%:Skip%>" onclick="cbi_submit(this, 'cbi.skip')" /> <%
end
if display_apply then
%><input class="cbi-button cbi-button-apply" type="button" value="<%:Save & Apply%>" onclick="cbi_submit(this, 'cbi.apply')" /> <%
end
if display_save then
%><input class="cbi-button cbi-button-save" type="submit" value="<%:Save%>" /> <%
end
if display_reset then
%><input class="cbi-button cbi-button-reset" type="button" value="<%:Reset%>" onclick="location.href='<%=REQUEST_URI%>'" /> <%
end
%></div><%
end
%>
<% if flow.skip then %>
<input class="cbi-button cbi-button-skip" type="submit" name="cbi.skip" value="<%:Skip%>" />
<% end %>
<% if not autoapply and not flow.hideapplybtn then %>
<input class="cbi-button cbi-button-apply" type="submit" name="cbi.apply" value="<%:Save & Apply%>" />
<% end %>
<% if not flow.hidesavebtn then %>
<input class="cbi-button cbi-button-save" type="submit" value="<%:Save%>" />
<% end %>
<% if not flow.hideresetbtn then %>
<input class="cbi-button cbi-button-reset" type="button" value="<%:Reset%>" onclick="location.href='<%=REQUEST_URI%>'" />
<% end %>
</div>
<%- end -%>
</form>
<script type="text/javascript">cbi_init();</script>

View file

@ -3,7 +3,6 @@
<br />
<%- end %>
<div class="cbi-value-description">
<span class="cbi-value-helpicon"><img src="<%=resource%>/cbi/help.gif" alt="<%:help%>" /></span>
<%=self.description%>
</div>
<%- end %>

View file

@ -1,18 +1,17 @@
<%+header%>
<form method="post" name="cbi" action="<%=REQUEST_URI%>" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, '<%:Some fields are invalid, cannot save values!%>')">
<form method="post" name="cbi" action="<%=REQUEST_URI%>" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, '<%:Some fields are invalid, cannot save values!%>')"<%=
attr("data-strings", luci.util.serialize_json({
label = {
choose = translate('-- Please choose --'),
custom = translate('-- custom --'),
},
path = {
resource = resource,
browser = url("admin/filebrowser")
}
}))
%>>
<div>
<script type="text/javascript" src="<%=resource%>/cbi.js"<%=
attr("data-strings", luci.util.serialize_json({
label = {
choose = translate('-- Please choose --'),
custom = translate('-- custom --'),
},
path = {
resource = resource,
browser = url("admin/filebrowser")
}
}))
%>></script>
<input type="hidden" name="token" value="<%=token%>" />
<input type="hidden" name="cbi.submit" value="1" />
<input type="submit" value="<%:Save%>" class="hidden" />

View file

@ -1,14 +1,10 @@
<%- if firstmap and messages then local msg; for _, msg in ipairs(messages) do -%>
<div class="errorbox"><%=pcdata(msg)%></div>
<div class="alert-message warning"><%=pcdata(msg)%></div>
<%- end end -%>
<%-+cbi/apply_xhr-%>
<div class="cbi-map" id="cbi-<%=self.config%>">
<% if self.title and #self.title > 0 then %><h2 name="content"><%=self.title%></h2><% end %>
<% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %>
<%- if firstmap and applymap then cbi_apply_xhr(self.config, parsechain, redirect) end -%>
<% if self.tabbed then %>
<ul class="cbi-tabmenu map">
<%- self.selected_tab = luci.http.formvalue("tab.m-" .. self.config) %>
@ -20,7 +16,6 @@
</li>
<% end %>
</ul>
<br />
<% for i, section in ipairs(self.children) do %>
<div class="cbi-tabcontainer" id="container.m-<%=self.config%>.<%=section.section or section.sectiontype%>"<% if section.sectiontype ~= self.selected_tab then %> style="display:none"<% end %>>
<% section:render() %>
@ -42,6 +37,4 @@
<% else %>
<%- self:render_children() %>
<% end %>
<br />
</div>

View file

@ -19,71 +19,73 @@
if value then
for value in utl.imatch(value) do
checked[value] = true
for value in utl.imatch(value) do
checked[value] = true
end
end
else
local n = self.network and net:get_network(self.network)
if n then
local i
for _, i in ipairs(n:get_interfaces() or { n:get_interface() }) do
checked[i:name()] = true
local a = n:is_alias()
if a then
checked['@' .. a] = true
else
local i
for _, i in ipairs(n:get_interfaces() or { n:get_interface() }) do
checked[i:name()] = true
end
end
end
end
-%>
<input type="hidden" name="<%=cbeid%>" value="1" />
<ul style="margin:0; list-style-type:none">
<% for _, iface in ipairs(ifaces) do
local link = iface:adminlink()
if (not self.nobridges or not iface:is_bridge()) and
(not self.noinactive or iface:is_up()) and
iface:name() ~= self.exclude
then %>
<li>
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%=
attr("type", self.widget or "radio") ..
attr("id", cbid .. "." .. iface:name()) ..
attr("name", cbid) .. attr("value", iface:name()) ..
ifattr(checked[iface:name()], "checked", "checked")
%> />
<%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%>
<label<%=attr("for", cbid .. "." .. iface:name())%>></label>
<%- end -%>
&#160;
<label<%=attr("for", cbid .. "." .. iface:name())%>>
<% if link then -%><a href="<%=link%>"><% end -%>
<img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<% if link then -%></a><% end -%>
<%=pcdata(iface:get_i18n())%>
<% local ns = iface:get_networks(); if #ns > 0 then %>(
<%- local i, n; for i, n in ipairs(ns) do -%>
<%-= (i>1) and ', ' -%>
<a href="<%=n:adminlink()%>"><%=n:name()%></a>
<%- end -%>
)<% end %>
</label>
</li>
<% end end %>
<% if not self.nocreate then %>
<li>
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%=
attr("type", self.widget or "radio") ..
attr("id", cbid .. "_custom") ..
attr("name", cbid) ..
attr("value", " ")
%> />
<%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%>
<label<%=attr("for", cbid .. "_custom")%>></label>
<%- end -%>
&#160;
<label<%=attr("for", cbid .. "_custom")%>>
<img title="<%:Custom Interface%>" style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/ethernet_disabled.png" />
<%:Custom Interface%>:
</label>
<input type="text" style="width:50px" onfocus="document.getElementById('<%=cbid%>_custom').checked=true" onblur="var x=document.getElementById('<%=cbid%>_custom'); x.value=this.value; x.checked=true" />
</li>
<% end %>
</ul>
<div class="cbi-dropdown" display-items="5" placeholder="<%:-- please select -- %>"<%=
attr("name", cbid) ..
ifattr(self.widget == "checkbox", "multiple", "multiple") ..
ifattr(self.widget == "checkbox", "optional", "optional")
%>>
<script type="item-template"><!--
<li value="{{value}}">
<img title="<%:Custom Interface%>: &quot;{{value}}&quot;" src="<%=resource%>/icons/ethernet_disabled.png" />
<span class="hide-open">{{value}}</span>
<span class="hide-close"><%:Custom Interface%>: "{{value}}"</span>
</li>
--></script>
<ul>
<% for _, iface in ipairs(ifaces) do
if (not self.noaliases or iface:type() ~= "alias") and
(not self.nobridges or not iface:is_bridge()) and
(not self.noinactive or iface:is_up()) and
iface:name() ~= self.exclude
then %>
<li<%=
attr("value", iface:name()) ..
ifattr(checked[iface:name()], "selected", "selected")
%>>
<img<%=attr("title", iface:get_i18n())%> src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<span class="hide-open"><%=pcdata(iface:name())%></span>
<span class="hide-close">
<%=pcdata(iface:get_i18n())%>
<% local ns = iface:get_networks(); if #ns > 0 then %>(
<%- local i, n; for i, n in ipairs(ns) do -%>
<%-= (i>1) and ', ' -%>
<a href="<%=n:adminlink()%>"><%=n:name()%></a>
<%- end -%>
)<% end %>
</span>
</li>
<% end end %>
<% if not self.nocreate then %>
<li value="">
<img title="<%:Custom Interface%>" src="<%=resource%>/icons/ethernet_disabled.png" />
<span><%:Custom Interface%>:</span>
<input type="password" style="display:none" />
<input class="create-item-input" type="text" />
</li>
<% end %>
</ul>
</div>
<%+cbi/valuefooter%>

View file

@ -20,66 +20,62 @@
end
-%>
<ul style="margin:0; list-style-type:none; text-align:left">
<% for _, net in ipairs(networks) do
if (net:name() ~= "loopback") and
(net:name() ~= self.exclude) and
(not self.novirtual or not net:is_virtual())
then %>
<li style="padding:0.25em 0">
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%=
attr("type", self.widget or "radio") ..
attr("id", cbid .. "." .. net:name()) ..
attr("name", cbid) .. attr("value", net:name()) ..
ifattr(checked[net:name()], "checked", "checked")
%> /> &#160;
<label<%=attr("for", cbid .. "." .. net:name())%>>
<div class="cbi-dropdown" display-items="5" placeholder="<%:-- please select -- %>"<%=
attr("name", cbid) ..
ifattr(self.widget == "checkbox", "multiple", "multiple") ..
ifattr(self.widget == "checkbox", "optional", "optional")
%>>
<script type="item-template"><!--
<li value="{{value}}">
<span class="ifacebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">
{{value}}: <em>(<%:create%>)</em>
</span>
</li>
--></script>
<ul>
<% if self.widget ~= "checkbox" then %>
<li value=""<%= ifattr(not value, "selected", "selected") %>>
<em><%:unspecified%></em>
</li>
<% end %>
<% for _, net in ipairs(networks) do
if (net:name() ~= "loopback") and
(net:name() ~= self.exclude) and
(not self.novirtual or not net:is_virtual())
then %>
<li<%= attr("value", net:name()) .. ifattr(checked[net:name()], "selected", "selected") %>>
<span class="ifacebadge"><%=net:name()%>:
<%
local empty = true
for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
if not iface:is_bridge() then
empty = false
%>
-%>
<img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
<% end end %>
<% if empty then %><em><%:(no interfaces attached)%></em><% end %>
<%- end end %>
<% if empty then %>
<em class="hide-close"><%:(no interfaces attached)%></em>
<em class="hide-open">-</em>
<% end %>
</span>
</label>
</li>
<% end end %>
</li>
<% end end %>
<% if not self.nocreate then %>
<li style="padding:0.25em 0">
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not value and self.widget ~= "checkbox", "checked", "checked")%> /> &#160;
<%- if not self.widget or self.widget == "checkbox" or self.widget == "radio" then -%>
<label<%=attr("for", cbid .. "_new")%>></label>
<%- end -%>
<div style="padding:0.5em; display:inline">
<label<%=attr("for", cbid .. "_new")%>><em>
<% if not self.nocreate then %>
<li value="-"<%= ifattr(not value and self.widget ~= "checkbox", "selected", "selected") %>>
<em>
<%- if self.widget == "checkbox" then -%>
<%:create:%>
<%- else -%>
<%:unspecified -or- create:%>
<%- end -%>&#160;</em></label>
<%- end -%>
</em>
<input style="display:none" type="password" />
<input style="width:6em" type="text"<%=attr("name", cbid .. ".newnet")%> onfocus="document.getElementById('<%=cbid%>_new').checked=true" />
</div>
</li>
<% elseif self.widget ~= "checkbox" and self.unspecified then %>
<li style="padding:0.25em 0">
<input class="cbi-input-<%=self.widget or "radio"%>" data-update="click change"<%=
attr("type", self.widget or "radio") ..
attr("id", cbid .. "_uns") ..
attr("name", cbid) ..
attr("value", "") ..
ifattr(not value or #value == 0, "checked", "checked")
%> /> &#160;
<div style="padding:0.5em; display:inline">
<label<%=attr("for", cbid .. "_uns")%>><em><%:unspecified%></em></label>
</div>
</li>
<% end %>
</ul>
<input class="create-item-input" type="text" />
</li>
<% end %>
</ul>
</div>
<%+cbi/valuefooter%>

View file

@ -1,5 +1,5 @@
<% if self:cfgvalue(self.section) then section = self.section %>
<fieldset class="cbi-section">
<div class="cbi-section">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<%- end %>
@ -15,17 +15,16 @@
<div class="cbi-section-node<% if self.tabs then %> cbi-section-node-tabbed<% end %>" id="cbi-<%=self.config%>-<%=section%>">
<%+cbi/ucisection%>
</div>
<br />
</fieldset>
</div>
<% elseif self.addremove then %>
<% if self.template_addremove then include(self.template_addremove) else -%>
<fieldset class="cbi-section" id="cbi-<%=self.config%>-<%=self.section%>">
<div class="cbi-section" id="cbi-<%=self.config%>-<%=self.section%>">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<%- end %>
<div class="cbi-section-descr"><%=self.description%></div>
<input type="submit" class="cbi-button cbi-button-add" name="cbi.cns.<%=self.config%>.<%=self.section%>" value="<%:Add%>" />
</fieldset>
</div>
<%- end %>
<% end %>
<!-- /nsection -->

View file

@ -1,4 +1,4 @@
<fieldset class="cbi-section">
<div class="cbi-section">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<%- end %>
@ -25,8 +25,7 @@
</div>
<%- end %>
</div>
<br />
</fieldset>
</div>
<%-
if type(self.hidden) == "table" then
for k, v in pairs(self.hidden) do

View file

@ -1,59 +1,77 @@
<% if not self.embedded then %>
<form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>">
<div>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<input type="hidden" name="token" value="<%=token%>" />
<input type="hidden" name="cbi.submit" value="1" />
</div>
<% end %>
<div class="cbi-map" id="cbi-<%=self.config%>">
<% if self.title and #self.title > 0 then %><h2 name="content"><%=self.title%></h2><% end %>
<% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %>
<% self:render_children() %>
<br />
</div>
<%- if self.message then %>
<div><%=self.message%></div>
<%- end %>
<%- if self.errmessage then %>
<div class="error"><%=self.errmessage%></div>
<%- end %>
<% if not self.embedded then %>
<div class="cbi-page-actions">
<%-
if type(self.hidden) == "table" then
for k, v in pairs(self.hidden) do
-%>
<input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" />
<%-
<%
if not self.embedded then
%><form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>">
<input type="hidden" name="token" value="<%=token%>" />
<input type="hidden" name="cbi.submit" value="1" /><%
end
%><div class="cbi-map" id="cbi-<%=self.config%>"><%
if self.title and #self.title > 0 then
%><h2 name="content"><%=self.title%></h2><%
end
if self.description and #self.description > 0 then
%><div class="cbi-map-descr"><%=self.description%></div><%
end
self:render_children()
%></div><%
if self.message then
%><div class="alert-message notice"><%=self.message%></div><%
end
if self.errmessage then
%><div class="alert-message warning"><%=self.errmessage%></div><%
end
if not self.embedded then
if type(self.hidden) == "table" then
local k, v
for k, v in pairs(self.hidden) do
%><input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" /><%
end
end
local display_back = (redirect)
local display_cancel = (self.cancel ~= false and self.on_cancel)
local display_skip = (self.flow and self.flow.skip)
local display_submit = (self.submit ~= false)
local display_reset = (self.reset ~= false)
if display_back or display_cancel or display_skip or display_submit or display_reset then
%><div class="cbi-page-actions"><%
if display_back then
%><input class="cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(redirect)%>'" /> <%
end
if display_cancel then
local label = pcdata(self.cancel or translate("Cancel"))
%><input class="cbi-button cbi-button-link" type="button" value="<%=label%>" onclick="cbi_submit(this, 'cbi.cancel')" /> <%
end
if display_skip then
%><input class="cbi-button cbi-button-neutral" type="button" value="<%:Skip%>" onclick="cbi_submit(this, 'cbi.skip')" /> <%
end
if display_submit then
local label = pcdata(self.submit or translate("Submit"))
%><input class="cbi-button cbi-button-save" type="submit" value="<%=label%>" /> <%
end
if display_reset then
local label = pcdata(self.reset or translate("Reset"))
%><input class="cbi-button cbi-button-reset" type="reset" value="<%=label%>" /> <%
end
%></div><%
end
%></form><%
end
%>
<% if redirect then %>
<div style="float:left">
<input class="cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(redirect)%>'" />
</div>
<% end %>
<%- if self.flow and self.flow.skip then %>
<input class="cbi-button cbi-button-skip" type="submit" name="cbi.skip" value="<%:Skip%>" />
<% end %>
<%- if self.submit ~= false then %>
<input class="cbi-button cbi-button-save" type="submit" value="
<%- if not self.submit then -%><%-:Submit-%><%-else-%><%=self.submit%><%end-%>
" />
<% end %>
<%- if self.reset ~= false then %>
<input class="cbi-button cbi-button-reset" type="reset" value="
<%- if not self.reset then -%><%-:Reset-%><%-else-%><%=self.reset%><%end-%>
" />
<% end %>
<%- if self.cancel ~= false and self.on_cancel then %>
<input class="cbi-button cbi-button-reset" type="submit" name="cbi.cancel" value="
<%- if not self.cancel then -%><%-:Cancel-%><%-else-%><%=self.cancel%><%end-%>
" />
<% end %>
</div>
</form>
<% end %>
<script type="text/javascript">cbi_init();</script>

View file

@ -1,8 +1,13 @@
<%-
local rowcnt = 1
local rowcnt = 0
function rowstyle()
rowcnt = rowcnt + 1
return (rowcnt % 2) + 1
if rowcnt % 2 == 0 then
return " cbi-rowstyle-1"
else
return " cbi-rowstyle-2"
end
end
function width(o)
@ -14,86 +19,138 @@ function width(o)
end
return ''
end
local has_titles = false
local has_descriptions = false
local anonclass = (not self.anonymous or self.sectiontitle) and "named" or "anonymous"
local titlename = ifattr(not self.anonymous or self.sectiontitle, "data-title", translate("Name"))
local i, k
for i, k in pairs(self.children) do
if not k.typename then
k.typename = k.template and k.template:gsub("^.+/", "") or ""
end
if not has_titles and k.title and #k.title > 0 then
has_titles = true
end
if not has_descriptions and k.description and #k.description > 0 then
has_descriptions = true
end
end
function render_titles()
if not has_titles then
return
end
%><div class="tr cbi-section-table-titles <%=anonclass%>"<%=titlename%>><%
local i, k
for i, k in ipairs(self.children) do
if not k.optional then
%><div class="th cbi-section-table-cell"<%=
width(k) .. attr('data-type', k.typename) %>><%
if k.titleref then
%><a title="<%=self.titledesc or translate('Go to relevant configuration page')%>" class="cbi-title-ref" href="<%=k.titleref%>"><%
end
write(k.title)
if k.titleref then
%></a><%
end
%></div><%
end
end
if self.sortable or self.extedit or self.addremove then
%><div class="th cbi-section-table-cell cbi-section-actions"></div><%
end
%></div><%
rowcnt = rowcnt + 1
end
function render_descriptions()
if not has_descriptions then
return
end
%><div class="tr cbi-section-table-descr <%=anonclass%>"><%
local i, k
for i, k in ipairs(self.children) do
if not k.optional then
%><div class="th cbi-section-table-cell"<%=
width(k) .. attr("data-type", k.typename) %>><%
write(k.description)
%></div><%
end
end
if self.sortable or self.extedit or self.addremove then
%><div class="th cbi-section-table-cell cbi-section-actions"></div><%
end
%></div><%
rowcnt = rowcnt + 1
end
-%>
<!-- tblsection -->
<fieldset class="cbi-section" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
<div class="cbi-section cbi-tblsection" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<h3><%=self.title%></h3>
<%- end %>
<%- if self.sortable then -%>
<input type="hidden" id="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" name="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" value="" />
<%- end -%>
<div class="cbi-section-descr"><%=self.description%></div>
<div class="cbi-section-node">
<%- local count = 0 -%>
<table class="cbi-section-table">
<tr class="cbi-section-table-titles">
<%- if not self.anonymous then -%>
<%- if self.sectionhead then -%>
<th class="cbi-section-table-cell"><%=self.sectionhead%></th>
<%- else -%>
<th>&#160;</th>
<%- end -%>
<%- count = count +1; end -%>
<%- for i, k in pairs(self.children) do if not k.optional then -%>
<th class="cbi-section-table-cell"<%=width(k)%>>
<%- if k.titleref then -%><a title="<%=self.titledesc or translate('Go to relevant configuration page')%>" class="cbi-title-ref" href="<%=k.titleref%>"><%- end -%>
<%-=k.title-%>
<%- if k.titleref then -%></a><%- end -%>
</th>
<%- count = count + 1; end; end; if self.sortable then -%>
<th class="cbi-section-table-cell"><%:Sort%></th>
<%- count = count + 1; end; if self.extedit or self.addremove then -%>
<th class="cbi-section-table-cell">&#160;</th>
<%- count = count + 1; end -%>
</tr>
<tr class="cbi-section-table-descr">
<%- if not self.anonymous then -%>
<%- if self.sectiondesc then -%>
<th class="cbi-section-table-cell"><%=self.sectiondesc%></th>
<%- else -%>
<th></th>
<%- end -%>
<%- end -%>
<%- for i, k in pairs(self.children) do if not k.optional then -%>
<th class="cbi-section-table-cell"<%=width(k)%>><%=k.description%></th>
<%- end; end; if self.sortable then -%>
<th class="cbi-section-table-cell"></th>
<%- end; if self.extedit or self.addremove then -%>
<th class="cbi-section-table-cell"></th>
<%- end -%>
</tr>
<%- local isempty = true
for i, k in ipairs(self:cfgsections()) do
section = k
isempty = false
scope = { valueheader = "cbi/cell_valueheader", valuefooter = "cbi/cell_valuefooter" }
-%>
<tr class="cbi-section-table-row<% if self.extedit or self.rowcolors then %> cbi-rowstyle-<%=rowstyle()%><% end %>" id="cbi-<%=self.config%>-<%=section%>">
<% if not self.anonymous then -%>
<th><h3><%=(type(self.sectiontitle) == "function") and self:sectiontitle(section) or k%></h3></th>
<%- end %>
<div class="table cbi-section-table">
<%-
render_titles()
render_descriptions()
local isempty, section, i, k = true, nil, nil
for i, k in ipairs(self:cfgsections()) do
isempty = false
section = k
<%-
for k, node in ipairs(self.children) do
if not node.optional then
node:render(section, scope or {})
end
local sectionname = striptags((type(self.sectiontitle) == "function") and self:sectiontitle(section) or k)
local sectiontitle = ifattr(sectionname and (not self.anonymous or self.sectiontitle), "data-title", sectionname)
local colorclass = (self.extedit or self.rowcolors) and rowstyle() or ""
local scope = {
valueheader = "cbi/cell_valueheader",
valuefooter = "cbi/cell_valuefooter"
}
-%>
<div class="tr cbi-section-table-row<%=colorclass%>" id="cbi-<%=self.config%>-<%=section%>"<%=sectiontitle%>>
<%-
local node
for k, node in ipairs(self.children) do
if not node.optional then
node:render(section, scope or {})
end
-%>
end
-%>
<%- if self.sortable then -%>
<td class="cbi-section-table-cell">
<input class="cbi-button cbi-button-up" type="button" value="" onclick="return cbi_row_swap(this, true, 'cbi.sts.<%=self.config%>.<%=self.sectiontype%>')" alt="<%:Move up%>" title="<%:Move up%>" />
<input class="cbi-button cbi-button-down" type="button" value="" onclick="return cbi_row_swap(this, false, 'cbi.sts.<%=self.config%>.<%=self.sectiontype%>')" alt="<%:Move down%>" title="<%:Move down%>" />
</td>
<%- end -%>
<%- if self.extedit or self.addremove then -%>
<td class="cbi-section-table-cell">
<%- if self.extedit then -%>
<%- if self.sortable or self.extedit or self.addremove then -%>
<div class="td cbi-section-table-cell nowrap cbi-section-actions">
<div>
<%- if self.sortable then -%>
<input class="cbi-button cbi-button-up" type="button" value="<%:Up%>" onclick="return cbi_row_swap(this, true, 'cbi.sts.<%=self.config%>.<%=self.sectiontype%>')" title="<%:Move up%>" />
<input class="cbi-button cbi-button-down" type="button" value="<%:Down%>" onclick="return cbi_row_swap(this, false, 'cbi.sts.<%=self.config%>.<%=self.sectiontype%>')" title="<%:Move down%>" />
<% end; if self.extedit then -%>
<input class="cbi-button cbi-button-edit" type="button" value="<%:Edit%>"
<%- if type(self.extedit) == "string" then
%> onclick="location.href='<%=self.extedit:format(section)%>'"
@ -101,45 +158,46 @@ end
%> onclick="location.href='<%=self:extedit(section)%>'"
<%- end
%> alt="<%:Edit%>" title="<%:Edit%>" />
<%- end; if self.addremove then %>
<% end; if self.addremove then %>
<input class="cbi-button cbi-button-remove" type="submit" value="<%:Delete%>" onclick="this.form.cbi_state='del-section'; return true" name="cbi.rts.<%=self.config%>.<%=k%>" alt="<%:Delete%>" title="<%:Delete%>" />
<%- end -%>
</td>
<%- end -%>
</tr>
</div>
</div>
<%- end -%>
</div>
<%- end -%>
<%- if isempty then -%>
<tr class="cbi-section-table-row">
<td colspan="<%=count%>"><em><br /><%:This section contains no values yet%></em></td>
</tr>
<%- end -%>
</table>
<% if self.error then %>
<div class="cbi-section-error">
<ul><% for _, c in pairs(self.error) do for _, e in ipairs(c) do -%>
<li><%=pcdata(e):gsub("\n","<br />")%></li>
<%- end end %></ul>
</div>
<% end %>
<%- if self.addremove then -%>
<% if self.template_addremove then include(self.template_addremove) else -%>
<div class="cbi-section-create cbi-tblsection-create">
<% if self.anonymous then %>
<input class="cbi-button cbi-button-add" type="submit" value="<%:Add%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" title="<%:Add%>" />
<% else %>
<% if self.invalid_cts then -%><div class="cbi-section-error"><% end %>
<input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" data-type="uciname" data-optional="true" />
<input class="cbi-button cbi-button-add" type="submit" onclick="this.form.cbi_state='add-section'; return true" value="<%:Add%>" title="<%:Add%>" />
<% if self.invalid_cts then -%>
<br /><%:Invalid%></div>
<%- end %>
<% end %>
</div>
<%- end %>
<%- if isempty then -%>
<div class="tr cbi-section-table-row placeholder">
<div class="td"><em><%:This section contains no values yet%></em></div>
</div>
<%- end -%>
</div>
</fieldset>
<% if self.error then %>
<div class="cbi-section-error">
<ul><% for _, c in pairs(self.error) do for _, e in ipairs(c) do -%>
<li><%=pcdata(e):gsub("\n","<br />")%></li>
<%- end end %></ul>
</div>
<% end %>
<%- if self.addremove then -%>
<% if self.template_addremove then include(self.template_addremove) else -%>
<div class="cbi-section-create cbi-tblsection-create">
<% if self.anonymous then %>
<input class="cbi-button cbi-button-add" type="submit" value="<%:Add%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" title="<%:Add%>" />
<% else %>
<% if self.invalid_cts then -%>
<div class="cbi-section-error"><%:Invalid%></div>
<%- end %>
<div>
<input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" data-type="uciname" data-optional="true" />
</div>
<input class="cbi-button cbi-button-add" type="submit" onclick="this.form.cbi_state='add-section'; return true" value="<%:Add%>" title="<%:Add%>" />
<% end %>
</div>
<%- end %>
<%- end -%>
</div>
<!-- /tblsection -->

View file

@ -1,4 +1,4 @@
<fieldset class="cbi-section" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
<div class="cbi-section" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<%- end %>
@ -20,10 +20,9 @@
<%+cbi/tabmenu%>
<fieldset class="cbi-section-node<% if self.tabs then %> cbi-section-node-tabbed<% end %>" id="cbi-<%=self.config%>-<%=section%>">
<div class="cbi-section-node<% if self.tabs then %> cbi-section-node-tabbed<% end %>" id="cbi-<%=self.config%>-<%=section%>">
<%+cbi/ucisection%>
</fieldset>
<br />
</div>
<%- end %>
<% if isempty then -%>
@ -36,14 +35,15 @@
<% if self.anonymous then -%>
<input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" value="<%:Add%>" />
<%- else -%>
<% if self.invalid_cts then -%><div class="cbi-section-error"><% end %>
<input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" data-type="uciname" data-optional="true" />
<input type="submit" class="cbi-button cbi-button-add" onclick="this.form.cbi_state='add-section'; return true" value="<%:Add%>" />
<% if self.invalid_cts then -%>
<br /><%:Invalid%></div>
<div class="cbi-section-error"><%:Invalid%></div>
<%- end %>
<div>
<input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>." name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>." data-type="uciname" data-optional="true" />
</div>
<input class="cbi-button cbi-button-add" type="submit" onclick="this.form.cbi_state='add-section'; return true" value="<%:Add%>" title="<%:Add%>" />
<%- end %>
</div>
<%- end %>
<%- end %>
</fieldset>
</div>

View file

@ -32,25 +32,25 @@
<% if self.optionals[section] and #self.optionals[section] > 0 or self.dynamic then %>
<div class="cbi-optionals" data-index="<%=#self.children + 1%>">
<%
<%-
if self.dynamic then
local keys, vals, name, opt = { }, { }
for name, opt in pairs(self.optionals[section]) do
keys[#keys+1] = name
vals[#vals+1] = opt.title
end
%>
-%>
<input type="text" id="cbi.opt.<%=self.config%>.<%=section%>" name="cbi.opt.<%=self.config%>.<%=section%>" data-type="uciname" data-optional="true"<%=
ifattr(#keys > 0, "data-choices", luci.util.json_encode({keys, vals}))
%> />
<% else %>
<%- else -%>
<select id="cbi.opt.<%=self.config%>.<%=section%>" name="cbi.opt.<%=self.config%>.<%=section%>" data-optionals="true">
<option><%: -- Additional Field -- %></option>
<% for key, val in pairs(self.optionals[section]) do -%>
<option id="cbi-<%=self.config.."-"..section.."-"..val.option%>" value="<%=val.option%>" data-index="<%=val.index%>" data-depends="<%=pcdata(val:deplist2json(section))%>"><%=striptags(val.title)%></option>
<%- end %>
</select>
<% end %>
<%- end -%>
<input type="submit" class="cbi-button cbi-button-fieldadd" value="<%:Add%>" />
</div>
<% end %>

View file

@ -8,7 +8,7 @@
<%:Uploaded File%> (<%=t.byte_format(s.size)%>)
<% if self.unsafeupload then %>
<input type="hidden"<%= attr("value", v) .. attr("name", cbid) .. attr("id", cbid) %> />
<input class="cbi-button cbi-input-image" type="image" value="<%:Replace entry%>" name="cbi.rlf.<%=section .. "." .. self.option%>" alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" />
<input class="cbi-button cbi-button-image" type="image" value="<%:Replace entry%>" name="cbi.rlf.<%=section .. "." .. self.option%>" alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" />
<% end %>
<% end %>

View file

@ -1,10 +1,16 @@
<%+cbi/valueheader%>
<%- if self.password then -%>
<input type="password" style="position:absolute; left:-4000px"<%=
attr("name", "password." .. cbid)
%> />
<%- end -%>
<input data-update="change"<%=
attr("id", cbid) ..
attr("name", cbid) ..
attr("type", self.password and "password" or "text") ..
attr("class", self.password and "cbi-input-password" or "cbi-input-text") ..
attr("value", self:cfgvalue(section) or self.default) ..
ifattr(self.password, "autocomplete", "new-password") ..
ifattr(self.size, "size") ..
ifattr(self.placeholder, "placeholder") ..
ifattr(self.readonly, "readonly") ..
@ -14,5 +20,7 @@
ifattr(self.combobox_manual, "data-manual", self.combobox_manual) ..
ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist })
%> />
<% if self.password then %><img src="<%=resource%>/cbi/reload.gif" style="vertical-align:middle" title="<%:Reveal/hide password%>" onclick="var e = document.getElementById('<%=cbid%>'); e.type = (e.type=='password') ? 'text' : 'password';" /><% end %>
<%- if self.password then -%>
<div class="cbi-button cbi-button-neutral" title="<%:Reveal/hide password%>" onclick="var e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'"></div>
<% end %>
<%+cbi/valuefooter%>

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