diff --git a/LICENSE b/LICENSE index 48f616c..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2020 gSpotx2f + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -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: + 1. Definitions. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -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. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile deleted file mode 100644 index 4f0b100..0000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (C) 2020 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector) -# -# This is free software, licensed under the MIT License. -# - -include $(TOPDIR)/rules.mk - -PKG_VERSION:=0.2 -PKG_RELEASE:=1 -LUCI_TITLE:=Internet detector for the LuCI status page -LUCI_DEPENDS:=+luci-mod-admin-full -LUCI_PKGARCH:=all -PKG_LICENSE:=MIT - -include ../../luci.mk - -# call BuildPackage - OpenWrt buildroot signature diff --git a/README.md b/README.md index 1d10c70..bde7dd4 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,43 @@ -# luci-app-internet-detector -Internet detector for the LuCI status page (OpenWrt webUI). +# Internet detector for OpenWrt. +Checking Internet availability. OpenWrt >= 19.07. +Dependences: lua, luci-lib-nixio, libuci-lua + **Installation notes:** - wget --no-check-certificate -O /tmp/luci-app-internet-detector_0.2-1_all.ipk https://github.com/gSpotx2f/luci-app-internet-detector/raw/master/packages/19.07/luci-app-internet-detector_0.2-1_all.ipk - opkg install /tmp/luci-app-internet-detector_0.2-1_all.ipk - rm /tmp/luci-app-internet-detector_0.2-1_all.ipk - /etc/init.d/rpcd restart + wget --no-check-certificate -O /tmp/internet-detector_0.3.0-1_all.ipk https://github.com/gSpotx2f/luci-app-internet-detector/raw/master/packages/19.07/internet-detector_0.3.0-1_all.ipk + opkg install /tmp/internet-detector_0.3.0-1_all.ipk + rm /tmp/internet-detector_0.3.0-1_all.ipk + + wget --no-check-certificate -O /tmp/luci-app-internet-detector_0.3.0-1_all.ipk https://github.com/gSpotx2f/luci-app-internet-detector/raw/master/packages/19.07/luci-app-internet-detector_0.3.0-1_all.ipk + opkg install /tmp/luci-app-internet-detector_0.3.0-1_all.ipk + rm /tmp/luci-app-internet-detector_0.3.0-1_all.ipk + /etc/init.d/rpcd reload **i18n-ru:** - wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_0.2-1_all.ipk https://github.com/gSpotx2f/luci-app-internet-detector/raw/master/packages/19.07/luci-i18n-internet-detector-ru_0.2-1_all.ipk - opkg install /tmp/luci-i18n-internet-detector-ru_0.2-1_all.ipk - rm /tmp/luci-i18n-internet-detector-ru_0.2-1_all.ipk + wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_0.3.0-1_all.ipk https://github.com/gSpotx2f/luci-app-internet-detector/raw/master/packages/19.07/luci-i18n-internet-detector-ru_0.3.0-1_all.ipk + opkg install /tmp/luci-i18n-internet-detector-ru_0.3.0-1_all.ipk + rm /tmp/luci-i18n-internet-detector-ru_0.3.0-1_all.ipk -**Screenshots:** +**Script for LED control:** + +![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/internet-led.jpg) + +LED is on when Internet is available. A specific LED can be set in `/etc/internet-detector/run-script` (`LEDN`), either by number or by name from /sys/class/leds/*****. The list of available LEDs can be obtained using the command: `/usr/bin/internet-detector-led.sh list`. + + wget --no-check-certificate -O /usr/bin/internet-detector-led.sh https://github.com/gSpotx2f/luci-app-internet-detector/raw/master/led/usr/bin/internet-detector-led.sh + chmod +x /usr/bin/internet-detector-led.sh + wget --no-check-certificate -O /etc/internet-detector/run-script https://github.com/gSpotx2f/luci-app-internet-detector/raw/master/led/etc/internet-detector/run-script + chmod +x /etc/internet-detector/run-script + uci set internet-detector.config.enable_run_script='1' + uci commit + /etc/init.d/internet-detector restart + +### Screenshots: ![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/01.jpg) +![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/03.jpg) +![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/04.jpg) diff --git a/htdocs/luci-static/resources/view/status/include/00_internet.js b/htdocs/luci-static/resources/view/status/include/00_internet.js deleted file mode 100644 index 01d4710..0000000 --- a/htdocs/luci-static/resources/view/status/include/00_internet.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; -'require fs'; - -return L.Class.extend({ - title: _('Internet'), - - hosts: [ - '8.8.8.8', - '2a00:1450:4010:c05::71', - '1.1.1.1', - '2606:4700::6811:b055', - //'8.8.4.4', - //'2a00:1450:4010:c09::66', - ], - - checkInterval: 6, // 5 x 6 = 30 sec. - - load: async function() { - window.internetDetectorCounter = ('internetDetectorCounter' in window) ? - ++window.internetDetectorCounter : 0; - if(!('internetDetectorState' in window)) { - window.internetDetectorState = 1; - }; - - if(window.internetDetectorState === 0 && - window.internetDetectorCounter % this.checkInterval) { - return; - }; - - for(let host of this.hosts) { - await fs.exec('/bin/ping', [ '-c', '1', '-W', '1', host ]).then(res => { - window.internetDetectorState = res.code; - }).catch(e => {}); - - if(window.internetDetectorState === 0) { - break; - }; - }; - }, - - render: function() { - let internetStatus = E('span', { 'class': 'label' }); - - if(window.internetDetectorState === 0) { - internetStatus.style.background = '#46a546'; - internetStatus.textContent = _('Internet connected'); - } else { - internetStatus.textContent = _('Internet disconnected'); - }; - - return E('div', { - 'class': 'cbi-section', - 'style': 'margin-bottom:1em', - }, internetStatus); - }, -}); diff --git a/internet-detector/Makefile b/internet-detector/Makefile new file mode 100644 index 0000000..39bf151 --- /dev/null +++ b/internet-detector/Makefile @@ -0,0 +1,52 @@ +# +# (с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector) +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=internet-detector +PKG_VERSION:=0.3.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=gSpot +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=net + CATEGORY:=Network + TITLE:=Internet detector + URL:=https://github.com/gSpotx2f/luci-app-internet-detector + PKGARCH:=all + DEPENDS:=+lua +luci-lib-nixio +libuci-lua +endef + +define Package/$(PKG_NAME)/description + +endef + +define Package/$(PKG_NAME)/conffiles + /etc/config/internet-detector + /etc/internet-detector/down-script + /etc/internet-detector/run-script + /etc/internet-detector/up-script +endef + +define Build/Prepare + $(CP) -r ./files $(PKG_BUILD_DIR)/files +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/files/usr/bin/internet-detector $(1)/usr/bin/internet-detector + $(INSTALL_DIR) $(1)/etc/internet-detector + $(INSTALL_BIN) $(PKG_BUILD_DIR)/files/etc/internet-detector/down-script $(1)/etc/internet-detector/down-script + $(INSTALL_BIN) $(PKG_BUILD_DIR)/files/etc/internet-detector/run-script $(1)/etc/internet-detector/run-script + $(INSTALL_BIN) $(PKG_BUILD_DIR)/files/etc/internet-detector/up-script $(1)/etc/internet-detector/up-script + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) $(PKG_BUILD_DIR)/files/etc/config/internet-detector $(1)/etc/config/internet-detector + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) $(PKG_BUILD_DIR)/files/etc/init.d/internet-detector $(1)/etc/init.d/internet-detector +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/internet-detector/files/etc/config/internet-detector b/internet-detector/files/etc/config/internet-detector new file mode 100644 index 0000000..c174361 --- /dev/null +++ b/internet-detector/files/etc/config/internet-detector @@ -0,0 +1,18 @@ +config main 'config' + option mode '2' + option enable_logger '1' + option enable_up_script '0' + option enable_down_script '0' + option enable_run_script '0' + option interval_up '30' + option interval_down '5' + option ui_interval_up '6' + option ui_interval_down '1' + list hosts '8.8.8.8' + list hosts '1.1.1.1' + option check_type '0' + option connection_attempts '2' + option connection_timeout '2' + option ui_connection_attempts '1' + option ui_connection_timeout '1' + option tcp_port '53' diff --git a/internet-detector/files/etc/init.d/internet-detector b/internet-detector/files/etc/init.d/internet-detector new file mode 100755 index 0000000..a6fdc8f --- /dev/null +++ b/internet-detector/files/etc/init.d/internet-detector @@ -0,0 +1,19 @@ +#!/bin/sh /etc/rc.common + +START=99 +STOP=01 + +ID="/usr/bin/internet-detector" + +start() { + $ID +} + +stop() { + $ID stop +} + +restart() { + stop + start +} diff --git a/internet-detector/files/etc/internet-detector/down-script b/internet-detector/files/etc/internet-detector/down-script new file mode 100755 index 0000000..e1f53ea --- /dev/null +++ b/internet-detector/files/etc/internet-detector/down-script @@ -0,0 +1 @@ +# Shell commands to run when disconnected from the Internet diff --git a/internet-detector/files/etc/internet-detector/run-script b/internet-detector/files/etc/internet-detector/run-script new file mode 100755 index 0000000..4ee28c6 --- /dev/null +++ b/internet-detector/files/etc/internet-detector/run-script @@ -0,0 +1,4 @@ +# Shell commands that are executed every time the Internet is checked for availability +# +# $1 - (0|1) - internet status: 0 is up, 1 is down +# diff --git a/internet-detector/files/etc/internet-detector/up-script b/internet-detector/files/etc/internet-detector/up-script new file mode 100755 index 0000000..b496240 --- /dev/null +++ b/internet-detector/files/etc/internet-detector/up-script @@ -0,0 +1 @@ +# Shell commands that run when connected to the Internet diff --git a/internet-detector/files/usr/bin/internet-detector b/internet-detector/files/usr/bin/internet-detector new file mode 100755 index 0000000..4514434 --- /dev/null +++ b/internet-detector/files/usr/bin/internet-detector @@ -0,0 +1,391 @@ +#!/usr/bin/env lua + +--[[ + Internet detector daemon for OpenWrt. + + Dependences: + lua + luci-lib-nixio + libuci-lua + + (с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector) +--]] + +-- Default settings + +local Config = { + ["mode"] = 2, + ["enableLogger"] = 1, + ["enableUpScript"] = 0, + ["enableDownScript"] = 0, + ["enableRunScript"] = 0, + ["intervalUp"] = 30, + ["intervalDown"] = 5, + ["connectionAttempts"] = 1, + ["UIConnectionAttempts"] = 1, + ["hosts"] = { + [1] = "8.8.8.8", + [2] = "1.1.1.1", + }, + ["parsedHosts"] = {}, + ["appName"] = "internet-detector", + ["commonDir"] = "/tmp/run", + ["pingCmd"] = "ping", + ["pingParams"] = "-c 1", + ["connectionTimeout"] = 3, + ["UIConnectionTimeout"] = 1, + ["tcpPort"] = 53, + ["checkType"] = 0, -- 0: ping, 1: TCP + ["loggerLevel"] = "info", + --["loggerCmd"] = "logger", +} +Config.configDir = "/etc/" .. Config.appName +Config.upScript = Config.configDir .. "/" .. "up-script" +Config.downScript = Config.configDir .. "/" .. "down-script" +Config.runScript = Config.configDir .. "/" .. "run-script" +Config.pidFile = Config.commonDir .. "/" .. Config.appName .. ".pid" +Config.statusFile = Config.commonDir .. "/" .. Config.appName .. ".status" + +-- Import packages + +local function prequire(package) + local retVal, pkg = pcall(require, package) + return retVal and pkg +end + +local nixio = prequire("nixio") +if not nixio then + error("You need to install nixio...") +end + +local uci = prequire("uci") +if uci then + + -- Load settings from UCI + + local cursor = uci.cursor() + Config.mode = cursor:get("internet-detector", "config", "mode") + Config.enableLogger = cursor:get("internet-detector", "config", "enable_logger") + Config.enableUpScript = cursor:get("internet-detector", "config", "enable_up_script") + Config.enableDownScript = cursor:get("internet-detector", "config", "enable_down_script") + Config.enableRunScript = cursor:get("internet-detector", "config", "enable_run_script") + Config.intervalUp = tonumber(cursor:get("internet-detector", "config", "interval_up")) + Config.intervalDown = tonumber(cursor:get("internet-detector", "config", "interval_down")) + Config.hosts = cursor:get("internet-detector", "config", "hosts") + Config.checkType = tonumber(cursor:get("internet-detector", "config", "check_type")) + Config.connectionAttempts = tonumber(cursor:get("internet-detector", "config", "connection_attempts")) + Config.connectionTimeout = tonumber(cursor:get("internet-detector", "config", "connection_timeout")) + Config.UIConnectionAttempts = tonumber(cursor:get("internet-detector", "config", "ui_connection_attempts")) + Config.UIConnectionTimeout = tonumber(cursor:get("internet-detector", "config", "ui_connection_timeout")) + Config.tcpPort = tonumber(cursor:get("internet-detector", "config", "tcp_port")) + +else + io.stderr:write("libuci-lua does not exists! The default settings will be used...\n") +end + +local function writeValueToFile(filePath, str) + local retValue = false + local fh = io.open(filePath, "w") + if fh then + fh:setvbuf("no") + fh:write(string.format("%s\n", str)) + fh:close() + retValue = true + end + return retValue +end + +local function readValueFromFile(filePath) + local retValue + local fh = io.open(filePath, "r") + if fh then + retValue = fh:read("*l") + fh:close() + end + return retValue +end + +local function writeLogMessage(msg) + if Config.enableLogger == "1" then + local pidValue = readValueFromFile(Config.pidFile) + --local fh = io.popen(string.format('%s -t "%s[%d]" -p daemon.%s "%s"', Config.loggerCmd, Config.appName, (pidValue or ""), Config.loggerLevel, msg), 'r') + --fh:close() + nixio.syslog(Config.loggerLevel, string.format("%s[%d]: %s", Config.appName, (pidValue or ""), msg)) + end +end + +local function runExternalScript(scriptPath, inetStat) + if inetStat == nil then + inetStat = "" + end + + if nixio.fs.access(scriptPath, "x") then + local fh = io.popen(string.format('/bin/sh -c "%s %s" &', scriptPath, inetStat), "r") + fh:close() + end +end + +local function parseHost(host) + local port + local addr = host:match("^[^:]+") + if host:find(":") then + port = host:match("[^:]+$") + end + return addr, port +end + +local function parseHosts() + Config.parsedHosts = {} + for k, v in ipairs(Config.hosts) do + local addr, port = parseHost(v) + Config.parsedHosts[k] = {[1] = addr, [2] = (tonumber(port) or false)} + end +end + +local function pingHost(host) + return os.execute(string.format("%s %s -W %d %s > /dev/null 2>&1", + Config.pingCmd, Config.pingParams, Config.connectionTimeout, host)) +end + +local function tcpConnectToHost(host, port) + local retCode = 1 + local addrInfo = nixio.getaddrinfo(host, "any") + if addrInfo then + local family = addrInfo[1].family + if family then + local socket = nixio.socket(family, "stream") + socket:setopt("socket", "sndtimeo", Config.connectionTimeout) + socket:setopt("socket", "rcvtimeo", Config.connectionTimeout) + local success = socket:connect(host, port or Config.tcpPort) + socket:close() + retCode = success and 0 or 1 + end + end + return retCode +end + +local function checkHosts() + local checkFunc = (Config.checkType == 1) and tcpConnectToHost or pingHost + local retCode = 1 + for k, v in ipairs(Config.parsedHosts) do + for i = 1, Config.connectionAttempts do + if checkFunc(v[1], v[2]) == 0 then + retCode = 0 + break + end + end + if retCode == 0 then + break + end + end + return retCode +end + +local function main() + local last_status + local current_status + local interval = Config.intervalUp + + while true do + current_status = checkHosts() + if not nixio.fs.access(Config.statusFile, "r") then + writeValueToFile(Config.statusFile, current_status) + end + + if current_status == 0 then + interval = Config.intervalUp + if last_status ~= nil and current_status ~= last_status then + writeValueToFile(Config.statusFile, current_status) + writeLogMessage("internet connected") + if Config.enableUpScript == "1" then + runExternalScript(Config.upScript) + end + end + else + interval = Config.intervalDown + if last_status ~= nil and current_status ~= last_status then + writeValueToFile(Config.statusFile, current_status) + writeLogMessage("internet disconnected") + if Config.enableDownScript == "1" then + runExternalScript(Config.downScript) + end + end + end + + if Config.enableRunScript == "1" then + runExternalScript(Config.runScript, current_status) + end + + last_status = current_status + nixio.nanosleep(interval) + end +end + +local function removeProcessFiles() + os.remove(Config.pidFile) + os.remove(Config.statusFile) +end + +local function status() + if nixio.fs.access(Config.pidFile, "r") then + return "running" + else + return "stoped" + end +end + +local function poll(attempts, timeout) + if Config.mode == "1" then + Config.connectionAttempts = Config.UIConnectionAttempts + Config.connectionTimeout = Config.UIConnectionTimeout + end + if attempts then + Config.connectionAttempts = attempts + end + if timeout then + Config.connectionTimeout = timeout + end + if checkHosts() == 0 then + return "up" + else + return "down" + end +end + +local function inetStatus() + local inetStat = "down" + if nixio.fs.access(Config.statusFile, "r") then + local inetStatVal = readValueFromFile(Config.statusFile) + if inetStatVal ~= nil and tonumber(inetStatVal) == 0 then + inetStat = "up" + end + elseif Config.mode == "1" then + inetStat = poll() + else + os.exit(126) + end + return inetStat +end + +local function stop() + local pidValue + if nixio.fs.access(Config.pidFile, "r") then + pidValue = readValueFromFile(Config.pidFile) + if pidValue then + local success + for i = 0, 10 do + success = nixio.kill(tonumber(pidValue), 15) + if success then + break + end + end + if not success then + io.stderr:write(string.format('No such process: "%s"\n', pidValue)) + end + writeLogMessage('stoped') + removeProcessFiles() + end + end + + if not pidValue then + io.stderr:write( + string.format('PID file "%s" does not exist. %s not running?\n', + Config.pidFile, Config.appName)) + end +end + +local function preRun() + -- Exit if internet detector mode != 2(Service) + if Config.mode ~= "2" then + io.stderr:write(string.format('Start failed, mode != "2"\n', Config.appName)) + os.exit(0) + end + if nixio.fs.access(Config.pidFile, "r") then + io.stderr:write( + string.format('PID file "%s" already exist. %s already running?\n', + Config.pidFile, Config.appName)) + return false + end + return true +end + +local function run() + local pidValue = nixio.getpid() + writeValueToFile(Config.pidFile, pidValue) + writeLogMessage('started') + main() +end + +local function noDaemon() + if not preRun() then + return + end + run() +end + +local function daemon() + if not preRun() then + return + end + -- UNIX double fork + if nixio.fork() == 0 then + nixio.setsid() + if nixio.fork() == 0 then + nixio.chdir("/") + nixio.umask(0) + local devnull = "/dev/null" + io.stdout:flush() + io.stderr:flush() + nixio.dup(io.open(devnull, "r"), io.stdin) + nixio.dup(io.open(devnull, "a+"), io.stdout) + nixio.dup(io.open(devnull, "a+"), io.stderr) + run() + end + os.exit(0) + end + os.exit(0) +end + +local function restart() + stop() + daemon() +end + +-- Main section + +parseHosts() + +local function help() + return string.format("Usage: %s [start|no-daemon|stop|restart|status|inet-status|poll [] []|--help]", arg[0]) +end + +local helpArgs = {["-h"] = true, ["--help"] = true, ["help"] = true} +if arg[1] == "start" or #arg == 0 then + daemon() +elseif arg[1] == "no-daemon" then + noDaemon() +elseif arg[1] == "stop" then + stop() +elseif arg[1] == "restart" then + restart() +elseif arg[1] == "status" then + print(status()) +elseif arg[1] == "inet-status" then + print(inetStatus()) +elseif arg[1] == "poll" then + local attempts, timeout + if arg[2] and arg[2]:match("[0-9]+") then + attempts = tonumber(arg[2]) + if arg[3] and arg[3]:match("[0-9]+") then + timeout = tonumber(arg[3]) + end + end + print(poll(attempts, timeout)) +elseif helpArgs[arg[1]] then + print(help()) +else + print(help()) + os.exit(1) +end + +os.exit(0) diff --git a/led/etc/internet-detector/run-script b/led/etc/internet-detector/run-script new file mode 100755 index 0000000..9a1e874 --- /dev/null +++ b/led/etc/internet-detector/run-script @@ -0,0 +1,14 @@ +# Shell commands that are executed every time the Internet is checked for availability +# +# $1 : (0|1) - internet status: 0 is up, 1 is down +# + +LEDN=1 +LED_CMD="/usr/bin/internet-detector-led.sh" +LED_STATE=`$LED_CMD state $LEDN` + +if [ $LED_STATE -eq 0 -a $1 -eq 0 ]; then + $LED_CMD on $LEDN +elif [ $LED_STATE -ne 0 -a $1 -eq 1 ]; then + $LED_CMD off $LEDN +fi diff --git a/led/usr/bin/internet-detector-led.sh b/led/usr/bin/internet-detector-led.sh new file mode 100755 index 0000000..237ec14 --- /dev/null +++ b/led/usr/bin/internet-detector-led.sh @@ -0,0 +1,63 @@ +#/bin/sh + +# +# Usage: internet-detector-led.sh on|off|state|list [|] +# +# Examples: +# internet-detector-led.sh list # list of available LEDs +# internet-detector-led.sh on 2 # turn on LED 2 +# internet-detector-led.sh off 2 # turn off LED 2 +# internet-detector-led.sh on "nbg6817:white:internet" # turn on LED by sysfs-name (/sys/class/leds/nbg6817:white:internet) +# internet-detector-led.sh off "nbg6817:white:internet" # turn off --"-- +# internet-detector-led.sh on # same as "internet-detector-led.sh on 1" (default is 1) +# internet-detector-led.sh off # turn off --"-- +# internet-detector-led.sh state 2 # current state (brightness) of LED 2 +# ... +# + +if [ -n $2 ]; then + LEDN=$2 +else + LEDN=1 +fi + +SYSFS_LEDS="/sys/class/leds/" + +LED="`ls -1 $SYSFS_LEDS | awk -v LEDN=$LEDN '{ + LEDN = (length(LEDN) == 0) ? 1 : LEDN; + if($0 == LEDN || NR == LEDN) { + print $0; + exit; + } +}'`" 2> /dev/null + +LED_BR_PATH="${SYSFS_LEDS}/${LED}/brightness" + +[ -w $LED_BR_PATH ] || exit 1 + +MAX_BRIGHTNESS=`cat ${SYSFS_LEDS}/${LED}/max_brightness` 2> /dev/null + +if [ -z $MAX_BRIGHTNESS ]; then + MAX_BRIGHTNESS=1 +fi + +case $1 in +on) + printf $MAX_BRIGHTNESS > $LED_BR_PATH +;; +off) + printf 0 > $LED_BR_PATH +;; +state) + cat $LED_BR_PATH 2> /dev/null +;; +list) + ls -1 $SYSFS_LEDS | awk '{print NR ": " $0}' +;; +*) + echo "Usage: `basename $0` on|off|state|list [|]" >&2 + exit 1 +;; +esac + +exit 0; diff --git a/luci-app-internet-detector/Makefile b/luci-app-internet-detector/Makefile new file mode 100644 index 0000000..9a80e67 --- /dev/null +++ b/luci-app-internet-detector/Makefile @@ -0,0 +1,16 @@ +# +# (с) 2021 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector) +# + +include $(TOPDIR)/rules.mk + +PKG_VERSION:=0.3.0 +PKG_RELEASE:=1 +LUCI_TITLE:=LuCI support for internet-detector +LUCI_DEPENDS:=+internet-detector +LUCI_PKGARCH:=all + +#include ../../luci.mk +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-internet-detector/htdocs/luci-static/resources/view/internet-detector.js b/luci-app-internet-detector/htdocs/luci-static/resources/view/internet-detector.js new file mode 100644 index 0000000..c31c130 --- /dev/null +++ b/luci-app-internet-detector/htdocs/luci-static/resources/view/internet-detector.js @@ -0,0 +1,554 @@ +'use strict'; +'require form'; +'require fs'; +'require rpc'; +'require uci'; +'require ui'; + +const btnStyleEnabled = 'btn cbi-button-save'; +const btnStyleDisabled = 'btn cbi-button-reset'; +const btnStyleApply = 'btn cbi-button-apply'; + +return L.view.extend({ + execPath : '/usr/bin/internet-detector', + initPath : '/etc/init.d/internet-detector', + upScriptPath : '/etc/internet-detector/up-script', + downScriptPath : '/etc/internet-detector/down-script', + runScriptPath : '/etc/internet-detector/run-script', + pollInterval : L.env.pollinterval, + appStatus : 'stoped', + initStatus : null, + inetStatus : null, + inetStatusLabel : E('span', { 'class': 'label' }), + inetStatusSpinner : E('span', { 'class': 'spinning', 'style': 'margin-top:1em' }, ' '), + serviceStatusLabel : E('em'), + serviceButton : null, + initButton : null, + uiPollCounter : 0, + uiPollState : null, + uiCheckIntervalUp : null, + uiCheckIntervalDown : null, + currentAppMode : '0', + + callInitAction: rpc.declare({ + object: 'luci', + method: 'setInitAction', + params: [ 'name', 'action' ], + expect: { result: false } + }), + + handleServiceAction: function(action) { + return this.callInitAction('internet-detector', action).then(success => { + if(!success) { + throw _('Command failed'); + }; + return true; + }).catch(e => { + ui.addNotification(null, + E('p', _('Failed to execute "%s %s": %s').format(this.initPath, action, e))); + }); + }, + + serviceRestart: function(ev) { + L.Poll.stop(); + return this.handleServiceAction('restart').then(() => { + this.servicePoll(); + L.Poll.start(); + }); + }, + + fileEditDialog: L.Class.extend({ + __init__: function(file, title, description, callback, fileExists=false) { + this.file = file; + this.title = title; + this.description = description; + this.callback = callback; + this.fileExists = fileExists; + }, + + load: function() { + return fs.read(this.file); + }, + + render: function(content) { + ui.showModal(this.title, [ + E('div', { 'class': 'cbi-section' }, [ + E('div', { 'class': 'cbi-section-descr' }, this.description), + E('div', { 'class': 'cbi-section' }, + E('p', {}, + E('textarea', { + 'id': 'widget.modal_content', + 'class': 'cbi-input-textarea', + 'style': 'width:100% !important', + 'rows': 10, + 'wrap': 'off', + 'spellcheck': 'false', + }, + content || '') + ) + ), + ]), + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'btn', + 'click': ui.hideModal, + }, _('Dismiss')), + ' ', + E('button', { + 'id': 'btn_save', + 'class': 'btn cbi-button-positive important', + 'click': ui.createHandlerFn(this, this.handleSave), + }, _('Save')), + ]), + ]); + }, + + handleSave: function(ev) { + let textarea = document.getElementById('widget.modal_content'); + let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n'; + + return fs.write(this.file, value).then(rc => { + textarea.value = value; + ui.addNotification(null, E('p', _('Contents have been saved.')), + 'info'); + if(this.callback) { + return this.callback(rc); + }; + }).catch(e => { + ui.addNotification(null, E('p', _('Unable to save the contents') + + ': %s'.format(e.message))); + }).finally(() => { + ui.hideModal(); + }); + }, + + error: function(e) { + if(!this.fileExists && e instanceof Error && e.name === 'NotFoundError') { + return this.render(); + } else { + ui.showModal(this.title, [ + E('div', { 'class': 'cbi-section' }, + E('p', {}, _('Unable to read the contents') + + ': %s'.format(e.message)) + ), + E('div', { 'class': 'right' }, + E('button', { + 'class': 'btn', + 'click': ui.hideModal, + }, _('Dismiss')) + ), + ]); + }; + }, + + show: function() { + ui.showModal(null, + E('p', { 'class': 'spinning' }, _('Loading')) + ); + this.load().then(content => { + ui.hideModal(); + return this.render(content); + }).catch(e => { + ui.hideModal(); + return this.error(e); + }) + }, + }), + + setInternetStatus: function(initial=false) { + if(this.inetStatus === 'up') { + this.inetStatusLabel.style.background = '#46a546'; + this.inetStatusLabel.textContent = _('Connected'); + } + else if(this.inetStatus === 'down') { + this.inetStatusLabel.textContent = _('Disconnected'); + this.inetStatusLabel.style.background = '#ff6c74'; + } + else { + this.inetStatusLabel.textContent = _('Undefined'); + this.inetStatusLabel.style.background = '#cccccc'; + }; + + if(!initial && this.inetStatusSpinner) { + this.inetStatusSpinner.remove(); + }; + + if(this.appStatus === 'running') { + this.serviceStatusLabel.textContent = _('Running'); + } else { + this.serviceStatusLabel.textContent = _('Stopped'); + }; + }, + + CBIBlockService: form.DummyValue.extend({ + ctx: null, + + renderWidget: function(section_id, option_index, cfgvalue) { + this.title = this.description = null; + + this.ctx.serviceButton = E('button', { + 'class': btnStyleApply, + 'click': ui.createHandlerFn(this.ctx, this.ctx.serviceRestart), + }, _('Restart')); + this.ctx.initButton = E('button', { + 'class': (this.ctx.initStatus === 1) ? btnStyleDisabled : btnStyleEnabled, + 'click': ui.createHandlerFn(this, () => { + return this.ctx.handleServiceAction( + (this.ctx.initStatus === 1) ? 'enable' : 'disable' + ).then(success => { + if(!success) { + return; + }; + if(this.ctx.initStatus === 1) { + this.ctx.initButton.textContent = _('Enabled'); + this.ctx.initButton.className = btnStyleEnabled; + this.ctx.initStatus = 0; + } + else { + this.ctx.initButton.textContent = _('Disabled'); + this.ctx.initButton.className = btnStyleDisabled; + this.ctx.initStatus = 1; + }; + }); + }), + }, (this.ctx.initStatus == 1) ? _('Disabled') : _('Enabled')); + + this.ctx.setInternetStatus(true); + + let serviceItems = ''; + if(this.ctx.currentAppMode === '2') { + serviceItems = E([ + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, + _('Service') + ), + E('div', { 'class': 'cbi-value-field' }, + this.ctx.serviceStatusLabel + ), + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, + _('Restart service') + ), + E('div', { 'class': 'cbi-value-field' }, + this.ctx.serviceButton + ), + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, + _('Run service at startup') + ), + E('div', { 'class': 'cbi-value-field' }, + this.ctx.initButton + ), + ]), + ]); + }; + + let internetStatus = (this.ctx.currentAppMode !== '0') ? + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, + _('Internet status') + ), + E('div', { 'class': 'cbi-value-field' }, [ + this.ctx.inetStatusLabel, + (!this.ctx.inetStatus) ? this.ctx.inetStatusSpinner : '', + ]), + ]) + : ''; + + return E('div', { 'class': 'cbi-section fade-in' }, + E('div', { 'class': 'cbi-section-node' }, [ + internetStatus, + serviceItems, + ]) + ); + }, + }), + + servicePoll: function() { + return Promise.all([ + fs.exec(this.execPath, [ 'status' ]), + fs.exec(this.execPath, [ 'inet-status' ]), + ]).then(stat => { + let curAppStatus = (stat[0].code === 0) ? stat[0].stdout.trim() : null; + let curInetStatus = (stat[1].code === 0) ? stat[1].stdout.trim() : null; + + if(this.inetStatus === curInetStatus && this.appStatus === curAppStatus) { + return; + }; + this.appStatus = curAppStatus; + this.inetStatus = curInetStatus; + this.setInternetStatus(); + }).catch(e => { + this.appStatus = 'stoped'; + this.inetStatus = null; + }); + }, + + uiPoll: function() { + let curInetStatus = null; + this.uiPollCounter = ++this.uiPollCounter; + + if((this.uiPollState === 0 && this.uiPollCounter % this.uiCheckIntervalUp) || + (this.uiPollState === 1 && this.uiPollCounter % this.uiCheckIntervalDown)) { + return; + }; + + this.uiPollCounter = 0; + + return fs.exec(this.execPath, [ 'inet-status' ]).then(res => { + this.uiPollState = (res.code === 0 && res.stdout.trim() === 'up') ? 0 : 1; + + if(this.uiPollState === 0) { + curInetStatus = 'up'; + } else { + curInetStatus = 'down'; + }; + + if(this.inetStatus !== curInetStatus) { + this.inetStatus = (this.currentAppMode === '0') ? null : curInetStatus; + this.setInternetStatus(); + }; + }); + }, + + load: function() { + return Promise.all([ + fs.exec(this.execPath, [ 'status' ]), + fs.exec(this.initPath, [ 'enabled' ]), + uci.load('internet-detector'), + ]).catch(e => { + ui.addNotification(null, E('p', _('An error has occurred') + ': %s'.format(e.message))); + }); + }, + + render: function(data) { + if(!data) { + return; + }; + this.appStatus = (data[0].code === 0) ? data[0].stdout.trim() : null; + this.initStatus = data[1].code; + this.currentAppMode = uci.get('internet-detector', 'config', 'mode'); + this.uiCheckIntervalUp = Number(uci.get('internet-detector', 'config', 'ui_interval_up')); + this.uiCheckIntervalDown = Number(uci.get('internet-detector', 'config', 'ui_interval_down')); + + let upScriptEditDialog = new this.fileEditDialog( + this.upScriptPath, + _('up-script'), + _('Shell commands that run when connected to the Internet'), + ); + let downScriptEditDialog = new this.fileEditDialog( + this.downScriptPath, + _('down-script'), + _('Shell commands to run when disconnected from the Internet'), + ); + let runScriptEditDialog = new this.fileEditDialog( + this.runScriptPath, + _('run-script'), + _("Shell commands that are executed every time the Internet is checked for availability"), + ); + + let m, s, o; + + m = new form.Map('internet-detector', _('Internet detector'), + _('Checking Internet availability.')); + + s = m.section(form.NamedSection, 'config'); + s.anonymous = true; + s.addremove = false; + + s.tab('main_settings', _('Main settings')); + + // service section + o = s.taboption('main_settings', this.CBIBlockService, '_dummy_service'); + o.ctx = this; + + // mode + o = s.taboption('main_settings', form.ListValue, + 'mode', _('Internet detector mode')); + o.value('0', _('Disabled')); + o.value('1', _('Web UI only')); + o.value('2', _('Service')); + o.description = '%s;
%s;
%s;'.format( + _('Disabled: detector is completely off'), + _('Web UI only: detector works only when the Web UI is open (UI detector)'), + _('Service: detector always runs as a system service') + ); + + // hosts + o = s.taboption('main_settings', form.DynamicList, + 'hosts', _('Hosts')); + o.description = _('Hosts to check Internet availability. Hosts are polled (in list order) until at least one of them responds'); + o.datatype = 'or(host,hostport)'; + + // check_type + o = s.taboption('main_settings', form.ListValue, + 'check_type', _('Check type')); + o.description = _('Host availability check type'); + o.value(0, _('Ping host')); + o.value(1, _('TCP port connection')); + + // tcp_port + o = s.taboption('main_settings', form.Value, + 'tcp_port', _('TCP port')); + o.description = _('Default port value for TCP connections'); + o.rmempty = false; + o.datatype = "port"; + + s.tab('ui_detector_configuration', _('UI detector configuration')); + + let makeUIIntervalOptions = L.bind(function(list) { + list.value(1, '%d %s'.format(this.pollInterval, _('sec'))); + list.value(2, '%d %s'.format(this.pollInterval * 2, _('sec'))); + list.value(3, '%d %s'.format(this.pollInterval * 3, _('sec'))); + list.value(4, '%d %s'.format(this.pollInterval * 4, _('sec'))); + list.value(5, '%d %s'.format(this.pollInterval * 5, _('sec'))); + list.value(6, '%d %s'.format(this.pollInterval * 6, _('sec'))); + }, this); + + // ui_interval_up + o = s.taboption('ui_detector_configuration', form.ListValue, + 'ui_interval_up', _('Alive interval')); + o.description = _('Hosts polling interval when the Internet is up'); + makeUIIntervalOptions(o); + + // ui_interval_down + o = s.taboption('ui_detector_configuration', form.ListValue, + 'ui_interval_down', _('Dead interval')); + o.description = _('Hosts polling interval when the Internet is down'); + makeUIIntervalOptions(o); + + // ui_connection_attempts + o = s.taboption('ui_detector_configuration', form.ListValue, + 'ui_connection_attempts', _('Connection attempts')); + o.description = _('Maximum number of attempts to connect to each host'); + o.value(1); + o.value(2); + o.value(3); + + // ui_connection_timeout + o = s.taboption('ui_detector_configuration', form.ListValue, + 'ui_connection_timeout', _('Connection timeout')); + o.description = _('Maximum timeout for waiting for a response from the host'); + o.value(1, "1 " + _('sec')); + o.value(2, "2 " + _('sec')); + o.value(3, "3 " + _('sec')); + + s.tab('service_configuration', _('Service configuration')); + + // enable_logger + o = s.taboption('service_configuration', form.Flag, + 'enable_logger', _('Enable logging')); + o.description = _('Write messages to the system log'); + o.rmempty = false; + + // enable_up_script + o = s.taboption('service_configuration', form.Flag, + 'enable_up_script', _('Enable up-script')); + o.description = _('Execute commands when the Internet is connected'); + o.rmempty = false; + + // up_script edit dialog + o = s.taboption('service_configuration', form.Button, + '_up_script_btn', _('Edit up-script')); + o.onclick = () => upScriptEditDialog.show(); + o.inputtitle = _('Edit'); + o.inputstyle = 'edit btn'; + + // enable_down_script + o = s.taboption('service_configuration', form.Flag, + 'enable_down_script', _('Enable down-script')); + o.description = _('Execute commands when the Internet is disconnected'); + o.rmempty = false; + + // down_script edit dialog + o = s.taboption('service_configuration', form.Button, + '_down_script_btn', _('Edit down-script')); + o.onclick = () => downScriptEditDialog.show(); + o.inputtitle = _('Edit'); + o.inputstyle = 'edit btn'; + + // enable_run_script + o = s.taboption('service_configuration', form.Flag, + 'enable_run_script', _('Enable run-script')); + o.description = _('Execute commands every time the Internet is checked for availability'); + o.rmempty = false; + + // run_script edit dialog + o = s.taboption('service_configuration', form.Button, + '_run_script_btn', _('Edit run-script')); + o.onclick = () => runScriptEditDialog.show(); + o.inputtitle = _('Edit'); + o.inputstyle = 'edit btn'; + + function makeIntervalOptions(list) { + list.value(2, '2 ' + _('sec')); + list.value(5, '5 ' + _('sec')); + list.value(10, '10 ' + _('sec')); + list.value(15, '15 ' + _('sec')); + list.value(20, '20 ' + _('sec')); + list.value(25, '25 ' + _('sec')); + list.value(30, '30 ' + _('sec')); + list.value(60, '1 ' + _('min')); + list.value(120, '2 ' + _('min')); + list.value(300, '5 ' + _('min')); + list.value(600, '10 ' + _('min')); + } + + // interval_up + o = s.taboption('service_configuration', form.ListValue, + 'interval_up', _('Alive interval')); + o.description = _('Hosts polling interval when the Internet is up'); + makeIntervalOptions(o); + + // interval_down + o = s.taboption('service_configuration', form.ListValue, + 'interval_down', _('Dead interval')); + o.description = _('Hosts polling interval when the Internet is down'); + makeIntervalOptions(o); + + // connection_attempts + o = s.taboption('service_configuration', form.ListValue, + 'connection_attempts', _('Connection attempts')); + o.description = _('Maximum number of attempts to connect to each host'); + o.value(1); + o.value(2); + o.value(3); + o.value(4); + o.value(5); + + // connection_timeout + o = s.taboption('service_configuration', form.ListValue, + 'connection_timeout', _('Connection timeout')); + o.description = _('Maximum timeout for waiting for a response from the host'); + o.value(1, "1 " + _('sec')); + o.value(2, "2 " + _('sec')); + o.value(3, "3 " + _('sec')); + o.value(4, "4 " + _('sec')); + o.value(5, "5 " + _('sec')); + o.value(6, "6 " + _('sec')); + o.value(7, "7 " + _('sec')); + o.value(8, "8 " + _('sec')); + o.value(9, "9 " + _('sec')); + o.value(10, "10 " + _('sec')); + + if(this.currentAppMode !== '0') { + L.Poll.add( + L.bind((this.currentAppMode === '2') ? this.servicePoll : this.uiPoll, this), + this.pollInterval + ); + }; + + let mapPromise = m.render(); + mapPromise.then(node => node.classList.add('fade-in')); + return mapPromise; + }, + + handleSaveApply: function(ev, mode) { + return this.handleSave(ev).then(() => { + ui.changes.apply(mode == '0'); + window.setTimeout(() => this.serviceRestart(), 3000); + }); + }, +}); diff --git a/luci-app-internet-detector/htdocs/luci-static/resources/view/status/include/00_internet.js b/luci-app-internet-detector/htdocs/luci-static/resources/view/status/include/00_internet.js new file mode 100644 index 0000000..4073cde --- /dev/null +++ b/luci-app-internet-detector/htdocs/luci-static/resources/view/status/include/00_internet.js @@ -0,0 +1,85 @@ +'use strict'; +'require fs'; +'require uci'; + +return L.Class.extend({ + title : _('Internet'), + execPath : '/usr/bin/internet-detector', + inetStatus : null, + + load: async function() { + if(!( + 'uiCheckIntervalUp' in window && + 'uiCheckIntervalDown' in window && + 'currentAppMode' in window + )) { + await uci.load('internet-detector').then(data => { + window.uiCheckIntervalUp = Number(uci.get('internet-detector', 'config', 'ui_interval_up')); + window.uiCheckIntervalDown = Number(uci.get('internet-detector', 'config', 'ui_interval_down')); + window.currentAppMode = uci.get('internet-detector', 'config', 'mode'); + }).catch(e => {}); + }; + + if(window.currentAppMode === '1' || window.currentAppMode === '2') { + window.internetDetectorCounter = ('internetDetectorCounter' in window) ? + ++window.internetDetectorCounter : 0; + + if(!('internetDetectorState' in window)) { + window.internetDetectorState = 2; + }; + + if(window.currentAppMode === '1' && ( + (window.internetDetectorState === 0 && window.internetDetectorCounter % window.uiCheckIntervalUp) || + (window.internetDetectorState === 1 && window.internetDetectorCounter % window.uiCheckIntervalDown) + )) { + return; + }; + + window.internetDetectorCounter = 0; + + return L.resolveDefault(fs.exec(this.execPath, [ 'inet-status' ]), null); + } + else { + window.internetDetectorState = 2; + }; + }, + + render: function(data) { + if(window.currentAppMode === '0') { + return + }; + + if(data) { + this.inetStatus = (data.code === 0) ? data.stdout.trim() : null; + if(this.inetStatus === 'up') { + window.internetDetectorState = 0; + } + else if(this.inetStatus === 'down') { + window.internetDetectorState = 1; + } + else { + window.internetDetectorState = 2; + }; + }; + + let internetStatus = E('span', { 'class': 'label' }); + + if(window.internetDetectorState === 0) { + internetStatus.textContent = _('Connected'); + internetStatus.style.background = '#46a546'; + } + else if(window.internetDetectorState === 1) { + internetStatus.textContent = _('Disconnected'); + internetStatus.style.background = '#ff6c74'; + } + else { + internetStatus.textContent = _('Undefined'); + internetStatus.background = '#cccccc'; + }; + + return E('div', { + 'class': 'cbi-section', + 'style': 'margin-bottom:1em', + }, internetStatus); + }, +}); diff --git a/luci-app-internet-detector/luasrc/controller/internet-detector.lua b/luci-app-internet-detector/luasrc/controller/internet-detector.lua new file mode 100644 index 0000000..a01dc91 --- /dev/null +++ b/luci-app-internet-detector/luasrc/controller/internet-detector.lua @@ -0,0 +1,8 @@ + +module('luci.controller.internet-detector', package.seeall) + +function index() + if nixio.fs.access('/usr/bin/internet-detector', 'x') then + entry({'admin', 'services', 'internet-detector'}, view('internet-detector'), _('Internet detector'), 10).acl_depends = { 'luci-app-internet-detector' } + end +end diff --git a/luci-app-internet-detector/po/ru/internet-detector.po b/luci-app-internet-detector/po/ru/internet-detector.po new file mode 100644 index 0000000..de47677 --- /dev/null +++ b/luci-app-internet-detector/po/ru/internet-detector.po @@ -0,0 +1,217 @@ +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.4.2\n" +"Last-Translator: \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"Language: ru\n" + +msgid "Alive interval" +msgstr "Интервал при подключении" + +msgid "An error has occurred" +msgstr "Произошла ошибка" + +msgid "Check type" +msgstr "Тип проверки" + +msgid "Checking Internet availability." +msgstr "Проверка доступности Интернет." + +msgid "Command failed" +msgstr "Команда не выполнена" + +msgid "Connected" +msgstr "Подключен" + +msgid "Connection timeout" +msgstr "Таймаут соединения" + +msgid "Connections" +msgstr "Подключения" + +msgid "Contents have been saved." +msgstr "Содержимое сохранено." + +msgid "Dead interval" +msgstr "Интервал при отключении" + +msgid "Default port value for TCP connections" +msgstr "Стандартное значение порта для TCP-подключений" + +msgid "Disabled" +msgstr "Отключен" + +msgid "Disabled: detector is completely off" +msgstr "Отключен: детектор полностью выключен" + +msgid "Disconnected" +msgstr "Отключен" + +msgid "Dismiss" +msgstr "Отмена" + +msgid "Edit" +msgstr "Изменить" + +msgid "Edit down-script" +msgstr "Изменить down-script" + +msgid "Edit run-script" +msgstr "Изменить run-script" + +msgid "Edit up-script" +msgstr "Изменить up-script" + +msgid "Enable down-script" +msgstr "Включить down-script" + +msgid "Enable logging" +msgstr "Включить логирование" + +msgid "Enable run-script" +msgstr "Включить run-script" + +msgid "Enable up-script" +msgstr "Включить up-script" + +msgid "Enabled" +msgstr "Включен" + +msgid "Execute commands every time the Internet is checked for availability" +msgstr "Выполнение команд при каждой проверке доступности Интернет" + +msgid "Execute commands when the Internet is connected" +msgstr "Выполнение команд при подключении к Интернет" + +msgid "Execute commands when the Internet is disconnected" +msgstr "Выполнение команд при отключении от Интернет" + +msgid "Failed to execute \"%s %s\": %s" +msgstr "Не удалось выполнить \"%s %s\": %s" + +msgid "Host availability check type" +msgstr "Тип проверки доступности хоста" + +msgid "Hosts" +msgstr "Хосты" + +msgid "Hosts polling interval when the Internet is down" +msgstr "Интервал опроса хостов если Интернет не доступен" + +msgid "Hosts polling interval when the Internet is up" +msgstr "Интервал опроса хостов если Интернет доступен" + +msgid "Hosts to check Internet availability. Hosts are polled (in list order) until at least one of them responds" +msgstr "Хосты для проверки доступности Интернет. Хосты опрашиваются (в порядке списка) до тех пор, пока хотя бы один из них не ответит" + +msgid "Internet" +msgstr "Интернет" + +msgid "Internet detector" +msgstr "Интернет-детектор" + +msgid "Internet detector mode" +msgstr "Режим интернет-детектора" + +msgid "Internet status" +msgstr "Статус Интернет" + +msgid "Loading" +msgstr "Загрузка" + +msgid "Main settings" +msgstr "Основные настройки" + +msgid "Maximum timeout for waiting for a response from the host" +msgstr "Маскимальный таймаут ожидания ответа от хоста" + +msgid "Maximum number of attempts to connect to each host" +msgstr "Максимальное количество попыток подключения к каждому хосту" + +msgid "Ping host" +msgstr "Пинг хоста" + +msgid "Restart" +msgstr "Перезапуск" + +msgid "Restart service" +msgstr "Перезапуск службы" + +msgid "Run service at startup" +msgstr "Запуск службы при старте" + +msgid "Running" +msgstr "Выполняется" + +msgid "Save" +msgstr "Сохранить" + +msgid "Service" +msgstr "Служба" + +msgid "Service configuration" +msgstr "Конфигурация службы" + +msgid "Service: detector always runs as a system service" +msgstr "Служба: детектор работает постоянно, как системная служба" + +msgid "Shell commands that are executed every time the Internet is checked for availability" +msgstr "Команды shell выполняемые при каждой проверке доступности Интернет" + +msgid "Shell commands that run when connected to the Internet" +msgstr "Команды shell выполняемые при подключении к Интернет" + +msgid "Shell commands to run when disconnected from the Internet" +msgstr "Команды shell выполняемые при отключении от Интернет" + +msgid "Stopped" +msgstr "Остановлена" + +msgid "TCP port" +msgstr "TCP-порт" + +msgid "TCP port connection" +msgstr "Подключение к TCP-порту" + +msgid "UI detector configuration" +msgstr "Конфигурация UI детектора" + +msgid "Unable to read the contents" +msgstr "Невозможно прочитать содержимое" + +msgid "Unable to save the contents" +msgstr "Невозможно сохранить содержимое" + +msgid "Undefined" +msgstr "Неопределён" + +msgid "Web UI only" +msgstr "Только web-интерфейс" + +msgid "Web UI only: detector works only when the Web UI is open (UI detector)" +msgstr "Только web-интерфейс: детектор работает только в web-интерфейсе (UI детектор)" + +msgid "Write messages to the system log" +msgstr "Записывать сообщения в системный журнал" + +msgid "down-script" +msgstr "" + +msgid "min" +msgstr "мин" + +msgid "run-script" +msgstr "" + +msgid "sec" +msgstr "сек" + +msgid "up-script" +msgstr "" diff --git a/luci-app-internet-detector/po/templates/internet-detector.pot b/luci-app-internet-detector/po/templates/internet-detector.pot new file mode 100644 index 0000000..d858368 --- /dev/null +++ b/luci-app-internet-detector/po/templates/internet-detector.pot @@ -0,0 +1,203 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "Alive interval" +msgstr "" + +msgid "An error has occurred" +msgstr "" + +msgid "Check type" +msgstr "" + +msgid "Checking Internet availability." +msgstr "" + +msgid "Command failed" +msgstr "" + +msgid "Connected" +msgstr "" + +msgid "Connection timeout" +msgstr "" + +msgid "Connections" +msgstr "" + +msgid "Contents have been saved." +msgstr "" + +msgid "Dead interval" +msgstr "" + +msgid "Default port value for TCP connections" +msgstr "" + +msgid "Disabled" +msgstr "" + +msgid "Disabled: detector is completely off" +msgstr "" + +msgid "Disconnected" +msgstr "" + +msgid "Dismiss" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit down-script" +msgstr "" + +msgid "Edit run-script" +msgstr "" + +msgid "Edit up-script" +msgstr "" + +msgid "Enable down-script" +msgstr "" + +msgid "Enable logging" +msgstr "" + +msgid "Enable run-script" +msgstr "" + +msgid "Enable up-script" +msgstr "" + +msgid "Enabled" +msgstr "" + +msgid "Execute commands every time the Internet is checked for availability" +msgstr "" + +msgid "Execute commands when the Internet is connected" +msgstr "" + +msgid "Execute commands when the Internet is disconnected" +msgstr "" + +msgid "Failed to execute \"%s %s\": %s" +msgstr "" + +msgid "Host availability check type" +msgstr "" + +msgid "Hosts" +msgstr "" + +msgid "Hosts polling interval when the Internet is down" +msgstr "" + +msgid "Hosts polling interval when the Internet is up" +msgstr "" + +msgid "Hosts to check Internet availability. Hosts are polled (in list order) until at least one of them responds" +msgstr "" + +msgid "Internet" +msgstr "" + +msgid "Internet detector" +msgstr "" + +msgid "Internet detector mode" +msgstr "" + +msgid "Internet status" +msgstr "" + +msgid "Loading" +msgstr "" + +msgid "Main settings" +msgstr "" + +msgid "Maximum timeout for waiting for a response from the host" +msgstr "" + +msgid "Maximum number of attempts to connect to each host" +msgstr "" + +msgid "Ping host" +msgstr "" + +msgid "Restart" +msgstr "" + +msgid "Restart service" +msgstr "" + +msgid "Run service at startup" +msgstr "" + +msgid "Running" +msgstr "" + +msgid "Service" +msgstr "" + +msgid "Service configuration" +msgstr "" + +msgid "Service: detector always runs as a system service" +msgstr "" + +msgid "Shell commands that are executed every time the Internet is checked for availability" +msgstr "" + +msgid "Shell commands that run when connected to the Internet" +msgstr "" + +msgid "Shell commands to run when disconnected from the Internet" +msgstr "" + +msgid "Stopped" +msgstr "" + +msgid "TCP port" +msgstr "" + +msgid "TCP port connection" +msgstr "" + +msgid "UI detector configuration" +msgstr "" + +msgid "Unable to read the contents" +msgstr "" + +msgid "Unable to save the contents" +msgstr "" + +msgid "Undefined" +msgstr "" + +msgid "Web UI only" +msgstr "" + +msgid "Web UI only: detector works only when the Web UI is open (UI detector)" +msgstr "" + +msgid "Write messages to the system log" +msgstr "" + +msgid "down-script" +msgstr "" + +msgid "min" +msgstr "" + +msgid "run-script" +msgstr "" + +msgid "sec" +msgstr "" + +msgid "up-script" +msgstr "" diff --git a/luci-app-internet-detector/root/usr/share/luci/menu.d/luci-app-internet-detector.json b/luci-app-internet-detector/root/usr/share/luci/menu.d/luci-app-internet-detector.json new file mode 100644 index 0000000..9b59524 --- /dev/null +++ b/luci-app-internet-detector/root/usr/share/luci/menu.d/luci-app-internet-detector.json @@ -0,0 +1,16 @@ +{ + "admin/services/internet-detector": { + "title": "Internet detector", + "order": 80, + "action": { + "type": "view", + "path": "internet-detector" + }, + "depends": { + "acl": [ "luci-app-internet-detector" ], + "fs": { + "/usr/bin/internet-detector": "executable" + } + } + } +} diff --git a/luci-app-internet-detector/root/usr/share/rpcd/acl.d/luci-app-internet-detector.json b/luci-app-internet-detector/root/usr/share/rpcd/acl.d/luci-app-internet-detector.json new file mode 100644 index 0000000..f1f486f --- /dev/null +++ b/luci-app-internet-detector/root/usr/share/rpcd/acl.d/luci-app-internet-detector.json @@ -0,0 +1,26 @@ +{ + "luci-app-internet-detector": { + "description": "Grant access to internet-detector procedures", + "read": { + "file": { + "/etc/internet-detector/up-script": [ "read" ], + "/etc/internet-detector/down-script": [ "read" ], + "/etc/internet-detector/run-script": [ "read" ], + "/etc/init.d/internet-detector": [ "exec" ], + "/usr/bin/internet-detector*": [ "exec" ] + }, + "uci": [ "internet-detector" ], + "ubus": { + "luci": [ "setInitAction" ] + } + }, + "write": { + "file": { + "/etc/internet-detector/up-script": [ "write" ], + "/etc/internet-detector/down-script": [ "write" ], + "/etc/internet-detector/run-script": [ "write" ] + }, + "uci": [ "internet-detector" ] + } + } +} diff --git a/packages/19.07/internet-detector_0.3.0-1_all.ipk b/packages/19.07/internet-detector_0.3.0-1_all.ipk new file mode 100644 index 0000000..909d806 Binary files /dev/null and b/packages/19.07/internet-detector_0.3.0-1_all.ipk differ diff --git a/packages/19.07/luci-app-internet-detector_0.2-1_all.ipk b/packages/19.07/luci-app-internet-detector_0.2-1_all.ipk deleted file mode 100644 index cf0b799..0000000 Binary files a/packages/19.07/luci-app-internet-detector_0.2-1_all.ipk and /dev/null differ diff --git a/packages/19.07/luci-app-internet-detector_0.3.0-1_all.ipk b/packages/19.07/luci-app-internet-detector_0.3.0-1_all.ipk new file mode 100644 index 0000000..a5ca034 Binary files /dev/null and b/packages/19.07/luci-app-internet-detector_0.3.0-1_all.ipk differ diff --git a/packages/19.07/luci-i18n-internet-detector-ru_0.2-1_all.ipk b/packages/19.07/luci-i18n-internet-detector-ru_0.2-1_all.ipk deleted file mode 100644 index 247d7d0..0000000 Binary files a/packages/19.07/luci-i18n-internet-detector-ru_0.2-1_all.ipk and /dev/null differ diff --git a/packages/19.07/luci-i18n-internet-detector-ru_0.3.0-1_all.ipk b/packages/19.07/luci-i18n-internet-detector-ru_0.3.0-1_all.ipk new file mode 100644 index 0000000..0eb2719 Binary files /dev/null and b/packages/19.07/luci-i18n-internet-detector-ru_0.3.0-1_all.ipk differ diff --git a/po/ru/internet-detector.po b/po/ru/internet-detector.po deleted file mode 100644 index d8f98d5..0000000 --- a/po/ru/internet-detector.po +++ /dev/null @@ -1,21 +0,0 @@ -msgid "" -msgstr "" -"Content-Type: text/plain; charset=UTF-8\n" -"Project-Id-Version: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ru\n" -"X-Generator: Poedit 2.0.6\n" - -msgid "Internet" -msgstr "Интернет" - -msgid "Internet connected" -msgstr "Интернет подключен" - -msgid "Internet disconnected" -msgstr "Интернет отключен" diff --git a/po/templates/internet-detector.pot b/po/templates/internet-detector.pot deleted file mode 100644 index e8f45f0..0000000 --- a/po/templates/internet-detector.pot +++ /dev/null @@ -1,11 +0,0 @@ -msgid "" -msgstr "Content-Type: text/plain; charset=UTF-8" - -msgid "Internet" -msgstr "" - -msgid "Internet connected" -msgstr "" - -msgid "Internet disconnected" -msgstr "" diff --git a/root/usr/share/rpcd/acl.d/luci-app-internet-detector.json b/root/usr/share/rpcd/acl.d/luci-app-internet-detector.json deleted file mode 100644 index 5f04c4e..0000000 --- a/root/usr/share/rpcd/acl.d/luci-app-internet-detector.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "luci-app-internet-detector": { - "description": "Grant access to internet-detector procedures", - "read": { - "file": { - "/bin/ping -c 1 -W 1 [a-z0-9:.]*": [ "exec" ] - } - } - } -} diff --git a/screenshots/01.jpg b/screenshots/01.jpg index a6768e2..c411d40 100644 Binary files a/screenshots/01.jpg and b/screenshots/01.jpg differ diff --git a/screenshots/02.jpg b/screenshots/02.jpg new file mode 100644 index 0000000..a023073 Binary files /dev/null and b/screenshots/02.jpg differ diff --git a/screenshots/03.jpg b/screenshots/03.jpg new file mode 100644 index 0000000..312101e Binary files /dev/null and b/screenshots/03.jpg differ diff --git a/screenshots/04.jpg b/screenshots/04.jpg new file mode 100644 index 0000000..7f56f84 Binary files /dev/null and b/screenshots/04.jpg differ diff --git a/screenshots/internet-led.jpg b/screenshots/internet-led.jpg new file mode 100644 index 0000000..d24bc37 Binary files /dev/null and b/screenshots/internet-led.jpg differ