v0.3. Internet detector daemon

This commit is contained in:
gSpot
2021-10-31 20:07:17 +03:00
parent 511dea9ee0
commit a008297225
34 changed files with 1917 additions and 143 deletions

214
LICENSE
View File

@@ -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 1. Definitions.
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all "License" shall mean the terms and conditions for use, reproduction,
copies or substantial portions of the Software. 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 "Licensor" shall mean the copyright owner or entity authorized by
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, the copyright owner that is granting the License.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER "Legal Entity" shall mean the union of the acting entity and all
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, other entities that control, are controlled by, or are under common
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE control with that entity. For the purposes of this definition,
SOFTWARE. "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.

View File

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

View File

@@ -1,21 +1,43 @@
# luci-app-internet-detector # Internet detector for OpenWrt.
Internet detector for the LuCI status page (OpenWrt webUI). Checking Internet availability.
OpenWrt >= 19.07. OpenWrt >= 19.07.
Dependences: lua, luci-lib-nixio, libuci-lua
**Installation notes:** **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 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/luci-app-internet-detector_0.2-1_all.ipk opkg install /tmp/internet-detector_0.3.0-1_all.ipk
rm /tmp/luci-app-internet-detector_0.2-1_all.ipk rm /tmp/internet-detector_0.3.0-1_all.ipk
/etc/init.d/rpcd restart
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:** **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 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.2-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.2-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/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)

View File

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

View File

@@ -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 <https://github.com/gSpotx2f/luci-app-internet-detector>
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)))

View File

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

View File

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

View File

@@ -0,0 +1 @@
# Shell commands to run when disconnected from the Internet

View File

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

View File

@@ -0,0 +1 @@
# Shell commands that run when connected to the Internet

View File

@@ -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 [<attempts num>] [<timeout sec>]|--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)

View File

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

View File

@@ -0,0 +1,63 @@
#/bin/sh
#
# Usage: internet-detector-led.sh on|off|state|list [<LED number>|<LED sysfs-name>]
#
# 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 <LED number> 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 [<LED number>|<LED sysfs-name>]" >&2
exit 1
;;
esac
exit 0;

View File

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

View File

@@ -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;<br>%s;<br>%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);
});
},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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 "Интернет отключен"

View File

@@ -1,11 +0,0 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Internet"
msgstr ""
msgid "Internet connected"
msgstr ""
msgid "Internet disconnected"
msgstr ""

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 180 KiB

BIN
screenshots/02.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
screenshots/03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

BIN
screenshots/04.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB